This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export type AccountingTab = 'pending' | 'draft' | 'review' | 'exported' | 'result' | 'completed' | 'reversed' | 'void';
|
||||
export type EventType = 'RECEIPT' | 'INVOICE' | 'PAYABLE' | 'PAYMENT' | 'PAYMENT_REFUND';
|
||||
export type VoucherStatus =
|
||||
| 'DRAFT'
|
||||
| 'REVIEWING'
|
||||
| 'RETURNED'
|
||||
| 'APPROVED'
|
||||
| 'EXPORTED'
|
||||
| 'RESULT_RECORDED'
|
||||
| 'COMPLETED'
|
||||
| 'RESULT_REVERSED'
|
||||
| 'VOID';
|
||||
export type AccountingAction =
|
||||
| 'GENERATE_DRAFT'
|
||||
| 'EDIT'
|
||||
| 'SUBMIT'
|
||||
| 'APPROVE'
|
||||
| 'RETURN'
|
||||
| 'EXPORT'
|
||||
| 'RECORD_RESULT'
|
||||
| 'VERIFY_RESULT'
|
||||
| 'REVERSE_RESULT'
|
||||
| 'REOPEN_RESULT'
|
||||
| 'VOID';
|
||||
|
||||
export interface PageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface Reference {
|
||||
publicId: string;
|
||||
code?: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AccountReference {
|
||||
publicId: string;
|
||||
code: string;
|
||||
name: string;
|
||||
accountType: string;
|
||||
auxiliaryRequired: boolean;
|
||||
}
|
||||
|
||||
export interface AccountingEvent {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
eventType: EventType;
|
||||
sourceType: string;
|
||||
sourcePublicId: string;
|
||||
sourceBusinessNo?: string | null;
|
||||
sourceVersion: string;
|
||||
eventSequence: number;
|
||||
sourceEventKey: string;
|
||||
company: Reference;
|
||||
project?: Reference | null;
|
||||
counterparty?: Reference | null;
|
||||
businessDate: string;
|
||||
period: string;
|
||||
amount: string;
|
||||
currency: string;
|
||||
status: string;
|
||||
voucherPublicId?: string | null;
|
||||
voucherStatus?: VoucherStatus | null;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
allowedActions: AccountingAction[];
|
||||
}
|
||||
|
||||
export interface VoucherEntry {
|
||||
publicId: string;
|
||||
lineNo: number;
|
||||
direction: 'DEBIT' | 'CREDIT';
|
||||
account: Reference;
|
||||
amount: string;
|
||||
summary: string;
|
||||
auxiliaryJson?: string | null;
|
||||
}
|
||||
|
||||
export interface Voucher {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
eventPublicId: string;
|
||||
eventBusinessNo: string;
|
||||
company: Reference;
|
||||
project?: Reference | null;
|
||||
period: string;
|
||||
businessDate: string;
|
||||
summary: string;
|
||||
debitTotal: string;
|
||||
creditTotal: string;
|
||||
balanceStatus: 'BALANCED' | 'UNBALANCED';
|
||||
status: VoucherStatus;
|
||||
submittedByName?: string | null;
|
||||
reviewedByName?: string | null;
|
||||
exportedAt?: string | null;
|
||||
exportBatchNo?: string | null;
|
||||
exportSha256?: string | null;
|
||||
exportFileId?: string | null;
|
||||
ruleVersion: string;
|
||||
externalVoucherNo?: string | null;
|
||||
resultAt?: string | null;
|
||||
resultCycleNo: number;
|
||||
evidenceFileIds: string[];
|
||||
resultRecordedByName?: string | null;
|
||||
resultRecordedAt?: string | null;
|
||||
resultRecordRemark?: string | null;
|
||||
resultVerifiedByName?: string | null;
|
||||
resultVerifiedAt?: string | null;
|
||||
resultVerifyOpinion?: string | null;
|
||||
voidReason?: string | null;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
allowedActions: AccountingAction[];
|
||||
}
|
||||
|
||||
export interface VoucherDetail {
|
||||
voucher: Voucher;
|
||||
event: AccountingEvent;
|
||||
entries: VoucherEntry[];
|
||||
evidenceFiles: ResultEvidenceFile[];
|
||||
resultActions: VoucherResultAction[];
|
||||
}
|
||||
|
||||
export interface ResultEvidenceFile {
|
||||
publicId: string;
|
||||
originalName: string;
|
||||
mediaType: string;
|
||||
sizeBytes: number;
|
||||
sha256: string;
|
||||
scanStatus: string;
|
||||
}
|
||||
|
||||
export interface VoucherResultAction {
|
||||
publicId: string;
|
||||
sequenceNo: number;
|
||||
resultCycleNo: number;
|
||||
actionType: 'REVERSE' | 'REOPEN';
|
||||
reason: string;
|
||||
externalVoucherNo?: string | null;
|
||||
resultAt?: string | null;
|
||||
resultRecordedByName?: string | null;
|
||||
resultRecordedAt?: string | null;
|
||||
resultRecordRemark?: string | null;
|
||||
resultVerifiedByName?: string | null;
|
||||
resultVerifiedAt?: string | null;
|
||||
resultVerifyOpinion?: string | null;
|
||||
evidenceFiles: ResultEvidenceFile[];
|
||||
operatedByName: string;
|
||||
operatedAt: string;
|
||||
versionBefore: number;
|
||||
versionAfter: number;
|
||||
}
|
||||
|
||||
export interface VoucherExport {
|
||||
voucher: Voucher;
|
||||
batchNo: string;
|
||||
sha256: string;
|
||||
fileId: string;
|
||||
rowCount: number;
|
||||
}
|
||||
|
||||
export interface RecordAccountingResultPayload {
|
||||
version: number;
|
||||
externalVoucherNo: string;
|
||||
resultAt: string;
|
||||
evidenceFileIds: string[];
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface AccountingEventQuery {
|
||||
period?: string;
|
||||
projectId?: string;
|
||||
eventType?: EventType;
|
||||
keyword?: string;
|
||||
sort: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface VoucherQuery extends AccountingEventQuery {
|
||||
tab: Exclude<AccountingTab, 'pending'>;
|
||||
status?: VoucherStatus;
|
||||
externalVoucherNo?: string;
|
||||
}
|
||||
|
||||
export interface VoucherEntryPayload {
|
||||
lineNo: number;
|
||||
direction: 'DEBIT' | 'CREDIT';
|
||||
accountId: string;
|
||||
amount: string;
|
||||
summary: string;
|
||||
auxiliaryJson?: string;
|
||||
}
|
||||
|
||||
export interface VoucherUpdatePayload {
|
||||
version: number;
|
||||
period: string;
|
||||
businessDate: string;
|
||||
summary: string;
|
||||
entries: VoucherEntryPayload[];
|
||||
changeReason: string;
|
||||
}
|
||||
|
||||
interface PageEnvelope<T> {
|
||||
data: T[];
|
||||
meta: PageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
async function page<T>(url: string, params: AccountingEventQuery | VoucherQuery) {
|
||||
const envelope = await request.get<PageEnvelope<T>>({ url, params }, { isTransformResponse: false });
|
||||
return {
|
||||
items: Array.isArray(envelope?.data) ? envelope.data : [],
|
||||
meta: envelope?.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope?.requestId || '',
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeEvent(event: AccountingEvent): AccountingEvent {
|
||||
return { ...event, allowedActions: Array.isArray(event.allowedActions) ? event.allowedActions : [] };
|
||||
}
|
||||
|
||||
function normalizeVoucher(voucher: Voucher): Voucher {
|
||||
return {
|
||||
...voucher,
|
||||
allowedActions: Array.isArray(voucher.allowedActions) ? voucher.allowedActions : [],
|
||||
evidenceFileIds: Array.isArray(voucher.evidenceFileIds) ? voucher.evidenceFileIds : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeVoucherDetail(detail: VoucherDetail): VoucherDetail {
|
||||
return {
|
||||
...detail,
|
||||
voucher: normalizeVoucher(detail.voucher),
|
||||
entries: Array.isArray(detail?.entries) ? detail.entries : [],
|
||||
evidenceFiles: Array.isArray(detail?.evidenceFiles) ? detail.evidenceFiles : [],
|
||||
resultActions: Array.isArray(detail?.resultActions)
|
||||
? detail.resultActions.map((action) => ({
|
||||
...action,
|
||||
evidenceFiles: Array.isArray(action.evidenceFiles) ? action.evidenceFiles : [],
|
||||
}))
|
||||
: [],
|
||||
event: normalizeEvent(detail.event),
|
||||
};
|
||||
}
|
||||
|
||||
export function getAccountingAccounts() {
|
||||
return request
|
||||
.get<AccountReference[]>({ url: '/accounting/accounts' })
|
||||
.then((rows) => (Array.isArray(rows) ? rows : []));
|
||||
}
|
||||
|
||||
export async function getAccountingEvents(params: AccountingEventQuery) {
|
||||
const result = await page<AccountingEvent>('/accounting/events', params);
|
||||
return { ...result, items: result.items.map(normalizeEvent) };
|
||||
}
|
||||
|
||||
export async function getAccountingVouchers(params: VoucherQuery) {
|
||||
const result = await page<Voucher>('/accounting/vouchers', params);
|
||||
return { ...result, items: result.items.map(normalizeVoucher) };
|
||||
}
|
||||
|
||||
export async function getAccountingVoucher(publicId: string) {
|
||||
const detail = await request.get<VoucherDetail>({ url: `/accounting/vouchers/${publicId}` });
|
||||
return normalizeVoucherDetail(detail);
|
||||
}
|
||||
|
||||
export function generateVoucherDraft(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/events/${publicId}/generate-draft`,
|
||||
data: { version, ruleVersion: 'ACCOUNTING-RULE-V1' },
|
||||
...commandHeaders('accounting-generate-draft', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function updateAccountingVoucher(publicId: string, payload: VoucherUpdatePayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.patch<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}`,
|
||||
data: payload,
|
||||
...commandHeaders('accounting-update', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function submitAccountingVoucher(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/submit`,
|
||||
data: { version },
|
||||
...commandHeaders('accounting-submit', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function approveAccountingVoucher(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/approve`,
|
||||
data: { version },
|
||||
...commandHeaders('accounting-approve', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function returnAccountingVoucher(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/return`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('accounting-return', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function exportAccountingVoucher(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request.post<VoucherExport>({
|
||||
url: `/accounting/vouchers/${publicId}/export`,
|
||||
data: { version },
|
||||
...commandHeaders('accounting-export', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function downloadAccountingVoucherExport(publicId: string) {
|
||||
return request.get<AxiosResponse<Blob>>(
|
||||
{ url: `/accounting/vouchers/${publicId}/export-file`, responseType: 'blob' },
|
||||
{ isReturnNativeResponse: true },
|
||||
);
|
||||
}
|
||||
|
||||
export function downloadAccountingVoucherResultEvidence(voucherPublicId: string, filePublicId: string) {
|
||||
return request.get<AxiosResponse<Blob>>(
|
||||
{
|
||||
url: `/accounting/vouchers/${voucherPublicId}/result-files/${filePublicId}/content`,
|
||||
responseType: 'blob',
|
||||
},
|
||||
{ isReturnNativeResponse: true },
|
||||
);
|
||||
}
|
||||
|
||||
export function recordAccountingResult(
|
||||
publicId: string,
|
||||
payload: RecordAccountingResultPayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/record-result`,
|
||||
data: payload,
|
||||
...commandHeaders('accounting-record-result', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function verifyAccountingResult(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/verify-result`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('accounting-verify-result', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function reverseAccountingResult(publicId: string, version: number, reason: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/reverse-result`,
|
||||
data: { version, reason },
|
||||
...commandHeaders('accounting-reverse-result', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function reopenAccountingResult(publicId: string, version: number, reason: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/reopen-result`,
|
||||
data: { version, reason },
|
||||
...commandHeaders('accounting-reopen-result', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
|
||||
export function voidAccountingVoucher(publicId: string, version: number, reason: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<VoucherDetail>({
|
||||
url: `/accounting/vouchers/${publicId}/void`,
|
||||
data: { version, reason },
|
||||
...commandHeaders('accounting-void', idempotencyKey),
|
||||
})
|
||||
.then(normalizeVoucherDetail);
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export const ARCHIVE_API_BASE = '/archives';
|
||||
|
||||
const array = <T>(value: T[] | null | undefined): T[] => (Array.isArray(value) ? value : []);
|
||||
|
||||
export interface ArchivePageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface ArchiveReference {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ArchivePackageItem {
|
||||
publicId: string;
|
||||
itemType: string;
|
||||
itemName: string;
|
||||
objectType?: string | null;
|
||||
objectPublicId?: string | null;
|
||||
filePublicId?: string | null;
|
||||
fileName?: string | null;
|
||||
fileSha256?: string | null;
|
||||
required: boolean;
|
||||
status: string;
|
||||
missingReason?: string | null;
|
||||
returnReason?: string | null;
|
||||
notApplicableReason?: string | null;
|
||||
notApplicableRequestedByName?: string | null;
|
||||
notApplicableRequestedAt?: string | null;
|
||||
notApplicableReviewedByName?: string | null;
|
||||
notApplicableReviewedAt?: string | null;
|
||||
notApplicableReviewOpinion?: string | null;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ArchivePackageAction {
|
||||
sequenceNo: number;
|
||||
actionCode: string;
|
||||
fromStatus?: string | null;
|
||||
toStatus?: string | null;
|
||||
opinion?: string | null;
|
||||
actorName?: string | null;
|
||||
occurredAt: string;
|
||||
manifestSha256?: string | null;
|
||||
}
|
||||
|
||||
export interface ArchivePackageObjectSnapshot {
|
||||
publicId: string;
|
||||
itemType: string;
|
||||
objectType: string;
|
||||
objectPublicId: string;
|
||||
objectBusinessNo?: string | null;
|
||||
objectName?: string | null;
|
||||
objectStatus: string;
|
||||
objectVersion?: number | null;
|
||||
objectFormType?: string | null;
|
||||
sourceVersionPublicId?: string | null;
|
||||
snapshotSha256: string;
|
||||
capturedAt: string;
|
||||
}
|
||||
|
||||
export interface ArchivePackage {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
company: ArchiveReference;
|
||||
project: ArchiveReference;
|
||||
packageVersion: number;
|
||||
rootPackagePublicId?: string | null;
|
||||
supersedesPackagePublicId?: string | null;
|
||||
revisionReason?: string | null;
|
||||
ruleVersion: string;
|
||||
completeness: string;
|
||||
missingCount: number;
|
||||
retentionYears: number;
|
||||
retentionExpiresOn?: string | null;
|
||||
retentionStatus: 'PENDING' | 'ACTIVE' | 'DUE_SOON' | 'EXPIRED' | 'FROZEN';
|
||||
physicalLocation?: string | null;
|
||||
frozen: boolean;
|
||||
status: string;
|
||||
manifestSha256?: string | null;
|
||||
submittedByName?: string | null;
|
||||
reviewedByName?: string | null;
|
||||
submittedAt?: string | null;
|
||||
archivedAt?: string | null;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
allowedActions: string[];
|
||||
items: ArchivePackageItem[];
|
||||
objectSnapshots: ArchivePackageObjectSnapshot[];
|
||||
actions: ArchivePackageAction[];
|
||||
}
|
||||
|
||||
export interface ArchiveFile {
|
||||
publicId: string;
|
||||
displayName: string;
|
||||
mediaType: string;
|
||||
originalType?: string | null;
|
||||
sizeBytes: number;
|
||||
sha256: string;
|
||||
scanStatus: string;
|
||||
companyPublicId: string;
|
||||
companyName: string;
|
||||
projectPublicId: string;
|
||||
projectBusinessNo: string;
|
||||
projectName: string;
|
||||
contractPublicId?: string | null;
|
||||
counterpartyPublicId?: string | null;
|
||||
formType?: string | null;
|
||||
documentType: string;
|
||||
seriesPublicId: string;
|
||||
versionNo: number;
|
||||
archiveStatus: string;
|
||||
borrowStatus?: string | null;
|
||||
packagePublicId?: string | null;
|
||||
packageStatus?: string | null;
|
||||
uploadedBy: string;
|
||||
uploadedAt: string;
|
||||
sensitive: boolean;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ArchiveFileDetail {
|
||||
file: ArchiveFile;
|
||||
versions: ArchiveFile[];
|
||||
borrows: ArchiveBorrow[];
|
||||
}
|
||||
|
||||
export interface ArchiveBorrow {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
filePublicId: string;
|
||||
fileName: string;
|
||||
projectPublicId: string;
|
||||
projectName: string;
|
||||
purpose: string;
|
||||
dueAt: string;
|
||||
status: string;
|
||||
applicantName: string;
|
||||
approvedByName?: string | null;
|
||||
approvedAt?: string | null;
|
||||
reviewOpinion?: string | null;
|
||||
returnCondition?: string | null;
|
||||
returnedByName?: string | null;
|
||||
returnedAt?: string | null;
|
||||
version: number;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ArchivePackageQuery {
|
||||
view?: 'prepare' | 'check' | 'returned' | 'archived';
|
||||
companyId?: string;
|
||||
projectId?: string;
|
||||
status?: string;
|
||||
completeness?: string;
|
||||
frozen?: boolean;
|
||||
keyword?: string;
|
||||
sort?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ArchiveFileQuery {
|
||||
companyId?: string;
|
||||
projectId?: string;
|
||||
contractId?: string;
|
||||
counterpartyId?: string;
|
||||
formType?: string;
|
||||
fileName?: string;
|
||||
uploadedBy?: string;
|
||||
uploadedDateFrom?: string;
|
||||
uploadedDateTo?: string;
|
||||
originalType?: string;
|
||||
archiveStatus?: string;
|
||||
scanStatus?: string;
|
||||
sort?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ArchiveBorrowQuery {
|
||||
status?: string;
|
||||
keyword?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ArchivePackageCreatePayload {
|
||||
projectId: string;
|
||||
retentionYears?: number;
|
||||
physicalLocation?: string;
|
||||
}
|
||||
|
||||
export interface ArchivePackageCommandPayload {
|
||||
version: number;
|
||||
opinion?: string;
|
||||
returnItemIds?: string[];
|
||||
}
|
||||
|
||||
export interface ArchiveFreezePayload {
|
||||
version: number;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface ArchivePackageRevisionPayload {
|
||||
version: number;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface ArchiveNotApplicablePayload {
|
||||
version: number;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface ArchiveNotApplicableReviewPayload {
|
||||
version: number;
|
||||
approved: boolean;
|
||||
opinion: string;
|
||||
}
|
||||
|
||||
export interface ArchiveFileMetadata {
|
||||
projectId: string;
|
||||
packageId?: string;
|
||||
packageItemId?: string;
|
||||
contractId?: string;
|
||||
counterpartyId?: string;
|
||||
formType?: string;
|
||||
fileType: string;
|
||||
originalType?: string;
|
||||
replacesFileId?: string;
|
||||
replacementReason?: string;
|
||||
notApplicableReason?: string;
|
||||
}
|
||||
|
||||
export interface ArchiveBorrowCreatePayload {
|
||||
fileId: string;
|
||||
purpose: string;
|
||||
dueAt: string;
|
||||
}
|
||||
|
||||
export interface ArchiveBorrowCommandPayload {
|
||||
version: number;
|
||||
opinion?: string;
|
||||
returnCondition?: string;
|
||||
}
|
||||
|
||||
interface PageEnvelope<T> {
|
||||
data: T[];
|
||||
meta: ArchivePageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export function normalizeArchivePackageItem(item: Partial<ArchivePackageItem> | null | undefined): ArchivePackageItem {
|
||||
return { ...(item || {}), allowedActions: array(item?.allowedActions) } as ArchivePackageItem;
|
||||
}
|
||||
|
||||
export function normalizeArchivePackage(value: Partial<ArchivePackage> | null | undefined): ArchivePackage {
|
||||
const item = value || {};
|
||||
return {
|
||||
...item,
|
||||
allowedActions: array(item.allowedActions),
|
||||
items: array(item.items).map(normalizeArchivePackageItem),
|
||||
objectSnapshots: array(item.objectSnapshots),
|
||||
actions: array(item.actions),
|
||||
} as ArchivePackage;
|
||||
}
|
||||
|
||||
export function normalizeArchiveFile(value: Partial<ArchiveFile> | null | undefined): ArchiveFile {
|
||||
return { ...(value || {}), allowedActions: array(value?.allowedActions) } as ArchiveFile;
|
||||
}
|
||||
|
||||
export function normalizeArchiveBorrow(value: Partial<ArchiveBorrow> | null | undefined): ArchiveBorrow {
|
||||
return { ...(value || {}), allowedActions: array(value?.allowedActions) } as ArchiveBorrow;
|
||||
}
|
||||
|
||||
export function normalizeArchiveFileDetail(value: Partial<ArchiveFileDetail> | null | undefined): ArchiveFileDetail {
|
||||
return {
|
||||
...(value || {}),
|
||||
file: normalizeArchiveFile(value?.file),
|
||||
versions: array(value?.versions).map(normalizeArchiveFile),
|
||||
borrows: array(value?.borrows).map(normalizeArchiveBorrow),
|
||||
} as ArchiveFileDetail;
|
||||
}
|
||||
|
||||
export function archiveErrorText(error: unknown, fallback: string): string {
|
||||
const value = error as Error & { status?: number; code?: string; fieldErrors?: Record<string, string> };
|
||||
const fields = value.fieldErrors ? Object.values(value.fieldErrors).join(';') : '';
|
||||
if (value.status === 401) return '登录会话已失效,请重新登录后重试。';
|
||||
if (value.status === 403) return '当前身份没有访问该数据或执行该操作的权限。';
|
||||
if (value.status === 404) return '记录不存在、已被删除或当前身份无权查看。';
|
||||
if (value.code === 'VERSION_CONFLICT' || value.status === 409)
|
||||
return value.message || '数据已发生变化,请刷新后重试。';
|
||||
if (value.status === 422) {
|
||||
return fields
|
||||
? `${value.message || '当前条件不满足业务规则'}:${fields}`
|
||||
: value.message || '当前条件不满足业务规则,请检查后重试。';
|
||||
}
|
||||
if (value.status && value.status >= 500) return value.message || '服务处理失败,请稍后重试。';
|
||||
return fields ? `${value.message || fallback}:${fields}` : value.message || fallback;
|
||||
}
|
||||
|
||||
async function page<T>(url: string, params: Record<string, unknown>) {
|
||||
const envelope = await request.get<PageEnvelope<T>>({ url, params }, { isTransformResponse: false });
|
||||
return {
|
||||
items: array(envelope.data),
|
||||
meta: envelope.meta || {
|
||||
page: Number(params.page) || 1,
|
||||
size: Number(params.size) || 20,
|
||||
totalElements: 0,
|
||||
totalPages: 0,
|
||||
},
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function getArchivePackages(params: ArchivePackageQuery) {
|
||||
return page<ArchivePackage>(`${ARCHIVE_API_BASE}/packages`, params as unknown as Record<string, unknown>).then(
|
||||
(result) => ({
|
||||
...result,
|
||||
items: result.items.map(normalizeArchivePackage),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getArchivePackage(publicId: string) {
|
||||
return request.get<ArchivePackage>({ url: `${ARCHIVE_API_BASE}/packages/${publicId}` }).then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function createArchivePackage(payload: ArchivePackageCreatePayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-create', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function checkArchivePackage(publicId: string, payload: ArchivePackageCommandPayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${publicId}/check`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-check', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function submitArchivePackage(publicId: string, payload: ArchivePackageCommandPayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${publicId}/submit`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-submit', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function returnArchivePackage(publicId: string, payload: ArchivePackageCommandPayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${publicId}/return`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-return', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function reviseArchivePackage(
|
||||
publicId: string,
|
||||
payload: ArchivePackageRevisionPayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${publicId}/revise`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-revise', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function archiveArchivePackage(
|
||||
publicId: string,
|
||||
payload: ArchivePackageCommandPayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${publicId}/archive`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-archive', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function freezeArchivePackage(publicId: string, payload: ArchiveFreezePayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${publicId}/freeze`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-freeze', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function unfreezeArchivePackage(
|
||||
publicId: string,
|
||||
payload: ArchivePackageCommandPayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${publicId}/unfreeze`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-package-unfreeze', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function markArchiveItemNotApplicable(
|
||||
packageId: string,
|
||||
itemId: string,
|
||||
payload: ArchiveNotApplicablePayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${packageId}/items/${itemId}/not-applicable`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-item-not-applicable', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function reviewArchiveItemNotApplicable(
|
||||
packageId: string,
|
||||
itemId: string,
|
||||
payload: ArchiveNotApplicableReviewPayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<ArchivePackage>({
|
||||
url: `${ARCHIVE_API_BASE}/packages/${packageId}/items/${itemId}/not-applicable/review`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-item-not-applicable-review', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchivePackage);
|
||||
}
|
||||
|
||||
export function getArchiveFiles(params: ArchiveFileQuery) {
|
||||
return page<ArchiveFile>(`${ARCHIVE_API_BASE}/files`, params as unknown as Record<string, unknown>).then(
|
||||
(result) => ({
|
||||
...result,
|
||||
items: result.items.map(normalizeArchiveFile),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getArchiveFile(publicId: string) {
|
||||
return request
|
||||
.get<ArchiveFileDetail>({ url: `${ARCHIVE_API_BASE}/files/${publicId}` })
|
||||
.then(normalizeArchiveFileDetail);
|
||||
}
|
||||
|
||||
export function rescanArchiveFile(publicId: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchiveFile>({
|
||||
url: `${ARCHIVE_API_BASE}/files/${publicId}/rescan`,
|
||||
data: {},
|
||||
...commandHeaders('archive-file-rescan', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchiveFile);
|
||||
}
|
||||
|
||||
export function getArchiveFileContent(publicId: string, preview = false) {
|
||||
return request.get<AxiosResponse<Blob>>(
|
||||
{ url: `${ARCHIVE_API_BASE}/files/${publicId}/content`, params: { preview }, responseType: 'blob' },
|
||||
{ isReturnNativeResponse: true },
|
||||
);
|
||||
}
|
||||
|
||||
export function uploadArchiveFile(metadata: ArchiveFileMetadata, file: File, idempotencyKey?: string) {
|
||||
const formData = new FormData();
|
||||
formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
|
||||
formData.append('file', file, file.name);
|
||||
return request
|
||||
.post<ArchiveFile>({
|
||||
url: `${ARCHIVE_API_BASE}/files`,
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
...commandHeaders('archive-file-upload', idempotencyKey).headers,
|
||||
},
|
||||
})
|
||||
.then(normalizeArchiveFile);
|
||||
}
|
||||
|
||||
export function getArchiveBorrows(params: ArchiveBorrowQuery) {
|
||||
return page<ArchiveBorrow>(`${ARCHIVE_API_BASE}/borrows`, params as unknown as Record<string, unknown>).then(
|
||||
(result) => ({
|
||||
...result,
|
||||
items: result.items.map(normalizeArchiveBorrow),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function createArchiveBorrow(payload: ArchiveBorrowCreatePayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchiveBorrow>({
|
||||
url: `${ARCHIVE_API_BASE}/borrows`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-borrow-create', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchiveBorrow);
|
||||
}
|
||||
|
||||
export function approveArchiveBorrow(publicId: string, payload: ArchiveBorrowCommandPayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchiveBorrow>({
|
||||
url: `${ARCHIVE_API_BASE}/borrows/${publicId}/approve`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-borrow-approve', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchiveBorrow);
|
||||
}
|
||||
|
||||
export function rejectArchiveBorrow(publicId: string, payload: ArchiveBorrowCommandPayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchiveBorrow>({
|
||||
url: `${ARCHIVE_API_BASE}/borrows/${publicId}/reject`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-borrow-reject', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchiveBorrow);
|
||||
}
|
||||
|
||||
export function returnArchiveBorrow(publicId: string, payload: ArchiveBorrowCommandPayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<ArchiveBorrow>({
|
||||
url: `${ARCHIVE_API_BASE}/borrows/${publicId}/return`,
|
||||
data: payload,
|
||||
...commandHeaders('archive-borrow-return', idempotencyKey),
|
||||
})
|
||||
.then(normalizeArchiveBorrow);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
export interface AuditPageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export type AuditSort = 'occurredAt,asc' | 'occurredAt,desc' | 'eventSequence,asc' | 'eventSequence,desc';
|
||||
|
||||
export interface AuditQuery {
|
||||
occurredFrom?: string;
|
||||
occurredTo?: string;
|
||||
actorId?: string;
|
||||
identityCode?: string;
|
||||
objectType?: string;
|
||||
objectId?: string;
|
||||
action?: string;
|
||||
requestId?: string;
|
||||
result?: string;
|
||||
keyword?: string;
|
||||
sort: AuditSort;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface AuditLog {
|
||||
publicId: string;
|
||||
eventSequence: number;
|
||||
requestId: string;
|
||||
userPublicId?: string | null;
|
||||
username: string;
|
||||
activeRole?: string | null;
|
||||
companyPublicId?: string | null;
|
||||
projectPublicId?: string | null;
|
||||
actionCode: string;
|
||||
objectType: string;
|
||||
objectPublicId?: string | null;
|
||||
resultCode: string;
|
||||
reason?: string | null;
|
||||
beforeJson?: string | null;
|
||||
afterJson?: string | null;
|
||||
occurredAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface AuditExport {
|
||||
exportId: string;
|
||||
rowCount: number;
|
||||
sha256: string;
|
||||
fileName: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
function requiredString(value: unknown, field: string): string {
|
||||
if (typeof value !== 'string' || !value.trim()) throw new Error(`审计导出响应缺少${field}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function nonNegativeInteger(value: unknown, field: string): number {
|
||||
if (!Number.isInteger(value) || Number(value) < 0) throw new Error(`审计导出响应中的${field}无效`);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
export function normalizeAuditExport(value: AuditExport): AuditExport {
|
||||
return {
|
||||
exportId: requiredString(value?.exportId, '导出编号'),
|
||||
rowCount: nonNegativeInteger(value?.rowCount, '导出行数'),
|
||||
sha256: requiredString(value?.sha256, '文件摘要'),
|
||||
fileName: requiredString(value?.fileName, '文件名'),
|
||||
content: requiredString(value?.content, '文件内容'),
|
||||
};
|
||||
}
|
||||
|
||||
interface AuditEnvelope {
|
||||
data: AuditLog[];
|
||||
meta: AuditPageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export async function getAuditLogs(params: AuditQuery) {
|
||||
const envelope = await request.get<AuditEnvelope>(
|
||||
{ url: '/audit/logs', params },
|
||||
{ isTransformResponse: false, joinTime: false },
|
||||
);
|
||||
return {
|
||||
items: Array.isArray(envelope?.data)
|
||||
? envelope.data.map((row) => ({
|
||||
...row,
|
||||
allowedActions: Array.isArray(row?.allowedActions) ? row.allowedActions : [],
|
||||
}))
|
||||
: [],
|
||||
meta: envelope?.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope?.requestId || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function getAuditLog(publicId: string) {
|
||||
return request.get<AuditLog>({ url: `/audit/logs/${publicId}` }, { joinTime: false }).then((row) => ({
|
||||
...row,
|
||||
allowedActions: Array.isArray(row?.allowedActions) ? row.allowedActions : [],
|
||||
}));
|
||||
}
|
||||
|
||||
export function exportAuditLogs(payload: Omit<AuditQuery, 'page' | 'size'>) {
|
||||
return request.post<AuditExport>({ url: '/audit/exports', data: payload }).then(normalizeAuditExport);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
export interface UserView {
|
||||
publicId: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
departmentName?: string;
|
||||
mustChangePassword: boolean;
|
||||
authorizationScopes?: AuthorizationScopeView[];
|
||||
recentLogins?: RecentLoginView[];
|
||||
}
|
||||
|
||||
function normalizeUser(user: UserView | null | undefined): UserView {
|
||||
return {
|
||||
publicId: user?.publicId || '',
|
||||
username: user?.username || '',
|
||||
displayName: user?.displayName || '',
|
||||
departmentName: user?.departmentName || '',
|
||||
mustChangePassword: Boolean(user?.mustChangePassword),
|
||||
authorizationScopes: Array.isArray(user?.authorizationScopes) ? user.authorizationScopes : [],
|
||||
recentLogins: Array.isArray(user?.recentLogins) ? user.recentLogins : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRole(role: RoleView): RoleView {
|
||||
return {
|
||||
code: String(role?.code || ''),
|
||||
name: role?.name || role?.code || '未命名身份',
|
||||
description: role?.description,
|
||||
workbenchRoute: role?.workbenchRoute,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSession(session: SessionView): SessionView {
|
||||
return {
|
||||
...session,
|
||||
authenticated: Boolean(session?.authenticated),
|
||||
user: normalizeUser(session?.user),
|
||||
roles: Array.isArray(session?.roles) ? session.roles.map(normalizeRole) : [],
|
||||
permissions: Array.isArray(session?.permissions) ? session.permissions : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSessionSummary(session: SessionSummaryView): SessionSummaryView {
|
||||
return {
|
||||
sessionId: String(session?.sessionId || ''),
|
||||
createdAt: session?.createdAt || '',
|
||||
lastAccessedAt: session?.lastAccessedAt || '',
|
||||
expiresAt: session?.expiresAt || '',
|
||||
current: Boolean(session?.current),
|
||||
deviceSummary: session?.deviceSummary,
|
||||
maskedIp: session?.maskedIp,
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuthorizationScopeView {
|
||||
rowKey?: string;
|
||||
roleCode: string;
|
||||
roleName: string;
|
||||
scopeType: 'GLOBAL' | 'COMPANY' | 'PROJECT' | string;
|
||||
companyPublicId?: string;
|
||||
companyCode?: string;
|
||||
companyName?: string;
|
||||
projectPublicId?: string;
|
||||
projectCode?: string;
|
||||
projectName?: string;
|
||||
permissionCount: number;
|
||||
amountLimit?: number;
|
||||
}
|
||||
|
||||
export interface RecentLoginView {
|
||||
occurredAt: string;
|
||||
deviceSummary: string;
|
||||
maskedIp: string;
|
||||
}
|
||||
|
||||
export interface RoleView {
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
workbenchRoute?: string;
|
||||
}
|
||||
|
||||
export interface SessionView {
|
||||
authenticated: boolean;
|
||||
user: UserView;
|
||||
roles: RoleView[];
|
||||
activeRole?: string | null;
|
||||
permissions: string[];
|
||||
permissionVersion?: string;
|
||||
sessionExpiresAt?: string;
|
||||
absoluteSessionExpiresAt?: string;
|
||||
}
|
||||
|
||||
export interface SessionSummaryView {
|
||||
sessionId: string;
|
||||
createdAt: string;
|
||||
lastAccessedAt: string;
|
||||
expiresAt: string;
|
||||
current: boolean;
|
||||
deviceSummary?: string;
|
||||
maskedIp?: string;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface PasswordChangeRequest {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
export interface CsrfView {
|
||||
headerName: string;
|
||||
parameterName: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export function getCsrf() {
|
||||
return request.get<CsrfView>({ url: '/auth/csrf' }, { isTransformResponse: true, retry: { count: 0, delay: 0 } });
|
||||
}
|
||||
|
||||
export function login(payload: LoginRequest) {
|
||||
return request
|
||||
.post<SessionView>({ url: '/auth/login', data: payload }, { retry: { count: 0, delay: 0 } })
|
||||
.then(normalizeSession);
|
||||
}
|
||||
|
||||
export function getSession() {
|
||||
return request.get<SessionView>({ url: '/auth/session' }).then(normalizeSession);
|
||||
}
|
||||
|
||||
export function getRoles() {
|
||||
return request
|
||||
.get<RoleView[]>({ url: '/auth/roles' })
|
||||
.then((roles) => (Array.isArray(roles) ? roles.map(normalizeRole) : []));
|
||||
}
|
||||
|
||||
export function getProfile() {
|
||||
return request.get<UserView>({ url: '/auth/profile' }).then(normalizeUser);
|
||||
}
|
||||
|
||||
export function selectRole(roleCode: string) {
|
||||
return request
|
||||
.post<SessionView>({ url: '/auth/select-role', data: { roleCode } }, { retry: { count: 0, delay: 0 } })
|
||||
.then(normalizeSession);
|
||||
}
|
||||
|
||||
export function changePassword(payload: PasswordChangeRequest) {
|
||||
return request
|
||||
.patch<SessionView>({ url: '/auth/password', data: payload }, { retry: { count: 0, delay: 0 } })
|
||||
.then(normalizeSession);
|
||||
}
|
||||
|
||||
export function listSessions() {
|
||||
return request
|
||||
.get<SessionSummaryView[]>({ url: '/auth/sessions' })
|
||||
.then((sessions) => (Array.isArray(sessions) ? sessions.map(normalizeSessionSummary) : []));
|
||||
}
|
||||
|
||||
export function deleteSession(sessionId: string) {
|
||||
return request.delete<void>(
|
||||
{ url: `/auth/sessions/${encodeURIComponent(sessionId)}` },
|
||||
{ retry: { count: 0, delay: 0 } },
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteAllSessions() {
|
||||
return request.delete<void>({ url: '/auth/sessions' }, { retry: { count: 0, delay: 0 } });
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return request.post<void>({ url: '/auth/logout' }, { retry: { count: 0, delay: 0 } });
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
function randomPart(): string {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
|
||||
/** Every mutating request gets a stable key for the lifetime of that call. */
|
||||
export function commandHeaders(prefix: string, key?: string) {
|
||||
return { headers: { 'Idempotency-Key': key ?? `${prefix}-${randomPart()}` } };
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export interface ContractCostPageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
interface ApiEnvelope<T> {
|
||||
data: T;
|
||||
meta?: ContractCostPageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface ContractCostReference {
|
||||
publicId: string;
|
||||
code?: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ContractCostReferences {
|
||||
companies: ContractCostReference[];
|
||||
projects: ContractCostReference[];
|
||||
counterparties: ContractCostReference[];
|
||||
costCategories: ContractCostReference[];
|
||||
}
|
||||
|
||||
export interface ContractCostQuery {
|
||||
companyId?: string;
|
||||
projectId?: string;
|
||||
counterpartyId?: string;
|
||||
keyword?: string;
|
||||
status?: string;
|
||||
costCategoryId?: string;
|
||||
businessDateFrom?: string;
|
||||
businessDateTo?: string;
|
||||
sort: string;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface ContractCostLedgerItem {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
name: string;
|
||||
company: ContractCostReference;
|
||||
project: ContractCostReference;
|
||||
counterparty: ContractCostReference;
|
||||
originalAmount: string;
|
||||
approvedChangeAmount: string;
|
||||
settlementAmount?: string | null;
|
||||
invoicedAmount: string;
|
||||
pendingPayableAmount: string;
|
||||
payableAmount: string;
|
||||
paidAmount: string;
|
||||
availableAmount: string;
|
||||
currency: string;
|
||||
status: string;
|
||||
version: number;
|
||||
updatedAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ContractCostSummary {
|
||||
contractCount: number;
|
||||
originalAmount: string;
|
||||
approvedChangeAmount: string;
|
||||
settlementAmount: string;
|
||||
invoicedAmount: string;
|
||||
pendingPayableAmount: string;
|
||||
payableAmount: string;
|
||||
paidAmount: string;
|
||||
availableAmount: string;
|
||||
}
|
||||
|
||||
export interface ContractCostPage {
|
||||
items: ContractCostLedgerItem[];
|
||||
summary: ContractCostSummary;
|
||||
}
|
||||
|
||||
export interface ContractPayableLine {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
businessDate: string;
|
||||
costCategory?: ContractCostReference | null;
|
||||
amount: string;
|
||||
invoicedAmount: string;
|
||||
paidAmount: string;
|
||||
unpaidAmount: string;
|
||||
status: string;
|
||||
version: number;
|
||||
updatedAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ContractChangeLine {
|
||||
publicId: string;
|
||||
changeNo: string;
|
||||
changeAmount: string;
|
||||
effectiveDate: string;
|
||||
reason: string;
|
||||
status: string;
|
||||
version: number;
|
||||
createdByName: string;
|
||||
createdAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ContractSettlementLine {
|
||||
publicId: string;
|
||||
settlementNo: string;
|
||||
settlementAmount: string;
|
||||
settlementDate: string;
|
||||
sourceDocumentId?: string | null;
|
||||
status: string;
|
||||
version: number;
|
||||
createdByName: string;
|
||||
createdAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ContractInvoiceLine {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
requestedDate: string;
|
||||
invoiceType: string;
|
||||
amount: string;
|
||||
taxRate?: string | null;
|
||||
invoiceNo?: string | null;
|
||||
issuedDate?: string | null;
|
||||
status: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ContractPaymentLine {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
requestedDate: string;
|
||||
requestedAmount: string;
|
||||
approvedAmount?: string | null;
|
||||
purpose?: string | null;
|
||||
status: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ContractCostDetail {
|
||||
contract: ContractCostLedgerItem;
|
||||
changes: ContractChangeLine[];
|
||||
settlements: ContractSettlementLine[];
|
||||
payables: ContractPayableLine[];
|
||||
invoices: ContractInvoiceLine[];
|
||||
payments: ContractPaymentLine[];
|
||||
}
|
||||
|
||||
function normalizeLedgerItem(item: ContractCostLedgerItem): ContractCostLedgerItem {
|
||||
return { ...item, allowedActions: Array.isArray(item?.allowedActions) ? item.allowedActions : [] };
|
||||
}
|
||||
|
||||
function normalizeContractCostDetail(detail: ContractCostDetail): ContractCostDetail {
|
||||
return {
|
||||
...detail,
|
||||
contract: normalizeLedgerItem(detail.contract),
|
||||
changes: (Array.isArray(detail?.changes) ? detail.changes : []).map((row) => ({
|
||||
...row,
|
||||
allowedActions: Array.isArray(row?.allowedActions) ? row.allowedActions : [],
|
||||
})),
|
||||
settlements: (Array.isArray(detail?.settlements) ? detail.settlements : []).map((row) => ({
|
||||
...row,
|
||||
allowedActions: Array.isArray(row?.allowedActions) ? row.allowedActions : [],
|
||||
})),
|
||||
payables: (Array.isArray(detail?.payables) ? detail.payables : []).map((row) => ({
|
||||
...row,
|
||||
allowedActions: Array.isArray(row?.allowedActions) ? row.allowedActions : [],
|
||||
})),
|
||||
invoices: Array.isArray(detail?.invoices) ? detail.invoices : [],
|
||||
payments: Array.isArray(detail?.payments) ? detail.payments : [],
|
||||
};
|
||||
}
|
||||
|
||||
export async function getContractCostPage(params: ContractCostQuery) {
|
||||
const envelope = await request.get<ApiEnvelope<ContractCostPage>>(
|
||||
{ url: '/contract-costs/contracts', params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
data: {
|
||||
items: Array.isArray(envelope?.data?.items) ? envelope.data.items.map(normalizeLedgerItem) : [],
|
||||
summary: envelope?.data?.summary || {
|
||||
contractCount: 0,
|
||||
originalAmount: '0.00',
|
||||
approvedChangeAmount: '0.00',
|
||||
settlementAmount: '0.00',
|
||||
invoicedAmount: '0.00',
|
||||
pendingPayableAmount: '0.00',
|
||||
payableAmount: '0.00',
|
||||
paidAmount: '0.00',
|
||||
availableAmount: '0.00',
|
||||
},
|
||||
},
|
||||
meta: envelope.meta || {
|
||||
page: params.page || 1,
|
||||
size: params.size || 20,
|
||||
totalElements: 0,
|
||||
totalPages: 0,
|
||||
},
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function getContractCostReferences() {
|
||||
return request.get<ContractCostReferences>({ url: '/contract-costs/references' });
|
||||
}
|
||||
|
||||
export function getContractCostDetail(publicId: string) {
|
||||
return request
|
||||
.get<ContractCostDetail>({ url: `/contract-costs/contracts/${publicId}` })
|
||||
.then(normalizeContractCostDetail);
|
||||
}
|
||||
|
||||
export interface ContractChangeCreatePayload {
|
||||
changeNo: string;
|
||||
changeAmount: string;
|
||||
effectiveDate: string;
|
||||
reason: string;
|
||||
contractVersion: number;
|
||||
}
|
||||
|
||||
export interface ContractSettlementCreatePayload {
|
||||
settlementNo: string;
|
||||
settlementAmount: string;
|
||||
settlementDate: string;
|
||||
sourceDocumentId: string;
|
||||
contractVersion: number;
|
||||
}
|
||||
|
||||
export interface ContractPayableCreatePayload {
|
||||
businessNo: string;
|
||||
costCategoryId: string;
|
||||
sourceDocumentId: string;
|
||||
businessDate: string;
|
||||
amount: string;
|
||||
contractVersion: number;
|
||||
}
|
||||
|
||||
export function createContractChange(contractId: string, data: ContractChangeCreatePayload, idempotencyKey?: string) {
|
||||
return request.post<ContractCostDetail>({
|
||||
url: `/contract-costs/contracts/${contractId}/changes`,
|
||||
data,
|
||||
...commandHeaders('contract-cost-change-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function voidContractChange(changeId: string, version: number, reason: string, idempotencyKey?: string) {
|
||||
return request.post<ContractCostDetail>({
|
||||
url: `/contract-costs/changes/${changeId}/void`,
|
||||
data: { version, reason },
|
||||
...commandHeaders('contract-cost-change-void', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function createContractSettlement(
|
||||
contractId: string,
|
||||
data: ContractSettlementCreatePayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request.post<ContractCostDetail>({
|
||||
url: `/contract-costs/contracts/${contractId}/settlements`,
|
||||
data,
|
||||
...commandHeaders('contract-cost-settlement-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function voidContractSettlement(settlementId: string, version: number, reason: string, idempotencyKey?: string) {
|
||||
return request.post<ContractCostDetail>({
|
||||
url: `/contract-costs/settlements/${settlementId}/void`,
|
||||
data: { version, reason },
|
||||
...commandHeaders('contract-cost-settlement-void', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function createContractPayable(contractId: string, data: ContractPayableCreatePayload, idempotencyKey?: string) {
|
||||
return request.post<ContractCostDetail>({
|
||||
url: `/contract-costs/contracts/${contractId}/payables`,
|
||||
data,
|
||||
...commandHeaders('contract-cost-payable-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function confirmContractPayable(payableId: string, version: number, idempotencyKey?: string) {
|
||||
return request.post<ContractCostDetail>({
|
||||
url: `/contract-costs/payables/${payableId}/confirm`,
|
||||
data: { version },
|
||||
...commandHeaders('contract-cost-payable-confirm', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function voidContractPayable(payableId: string, version: number, reason: string, idempotencyKey?: string) {
|
||||
return request.post<ContractCostDetail>({
|
||||
url: `/contract-costs/payables/${payableId}/void`,
|
||||
data: { version, reason },
|
||||
...commandHeaders('contract-cost-payable-void', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function exportContractCostCsv(params: Omit<ContractCostQuery, 'page' | 'size'>) {
|
||||
return request.get<AxiosResponse<Blob>>(
|
||||
{ url: '/contract-costs/contracts/export', params, responseType: 'blob' },
|
||||
{ isReturnNativeResponse: true },
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
export type GovernanceResource = 'users' | 'roles' | 'scopes' | 'templates' | 'parameters';
|
||||
|
||||
export interface GovernancePageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface GovernanceRow {
|
||||
resource: GovernanceResource;
|
||||
publicId: string;
|
||||
username?: string;
|
||||
displayName?: string;
|
||||
departmentName?: string;
|
||||
enabled?: boolean;
|
||||
mustChangePassword?: boolean;
|
||||
roleCodes?: string[];
|
||||
code?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
sortOrder?: number;
|
||||
memberCount?: number;
|
||||
userPublicId?: string;
|
||||
roleCode?: string;
|
||||
permissionCode?: string;
|
||||
scopeType?: string;
|
||||
companyPublicId?: string;
|
||||
projectPublicId?: string;
|
||||
amountLimit?: string;
|
||||
status?: string;
|
||||
formType?: string;
|
||||
templateVersion?: number;
|
||||
schemaVersion?: number;
|
||||
parameterGroup?: string;
|
||||
versionNo?: number;
|
||||
version?: number;
|
||||
publishedAt?: string;
|
||||
effectiveAt?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface GovernanceResourceView {
|
||||
resource: GovernanceResource;
|
||||
publicId: string;
|
||||
businessCode: string;
|
||||
status: string;
|
||||
version: number;
|
||||
values: Record<string, unknown>;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface GovernanceCreatePayload {
|
||||
code?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
username?: string;
|
||||
displayName?: string;
|
||||
departmentName?: string;
|
||||
password?: string;
|
||||
roleCode?: string;
|
||||
permissionCode?: string;
|
||||
userId?: string;
|
||||
scopeType?: string;
|
||||
companyId?: string;
|
||||
projectId?: string;
|
||||
amountLimit?: number;
|
||||
formType?: string;
|
||||
templateVersion?: number;
|
||||
schemaVersion?: number;
|
||||
parameterGroup?: string;
|
||||
versionNo?: number;
|
||||
valueJson?: string;
|
||||
roleCodes?: string[];
|
||||
permissionCodes?: string[];
|
||||
}
|
||||
|
||||
export interface GovernanceUpdatePayload extends GovernanceCreatePayload {
|
||||
version: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface GovernanceCommandPayload {
|
||||
version: number;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
interface ListEnvelope {
|
||||
data: GovernanceRow[];
|
||||
meta: GovernancePageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
function normalizeGovernanceView(view: GovernanceResourceView): GovernanceResourceView {
|
||||
return {
|
||||
...view,
|
||||
values: view?.values && typeof view.values === 'object' ? view.values : {},
|
||||
allowedActions: Array.isArray(view?.allowedActions) ? view.allowedActions : [],
|
||||
};
|
||||
}
|
||||
|
||||
export async function getGovernanceResource(
|
||||
resource: GovernanceResource,
|
||||
page: number,
|
||||
size: number,
|
||||
keyword?: string,
|
||||
) {
|
||||
const envelope = await request.get<ListEnvelope>(
|
||||
{ url: `/admin/${resource}`, params: { keyword: keyword || undefined, page, size } },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
items: Array.isArray(envelope?.data) ? envelope.data : [],
|
||||
meta: envelope.meta || { page, size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function getGovernanceDetail(resource: GovernanceResource, publicId: string) {
|
||||
return request.get<GovernanceResourceView>({ url: `/admin/${resource}/${publicId}` }).then(normalizeGovernanceView);
|
||||
}
|
||||
|
||||
export function createGovernanceResource(resource: GovernanceResource, payload: GovernanceCreatePayload) {
|
||||
return request
|
||||
.post<GovernanceResourceView>({ url: `/admin/${resource}`, data: payload })
|
||||
.then(normalizeGovernanceView);
|
||||
}
|
||||
|
||||
export function updateGovernanceResource(
|
||||
resource: GovernanceResource,
|
||||
publicId: string,
|
||||
payload: GovernanceUpdatePayload,
|
||||
) {
|
||||
return request
|
||||
.patch<GovernanceResourceView>({ url: `/admin/${resource}/${publicId}`, data: payload })
|
||||
.then(normalizeGovernanceView);
|
||||
}
|
||||
|
||||
export function commandGovernanceResource(
|
||||
resource: GovernanceResource,
|
||||
publicId: string,
|
||||
command: 'enable' | 'disable' | 'publish' | 'restore',
|
||||
payload: GovernanceCommandPayload,
|
||||
) {
|
||||
return request
|
||||
.post<GovernanceResourceView>({ url: `/admin/${resource}/${publicId}/${command}`, data: payload })
|
||||
.then(normalizeGovernanceView);
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
export type MasterDataResource =
|
||||
| 'companies'
|
||||
| 'departments'
|
||||
| 'counterparties'
|
||||
| 'bank-accounts'
|
||||
| 'contracts'
|
||||
| 'cost-categories'
|
||||
| 'accounts'
|
||||
| 'tax-rates';
|
||||
|
||||
export type MasterDataAction = 'EDIT' | 'SUBMIT' | 'REVIEW' | 'DISABLE';
|
||||
export type LegacyIdentifierResource = MasterDataResource | 'projects';
|
||||
export type LegacyIdentifierAction = 'CREATE' | 'DISABLE';
|
||||
|
||||
export interface MasterDataPageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
interface ApiEnvelope<T> {
|
||||
data: T;
|
||||
meta?: MasterDataPageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface MasterDataReference {
|
||||
publicId: string;
|
||||
code?: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface MasterDataReferences {
|
||||
companies: MasterDataReference[];
|
||||
projects: MasterDataReference[];
|
||||
counterparties: MasterDataReference[];
|
||||
costCategories: MasterDataReference[];
|
||||
}
|
||||
|
||||
export interface MasterDataBase {
|
||||
publicId: string;
|
||||
status: string;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
allowedActions: MasterDataAction[];
|
||||
}
|
||||
|
||||
export type MasterDataSnapshot = Record<string, unknown>;
|
||||
|
||||
export interface MasterDataVersion {
|
||||
auditId: string;
|
||||
version: number;
|
||||
status: string;
|
||||
actionCode: string;
|
||||
actorName: string;
|
||||
changedAt: string;
|
||||
before: MasterDataSnapshot | null;
|
||||
after: MasterDataSnapshot | null;
|
||||
}
|
||||
|
||||
export interface LegacyIdentifier {
|
||||
publicId: string;
|
||||
resource: LegacyIdentifierResource;
|
||||
sourceSystem: string;
|
||||
legacyCode: string;
|
||||
targetPublicId: string;
|
||||
status: 'ACTIVE' | 'DISABLED';
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
allowedActions: LegacyIdentifierAction[];
|
||||
}
|
||||
|
||||
export interface LegacyIdentifierResolution {
|
||||
mappingPublicId: string;
|
||||
resource: LegacyIdentifierResource;
|
||||
sourceSystem: string;
|
||||
legacyCode: string;
|
||||
targetPublicId: string;
|
||||
}
|
||||
|
||||
export interface CompanyMasterData extends MasterDataBase {
|
||||
businessNo: string;
|
||||
name: string;
|
||||
taxNo?: string | null;
|
||||
}
|
||||
|
||||
export interface CounterpartyMasterData extends MasterDataBase {
|
||||
businessNo: string;
|
||||
type: string;
|
||||
name: string;
|
||||
taxNo?: string | null;
|
||||
contactName?: string | null;
|
||||
contactPhone?: string | null;
|
||||
}
|
||||
|
||||
export interface BankAccountMasterData extends MasterDataBase {
|
||||
ownerType: string;
|
||||
owner: MasterDataReference;
|
||||
accountCategory: string;
|
||||
accountName: string;
|
||||
bankName: string;
|
||||
maskedAccountNo: string;
|
||||
versionNo: number;
|
||||
validFrom: string;
|
||||
validTo?: string | null;
|
||||
}
|
||||
|
||||
export interface ContractMasterData extends MasterDataBase {
|
||||
company: MasterDataReference;
|
||||
project: MasterDataReference;
|
||||
counterparty: MasterDataReference;
|
||||
businessNo: string;
|
||||
name: string;
|
||||
originalAmount: string;
|
||||
approvedChangeAmount: string;
|
||||
settlementAmount: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface DictionaryMasterData extends MasterDataBase {
|
||||
resource: 'departments' | 'cost-categories' | 'accounts' | 'tax-rates';
|
||||
code: string;
|
||||
name: string;
|
||||
parent?: MasterDataReference | null;
|
||||
accountType?: string | null;
|
||||
auxiliaryRequired?: boolean | null;
|
||||
rate?: string | null;
|
||||
}
|
||||
|
||||
export type MasterDataItem =
|
||||
CompanyMasterData | CounterpartyMasterData | BankAccountMasterData | ContractMasterData | DictionaryMasterData;
|
||||
|
||||
function normalizeMasterDataItem(item: MasterDataItem): MasterDataItem {
|
||||
return {
|
||||
...item,
|
||||
allowedActions: Array.isArray(item?.allowedActions) ? item.allowedActions : [],
|
||||
} as MasterDataItem;
|
||||
}
|
||||
|
||||
function normalizeReferences(references: MasterDataReferences): MasterDataReferences {
|
||||
return {
|
||||
companies: Array.isArray(references?.companies) ? references.companies : [],
|
||||
projects: Array.isArray(references?.projects) ? references.projects : [],
|
||||
counterparties: Array.isArray(references?.counterparties) ? references.counterparties : [],
|
||||
costCategories: Array.isArray(references?.costCategories) ? references.costCategories : [],
|
||||
};
|
||||
}
|
||||
|
||||
export interface MasterDataQuery {
|
||||
keyword?: string;
|
||||
status?: string;
|
||||
type?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface MasterDataVersionQuery {
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface LegacyIdentifierQuery {
|
||||
keyword?: string;
|
||||
status?: 'ACTIVE' | 'DISABLED';
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface LegacyIdentifierPageMeta extends MasterDataPageMeta {
|
||||
allowedActions: LegacyIdentifierAction[];
|
||||
}
|
||||
|
||||
export interface LegacyIdentifierCreatePayload {
|
||||
sourceSystem: string;
|
||||
legacyCode: string;
|
||||
}
|
||||
|
||||
export interface CompanyCreatePayload {
|
||||
businessNo: string;
|
||||
name: string;
|
||||
taxNo?: string;
|
||||
}
|
||||
|
||||
export interface CompanyUpdatePayload {
|
||||
name: string;
|
||||
taxNo?: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface CounterpartyCreatePayload {
|
||||
businessNo: string;
|
||||
type: string;
|
||||
name: string;
|
||||
taxNo?: string;
|
||||
contactName?: string;
|
||||
contactPhone?: string;
|
||||
}
|
||||
|
||||
export interface CounterpartyUpdatePayload extends Omit<CounterpartyCreatePayload, 'businessNo'> {
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface BankAccountCreatePayload {
|
||||
ownerType: string;
|
||||
ownerId: string;
|
||||
accountCategory: string;
|
||||
accountName: string;
|
||||
bankName: string;
|
||||
accountNo: string;
|
||||
validFrom: string;
|
||||
validTo?: string;
|
||||
}
|
||||
|
||||
export interface BankAccountUpdatePayload {
|
||||
accountCategory: string;
|
||||
accountName: string;
|
||||
bankName: string;
|
||||
accountNo?: string;
|
||||
validFrom: string;
|
||||
validTo?: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface ContractCreatePayload {
|
||||
companyId: string;
|
||||
projectId: string;
|
||||
counterpartyId: string;
|
||||
businessNo: string;
|
||||
name: string;
|
||||
originalAmount: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ContractUpdatePayload extends Omit<ContractCreatePayload, 'businessNo'> {
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface DictionaryCreatePayload {
|
||||
code: string;
|
||||
name: string;
|
||||
parentId?: string;
|
||||
accountType?: string;
|
||||
auxiliaryRequired?: boolean;
|
||||
rate?: string;
|
||||
}
|
||||
|
||||
export interface DictionaryUpdatePayload extends Omit<DictionaryCreatePayload, 'code'> {
|
||||
version: number;
|
||||
}
|
||||
|
||||
export type MasterDataCreatePayload =
|
||||
| CompanyCreatePayload
|
||||
| CounterpartyCreatePayload
|
||||
| BankAccountCreatePayload
|
||||
| ContractCreatePayload
|
||||
| DictionaryCreatePayload;
|
||||
|
||||
export type MasterDataUpdatePayload =
|
||||
| CompanyUpdatePayload
|
||||
| CounterpartyUpdatePayload
|
||||
| BankAccountUpdatePayload
|
||||
| ContractUpdatePayload
|
||||
| DictionaryUpdatePayload;
|
||||
|
||||
export interface MasterDataReviewPayload {
|
||||
version: number;
|
||||
decision: 'APPROVE' | 'RETURN';
|
||||
opinion: string;
|
||||
}
|
||||
|
||||
export async function getMasterDataPage(resource: MasterDataResource, params: MasterDataQuery) {
|
||||
const envelope = await request.get<ApiEnvelope<MasterDataItem[]>>(
|
||||
{ url: `/masterdata/${resource}`, params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
items: Array.isArray(envelope?.data) ? envelope.data.map(normalizeMasterDataItem) : [],
|
||||
meta: envelope.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function getMasterDataDetail(resource: MasterDataResource, publicId: string) {
|
||||
return request.get<MasterDataItem>({ url: `/masterdata/${resource}/${publicId}` }).then(normalizeMasterDataItem);
|
||||
}
|
||||
|
||||
export async function getMasterDataVersions(
|
||||
resource: MasterDataResource,
|
||||
publicId: string,
|
||||
params: MasterDataVersionQuery,
|
||||
) {
|
||||
const envelope = await request.get<ApiEnvelope<MasterDataVersion[]>>(
|
||||
{ url: `/masterdata/${resource}/${publicId}/versions`, params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
items: Array.isArray(envelope?.data) ? envelope.data : [],
|
||||
meta: envelope.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getLegacyIdentifiers(
|
||||
resource: LegacyIdentifierResource,
|
||||
publicId: string,
|
||||
params: LegacyIdentifierQuery,
|
||||
) {
|
||||
const envelope = await request.get<ApiEnvelope<LegacyIdentifier[]>>(
|
||||
{ url: `/masterdata/${resource}/${publicId}/legacy-identifiers`, params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
const meta = envelope.meta as LegacyIdentifierPageMeta | undefined;
|
||||
return {
|
||||
items: Array.isArray(envelope?.data)
|
||||
? envelope.data.map((item) => ({
|
||||
...item,
|
||||
allowedActions: Array.isArray(item.allowedActions) ? item.allowedActions : [],
|
||||
}))
|
||||
: [],
|
||||
meta: {
|
||||
page: meta?.page || params.page,
|
||||
size: meta?.size || params.size,
|
||||
totalElements: meta?.totalElements || 0,
|
||||
totalPages: meta?.totalPages || 0,
|
||||
allowedActions: Array.isArray(meta?.allowedActions) ? meta.allowedActions : [],
|
||||
} as LegacyIdentifierPageMeta,
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function createLegacyIdentifier(
|
||||
resource: LegacyIdentifierResource,
|
||||
publicId: string,
|
||||
payload: LegacyIdentifierCreatePayload,
|
||||
) {
|
||||
return request.post<LegacyIdentifier>({
|
||||
url: `/masterdata/${resource}/${publicId}/legacy-identifiers`,
|
||||
data: payload,
|
||||
});
|
||||
}
|
||||
|
||||
export function disableLegacyIdentifier(
|
||||
resource: LegacyIdentifierResource,
|
||||
targetPublicId: string,
|
||||
mappingPublicId: string,
|
||||
version: number,
|
||||
) {
|
||||
return request.post<LegacyIdentifier>({
|
||||
url: `/masterdata/${resource}/${targetPublicId}/legacy-identifiers/${mappingPublicId}/disable`,
|
||||
data: { version },
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveLegacyIdentifier(resource: LegacyIdentifierResource, sourceSystem: string, legacyCode: string) {
|
||||
return request.get<LegacyIdentifierResolution>({
|
||||
url: `/masterdata/${resource}/legacy-identifiers/resolve`,
|
||||
params: { sourceSystem, legacyCode },
|
||||
});
|
||||
}
|
||||
|
||||
export function getMasterDataReferences() {
|
||||
return request.get<MasterDataReferences>({ url: '/masterdata/references' }).then(normalizeReferences);
|
||||
}
|
||||
|
||||
export function createMasterData(resource: MasterDataResource, payload: MasterDataCreatePayload) {
|
||||
return request.post<MasterDataItem>({ url: `/masterdata/${resource}`, data: payload }).then(normalizeMasterDataItem);
|
||||
}
|
||||
|
||||
export function updateMasterData(resource: MasterDataResource, publicId: string, payload: MasterDataUpdatePayload) {
|
||||
return request
|
||||
.patch<MasterDataItem>({ url: `/masterdata/${resource}/${publicId}`, data: payload })
|
||||
.then(normalizeMasterDataItem);
|
||||
}
|
||||
|
||||
export function submitMasterData(resource: MasterDataResource, publicId: string, version: number) {
|
||||
return request
|
||||
.post<MasterDataItem>({ url: `/masterdata/${resource}/${publicId}/submit`, data: { version } })
|
||||
.then(normalizeMasterDataItem);
|
||||
}
|
||||
|
||||
export function reviewMasterData(resource: MasterDataResource, publicId: string, payload: MasterDataReviewPayload) {
|
||||
return request
|
||||
.post<MasterDataItem>({ url: `/masterdata/${resource}/${publicId}/review`, data: payload })
|
||||
.then(normalizeMasterDataItem);
|
||||
}
|
||||
|
||||
export function disableMasterData(resource: MasterDataResource, publicId: string, version: number) {
|
||||
return request
|
||||
.post<MasterDataItem>({ url: `/masterdata/${resource}/${publicId}/disable`, data: { version } })
|
||||
.then(normalizeMasterDataItem);
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export type PaymentTab = 'all' | 'my' | 'finance-check' | 'offline' | 'result' | 'paid' | 'failed';
|
||||
|
||||
export type PaymentStatus =
|
||||
| 'DRAFT'
|
||||
| 'REVIEWING'
|
||||
| 'RETURNED'
|
||||
| 'REJECTED'
|
||||
| 'APPROVED'
|
||||
| 'OFFLINE_PENDING'
|
||||
| 'RESULT_RECORDED'
|
||||
| 'RESULT_VERIFIED'
|
||||
| 'PART_PAID'
|
||||
| 'PAID'
|
||||
| 'FAILED'
|
||||
| 'REFUNDED'
|
||||
| 'VOID';
|
||||
|
||||
export type PaymentAction =
|
||||
| 'EDIT'
|
||||
| 'SUBMIT'
|
||||
| 'VOID'
|
||||
| 'APPROVE'
|
||||
| 'RETURN'
|
||||
| 'REQUEST_CONTROL_EXCEPTION'
|
||||
| 'RECORD_SUCCESS'
|
||||
| 'RECORD_FAILURE'
|
||||
| 'RECORD_REFUND'
|
||||
| 'VERIFY_RESULT'
|
||||
| 'RETURN_RESULT'
|
||||
| 'CREATE_REPAYMENT';
|
||||
|
||||
export interface PageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface Reference {
|
||||
publicId: string;
|
||||
code?: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ContractReference extends Reference {
|
||||
companyId: string;
|
||||
projectId: string;
|
||||
supplierId: string;
|
||||
}
|
||||
|
||||
export interface BankAccountReference {
|
||||
publicId: string;
|
||||
supplierId: string;
|
||||
accountName: string;
|
||||
bankName: string;
|
||||
maskedAccountNo: string;
|
||||
versionNo: number;
|
||||
}
|
||||
|
||||
export interface PayableReference {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
projectId: string;
|
||||
contractId: string;
|
||||
supplierId: string;
|
||||
costCategoryCode?: string | null;
|
||||
costCategoryName?: string | null;
|
||||
amount: string;
|
||||
paidAmount: string;
|
||||
remainingAmount: string;
|
||||
}
|
||||
|
||||
export interface PaymentAttachmentRequirement {
|
||||
documentType: string;
|
||||
name: string;
|
||||
required: boolean;
|
||||
allowNotApplicable: boolean;
|
||||
}
|
||||
|
||||
export interface PaymentAttachmentRule {
|
||||
parameterId: string;
|
||||
versionNo: number;
|
||||
costCategoryCode: string;
|
||||
costCategoryName: string;
|
||||
requirements: PaymentAttachmentRequirement[];
|
||||
}
|
||||
|
||||
export interface PaymentReferenceOptions {
|
||||
companies: Reference[];
|
||||
projects: Reference[];
|
||||
suppliers: Reference[];
|
||||
contracts: ContractReference[];
|
||||
payables: PayableReference[];
|
||||
bankAccounts: BankAccountReference[];
|
||||
attachmentRules: PaymentAttachmentRule[];
|
||||
}
|
||||
|
||||
export interface FundControl {
|
||||
projectId: string;
|
||||
projectCode: string;
|
||||
projectName: string;
|
||||
currency: string;
|
||||
confirmedReceipt: string;
|
||||
confirmedDeduction: string;
|
||||
approvedAdjustment: string;
|
||||
frozenAmount: string;
|
||||
approvedUnpaid: string;
|
||||
paidAmount: string;
|
||||
availableBalance: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface FundLedgerEntry {
|
||||
publicId: string;
|
||||
entryType: string;
|
||||
sourceType: string;
|
||||
sourcePublicId?: string | null;
|
||||
sourceBusinessNo?: string | null;
|
||||
description: string;
|
||||
currency: string;
|
||||
eventAmount: string;
|
||||
deltaAmount: string;
|
||||
confirmedReceiptDelta: string;
|
||||
frozenAmountDelta: string;
|
||||
paidAmountDelta: string;
|
||||
balanceAfter: string;
|
||||
controlVersionAfter: number;
|
||||
createdByName?: string | null;
|
||||
occurredAt: string;
|
||||
}
|
||||
|
||||
export interface FundLedgerPage {
|
||||
control: FundControl;
|
||||
items: FundLedgerEntry[];
|
||||
meta: PageMeta;
|
||||
}
|
||||
|
||||
export interface FundLedgerQuery {
|
||||
projectId: string;
|
||||
currency?: string;
|
||||
entryType?: string;
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface PaymentQuery {
|
||||
tab: PaymentTab;
|
||||
companyId?: string;
|
||||
projectId?: string;
|
||||
supplierId?: string;
|
||||
status?: PaymentStatus;
|
||||
keyword?: string;
|
||||
riskCode?: string;
|
||||
submittedDateFrom?: string;
|
||||
submittedDateTo?: string;
|
||||
sort: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
company: Reference;
|
||||
project: Reference;
|
||||
contract: Reference;
|
||||
payable?: Reference | null;
|
||||
supplier: Reference;
|
||||
bankAccount: BankAccountReference;
|
||||
sourceDocumentId?: string | null;
|
||||
originalPaymentId?: string | null;
|
||||
repaymentSequence: number;
|
||||
requestedAmount: string;
|
||||
approvedAmount?: string | null;
|
||||
frozenAmount?: string | null;
|
||||
paidAmount?: string | null;
|
||||
currency: string;
|
||||
requestedDate: string;
|
||||
purpose: string;
|
||||
status: PaymentStatus;
|
||||
submittedByName?: string | null;
|
||||
approvedByName?: string | null;
|
||||
resultRecordedByName?: string | null;
|
||||
resultVerifiedByName?: string | null;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
allowedActions: PaymentAction[];
|
||||
}
|
||||
|
||||
export type PaymentCheckCode =
|
||||
| 'PROJECT_FUNDS'
|
||||
| 'CONTRACT_LIMIT'
|
||||
| 'PAYABLE_LIMIT'
|
||||
| 'ACCOUNT_ACTIVE'
|
||||
| 'ATTACHMENTS'
|
||||
| 'DUPLICATE'
|
||||
| 'SOURCE_APPROVED'
|
||||
| 'PROJECT_RISK';
|
||||
|
||||
export interface PaymentCheck {
|
||||
publicId: string;
|
||||
code: PaymentCheckCode;
|
||||
result: 'PASS' | 'WARNING' | 'BLOCK';
|
||||
message: string;
|
||||
checkedAt: string;
|
||||
}
|
||||
|
||||
export interface PaymentResult {
|
||||
publicId: string;
|
||||
resultNo: string;
|
||||
resultType: 'SUCCESS' | 'FAILURE' | 'REFUND';
|
||||
resultAt: string;
|
||||
amount?: string | null;
|
||||
failureReason?: string | null;
|
||||
remark?: string | null;
|
||||
recordedByName?: string | null;
|
||||
recordedAt: string;
|
||||
verifiedByName?: string | null;
|
||||
verifiedAt?: string | null;
|
||||
verificationOpinion?: string | null;
|
||||
recordedFromStatus?: PaymentStatus | null;
|
||||
returnedByName?: string | null;
|
||||
returnedAt?: string | null;
|
||||
returnOpinion?: string | null;
|
||||
status: 'PENDING_VERIFY' | 'VERIFIED' | 'RETURNED';
|
||||
version: number;
|
||||
receiptFileIds: string[];
|
||||
}
|
||||
|
||||
export interface PaymentAttachment {
|
||||
publicId: string;
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
documentType: string;
|
||||
scanStatus: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface PaymentAttachmentException {
|
||||
publicId: string;
|
||||
documentType: string;
|
||||
documentName: string;
|
||||
version: number;
|
||||
reason: string;
|
||||
status: 'PENDING' | 'APPROVED' | 'REJECTED';
|
||||
requestedByName?: string | null;
|
||||
requestedAt: string;
|
||||
reviewedByName?: string | null;
|
||||
reviewedAt?: string | null;
|
||||
reviewOpinion?: string | null;
|
||||
consumedAt?: string | null;
|
||||
allowedActions: Array<'REVIEW'>;
|
||||
}
|
||||
|
||||
export type PaymentControlCode = 'DUPLICATE' | 'PROJECT_RISK';
|
||||
|
||||
export interface PaymentControlException {
|
||||
publicId: string;
|
||||
controlCode: PaymentControlCode;
|
||||
findingSummary: string;
|
||||
version: number;
|
||||
reason: string;
|
||||
validUntil: string;
|
||||
status: 'PENDING' | 'APPROVED' | 'REJECTED';
|
||||
requestedByName?: string | null;
|
||||
requestedAt: string;
|
||||
reviewedByName?: string | null;
|
||||
reviewedAt?: string | null;
|
||||
reviewOpinion?: string | null;
|
||||
consumedAt?: string | null;
|
||||
allowedActions: Array<'REVIEW'>;
|
||||
}
|
||||
|
||||
export interface PaymentDetail {
|
||||
payment: Payment;
|
||||
checks: PaymentCheck[];
|
||||
results: PaymentResult[];
|
||||
attachments: PaymentAttachment[];
|
||||
attachmentExceptions: PaymentAttachmentException[];
|
||||
controlExceptions: PaymentControlException[];
|
||||
}
|
||||
|
||||
export interface PaymentControlExceptionRequestPayload {
|
||||
paymentVersion: number;
|
||||
controlCode: PaymentControlCode;
|
||||
reason: string;
|
||||
validUntil: string;
|
||||
}
|
||||
|
||||
export interface PaymentControlExceptionReviewPayload {
|
||||
paymentVersion: number;
|
||||
version: number;
|
||||
decision: 'APPROVE' | 'REJECT';
|
||||
opinion: string;
|
||||
}
|
||||
|
||||
export interface PaymentDraftPayload {
|
||||
companyId: string;
|
||||
projectId: string;
|
||||
contractId: string;
|
||||
payableId?: string;
|
||||
supplierId: string;
|
||||
bankAccountVersionId: string;
|
||||
sourceDocumentId?: string;
|
||||
requestedAmount: string;
|
||||
currency: string;
|
||||
requestedDate: string;
|
||||
purpose: string;
|
||||
attachments: Array<{ fileId: string; documentType: string }>;
|
||||
attachmentExceptions: Array<{ documentType: string; reason: string }>;
|
||||
}
|
||||
|
||||
export interface PaymentUpdatePayload extends PaymentDraftPayload {
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface PaymentResultPayload {
|
||||
version: number;
|
||||
resultAt: string;
|
||||
amount: string;
|
||||
receiptFileIds: string[];
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface PaymentFailurePayload {
|
||||
version: number;
|
||||
resultAt: string;
|
||||
reason: string;
|
||||
receiptFileIds?: string[];
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface PaymentRefundPayload extends PaymentResultPayload {
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface RepaymentPayload {
|
||||
version: number;
|
||||
requestedDate: string;
|
||||
requestedAmount: string;
|
||||
purpose: string;
|
||||
}
|
||||
|
||||
interface PageEnvelope<T> {
|
||||
data: T[];
|
||||
meta: PageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
async function page<T>(url: string, params: PaymentQuery) {
|
||||
const envelope = await request.get<PageEnvelope<T>>({ url, params }, { isTransformResponse: false });
|
||||
return {
|
||||
items: Array.isArray(envelope?.data) ? envelope.data : [],
|
||||
meta: envelope?.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope?.requestId || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function getPaymentReferences() {
|
||||
return request.get<PaymentReferenceOptions>({ url: '/payments/references' });
|
||||
}
|
||||
|
||||
function normalizePayment(payment: Payment): Payment {
|
||||
return { ...payment, allowedActions: Array.isArray(payment?.allowedActions) ? payment.allowedActions : [] };
|
||||
}
|
||||
|
||||
export async function getPayments(params: PaymentQuery) {
|
||||
const result = await page<Payment>('/payments', params);
|
||||
return { ...result, items: result.items.map(normalizePayment) };
|
||||
}
|
||||
|
||||
export async function getPayment(publicId: string) {
|
||||
const detail = await request.get<PaymentDetail>({ url: `/payments/${publicId}` });
|
||||
return {
|
||||
...detail,
|
||||
payment: normalizePayment(detail.payment),
|
||||
checks: Array.isArray(detail?.checks) ? detail.checks : [],
|
||||
results: (Array.isArray(detail?.results) ? detail.results : []).map((item) => ({
|
||||
...item,
|
||||
receiptFileIds: Array.isArray(item?.receiptFileIds) ? item.receiptFileIds : [],
|
||||
})),
|
||||
attachments: Array.isArray(detail?.attachments) ? detail.attachments : [],
|
||||
attachmentExceptions: (Array.isArray(detail?.attachmentExceptions) ? detail.attachmentExceptions : []).map(
|
||||
(item) => ({
|
||||
...item,
|
||||
allowedActions: Array.isArray(item?.allowedActions) ? item.allowedActions : [],
|
||||
}),
|
||||
),
|
||||
controlExceptions: (Array.isArray(detail?.controlExceptions) ? detail.controlExceptions : []).map((item) => ({
|
||||
...item,
|
||||
allowedActions: Array.isArray(item?.allowedActions) ? item.allowedActions : [],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function getPaymentChecks(publicId: string) {
|
||||
return request.get<PaymentCheck[]>({ url: `/payments/${publicId}/checks` });
|
||||
}
|
||||
|
||||
export function getFundLedger(params: FundLedgerQuery) {
|
||||
return request.get<FundLedgerPage>({ url: '/funds/ledger', params });
|
||||
}
|
||||
|
||||
export function createPayment(payload: PaymentDraftPayload, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: '/payments',
|
||||
data: payload,
|
||||
...commandHeaders('payment-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function updatePayment(publicId: string, payload: PaymentUpdatePayload, idempotencyKey?: string) {
|
||||
return request.patch<Payment>({
|
||||
url: `/payments/${publicId}`,
|
||||
data: payload,
|
||||
...commandHeaders('payment-update', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function reviewPaymentAttachmentException(
|
||||
paymentPublicId: string,
|
||||
exceptionPublicId: string,
|
||||
version: number,
|
||||
decision: 'APPROVE' | 'REJECT',
|
||||
opinion: string,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${paymentPublicId}/attachment-exceptions/${exceptionPublicId}/review`,
|
||||
data: { version, decision, opinion },
|
||||
...commandHeaders('payment-attachment-exception-review', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function requestPaymentControlException(
|
||||
paymentPublicId: string,
|
||||
payload: PaymentControlExceptionRequestPayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${paymentPublicId}/control-exceptions`,
|
||||
data: payload,
|
||||
...commandHeaders('payment-control-exception-request', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function reviewPaymentControlException(
|
||||
paymentPublicId: string,
|
||||
exceptionPublicId: string,
|
||||
payload: PaymentControlExceptionReviewPayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${paymentPublicId}/control-exceptions/${exceptionPublicId}/review`,
|
||||
data: payload,
|
||||
...commandHeaders('payment-control-exception-review', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function submitPayment(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/submit`,
|
||||
data: { version },
|
||||
...commandHeaders('payment-submit', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function approvePayment(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/approve`,
|
||||
data: { version },
|
||||
...commandHeaders('payment-approve', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function returnPayment(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/return`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('payment-return', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function voidPayment(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/void`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('payment-void', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function confirmOfflinePaymentResult(publicId: string, payload: PaymentResultPayload, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/confirm-offline-result`,
|
||||
data: payload,
|
||||
...commandHeaders('payment-result', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function recordPaymentFailure(publicId: string, payload: PaymentFailurePayload, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/record-failure`,
|
||||
data: payload,
|
||||
...commandHeaders('payment-failure', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function recordPaymentRefund(publicId: string, payload: PaymentRefundPayload, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/record-refund`,
|
||||
data: payload,
|
||||
...commandHeaders('payment-refund', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyPaymentResult(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/verify-result`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('payment-verify', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function returnPaymentResult(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/return-result`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('payment-return-result', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function createRepayment(publicId: string, payload: RepaymentPayload, idempotencyKey?: string) {
|
||||
return request.post<Payment>({
|
||||
url: `/payments/${publicId}/create-repayment`,
|
||||
data: payload,
|
||||
...commandHeaders('payment-repayment', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function exportPayments(params: Omit<PaymentQuery, 'page' | 'size'>) {
|
||||
return request.post<AxiosResponse<Blob>>(
|
||||
{ url: '/payments/exports', data: params, responseType: 'blob' },
|
||||
{ isReturnNativeResponse: true },
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export interface ApiPageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
interface ApiEnvelope<T> {
|
||||
data: T;
|
||||
meta?: ApiPageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface ProjectReference {
|
||||
publicId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ProjectReferenceOptions {
|
||||
companies: ProjectReference[];
|
||||
customers: ProjectReference[];
|
||||
owners: ProjectReference[];
|
||||
}
|
||||
|
||||
export interface ProjectOverview {
|
||||
contractAmount: string;
|
||||
receivedAmount: string;
|
||||
paidAmount: string;
|
||||
availableBalance: string;
|
||||
sourceFormCount: number;
|
||||
contractCount: number;
|
||||
receiptCount: number;
|
||||
invoiceCount: number;
|
||||
paymentCount: number;
|
||||
progressCount: number;
|
||||
peopleCount: number;
|
||||
accountingCount: number;
|
||||
voucherCount: number;
|
||||
pendingTaskCount: number;
|
||||
fileCount: number;
|
||||
activeRiskCount: number;
|
||||
documentCompleteness: number;
|
||||
}
|
||||
|
||||
export interface ProjectRiskFlag {
|
||||
publicId: string;
|
||||
riskType: string;
|
||||
reason: string;
|
||||
effectiveFrom: string;
|
||||
effectiveUntil?: string | null;
|
||||
blocking: boolean;
|
||||
status: string;
|
||||
version: number;
|
||||
createdByName: string;
|
||||
createdAt: string;
|
||||
resolvedReason?: string | null;
|
||||
resolvedByName?: string | null;
|
||||
resolvedAt?: string | null;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ProjectTimelineItem {
|
||||
publicId: string;
|
||||
actionCode: string;
|
||||
resultCode: string;
|
||||
reason?: string | null;
|
||||
username: string;
|
||||
activeRole?: string | null;
|
||||
occurredAt: string;
|
||||
}
|
||||
|
||||
export interface ProjectListItem {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
name: string;
|
||||
company: ProjectReference;
|
||||
customer?: ProjectReference | null;
|
||||
owner: ProjectReference;
|
||||
contractAmount: string;
|
||||
receivedAmount: string;
|
||||
paidAmount: string;
|
||||
currency: string;
|
||||
stage: string;
|
||||
financeStatus: string;
|
||||
archiveStatus: string;
|
||||
status: string;
|
||||
pendingTaskCount: number;
|
||||
documentCompleteness: number;
|
||||
startDate?: string | null;
|
||||
endDate?: string | null;
|
||||
version: number;
|
||||
updatedAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ProjectDetail extends Omit<
|
||||
ProjectListItem,
|
||||
'contractAmount' | 'receivedAmount' | 'paidAmount' | 'pendingTaskCount' | 'documentCompleteness'
|
||||
> {
|
||||
description?: string | null;
|
||||
createdAt: string;
|
||||
overview: ProjectOverview;
|
||||
riskFlags: ProjectRiskFlag[];
|
||||
timeline: ProjectTimelineItem[];
|
||||
}
|
||||
|
||||
export interface ProjectQuery {
|
||||
companyId?: string;
|
||||
keyword?: string;
|
||||
ownerId?: string;
|
||||
customerId?: string;
|
||||
stage?: string;
|
||||
financeStatus?: string;
|
||||
archiveStatus?: string;
|
||||
status?: string;
|
||||
startDateFrom?: string;
|
||||
startDateTo?: string;
|
||||
endDateFrom?: string;
|
||||
endDateTo?: string;
|
||||
sort?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ProjectExport {
|
||||
exportId: string;
|
||||
rowCount: number;
|
||||
sha256: string;
|
||||
filterHash: string;
|
||||
columns: string[];
|
||||
fileName: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type ProjectExportPayload = Omit<ProjectQuery, 'page' | 'size'> & { columns?: string[] };
|
||||
|
||||
export interface ProjectImportRow {
|
||||
rowNo: number;
|
||||
businessNo: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
companyId: string;
|
||||
customerId?: string;
|
||||
ownerId: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ProjectImportIssue {
|
||||
rowNo: number;
|
||||
fieldCode?: string | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ProjectImportSummary {
|
||||
publicId: string;
|
||||
batchNo: string;
|
||||
sourceFileId: string;
|
||||
fileSha256: string;
|
||||
totalRows: number;
|
||||
validRows: number;
|
||||
errorRows: number;
|
||||
status: string;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
issues: ProjectImportIssue[];
|
||||
}
|
||||
|
||||
export interface ProjectImportPayload {
|
||||
sourceFileId: string;
|
||||
fileSha256: string;
|
||||
/** Compatibility field for pre-server-parser clients; new clients omit it. */
|
||||
rows?: ProjectImportRow[];
|
||||
}
|
||||
|
||||
export interface ProjectCreatePayload {
|
||||
businessNo: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
companyId: string;
|
||||
customerId?: string;
|
||||
ownerId: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ProjectUpdatePayload {
|
||||
name: string;
|
||||
description?: string;
|
||||
customerId?: string;
|
||||
ownerId: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface ProjectRiskFlagCreatePayload {
|
||||
riskType: string;
|
||||
reason: string;
|
||||
effectiveFrom?: string;
|
||||
effectiveUntil?: string;
|
||||
blocking: boolean;
|
||||
}
|
||||
|
||||
export type ProjectSubresource =
|
||||
| 'forms'
|
||||
| 'contracts'
|
||||
| 'receipts'
|
||||
| 'invoices'
|
||||
| 'payments'
|
||||
| 'progress'
|
||||
| 'people'
|
||||
| 'accounting'
|
||||
| 'archives'
|
||||
| 'timeline'
|
||||
| 'risk-flags';
|
||||
|
||||
export interface ProjectSubresourceQuery {
|
||||
page: number;
|
||||
size: number;
|
||||
[key: string]: string | number | boolean | undefined;
|
||||
}
|
||||
|
||||
export interface ProjectSubresourceEnvelope<T> {
|
||||
data: T;
|
||||
meta?: ApiPageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only PAGE-08 drill-down query. The resource is a closed union so a
|
||||
* caller cannot turn a project URL into an arbitrary backend path.
|
||||
*/
|
||||
export function getProjectSubresource<T>(
|
||||
projectId: string,
|
||||
resource: ProjectSubresource,
|
||||
params: ProjectSubresourceQuery,
|
||||
) {
|
||||
return request
|
||||
.get<ProjectSubresourceEnvelope<T>>(
|
||||
{ url: `/projects/${projectId}/${resource}`, params },
|
||||
{ isTransformResponse: false },
|
||||
)
|
||||
.then((envelope) => ({
|
||||
...envelope,
|
||||
data: envelope && envelope.data !== undefined ? envelope.data : ([] as T),
|
||||
meta: envelope?.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getProjects(params: ProjectQuery) {
|
||||
const envelope = await request.get<ApiEnvelope<ProjectListItem[]>>(
|
||||
{ url: '/projects', params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
items: Array.isArray(envelope?.data) ? envelope.data : [],
|
||||
meta: envelope.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function exportProjects(payload: ProjectExportPayload) {
|
||||
return request.post<ProjectExport>({ url: '/projects/exports', data: payload });
|
||||
}
|
||||
|
||||
export function createProjectImport(payload: ProjectImportPayload, idempotencyKey: string) {
|
||||
return request.post<ProjectImportSummary>({
|
||||
url: '/projects/imports',
|
||||
data: payload,
|
||||
headers: { 'Idempotency-Key': idempotencyKey },
|
||||
});
|
||||
}
|
||||
|
||||
export function getProjectImport(publicId: string) {
|
||||
return request.get<ProjectImportSummary>({ url: `/projects/imports/${publicId}` });
|
||||
}
|
||||
|
||||
export function confirmProjectImport(publicId: string, version: number, idempotencyKey: string) {
|
||||
return request.post<ProjectDetail[]>({
|
||||
url: `/projects/imports/${publicId}/confirm`,
|
||||
data: { version },
|
||||
headers: { 'Idempotency-Key': idempotencyKey },
|
||||
});
|
||||
}
|
||||
|
||||
export function getProject(publicId: string) {
|
||||
return request.get<ProjectDetail>({ url: `/projects/${publicId}` });
|
||||
}
|
||||
|
||||
export function getProjectReferences() {
|
||||
return request.get<ProjectReferenceOptions>({ url: '/projects/references' });
|
||||
}
|
||||
|
||||
export function createProject(payload: ProjectCreatePayload, idempotencyKey?: string) {
|
||||
return request.post<ProjectDetail>({
|
||||
url: '/projects',
|
||||
data: payload,
|
||||
...commandHeaders('project-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function updateProject(publicId: string, payload: ProjectUpdatePayload, idempotencyKey?: string) {
|
||||
return request.patch<ProjectDetail>({
|
||||
url: `/projects/${publicId}`,
|
||||
data: payload,
|
||||
...commandHeaders('project-update', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function completeProjectFinance(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request.post<ProjectDetail>({
|
||||
url: `/projects/${publicId}/finance-complete`,
|
||||
data: { version },
|
||||
...commandHeaders('project-finance-complete', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function createProjectRiskFlag(
|
||||
publicId: string,
|
||||
payload: ProjectRiskFlagCreatePayload,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request.post<ProjectRiskFlag>({
|
||||
url: `/projects/${publicId}/risk-flags`,
|
||||
data: payload,
|
||||
...commandHeaders('project-risk-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveProjectRiskFlag(
|
||||
publicId: string,
|
||||
riskPublicId: string,
|
||||
version: number,
|
||||
reason: string,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request.post<ProjectRiskFlag>({
|
||||
url: `/projects/${publicId}/risk-flags/${riskPublicId}/resolve`,
|
||||
data: { version, reason },
|
||||
...commandHeaders('project-risk-resolve', idempotencyKey),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export interface PageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface Reference {
|
||||
publicId: string;
|
||||
code?: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ReferenceOptions {
|
||||
companies: Reference[];
|
||||
projects: Reference[];
|
||||
counterparties: Reference[];
|
||||
contracts: Reference[];
|
||||
receivables: ReceivableOption[];
|
||||
}
|
||||
|
||||
export interface ReceivableOption {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
project: Reference;
|
||||
counterparty: Reference;
|
||||
amount: string;
|
||||
outstandingAmount: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ReceivableQuery {
|
||||
companyId?: string;
|
||||
projectId?: string;
|
||||
counterpartyId?: string;
|
||||
keyword?: string;
|
||||
status?: string;
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
sort?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface Receipt {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
company: Reference;
|
||||
project?: Reference | null;
|
||||
customer?: Reference | null;
|
||||
receiptDate: string;
|
||||
amount: string;
|
||||
allocatedAmount: string;
|
||||
unallocatedAmount: string;
|
||||
currency: string;
|
||||
payerName: string;
|
||||
referenceNo?: string | null;
|
||||
status: string;
|
||||
confirmedByName?: string | null;
|
||||
confirmedAt?: string | null;
|
||||
voidReason?: string | null;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ReceiptAllocation {
|
||||
publicId: string;
|
||||
sequenceNo: number;
|
||||
receivable?: Reference | null;
|
||||
project: Reference;
|
||||
contract?: Reference | null;
|
||||
amount: string;
|
||||
receivableAmount?: string | null;
|
||||
receivableOutstandingAmount?: string | null;
|
||||
note?: string | null;
|
||||
createdByName?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Receivable {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
company: Reference;
|
||||
project: Reference;
|
||||
contract?: Reference | null;
|
||||
counterparty: Reference;
|
||||
businessDate: string;
|
||||
amount: string;
|
||||
settledAmount: string;
|
||||
outstandingAmount: string;
|
||||
currency: string;
|
||||
status: string;
|
||||
sourceInvoiceId?: string | null;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ReceiptDetail {
|
||||
receipt: Receipt;
|
||||
allocations: ReceiptAllocation[];
|
||||
}
|
||||
|
||||
export interface Invoice {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
sourceDocumentId?: string | null;
|
||||
sourceVersionId?: string | null;
|
||||
company: Reference;
|
||||
project: Reference;
|
||||
contract?: Reference | null;
|
||||
counterparty: Reference;
|
||||
invoiceType: string;
|
||||
amount: string;
|
||||
currency: string;
|
||||
taxRate: string;
|
||||
requestedDate: string;
|
||||
invoiceNo?: string | null;
|
||||
issuedDate?: string | null;
|
||||
resultFileId?: string | null;
|
||||
originalInvoiceId?: string | null;
|
||||
status: string;
|
||||
submittedByName?: string | null;
|
||||
reviewedByName?: string | null;
|
||||
resultRecordedByName?: string | null;
|
||||
submittedAt?: string | null;
|
||||
reviewedAt?: string | null;
|
||||
reviewOpinion?: string | null;
|
||||
resultRecordedAt?: string | null;
|
||||
terminationReason?: string | null;
|
||||
contractLimit?: string | null;
|
||||
contractCommittedAmount?: string | null;
|
||||
contractAvailableAmount?: string | null;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
interface PageEnvelope<T> {
|
||||
data: T[];
|
||||
meta: PageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
interface DataEnvelope<T> {
|
||||
data: T;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
async function page<T>(url: string, params: ReceivableQuery) {
|
||||
const envelope = await request.get<PageEnvelope<T>>({ url, params }, { isTransformResponse: false });
|
||||
return {
|
||||
items: Array.isArray(envelope?.data) ? envelope.data : [],
|
||||
meta: envelope?.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope?.requestId || '',
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeReceipt(receipt: Receipt): Receipt {
|
||||
return { ...receipt, allowedActions: Array.isArray(receipt?.allowedActions) ? receipt.allowedActions : [] };
|
||||
}
|
||||
|
||||
function normalizeInvoice(invoice: Invoice): Invoice {
|
||||
return { ...invoice, allowedActions: Array.isArray(invoice?.allowedActions) ? invoice.allowedActions : [] };
|
||||
}
|
||||
|
||||
function normalizeReceiptDetail(detail: ReceiptDetail): ReceiptDetail {
|
||||
return {
|
||||
...detail,
|
||||
receipt: normalizeReceipt(detail.receipt),
|
||||
allocations: Array.isArray(detail?.allocations) ? detail.allocations : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function getReceivableReferences() {
|
||||
return request.get<ReferenceOptions>({ url: '/receivables/references' });
|
||||
}
|
||||
|
||||
export async function getReceipts(params: ReceivableQuery) {
|
||||
const result = await page<Receipt>('/receipts', params);
|
||||
return { ...result, items: result.items.map(normalizeReceipt) };
|
||||
}
|
||||
|
||||
export function getReceivables(params: ReceivableQuery) {
|
||||
return page<Receivable>('/receivables', params);
|
||||
}
|
||||
|
||||
export function getReceipt(publicId: string) {
|
||||
return request.get<ReceiptDetail>({ url: `/receipts/${publicId}` }).then(normalizeReceiptDetail);
|
||||
}
|
||||
|
||||
export function createReceipt(payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<Receipt>({
|
||||
url: '/receipts',
|
||||
data: payload,
|
||||
...commandHeaders('receipt-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function updateReceipt(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.patch<Receipt>({
|
||||
url: `/receipts/${publicId}`,
|
||||
data: payload,
|
||||
...commandHeaders('receipt-update', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function confirmReceipt(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request.post<Receipt>({
|
||||
url: `/receipts/${publicId}/confirm`,
|
||||
data: { version },
|
||||
...commandHeaders('receipt-confirm', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function allocateReceipt(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<ReceiptDetail>({
|
||||
url: `/receipts/${publicId}/allocations`,
|
||||
data: payload,
|
||||
...commandHeaders('receipt-allocate', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function voidReceipt(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<Receipt>({
|
||||
url: `/receipts/${publicId}/void`,
|
||||
data: payload,
|
||||
...commandHeaders('receipt-void', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function exportReceipts(params: Omit<ReceivableQuery, 'page' | 'size'>) {
|
||||
return request.get<AxiosResponse<Blob>>(
|
||||
{ url: '/receipts/export', params, responseType: 'blob' },
|
||||
{ isReturnNativeResponse: true },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getInvoices(params: ReceivableQuery) {
|
||||
const result = await page<Invoice>('/invoices', params);
|
||||
return { ...result, items: result.items.map(normalizeInvoice) };
|
||||
}
|
||||
|
||||
export function getInvoice(publicId: string) {
|
||||
return request.get<Invoice>({ url: `/invoices/${publicId}` }).then(normalizeInvoice);
|
||||
}
|
||||
|
||||
export function createInvoice(payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<Invoice>({
|
||||
url: '/invoices',
|
||||
data: payload,
|
||||
...commandHeaders('invoice-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function updateInvoice(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.patch<Invoice>({
|
||||
url: `/invoices/${publicId}`,
|
||||
data: payload,
|
||||
...commandHeaders('invoice-update', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function submitInvoice(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request.post<Invoice>({
|
||||
url: `/invoices/${publicId}/submit`,
|
||||
data: { version },
|
||||
...commandHeaders('invoice-submit', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function reviewInvoice(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<Invoice>({
|
||||
url: `/invoices/${publicId}/review`,
|
||||
data: payload,
|
||||
...commandHeaders('invoice-review', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function recordInvoiceResult(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<Invoice>({
|
||||
url: `/invoices/${publicId}/record-result`,
|
||||
data: payload,
|
||||
...commandHeaders('invoice-result', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function voidInvoice(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<Invoice>({
|
||||
url: `/invoices/${publicId}/void`,
|
||||
data: payload,
|
||||
...commandHeaders('invoice-void', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function redLetterInvoice(publicId: string, payload: Record<string, unknown>, idempotencyKey?: string) {
|
||||
return request.post<Invoice>({
|
||||
url: `/invoices/${publicId}/red-letter`,
|
||||
data: payload,
|
||||
...commandHeaders('invoice-red-letter', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function exportInvoices(params: Omit<ReceivableQuery, 'page' | 'size'>) {
|
||||
return request.get<AxiosResponse<Blob>>(
|
||||
{ url: '/invoices/export', params, responseType: 'blob' },
|
||||
{ isReturnNativeResponse: true },
|
||||
);
|
||||
}
|
||||
|
||||
export type ReceivableDataEnvelope<T> = DataEnvelope<T>;
|
||||
@@ -0,0 +1,191 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
export type ReportCode =
|
||||
| 'project-ledger'
|
||||
| 'contract-execution'
|
||||
| 'receipt-invoice'
|
||||
| 'payment-progress'
|
||||
| 'workflow-duration'
|
||||
| 'archive-completeness';
|
||||
|
||||
export interface ReportPageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
reportCode: ReportCode;
|
||||
definitionVersion: string;
|
||||
filterHash: string;
|
||||
columns: string[];
|
||||
sort: string;
|
||||
summary: Record<string, string>;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ReportQuery {
|
||||
companyId?: string;
|
||||
projectId?: string;
|
||||
counterpartyId?: string;
|
||||
keyword?: string;
|
||||
status?: string;
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export type ReportRow = Record<string, unknown> & { rowId?: string; publicId?: string };
|
||||
|
||||
export interface ReportDrilldown {
|
||||
reportCode: ReportCode;
|
||||
rowId: string;
|
||||
definitionVersion: string;
|
||||
filterHash: string;
|
||||
source: Record<string, unknown>;
|
||||
groups: Array<{ code: string; label: string; value: string }>;
|
||||
relatedRecords: Array<{
|
||||
relationType: string;
|
||||
objectType: string;
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
status: string;
|
||||
amount: string;
|
||||
businessDate: string;
|
||||
sourceDocumentId: string;
|
||||
}>;
|
||||
sourceForms: Array<{
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
formType: string;
|
||||
sourceSystem: string;
|
||||
sourceNo: string;
|
||||
status: string;
|
||||
sourceVersionId: string;
|
||||
versionNo?: number;
|
||||
relationType: string;
|
||||
ownerType: string;
|
||||
ownerPublicId: string;
|
||||
exactVersion: boolean;
|
||||
}>;
|
||||
approvals: Array<{
|
||||
publicId: string;
|
||||
sourceDocumentId: string;
|
||||
sourceVersionId: string;
|
||||
nodeCode: string;
|
||||
action: string;
|
||||
actorName: string;
|
||||
opinion: string;
|
||||
actedAt: string;
|
||||
}>;
|
||||
attachments: Array<{
|
||||
fileId: string;
|
||||
ownerType: string;
|
||||
ownerId: string;
|
||||
sourceDocumentId: string;
|
||||
documentType: string;
|
||||
displayName: string;
|
||||
scanStatus: string;
|
||||
versionNo?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ReportExport {
|
||||
exportId: string;
|
||||
reportCode: ReportCode;
|
||||
definitionVersion: string;
|
||||
filterHash: string;
|
||||
status: string;
|
||||
estimatedRows: number;
|
||||
rowCount: number;
|
||||
sha256: string;
|
||||
fileName: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface ReportExportPreview {
|
||||
exportId: string;
|
||||
reportCode: ReportCode;
|
||||
definitionVersion: string;
|
||||
filterHash: string;
|
||||
status: 'PREPARED';
|
||||
estimatedRows: number;
|
||||
columns: string[];
|
||||
sort: string;
|
||||
expiresAt: string;
|
||||
maskingNotice: string;
|
||||
}
|
||||
|
||||
export interface ReportExportSnapshot {
|
||||
reportCode: ReportCode;
|
||||
filters: Omit<ReportQuery, 'page' | 'size'>;
|
||||
definitionVersion: string;
|
||||
filterHash: string;
|
||||
}
|
||||
|
||||
function requiredString(value: unknown, field: string): string {
|
||||
if (typeof value !== 'string' || !value.trim()) throw new Error(`报表导出响应缺少${field}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function nonNegativeInteger(value: unknown, field: string): number {
|
||||
if (!Number.isInteger(value) || Number(value) < 0) throw new Error(`报表导出响应中的${field}无效`);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
export function normalizeReportExport(value: ReportExport): ReportExport {
|
||||
return {
|
||||
exportId: requiredString(value?.exportId, '导出编号'),
|
||||
reportCode: requiredString(value?.reportCode, '报表编号') as ReportCode,
|
||||
definitionVersion: requiredString(value?.definitionVersion, '口径版本'),
|
||||
filterHash: requiredString(value?.filterHash, '筛选快照'),
|
||||
status: requiredString(value?.status, '导出状态'),
|
||||
estimatedRows: nonNegativeInteger(value?.estimatedRows, '预计行数'),
|
||||
rowCount: nonNegativeInteger(value?.rowCount, '导出行数'),
|
||||
sha256: requiredString(value?.sha256, '文件摘要'),
|
||||
fileName: requiredString(value?.fileName, '文件名'),
|
||||
content: requiredString(value?.content, '文件内容'),
|
||||
};
|
||||
}
|
||||
|
||||
interface ReportEnvelope {
|
||||
data: ReportRow[];
|
||||
meta: ReportPageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export async function getReport(reportCode: ReportCode, params: ReportQuery) {
|
||||
const envelope = await request.get<ReportEnvelope>(
|
||||
{ url: `/reports/${reportCode}`, params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
items: envelope.data || [],
|
||||
meta: envelope.meta,
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function getReportDrilldown(reportCode: ReportCode, rowId: string, params: Omit<ReportQuery, 'page' | 'size'>) {
|
||||
return request.get<ReportDrilldown>({ url: `/reports/${reportCode}/${rowId}/drilldown`, params });
|
||||
}
|
||||
|
||||
export function prepareReportExport(
|
||||
reportCode: ReportCode,
|
||||
payload: Omit<ReportQuery, 'page' | 'size'> & {
|
||||
columns?: string[];
|
||||
sort: string;
|
||||
expectedFilterHash: string;
|
||||
expectedDefinitionVersion: string;
|
||||
},
|
||||
) {
|
||||
return request.post<ReportExportPreview>({ url: `/reports/${reportCode}/exports/prepare`, data: payload });
|
||||
}
|
||||
|
||||
export function confirmReportExport(reportCode: ReportCode, exportId: string) {
|
||||
return request
|
||||
.post<ReportExport>({
|
||||
url: `/reports/${reportCode}/exports/confirm`,
|
||||
data: { exportId, maskingConfirmed: true },
|
||||
})
|
||||
.then(normalizeReportExport);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
export interface SetupStatus {
|
||||
required: boolean;
|
||||
locked: boolean;
|
||||
supportedDatabaseTypes: string[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SetupDatabaseRequest {
|
||||
setupCode: string;
|
||||
databaseType: 'MYSQL' | 'POSTGRESQL';
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface SetupConnection {
|
||||
successful: boolean;
|
||||
databaseType: string;
|
||||
serverVersion: string;
|
||||
schemaReady: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SetupCompleteRequest extends SetupDatabaseRequest {
|
||||
adminUsername: string;
|
||||
adminDisplayName: string;
|
||||
adminPassword: string;
|
||||
adminPasswordConfirmation: string;
|
||||
}
|
||||
|
||||
export interface SetupCompleted {
|
||||
completed: boolean;
|
||||
username: string;
|
||||
message: string;
|
||||
restartAfterSeconds: number;
|
||||
}
|
||||
|
||||
function normalizeStatus(value: SetupStatus): SetupStatus {
|
||||
return {
|
||||
required: Boolean(value?.required),
|
||||
locked: Boolean(value?.locked),
|
||||
supportedDatabaseTypes: Array.isArray(value?.supportedDatabaseTypes) ? value.supportedDatabaseTypes : [],
|
||||
message: value?.message || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function getSetupStatus() {
|
||||
return request.get<SetupStatus>({ url: '/setup/status' }).then(normalizeStatus);
|
||||
}
|
||||
|
||||
export function testSetupConnection(data: SetupDatabaseRequest) {
|
||||
return request.post<SetupConnection>({ url: '/setup/test-connection', data }).then((value) => value);
|
||||
}
|
||||
|
||||
export function completeSetup(data: SetupCompleteRequest) {
|
||||
return request.post<SetupCompleted>({ url: '/setup/complete', data }).then((value) => value);
|
||||
}
|
||||
@@ -0,0 +1,564 @@
|
||||
import type { UploadFile } from 'tdesign-vue-next';
|
||||
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export interface ApiPageMeta {
|
||||
page: number;
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
interface ApiEnvelope<T> {
|
||||
data: T;
|
||||
meta?: ApiPageMeta;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface ReferenceItem {
|
||||
publicId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ContractReferenceItem extends ReferenceItem {
|
||||
businessNo: string;
|
||||
companyId: string;
|
||||
projectId: string;
|
||||
counterpartyId: string;
|
||||
counterpartyName: string;
|
||||
counterpartyType: 'CUSTOMER' | 'SUPPLIER' | 'BOTH';
|
||||
}
|
||||
|
||||
export interface FormReferenceOptions {
|
||||
companies: ReferenceItem[];
|
||||
projects: ReferenceItem[];
|
||||
suppliers: ReferenceItem[];
|
||||
customers: ReferenceItem[];
|
||||
contracts: ContractReferenceItem[];
|
||||
payables: PayableReferenceItem[];
|
||||
costCategories: ReferenceItem[];
|
||||
}
|
||||
|
||||
export interface PayableReferenceItem {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
projectId: string;
|
||||
contractId: string;
|
||||
supplierId: string;
|
||||
amount: string;
|
||||
paidAmount: string;
|
||||
remainingAmount: string;
|
||||
}
|
||||
|
||||
export interface TemplateColumnOption {
|
||||
code: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface TemplateColumn {
|
||||
code: string;
|
||||
label: string;
|
||||
dataType?: 'TEXT' | 'LONG_TEXT' | 'ENUM' | 'DATE' | 'DECIMAL' | 'INTEGER' | 'BOOLEAN' | 'REFERENCE' | 'FILE';
|
||||
required?: boolean;
|
||||
maxLength?: number;
|
||||
options?: TemplateColumnOption[];
|
||||
}
|
||||
|
||||
export interface SourceTemplateField {
|
||||
code: string;
|
||||
label: string;
|
||||
sectionCode: string;
|
||||
sectionName: string;
|
||||
dataType:
|
||||
| 'TEXT'
|
||||
| 'LONG_TEXT'
|
||||
| 'INTEGER'
|
||||
| 'DECIMAL'
|
||||
| 'DATE'
|
||||
| 'DATETIME'
|
||||
| 'BOOLEAN'
|
||||
| 'ENUM'
|
||||
| 'REFERENCE'
|
||||
| 'FILE'
|
||||
| 'TABLE';
|
||||
required: boolean;
|
||||
repeatable: boolean;
|
||||
maxLength?: number;
|
||||
numericPrecision?: number;
|
||||
numericScale?: number;
|
||||
options: TemplateColumn[];
|
||||
unit?: string;
|
||||
sensitivity: 'NORMAL' | 'INTERNAL' | 'SENSITIVE' | 'RESTRICTED';
|
||||
maskingRule?: string;
|
||||
helpText?: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface SourceTemplate {
|
||||
publicId: string;
|
||||
formType: string;
|
||||
templateVersion: number;
|
||||
name: string;
|
||||
groupCode: string;
|
||||
groupName: string;
|
||||
purpose: string;
|
||||
initiatorRoles: string[];
|
||||
status: string;
|
||||
publishedAt: string;
|
||||
myDraftCount: number;
|
||||
myPendingCount: number;
|
||||
canCreate: boolean;
|
||||
fields: SourceTemplateField[];
|
||||
}
|
||||
|
||||
export interface SourceFormSummary {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
formType: string;
|
||||
formName: string;
|
||||
templateVersion: number;
|
||||
sourceSystem: string;
|
||||
sourceNo: string;
|
||||
sourceVersion: string;
|
||||
company: ReferenceItem;
|
||||
project?: ReferenceItem | null;
|
||||
applicantName: string;
|
||||
businessDate: string;
|
||||
status: string;
|
||||
currentNodeCode?: string | null;
|
||||
currentNodeName?: string | null;
|
||||
version: number;
|
||||
createdByName: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface ValidationIssue {
|
||||
level: string;
|
||||
sectionCode: string;
|
||||
fieldCode: string;
|
||||
rowNo?: number | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SourceValidation {
|
||||
documentId: string;
|
||||
version: number;
|
||||
valid: boolean;
|
||||
canSubmit: boolean;
|
||||
errors: ValidationIssue[];
|
||||
warnings: ValidationIssue[];
|
||||
validatedAt: string;
|
||||
}
|
||||
|
||||
export interface ApprovalItem {
|
||||
publicId: string;
|
||||
nodeCode: string;
|
||||
nodeName: string;
|
||||
actorName: string;
|
||||
action: string;
|
||||
opinion?: string;
|
||||
actedAt: string;
|
||||
}
|
||||
|
||||
export interface SourceFormDetail {
|
||||
summary: SourceFormSummary;
|
||||
documentVersion: number;
|
||||
snapshotSha256: string;
|
||||
values: Record<string, unknown>;
|
||||
fields: SourceTemplateField[];
|
||||
approvals: ApprovalItem[];
|
||||
validation: SourceValidation;
|
||||
}
|
||||
|
||||
export interface SourceFormPayload {
|
||||
formType: string;
|
||||
templateVersion: number;
|
||||
sourceSystem: string;
|
||||
sourceNo: string;
|
||||
sourceVersion: string;
|
||||
companyId: string;
|
||||
projectId?: string;
|
||||
applicantName: string;
|
||||
businessDate: string;
|
||||
values: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SourceFormUpdatePayload extends Omit<
|
||||
SourceFormPayload,
|
||||
'formType' | 'templateVersion' | 'sourceSystem'
|
||||
> {
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface SourceFormQuery {
|
||||
groupCode?: string;
|
||||
formType?: string;
|
||||
templateVersion?: number;
|
||||
projectId?: string;
|
||||
status?: string;
|
||||
sourceSystem?: string;
|
||||
createdByMe?: boolean;
|
||||
keyword?: string;
|
||||
createdDateFrom?: string;
|
||||
createdDateTo?: string;
|
||||
sort?:
|
||||
| 'updatedAt,asc'
|
||||
| 'updatedAt,desc'
|
||||
| 'createdAt,asc'
|
||||
| 'createdAt,desc'
|
||||
| 'businessNo,asc'
|
||||
| 'businessNo,desc'
|
||||
| 'status,asc'
|
||||
| 'status,desc';
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface WorkflowTaskSummary {
|
||||
publicId: string;
|
||||
instancePublicId: string;
|
||||
nodeCode: string;
|
||||
nodeName: string;
|
||||
sequenceNo: number;
|
||||
status: string;
|
||||
version: number;
|
||||
assigneeName: string;
|
||||
arrivedAt: string;
|
||||
dueAt?: string | null;
|
||||
updatedAt: string;
|
||||
keyAmount?: string | null;
|
||||
form: SourceFormSummary;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
export interface WorkflowTaskDetail {
|
||||
task: WorkflowTaskSummary;
|
||||
form: SourceFormDetail;
|
||||
timeline: ApprovalItem[];
|
||||
}
|
||||
|
||||
export interface WorkflowTaskQuery {
|
||||
view: 'TODO' | 'PROCESSED' | 'CREATED' | 'RETURNED' | 'COMPLETED';
|
||||
formType?: string;
|
||||
status?: string;
|
||||
keyword?: string;
|
||||
projectId?: string;
|
||||
arrivedDateFrom?: string;
|
||||
arrivedDateTo?: string;
|
||||
overdue?: boolean;
|
||||
sort?: 'arrivedAt,asc' | 'arrivedAt,desc' | 'dueAt,asc' | 'dueAt,desc' | 'updatedAt,asc' | 'updatedAt,desc';
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface SourceImportRowPayload {
|
||||
rowNo: number;
|
||||
sourceNo: string;
|
||||
sourceVersion: string;
|
||||
companyId: string;
|
||||
projectId?: string;
|
||||
applicantName: string;
|
||||
businessDate: string;
|
||||
values: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SourceImportRequest {
|
||||
formType: string;
|
||||
templateVersion: number;
|
||||
sourceSystem: 'EXCEL_IMPORT' | 'OA_EXPORT';
|
||||
sourceFileId: string;
|
||||
fileSha256: string;
|
||||
rows: SourceImportRowPayload[];
|
||||
}
|
||||
|
||||
export interface SourceImportIssue {
|
||||
rowNo: number;
|
||||
fieldCode?: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SourceImportSummary {
|
||||
publicId: string;
|
||||
batchNo: string;
|
||||
formType: string;
|
||||
sourceSystem: string;
|
||||
sourceFileId: string;
|
||||
fileSha256: string;
|
||||
totalRows: number;
|
||||
validRows: number;
|
||||
errorRows: number;
|
||||
status: 'VALIDATING' | 'INVALID' | 'READY' | 'CONFIRMED' | 'CANCELLED' | string;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
issues: SourceImportIssue[];
|
||||
}
|
||||
|
||||
export interface FileView {
|
||||
publicId: string;
|
||||
originalName: string;
|
||||
mediaType: string;
|
||||
extension: string;
|
||||
sizeBytes: number;
|
||||
sha256: string;
|
||||
scanStatus: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export function getSourceTemplates() {
|
||||
return request
|
||||
.get<SourceTemplate[]>({ url: '/source/templates' })
|
||||
.then((items) => (Array.isArray(items) ? items : []).map(normalizeTemplate));
|
||||
}
|
||||
|
||||
export function getSourceTemplate(formType: string, version: number) {
|
||||
return request
|
||||
.get<SourceTemplate>({ url: `/source/templates/${formType}/versions/${version}` })
|
||||
.then(normalizeTemplate);
|
||||
}
|
||||
|
||||
export function getSourceReferences() {
|
||||
return request.get<FormReferenceOptions>({ url: '/source/references' }).then((references) => ({
|
||||
companies: Array.isArray(references?.companies) ? references.companies : [],
|
||||
projects: Array.isArray(references?.projects) ? references.projects : [],
|
||||
suppliers: Array.isArray(references?.suppliers) ? references.suppliers : [],
|
||||
customers: Array.isArray(references?.customers) ? references.customers : [],
|
||||
contracts: Array.isArray(references?.contracts) ? references.contracts : [],
|
||||
payables: Array.isArray(references?.payables) ? references.payables : [],
|
||||
costCategories: Array.isArray(references?.costCategories) ? references.costCategories : [],
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getSourceForms(params: SourceFormQuery) {
|
||||
const envelope = await request.get<ApiEnvelope<SourceFormSummary[]>>(
|
||||
{ url: '/source/forms', params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
items: (Array.isArray(envelope?.data) ? envelope.data : []).map(normalizeSummary),
|
||||
meta: envelope.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSourceForm(publicId: string) {
|
||||
return request.get<SourceFormDetail>({ url: `/source/forms/${publicId}` }).then(normalizeDetail);
|
||||
}
|
||||
|
||||
export function createSourceForm(payload: SourceFormPayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<SourceFormDetail>({
|
||||
url: '/source/forms',
|
||||
data: payload,
|
||||
...commandHeaders('source-form-create', idempotencyKey),
|
||||
})
|
||||
.then(normalizeDetail);
|
||||
}
|
||||
|
||||
export function updateSourceForm(publicId: string, payload: SourceFormUpdatePayload, idempotencyKey?: string) {
|
||||
return request
|
||||
.patch<SourceFormDetail>({
|
||||
url: `/source/forms/${publicId}`,
|
||||
data: payload,
|
||||
...commandHeaders('source-form-update', idempotencyKey),
|
||||
})
|
||||
.then(normalizeDetail);
|
||||
}
|
||||
|
||||
export function validateSourceForm(publicId: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<SourceValidation>({
|
||||
url: `/source/forms/${publicId}/validate`,
|
||||
data: {},
|
||||
...commandHeaders('source-form-validate', idempotencyKey),
|
||||
})
|
||||
.then(normalizeValidation);
|
||||
}
|
||||
|
||||
export function submitSourceForm(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<SourceFormDetail>({
|
||||
url: `/source/forms/${publicId}/submit`,
|
||||
data: { version },
|
||||
...commandHeaders('source-form-submit', idempotencyKey),
|
||||
})
|
||||
.then(normalizeDetail);
|
||||
}
|
||||
|
||||
export function withdrawSourceForm(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<SourceFormDetail>({
|
||||
url: `/source/forms/${publicId}/withdraw`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('source-form-withdraw', idempotencyKey),
|
||||
})
|
||||
.then(normalizeDetail);
|
||||
}
|
||||
|
||||
export function voidSourceForm(publicId: string, version: number, opinion: string, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<SourceFormDetail>({
|
||||
url: `/source/forms/${publicId}/void`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('source-form-void', idempotencyKey),
|
||||
})
|
||||
.then(normalizeDetail);
|
||||
}
|
||||
|
||||
export async function getWorkflowTasks(params: WorkflowTaskQuery) {
|
||||
const envelope = await request.get<ApiEnvelope<WorkflowTaskSummary[]>>(
|
||||
{ url: '/workflow/tasks', params },
|
||||
{ isTransformResponse: false },
|
||||
);
|
||||
return {
|
||||
items: (Array.isArray(envelope?.data) ? envelope.data : []).map(normalizeTask),
|
||||
meta: envelope.meta || { page: params.page, size: params.size, totalElements: 0, totalPages: 0 },
|
||||
requestId: envelope.requestId,
|
||||
};
|
||||
}
|
||||
|
||||
export function getWorkflowTask(publicId: string) {
|
||||
return request.get<WorkflowTaskDetail>({ url: `/workflow/tasks/${publicId}` }).then(normalizeTaskDetail);
|
||||
}
|
||||
|
||||
export function actOnWorkflowTask(
|
||||
publicId: string,
|
||||
action: 'approve' | 'return' | 'reject',
|
||||
payload: { version: number; opinion: string; returnPoints: string[] },
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<WorkflowTaskDetail>({
|
||||
url: `/workflow/tasks/${publicId}/${action}`,
|
||||
data: payload,
|
||||
...commandHeaders(`workflow-task-${action}`, idempotencyKey),
|
||||
})
|
||||
.then(normalizeTaskDetail);
|
||||
}
|
||||
|
||||
export function withdrawWorkflowInstance(
|
||||
instancePublicId: string,
|
||||
version: number,
|
||||
opinion: string,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<SourceFormDetail>({
|
||||
url: `/workflow/instances/${instancePublicId}/withdraw`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('workflow-instance-withdraw', idempotencyKey),
|
||||
})
|
||||
.then(normalizeDetail);
|
||||
}
|
||||
|
||||
export function voidWorkflowInstance(
|
||||
instancePublicId: string,
|
||||
version: number,
|
||||
opinion: string,
|
||||
idempotencyKey?: string,
|
||||
) {
|
||||
return request
|
||||
.post<SourceFormDetail>({
|
||||
url: `/workflow/instances/${instancePublicId}/void`,
|
||||
data: { version, opinion },
|
||||
...commandHeaders('workflow-instance-void', idempotencyKey),
|
||||
})
|
||||
.then(normalizeDetail);
|
||||
}
|
||||
|
||||
export function createSourceImport(payload: SourceImportRequest, idempotencyKey?: string) {
|
||||
return request.post<SourceImportSummary>({
|
||||
url: '/source/imports',
|
||||
data: payload,
|
||||
...commandHeaders('source-import-create', idempotencyKey),
|
||||
});
|
||||
}
|
||||
|
||||
export function getSourceImport(publicId: string) {
|
||||
return request.get<SourceImportSummary>({ url: `/source/imports/${publicId}` });
|
||||
}
|
||||
|
||||
export function confirmSourceImport(publicId: string, version: number, idempotencyKey?: string) {
|
||||
return request
|
||||
.post<SourceFormDetail[]>({
|
||||
url: `/source/imports/${publicId}/confirm`,
|
||||
data: { version },
|
||||
...commandHeaders('source-import-confirm', idempotencyKey),
|
||||
})
|
||||
.then((items) => (Array.isArray(items) ? items : []).map(normalizeDetail));
|
||||
}
|
||||
|
||||
export async function uploadBusinessFile(uploadFile: UploadFile) {
|
||||
if (!uploadFile.raw) throw new Error('未读取到待上传文件');
|
||||
const formData = new FormData();
|
||||
formData.append('file', uploadFile.raw);
|
||||
return request.post<FileView>({
|
||||
url: '/files',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
...commandHeaders('business-file-upload').headers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeTemplate(template: SourceTemplate): SourceTemplate {
|
||||
return {
|
||||
...template,
|
||||
initiatorRoles: Array.isArray(template?.initiatorRoles) ? template.initiatorRoles : [],
|
||||
fields: (Array.isArray(template?.fields) ? template.fields : []).map((field) => ({
|
||||
...field,
|
||||
options: Array.isArray(field.options) ? field.options : [],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSummary(summary: SourceFormSummary): SourceFormSummary {
|
||||
return {
|
||||
...summary,
|
||||
allowedActions: Array.isArray(summary?.allowedActions) ? summary.allowedActions : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeValidation(validation: SourceValidation): SourceValidation {
|
||||
return {
|
||||
...validation,
|
||||
errors: Array.isArray(validation?.errors) ? validation.errors : [],
|
||||
warnings: Array.isArray(validation?.warnings) ? validation.warnings : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeDetail(detail: SourceFormDetail): SourceFormDetail {
|
||||
return {
|
||||
...detail,
|
||||
summary: normalizeSummary(detail.summary),
|
||||
values: detail?.values && typeof detail.values === 'object' ? detail.values : {},
|
||||
fields: (Array.isArray(detail?.fields) ? detail.fields : []).map((field) => ({
|
||||
...field,
|
||||
options: Array.isArray(field.options) ? field.options : [],
|
||||
})),
|
||||
approvals: Array.isArray(detail?.approvals) ? detail.approvals : [],
|
||||
validation: normalizeValidation(detail.validation),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTask(task: WorkflowTaskSummary): WorkflowTaskSummary {
|
||||
return {
|
||||
...task,
|
||||
form: normalizeSummary(task.form),
|
||||
allowedActions: Array.isArray(task?.allowedActions) ? task.allowedActions : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTaskDetail(detail: WorkflowTaskDetail): WorkflowTaskDetail {
|
||||
return {
|
||||
...detail,
|
||||
task: normalizeTask(detail.task),
|
||||
form: normalizeDetail(detail.form),
|
||||
timeline: Array.isArray(detail?.timeline) ? detail.timeline : [],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
import { commandHeaders } from './command';
|
||||
|
||||
export interface SystemUpdateView {
|
||||
enabled: boolean;
|
||||
currentVersion: string;
|
||||
latestVersion?: string | null;
|
||||
updateAvailable: boolean;
|
||||
state: string;
|
||||
message: string;
|
||||
releaseNotes?: string | null;
|
||||
publishedAt?: string | null;
|
||||
checkedAt?: string | null;
|
||||
statusUpdatedAt?: string | null;
|
||||
allowedActions: string[];
|
||||
}
|
||||
|
||||
function normalize(value: SystemUpdateView): SystemUpdateView {
|
||||
return {
|
||||
...value,
|
||||
enabled: Boolean(value?.enabled),
|
||||
updateAvailable: Boolean(value?.updateAvailable),
|
||||
allowedActions: Array.isArray(value?.allowedActions) ? value.allowedActions : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function getSystemUpdateStatus() {
|
||||
return request.get<SystemUpdateView>({ url: '/admin/system-update' }).then(normalize);
|
||||
}
|
||||
|
||||
export function checkSystemUpdate() {
|
||||
return request
|
||||
.post<SystemUpdateView>({ url: '/admin/system-update/check', ...commandHeaders('system-update-check') })
|
||||
.then(normalize);
|
||||
}
|
||||
|
||||
export function downloadSystemUpdate(version: string) {
|
||||
return request
|
||||
.post<SystemUpdateView>({
|
||||
url: '/admin/system-update/download',
|
||||
data: { version },
|
||||
...commandHeaders('system-update-download'),
|
||||
})
|
||||
.then(normalize);
|
||||
}
|
||||
|
||||
export function installSystemUpdate(version: string, reason?: string) {
|
||||
return request
|
||||
.post<SystemUpdateView>({
|
||||
url: '/admin/system-update/install',
|
||||
data: { version, reason },
|
||||
...commandHeaders('system-update-install'),
|
||||
})
|
||||
.then(normalize);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
export interface WorkbenchMetric {
|
||||
code: string;
|
||||
label: string;
|
||||
value: number;
|
||||
targetRoute: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchTodo {
|
||||
publicId: string;
|
||||
formType: string;
|
||||
formName: string;
|
||||
businessNo: string;
|
||||
projectPublicId?: string;
|
||||
projectName?: string;
|
||||
nodeCode: string;
|
||||
status: string;
|
||||
arrivedAt?: string;
|
||||
dueAt?: string;
|
||||
targetRoute: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchProject {
|
||||
publicId: string;
|
||||
businessNo: string;
|
||||
name: string;
|
||||
companyName: string;
|
||||
stage: string;
|
||||
status: string;
|
||||
contractAmount: string;
|
||||
updatedAt?: string;
|
||||
targetRoute: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchIssue {
|
||||
code: string;
|
||||
label: string;
|
||||
severity: string;
|
||||
count: number;
|
||||
detail: string;
|
||||
targetRoute: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchFund {
|
||||
projectPublicId: string;
|
||||
projectName: string;
|
||||
currency: string;
|
||||
confirmedReceipt: string;
|
||||
approvedUnpaid: string;
|
||||
paidAmount: string;
|
||||
frozenAmount: string;
|
||||
availableAmount: string;
|
||||
targetRoute: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchArchiveItem {
|
||||
kind: string;
|
||||
publicId: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
status: string;
|
||||
completeness?: string;
|
||||
missingCount: number;
|
||||
dueAt?: string;
|
||||
updatedAt?: string;
|
||||
targetRoute: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchActivity {
|
||||
publicId: string;
|
||||
actionCode: string;
|
||||
objectType: string;
|
||||
objectPublicId?: string;
|
||||
title: string;
|
||||
resultCode: string;
|
||||
occurredAt?: string;
|
||||
targetRoute: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchView {
|
||||
roleCode: string;
|
||||
title: string;
|
||||
refreshedAt: string;
|
||||
metrics: WorkbenchMetric[];
|
||||
todos: WorkbenchTodo[];
|
||||
projects: WorkbenchProject[];
|
||||
issues: WorkbenchIssue[];
|
||||
funds: WorkbenchFund[];
|
||||
archiveItems: WorkbenchArchiveItem[];
|
||||
activities: WorkbenchActivity[];
|
||||
}
|
||||
|
||||
const array = <T>(value: T[] | null | undefined): T[] => (Array.isArray(value) ? value : []);
|
||||
|
||||
export function normalizeWorkbench(view: Partial<WorkbenchView> | null | undefined): WorkbenchView {
|
||||
return {
|
||||
roleCode: String(view?.roleCode || ''),
|
||||
title: String(view?.title || ''),
|
||||
refreshedAt: String(view?.refreshedAt || ''),
|
||||
metrics: array(view?.metrics),
|
||||
todos: array(view?.todos),
|
||||
projects: array(view?.projects),
|
||||
issues: array(view?.issues),
|
||||
funds: array(view?.funds),
|
||||
archiveItems: array(view?.archiveItems),
|
||||
activities: array(view?.activities),
|
||||
};
|
||||
}
|
||||
|
||||
export function getWorkbench(role: 'project' | 'finance' | 'archive') {
|
||||
return request.get<WorkbenchView>({ url: `/workbenches/${role}` }).then(normalizeWorkbench);
|
||||
}
|
||||
Reference in New Issue
Block a user