feat(order): support balance payment, dynamic marketing coupon recommendation, and quick activity picker
这个提交包含在:
@@ -120,7 +120,9 @@ export async function initDatabase(): Promise<void> {
|
||||
"ALTER TABLE orders ADD COLUMN expert_avatar TEXT AFTER companion_name",
|
||||
"ALTER TABLE orders ADD COLUMN service_title VARCHAR(255) DEFAULT '同城陪伴服务' AFTER track",
|
||||
"ALTER TABLE orders ADD COLUMN verify_code VARCHAR(8) AFTER location",
|
||||
"ALTER TABLE orders ADD COLUMN remark VARCHAR(255) AFTER verify_code"
|
||||
"ALTER TABLE orders ADD COLUMN remark VARCHAR(255) AFTER verify_code",
|
||||
"ALTER TABLE orders ADD COLUMN pay_type VARCHAR(32) DEFAULT 'WECHAT' AFTER status",
|
||||
"ALTER TABLE orders ADD COLUMN coupon_discount DECIMAL(10,2) DEFAULT 0.00 AFTER amount"
|
||||
];
|
||||
for (const colSql of extraOrderCols) {
|
||||
try {
|
||||
|
||||
@@ -617,6 +617,8 @@ export class MysqlRepository {
|
||||
hourly_rate as hourlyRate,
|
||||
duration_hours as durationHours,
|
||||
amount,
|
||||
COALESCE(coupon_discount, 0.00) as couponDiscount,
|
||||
COALESCE(pay_type, 'WECHAT') as payType,
|
||||
location,
|
||||
verify_code as verifyCode,
|
||||
remark,
|
||||
@@ -639,6 +641,8 @@ export class MysqlRepository {
|
||||
hourlyRate?: number;
|
||||
durationHours?: number;
|
||||
amount?: number;
|
||||
couponDiscount?: number;
|
||||
payType?: string;
|
||||
location?: string;
|
||||
expenseBurden?: string;
|
||||
remark?: string;
|
||||
@@ -656,6 +660,8 @@ export class MysqlRepository {
|
||||
const hourlyRate = Number(data.hourlyRate) || 35;
|
||||
const durationHours = Number(data.durationHours) || 2;
|
||||
const amount = Number(data.amount) || hourlyRate * durationHours;
|
||||
const couponDiscount = Number(data.couponDiscount || 0);
|
||||
const payType = data.payType === 'BALANCE' ? 'BALANCE' : 'WECHAT';
|
||||
const location = data.location || '朝阳大悦城';
|
||||
const expenseBurden = data.expenseBurden || 'AA制各自付';
|
||||
const verifyCode = String(Math.floor(1000 + Math.random() * 9000));
|
||||
@@ -665,27 +671,52 @@ export class MysqlRepository {
|
||||
const conn = await pool.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
|
||||
// 如果是余额支付,先锁行校验扣减用户钱包余额
|
||||
if (payType === 'BALANCE') {
|
||||
const [uRows]: any = await conn.query('SELECT balance FROM users WHERE id = ? FOR UPDATE', [buyerId]);
|
||||
if (!uRows || uRows.length === 0) {
|
||||
throw new Error('支付账户不存在');
|
||||
}
|
||||
const userBalance = Number(uRows[0].balance || 0);
|
||||
if (userBalance < amount) {
|
||||
throw new Error(`钱包余额不足 (当前可用 ¥${userBalance.toFixed(2)},需支付 ¥${amount.toFixed(2)})`);
|
||||
}
|
||||
const newBalance = userBalance - amount;
|
||||
await conn.query('UPDATE users SET balance = ? WHERE id = ?', [newBalance, buyerId]);
|
||||
|
||||
// 记一笔钱包扣款流水
|
||||
const walletFinId = `fin_bal_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const walletRecNo = `WB${Date.now()}${Math.floor(Math.random() * 100)}`;
|
||||
await conn.query(`
|
||||
INSERT INTO finance_records (id, record_no, type, amount, direction, counterparty, related_id)
|
||||
VALUES (?, ?, '钱包余额支付订单', ?, 'OUT', ?, ?)
|
||||
`, [walletFinId, walletRecNo, amount.toFixed(2), `用户 ${buyerId} (${companionName})`, id]);
|
||||
}
|
||||
|
||||
await conn.query(`
|
||||
INSERT INTO orders (
|
||||
id, buyer_id, expert_id, order_no, client_name,
|
||||
companion_name, expert_avatar, track, service_title,
|
||||
expense_burden, hourly_rate, duration_hours, amount,
|
||||
coupon_discount, pay_type,
|
||||
location, verify_code, remark, status, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
`, [
|
||||
id, buyerId, expertId, orderNo, clientName,
|
||||
companionName, expertAvatar, track, serviceTitle,
|
||||
expenseBurden, hourlyRate, durationHours, amount,
|
||||
couponDiscount, payType,
|
||||
location, verifyCode, remark, status
|
||||
]);
|
||||
|
||||
// 记一笔进账流水
|
||||
// 记一笔平台订单进账流水
|
||||
const finId = `fin_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const recordNo = `TX${Date.now()}${Math.floor(Math.random() * 100)}`;
|
||||
await conn.query(`
|
||||
INSERT INTO finance_records (id, record_no, type, amount, direction, counterparty, related_id)
|
||||
VALUES (?, ?, '服务预约订单付款', ?, 'IN', ?, ?)
|
||||
`, [finId, recordNo, amount.toFixed(2), clientName, id]);
|
||||
VALUES (?, ?, ?, ?, 'IN', ?, ?)
|
||||
`, [finId, recordNo, payType === 'BALANCE' ? '服务预约订单付款 (钱包余额)' : '服务预约订单付款 (微信支付)', amount.toFixed(2), clientName, id]);
|
||||
|
||||
await conn.commit();
|
||||
|
||||
@@ -703,6 +734,8 @@ export class MysqlRepository {
|
||||
hourlyRate,
|
||||
durationHours,
|
||||
amount,
|
||||
couponDiscount,
|
||||
payType,
|
||||
location,
|
||||
verifyCode,
|
||||
remark,
|
||||
|
||||
在新工单中引用
屏蔽一个用户