123 lines
3.4 KiB
TypeScript
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)}`;
|
|
}
|