fix(est): reject ambiguous progress inputs

This commit is contained in:
2026-11-07 10:38:36 +09:00
parent 448b92276f
commit c1a4b9a178
4 changed files with 47 additions and 6 deletions
@@ -1,6 +1,23 @@
from test_est_rea_parity import run
def test_progress_rejects_ambiguous_inputs_without_applying_them_to_multiple_rows():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.options.SourceRefresh=false;
p.tables.EstMainDat=[{Code:'A',GN:10,GU:100},{Code:'A',GN:20,GU:100},
{PkID:3,Code:'B',GN:10,GU:100},{LinkIndex:54}];
p.progress=[{id:'one',inputs:{A:{Gum:2},'3':{Gum:3}}}];
const out=EstProgress.calculate(EstNative.calculate(p),'one');let rejected=false;
try{EstProgress.lock(EstNative.calculate(p),'one')}catch{rejected=true}
return {out,rejected};
})()""",
)
assert result["rejected"] and result["out"]["errors"]
assert [row["GumTC"] for row in result["out"]["rows"][:3]] == [0, 0, 300]
def test_progress_amount_input_preserves_exact_money_and_component_total():
result = run(
"math",
+2
View File
@@ -552,6 +552,8 @@ const EstActions = (() => {
if (node.matches("[data-progress]")) {
const round = project.progress.find((r) => r.id === state.roundId);
if (round.locked) throw Error("확정 회차입니다.");
if (EstProgress.ambiguousKeys(project.tables.EstMainDat).has(node.dataset.key))
throw Error("기성 내역 식별자가 중복되어 입력할 수 없습니다.");
const value = node.type === "number" ? Number(node.value) : node.value;
if (node.type === "number" && !Number.isFinite(value)) throw Error("수치 입력 오류");
EstApp.change(() => {
+14 -3
View File
@@ -109,9 +109,19 @@ const EstBusinessActions = (() => {
},
{ name: "ratio", label: "비율 %", type: "number", value: 100 },
],
(values) =>
(values) => {
const value = numeric(values.ratio);
const ambiguous = EstProgress.ambiguousKeys(project.tables.EstMainDat);
const hasAmbiguous = project.tables.EstMainDat.some(
(row, index) =>
Number(row.Level || 0) <= 0 &&
Number(row.CalcIndex || 0) < 2 &&
![54, 55, 58, 59, 62, 67].includes(Number(row.LinkIndex)) &&
!(values.mode === "full" && Number(row.LinkIndex) === 57) &&
ambiguous.has(EstBusiness.rowKey(row, index)),
);
if (hasAmbiguous) throw Error("기성 내역 식별자가 중복되어 일괄 입력할 수 없습니다.");
EstApp.change(() => {
const value = numeric(values.ratio);
project.tables.EstMainDat.forEach((row, index) => {
if (
Number(row.Level) > 0 ||
@@ -127,7 +137,8 @@ const EstBusinessActions = (() => {
round.inputs[key][values.prefix] =
values.mode === "full" ? Number(row.GN) || 0 : value;
});
}, false),
}, false);
},
);
} else if (name === "load-progress-exclusion") {
const locked = project.progress.filter((round) => round.locked && round.result);
+14 -3
View File
@@ -8,6 +8,14 @@ const EstProgress = (() => {
return Math.max(0, (mantissa.split(".")[1]?.length || 0) - Number(exponent || 0));
};
const ordinary = (row) => num(row.Level) === 0 && ![54, 55, 58, 59].includes(num(row.LinkIndex));
function ambiguousKeys(rows) {
const counts = new Map();
rows.forEach((row, index) => {
const key = EstBusiness.rowKey(row, index);
counts.set(key, (counts.get(key) || 0) + 1);
});
return new Set([...counts].filter(([, count]) => count > 1).map(([key]) => key));
}
function distribute(amount, row, digits) {
const parts = components.map((field) => num(row[field]));
const base = sum(row);
@@ -31,6 +39,7 @@ const EstProgress = (() => {
const errors = [],
position = project.progress.indexOf(round),
key = EstBusiness.rowKey;
const ambiguous = ambiguousKeys(rows);
const earlier = project.progress.slice(0, position);
if (earlier.some((item) => !item.locked)) errors.push("전회 기성 회차를 먼저 확정하세요.");
const previous = earlier.at(-1)?.result,
@@ -39,8 +48,10 @@ const EstProgress = (() => {
const separated = round.separateComponents !== false;
rows.forEach((row, index) => {
const id = key(row, index),
input = round.inputs?.[id] || {},
old = oldRows.get(id);
input = ambiguous.has(id) ? {} : round.inputs?.[id] || {},
old = ambiguous.has(id) ? null : oldRows.get(id);
if (ambiguous.has(id) && (round.inputs?.[id] || oldRows.has(id)))
errors.push(`기성 내역 식별자가 중복됩니다: ${id}`);
for (const field of ["GN", "TC", "Ratio", ...components])
row["Jun" + field] = num(old?.["Total" + field]);
if (num(row.Level) > 0 || [54, 59].includes(num(row.LinkIndex))) {
@@ -301,5 +312,5 @@ const EstProgress = (() => {
}
return project;
}
return { calculate, lock, unlock, rebuild };
return { calculate, lock, unlock, rebuild, ambiguousKeys };
})();