feat(appointment): integrate work calendar and stable cargo batches

This commit is contained in:
2026-10-15 16:55:57 +08:00
parent dbb93a06c2
commit a1d5ebdab2
171 changed files with 7898 additions and 396 deletions
@@ -3,6 +3,7 @@
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { pathToFileURL } from 'node:url';
const moduleRoot = path.resolve(import.meta.dirname, '..');
const sourceRoots = {
@@ -15,8 +16,8 @@ const sliceRules = [
['customer-statistics', /(?:^|\.)(?:customerstatistics)(?:\.|$)/],
['pricebook-query', /(?:^|\.)(?:pricebookquery)(?:\.|$)/],
['finance-integration', /(?:InternalFinance|FinanceFeeSource|FinanceCostSnapshot|FeePaymentVoucher|FeePaymentSnapshot|ContainerFeeSourceSnapshot)/],
['shipping-nodes', /(?:CarrierScheduleQuery|CarrierScheduleNode|CarrierScheduleAdapter|CarrierScheduleAttempt|CarrierScheduleException)/],
['booking-customs-trucking', /(?:^|\.)(?:oceanbooking|customs|terminaldrayage)(?:\.|$)|(?:TruckAppointment|WarehouseSupplier|PortSchedule|DispatchGate)/],
['shipping-nodes', /(?:CarrierScheduleQuery|CarrierScheduleNode|CarrierScheduleAdapter|CarrierScheduleAttempt|CarrierScheduleException|ContainerShippingNode)/],
['booking-customs-trucking', /(?:^|\.)(?:oceanbooking|customs|terminaldrayage|sailingcalendar)(?:\.|$)|(?:TruckAppointment|WarehouseSupplier|PortSchedule|DispatchGate)/],
['outbound', /(?:^|\.)outbound(?:\.|$)|(?:OutboundDispatch|ContainerTransport|OverseasContainerEvent)/],
['split-loading', /(?:^|\.)(?:split|loading)(?:\.|$)|(?:Loading|SmartLoading|Container(?:Booking|Overview|Service|Dtos)|PdaPick)/],
['measurement-pricing', /(?:^|\.)pricing(?:\.|$)|(?:Measurement|MeasureReview|Mojia|Pricing|PriceBook|PriceChannel|PriceOrigin|PriceWarehouse|Fba|Dimension|FeeItem|MerchantInboundBilling|InboundFee)/],
@@ -261,14 +262,28 @@ function renderDot(report) {
return lines.join('\n');
}
function checkContract(report, contractPath) {
const absolute = path.resolve(process.cwd(), contractPath);
const contract = JSON.parse(fs.readFileSync(absolute, 'utf8'));
export function contractViolations(report, contract) {
const allowed = new Set(contract.allowedSliceEdges ?? []);
const actual = report.sliceEdges.map(edge => `${edge.source}->${edge.target}`);
const additions = actual.filter(edge => !allowed.has(edge));
const violations = additions.map(edge => `new slice dependency: ${edge}`);
const violations = [];
const cycles=new Set((contract.reviewedPackageCycles ?? []).filter(c=>c.reason?.trim()
&& c.packages?.length>1 && c.packages.every(p=>!p.includes('*'))).map(c=>[...c.packages].sort().join('|')));
for (const cycle of report.packageSccs ?? []) {
if (!cycles.has([...cycle].sort().join('|'))) violations.push(`unreviewed package cycle: ${cycle.join(' -> ')}`);
}
const reviewed = new Set();
for (const edge of contract.reviewedClassDependencies ?? []) {
if (!edge.source || !edge.target || !edge.reason?.trim() || /[*]/.test(edge.source + edge.target)) {
violations.push('reviewed dependency requires exact classes and a rationale');
continue;
}
reviewed.add(`${edge.source}->${edge.target}`);
}
const classesByName = new Map(report.classes.map(entry => [entry.fqcn, entry]));
for (const edge of report.classDependencies) {
const from=classesByName.get(edge.source)?.slice, to=classesByName.get(edge.target)?.slice;
if (from && to && from!==to && !allowed.has(`${from}->${to}`) && !reviewed.has(`${edge.source}->${edge.target}`))
violations.push(`unreviewed slice dependency: ${from}->${to}: ${edge.source}->${edge.target}`);
}
for (const [fqcn, expectedFile] of Object.entries(contract.requiredClassLocations ?? {})) {
const actualClass = classesByName.get(fqcn);
if (!actualClass) violations.push(`required class missing: ${fqcn}`);
@@ -312,12 +327,19 @@ function checkContract(report, contractPath) {
violations.push(`${slice} incoming dependencies changed: ${incoming.join(', ')}`);
}
}
return violations;
}
function checkContract(report, contractPath) {
const contract=JSON.parse(fs.readFileSync(path.resolve(process.cwd(),contractPath),'utf8'));
const violations=contractViolations(report,contract);
if (violations.length > 0) {
process.stderr.write(`Firstleg dependency contract violations in ${contractPath}:\n${violations.map(item => `- ${item}`).join('\n')}\n`);
process.exitCode = 1;
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
const options = parseArgs(process.argv.slice(2));
const report = buildReport(options.sourceSet);
if (options.check) checkContract(report, options.check);
@@ -328,3 +350,5 @@ const rendered = options.format === 'json'
: renderDot(report);
if (options.output) fs.writeFileSync(path.resolve(process.cwd(), options.output), rendered, 'utf8');
else process.stdout.write(rendered);
}