实现业务附件受控入库 V1(05-28-business-attachment-governance-v1)

这个提交包含在:
2026-05-28 19:44:15 +08:00
父节点 2c067866ef
当前提交 52e5284954
共修改 25 个文件,包含 1119 行新增和 21 行删除
@@ -0,0 +1,143 @@
const GOVERNANCE_STATUS = Object.freeze({
draft: "draft",
underApproval: "under_approval",
pendingReview: "pending_review",
approved: "approved",
archived: "archived",
returned: "returned",
rejected: "rejected",
quarantined: "quarantined",
});
const SENSITIVITY_LEVEL = Object.freeze({
public: "public",
internal: "internal",
confidential: "confidential",
restricted: "restricted",
indexOnly: "index_only",
});
const ATTACHMENT_TYPE = Object.freeze({
contractDraft: "contract_draft",
contractStamped: "contract_stamped",
invoice: "invoice",
receipt: "receipt",
quote: "quote",
acceptance: "acceptance",
delivery: "delivery",
policyDocument: "policy_document",
supportingMaterial: "supporting_material",
other: "other",
});
const DOWNLOAD_POLICY = Object.freeze({
standard: "standard",
reasonRequired: "reason_required",
restricted: "restricted",
});
const GOVERNANCE_STATUS_VALUES = new Set(Object.values(GOVERNANCE_STATUS));
const SENSITIVITY_LEVEL_VALUES = new Set(Object.values(SENSITIVITY_LEVEL));
const ATTACHMENT_TYPE_VALUES = new Set(Object.values(ATTACHMENT_TYPE));
const DOWNLOAD_POLICY_VALUES = new Set(Object.values(DOWNLOAD_POLICY));
const DEFAULT_GOVERNANCE = Object.freeze({
governanceStatus: GOVERNANCE_STATUS.approved,
sensitivityLevel: SENSITIVITY_LEVEL.internal,
attachmentType: ATTACHMENT_TYPE.other,
downloadPolicy: DOWNLOAD_POLICY.standard,
});
function normalizeEnumValue(value, allowedValues, fallback) {
if (typeof value !== "string") {
return fallback;
}
const normalized = value.trim();
return allowedValues.has(normalized) ? normalized : fallback;
}
function normalizeGovernanceInput(input = {}, defaults = DEFAULT_GOVERNANCE) {
return {
governanceStatus: normalizeEnumValue(input.governanceStatus ?? input.governance_status, GOVERNANCE_STATUS_VALUES, defaults.governanceStatus),
sensitivityLevel: normalizeEnumValue(input.sensitivityLevel ?? input.sensitivity_level, SENSITIVITY_LEVEL_VALUES, defaults.sensitivityLevel),
attachmentType: normalizeEnumValue(input.attachmentType ?? input.attachment_type, ATTACHMENT_TYPE_VALUES, defaults.attachmentType),
downloadPolicy: normalizeEnumValue(input.downloadPolicy ?? input.download_policy, DOWNLOAD_POLICY_VALUES, defaults.downloadPolicy),
};
}
function governanceCreateData(input = {}, defaults = DEFAULT_GOVERNANCE) {
const normalized = normalizeGovernanceInput(input, defaults);
return {
governance_status: normalized.governanceStatus,
sensitivity_level: normalized.sensitivityLevel,
attachment_type: normalized.attachmentType,
download_policy: normalized.downloadPolicy,
};
}
function mapGovernanceFields(item = {}, defaults = DEFAULT_GOVERNANCE) {
const normalized = normalizeGovernanceInput({
governance_status: item.governance_status,
sensitivity_level: item.sensitivity_level,
attachment_type: item.attachment_type,
download_policy: item.download_policy,
}, defaults);
return {
governanceStatus: normalized.governanceStatus,
sensitivityLevel: normalized.sensitivityLevel,
attachmentType: normalized.attachmentType,
downloadPolicy: normalized.downloadPolicy,
reviewedBy: item.reviewed_by ?? null,
reviewedAt: item.reviewed_at?.toISOString?.() ?? null,
reviewNote: item.review_note ?? null,
archivedBy: item.archived_by ?? null,
archivedAt: item.archived_at?.toISOString?.() ?? null,
archiveNote: item.archive_note ?? null,
};
}
function parseMultipartGovernanceField(part, current = {}) {
if (!part || part.type !== "field") {
return current;
}
const fieldName = part.fieldname;
if (!["attachmentType", "sensitivityLevel", "governanceStatus", "downloadPolicy"].includes(fieldName)) {
return current;
}
return {
...current,
[fieldName]: typeof part.value === "string" ? part.value : "",
};
}
function requiresDownloadReason(attachment = {}) {
const governance = normalizeGovernanceInput(attachment);
return (
governance.downloadPolicy === DOWNLOAD_POLICY.reasonRequired ||
governance.downloadPolicy === DOWNLOAD_POLICY.restricted ||
governance.sensitivityLevel === SENSITIVITY_LEVEL.confidential ||
governance.sensitivityLevel === SENSITIVITY_LEVEL.restricted
);
}
function sanitizeDownloadReason(reason) {
if (typeof reason !== "string") {
return null;
}
const trimmed = reason.trim();
return trimmed ? trimmed.slice(0, 200) : null;
}
module.exports = {
ATTACHMENT_TYPE,
DEFAULT_GOVERNANCE,
DOWNLOAD_POLICY,
GOVERNANCE_STATUS,
SENSITIVITY_LEVEL,
governanceCreateData,
mapGovernanceFields,
normalizeGovernanceInput,
parseMultipartGovernanceField,
requiresDownloadReason,
sanitizeDownloadReason,
};