feat(@vben/web-antd): add finance cost management pages
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import type { FinanceCostEntryState } from '../../types';
|
||||
|
||||
/**
|
||||
* 文件职责:成本录入分类与明细本地编辑动作。
|
||||
*/
|
||||
import type {
|
||||
FinanceCostCategoryCode,
|
||||
FinanceCostEntryDetailDto,
|
||||
} from '#/api/finance/cost';
|
||||
|
||||
import { roundAmount, sumAllCategoryTotal, sumCategoryItems } from './helpers';
|
||||
|
||||
interface EntryActionOptions {
|
||||
entry: FinanceCostEntryState;
|
||||
}
|
||||
|
||||
/** 创建录入区编辑动作。 */
|
||||
export function createEntryActions(options: EntryActionOptions) {
|
||||
function toggleCategoryExpanded(category: FinanceCostCategoryCode) {
|
||||
const target = options.entry.categories.find(
|
||||
(item) => item.category === category,
|
||||
);
|
||||
if (!target) return;
|
||||
target.expanded = !target.expanded;
|
||||
}
|
||||
|
||||
function setCategoryTotal(category: FinanceCostCategoryCode, value: number) {
|
||||
const target = options.entry.categories.find(
|
||||
(item) => item.category === category,
|
||||
);
|
||||
if (!target) return;
|
||||
target.totalAmount = Math.max(0, roundAmount(value));
|
||||
syncEntrySummary();
|
||||
}
|
||||
|
||||
function upsertDetailItem(
|
||||
category: FinanceCostCategoryCode,
|
||||
detail: FinanceCostEntryDetailDto,
|
||||
mode: 'create' | 'edit',
|
||||
) {
|
||||
const target = options.entry.categories.find(
|
||||
(item) => item.category === category,
|
||||
);
|
||||
if (!target) return;
|
||||
|
||||
const nextItem: FinanceCostEntryDetailDto = {
|
||||
...detail,
|
||||
itemId: detail.itemId || createTempItemId(),
|
||||
amount: resolveDetailAmount(category, detail),
|
||||
quantity:
|
||||
detail.quantity === undefined
|
||||
? undefined
|
||||
: roundAmount(detail.quantity),
|
||||
unitPrice:
|
||||
detail.unitPrice === undefined
|
||||
? undefined
|
||||
: roundAmount(detail.unitPrice),
|
||||
sortOrder: detail.sortOrder > 0 ? detail.sortOrder : 1,
|
||||
};
|
||||
|
||||
const index = target.items.findIndex(
|
||||
(item) => item.itemId === detail.itemId,
|
||||
);
|
||||
if (mode === 'edit' && index !== -1) {
|
||||
target.items.splice(index, 1, nextItem);
|
||||
} else {
|
||||
target.items.push(nextItem);
|
||||
}
|
||||
|
||||
target.items.sort((left, right) => left.sortOrder - right.sortOrder);
|
||||
target.totalAmount = sumCategoryItems(target);
|
||||
syncEntrySummary();
|
||||
}
|
||||
|
||||
function removeDetailItem(
|
||||
category: FinanceCostCategoryCode,
|
||||
itemId: string | undefined,
|
||||
) {
|
||||
const target = options.entry.categories.find(
|
||||
(item) => item.category === category,
|
||||
);
|
||||
if (!target || !itemId) return;
|
||||
|
||||
target.items = target.items.filter((item) => item.itemId !== itemId);
|
||||
target.totalAmount = sumCategoryItems(target);
|
||||
syncEntrySummary();
|
||||
}
|
||||
|
||||
function syncEntrySummary() {
|
||||
const totalCost = sumAllCategoryTotal(options.entry.categories);
|
||||
options.entry.totalCost = totalCost;
|
||||
options.entry.costRate =
|
||||
options.entry.monthRevenue > 0
|
||||
? roundAmount((totalCost / options.entry.monthRevenue) * 100)
|
||||
: 0;
|
||||
}
|
||||
|
||||
return {
|
||||
removeDetailItem,
|
||||
setCategoryTotal,
|
||||
syncEntrySummary,
|
||||
toggleCategoryExpanded,
|
||||
upsertDetailItem,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveDetailAmount(
|
||||
category: FinanceCostCategoryCode,
|
||||
detail: FinanceCostEntryDetailDto,
|
||||
) {
|
||||
if (category !== 'labor') {
|
||||
return Math.max(0, roundAmount(detail.amount));
|
||||
}
|
||||
|
||||
const quantity = roundAmount(detail.quantity ?? 0);
|
||||
const unitPrice = roundAmount(detail.unitPrice ?? 0);
|
||||
return Math.max(0, roundAmount(quantity * unitPrice));
|
||||
}
|
||||
|
||||
function createTempItemId() {
|
||||
return `tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user