fix: align ticket order entitlement model

This commit is contained in:
2026-07-30 16:09:35 +08:00
parent 614778a767
commit cb8cbb66f7
2 changed files with 42 additions and 34 deletions

View File

@@ -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,
}

View File

@@ -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, [])