feat: integrate electronic signature verification for document effective action using password and optional PIN
This commit is contained in:
parent
f76bbc6f65
commit
67baf47dbd
@ -4,6 +4,7 @@ import { Layout } from "@/components/layout/Layout";
|
|||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
FormSelect,
|
FormSelect,
|
||||||
|
FormField,
|
||||||
Modal,
|
Modal,
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
SecondaryButton,
|
SecondaryButton,
|
||||||
@ -76,7 +77,8 @@ const ViewDocument = (): ReactElement => {
|
|||||||
>([]);
|
>([]);
|
||||||
const [actionComment, setActionComment] = useState("");
|
const [actionComment, setActionComment] = useState("");
|
||||||
const [effectiveDate, setEffectiveDate] = useState("");
|
const [effectiveDate, setEffectiveDate] = useState("");
|
||||||
const [signatureId, setSignatureId] = useState("");
|
const [actionPassword, setActionPassword] = useState("");
|
||||||
|
const [actionPin, setActionPin] = useState("");
|
||||||
const [showNewVersionForm, setShowNewVersionForm] = useState(false);
|
const [showNewVersionForm, setShowNewVersionForm] = useState(false);
|
||||||
const [newVersionContent, setNewVersionContent] = useState("");
|
const [newVersionContent, setNewVersionContent] = useState("");
|
||||||
const [newVersionContentHtml, setNewVersionContentHtml] = useState("");
|
const [newVersionContentHtml, setNewVersionContentHtml] = useState("");
|
||||||
@ -310,7 +312,8 @@ const ViewDocument = (): ReactElement => {
|
|||||||
setWorkflowDefinitionId("");
|
setWorkflowDefinitionId("");
|
||||||
setActionComment("");
|
setActionComment("");
|
||||||
setEffectiveDate("");
|
setEffectiveDate("");
|
||||||
setSignatureId("");
|
setActionPassword("");
|
||||||
|
setActionPin("");
|
||||||
setWorkflowOptions([]);
|
setWorkflowOptions([]);
|
||||||
setActionErrors({});
|
setActionErrors({});
|
||||||
};
|
};
|
||||||
@ -356,8 +359,13 @@ const ViewDocument = (): ReactElement => {
|
|||||||
if (action === "reject" && !actionComment.trim()) {
|
if (action === "reject" && !actionComment.trim()) {
|
||||||
localErrors.comment = "Reason is required for reject";
|
localErrors.comment = "Reason is required for reject";
|
||||||
}
|
}
|
||||||
if (action === "effective" && !effectiveDate) {
|
if (action === "effective") {
|
||||||
localErrors.effective_date = "Effective date is required";
|
if (!effectiveDate) {
|
||||||
|
localErrors.effective_date = "Effective date is required";
|
||||||
|
}
|
||||||
|
if (!actionPassword.trim()) {
|
||||||
|
localErrors.password = "Password is required for electronic signature";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (action === "obsolete" && !actionComment.trim()) {
|
if (action === "obsolete" && !actionComment.trim()) {
|
||||||
localErrors.comment = "Reason is required to obsolete";
|
localErrors.comment = "Reason is required to obsolete";
|
||||||
@ -381,10 +389,39 @@ const ViewDocument = (): ReactElement => {
|
|||||||
await documentService.reject(id, actionComment.trim());
|
await documentService.reject(id, actionComment.trim());
|
||||||
}
|
}
|
||||||
if (action === "effective") {
|
if (action === "effective") {
|
||||||
|
const currentVersionObj = versions.find(
|
||||||
|
(v) => v.version_number === document?.current_version
|
||||||
|
) || versions[0];
|
||||||
|
const currentVersionId = currentVersionObj?.id;
|
||||||
|
|
||||||
|
if (!currentVersionId) {
|
||||||
|
showToast.error("Current document version could not be resolved.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate electronic signature
|
||||||
|
const signRes = await electronicSignatureService.sign({
|
||||||
|
resource_type: "document_version",
|
||||||
|
resource_id: currentVersionId,
|
||||||
|
resource_name: `${document?.document_number || "Document"} - Version ${document?.current_version || "1.0"} - Make Effective`,
|
||||||
|
action: "RELEASE",
|
||||||
|
meaning: "I authorize the release of this record",
|
||||||
|
password: actionPassword.trim(),
|
||||||
|
pin: actionPin.trim() || null,
|
||||||
|
auth_method: actionPin.trim() ? "password_pin" : "password",
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalSignatureId = signRes.data?.id || signRes.data?.signature?.id;
|
||||||
|
|
||||||
|
if (!finalSignatureId) {
|
||||||
|
showToast.error("Failed to generate electronic signature.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await documentService.makeEffective(
|
await documentService.makeEffective(
|
||||||
id,
|
id,
|
||||||
effectiveDate,
|
effectiveDate,
|
||||||
signatureId.trim(),
|
finalSignatureId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (action === "obsolete") {
|
if (action === "obsolete") {
|
||||||
@ -1782,46 +1819,57 @@ const ViewDocument = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeAction === "effective" && (
|
{activeAction === "effective" && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="space-y-4">
|
||||||
<div>
|
<FormField
|
||||||
<label className="text-[13px] font-medium text-[#0e1b2a] mb-2 block">
|
label="Effective Date"
|
||||||
Effective Date <span className="text-red-500">*</span>
|
required
|
||||||
</label>
|
type="date"
|
||||||
<input
|
value={effectiveDate}
|
||||||
type="date"
|
onChange={(event) => {
|
||||||
value={effectiveDate}
|
setEffectiveDate(event.target.value);
|
||||||
onChange={(event) => {
|
if (event.target.value)
|
||||||
setEffectiveDate(event.target.value);
|
setActionErrors((prev) => ({
|
||||||
if (event.target.value)
|
...prev,
|
||||||
setActionErrors((prev) => ({
|
effective_date: "",
|
||||||
...prev,
|
}));
|
||||||
effective_date: "",
|
}}
|
||||||
}));
|
error={actionErrors.effective_date}
|
||||||
}}
|
/>
|
||||||
className={`h-10 px-3 w-full border rounded-md text-sm transition-colors ${
|
|
||||||
actionErrors.effective_date
|
<div className="border-t border-[rgba(0,0,0,0.08)] pt-3">
|
||||||
? "border-red-500 bg-red-50/30"
|
<p className="text-xs font-semibold text-gray-500 mb-2 uppercase tracking-wider">
|
||||||
: "border-[rgba(0,0,0,0.08)]"
|
Electronic Signature Verification
|
||||||
}`}
|
</p>
|
||||||
/>
|
<p className="text-[11px] text-gray-400 mb-4">
|
||||||
{actionErrors.effective_date && (
|
Enter your credentials below to electronically sign this document and authorize its release.
|
||||||
<p className="text-[11px] text-red-500 mt-1 font-medium italic">
|
</p>
|
||||||
{actionErrors.effective_date}
|
|
||||||
</p>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
)}
|
<FormField
|
||||||
</div>
|
label="Account Password"
|
||||||
<div>
|
required
|
||||||
<label className="text-[13px] font-medium text-[#0e1b2a] mb-2 block">
|
type="password"
|
||||||
Signature ID{" "}
|
value={actionPassword}
|
||||||
<span className="text-gray-400 font-normal">(Optional)</span>
|
onChange={(event) => {
|
||||||
</label>
|
setActionPassword(event.target.value);
|
||||||
<input
|
if (event.target.value) {
|
||||||
type="text"
|
setActionErrors((prev) => ({
|
||||||
value={signatureId}
|
...prev,
|
||||||
onChange={(event) => setSignatureId(event.target.value)}
|
password: "",
|
||||||
placeholder="Enter signature ID"
|
}));
|
||||||
className="h-10 px-3 w-full border border-[rgba(0,0,0,0.08)] rounded-md text-sm"
|
}
|
||||||
/>
|
}}
|
||||||
|
placeholder="Enter password"
|
||||||
|
error={actionErrors.password}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
label="Signature PIN"
|
||||||
|
type="password"
|
||||||
|
value={actionPin}
|
||||||
|
onChange={(event) => setActionPin(event.target.value)}
|
||||||
|
placeholder="Enter PIN (Optional)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user