378 lines
12 KiB
TypeScript
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 },
|
|
})
|
|
}
|