Files
TakeoutSaaS.TenantUI/apps/web-antd/src/views/finance/cost/composables/cost-page/entry-actions.ts

123 lines
3.4 KiB
TypeScript

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)}`;
}