Files
cz-web/src/api/finance.ts
2026-10-01 17:24:57 +08:00

378 lines
12 KiB
TypeScript

import type { PageResult } from '@/types/api'
import type {
Bill,
BillQuery,
BillVo,
FinanceStats,
Invoice,
InvoiceQuery,
InvoiceTitle,
InvoiceTitlePayload,
PayMethod,
PayOrderResponse,
Payment,
PaymentResult,
} from '@/types/business/finance'
import { toBills, toPlanBills, type PlanLike } from '@/utils/billVo'
import { toPayMethod } from '@/utils/payMeta'
import { request } from '@/utils/request'
const MOCK_BILLS: Bill[] = [
{
id: 'bill-001',
planName: '第一期·预付款 比例33%',
billNo: '20260813ZZ251245',
tradeNo: 'T20260819153035678K2M001',
projectName: '某大型银行数字化转型方案',
amount: 20000,
status: '已支付',
createTime: '2026-8-17 14:00',
},
{
id: 'bill-002',
planName: '第二期·进度款 比例30%',
billNo: '20260813ZZ251246',
projectName: '智慧城市大数据平台建设项目',
amount: 20100,
status: '待支付',
createTime: '2026-8-17 14:00',
},
{
id: 'bill-003',
planName: '第三期·验收款 比例27%',
billNo: '20260813ZZ251247',
projectName: '三甲医院指挥医疗信息化系统',
amount: 20000,
status: '未到期',
createTime: '2026-8-17 14:00',
},
{
id: 'bill-004',
planName: '第四期·尾款 比例10%',
billNo: '20260813ZZ251248',
projectName: '智慧城市大数据平台建设项目',
amount: 20100,
status: '未到期',
createTime: '2026-8-17 14:00',
},
{
id: 'bill-005',
planName: '第二期·进度款 比例30%',
billNo: '20260813ZZ251249',
projectName: '三甲医院指挥医疗信息化系统',
amount: 20100,
status: '待支付',
createTime: '2026-8-16 10:30',
},
{
id: 'bill-006',
planName: '第一期·预付款 比例33%',
billNo: '20260813ZZ251250',
tradeNo: 'T20260819153035678K2M002',
projectName: '某大型银行数字化转型方案',
amount: 20000,
status: '已支付',
createTime: '2026-8-15 09:12',
},
]
const MOCK_INVOICES: Invoice[] = [
{
id: 'invoice-001',
planName: '第一期·预付款 比例33%',
billNo: '20260813ZZ251245',
tradeNo: 'T20260819153035678K2M001',
projectName: '某大型银行数字化转型方案',
amount: 20000,
status: '已开票',
applyTime: '2026-8-17 14:00',
},
{
id: 'invoice-002',
planName: '第一期·预付款 比例33%',
billNo: '20260813ZZ251250',
tradeNo: 'T20260819153035678K2M002',
projectName: '某大型银行数字化转型方案',
amount: 20000,
status: '开票中',
applyTime: '2026-8-17 14:00',
},
{
id: 'invoice-003',
planName: '第二期·进度款 比例30%',
billNo: '20260813ZZ251249',
projectName: '三甲医院指挥医疗信息化系统',
amount: 20100,
status: '未开票',
applyTime: '2026-8-17 14:00',
},
]
const MOCK_TITLES: InvoiceTitle[] = [
{
id: 'title-001',
type: '企业单位',
title: '山东日照有限有限公司',
taxNo: '344683434513543543545535',
isDefault: true,
},
{
id: 'title-002',
type: '企业单位',
title: '山东省一周练七次有限公司',
taxNo: '6856754615216565476846',
isDefault: false,
},
]
const MOCK_PAYMENTS = new Map<string, Payment>()
const MOCK_RESULTS = new Map<string, PaymentResult>()
MOCK_RESULTS.set('bill-001', {
method: 'wechat',
amount: 20000,
payTime: '2026-01-21 15:00:00',
tradeNo: 'T20260819153035678K2M001',
bills: [{ ...MOCK_BILLS[0] }],
})
MOCK_RESULTS.set('bill-006', {
method: 'wechat',
amount: 20000,
payTime: '2026-01-21 15:00:00',
tradeNo: 'T20260819153035678K2M002',
bills: [{ ...MOCK_BILLS[5] }],
})
const DEFAULT_STATS: FinanceStats = {
totalExpense: 1726823900,
month: '2026年1',
pendingCount: 3,
distribution: [
{ name: '生产智造', value: 25, color: '#3370ff' },
{ name: '物联网全案', value: 25, color: '#34c724' },
{ name: '硬件电路', value: 25, color: '#ff8f1f' },
{ name: '互联网软件', value: 25, color: '#f54a45' },
],
}
export function getFinanceStats(period?: string): Promise<FinanceStats> {
void period
return Promise.resolve(structuredClone(DEFAULT_STATS))
}
function isRealBillId(id: string): boolean {
return /^\d+$/.test(id)
}
async function fetchOwnBills(): Promise<Bill[]> {
const rows = await request<BillVo[]>({ url: '/web/bill/list', method: 'get' })
return toBills(rows)
}
function matchBillKeyword(item: Bill, keyword: string): boolean {
if (!keyword) return true
return (
item.planName.includes(keyword) ||
item.billNo.includes(keyword) ||
item.projectName.includes(keyword) ||
(item.tradeNo?.includes(keyword) ?? false)
)
}
export async function getBillList(query: BillQuery): Promise<PageResult<Bill>> {
const all = await fetchOwnBills()
const filtered = all.filter((item) => {
if (query.status !== 'all' && item.status !== query.status) return false
return matchBillKeyword(item, query.keyword)
})
const start = (query.page - 1) * query.pageSize
return { list: filtered.slice(start, start + query.pageSize), total: filtered.length }
}
export async function getBillDetail(id: string): Promise<Bill | null> {
const rows = await request<BillVo[]>({ url: '/web/bill/list', method: 'get', params: { ids: id } })
return toBills(rows)[0] ?? null
}
// ═══════════════════════════════════════════════════════════════════════
// ---------------------------------------------------------------------
// ═══════════════════════════════════════════════════════════════════════
export function createPayment(billId: string, method: PayMethod): Promise<Payment> {
const bill = MOCK_BILLS.find((item) => item.id === billId)
if (!bill) return Promise.reject(new Error('账单不存在'))
const payment: Payment = {
billId,
method,
amount: bill.amount,
qrExpiresInSeconds: 300,
}
MOCK_PAYMENTS.set(billId, payment)
return Promise.resolve({ ...payment })
}
export function switchPaymentMethod(billId: string, method: PayMethod): Promise<Payment> {
return createPayment(billId, method)
}
export async function getPayResult(billId: string): Promise<PaymentResult | null> {
if (isRealBillId(billId)) {
const bill = await getBillDetail(billId)
if (!bill || bill.status !== '已支付') return null
return {
method: toPayMethod(bill.payChannel),
amount: bill.amount,
payTime: bill.payTime ?? '—',
tradeNo: bill.tradeNo ?? '—',
bills: [bill],
}
}
return getLegacyPayResult(billId)
}
function getLegacyPayResult(billId: string): Promise<PaymentResult | null> {
const existing = MOCK_RESULTS.get(billId)
if (existing) return Promise.resolve(structuredClone(existing))
const payment = MOCK_PAYMENTS.get(billId)
const bill = MOCK_BILLS.find((item) => item.id === billId)
if (!payment || !bill) return Promise.resolve(null)
const tradeNo = bill.tradeNo ?? 'T20260819153035678K2M001'
if (bill.status !== '已支付') {
bill.status = '已支付'
bill.tradeNo = tradeNo
}
const result: PaymentResult = {
method: payment.method,
amount: payment.amount,
payTime: '2026-01-21 15:00:00',
tradeNo,
bills: [{ ...bill }],
}
MOCK_RESULTS.set(billId, result)
return Promise.resolve(structuredClone(result))
}
function matchInvoiceKeyword(item: Invoice, keyword: string): boolean {
if (!keyword) return true
return (
item.planName.includes(keyword) ||
item.billNo.includes(keyword) ||
item.projectName.includes(keyword) ||
(item.tradeNo?.includes(keyword) ?? false)
)
}
export function getInvoiceList(query: InvoiceQuery): Promise<PageResult<Invoice>> {
const filtered = MOCK_INVOICES.filter((item) => {
if (query.status !== 'all' && item.status !== query.status) return false
return matchInvoiceKeyword(item, query.keyword)
})
const start = (query.page - 1) * query.pageSize
return Promise.resolve({
list: filtered.slice(start, start + query.pageSize).map((item) => ({ ...item })),
total: filtered.length,
})
}
export function applyInvoice(id: string): Promise<Invoice> {
const found = MOCK_INVOICES.find((item) => item.id === id)
if (!found) return Promise.reject(new Error('发票记录不存在'))
found.status = '开票中'
return Promise.resolve({ ...found })
}
export function getInvoiceTitles(): Promise<InvoiceTitle[]> {
return Promise.resolve(MOCK_TITLES.map((item) => ({ ...item })))
}
export function createInvoiceTitle(payload: InvoiceTitlePayload): Promise<InvoiceTitle> {
const created: InvoiceTitle = {
id: `title-${Date.now()}`,
...payload,
isDefault: false,
}
MOCK_TITLES.push(created)
return Promise.resolve({ ...created })
}
export function updateInvoiceTitle(id: string, payload: InvoiceTitlePayload): Promise<InvoiceTitle> {
const found = MOCK_TITLES.find((item) => item.id === id)
if (!found) return Promise.reject(new Error('抬头不存在'))
found.type = payload.type
found.title = payload.title
found.taxNo = payload.taxNo
return Promise.resolve({ ...found })
}
export function deleteInvoiceTitle(id: string): Promise<void> {
const index = MOCK_TITLES.findIndex((item) => item.id === id)
if (index < 0) return Promise.reject(new Error('抬头不存在'))
MOCK_TITLES.splice(index, 1)
return Promise.resolve()
}
export function setDefaultTitle(id: string): Promise<InvoiceTitle[]> {
for (const item of MOCK_TITLES) {
item.isDefault = item.id === id
}
return Promise.resolve(MOCK_TITLES.map((item) => ({ ...item })))
}
export function exportReport(): Promise<void> {
return Promise.resolve()
}
// ═══════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════════
export async function getCheckoutBills(requirementId: string, ids: string[] = []): Promise<Bill[]> {
if (!/^\d+$/.test(requirementId)) return []
const vo = await request<{ billPlans?: PlanLike[] }>({
url: `/web/requirementPool/get/finance/${requirementId}`,
method: 'get',
})
const all = toPlanBills(vo?.billPlans)
return ids.length > 0
? all.filter((bill) => ids.includes(bill.id))
: all.filter((bill) => bill.status === '待支付')
}
export async function getPlanPayResult(requirementId: string, planId: string): Promise<PaymentResult | null> {
const bills = await getCheckoutBills(requirementId, [planId])
const bill = bills[0]
if (!bill || bill.status !== '已支付') return null
return {
method: toPayMethod(bill.payChannel),
amount: bill.amount,
payTime: bill.payTime ?? '—',
tradeNo: bill.tradeNo ?? '—',
bills: [bill],
}
}
export function createWechatNativeOrder(
orderNo: string,
amount: number,
billIds: string[] = [],
): Promise<PayOrderResponse> {
return request<PayOrderResponse>({
url: '/pay/web/wechat/native',
method: 'post',
data: { orderNo, amount, billIds },
})
}
export function createAlipayPageOrder(
outTradeNo: string,
amount: number,
billIds: string[] = [],
): Promise<PayOrderResponse> {
return request<PayOrderResponse>({
url: '/pay/web/alipay/page',
method: 'post',
data: { outTradeNo, amount, billIds },
})
}