添加 IntelliJ IDEA 项目配置文件
This commit is contained in:
490
backend/routes/finance.js
Normal file
490
backend/routes/finance.js
Normal file
@@ -0,0 +1,490 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user