From cb8cbb66f7edd96bea126bdbfb726b9657ef8573 Mon Sep 17 00:00:00 2001 From: zoujing Date: Thu, 30 Jul 2026 16:09:35 +0800 Subject: [PATCH] fix: align ticket order entitlement model --- src/pages-service/tickets/model.mjs | 37 +++++++++++----------- src/pages-service/tickets/model.test.mjs | 39 ++++++++++++++---------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/pages-service/tickets/model.mjs b/src/pages-service/tickets/model.mjs index 3928e82..1c495c1 100644 --- a/src/pages-service/tickets/model.mjs +++ b/src/pages-service/tickets/model.mjs @@ -19,10 +19,9 @@ const formatTimestamp = (date) => { return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` } -const createCredential = (prefix, orderId, createdAt) => ({ - type: prefix === 'GATE' ? 'gate' : 'self', - qrToken: `${prefix}-${orderId}`, - issuedAt: createdAt, +const createCredential = (prefix, orderNo) => ({ + status: 'issued', + qrToken: `${prefix}-${orderNo}`, }) export const calculateAge = (idNumber, visitDate) => { @@ -120,18 +119,20 @@ export const createPaidOrder = ({ product, draft, travelers, now = new Date() }) const totals = calculateBookingTotals(product, draft.quantity, draft.insuranceSelected, draft.insurancePrice) const entitlements = (product.entitlements || []).map((entitlement, index) => ({ ...entitlement, - id: `${entitlement.id || 'entitlement'}-${index + 1}`, + id: `${orderId}-${entitlement.id || 'entitlement'}-${index + 1}`, sourceId: entitlement.id, - quantity: draft.quantity, - allocation: toMoney(entitlement.allocation * draft.quantity), + quantity: (Number(entitlement.quantity) || 1) * draft.quantity, + usedQuantity: 0, status: 'pending', })) - const hasGateCredential = entitlements.some((entitlement) => entitlement.third_party) - const hasSelfCredential = entitlements.some((entitlement) => entitlement.self) + const hasGateCredential = entitlements.some((entitlement) => entitlement.fulfillment === 'third_party') + const hasSelfCredential = entitlements.some((entitlement) => entitlement.fulfillment === 'self') return { id: orderId, - product: { ...product }, + orderNo: orderId, + productId: product.id, + productName: product.name, visitDate: draft.visitDate, timeSlot: draft.timeSlot, quantity: draft.quantity, @@ -139,23 +140,23 @@ export const createPaidOrder = ({ product, draft, travelers, now = new Date() }) contactName: draft.contactName, phone: draft.phone, insurance: draft.insuranceSelected - ? { price: toMoney(draft.insurancePrice), amount: totals.insuranceAmount } - : undefined, + ? { name: '出行意外险', price: toMoney(draft.insurancePrice), quantity: draft.quantity, status: 'active' } + : null, ...totals, status: 'paid', refundStatus: '', entitlements, - ...(hasGateCredential ? { gateCredential: createCredential('GATE', orderId, createdAt) } : {}), - ...(hasSelfCredential ? { selfCredential: createCredential('SELF', orderId, createdAt) } : {}), - createdAt, - logs: [{ message: '订单支付成功', createdAt }], + gateCredential: hasGateCredential ? createCredential('GATE', orderId) : null, + selfCredential: hasSelfCredential ? createCredential('SELF', orderId) : null, + paidAt: createdAt, + logs: [{ time: createdAt, text: '订单支付成功', operator: '系统' }], } } export const calculateRefundAmount = (order, entitlementIds) => toMoney( (order?.entitlements || []) .filter((entitlement) => entitlement.status === 'pending' && entitlementIds.includes(entitlement.id)) - .reduce((total, entitlement) => total + Number(entitlement.allocation || 0), 0), + .reduce((total, entitlement) => total + Number(entitlement.refundAllocation || 0) * Number(entitlement.quantity || 0), 0), ) export const applyRefund = (order, entitlementIds, reason, now = new Date()) => { @@ -185,7 +186,7 @@ export const applyRefund = (order, entitlementIds, reason, now = new Date()) => entitlements, status: 'refunding', refundStatus: 'pending', - logs: [...(order.logs || []), { message: '已申请退款', createdAt }], + logs: [...(order.logs || []), { time: createdAt, text: '已申请退款', operator: '用户' }], }, refund, } diff --git a/src/pages-service/tickets/model.test.mjs b/src/pages-service/tickets/model.test.mjs index cdc4293..d47bdbf 100644 --- a/src/pages-service/tickets/model.test.mjs +++ b/src/pages-service/tickets/model.test.mjs @@ -29,16 +29,18 @@ const adultProduct = { { id: 'adult-ticket', name: '成人门票', - allocation: 120, + fulfillment: 'third_party', + quantity: 1, + unit: '张', refundAllocation: 120, - third_party: true, }, { id: 'shuttle', - name: '摆渡车', - allocation: 40, + name: '观光车票', + fulfillment: 'self', + quantity: 1, + unit: '张', refundAllocation: 40, - self: true, }, ], } @@ -151,14 +153,16 @@ test('creates a paid order with entitlements and credentials', () => { assert.equal(order.status, 'paid') assert.equal(order.paidAmount, 165) assert.equal(order.entitlements.length, 2) - assert.match(order.gateCredential.qrToken, /^GATE-/) - assert.match(order.selfCredential.qrToken, /^SELF-/) - assert.equal(order.createdAt, '2026-07-30 10:00:00') + assert.equal(order.product, undefined) + assert.equal(order.productId, 'adult') + assert.equal(order.productName, '成人票') assert.equal(order.id, `ORDER-${now.getTime()}`) - assert.equal(order.gateCredential.qrToken, `GATE-${order.id}`) - assert.equal(order.selfCredential.qrToken, `SELF-${order.id}`) - assert.deepEqual(order.insurance, { price: 5, amount: 5 }) - assert.equal(order.logs[0].message, '订单支付成功') + assert.equal(order.orderNo, order.id) + assert.equal(order.paidAt, '2026-07-30 10:00:00') + assert.deepEqual(order.gateCredential, { status: 'issued', qrToken: `GATE-${order.orderNo}` }) + assert.deepEqual(order.selfCredential, { status: 'issued', qrToken: `SELF-${order.orderNo}` }) + assert.deepEqual(order.insurance, { name: '出行意外险', price: 5, quantity: 1, status: 'active' }) + assert.deepEqual(order.logs[0], { time: order.paidAt, text: '订单支付成功', operator: '系统' }) const secondTraveler = { ...eligibleTraveler, id: 't-2', idNumber: '110101199101020010' } const thirdTraveler = { ...eligibleTraveler, id: 't-3', idNumber: '110101199201020010' } @@ -180,7 +184,7 @@ test('creates a paid order with entitlements and credentials', () => { assert.equal(uninsuredOrder.travelers.length, 2) assert.deepEqual(uninsuredOrder.entitlements.map((entitlement) => entitlement.quantity), [2, 2]) assert.deepEqual(uninsuredOrder.entitlements.map((entitlement) => entitlement.status), ['pending', 'pending']) - assert.equal(uninsuredOrder.insurance, undefined) + assert.equal(uninsuredOrder.insurance, null) const insuredOrder = createPaidOrder({ product: adultProduct, @@ -201,12 +205,14 @@ test('creates a paid order with entitlements and credentials', () => { assert.deepEqual(insuredOrder.travelers.map((traveler) => traveler.id), ['t-1', 't-2']) assert.deepEqual(insuredOrder.entitlements.map((entitlement) => entitlement.quantity), [2, 2]) assert.deepEqual(insuredOrder.entitlements.map((entitlement) => entitlement.refundAllocation), [120, 40]) + assert.deepEqual(insuredOrder.entitlements.map((entitlement) => entitlement.usedQuantity), [0, 0]) + assert.equal(calculateRefundAmount(insuredOrder, [insuredOrder.entitlements[0].id]), 240) assert.notEqual(insuredOrder.entitlements[0], adultProduct.entitlements[0]) assert.deepEqual(adultProduct.entitlements, [ - { id: 'adult-ticket', name: '成人门票', allocation: 120, refundAllocation: 120, third_party: true }, - { id: 'shuttle', name: '摆渡车', allocation: 40, refundAllocation: 40, self: true }, + { id: 'adult-ticket', name: '成人门票', fulfillment: 'third_party', quantity: 1, unit: '张', refundAllocation: 120 }, + { id: 'shuttle', name: '观光车票', fulfillment: 'self', quantity: 1, unit: '张', refundAllocation: 40 }, ]) - assert.deepEqual(insuredOrder.insurance, { price: 5, amount: 10 }) + assert.deepEqual(insuredOrder.insurance, { name: '出行意外险', price: 5, quantity: 2, status: 'active' }) }) test('applies refunds and pops view history without mutation', () => { @@ -231,6 +237,7 @@ test('applies refunds and pops view history without mutation', () => { assert.equal(result.order.status, 'refunding') assert.equal(result.refund.amount, 120) assert.equal(result.order.entitlements[0].status, 'refunding') + assert.deepEqual(result.order.logs.at(-1), { time: '2026-07-30 10:01:00', text: '已申请退款', operator: '用户' }) const repeatedRefund = applyRefund(result.order, [entitlementId, 'missing-entitlement'], '重复申请') assert.equal(repeatedRefund.refund.amount, 0) assert.deepEqual(repeatedRefund.refund.entitlementIds, [])