|
|
@@ -0,0 +1,351 @@
|
|
|
+import type {
|
|
|
+ Contract,
|
|
|
+ ContractFormData,
|
|
|
+ ContractSearchParams,
|
|
|
+ PaymentPlan,
|
|
|
+ PaymentRecord
|
|
|
+} from '@/types/contract'
|
|
|
+import { delay, generateId } from '@/utils'
|
|
|
+
|
|
|
+const now = new Date().toISOString()
|
|
|
+
|
|
|
+const mockContracts: Contract[] = [
|
|
|
+ {
|
|
|
+ id: 'ct_001',
|
|
|
+ code: 'HT-2026-001',
|
|
|
+ name: '2026年度清洁服务合同',
|
|
|
+ customerId: 'cust_001',
|
|
|
+ customerName: '阿里巴巴科技有限公司',
|
|
|
+ projectId: 'proj_001',
|
|
|
+ projectName: '阿里园区日常保洁',
|
|
|
+ type: 'annual',
|
|
|
+ amount: 580000,
|
|
|
+ paidAmount: 348000,
|
|
|
+ paymentMethod: 'installment',
|
|
|
+ startDate: '2026-01-01',
|
|
|
+ endDate: '2026-12-31',
|
|
|
+ signDate: '2025-12-25',
|
|
|
+ serviceContent: '园区日常保洁、垃圾清运、卫生间清洁、公共区域消毒等',
|
|
|
+ status: 'active',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_001', contractId: 'ct_001', phase: 1, amount: 145000, plannedDate: '2026-01-10', actualDate: '2026-01-12', actualAmount: 145000, status: 'paid' },
|
|
|
+ { id: 'pp_002', contractId: 'ct_001', phase: 2, amount: 145000, plannedDate: '2026-04-10', actualDate: '2026-04-15', actualAmount: 145000, status: 'paid' },
|
|
|
+ { id: 'pp_003', contractId: 'ct_001', phase: 3, amount: 145000, plannedDate: '2026-07-10', status: 'unpaid' },
|
|
|
+ { id: 'pp_004', contractId: 'ct_001', phase: 4, amount: 145000, plannedDate: '2026-10-10', status: 'unpaid' }
|
|
|
+ ],
|
|
|
+ attachments: [{ id: 'att_001', name: '合同扫描件.pdf', url: '', size: 2048, createTime: now }],
|
|
|
+ createTime: '2025-12-25T10:00:00',
|
|
|
+ updateTime: now
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'ct_002',
|
|
|
+ code: 'HT-2026-002',
|
|
|
+ name: '季度保洁合同',
|
|
|
+ customerId: 'cust_005',
|
|
|
+ customerName: '万科地产杭州分公司',
|
|
|
+ projectId: 'proj_002',
|
|
|
+ projectName: '万科中心保洁',
|
|
|
+ type: 'quarterly',
|
|
|
+ amount: 120000,
|
|
|
+ paidAmount: 60000,
|
|
|
+ paymentMethod: 'installment',
|
|
|
+ startDate: '2026-04-01',
|
|
|
+ endDate: '2026-06-30',
|
|
|
+ signDate: '2026-03-28',
|
|
|
+ serviceContent: '写字楼公共区域保洁、地毯清洗、玻璃清洁',
|
|
|
+ status: 'active',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_005', contractId: 'ct_002', phase: 1, amount: 60000, plannedDate: '2026-04-05', actualDate: '2026-04-08', actualAmount: 60000, status: 'paid' },
|
|
|
+ { id: 'pp_006', contractId: 'ct_002', phase: 2, amount: 60000, plannedDate: '2026-06-05', status: 'unpaid' }
|
|
|
+ ],
|
|
|
+ attachments: [],
|
|
|
+ createTime: '2026-03-28T09:00:00',
|
|
|
+ updateTime: now
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'ct_003',
|
|
|
+ code: 'HT-2026-003',
|
|
|
+ name: '单次深度清洁',
|
|
|
+ customerId: 'cust_002',
|
|
|
+ customerName: '张三',
|
|
|
+ type: 'single',
|
|
|
+ amount: 3500,
|
|
|
+ paidAmount: 3500,
|
|
|
+ paymentMethod: 'lump_sum',
|
|
|
+ startDate: '2026-06-15',
|
|
|
+ endDate: '2026-06-15',
|
|
|
+ signDate: '2026-06-14',
|
|
|
+ serviceContent: '家庭全屋深度清洁、油烟机清洗',
|
|
|
+ status: 'completed',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_007', contractId: 'ct_003', phase: 1, amount: 3500, plannedDate: '2026-06-15', actualDate: '2026-06-15', actualAmount: 3500, status: 'paid' }
|
|
|
+ ],
|
|
|
+ attachments: [],
|
|
|
+ createTime: '2026-06-14T10:00:00',
|
|
|
+ updateTime: now
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'ct_004',
|
|
|
+ code: 'HT-2026-004',
|
|
|
+ name: '年度服务合同',
|
|
|
+ customerId: 'cust_007',
|
|
|
+ customerName: '浙江大学后勤集团',
|
|
|
+ projectId: 'proj_003',
|
|
|
+ projectName: '浙大校区保洁',
|
|
|
+ type: 'annual',
|
|
|
+ amount: 780000,
|
|
|
+ paidAmount: 234000,
|
|
|
+ paymentMethod: 'quarterly',
|
|
|
+ startDate: '2026-01-01',
|
|
|
+ endDate: '2026-12-31',
|
|
|
+ signDate: '2025-12-20',
|
|
|
+ serviceContent: '校园教学楼、宿舍区、食堂周边保洁及垃圾清运',
|
|
|
+ status: 'active',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_008', contractId: 'ct_004', phase: 1, amount: 195000, plannedDate: '2026-01-05', actualDate: '2026-01-08', actualAmount: 195000, status: 'paid' },
|
|
|
+ { id: 'pp_009', contractId: 'ct_004', phase: 2, amount: 195000, plannedDate: '2026-04-05', actualAmount: 39000, status: 'partpaid' },
|
|
|
+ { id: 'pp_010', contractId: 'ct_004', phase: 3, amount: 195000, plannedDate: '2026-07-05', status: 'unpaid' },
|
|
|
+ { id: 'pp_011', contractId: 'ct_004', phase: 4, amount: 195000, plannedDate: '2026-10-05', status: 'unpaid' }
|
|
|
+ ],
|
|
|
+ attachments: [{ id: 'att_002', name: '招标文件.pdf', url: '', size: 5120, createTime: now }],
|
|
|
+ createTime: '2025-12-20T11:00:00',
|
|
|
+ updateTime: now
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'ct_005',
|
|
|
+ code: 'HT-2026-005',
|
|
|
+ name: '月度保洁服务',
|
|
|
+ customerId: 'cust_008',
|
|
|
+ customerName: '网易网络有限公司',
|
|
|
+ projectId: 'proj_004',
|
|
|
+ projectName: '网易园区保洁',
|
|
|
+ type: 'monthly',
|
|
|
+ amount: 15000,
|
|
|
+ paidAmount: 0,
|
|
|
+ paymentMethod: 'monthly',
|
|
|
+ startDate: '2026-07-01',
|
|
|
+ endDate: '2026-07-31',
|
|
|
+ signDate: '2026-06-30',
|
|
|
+ serviceContent: '月度保洁服务,待双方签订后执行',
|
|
|
+ status: 'pending',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_012', contractId: 'ct_005', phase: 1, amount: 15000, plannedDate: '2026-07-05', status: 'unpaid' }
|
|
|
+ ],
|
|
|
+ attachments: [],
|
|
|
+ createTime: '2026-06-30T14:00:00',
|
|
|
+ updateTime: now
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'ct_006',
|
|
|
+ code: 'HT-2026-006',
|
|
|
+ name: '季度合同',
|
|
|
+ customerId: 'cust_009',
|
|
|
+ customerName: '绿城物业服务集团',
|
|
|
+ projectId: 'proj_005',
|
|
|
+ projectName: '绿城项目保洁',
|
|
|
+ type: 'quarterly',
|
|
|
+ amount: 140000,
|
|
|
+ paidAmount: 35000,
|
|
|
+ paymentMethod: 'installment',
|
|
|
+ startDate: '2026-05-01',
|
|
|
+ endDate: '2026-07-31',
|
|
|
+ signDate: '2026-04-28',
|
|
|
+ serviceContent: '住宅小区公共区域保洁、绿化养护配合',
|
|
|
+ status: 'expiring',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_013', contractId: 'ct_006', phase: 1, amount: 35000, plannedDate: '2026-05-05', actualDate: '2026-05-10', actualAmount: 35000, status: 'paid' },
|
|
|
+ { id: 'pp_014', contractId: 'ct_006', phase: 2, amount: 35000, plannedDate: '2026-06-05', status: 'unpaid' },
|
|
|
+ { id: 'pp_015', contractId: 'ct_006', phase: 3, amount: 35000, plannedDate: '2026-07-05', status: 'unpaid' },
|
|
|
+ { id: 'pp_016', contractId: 'ct_006', phase: 4, amount: 35000, plannedDate: '2026-07-25', status: 'unpaid' }
|
|
|
+ ],
|
|
|
+ attachments: [],
|
|
|
+ createTime: '2026-04-28T09:30:00',
|
|
|
+ updateTime: now
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'ct_007',
|
|
|
+ code: 'HT-2026-007',
|
|
|
+ name: '年度合同',
|
|
|
+ customerId: 'cust_003',
|
|
|
+ customerName: '杭州市政府后勤部',
|
|
|
+ projectId: 'proj_006',
|
|
|
+ projectName: '市政府后勤保洁',
|
|
|
+ type: 'annual',
|
|
|
+ amount: 1200000,
|
|
|
+ paidAmount: 960000,
|
|
|
+ paymentMethod: 'quarterly',
|
|
|
+ startDate: '2026-01-01',
|
|
|
+ endDate: '2026-12-31',
|
|
|
+ signDate: '2025-12-15',
|
|
|
+ serviceContent: '市政府办公楼日常保洁、会议保障、专项清洁',
|
|
|
+ status: 'active',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_017', contractId: 'ct_007', phase: 1, amount: 300000, plannedDate: '2026-01-05', actualDate: '2026-01-06', actualAmount: 300000, status: 'paid' },
|
|
|
+ { id: 'pp_018', contractId: 'ct_007', phase: 2, amount: 300000, plannedDate: '2026-04-05', actualDate: '2026-04-07', actualAmount: 300000, status: 'paid' },
|
|
|
+ { id: 'pp_019', contractId: 'ct_007', phase: 3, amount: 300000, plannedDate: '2026-07-05', actualDate: '2026-07-06', actualAmount: 300000, status: 'paid' },
|
|
|
+ { id: 'pp_020', contractId: 'ct_007', phase: 4, amount: 300000, plannedDate: '2026-10-05', status: 'unpaid' }
|
|
|
+ ],
|
|
|
+ attachments: [{ id: 'att_003', name: '政府采购合同.pdf', url: '', size: 8192, createTime: now }],
|
|
|
+ createTime: '2025-12-15T10:00:00',
|
|
|
+ updateTime: now
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'ct_008',
|
|
|
+ code: 'HT-2026-008',
|
|
|
+ name: '单次服务',
|
|
|
+ customerId: 'cust_006',
|
|
|
+ customerName: '李四',
|
|
|
+ type: 'single',
|
|
|
+ amount: 6000,
|
|
|
+ paidAmount: 6000,
|
|
|
+ paymentMethod: 'lump_sum',
|
|
|
+ startDate: '2026-06-20',
|
|
|
+ endDate: '2026-06-20',
|
|
|
+ signDate: '2026-06-18',
|
|
|
+ serviceContent: '搬家后全屋清洁、窗帘清洗',
|
|
|
+ status: 'completed',
|
|
|
+ paymentPlans: [
|
|
|
+ { id: 'pp_021', contractId: 'ct_008', phase: 1, amount: 6000, plannedDate: '2026-06-20', actualDate: '2026-06-20', actualAmount: 6000, status: 'paid' }
|
|
|
+ ],
|
|
|
+ attachments: [],
|
|
|
+ createTime: '2026-06-18T16:00:00',
|
|
|
+ updateTime: now
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const mockPaymentRecords: PaymentRecord[] = [
|
|
|
+ { id: 'pr_001', contractId: 'ct_001', planId: 'pp_001', amount: 145000, payDate: '2026-01-12', payer: '阿里巴巴科技有限公司', receiver: '清道夫', createTime: '2026-01-12T10:00:00' },
|
|
|
+ { id: 'pr_002', contractId: 'ct_001', planId: 'pp_002', amount: 145000, payDate: '2026-04-15', payer: '阿里巴巴科技有限公司', receiver: '清道夫', createTime: '2026-04-15T10:00:00' },
|
|
|
+ { id: 'pr_003', contractId: 'ct_002', planId: 'pp_005', amount: 60000, payDate: '2026-04-08', payer: '万科地产杭州分公司', receiver: '清道夫', createTime: '2026-04-08T10:00:00' },
|
|
|
+ { id: 'pr_004', contractId: 'ct_003', planId: 'pp_007', amount: 3500, payDate: '2026-06-15', payer: '张三', receiver: '清道夫', createTime: '2026-06-15T10:00:00' },
|
|
|
+ { id: 'pr_005', contractId: 'ct_004', planId: 'pp_008', amount: 195000, payDate: '2026-01-08', payer: '浙江大学', receiver: '清道夫', createTime: '2026-01-08T10:00:00' },
|
|
|
+ { id: 'pr_006', contractId: 'ct_004', planId: 'pp_009', amount: 39000, payDate: '2026-04-10', payer: '浙江大学', receiver: '清道夫', createTime: '2026-04-10T10:00:00' },
|
|
|
+ { id: 'pr_007', contractId: 'ct_006', planId: 'pp_013', amount: 35000, payDate: '2026-05-10', payer: '绿城物业服务集团', receiver: '清道夫', createTime: '2026-05-10T10:00:00' },
|
|
|
+ { id: 'pr_008', contractId: 'ct_007', planId: 'pp_017', amount: 300000, payDate: '2026-01-06', payer: '杭州市政府后勤部', receiver: '清道夫', createTime: '2026-01-06T10:00:00' },
|
|
|
+ { id: 'pr_009', contractId: 'ct_007', planId: 'pp_018', amount: 300000, payDate: '2026-04-07', payer: '杭州市政府后勤部', receiver: '清道夫', createTime: '2026-04-07T10:00:00' },
|
|
|
+ { id: 'pr_010', contractId: 'ct_007', planId: 'pp_019', amount: 300000, payDate: '2026-07-06', payer: '杭州市政府后勤部', receiver: '清道夫', createTime: '2026-07-06T10:00:00' },
|
|
|
+ { id: 'pr_011', contractId: 'ct_008', planId: 'pp_021', amount: 6000, payDate: '2026-06-20', payer: '李四', receiver: '清道夫', createTime: '2026-06-20T10:00:00' }
|
|
|
+]
|
|
|
+
|
|
|
+export async function getContractList(params: ContractSearchParams = {}) {
|
|
|
+ await delay(300)
|
|
|
+ let list = [...mockContracts]
|
|
|
+ if (params.keyword) {
|
|
|
+ const kw = params.keyword.trim().toLowerCase()
|
|
|
+ list = list.filter(item =>
|
|
|
+ item.name.toLowerCase().includes(kw) ||
|
|
|
+ item.code.toLowerCase().includes(kw) ||
|
|
|
+ item.customerName.toLowerCase().includes(kw)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ if (params.status) {
|
|
|
+ list = list.filter(item => item.status === params.status)
|
|
|
+ }
|
|
|
+ if (params.customerId) {
|
|
|
+ list = list.filter(item => item.customerId === params.customerId)
|
|
|
+ }
|
|
|
+ const pageNum = params.pageNum || 1
|
|
|
+ const pageSize = params.pageSize || 10
|
|
|
+ const total = list.length
|
|
|
+ const start = (pageNum - 1) * pageSize
|
|
|
+ const data = list.slice(start, start + pageSize)
|
|
|
+ return Promise.resolve({
|
|
|
+ list: data,
|
|
|
+ total,
|
|
|
+ pageNum,
|
|
|
+ pageSize,
|
|
|
+ pages: Math.ceil(total / pageSize)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+export async function getContractDetail(id: string) {
|
|
|
+ await delay(200)
|
|
|
+ const item = mockContracts.find(c => c.id === id)
|
|
|
+ return Promise.resolve(item ? { ...item } : null)
|
|
|
+}
|
|
|
+
|
|
|
+export async function getContractStats() {
|
|
|
+ await delay(200)
|
|
|
+ const totalAmount = mockContracts.reduce((sum, c) => sum + c.amount, 0)
|
|
|
+ const active = mockContracts.filter(c => c.status === 'active').length
|
|
|
+ const pending = mockContracts.filter(c => {
|
|
|
+ const unpaid = c.paymentPlans.filter(p => p.status === 'unpaid' || p.status === 'overdue')
|
|
|
+ return unpaid.length > 0 && c.status !== 'completed'
|
|
|
+ }).length
|
|
|
+ const expiring = mockContracts.filter(c => c.status === 'expiring').length
|
|
|
+ return Promise.resolve({ totalAmount, active, pending, expiring })
|
|
|
+}
|
|
|
+
|
|
|
+export async function createContract(data: ContractFormData) {
|
|
|
+ await delay(400)
|
|
|
+ const newContract: Contract = {
|
|
|
+ ...data,
|
|
|
+ id: generateId('ct'),
|
|
|
+ customerName: '',
|
|
|
+ paidAmount: data.paymentPlans.reduce((sum, p) => sum + (p.actualAmount || 0), 0),
|
|
|
+ attachments: [],
|
|
|
+ createTime: new Date().toISOString(),
|
|
|
+ updateTime: new Date().toISOString()
|
|
|
+ }
|
|
|
+ mockContracts.unshift(newContract)
|
|
|
+ return Promise.resolve(newContract)
|
|
|
+}
|
|
|
+
|
|
|
+export async function updateContract(data: ContractFormData & { id: string }) {
|
|
|
+ await delay(400)
|
|
|
+ const index = mockContracts.findIndex(c => c.id === data.id)
|
|
|
+ if (index === -1) return Promise.reject(new Error('合同不存在'))
|
|
|
+ mockContracts[index] = {
|
|
|
+ ...mockContracts[index],
|
|
|
+ ...data,
|
|
|
+ paidAmount: data.paymentPlans.reduce((sum, p) => sum + (p.actualAmount || 0), 0),
|
|
|
+ updateTime: new Date().toISOString()
|
|
|
+ }
|
|
|
+ return Promise.resolve(mockContracts[index])
|
|
|
+}
|
|
|
+
|
|
|
+export async function deleteContract(id: string) {
|
|
|
+ await delay(300)
|
|
|
+ const index = mockContracts.findIndex(c => c.id === id)
|
|
|
+ if (index === -1) return Promise.reject(new Error('合同不存在'))
|
|
|
+ mockContracts.splice(index, 1)
|
|
|
+ return Promise.resolve(true)
|
|
|
+}
|
|
|
+
|
|
|
+export async function getPaymentPlans(contractId: string) {
|
|
|
+ await delay(200)
|
|
|
+ const contract = mockContracts.find(c => c.id === contractId)
|
|
|
+ return Promise.resolve(contract ? [...contract.paymentPlans] : [])
|
|
|
+}
|
|
|
+
|
|
|
+export async function markPayment(contractId: string, planId: string, data: { actualDate: string; actualAmount: number }) {
|
|
|
+ await delay(300)
|
|
|
+ const contract = mockContracts.find(c => c.id === contractId)
|
|
|
+ if (!contract) return Promise.reject(new Error('合同不存在'))
|
|
|
+ const plan = contract.paymentPlans.find(p => p.id === planId)
|
|
|
+ if (!plan) return Promise.reject(new Error('付款计划不存在'))
|
|
|
+ plan.actualDate = data.actualDate
|
|
|
+ plan.actualAmount = data.actualAmount
|
|
|
+ plan.status = data.actualAmount >= plan.amount ? 'paid' : data.actualAmount > 0 ? 'partpaid' : 'unpaid'
|
|
|
+ contract.paidAmount = contract.paymentPlans.reduce((sum, p) => sum + (p.actualAmount || 0), 0)
|
|
|
+ contract.updateTime = new Date().toISOString()
|
|
|
+ mockPaymentRecords.push({
|
|
|
+ id: generateId('pr'),
|
|
|
+ contractId,
|
|
|
+ planId,
|
|
|
+ amount: data.actualAmount,
|
|
|
+ payDate: data.actualDate,
|
|
|
+ createTime: new Date().toISOString()
|
|
|
+ })
|
|
|
+ return Promise.resolve(plan)
|
|
|
+}
|
|
|
+
|
|
|
+export async function getPaymentRecords(contractId: string) {
|
|
|
+ await delay(200)
|
|
|
+ return Promise.resolve(mockPaymentRecords.filter(r => r.contractId === contractId))
|
|
|
+}
|
|
|
+
|
|
|
+export function getContractProgress(contract: Contract): number {
|
|
|
+ if (!contract.amount) return 0
|
|
|
+ return Math.min(100, Math.round((contract.paidAmount / contract.amount) * 100))
|
|
|
+}
|