const { Bill, MeterReading, Room, Renter, Rental, BillPayment } = require('../models'); const { Op } = require('sequelize'); const response = require('../utils/response'); // 生成账单编号 const generateBillNo = () => { const date = new Date(); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const random = Math.floor(1000 + Math.random() * 9000); return `B${year}${month}${day}${random}`; }; // 格式化时间(考虑时区,转换为北京时间) const formatDate = (date) => { if (!date) return null; // 确保 date 是 Date 对象 const dateObj = date instanceof Date ? date : new Date(date); if (isNaN(dateObj.getTime())) return null; const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000); return beijingDate.toISOString().split('T')[0]; }; const formatDateTime = (date) => { if (!date) return null; // 确保 date 是 Date 对象 const dateObj = date instanceof Date ? date : new Date(date); if (isNaN(dateObj.getTime())) return null; const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000); return beijingDate.toISOString().replace('T', ' ').slice(0, 19); }; // 格式化账单数据 const formatBillData = (bill) => { const formattedBill = { ...bill.toJSON(), createTime: formatDateTime(bill.createTime), updateTime: formatDateTime(bill.updateTime), billDate: formatDate(bill.billDate) }; // 格式化关联数据 if (formattedBill.Room) { formattedBill.roomNumber = formattedBill.Room.roomNumber; formattedBill.apartmentName = formattedBill.Room.Apartment?.name; } if (formattedBill.Renter) { formattedBill.renterName = formattedBill.Renter.name; } if (formattedBill.Rental) { formattedBill.rentalInfo = formattedBill.Rental; } if (formattedBill.MeterReading) { formattedBill.meterReading = formattedBill.MeterReading; } return formattedBill; }; // 构建账单查询条件 const buildBillWhere = (req) => { const { type, category, status, roomId, renterId, rentalId, billMonth, startDate, endDate } = req.query; const where = { tenantId: req.user.tenantId, isDeleted: 0 }; if (type) { where.type = type; } if (category) { where.category = category; } if (status) { where.status = status; } if (roomId) { where.roomId = roomId; } if (renterId) { where.renterId = renterId; } if (rentalId) { where.rentalId = rentalId; } if (billMonth) { where.billMonth = billMonth; } if (startDate && endDate) { where.billDate = { [Op.between]: [startDate, endDate] }; } return where; }; // 账单关联查询配置 const billIncludeOptions = [ { model: Room, attributes: ['roomNumber'], include: [{ model: require('../models').Apartment, attributes: ['name'] }] }, { model: Renter, attributes: ['name', 'phone'] }, { model: Rental, attributes: ['startDate', 'endDate'] }, { model: MeterReading, as: 'meterReading', required: false, where: { isDeleted: 0 } } ]; // 获取所有账单(支持搜索和分页) const getAllBills = async (req, res) => { try { const { page = 1, pageSize = 10 } = req.query; const where = buildBillWhere(req); // 计算偏移量 const offset = (page - 1) * pageSize; // 查询账单数据 const { count, rows } = await Bill.findAndCountAll({ where, include: billIncludeOptions, limit: parseInt(pageSize), offset: parseInt(offset), order: [['createTime', 'DESC']] }); // 格式化数据 const formattedBills = rows.map(formatBillData); // 返回结果 response.success(res, '获取成功', { list: formattedBills, total: count, page: parseInt(page), pageSize: parseInt(pageSize) }); } catch (error) { console.error('获取账单列表失败:', error); response.serverError(res, '获取账单列表失败', error); } }; // 获取账单列表(不分页) const getBillsList = async (req, res) => { try { const where = buildBillWhere(req); // 查询所有账单数据(不分页) const rows = await Bill.findAll({ where, include: billIncludeOptions, order: [['createTime', 'DESC']] }); // 格式化数据 const formattedBills = rows.map(formatBillData); // 返回结果 response.success(res, '获取成功', formattedBills); } catch (error) { console.error('获取账单列表失败:', error); response.serverError(res, '获取账单列表失败', error); } }; // 获取单个账单 const getBillById = async (req, res) => { try { const { id } = req.params; const bill = await Bill.findOne({ where: { id, tenantId: req.user.tenantId, isDeleted: 0 }, include: [ { model: Room, attributes: ['roomNumber'], include: [{ model: require('../models').Apartment, attributes: ['name'] }] }, { model: Renter, attributes: ['name', 'phone'] }, { model: Rental, attributes: ['startDate', 'endDate'] }, { model: MeterReading, as: 'meterReading', required: false, where: { isDeleted: 0 } }, { model: BillPayment, as: 'billPayments', required: false, where: { isDeleted: 0 }, order: [['paymentTime', 'DESC']] } ] }); if (!bill) { return response.notFound(res, '账单不存在'); } const formattedBill = formatBillData(bill); response.success(res, '获取成功', formattedBill); } catch (error) { console.error('获取账单详情失败:', error); response.serverError(res, '获取账单详情失败', error); } }; // 创建账单 const createBill = async (req, res) => { try { const { roomId, renterId, rentalId, type, category, receivableAmount, billMonth, billDate, remark } = req.body; const bill = await Bill.create({ billNo: generateBillNo(), roomId: roomId || null, renterId: renterId || null, rentalId: rentalId || null, type, category, receivableAmount, receivedAmount: 0, status: 'unpaid', billMonth: billMonth || null, billDate: billDate || new Date(), remark, tenantId: req.user.tenantId, createBy: req.user.id, updateBy: req.user.id }); const formattedBill = formatBillData(bill); response.created(res, '创建成功', formattedBill); } catch (error) { console.error('创建账单失败:', error); response.serverError(res, '创建账单失败', error); } }; // 更新账单 const updateBill = async (req, res) => { try { const { id } = req.params; const { roomId, renterId, rentalId, type, category, receivableAmount, billMonth, billDate, remark } = req.body; const bill = await Bill.findOne({ where: { id, tenantId: req.user.tenantId, isDeleted: 0 } }); if (!bill) { return response.notFound(res, '账单不存在'); } await bill.update({ roomId: roomId || null, renterId: renterId || null, rentalId: rentalId || null, type, category, receivableAmount, billMonth: billMonth || null, billDate: billDate || bill.billDate, remark, updateBy: req.user.id }); response.success(res, '更新成功', formatBillData(bill)); } catch (error) { console.error('更新账单失败:', error); response.serverError(res, '更新账单失败', error); } }; // 删除账单(软删除) const deleteBill = async (req, res) => { try { const { id } = req.params; const bill = await Bill.findOne({ where: { id, tenantId: req.user.tenantId, isDeleted: 0 } }); if (!bill) { return response.notFound(res, '账单不存在'); } await bill.update({ isDeleted: 1, updateBy: req.user.id }); response.success(res, '账单删除成功'); } catch (error) { console.error('删除账单失败:', error); response.serverError(res, '删除账单失败', error); } }; // 账单收款 const receivePayment = async (req, res) => { try { const { id } = req.params; const { amount, paymentMethod } = req.body; const bill = await Bill.findOne({ where: { id, tenantId: req.user.tenantId, isDeleted: 0 } }); if (!bill) { return response.notFound(res, '账单不存在'); } const newReceivedAmount = parseFloat(bill.receivedAmount) + parseFloat(amount); const receivableAmount = parseFloat(bill.receivableAmount); let newStatus = 'unpaid'; if (newReceivedAmount >= receivableAmount) { newStatus = 'paid'; } else if (newReceivedAmount > 0) { newStatus = 'partial'; } await bill.update({ receivedAmount: newReceivedAmount, status: newStatus, paymentMethod: paymentMethod || bill.paymentMethod, updateBy: req.user.id }); response.success(res, '收款成功', formatBillData(bill)); } catch (error) { console.error('账单收款失败:', error); response.serverError(res, '账单收款失败', error); } }; // 获取账单统计 const getBillStatistics = async (req, res) => { try { const { startDate, endDate, billMonth } = req.query; const tenantId = req.user.tenantId; const where = { tenantId, isDeleted: 0 }; if (startDate && endDate) { where.billDate = { [Op.between]: [startDate, endDate] }; } if (billMonth) { where.billMonth = billMonth; } // 收入统计 const incomeStats = await Bill.findAll({ where: { ...where, type: 'income' }, attributes: [ 'category', [Bill.sequelize.fn('SUM', Bill.sequelize.col('receivableAmount')), 'totalReceivable'], [Bill.sequelize.fn('SUM', Bill.sequelize.col('receivedAmount')), 'totalReceived'] ], group: ['category'] }); // 支出统计 const expenseStats = await Bill.findAll({ where: { ...where, type: 'expense' }, attributes: [ 'category', [Bill.sequelize.fn('SUM', Bill.sequelize.col('receivableAmount')), 'totalReceivable'], [Bill.sequelize.fn('SUM', Bill.sequelize.col('receivedAmount')), 'totalReceived'] ], group: ['category'] }); // 状态统计 const statusStats = await Bill.findAll({ where, attributes: [ 'status', [Bill.sequelize.fn('COUNT', Bill.sequelize.col('id')), 'count'], [Bill.sequelize.fn('SUM', Bill.sequelize.col('receivableAmount')), 'totalReceivable'], [Bill.sequelize.fn('SUM', Bill.sequelize.col('receivedAmount')), 'totalReceived'] ], group: ['status'] }); // 计算汇总 const totalReceivable = await Bill.sum('receivableAmount', { where }); const totalReceived = await Bill.sum('receivedAmount', { where }); response.success(res, '获取成功', { summary: { totalReceivable: totalReceivable || 0, totalReceived: totalReceived || 0, totalUnreceived: (totalReceivable || 0) - (totalReceived || 0) }, income: incomeStats, expense: expenseStats, status: statusStats }); } catch (error) { console.error('获取账单统计失败:', error); response.serverError(res, '获取账单统计失败', error); } }; module.exports = { getAllBills, getBillsList, getBillById, createBill, updateBill, deleteBill, receivePayment, getBillStatistics };