705 行
20 KiB
JavaScript
705 行
20 KiB
JavaScript
const crypto = require('node:crypto');
|
|
|
|
const { enqueueIntegrationEvent } = require('./outbox');
|
|
|
|
const PROCUREMENT_TRACK_STATUSES = Object.freeze({
|
|
notTriggered: 'not_triggered',
|
|
skipped: 'skipped',
|
|
});
|
|
|
|
const PROCUREMENT_ERP_TARGETS = Object.freeze({
|
|
commitmentJournal: 'commitment_journal',
|
|
accrualJournal: 'accrual_journal',
|
|
archiveSummary: 'archive_summary',
|
|
});
|
|
|
|
function normalizeString(value) {
|
|
const text = String(value ?? '').trim();
|
|
return text || null;
|
|
}
|
|
|
|
function toIsoString(value) {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return new Date(value).toISOString();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function normalizeMoney(value) {
|
|
if (value == null) {
|
|
return '0.00';
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
const trimmed = value.trim();
|
|
return trimmed || '0.00';
|
|
}
|
|
|
|
if (typeof value?.toString === 'function') {
|
|
return value.toString();
|
|
}
|
|
|
|
return '0.00';
|
|
}
|
|
|
|
function resolveCurrencyCode(purchaseRequest) {
|
|
return normalizeString(purchaseRequest?.currency_code) ?? 'CNY';
|
|
}
|
|
|
|
function resolveLegalEntityCode(purchaseRequest) {
|
|
return normalizeString(purchaseRequest?.legal_entity?.code);
|
|
}
|
|
|
|
function resolveCostCenterCode(purchaseRequest) {
|
|
return normalizeString(purchaseRequest?.cost_center?.code);
|
|
}
|
|
|
|
function resolveSupplierCode(purchaseRequest) {
|
|
return normalizeString(purchaseRequest?.supplier?.code);
|
|
}
|
|
|
|
function resolveBudgetPoolCode(purchaseRequest) {
|
|
return normalizeString(purchaseRequest?.budget_pool?.code);
|
|
}
|
|
|
|
function resolveDocumentCategoryCode(purchaseRequest) {
|
|
return normalizeString(purchaseRequest?.document_category?.code);
|
|
}
|
|
|
|
function stableNormalizeJson(value) {
|
|
if (Array.isArray(value)) {
|
|
return value.map((item) => stableNormalizeJson(item));
|
|
}
|
|
|
|
if (value && typeof value === 'object' && value.constructor === Object) {
|
|
return Object.keys(value)
|
|
.sort()
|
|
.reduce((result, key) => {
|
|
result[key] = stableNormalizeJson(value[key]);
|
|
return result;
|
|
}, {});
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function stableStringifyJson(value) {
|
|
return JSON.stringify(stableNormalizeJson(value));
|
|
}
|
|
|
|
function hashProcurementPayload(value) {
|
|
return crypto.createHash('sha256').update(stableStringifyJson(value)).digest('hex');
|
|
}
|
|
|
|
function buildPurchaseCommitmentJournalPayload(input) {
|
|
const purchaseRequest = input?.purchaseRequest ?? {};
|
|
|
|
return {
|
|
journalType: 'purchase_request_commitment',
|
|
sourceEventName: 'purchase_request.approved',
|
|
entityType: 'purchase_request',
|
|
entityId: purchaseRequest.id ?? null,
|
|
documentNo: purchaseRequest.request_no ?? null,
|
|
currencyCode: resolveCurrencyCode(purchaseRequest),
|
|
totalAmount: normalizeMoney(purchaseRequest.total_amount),
|
|
occurredAt:
|
|
toIsoString(input?.occurredAt) ??
|
|
toIsoString(purchaseRequest.updated_at) ??
|
|
toIsoString(purchaseRequest.created_at),
|
|
dimensions: {
|
|
departmentId: purchaseRequest.department_id ?? null,
|
|
applicantEmployeeId: purchaseRequest.applicant_id ?? null,
|
|
submitterEmployeeId: purchaseRequest.submitter_id ?? null,
|
|
legalEntityCode: resolveLegalEntityCode(purchaseRequest),
|
|
costCenterCode: resolveCostCenterCode(purchaseRequest),
|
|
supplierCode: resolveSupplierCode(purchaseRequest),
|
|
budgetPoolCode: resolveBudgetPoolCode(purchaseRequest),
|
|
},
|
|
references: {
|
|
purchaseRequestId: purchaseRequest.id ?? null,
|
|
documentCategoryCode: resolveDocumentCategoryCode(purchaseRequest),
|
|
requestNo: purchaseRequest.request_no ?? null,
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildPurchaseAccrualJournalPayload(input) {
|
|
const purchaseRequest = input?.purchaseRequest ?? {};
|
|
const archive = input?.archive ?? purchaseRequest.order_archive ?? {};
|
|
|
|
return {
|
|
journalType: 'purchase_order_accrual',
|
|
sourceEventName: 'purchase_order.archived',
|
|
entityType: 'purchase_order_archive',
|
|
entityId: archive.id ?? null,
|
|
documentNo: archive.po_no ?? null,
|
|
currencyCode: resolveCurrencyCode(purchaseRequest),
|
|
totalAmount: normalizeMoney(purchaseRequest.total_amount),
|
|
occurredAt:
|
|
toIsoString(input?.occurredAt) ??
|
|
toIsoString(archive.archived_at) ??
|
|
toIsoString(purchaseRequest.updated_at) ??
|
|
toIsoString(purchaseRequest.created_at),
|
|
dimensions: {
|
|
departmentId: purchaseRequest.department_id ?? null,
|
|
applicantEmployeeId: purchaseRequest.applicant_id ?? null,
|
|
submitterEmployeeId: purchaseRequest.submitter_id ?? null,
|
|
legalEntityCode: resolveLegalEntityCode(purchaseRequest),
|
|
costCenterCode: resolveCostCenterCode(purchaseRequest),
|
|
supplierCode: resolveSupplierCode(purchaseRequest),
|
|
budgetPoolCode: resolveBudgetPoolCode(purchaseRequest),
|
|
},
|
|
references: {
|
|
purchaseRequestId: purchaseRequest.id ?? null,
|
|
purchaseOrderArchiveId: archive.id ?? null,
|
|
requestNo: purchaseRequest.request_no ?? null,
|
|
poNo: archive.po_no ?? null,
|
|
contractNo: archive.contract_no ?? null,
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildPurchaseArchiveSummaryPayload(input) {
|
|
const purchaseRequest = input?.purchaseRequest ?? {};
|
|
const archive = input?.archive ?? purchaseRequest.order_archive ?? {};
|
|
const archivedFiles = Array.isArray(archive.archived_files)
|
|
? archive.archived_files.map((file) => ({
|
|
storagePath: file?.storagePath ?? null,
|
|
originalName: file?.originalName ?? null,
|
|
mimeType: file?.mimeType ?? null,
|
|
fileSize: Number(file?.fileSize ?? 0),
|
|
}))
|
|
: [];
|
|
|
|
return {
|
|
exportType: 'purchase_order_archive',
|
|
sourceEventName: 'purchase_order.archived',
|
|
entityType: 'purchase_order_archive',
|
|
entityId: archive.id ?? null,
|
|
purchaseRequestId: purchaseRequest.id ?? null,
|
|
requestNo: purchaseRequest.request_no ?? null,
|
|
poNo: archive.po_no ?? null,
|
|
contractNo: archive.contract_no ?? null,
|
|
currencyCode: resolveCurrencyCode(purchaseRequest),
|
|
totalAmount: normalizeMoney(purchaseRequest.total_amount),
|
|
archivedAt:
|
|
toIsoString(input?.archivedAt) ??
|
|
toIsoString(archive.archived_at) ??
|
|
toIsoString(purchaseRequest.updated_at) ??
|
|
toIsoString(purchaseRequest.created_at),
|
|
supplier: purchaseRequest.supplier
|
|
? {
|
|
id: purchaseRequest.supplier.id,
|
|
code: purchaseRequest.supplier.code ?? null,
|
|
name: purchaseRequest.supplier.name ?? null,
|
|
}
|
|
: {
|
|
id: purchaseRequest.supplier_id ?? null,
|
|
code: null,
|
|
name: null,
|
|
},
|
|
dimensions: {
|
|
legalEntityCode: resolveLegalEntityCode(purchaseRequest),
|
|
costCenterCode: resolveCostCenterCode(purchaseRequest),
|
|
departmentId: purchaseRequest.department_id ?? null,
|
|
},
|
|
files: archivedFiles,
|
|
};
|
|
}
|
|
|
|
function buildPurchaseCommitmentJournalDedupeKey(purchaseRequestId) {
|
|
return `purchase_request:${purchaseRequestId}:commitment`;
|
|
}
|
|
|
|
function buildPurchaseAccrualJournalDedupeKey(archiveId) {
|
|
return `purchase_order_archive:${archiveId}:accrual`;
|
|
}
|
|
|
|
function buildPurchaseArchiveSummaryDedupeKey(archiveId) {
|
|
return `purchase_order_archive:${archiveId}:summary`;
|
|
}
|
|
|
|
function mapIntegrationBinding(record) {
|
|
if (!record) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: record.id,
|
|
provider: record.provider,
|
|
entityType: record.entity_type,
|
|
localId: record.local_id,
|
|
externalId: record.external_id ?? null,
|
|
externalCode: record.external_code ?? null,
|
|
lastSyncedAt: record.last_synced_at?.toISOString?.() ?? null,
|
|
};
|
|
}
|
|
|
|
function redactBinding(binding, canManage) {
|
|
if (!binding) {
|
|
return null;
|
|
}
|
|
|
|
if (canManage) {
|
|
return binding;
|
|
}
|
|
|
|
return {
|
|
...binding,
|
|
externalId: null,
|
|
externalCode: null,
|
|
};
|
|
}
|
|
|
|
function mapIntegrationOutboxState(record) {
|
|
if (!record) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: record.id,
|
|
eventType: record.event_type,
|
|
status: record.status,
|
|
attempts: Number(record.attempts ?? 0),
|
|
lastError: record.last_error ?? null,
|
|
processedAt: record.processed_at?.toISOString?.() ?? null,
|
|
updatedAt: record.updated_at?.toISOString?.() ?? null,
|
|
};
|
|
}
|
|
|
|
async function findOutboxByKey(prisma, eventType, dedupeKey) {
|
|
if (!prisma || !eventType || !dedupeKey) {
|
|
return null;
|
|
}
|
|
|
|
return prisma.integrationOutbox.findUnique({
|
|
where: {
|
|
event_type_dedupe_key: {
|
|
event_type: eventType,
|
|
dedupe_key: dedupeKey,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async function findBindingByLocalId(prisma, entityType, localId) {
|
|
if (!prisma || !entityType || !localId) {
|
|
return null;
|
|
}
|
|
|
|
return prisma.integrationBinding.findUnique({
|
|
where: {
|
|
provider_entity_type_local_id: {
|
|
provider: 'erp',
|
|
entity_type: entityType,
|
|
local_id: localId,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async function enqueuePurchaseCommitmentJournalExport(prisma, config, input) {
|
|
const payload =
|
|
input?.payload ??
|
|
buildPurchaseCommitmentJournalPayload({
|
|
purchaseRequest: input?.purchaseRequest,
|
|
occurredAt: input?.occurredAt,
|
|
});
|
|
|
|
const enqueued = await enqueueIntegrationEvent(prisma, {
|
|
eventType: 'erp.journal.export',
|
|
payload,
|
|
dedupeKey: buildPurchaseCommitmentJournalDedupeKey(payload.entityId),
|
|
maxAttempts: config.integrationOutboxMaxAttempts,
|
|
actorId: input?.actorId ?? null,
|
|
requeueOnFailure: input?.requeueOnFailure === true,
|
|
});
|
|
|
|
return {
|
|
...enqueued,
|
|
eventType: 'erp.journal.export',
|
|
erpTarget: PROCUREMENT_ERP_TARGETS.commitmentJournal,
|
|
payload,
|
|
};
|
|
}
|
|
|
|
async function enqueuePurchaseAccrualJournalExport(prisma, config, input) {
|
|
const payload =
|
|
input?.payload ??
|
|
buildPurchaseAccrualJournalPayload({
|
|
purchaseRequest: input?.purchaseRequest,
|
|
archive: input?.archive,
|
|
occurredAt: input?.occurredAt,
|
|
});
|
|
|
|
const enqueued = await enqueueIntegrationEvent(prisma, {
|
|
eventType: 'erp.journal.export',
|
|
payload,
|
|
dedupeKey: buildPurchaseAccrualJournalDedupeKey(payload.entityId),
|
|
maxAttempts: config.integrationOutboxMaxAttempts,
|
|
actorId: input?.actorId ?? null,
|
|
requeueOnFailure: input?.requeueOnFailure === true,
|
|
});
|
|
|
|
return {
|
|
...enqueued,
|
|
eventType: 'erp.journal.export',
|
|
erpTarget: PROCUREMENT_ERP_TARGETS.accrualJournal,
|
|
payload,
|
|
};
|
|
}
|
|
|
|
async function enqueuePurchaseArchiveSummaryExport(prisma, config, input) {
|
|
const payload =
|
|
input?.payload ??
|
|
buildPurchaseArchiveSummaryPayload({
|
|
purchaseRequest: input?.purchaseRequest,
|
|
archive: input?.archive,
|
|
archivedAt: input?.archivedAt,
|
|
});
|
|
|
|
const enqueued = await enqueueIntegrationEvent(prisma, {
|
|
eventType: 'erp.procurement.export',
|
|
payload,
|
|
dedupeKey: buildPurchaseArchiveSummaryDedupeKey(payload.entityId),
|
|
maxAttempts: config.integrationOutboxMaxAttempts,
|
|
actorId: input?.actorId ?? null,
|
|
requeueOnFailure: input?.requeueOnFailure === true,
|
|
resetOnPayloadChange: true,
|
|
});
|
|
|
|
return {
|
|
...enqueued,
|
|
eventType: 'erp.procurement.export',
|
|
erpTarget: PROCUREMENT_ERP_TARGETS.archiveSummary,
|
|
payload,
|
|
};
|
|
}
|
|
|
|
async function loadPurchaseRequestErpContext(prisma, purchaseRequestId) {
|
|
const purchaseRequest = await prisma.purchaseRequest.findUnique({
|
|
where: { id: purchaseRequestId },
|
|
select: {
|
|
id: true,
|
|
request_no: true,
|
|
applicant_id: true,
|
|
submitter_id: true,
|
|
department_id: true,
|
|
legal_entity_id: true,
|
|
cost_center_id: true,
|
|
supplier_id: true,
|
|
document_category_id: true,
|
|
budget_pool_id: true,
|
|
status: true,
|
|
currency_code: true,
|
|
total_amount: true,
|
|
created_at: true,
|
|
updated_at: true,
|
|
applicant: {
|
|
select: {
|
|
id: true,
|
|
employee_no: true,
|
|
name: true,
|
|
},
|
|
},
|
|
submitter: {
|
|
select: {
|
|
id: true,
|
|
employee_no: true,
|
|
name: true,
|
|
},
|
|
},
|
|
legal_entity: {
|
|
select: {
|
|
id: true,
|
|
code: true,
|
|
name: true,
|
|
},
|
|
},
|
|
cost_center: {
|
|
select: {
|
|
id: true,
|
|
code: true,
|
|
name: true,
|
|
},
|
|
},
|
|
supplier: {
|
|
select: {
|
|
id: true,
|
|
code: true,
|
|
name: true,
|
|
},
|
|
},
|
|
document_category: {
|
|
select: {
|
|
id: true,
|
|
code: true,
|
|
name: true,
|
|
},
|
|
},
|
|
budget_pool: {
|
|
select: {
|
|
id: true,
|
|
code: true,
|
|
name: true,
|
|
},
|
|
},
|
|
order_archive: {
|
|
select: {
|
|
id: true,
|
|
po_no: true,
|
|
contract_no: true,
|
|
archived_files: true,
|
|
archived_at: true,
|
|
archived_by: true,
|
|
},
|
|
},
|
|
workflow_instances: {
|
|
where: { entity_type: 'purchase_request' },
|
|
orderBy: { created_at: 'desc' },
|
|
take: 1,
|
|
select: { id: true },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!purchaseRequest) {
|
|
return null;
|
|
}
|
|
|
|
const workflowInstanceId = purchaseRequest.workflow_instances?.[0]?.id ?? null;
|
|
const approvalHistory = workflowInstanceId
|
|
? await prisma.workflowHistory.findFirst({
|
|
where: {
|
|
instance_id: workflowInstanceId,
|
|
action: 'approve',
|
|
},
|
|
orderBy: { created_at: 'desc' },
|
|
select: { created_at: true },
|
|
})
|
|
: null;
|
|
|
|
return {
|
|
purchaseRequest,
|
|
approvalOccurredAt:
|
|
approvalHistory?.created_at ?? purchaseRequest.updated_at ?? purchaseRequest.created_at ?? null,
|
|
};
|
|
}
|
|
|
|
function hasAnyBinding(binding) {
|
|
if (!binding) {
|
|
return false;
|
|
}
|
|
|
|
return Boolean(binding.externalId || binding.externalCode || binding.lastSyncedAt);
|
|
}
|
|
|
|
function resolveTrackStageStatus({ eligible, outbox, binding, bindings, gateEnabled }) {
|
|
if (outbox?.status) {
|
|
return outbox.status;
|
|
}
|
|
|
|
if (binding && hasAnyBinding(binding)) {
|
|
return 'succeeded';
|
|
}
|
|
|
|
if (bindings && Object.values(bindings).some((item) => hasAnyBinding(item))) {
|
|
return 'succeeded';
|
|
}
|
|
|
|
if (!eligible) {
|
|
return PROCUREMENT_TRACK_STATUSES.notTriggered;
|
|
}
|
|
|
|
if (!gateEnabled) {
|
|
return PROCUREMENT_TRACK_STATUSES.skipped;
|
|
}
|
|
|
|
return PROCUREMENT_TRACK_STATUSES.notTriggered;
|
|
}
|
|
|
|
function buildPurchaseTrackSummary(context) {
|
|
const purchaseRequest = context?.purchaseRequest ?? {};
|
|
const archive = purchaseRequest.order_archive ?? null;
|
|
|
|
return {
|
|
id: purchaseRequest.id,
|
|
requestNo: purchaseRequest.request_no ?? null,
|
|
status: purchaseRequest.status,
|
|
totalAmount: normalizeMoney(purchaseRequest.total_amount),
|
|
supplier: purchaseRequest.supplier
|
|
? {
|
|
id: purchaseRequest.supplier.id,
|
|
code: purchaseRequest.supplier.code ?? null,
|
|
name: purchaseRequest.supplier.name ?? null,
|
|
}
|
|
: null,
|
|
archiveOrder: archive
|
|
? {
|
|
id: archive.id,
|
|
poNo: archive.po_no,
|
|
contractNo: archive.contract_no ?? null,
|
|
archivedAt: toIsoString(archive.archived_at),
|
|
}
|
|
: null,
|
|
};
|
|
}
|
|
|
|
async function buildPurchaseRequestErpTrack(prisma, config, purchaseRequestId, options = {}) {
|
|
const context = options.context ?? (await loadPurchaseRequestErpContext(prisma, purchaseRequestId));
|
|
if (!context) {
|
|
return null;
|
|
}
|
|
|
|
const purchaseRequest = context.purchaseRequest;
|
|
const archive = purchaseRequest.order_archive ?? null;
|
|
const canManage = Boolean(options.canManage);
|
|
const gateEnabled = Boolean(options.gate?.effectiveEnabled ?? options.gate?.enabled ?? true);
|
|
|
|
const [
|
|
commitmentOutboxRow,
|
|
accrualOutboxRow,
|
|
summaryOutboxRow,
|
|
commitmentBindingRow,
|
|
accrualBindingRow,
|
|
poBindingRow,
|
|
contractBindingRow,
|
|
] = await Promise.all([
|
|
findOutboxByKey(
|
|
prisma,
|
|
'erp.journal.export',
|
|
buildPurchaseCommitmentJournalDedupeKey(purchaseRequest.id),
|
|
),
|
|
archive
|
|
? findOutboxByKey(
|
|
prisma,
|
|
'erp.journal.export',
|
|
buildPurchaseAccrualJournalDedupeKey(archive.id),
|
|
)
|
|
: Promise.resolve(null),
|
|
archive
|
|
? findOutboxByKey(
|
|
prisma,
|
|
'erp.procurement.export',
|
|
buildPurchaseArchiveSummaryDedupeKey(archive.id),
|
|
)
|
|
: Promise.resolve(null),
|
|
findBindingByLocalId(prisma, 'purchase_request_commitment_journal', purchaseRequest.id),
|
|
archive
|
|
? findBindingByLocalId(prisma, 'purchase_order_accrual_journal', archive.id)
|
|
: Promise.resolve(null),
|
|
archive ? findBindingByLocalId(prisma, 'purchase_order_po', archive.id) : Promise.resolve(null),
|
|
archive
|
|
? findBindingByLocalId(prisma, 'purchase_order_contract', archive.id)
|
|
: Promise.resolve(null),
|
|
]);
|
|
|
|
const commitmentPreview = buildPurchaseCommitmentJournalPayload({
|
|
purchaseRequest,
|
|
occurredAt: context.approvalOccurredAt,
|
|
});
|
|
const accrualPreview = archive
|
|
? buildPurchaseAccrualJournalPayload({
|
|
purchaseRequest,
|
|
archive,
|
|
occurredAt: archive.archived_at,
|
|
})
|
|
: {};
|
|
const summaryPreview = archive
|
|
? buildPurchaseArchiveSummaryPayload({
|
|
purchaseRequest,
|
|
archive,
|
|
archivedAt: archive.archived_at,
|
|
})
|
|
: {};
|
|
|
|
const commitmentBinding = redactBinding(mapIntegrationBinding(commitmentBindingRow), canManage);
|
|
const accrualBinding = redactBinding(mapIntegrationBinding(accrualBindingRow), canManage);
|
|
const poBinding = redactBinding(mapIntegrationBinding(poBindingRow), canManage);
|
|
const contractBinding = redactBinding(mapIntegrationBinding(contractBindingRow), canManage);
|
|
|
|
const commitmentOutbox = mapIntegrationOutboxState(commitmentOutboxRow);
|
|
const accrualOutbox = mapIntegrationOutboxState(accrualOutboxRow);
|
|
const summaryOutbox = mapIntegrationOutboxState(summaryOutboxRow);
|
|
|
|
const commitmentEligible = purchaseRequest.status === 'approved';
|
|
const archiveEligible = Boolean(archive);
|
|
|
|
return {
|
|
purchaseRequest: buildPurchaseTrackSummary(context),
|
|
stages: {
|
|
commitmentJournal: {
|
|
eligible: commitmentEligible,
|
|
reason: commitmentEligible ? null : 'purchase_request_not_approved',
|
|
status: resolveTrackStageStatus({
|
|
eligible: commitmentEligible,
|
|
outbox: commitmentOutbox,
|
|
binding: commitmentBinding,
|
|
gateEnabled,
|
|
}),
|
|
preview: commitmentPreview,
|
|
outbox: commitmentOutbox,
|
|
binding: commitmentBinding,
|
|
},
|
|
accrualJournal: {
|
|
eligible: archiveEligible,
|
|
reason: archiveEligible ? null : 'purchase_order_archive_missing',
|
|
status: resolveTrackStageStatus({
|
|
eligible: archiveEligible,
|
|
outbox: accrualOutbox,
|
|
binding: accrualBinding,
|
|
gateEnabled,
|
|
}),
|
|
preview: accrualPreview,
|
|
outbox: accrualOutbox,
|
|
binding: accrualBinding,
|
|
},
|
|
archiveSummary: {
|
|
eligible: archiveEligible,
|
|
reason: archiveEligible ? null : 'purchase_order_archive_missing',
|
|
status: resolveTrackStageStatus({
|
|
eligible: archiveEligible,
|
|
outbox: summaryOutbox,
|
|
bindings: {
|
|
po: poBinding,
|
|
contract: contractBinding,
|
|
},
|
|
gateEnabled,
|
|
}),
|
|
preview: summaryPreview,
|
|
outbox: summaryOutbox,
|
|
bindings: {
|
|
po: poBinding,
|
|
contract: contractBinding,
|
|
},
|
|
},
|
|
},
|
|
permissions: {
|
|
canRetry: Boolean(canManage && gateEnabled),
|
|
},
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
PROCUREMENT_ERP_TARGETS,
|
|
PROCUREMENT_TRACK_STATUSES,
|
|
buildPurchaseAccrualJournalDedupeKey,
|
|
buildPurchaseAccrualJournalPayload,
|
|
buildPurchaseArchiveSummaryDedupeKey,
|
|
buildPurchaseArchiveSummaryPayload,
|
|
buildPurchaseCommitmentJournalDedupeKey,
|
|
buildPurchaseCommitmentJournalPayload,
|
|
buildPurchaseRequestErpTrack,
|
|
enqueuePurchaseAccrualJournalExport,
|
|
enqueuePurchaseArchiveSummaryExport,
|
|
enqueuePurchaseCommitmentJournalExport,
|
|
hashProcurementPayload,
|
|
loadPurchaseRequestErpContext,
|
|
stableStringifyJson,
|
|
};
|