feat: implement utility recharge feature with CRUD operations and balance management

- Added UtilityRecharge entity to manage utility recharge records.
- Created DTO for utility recharge with validation.
- Developed UtilityBalancesService to handle business logic for utility recharges and balances.
- Implemented UtilityBalancesController for API endpoints related to utility recharges.
- Introduced utility balance management in BillsService to reflect utility charges in bills.
- Updated DepositsService to include personal expense amounts in deposit records.
- Created frontend page for managing utility balances and recharges with Ant Design components.
- Refactored existing bill generation logic to exclude personal expenses and integrate utility charges.
- Updated tests to cover new utility recharge functionality and ensure existing features remain intact.
This commit is contained in:
2026-07-14 18:05:04 +08:00
parent ce5fd1c6cb
commit 3c4e3bf162
23 changed files with 719 additions and 321 deletions

View File

@@ -38,6 +38,8 @@ const isFormValidationError = (error: unknown) =>
&& error !== null
&& Array.isArray((error as { errorFields?: unknown }).errorFields);
const money = (value: unknown) => Number(Number(value || 0).toFixed(2));
const DepositsPage: React.FC = () => {
const [data, setData] = useState<any[]>([]);
const [students, setStudents] = useState<any[]>([]);
@@ -221,8 +223,13 @@ const DepositsPage: React.FC = () => {
size="small"
type="primary"
onClick={() => {
const personalExpenseAmount = money(record.personalExpenseAmount);
const depositAmount = money(record.amount);
setRefundModal(record);
refundForm.setFieldsValue({ refundDate: dayjs(), deductionAmount: 0 });
refundForm.setFieldsValue({
refundDate: dayjs(),
deductionAmount: Math.min(personalExpenseAmount, depositAmount),
});
}}
>
退
@@ -365,15 +372,25 @@ const DepositsPage: React.FC = () => {
>
<Form form={refundForm} layout="vertical">
<div style={{ marginBottom: 16, padding: 12, background: '#f5f5f5', borderRadius: 8 }}>
: <strong>¥{Number(refundModal?.amount || 0).toFixed(2)}</strong>
<div>
: <strong>¥{money(refundModal?.amount).toFixed(2)}</strong>
</div>
<div style={{ marginTop: 4 }}>
:{' '}
<strong>¥{money(refundModal?.personalExpenseAmount).toFixed(2)}</strong>
</div>
</div>
<Form.Item name="refundDate" label="退还日期" rules={[{ required: true }]}>
<DatePicker style={{ width: '100%' }} placeholder="选择退还日期" format="YYYY-MM-DD" />
</Form.Item>
<Form.Item name="deductionAmount" label="扣除金额(元)" extra="如无扣除填0">
<Form.Item
name="deductionAmount"
label="扣除金额(元)"
extra="自动填入该学生个人附加费用总和;超过押金金额时按押金金额封顶"
>
<InputNumber
min={0}
max={Number(refundModal?.amount || 500)}
max={money(refundModal?.amount || 500)}
precision={2}
style={{ width: '100%' }}
/>