import { addNumbers, roundTo } from '@/lib/decimal' import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit, type ScaleFeeSplitResult } from '@/lib/pricingScaleFee' export type ScaleMode = 'cost' | 'area' export interface ScaleBudgetCheckRow { benchmarkBudgetBasicChecked?: boolean benchmarkBudgetOptionalChecked?: boolean } export interface ScaleBudgetSourceRow extends ScaleBudgetCheckRow { amount?: number | null landArea?: number | null majorFactor?: number | null consultCategoryFactor?: number | null workStageFactor?: number | null workRatio?: number | null } export interface ScaleDetailComputedRow extends ScaleBudgetSourceRow { benchmarkBudget?: number | null benchmarkBudgetBasic?: number | null benchmarkBudgetOptional?: number | null basicFormula?: string | null optionalFormula?: string | null budgetFee?: number | null budgetFeeBasic?: number | null budgetFeeOptional?: number | null } export type CheckedScaleFeeSplitResult = Omit & { total: number | null } const getScaleValueByMode = ( row: Pick | undefined, mode: ScaleMode ) => (mode === 'cost' ? row?.amount : row?.landArea) export const isSameNullableNumber = (left: number | null | undefined, right: number | null | undefined) => { if (left == null && right == null) return true if (left == null || right == null) return false return roundTo(left, 6) === roundTo(right, 6) } export const isSameNullableText = (left: string | null | undefined, right: string | null | undefined) => String(left ?? '') === String(right ?? '') export const isBenchmarkBudgetFullyUnchecked = ( row?: Pick ) => row?.benchmarkBudgetBasicChecked === false && row?.benchmarkBudgetOptionalChecked === false export const getBenchmarkBudgetRawSplitByRow = ( row: Pick | undefined, mode: ScaleMode ) => getBenchmarkBudgetSplitByScale(getScaleValueByMode(row, mode), mode) export const getCheckedBenchmarkBudgetSplitByRow = ( row?: Pick, mode: ScaleMode = 'cost' ): CheckedScaleFeeSplitResult | null => { const split = getBenchmarkBudgetRawSplitByRow(row, mode) if (!split) return null const basic = row?.benchmarkBudgetBasicChecked === false ? 0 : split.basic const optional = row?.benchmarkBudgetOptionalChecked === false ? 0 : split.optional return { ...split, basic, optional, total: isBenchmarkBudgetFullyUnchecked(row) ? null : roundTo(addNumbers(basic, optional), 2) } } export const getScaleBudgetFeeSplitByRow = ( row?: Pick< ScaleBudgetSourceRow, | 'amount' | 'landArea' | 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked' | 'majorFactor' | 'consultCategoryFactor' | 'workStageFactor' | 'workRatio' >, mode: ScaleMode = 'cost' ) => { if (isBenchmarkBudgetFullyUnchecked(row)) return null const benchmarkBudgetSplit = getCheckedBenchmarkBudgetSplitByRow(row, mode) if (!benchmarkBudgetSplit) return null return getScaleBudgetFeeSplit({ benchmarkBudgetBasic: benchmarkBudgetSplit.basic, benchmarkBudgetOptional: benchmarkBudgetSplit.optional, majorFactor: row?.majorFactor, consultCategoryFactor: row?.consultCategoryFactor, workStageFactor: row?.workStageFactor, workRatio: row?.workRatio }) } export const getScaleBudgetFeeByRow = ( row?: Pick< ScaleBudgetSourceRow, | 'amount' | 'landArea' | 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked' | 'majorFactor' | 'consultCategoryFactor' | 'workStageFactor' | 'workRatio' >, mode: ScaleMode = 'cost' ) => getScaleBudgetFeeSplitByRow(row, mode)?.total ?? null export const recomputeScaleDetailRow = ( row: TRow, mode: ScaleMode ): TRow => { const benchmarkBudgetRawSplit = getBenchmarkBudgetRawSplitByRow(row, mode) const benchmarkBudgetSplit = getCheckedBenchmarkBudgetSplitByRow(row, mode) const budgetFeeSplit = getScaleBudgetFeeSplitByRow(row, mode) return { ...row, benchmarkBudget: benchmarkBudgetSplit?.total ?? null, benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null, benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null, basicFormula: row.benchmarkBudgetBasicChecked === false ? null : (benchmarkBudgetRawSplit?.basicFormula ?? ''), optionalFormula: row.benchmarkBudgetOptionalChecked === false ? null : (benchmarkBudgetRawSplit?.optionalFormula ?? ''), budgetFee: budgetFeeSplit?.total ?? null, budgetFeeBasic: budgetFeeSplit?.basic ?? null, budgetFeeOptional: budgetFeeSplit?.optional ?? null } } export const recomputeScaleDetailRowsInPlace = ( rows: TRow[], mode: ScaleMode ) => { for (const row of rows) { Object.assign(row, recomputeScaleDetailRow(row, mode)) } } export const isSameScaleDetailRow = ( left: ScaleDetailComputedRow, right: ScaleDetailComputedRow, mode: ScaleMode ) => { const isSameScaleValue = mode === 'cost' ? isSameNullableNumber(left.amount, right.amount) : isSameNullableNumber(left.landArea, right.landArea) return isSameScaleValue && isSameNullableNumber(left.benchmarkBudget, right.benchmarkBudget) && isSameNullableNumber(left.benchmarkBudgetBasic, right.benchmarkBudgetBasic) && isSameNullableNumber(left.benchmarkBudgetOptional, right.benchmarkBudgetOptional) && isSameNullableText(left.basicFormula, right.basicFormula) && isSameNullableText(left.optionalFormula, right.optionalFormula) && isSameNullableNumber(left.budgetFee, right.budgetFee) && isSameNullableNumber(left.budgetFeeBasic, right.budgetFeeBasic) && isSameNullableNumber(left.budgetFeeOptional, right.budgetFeeOptional) }