/** * 付款执行统一路由 * 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.md * 章节:七、付款执行统一页面 * * 功能: * - 整合所有支付类型:材料采购、一次运费、二次运费、预支、报销等 * - 执行付款时必须上传付款凭证 * - 统一的付款执行列表和操作界面 */ const express = require('express'); const db = require('../db'); const router = express.Router(); /** * 获取待执行付款列表 * 整合所有支付类型 */ router.get('/pending', async (req, res) => { try { const { payment_type } = req.query; const results = []; // 1. 材料采购付款(来自付款计划) if (!payment_type || payment_type === 'material') { const materialPayments = await db.query(` SELECT 'material' as payment_type, pp.id as source_id, pp.stage as description, pp.planned_amount as amount, po.currency, pp.planned_date as due_date, s.name as payee_name, po.code as order_code, pp.status, '付款计划' as source_type FROM payment_plans pp LEFT JOIN purchase_orders po ON pp.purchase_order_id = po.id LEFT JOIN suppliers s ON po.supplier_id = s.id WHERE pp.status IN ('pending', 'requested') `); results.push(...materialPayments.rows); } // 2. 一次运费付款 if (!payment_type || payment_type === 'primary_freight') { const primaryFreight = await db.query(` SELECT 'primary_freight' as payment_type, lr.id as source_id, '一次运费' as description, lr.primary_freight as amount, lr.primary_freight_currency as currency, lr.ship_date as due_date, lc.name as payee_name, po.code as order_code, lr.primary_freight_status as status, '物流单' as source_type FROM logistics_records lr LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id LEFT JOIN logistics_companies lc ON lr.logistics_company_id = lc.id WHERE lr.primary_freight_status IN ('pending', 'requested') AND lr.primary_freight > 0 `); results.push(...primaryFreight.rows); } // 3. 二次运费付款 if (!payment_type || payment_type === 'secondary_freight') { const secondaryFreight = await db.query(` SELECT 'secondary_freight' as payment_type, lr.id as source_id, '二次运费' as description, lr.secondary_freight as amount, lr.secondary_freight_currency as currency, lr.second_ship_date as due_date, lr.driver_phone as payee_name, po.code as order_code, lr.secondary_freight_status as status, '物流单' as source_type FROM logistics_records lr LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id WHERE lr.secondary_freight_status IN ('pending', 'requested') AND lr.secondary_freight > 0 `); results.push(...secondaryFreight.rows); } // 4. 预支款 if (!payment_type || payment_type === 'advance') { const advances = await db.query(` SELECT 'advance' as payment_type, a.id as source_id, a.purpose as description, a.amount, a.currency, a.request_date as due_date, a.applicant as payee_name, NULL as order_code, a.status, '预支申请' as source_type FROM advances a WHERE a.status = 'approved' `); results.push(...advances.rows); } // 5. 报销 if (!payment_type || payment_type === 'reimbursement') { const reimbursements = await db.query(` SELECT 'reimbursement' as payment_type, r.id as source_id, r.description, r.total_amount as amount, r.currency, r.request_date as due_date, r.applicant as payee_name, NULL as order_code, r.status, '报销申请' as source_type FROM reimbursements r WHERE r.status = 'approved' `); results.push(...reimbursements.rows); } // 按日期排序 results.sort((a, b) => { if (!a.due_date) return 1; if (!b.due_date) return -1; return new Date(a.due_date) - new Date(b.due_date); }); res.json({ success: true, data: results, count: results.length }); } catch (error) { console.error('获取待执行付款列表失败:', error); res.status(500).json({ success: false, message: '获取待执行付款列表失败', error: error.message }); } }); /** * 获取已执行付款记录 */ router.get('/executed', async (req, res) => { try { const results = []; // 1. 材料采购付款记录 const materialPayments = await db.query(` SELECT 'material' as payment_type, pp.id as source_id, pp.stage as description, pp.actual_amount as amount, po.currency, pp.actual_date as payment_date, s.name as payee_name, po.code as order_code, '已支付' as status, '付款计划' as source_type FROM payment_plans pp LEFT JOIN purchase_orders po ON pp.purchase_order_id = po.id LEFT JOIN suppliers s ON po.supplier_id = s.id WHERE pp.status = 'paid' `); results.push(...materialPayments.rows); // 2. 一次运费付款记录 const primaryFreight = await db.query(` SELECT 'primary_freight' as payment_type, lr.id as source_id, '一次运费' as description, lr.primary_freight as amount, lr.primary_freight_currency as currency, lr.final_arrival_date as payment_date, lc.name as payee_name, po.code as order_code, '已支付' as status, '物流单' as source_type FROM logistics_records lr LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id LEFT JOIN logistics_companies lc ON lr.logistics_company_id = lc.id WHERE lr.primary_freight_status = 'paid' `); results.push(...primaryFreight.rows); // 3. 二次运费付款记录 const secondaryFreight = await db.query(` SELECT 'secondary_freight' as payment_type, lr.id as source_id, '二次运费' as description, lr.secondary_freight as amount, lr.secondary_freight_currency as currency, lr.final_arrival_date as payment_date, lr.driver_phone as payee_name, po.code as order_code, '已支付' as status, '物流单' as source_type FROM logistics_records lr LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id WHERE lr.secondary_freight_status = 'paid' `); results.push(...secondaryFreight.rows); // 按日期排序(最新的在前) results.sort((a, b) => { if (!a.payment_date) return 1; if (!b.payment_date) return -1; return new Date(b.payment_date) - new Date(a.payment_date); }); res.json({ success: true, data: results, count: results.length }); } catch (error) { console.error('获取已执行付款记录失败:', error); res.status(500).json({ success: false, message: '获取已执行付款记录失败', error: error.message }); } }); /** * 执行付款 * 遵循设计方案:必须上传付款凭证 */ router.post('/execute', async (req, res) => { try { const { payment_type, source_id, amount, payment_date, voucher_url, remark, payee_account } = req.body; if (!payment_type || !source_id) { return res.status(400).json({ success: false, message: '缺少付款类型或来源ID' }); } if (!voucher_url) { return res.status(400).json({ success: false, message: '执行付款必须上传付款凭证' }); } await db.query('BEGIN TRANSACTION'); try { const paymentDate = payment_date || new Date().toISOString().slice(0, 10); switch (payment_type) { case 'material': await db.query(` UPDATE payment_plans SET status = 'paid', actual_amount = ?, actual_date = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `, [amount, paymentDate, source_id]); const planResult = await db.query('SELECT purchase_order_id FROM payment_plans WHERE id = $1', [source_id]); if (planResult.rows.length > 0) { const orderId = planResult.rows[0].purchase_order_id; const orderStats = await db.query(` SELECT SUM(CASE WHEN status = 'paid' THEN COALESCE(actual_amount, 0) ELSE 0 END) as paid_amount, SUM(planned_amount) as total_amount FROM payment_plans WHERE purchase_order_id = ? `, [orderId]); const { paid_amount, total_amount } = orderStats.rows[0]; let newStatus = 'confirmed'; if (paid_amount >= total_amount) { newStatus = 'paid'; } else if (paid_amount > 0) { newStatus = 'partial_paid'; } await db.query(` UPDATE purchase_orders SET paid_amount = ?, status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `, [paid_amount, newStatus, orderId]); } break; case 'primary_freight': await db.query(` UPDATE logistics_records SET primary_freight_status = 'paid', primary_freight_document = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `, [voucher_url, source_id]); break; case 'secondary_freight': await db.query(` UPDATE logistics_records SET secondary_freight_status = 'paid', updated_at = CURRENT_TIMESTAMP WHERE id = ? `, [source_id]); break; case 'advance': await db.query(` UPDATE advances SET status = 'paid', updated_at = CURRENT_TIMESTAMP WHERE id = ? `, [source_id]); break; case 'reimbursement': await db.query(` UPDATE reimbursements SET status = 'paid', updated_at = CURRENT_TIMESTAMP WHERE id = ? `, [source_id]); break; default: throw new Error('未知的付款类型'); } const recordCode = 'PAY-REC-' + Date.now(); await db.query(` INSERT INTO payment_records (code, payment_type, source_id, amount, currency, payment_date, voucher_url, payee_account, remark, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) `, [recordCode, payment_type, source_id, amount, 'CNY', paymentDate, voucher_url, payee_account, remark]); await db.query('COMMIT'); res.json({ success: true, message: '付款执行成功', data: { record_code: recordCode } }); } catch (innerError) { await db.query('ROLLBACK'); throw innerError; } } catch (error) { console.error('执行付款失败:', error); res.status(500).json({ success: false, message: '执行付款失败', error: error.message }); } }); /** * 获取付款详情 */ router.get('/detail/:payment_type/:source_id', async (req, res) => { try { const { payment_type, source_id } = req.params; let result; switch (payment_type) { case 'material': result = await db.query(` SELECT pp.*, po.code as order_code, s.name as supplier_name, s.country as supplier_country FROM payment_plans pp LEFT JOIN purchase_orders po ON pp.purchase_order_id = po.id LEFT JOIN suppliers s ON po.supplier_id = s.id WHERE pp.id = ? `, [source_id]); break; case 'primary_freight': case 'secondary_freight': result = await db.query(` SELECT lr.*, po.code as order_code, lc.name as logistics_company_name FROM logistics_records lr LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id LEFT JOIN logistics_companies lc ON lr.logistics_company_id = lc.id WHERE lr.id = ? `, [source_id]); break; case 'advance': result = await db.query('SELECT * FROM advances WHERE id = $1', [source_id]); break; case 'reimbursement': result = await db.query('SELECT * FROM reimbursements WHERE id = $1', [source_id]); break; default: return res.status(400).json({ success: false, message: '未知的付款类型' }); } if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '付款记录不存在' }); } res.json({ success: true, data: result.rows[0] }); } catch (error) { console.error('获取付款详情失败:', error); res.status(500).json({ success: false, message: '获取付款详情失败', error: error.message }); } }); /** * 获取付款统计 */ router.get('/statistics', async (req, res) => { try { const stats = { pending_count: 0, pending_amount: 0, executed_count: 0, executed_amount: 0, by_type: {} }; // 统计待执行付款 const pendingResult = await db.query(` SELECT payment_type, COUNT(*) as count, SUM(amount) as total_amount FROM ( SELECT 'material' as payment_type, pp.planned_amount as amount FROM payment_plans pp WHERE pp.status IN ('pending', 'requested') UNION ALL SELECT 'primary_freight', lr.primary_freight FROM logistics_records lr WHERE lr.primary_freight_status IN ('pending', 'requested') AND lr.primary_freight > 0 UNION ALL SELECT 'secondary_freight', lr.secondary_freight FROM logistics_records lr WHERE lr.secondary_freight_status IN ('pending', 'requested') AND lr.secondary_freight > 0 UNION ALL SELECT 'advance', a.amount FROM advances a WHERE a.status = 'approved' UNION ALL SELECT 'reimbursement', r.total_amount FROM reimbursements r WHERE r.status = 'approved' ) GROUP BY payment_type `); for (const row of pendingResult.rows) { stats.pending_count += row.count; stats.pending_amount += row.total_amount || 0; stats.by_type[row.payment_type] = { pending_count: row.count, pending_amount: row.total_amount || 0 }; } // 统计已执行付款 const executedResult = await db.query(` SELECT payment_type, COUNT(*) as count, SUM(amount) as total_amount FROM ( SELECT 'material' as payment_type, pp.actual_amount as amount FROM payment_plans pp WHERE pp.status = 'paid' UNION ALL SELECT 'primary_freight', lr.primary_freight FROM logistics_records lr WHERE lr.primary_freight_status = 'paid' UNION ALL SELECT 'secondary_freight', lr.secondary_freight FROM logistics_records lr WHERE lr.secondary_freight_status = 'paid' ) GROUP BY payment_type `); for (const row of executedResult.rows) { stats.executed_count += row.count; stats.executed_amount += row.total_amount || 0; if (!stats.by_type[row.payment_type]) { stats.by_type[row.payment_type] = {}; } stats.by_type[row.payment_type].executed_count = row.count; stats.by_type[row.payment_type].executed_amount = row.total_amount || 0; } res.json({ success: true, data: stats }); } catch (error) { console.error('获取付款统计失败:', error); res.status(500).json({ success: false, message: '获取付款统计失败', error: error.message }); } }); module.exports = router;