Files
KArtSell.Aegis/docs/Design/kbx-foundation-v60-status-canonical-contract-hardening/scripts/validate-status-canonical-v60.mjs
T
kjh2064 3f293d8aa8
deploy / deploy (push) Successful in 1m52s
deploy / notify (push) Successful in 1s
V13-FE-006: consolidate approved UI and contract hardening
2026-08-13 02:41:00 +09:00

74 lines
7.8 KiB
JavaScript

import fs from 'node:fs'
const read = file => fs.readFileSync(file, 'utf8')
const expect = (condition, message) => {
if (!condition) { console.error(`FAIL: ${message}`); process.exit(1) }
console.log(`PASS: ${message}`)
}
const app = read('apps/web/src/App.vue')
expect(app.includes('app-version="v60-fe-status-canonical-contract-hardening"'), 'Runtime metadata identifies the v60 canonical-status build')
const backendStatus = read('backend/Modules/OMS/Orders/OrderLifecycleStatus.cs')
const backendValues = [...backendStatus.matchAll(/const string \w+ = "([A-Z_]+)"/g)].map(match => match[1])
expect(JSON.stringify(backendValues) === JSON.stringify(['DRAFT','CONFIRMED','ALLOCATED','PICKING','CHECKED','SHIPPED']), 'OMS backend lifecycle constants are explicit canonical codes')
const getEndpoint = read('backend/Modules/OMS/Orders/Get/Endpoint.cs')
expect(getEndpoint.includes('(string)h.status') && !getEndpoint.includes('string display=') && !getEndpoint.includes('"DRAFT"=>"작성"'), 'Order GET returns canonical database status without server-side label translation')
const registerHandler = read('backend/Modules/OMS/Orders/Register/Handler.cs')
expect(registerHandler.includes('InitialStatus = OrderLifecycleStatus.Draft') && registerHandler.includes('OrderLifecycleStatus.Draft));'), 'Order register persists and returns DRAFT through the shared backend constant')
const confirmEndpoint = read('backend/Modules/OMS/Orders/Confirm/Endpoint.cs')
expect(confirmEndpoint.includes('current.Status!=OrderLifecycleStatus.Draft') && confirmEndpoint.includes('ConfirmedStatus=OrderLifecycleStatus.Confirmed') && confirmEndpoint.includes('new Response(id,next,OrderLifecycleStatus.Confirmed)'), 'Order confirm validates and returns canonical lifecycle codes')
const shipmentStatus = read('backend/Modules/OMS/Orders/OrderShipmentStatus.cs')
expect(shipmentStatus.includes('Ready = "READY"') && shipmentStatus.includes('Shipped = "SHIPPED"'), 'OMS backend shipment projection vocabulary is explicit canonical code')
const shipEndpoint = read('backend/Modules/OMS/Orders/Ship/Endpoint.cs')
expect(shipEndpoint.includes('RequiredOrderStatus = OrderLifecycleStatus.Confirmed') && shipEndpoint.includes('o.status=@RequiredOrderStatus'), 'Ship request requires CONFIRMED aggregate lifecycle state')
expect(shipEndpoint.includes('RequiredShipmentStatus = OrderShipmentStatus.Ready') && shipEndpoint.includes('p.shipment_status=@RequiredShipmentStatus'), 'Ship request also requires READY shipment projection state for ids and filter modes')
expect(!shipEndpoint.includes("status in ('NEW','DRAFT','READY')") && !shipEndpoint.includes("set status='CONFIRMED'"), 'Ship request no longer mixes shipment projection states into aggregate lifecycle or fabricates a lifecycle transition')
const catalog = read('apps/web/src/registry/statusCatalog.ts')
for (const marker of ["{ value:'NEW', label:'신규'", "{ value:'DRAFT', label:'작성'", "{ value:'CONFIRMED', label:'확정'", "{ value:'SHIPPED', label:'출고완료'"]) {
expect(catalog.includes(marker), `Order lifecycle catalog maps canonical code through familiar label: ${marker}`)
}
expect(!catalog.includes("orderLifecycle: [\n { value:'신규'"), 'Order lifecycle dictionary no longer uses display labels as raw values')
for (const value of backendValues) expect(catalog.includes(`value:'${value}'`), `Frontend lifecycle catalog covers backend status ${value}`)
expect(catalog.includes('kbxStatusOptions') && catalog.includes('normalizeKbxStatusValue'), 'Status catalog owns select-option projection and canonical filter normalization')
const registerDefinition = read('apps/web/src/modules/oms/orders/register/order-register.definition.ts')
for (const marker of ["from:['DRAFT']", "to:'CONFIRMED'", "allowedStatuses:['NEW','DRAFT']", "allowedStatuses:['DRAFT']", "permissionByStatus:{NEW:'oms.order.create',DRAFT:'oms.order.write'}"]) {
expect(registerDefinition.includes(marker), `Order transaction command/workflow uses canonical status: ${marker}`)
}
expect(!registerDefinition.includes("allowedStatuses:['작성']") && !registerDefinition.includes("status:'작성'"), 'Order transaction policy does not use Korean display labels as machine state')
const registration = read('apps/web/src/modules/oms/orders/register/useOrderRegistration.ts')
expect(registration.includes("ref<KbxOrderLifecycleValue>('NEW')") && registration.includes("status.value='NEW'"), 'Unsaved order state is an explicit client NEW code before server DRAFT')
const registerPage = read('apps/web/src/modules/oms/orders/register/OrderRegisterPage.vue')
expect(registerPage.includes('displayStatus') && registerPage.includes('resolveKbxGridStatus') && registerPage.includes('value:displayStatus.value.label'), 'Transaction context displays catalog labels while commands receive raw canonical status')
const searchDefinition = read('apps/web/src/modules/oms/orders/search/order-list.definition.ts')
expect(searchDefinition.includes('options: kbxStatusOptions(kbxStatusCatalog.orderShipment)'), 'Order status search options are generated from the same catalog as Grid rendering')
const searchVm = read('apps/web/src/modules/oms/orders/search/useOrderSearch.ts')
expect(searchVm.includes('normalizeKbxStatusValue(kbxStatusCatalog.orderShipment, status)') && searchVm.includes('normalizeKbxStatusValue(kbxStatusCatalog.orderShipment, search.status)'), 'Route and saved-search status values are validated against the canonical shipment catalog')
expect(!searchVm.includes("['NEW','READY','SHIPPED','HOLD'].includes"), 'Order search no longer carries a second hard-coded status allow-list')
const demo = read('apps/web/src/http/demo/installFrontendDemoApi.ts')
expect(demo.includes("status:'DRAFT'") && demo.includes("status:'CONFIRMED'") && !demo.includes("status:'작성'") && !demo.includes("status:'확정'"), 'Demo order detail/register/confirm use canonical lifecycle codes')
expect(demo.includes('x=>x.shipmentStatus===q.status') && demo.includes("row=>row.shipmentStatus==='READY'") && !demo.includes("orderRows.filter(x=>x.status==='READY')"), 'Demo projection uses shipmentStatus as its single query/bulk source of truth')
expect(demo.includes("if(existing.status!=='DRAFT')return fail") && demo.includes('ORDER_NOT_CONFIRMABLE'), 'Demo confirm reproduces authoritative DRAFT-only transition failure')
expect(demo.includes('demoOrderLifecycleFromShipment'), 'Demo detail fallback translates projection semantics into lifecycle canonical codes instead of reusing shipment state blindly')
const reference = read('apps/web/fe-reference/kbx-fe.js')
expect(reference.includes("status:'READY'") && reference.includes("status:'CONFIRMED'") && reference.includes("transactionStatus:'DRAFT'"), 'Static HTML/JS reference stores OMS statuses as canonical codes')
expect(reference.includes("searchOption('READY','출고대기'") && reference.includes('canonicalOrderStatus') && reference.includes("legacy={신규:'NEW',출고대기:'READY'"), 'Static reference keeps familiar labels and migrates legacy saved display values to canonical codes')
expect(reference.includes("statusChip(order.status,'shipment')") && reference.includes("statusLabel(state.transactionStatus,'lifecycle')"), 'Static reference separates shipment and lifecycle display semantics')
const referenceHtml = read('apps/web/fe-reference/index.html')
expect(referenceHtml.includes('KBX FE Reference Lab v60 · Canonical Status Contract'), 'Static HTML reference identifies the v60 canonical-status build')
const unit = read('tests/unit/kbx-order-status-canonical.contract.spec.ts')
expect(unit.includes("rawValue:'DRAFT'") && unit.includes("value:'READY', label:'출고대기'") && unit.includes("'출고대기')).toBeNull()"), 'Unit contract covers raw/display separation, catalog options and invalid persisted display labels')
console.log('PASS: OMS canonical status API + Grid/Filter/Workflow parity hardening v60 contract')