165 lines
5.9 KiB
TypeScript
165 lines
5.9 KiB
TypeScript
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<ScaleFeeSplitResult, 'total'> & {
|
|
total: number | null
|
|
}
|
|
|
|
const getScaleValueByMode = (
|
|
row: Pick<ScaleBudgetSourceRow, 'amount' | 'landArea'> | 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<ScaleBudgetCheckRow, 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked'>
|
|
) => row?.benchmarkBudgetBasicChecked === false && row?.benchmarkBudgetOptionalChecked === false
|
|
|
|
export const getBenchmarkBudgetRawSplitByRow = (
|
|
row: Pick<ScaleBudgetSourceRow, 'amount' | 'landArea'> | undefined,
|
|
mode: ScaleMode
|
|
) => getBenchmarkBudgetSplitByScale(getScaleValueByMode(row, mode), mode)
|
|
|
|
export const getCheckedBenchmarkBudgetSplitByRow = (
|
|
row?: Pick<ScaleBudgetSourceRow, 'amount' | 'landArea' | 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked'>,
|
|
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 = <TRow extends ScaleDetailComputedRow>(
|
|
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 = <TRow extends ScaleDetailComputedRow>(
|
|
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)
|
|
}
|