feat(P21PWA1-1,P21PWA1-2,P21PWA1-3): 资产搜索扩展/扫码适配器/IndexedDB离线队列与去重重放

这个提交包含在:
2026-03-11 20:52:11 +08:00
父节点 db8c7e25e0
当前提交 efd798c192
共修改 6 个文件,包含 310 行新增和 0 行删除
+14
查看文件
@@ -28,6 +28,7 @@ const ASSET_LIST_QUERY_SCHEMA = z.object({
assigneeId: UUID_SCHEMA.optional(),
legalEntityId: UUID_SCHEMA.optional(),
costCenterId: UUID_SCHEMA.optional(),
q: z.string().trim().max(120).optional(),
mine: BOOLEAN_QUERY_SCHEMA,
});
@@ -200,6 +201,7 @@ function parseAssetListQuery(query) {
assigneeId: parsed.data.assigneeId,
legalEntityId: parsed.data.legalEntityId,
costCenterId: parsed.data.costCenterId,
q: parsed.data.q?.trim() ?? "",
mine: parseBooleanQueryValue(parsed.data.mine),
};
}
@@ -848,6 +850,18 @@ async function assetsRoutes(fastify) {
if (query.costCenterId) {
where.cost_center_id = query.costCenterId;
}
if (query.q) {
where.OR = [
{ asset_no: query.q },
{ serial_no: query.q },
{
name: {
contains: query.q,
mode: "insensitive",
},
},
];
}
if (mine) {
where.assignee_employee_id = request.employee.id;
} else if (query.assigneeId) {
+12
查看文件
@@ -172,6 +172,16 @@ const DEFAULT_DOCUMENT_CATEGORIES = [
default_workflow_key: null,
enabled: true,
},
{
code: "TR_RECON",
name: "差旅对账批次",
entity_type: "travel_reconciliation_batch",
sequence_prefix: "TRB",
number_length: 4,
reset_period: "yearly",
default_workflow_key: null,
enabled: true,
},
{
code: "ASSET_CARD_DEFAULT",
name: "固定资产卡片",
@@ -616,6 +626,8 @@ async function main() {
// eslint-disable-next-line no-console
console.log("- workflow definition: purchase_request v1 (1-step department_manager)");
// eslint-disable-next-line no-console
console.log("- workflow definition: travel_request v1 (1-step department_manager)");
// eslint-disable-next-line no-console
console.log(`- leave types: ${DEFAULT_LEAVE_TYPES.length} seeded with default policies`);
// eslint-disable-next-line no-console
console.log(`- feature flags: ${DEFAULT_FEATURE_FLAGS.length} seeded`);
+56
查看文件
@@ -48,6 +48,10 @@ function resolveAssetExportEndpoint(config) {
);
}
function resolveTravelExportEndpoint(config) {
return String(config.erpSyncEndpoint ?? '').trim();
}
async function parseResponseBody(response) {
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
@@ -345,6 +349,57 @@ async function exportProcurementToErp(config, payload) {
};
}
async function exportTravelToErp(config, payload) {
if (!config.erpSyncEnabled) {
return {
ok: false,
code: 'erp_sync_disabled',
message: 'ERP sync disabled',
retryable: false,
};
}
const provider = normalizeProvider(config);
if (provider === 'mock') {
const identity = payload?.batchNo ?? payload?.entityId ?? 'UNKNOWN';
return {
ok: true,
code: 'mock_synced',
message: 'mock ERP travel synced',
response: {
externalSummaryId: `ERP-TRAVEL-SUM-${identity}`,
externalSummaryNo: `ERP-TRAVEL-SUM-${identity}`,
externalBatchId: `ERP-TRAVEL-BATCH-${identity}`,
externalBatchNo: `ERP-TRAVEL-BATCH-${identity}`,
},
};
}
if (provider === 'http') {
return postJsonToErp(config, {
endpoint: resolveTravelExportEndpoint(config),
timeoutMs: coerceTimeout(config.erpSyncTimeoutMs, config.erpSyncTimeoutMs),
payload,
successCode: 'http_synced',
successMessage: 'ERP travel synced',
missingEndpointCode: 'erp_travel_endpoint_missing',
missingEndpointMessage: 'ERP travel endpoint missing',
http4xxCode: 'erp_travel_http_4xx',
http5xxCode: 'erp_travel_http_5xx',
timeoutCode: 'erp_travel_timeout',
networkCode: 'erp_travel_network_error',
messagePrefix: 'ERP travel sync',
});
}
return {
ok: false,
code: 'erp_sync_provider_invalid',
message: `Unsupported ERP sync provider: ${provider}`,
retryable: false,
};
}
function buildErpPayload(input) {
return buildReimbursementJournalPayload(input);
}
@@ -358,6 +413,7 @@ module.exports = {
exportAssetToErp,
exportJournalToErp,
exportProcurementToErp,
exportTravelToErp,
normalizeProvider,
pullMasterDataFromErp,
pushPaidReimbursementToErp,