490 lines
13 KiB
JavaScript
490 lines
13 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const Joi = require('joi');
|
|
|
|
// 模拟财务数据
|
|
let settlements = [
|
|
{
|
|
id: 1,
|
|
orderId: 1,
|
|
settlementCode: 'SET001',
|
|
supplierName: '山东优质牲畜合作社',
|
|
buyerName: '北京肉类加工有限公司',
|
|
cattleCount: 50,
|
|
unitPrice: 25000,
|
|
totalAmount: 1250000,
|
|
paymentMethod: 'bank_transfer',
|
|
paymentStatus: 'paid',
|
|
settlementDate: '2024-01-20',
|
|
paymentDate: '2024-01-22',
|
|
invoiceNumber: 'INV001',
|
|
invoiceStatus: 'issued',
|
|
taxAmount: 125000,
|
|
actualPayment: 1125000,
|
|
bankAccount: '1234567890123456789',
|
|
bankName: '中国农业银行',
|
|
createdAt: new Date('2024-01-20'),
|
|
updatedAt: new Date('2024-01-22')
|
|
},
|
|
{
|
|
id: 2,
|
|
orderId: 2,
|
|
settlementCode: 'SET002',
|
|
supplierName: '内蒙古草原牲畜有限公司',
|
|
buyerName: '天津屠宰加工厂',
|
|
cattleCount: 80,
|
|
unitPrice: 24000,
|
|
totalAmount: 1920000,
|
|
paymentMethod: 'cash',
|
|
paymentStatus: 'pending',
|
|
settlementDate: '2024-01-25',
|
|
paymentDate: null,
|
|
invoiceNumber: 'INV002',
|
|
invoiceStatus: 'pending',
|
|
taxAmount: 192000,
|
|
actualPayment: 1728000,
|
|
bankAccount: '9876543210987654321',
|
|
bankName: '中国建设银行',
|
|
createdAt: new Date('2024-01-25'),
|
|
updatedAt: new Date('2024-01-25')
|
|
}
|
|
];
|
|
|
|
let payments = [
|
|
{
|
|
id: 1,
|
|
settlementId: 1,
|
|
paymentCode: 'PAY001',
|
|
amount: 1125000,
|
|
paymentMethod: 'bank_transfer',
|
|
status: 'success',
|
|
transactionId: 'TXN20240122001',
|
|
paidAt: '2024-01-22T10:30:00Z',
|
|
createdAt: new Date('2024-01-22T10:30:00Z')
|
|
}
|
|
];
|
|
|
|
// 验证schemas
|
|
const settlementCreateSchema = Joi.object({
|
|
orderId: Joi.number().integer().required(),
|
|
cattleCount: Joi.number().integer().min(1).required(),
|
|
unitPrice: Joi.number().min(0).required(),
|
|
paymentMethod: Joi.string().valid('bank_transfer', 'cash', 'check', 'online').required(),
|
|
settlementDate: Joi.date().iso().required(),
|
|
invoiceNumber: Joi.string().min(3).max(50)
|
|
});
|
|
|
|
const paymentCreateSchema = Joi.object({
|
|
settlementId: Joi.number().integer().required(),
|
|
amount: Joi.number().min(0).required(),
|
|
paymentMethod: Joi.string().valid('bank_transfer', 'cash', 'check', 'online').required(),
|
|
transactionId: Joi.string().max(100)
|
|
});
|
|
|
|
// 获取结算列表
|
|
router.get('/settlements', (req, res) => {
|
|
try {
|
|
const {
|
|
page = 1,
|
|
pageSize = 20,
|
|
keyword,
|
|
paymentStatus,
|
|
startDate,
|
|
endDate
|
|
} = req.query;
|
|
|
|
let filteredSettlements = [...settlements];
|
|
|
|
// 关键词搜索
|
|
if (keyword) {
|
|
filteredSettlements = filteredSettlements.filter(settlement =>
|
|
settlement.settlementCode.includes(keyword) ||
|
|
settlement.supplierName.includes(keyword) ||
|
|
settlement.buyerName.includes(keyword)
|
|
);
|
|
}
|
|
|
|
// 支付状态筛选
|
|
if (paymentStatus) {
|
|
filteredSettlements = filteredSettlements.filter(settlement => settlement.paymentStatus === paymentStatus);
|
|
}
|
|
|
|
// 时间范围筛选
|
|
if (startDate) {
|
|
filteredSettlements = filteredSettlements.filter(settlement =>
|
|
new Date(settlement.settlementDate) >= new Date(startDate)
|
|
);
|
|
}
|
|
|
|
if (endDate) {
|
|
filteredSettlements = filteredSettlements.filter(settlement =>
|
|
new Date(settlement.settlementDate) <= new Date(endDate)
|
|
);
|
|
}
|
|
|
|
// 分页处理
|
|
const startIndex = (page - 1) * pageSize;
|
|
const endIndex = startIndex + parseInt(pageSize);
|
|
const paginatedSettlements = filteredSettlements.slice(startIndex, endIndex);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
list: paginatedSettlements,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
pageSize: parseInt(pageSize),
|
|
total: filteredSettlements.length,
|
|
totalPages: Math.ceil(filteredSettlements.length / pageSize)
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取结算列表失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 获取结算详情
|
|
router.get('/settlements/:id', (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const settlement = settlements.find(s => s.id === parseInt(id));
|
|
|
|
if (!settlement) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '结算记录不存在'
|
|
});
|
|
}
|
|
|
|
// 获取相关支付记录
|
|
const relatedPayments = payments.filter(p => p.settlementId === settlement.id);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
...settlement,
|
|
payments: relatedPayments
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取结算详情失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 创建结算记录
|
|
router.post('/settlements', (req, res) => {
|
|
try {
|
|
const { error, value } = settlementCreateSchema.validate(req.body);
|
|
if (error) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '参数验证失败',
|
|
errors: error.details.map(detail => detail.message)
|
|
});
|
|
}
|
|
|
|
const settlementCode = `SET${String(Date.now()).slice(-6)}`;
|
|
const totalAmount = value.cattleCount * value.unitPrice;
|
|
const taxAmount = totalAmount * 0.1; // 假设税率10%
|
|
const actualPayment = totalAmount - taxAmount;
|
|
|
|
const newSettlement = {
|
|
id: Math.max(...settlements.map(s => s.id)) + 1,
|
|
...value,
|
|
settlementCode,
|
|
totalAmount,
|
|
taxAmount,
|
|
actualPayment,
|
|
paymentStatus: 'pending',
|
|
paymentDate: null,
|
|
invoiceStatus: 'pending',
|
|
supplierName: '供应商名称', // 实际应从订单获取
|
|
buyerName: '采购商名称', // 实际应从订单获取
|
|
bankAccount: '',
|
|
bankName: '',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
};
|
|
|
|
settlements.push(newSettlement);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: '结算记录创建成功',
|
|
data: newSettlement
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '创建结算记录失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 更新结算状态
|
|
router.put('/settlements/:id/status', (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { paymentStatus, invoiceStatus } = req.body;
|
|
|
|
const settlementIndex = settlements.findIndex(s => s.id === parseInt(id));
|
|
if (settlementIndex === -1) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '结算记录不存在'
|
|
});
|
|
}
|
|
|
|
if (paymentStatus) {
|
|
settlements[settlementIndex].paymentStatus = paymentStatus;
|
|
if (paymentStatus === 'paid') {
|
|
settlements[settlementIndex].paymentDate = new Date().toISOString().split('T')[0];
|
|
}
|
|
}
|
|
|
|
if (invoiceStatus) {
|
|
settlements[settlementIndex].invoiceStatus = invoiceStatus;
|
|
}
|
|
|
|
settlements[settlementIndex].updatedAt = new Date();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '结算状态更新成功',
|
|
data: settlements[settlementIndex]
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '更新结算状态失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 获取支付记录列表
|
|
router.get('/payments', (req, res) => {
|
|
try {
|
|
const {
|
|
page = 1,
|
|
pageSize = 20,
|
|
settlementId,
|
|
status
|
|
} = req.query;
|
|
|
|
let filteredPayments = [...payments];
|
|
|
|
// 按结算单筛选
|
|
if (settlementId) {
|
|
filteredPayments = filteredPayments.filter(payment => payment.settlementId === parseInt(settlementId));
|
|
}
|
|
|
|
// 按状态筛选
|
|
if (status) {
|
|
filteredPayments = filteredPayments.filter(payment => payment.status === status);
|
|
}
|
|
|
|
// 分页处理
|
|
const startIndex = (page - 1) * pageSize;
|
|
const endIndex = startIndex + parseInt(pageSize);
|
|
const paginatedPayments = filteredPayments.slice(startIndex, endIndex);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
list: paginatedPayments,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
pageSize: parseInt(pageSize),
|
|
total: filteredPayments.length,
|
|
totalPages: Math.ceil(filteredPayments.length / pageSize)
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取支付记录失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 创建支付记录
|
|
router.post('/payments', (req, res) => {
|
|
try {
|
|
const { error, value } = paymentCreateSchema.validate(req.body);
|
|
if (error) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '参数验证失败',
|
|
errors: error.details.map(detail => detail.message)
|
|
});
|
|
}
|
|
|
|
const paymentCode = `PAY${String(Date.now()).slice(-6)}`;
|
|
|
|
const newPayment = {
|
|
id: Math.max(...payments.map(p => p.id)) + 1,
|
|
...value,
|
|
paymentCode,
|
|
status: 'processing',
|
|
paidAt: null,
|
|
createdAt: new Date()
|
|
};
|
|
|
|
payments.push(newPayment);
|
|
|
|
// 模拟支付处理
|
|
setTimeout(() => {
|
|
const paymentIndex = payments.findIndex(p => p.id === newPayment.id);
|
|
if (paymentIndex !== -1) {
|
|
payments[paymentIndex].status = 'success';
|
|
payments[paymentIndex].paidAt = new Date().toISOString();
|
|
payments[paymentIndex].transactionId = `TXN${Date.now()}`;
|
|
|
|
// 更新对应结算单状态
|
|
const settlementIndex = settlements.findIndex(s => s.id === value.settlementId);
|
|
if (settlementIndex !== -1) {
|
|
settlements[settlementIndex].paymentStatus = 'paid';
|
|
settlements[settlementIndex].paymentDate = new Date().toISOString().split('T')[0];
|
|
settlements[settlementIndex].updatedAt = new Date();
|
|
}
|
|
}
|
|
}, 3000); // 3秒后处理完成
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: '支付申请已提交',
|
|
data: newPayment
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '创建支付记录失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 获取财务统计
|
|
router.get('/stats/overview', (req, res) => {
|
|
try {
|
|
const totalSettlements = settlements.length;
|
|
const paidCount = settlements.filter(s => s.paymentStatus === 'paid').length;
|
|
const pendingCount = settlements.filter(s => s.paymentStatus === 'pending').length;
|
|
|
|
const totalAmount = settlements.reduce((sum, s) => sum + s.totalAmount, 0);
|
|
const paidAmount = settlements
|
|
.filter(s => s.paymentStatus === 'paid')
|
|
.reduce((sum, s) => sum + s.actualPayment, 0);
|
|
const pendingAmount = settlements
|
|
.filter(s => s.paymentStatus === 'pending')
|
|
.reduce((sum, s) => sum + s.actualPayment, 0);
|
|
|
|
const totalTaxAmount = settlements.reduce((sum, s) => sum + s.taxAmount, 0);
|
|
|
|
// 本月统计
|
|
const currentMonth = new Date().getMonth();
|
|
const currentYear = new Date().getFullYear();
|
|
const monthlySettlements = settlements.filter(s => {
|
|
const settleDate = new Date(s.settlementDate);
|
|
return settleDate.getMonth() === currentMonth && settleDate.getFullYear() === currentYear;
|
|
});
|
|
const monthlyAmount = monthlySettlements.reduce((sum, s) => sum + s.totalAmount, 0);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
totalSettlements,
|
|
paidCount,
|
|
pendingCount,
|
|
totalAmount,
|
|
paidAmount,
|
|
pendingAmount,
|
|
totalTaxAmount,
|
|
monthlyAmount,
|
|
paymentRate: totalSettlements > 0 ? Math.round((paidCount / totalSettlements) * 100) : 0
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取财务统计失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 获取财务报表
|
|
router.get('/reports/monthly', (req, res) => {
|
|
try {
|
|
const { year = new Date().getFullYear(), month } = req.query;
|
|
|
|
let targetSettlements = settlements;
|
|
|
|
// 筛选指定年份
|
|
targetSettlements = targetSettlements.filter(s => {
|
|
const settleDate = new Date(s.settlementDate);
|
|
return settleDate.getFullYear() === parseInt(year);
|
|
});
|
|
|
|
// 如果指定了月份,进一步筛选
|
|
if (month) {
|
|
targetSettlements = targetSettlements.filter(s => {
|
|
const settleDate = new Date(s.settlementDate);
|
|
return settleDate.getMonth() === parseInt(month) - 1;
|
|
});
|
|
}
|
|
|
|
// 按月份分组统计
|
|
const monthlyStats = {};
|
|
for (let i = 1; i <= 12; i++) {
|
|
monthlyStats[i] = {
|
|
month: i,
|
|
settlementCount: 0,
|
|
totalAmount: 0,
|
|
paidAmount: 0,
|
|
pendingAmount: 0
|
|
};
|
|
}
|
|
|
|
targetSettlements.forEach(settlement => {
|
|
const settleMonth = new Date(settlement.settlementDate).getMonth() + 1;
|
|
monthlyStats[settleMonth].settlementCount++;
|
|
monthlyStats[settleMonth].totalAmount += settlement.totalAmount;
|
|
|
|
if (settlement.paymentStatus === 'paid') {
|
|
monthlyStats[settleMonth].paidAmount += settlement.actualPayment;
|
|
} else {
|
|
monthlyStats[settleMonth].pendingAmount += settlement.actualPayment;
|
|
}
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
year: parseInt(year),
|
|
monthlyStats: Object.values(monthlyStats)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取财务报表失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router; |