fix: align ticket order entitlement model
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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, [])
|
||||
|
||||
Reference in New Issue
Block a user