fix(est): preserve component rates in resource totals
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
# Annual allocation and resource expansion regression cases.
|
||||
|
||||
from test_est_rea_parity import run
|
||||
|
||||
|
||||
def test_annual_formulas_split_component_amounts_and_preserve_remainder():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'R',Name:'item',GN:1,NU:5,GU:3},{LinkIndex:54}];
|
||||
p.annual=[{name:'1차',formula:'[기준]*0.5'},{name:'2차',formula:'[기준]-[1차]'}];
|
||||
const q=EstNative.calculate(p),before=JSON.stringify(q);
|
||||
return {output:EstBusiness.annual(q),preserved:before===JSON.stringify(q)};
|
||||
})()""",
|
||||
)
|
||||
assert result["preserved"]
|
||||
output = result["output"]
|
||||
assert output["errors"] == []
|
||||
first, second = [item["project"] for item in output["results"]]
|
||||
assert first["tables"]["EstMainDat"][0]["GN"] == 0.5
|
||||
assert second["tables"]["EstMainDat"][0]["GN"] == 0.5
|
||||
assert [item["calculation"]["direct"]["TC"] for item in [first, second]] == [3, 5]
|
||||
assert first["tables"]["EstMainDat"][0]["NC"] == 2
|
||||
assert second["tables"]["EstMainDat"][0]["NC"] == 3
|
||||
assert second["tables"]["EstMainDat"][0]["GC"] == 2
|
||||
assert all(first["tables"]["EstMainDat"][0][key] == 0 for key in ["TU", "NU", "GU", "JU"])
|
||||
|
||||
|
||||
def test_annual_fixed_amount_with_zero_quantity_and_duplicate_names():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:0,NC:3,GC:7,TC:10,CalcIndex:1},{LinkIndex:54}];
|
||||
p.annual=[{name:'1차',formula:'[기준]*0.5'},{name:'2차',formula:'[기준]-[1차]'}];
|
||||
const output=EstBusiness.annual(EstNative.calculate(p));
|
||||
p.annual[1].name='1차';
|
||||
return {output,invalid:EstBusiness.annual(p)};
|
||||
})()""",
|
||||
)
|
||||
assert result["output"]["errors"] == []
|
||||
assert [
|
||||
item["project"]["calculation"]["direct"]["TC"] for item in result["output"]["results"]
|
||||
] == [4, 6]
|
||||
assert result["invalid"]["results"] == []
|
||||
assert len(result["invalid"]["errors"]) == 1
|
||||
|
||||
|
||||
def test_resource_expansion_preserves_different_labor_and_material_bid_rates():
|
||||
result = run(
|
||||
"math",
|
||||
r"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:100}];
|
||||
p.tables.LaborcDat=[{Code:'L',Name:'labor',ValueIndex:4,InputTC:100}];
|
||||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||||
{Code:'M',LinkIndex:5,SCheck:-2,LineData:' \x0ematerial\x0e: \x03100\x03 = \x12100\x12'},
|
||||
{Code:'L',LinkIndex:6,SCheck:-1,LineData:' \x0elabor\x0e: \x03100\x03 = \x12100\x12'}];
|
||||
p.tables.CostTitleDat=[{Code:'C'}];
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'C',LinkIndex:104,GN:10},{LinkIndex:54}];
|
||||
const bid=EstBusiness.bid(EstNative.calculate(p),[80,90,100,100]);
|
||||
const direct=EstResources.calculate(bid);
|
||||
p.tables.HeadMainDat=[{Index:1,Code:'H'},{Code:'C',LinkIndex:104,GN:1}];
|
||||
p.tables.HeadTitleDat=[{Code:'H'}];p.tables.EstMainDat[0].LinkIndex=103;p.tables.EstMainDat[0].Code='H';
|
||||
const nested=EstResources.calculate(EstBusiness.bid(EstNative.calculate(p),[80,90,100,100]));
|
||||
return {direct,nested};
|
||||
})()""",
|
||||
)
|
||||
for output in result.values():
|
||||
assert output["errors"] == []
|
||||
resources = {row["Code"]: row for row in output["rows"]}
|
||||
assert resources["M"]["quantity"] == 9
|
||||
assert resources["M"]["amount"] == 900
|
||||
assert resources["L"]["quantity"] == 8
|
||||
assert resources["L"]["amount"] == 800
|
||||
|
||||
|
||||
def test_resource_expansion_selects_nested_head_component_and_all_conversions():
|
||||
result = run(
|
||||
"math",
|
||||
r"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:100}];
|
||||
p.tables.LaborcDat=[{Code:'L',Name:'labor',ValueIndex:4,InputTC:100}];
|
||||
p.tables.HeadMainDat=[{Index:1,Code:'H',Name:'head'},
|
||||
{Code:'M',LinkIndex:5,GN:1},{Code:'L',LinkIndex:6,GN:1}];
|
||||
p.tables.HeadTitleDat=[{Code:'H',Name:'head'}];
|
||||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||||
{Code:'H',LinkIndex:103,LineData:' \x0ehead\x0e'},
|
||||
{Code:'H',LinkIndex:103,SCheck:-1,LineData:' 노무비: \x03100\x03 = \x12100\x12'},
|
||||
{Code:'H',LinkIndex:103,SCheck:-2,LineData:' 재료비: \x03100\x03 = \x12100\x12'}];
|
||||
p.tables.CostTitleDat=[{Code:'C'}];
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'C',LinkIndex:104,GN:10},{LinkIndex:54}];
|
||||
const selected=EstResources.calculate(p);
|
||||
p.tables.HeadMainDat.push({LinkIndex:68,GN:0.5},{LinkIndex:68,GN:0.1});
|
||||
p.tables.EstMainDat[0].Code='H';p.tables.EstMainDat[0].LinkIndex=103;
|
||||
return {selected,converted:EstResources.calculate(p)};
|
||||
})()""",
|
||||
)
|
||||
assert all(output["errors"] == [] for output in result.values())
|
||||
assert len(result["selected"]["rows"]) == 2
|
||||
assert all(
|
||||
row["quantity"] == 10 and row["amount"] == 1000 for row in result["selected"]["rows"]
|
||||
)
|
||||
assert all(
|
||||
row["quantity"] == 0.5 and row["amount"] == 50 for row in result["converted"]["rows"]
|
||||
)
|
||||
|
||||
|
||||
def test_resource_expansion_includes_money_only_rows_and_percentage_costs():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{PkID:1,Name:'fixed',GN:0,NC:3,GC:7,JC:2,TC:12,CalcIndex:1},{LinkIndex:54}];
|
||||
const fixed=EstResources.calculate(p);
|
||||
const q=EstNative.empty();q.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:100}];
|
||||
q.tables.HeadMainDat=[{Index:1,Code:'H'},{Code:'M',LinkIndex:5,GN:1},
|
||||
{LinkIndex:58,Size:'합계',SCheck:3,GN:10}];q.tables.HeadTitleDat=[{Code:'H'}];
|
||||
q.tables.EstMainDat=[{Code:'H',LinkIndex:103,GN:2},{LinkIndex:54}];
|
||||
return {fixed,percentage:EstResources.calculate(q)};
|
||||
})()""",
|
||||
)
|
||||
assert all(output["errors"] == [] for output in result.values())
|
||||
fixed = {row["Code"]: row for row in result["fixed"]["rows"]}
|
||||
assert fixed["LZZZZZZZZZZ"]["amount"] == fixed["LZZZZZZZZZZ"]["quantity"] == 3
|
||||
assert fixed["MZZZZZZZZZZ"]["amount"] == fixed["MZZZZZZZZZZ"]["quantity"] == 7
|
||||
assert fixed["EZZZZZZZZZZ"]["amount"] == 2
|
||||
assert fixed["EZZZZZZZZZZ"]["Unite"] == "제외금액"
|
||||
assert sum(row["amount"] for row in result["percentage"]["rows"]) == 220
|
||||
|
||||
|
||||
def test_cost_code_refresh_without_name_marker_still_tracks_the_actual_resource():
|
||||
result = run(
|
||||
"math",
|
||||
r"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:150}];
|
||||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||||
{Code:'M',LinkIndex:5,SCheck:-2,LineData:' 재료비: \x03100\x03 ×2 = \x12200\x12'}];
|
||||
p.tables.CostTitleDat=[{Code:'C'}];
|
||||
p.tables.EstMainDat=[{Code:'C',LinkIndex:104,GN:1},{LinkIndex:54}];
|
||||
return {calculated:EstNative.calculate(p),resources:EstResources.calculate(p)};
|
||||
})()""",
|
||||
)
|
||||
assert result["calculated"]["calculation"]["errors"] == []
|
||||
assert result["calculated"]["calculation"]["direct"]["TC"] == 300
|
||||
assert result["resources"]["errors"] == []
|
||||
assert result["resources"]["rows"][0]["Code"] == "M"
|
||||
assert result["resources"]["rows"][0]["quantity"] == 2
|
||||
assert result["resources"]["rows"][0]["amount"] == 300
|
||||
|
||||
|
||||
def test_unlinked_text_price_does_not_inherit_the_previous_rows_source():
|
||||
result = run(
|
||||
"math",
|
||||
r"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:150}];
|
||||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||||
{Code:'M',LinkIndex:5,SCheck:-2,LineData:' \x0ematerial\x0e: \x03100\x03 = \x12100\x12'},
|
||||
{Code:'M',LinkIndex:0,SCheck:-2,LineData:' 직접입력: \x0350\x03 ×2 = \x12100\x12'}];
|
||||
p.tables.CostTitleDat=[{Code:'C'}];
|
||||
p.tables.EstMainDat=[{Code:'C',LinkIndex:104,GN:1},{LinkIndex:54}];
|
||||
return {calculated:EstNative.calculate(p),resources:EstResources.calculate(p)};
|
||||
})()""",
|
||||
)
|
||||
assert result["calculated"]["calculation"]["errors"] == []
|
||||
assert result["calculated"]["calculation"]["direct"]["TC"] == 250
|
||||
resources = {row["Code"]: row for row in result["resources"]["rows"]}
|
||||
assert resources["M"]["quantity"] == 1
|
||||
assert resources["M"]["amount"] == 150
|
||||
assert resources["MZZZZZZZZZZ"]["amount"] == 100
|
||||
@@ -648,47 +648,3 @@ def test_text_total_without_result_marker_evaluates_its_expression():
|
||||
assert result["rows"][0]["TC"] == 20
|
||||
assert result["rows"][1]["TC"] == 20
|
||||
assert result["rows"][2]["TC"] == 0
|
||||
|
||||
|
||||
def test_annual_formulas_split_component_amounts_and_preserve_remainder():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'R',Name:'item',GN:1,NU:5,GU:3},{LinkIndex:54}];
|
||||
p.annual=[{name:'1차',formula:'[기준]*0.5'},{name:'2차',formula:'[기준]-[1차]'}];
|
||||
const q=EstNative.calculate(p),before=JSON.stringify(q);
|
||||
return {output:EstBusiness.annual(q),preserved:before===JSON.stringify(q)};
|
||||
})()""",
|
||||
)
|
||||
assert result["preserved"]
|
||||
output = result["output"]
|
||||
assert output["errors"] == []
|
||||
first, second = [item["project"] for item in output["results"]]
|
||||
assert first["tables"]["EstMainDat"][0]["GN"] == 0.5
|
||||
assert second["tables"]["EstMainDat"][0]["GN"] == 0.5
|
||||
assert [item["calculation"]["direct"]["TC"] for item in [first, second]] == [3, 5]
|
||||
assert first["tables"]["EstMainDat"][0]["NC"] == 2
|
||||
assert second["tables"]["EstMainDat"][0]["NC"] == 3
|
||||
assert second["tables"]["EstMainDat"][0]["GC"] == 2
|
||||
assert all(first["tables"]["EstMainDat"][0][key] == 0 for key in ["TU", "NU", "GU", "JU"])
|
||||
|
||||
|
||||
def test_annual_fixed_amount_with_zero_quantity_and_duplicate_names():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:0,NC:3,GC:7,TC:10,CalcIndex:1},{LinkIndex:54}];
|
||||
p.annual=[{name:'1차',formula:'[기준]*0.5'},{name:'2차',formula:'[기준]-[1차]'}];
|
||||
const output=EstBusiness.annual(EstNative.calculate(p));
|
||||
p.annual[1].name='1차';
|
||||
return {output,invalid:EstBusiness.annual(p)};
|
||||
})()""",
|
||||
)
|
||||
assert result["output"]["errors"] == []
|
||||
assert [
|
||||
item["project"]["calculation"]["direct"]["TC"] for item in result["output"]["results"]
|
||||
] == [4, 6]
|
||||
assert result["invalid"]["results"] == []
|
||||
assert len(result["invalid"]["errors"]) == 1
|
||||
|
||||
@@ -26,12 +26,14 @@
|
||||
|
||||
연차공사의 `[기준]*0.5` 수식은 수량과 각 성분 금액에 각각 적용됩니다. 금액은 정수 절사하고, 다음 차수의 `[기준]-[1차]`로 절사 후 잔액을 배분합니다. 차수 사본은 단가를 비운 고정 금액으로 저장하여 다시 열어도 금액을 유지합니다. `[계약수량]`은 이전 작업 파일과의 호환 표기로 `[기준]`과 같은 역할을 합니다.
|
||||
|
||||
동원 집계는 비목별 낙찰률과 단위환산을 하위 산근·일위대가에도 전달합니다. 연결 단가가 없는 금액 입력분은 원본 전용 코드로 따로 합산하고, 장비 시간으로 바꿀 수 없는 경비는 `제외금액` 단위로 보존합니다. 산근에서 연결을 해제한 단가는 앞 행의 원천을 가져오지 않습니다.
|
||||
|
||||
**전체 원본 동등성은 아직 확보하지 못했습니다.** 기성·물가변동·연차 등은 분석 분기와 합성 시나리오를 시험했으며 원본 업무 샘플 대조는 없습니다. 원본 화면 실행 비교는 못했습니다. 원본 페이지·도면 배치, CAD/VEC0650 편집, 원본 인쇄 서식, Excel/XML/JDL 변환, ESTX 수정 저장은 미구현입니다. 원본 대체품의 완료 판정으로 보지 않습니다.
|
||||
|
||||
검사:
|
||||
|
||||
```powershell
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_est_rea_parity.py resources/tester/test_est_standalone.py -q
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_est_rea_parity.py resources/tester/test_est_business.py resources/tester/test_est_standalone.py -q
|
||||
```
|
||||
|
||||
`native-*.js`가 현재 화면·계산 엔진이며 브라우저와 로컬 서버가 같은 계산 코드를 사용합니다. `build_sample.py`는 REA 자료에서 입력·기대값을 재생성합니다. 기존 `app.js`, `calc.js`, `formula.js`, `workflows.js`는 이전 데모의 회귀 시험용이며 현재 진입 화면에서 사용하지 않습니다.
|
||||
|
||||
@@ -32,7 +32,55 @@ const EstResources = (() => {
|
||||
if (kind === 105 && quantity === 0 && amount !== null) row.amountForHours += amount;
|
||||
output.set(id, row);
|
||||
}
|
||||
function expand(kind, code, multiplier, path) {
|
||||
function bidComponents(row, source) {
|
||||
const classified = [1, 2, 3].includes(num(row.SCheck));
|
||||
const whole = row.ApplyNacChal && num(source?.TC) ? num(row.TU) / num(source.TC) : 1;
|
||||
return Object.fromEntries(
|
||||
components.map((key, index) => [
|
||||
key,
|
||||
classified
|
||||
? whole
|
||||
: row.ApplyNacChal && num(source?.[key])
|
||||
? num(row[EstMath.units[index]]) / num(source[key])
|
||||
: 1,
|
||||
]),
|
||||
);
|
||||
}
|
||||
function weightedRate(source, rates) {
|
||||
return num(source?.TC)
|
||||
? components.reduce((amount, key) => amount + num(source[key]) * rates[key], 0) /
|
||||
num(source.TC)
|
||||
: 1;
|
||||
}
|
||||
function money(row, multiplier, rates) {
|
||||
const labels = {
|
||||
NC: [6, "LZZZZZZZZZZ", "노무비"],
|
||||
GC: [5, "MZZZZZZZZZZ", "재료비"],
|
||||
JC: [105, "EZZZZZZZZZZ", "경비"],
|
||||
SC: [21, "SZZZZZZZZZZ", "표준시장단가"],
|
||||
};
|
||||
for (const key of components) {
|
||||
const amount = num(row[key]) * multiplier * rates[key];
|
||||
if (!amount) continue;
|
||||
const [kind, Code, Name] = labels[key],
|
||||
id = `${kind}:${Code}`;
|
||||
const item = output.get(id) || {
|
||||
Code,
|
||||
Name,
|
||||
Size: "금액 입력분",
|
||||
Unite: key === "JC" ? "제외금액" : "원",
|
||||
kind,
|
||||
quantity: 0,
|
||||
amount: 0,
|
||||
amountForHours: 0,
|
||||
};
|
||||
item.amount += amount;
|
||||
if (key !== "JC") item.quantity += amount;
|
||||
output.set(id, item);
|
||||
}
|
||||
}
|
||||
function expand(kind, code, multiplier, path, componentRates = null) {
|
||||
const rates = componentRates || Object.fromEntries(components.map((key) => [key, 1]));
|
||||
const id = `${kind}:${code}`;
|
||||
if (path.includes(id)) {
|
||||
errors.push(`동원 순환 참조: ${id}`);
|
||||
@@ -45,20 +93,39 @@ const EstResources = (() => {
|
||||
return;
|
||||
}
|
||||
for (const row of group.rows) {
|
||||
if (!row.ResourceCode || row.ResourceFactor == null) continue;
|
||||
if (!row.ResourceCode || row.ResourceFactor == null) {
|
||||
const key = { [-1]: "NC", [-2]: "GC", [-3]: "JC", [-4]: "SC" }[num(row.SCheck)];
|
||||
if (key && !String(row.LineData || "").includes("\x1a") && num(row.LinkIndex) !== 61)
|
||||
money({ [key]: num(row.TC) }, multiplier, rates);
|
||||
continue;
|
||||
}
|
||||
const source = tables[EstNative.sourceTables[num(row.ResourceKind)]]?.find(
|
||||
(item) => item.Code === row.ResourceCode,
|
||||
);
|
||||
const directKey = row.ResourceComponent;
|
||||
const rate = directKey ? rates[directKey] : weightedRate(source, rates);
|
||||
if (!rate) continue;
|
||||
if ([103, 104].includes(num(row.ResourceKind)))
|
||||
expand(num(row.ResourceKind), row.ResourceCode, multiplier * num(row.ResourceFactor), [
|
||||
...path,
|
||||
id,
|
||||
]);
|
||||
expand(
|
||||
num(row.ResourceKind),
|
||||
row.ResourceCode,
|
||||
multiplier * num(row.ResourceFactor),
|
||||
[...path, id],
|
||||
Object.fromEntries(
|
||||
components.map((key) => [
|
||||
key,
|
||||
!row.ResourceComponent || row.ResourceComponent === key ? rates[key] : 0,
|
||||
]),
|
||||
),
|
||||
);
|
||||
else if (num(row.ResourceKind) === 105)
|
||||
record(105, row.ResourceCode, 0, multiplier * num(row.TC));
|
||||
record(105, row.ResourceCode, 0, multiplier * num(row.TC) * rate);
|
||||
else
|
||||
record(
|
||||
num(row.ResourceKind),
|
||||
row.ResourceCode,
|
||||
multiplier * num(row.ResourceFactor),
|
||||
multiplier * num(row.TC),
|
||||
multiplier * num(row.ResourceFactor) * rate,
|
||||
multiplier * num(row.TC) * rate,
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -70,43 +137,61 @@ const EstResources = (() => {
|
||||
return;
|
||||
}
|
||||
let rate = 1;
|
||||
const bid = group.rows.find((row) => num(row.LinkIndex) === 52);
|
||||
if (bid) rate = num(bid.GN) / 100;
|
||||
const conversion = group.rows.find((row) => num(row.LinkIndex) === 68);
|
||||
if (conversion)
|
||||
rate *= num(conversion.GN) / (["%", "%"].includes(conversion.Unite) ? 100 : 1);
|
||||
for (const row of group.rows.slice(1)) {
|
||||
if (num(row.CalcIndex) >= 2 || [51, 52, 58, 62, 68].includes(num(row.LinkIndex)))
|
||||
continue;
|
||||
if (num(row.LinkIndex) === 52) {
|
||||
rate *= num(row.GN) / 100;
|
||||
break;
|
||||
}
|
||||
if (num(row.LinkIndex) === 68)
|
||||
rate *= num(row.GN) / (["%", "%"].includes(row.Unite) ? 100 : 1);
|
||||
}
|
||||
for (const row of group.rows.slice(1)) {
|
||||
if (num(row.CalcIndex) >= 2 || [51, 52, 68].includes(num(row.LinkIndex))) continue;
|
||||
const source = tables[EstNative.sourceTables[num(row.LinkIndex)]]?.find(
|
||||
(r) => r.Code === row.Code,
|
||||
);
|
||||
const unitRatio = row.ApplyNacChal && num(source?.TC) ? num(row.TU) / num(source.TC) : 1;
|
||||
const bid = bidComponents(row, source);
|
||||
const classified = { 1: "GC", 2: "NC", 3: "JC" }[num(row.SCheck)];
|
||||
const childRates = Object.fromEntries(
|
||||
components.map((key) => [
|
||||
key,
|
||||
bid[key] * (classified ? rates[classified] : rates[key]),
|
||||
]),
|
||||
);
|
||||
const quantity =
|
||||
(multiplier * num(row.GN) * rate * unitRatio) /
|
||||
(["%", "%"].includes(row.Unite) ? 100 : 1);
|
||||
(multiplier * num(row.GN) * rate) / (["%", "%"].includes(row.Unite) ? 100 : 1);
|
||||
if ([103, 104].includes(num(row.LinkIndex)))
|
||||
expand(num(row.LinkIndex), row.Code, quantity, [...path, id]);
|
||||
else if (EstNative.sourceTables[num(row.LinkIndex)])
|
||||
record(num(row.LinkIndex), row.Code, quantity, multiplier * num(row.TC) * rate);
|
||||
expand(num(row.LinkIndex), row.Code, quantity, [...path, id], childRates);
|
||||
else if (EstNative.sourceTables[num(row.LinkIndex)]) {
|
||||
const count = quantity * weightedRate(source, childRates);
|
||||
const amount =
|
||||
multiplier *
|
||||
rate *
|
||||
components.reduce((amount, key) => amount + num(row[key]) * rates[key], 0);
|
||||
if (count || amount) record(num(row.LinkIndex), row.Code, count, amount);
|
||||
} else money(row, multiplier * rate, rates);
|
||||
}
|
||||
return;
|
||||
}
|
||||
record(kind, code, multiplier);
|
||||
const source = tables[EstNative.sourceTables[kind]]?.find((row) => row.Code === code);
|
||||
record(kind, code, multiplier * weightedRate(source, rates));
|
||||
}
|
||||
for (const row of tables.EstMainDat) {
|
||||
if (num(row.LinkIndex) === 54) break;
|
||||
if (
|
||||
num(row.Level) > 0 ||
|
||||
num(row.CalcIndex) >= 2 ||
|
||||
!EstNative.sourceTables[num(row.LinkIndex)]
|
||||
[51, 52, 68].includes(num(row.LinkIndex))
|
||||
)
|
||||
continue;
|
||||
if (!EstNative.sourceTables[num(row.LinkIndex)]) {
|
||||
money(row, 1, { NC: 1, GC: 1, JC: 1, SC: 1 });
|
||||
continue;
|
||||
}
|
||||
const source = tables[EstNative.sourceTables[num(row.LinkIndex)]]?.find(
|
||||
(r) => r.Code === row.Code,
|
||||
);
|
||||
const unitRatio = row.ApplyNacChal && num(source?.TC) ? num(row.TU) / num(source.TC) : 1;
|
||||
expand(num(row.LinkIndex), row.Code, num(row.GN) * unitRatio, []);
|
||||
expand(num(row.LinkIndex), row.Code, num(row.GN), [], bidComponents(row, source));
|
||||
}
|
||||
const rows = [...output.values()];
|
||||
for (const row of rows)
|
||||
|
||||
@@ -63,7 +63,10 @@ const EstText = (() => {
|
||||
}
|
||||
for (let index = 0; index < rows.length; index++) {
|
||||
const row = rows[index];
|
||||
currentSource = null;
|
||||
delete row.Error;
|
||||
for (const key of ["ResourceFactor", "ResourceCode", "ResourceKind", "ResourceComponent"])
|
||||
delete row[key];
|
||||
if (num(row.Index) > 0) {
|
||||
finish();
|
||||
head = row;
|
||||
@@ -126,11 +129,19 @@ const EstText = (() => {
|
||||
matched = original.match(marker),
|
||||
sc = num(row.SCheck);
|
||||
const oldValue = matched ? numeric(matched[1]) : null;
|
||||
if (num(row.LinkIndex) > 0 && /\x0e/.test(original)) {
|
||||
if (
|
||||
[5, 6, 21, 101, 103, 104, 105].includes(num(row.LinkIndex)) &&
|
||||
(row.Code || /[\x0e\x03]/.test(original))
|
||||
) {
|
||||
const name = original.match(/\x0e([^\x0e]+)\x0e/),
|
||||
size = original.match(/\x13([^\x13]+)\x13/);
|
||||
try {
|
||||
currentSource = resolve({ ...row, Name: name?.[1], Size: size?.[1] });
|
||||
currentSource = null;
|
||||
currentSource = resolve({
|
||||
...row,
|
||||
Name: name?.[1] ?? row.Name,
|
||||
Size: size?.[1] ?? row.Size,
|
||||
});
|
||||
} catch (error) {
|
||||
errors.push(`${head.Code}:${index + 1} ${error.message}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user