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

155 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { FinanceCostFilterState } from '../../types';
/**
* 文件职责:成本管理页面纯函数与格式化工具。
*/
import type {
FinanceCostCategoryCode,
FinanceCostEntryCategoryDto,
SaveFinanceCostCategoryPayload,
} from '#/api/finance/cost';
/** 解析为有限数字。 */
export function toFiniteNumber(value: unknown, fallback = 0) {
const normalized = Number(value);
return Number.isFinite(normalized) ? normalized : fallback;
}
/** 金额保留两位小数。 */
export function roundAmount(value: unknown) {
const normalized = toFiniteNumber(value, 0);
return Math.round(normalized * 100) / 100;
}
/** 货币格式化。 */
export function formatCurrency(value: unknown) {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(roundAmount(value));
}
/** 百分比格式化。 */
export function formatPercent(value: unknown) {
return `${roundAmount(value).toFixed(2)}%`;
}
/** 获取当前月份yyyy-MM。 */
export function getCurrentMonthString() {
const now = new Date();
const month = `${now.getMonth() + 1}`.padStart(2, '0');
return `${now.getFullYear()}-${month}`;
}
/** 月份字符串转标题文本。 */
export function formatMonthTitle(month: string) {
const normalized = month.trim();
const [year, monthValue] = normalized.split('-');
if (!year || !monthValue) {
return normalized || '--';
}
return `${year}${Number(monthValue)}`;
}
/** 月份位移。 */
export function shiftMonth(month: string, offset: number) {
const parsed = parseMonth(month);
if (!parsed) {
return getCurrentMonthString();
}
parsed.setMonth(parsed.getMonth() + offset);
return formatMonth(parsed);
}
/** 构建查询作用域参数。 */
export function buildScopeQueryPayload(filters: FinanceCostFilterState) {
return {
dimension: filters.dimension,
month: filters.month || undefined,
storeId:
filters.dimension === 'store' ? filters.storeId || undefined : undefined,
};
}
/** 将 API 分类数据映射为页面视图模型。 */
export function mapCategoriesToViewModel(
categories: FinanceCostEntryCategoryDto[],
expandedMap: Map<FinanceCostCategoryCode, boolean>,
) {
return (categories ?? []).map((item) => ({
...item,
items: [...(item.items ?? [])],
expanded: expandedMap.get(item.category) ?? false,
}));
}
/** 计算分类总金额。 */
export function sumCategoryItems(
category: Pick<FinanceCostEntryCategoryDto, 'category' | 'items'>,
) {
let total = 0;
for (const item of category.items ?? []) {
if (category.category === 'labor') {
const quantity = toFiniteNumber(item.quantity, 0);
const unitPrice = toFiniteNumber(item.unitPrice, 0);
total += roundAmount(quantity * unitPrice);
continue;
}
total += roundAmount(item.amount);
}
return roundAmount(total);
}
/** 计算全部分类总成本。 */
export function sumAllCategoryTotal(categories: FinanceCostEntryCategoryDto[]) {
let total = 0;
for (const category of categories ?? []) {
total += roundAmount(category.totalAmount);
}
return roundAmount(total);
}
/** 构建保存请求分类数组。 */
export function buildSaveCategoryPayload(
categories: FinanceCostEntryCategoryDto[],
): SaveFinanceCostCategoryPayload[] {
return (categories ?? []).map((category) => ({
category: category.category,
totalAmount: roundAmount(category.totalAmount),
items: (category.items ?? []).map((item, index) => ({
itemId: item.itemId,
itemName: item.itemName.trim(),
amount: roundAmount(item.amount),
quantity:
item.quantity === undefined ? undefined : roundAmount(item.quantity),
unitPrice:
item.unitPrice === undefined ? undefined : roundAmount(item.unitPrice),
sortOrder: item.sortOrder > 0 ? item.sortOrder : index + 1,
})),
}));
}
function parseMonth(value: string) {
const normalized = value.trim();
const [year, month] = normalized.split('-');
const yearValue = Number(year);
const monthValue = Number(month);
if (
!Number.isInteger(yearValue) ||
!Number.isInteger(monthValue) ||
monthValue < 1 ||
monthValue > 12
) {
return null;
}
return new Date(yearValue, monthValue - 1, 1);
}
function formatMonth(date: Date) {
const year = date.getFullYear();
const month = `${date.getMonth() + 1}`.padStart(2, '0');
return `${year}-${month}`;
}