194 lines
7.2 KiB
Python
194 lines
7.2 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
CALC = ROOT / "tools" / "est-test" / "calc.js"
|
|
|
|
|
|
def calculate(data):
|
|
script = 'const vm=require("vm"), fs=require("fs"); const ctx={}; vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.calc=EstCalc",ctx); console.log(JSON.stringify(ctx.calc.calculate(JSON.parse(process.argv[2]))));'
|
|
return json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script, str(CALC), json.dumps(data)], text=True, encoding="utf-8"
|
|
)
|
|
)
|
|
|
|
|
|
def test_est_test_calculation_and_linked_price_change():
|
|
data = {
|
|
"sources": [{"id": "s", "kind": "재료비", "price": 10.129}],
|
|
"units": [{"id": "u", "lines": [{"sourceId": "s", "quantity": 2, "digits": 2}]}],
|
|
"estimate": [{"unitId": "u", "quantity": 3}],
|
|
"costs": [{"base": "직접공사비", "rate": 10}, {"base": "누계", "rate": 5}],
|
|
}
|
|
result = calculate(data)
|
|
assert result["units"]["u"]["lines"][0]["amount"] == 20.25
|
|
assert result["directTotal"] == 60
|
|
assert [row["amount"] for row in result["costs"]] == [6, 3]
|
|
assert result["total"] == 69
|
|
data["sources"][0]["price"] = 20
|
|
assert calculate(data)["total"] == 138
|
|
|
|
|
|
def test_est_test_unlinked_rows_contribute_zero():
|
|
data = {
|
|
"sources": [],
|
|
"units": [{"id": "u", "lines": [{"sourceId": "", "quantity": 2}]}],
|
|
"estimate": [{"unitId": "missing", "quantity": 4}],
|
|
"costs": [],
|
|
}
|
|
result = calculate(data)
|
|
assert result["units"]["u"]["total"] == result["total"] == 0
|
|
|
|
|
|
def test_source_selection_recursive_unit_and_percent():
|
|
data = {
|
|
"sources": [
|
|
{
|
|
"id": "s",
|
|
"kind": "실적",
|
|
"price": 900,
|
|
"prices": [0, 125, 155, 0, 0, 0],
|
|
"priceMode": "평균",
|
|
}
|
|
],
|
|
"units": [
|
|
{"id": "u1", "name": "기초", "lines": [{"sourceId": "s", "quantity": 2}]},
|
|
{
|
|
"id": "u2",
|
|
"name": "상위",
|
|
"lines": [
|
|
{"type": "일위대가", "sourceId": "", "unitId": "u1", "quantity": 1},
|
|
{
|
|
"type": "비율",
|
|
"sourceId": "",
|
|
"base": "실적",
|
|
"kind": "경비",
|
|
"quantity": 10,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"estimate": [{"unitId": "u2", "quantity": 2}],
|
|
"costs": [],
|
|
}
|
|
result = calculate(data)
|
|
assert result["units"]["u1"]["parts"]["실적"] == 280
|
|
assert result["units"]["u2"]["parts"]["경비"] == 28
|
|
assert result["directTotal"] == 616
|
|
|
|
|
|
def test_sample_percentage_row_and_cycle_error():
|
|
data = {
|
|
"sources": [{"id": "labor", "kind": "노무비", "price": 66841}],
|
|
"units": [
|
|
{
|
|
"id": "u",
|
|
"name": "호표",
|
|
"lines": [
|
|
{"sourceId": "labor", "quantity": 1},
|
|
{
|
|
"type": "비율",
|
|
"sourceId": "",
|
|
"base": "노무비",
|
|
"kind": "재료비",
|
|
"quantity": 2,
|
|
},
|
|
],
|
|
}
|
|
],
|
|
"estimate": [],
|
|
"costs": [],
|
|
}
|
|
result = calculate(data)
|
|
assert result["units"]["u"]["parts"]["재료비"] == 1336
|
|
data["units"][0]["lines"].append(
|
|
{"type": "일위대가", "sourceId": "", "unitId": "u", "quantity": 1}
|
|
)
|
|
assert result["errors"] == []
|
|
assert calculate(data)["errors"]
|
|
|
|
|
|
def test_progress_uses_approved_quantity_only_when_selected():
|
|
script = (
|
|
'const vm=require("vm"),fs=require("fs");const c={};'
|
|
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.calc=EstCalc",c);'
|
|
"console.log(JSON.stringify([c.calc.progress(100,2,3,1,false),"
|
|
"c.calc.progress(100,2,3,1,true)]));"
|
|
)
|
|
ordinary, approved = json.loads(
|
|
subprocess.check_output(["node", "-e", script, str(CALC)], text=True, encoding="utf-8")
|
|
)
|
|
assert ordinary["currentAmount"] == 300
|
|
assert ordinary["cumulativeAmount"] == 500
|
|
assert approved["cumulativeAmount"] == 300
|
|
|
|
|
|
def test_machine_components():
|
|
script = (
|
|
'const vm=require("vm"),fs=require("fs");const c={};'
|
|
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.calc=EstCalc",c);'
|
|
"console.log(JSON.stringify(c.calc.machine({purchase:1000000,rentalCoefficient:100,"
|
|
"fuelPrice:1000,fuelQuantity:2,suppliesRate:5,laborPrice:8000,laborFactor:0.125,laborHours:1})));"
|
|
)
|
|
result = json.loads(
|
|
subprocess.check_output(["node", "-e", script, str(CALC)], text=True, encoding="utf-8")
|
|
)
|
|
assert result == {"재료비": 2100, "노무비": 1000, "경비": 10, "실적": 0}
|
|
|
|
|
|
def test_formula_parser_rejects_invalid_expressions():
|
|
formula = ROOT / "tools" / "est-test" / "formula.js"
|
|
script = (
|
|
'const vm=require("vm"),fs=require("fs");const c={};'
|
|
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.f=EstFormula",c);'
|
|
'let failed=false;try{c.f.evaluate("1/0",{})}catch{failed=true};'
|
|
'console.log(JSON.stringify([c.f.evaluate("([금액]+2)*3",{"금액":8}),failed]));'
|
|
)
|
|
assert json.loads(
|
|
subprocess.check_output(["node", "-e", script, str(formula)], text=True, encoding="utf-8")
|
|
) == [30, True]
|
|
|
|
|
|
def test_escalation_comparison_branches():
|
|
workflows = ROOT / "tools" / "est-test" / "workflows.js"
|
|
script = (
|
|
'const vm=require("vm"),fs=require("fs");const c={};'
|
|
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.w=EstWorkflows",c);'
|
|
"console.log(JSON.stringify([c.w.escalation(100,80,120,2),"
|
|
"c.w.escalation(100,110,125,2),c.w.escalation(100,80,90,1),"
|
|
"c.w.excluded(3,5,4)]));"
|
|
)
|
|
a, b, lower, excluded = json.loads(
|
|
subprocess.check_output(["node", "-e", script, str(workflows)], text=True, encoding="utf-8")
|
|
)
|
|
assert (a["rate"], a["difference"], a["amount"]) == (20, 16, 192)
|
|
assert (b["difference"], b["amount"]) == (15, 250)
|
|
assert lower["difference"] == 0
|
|
assert excluded == 5
|
|
|
|
|
|
def test_resource_quantities_follow_nested_units():
|
|
workflows = ROOT / "tools" / "est-test" / "workflows.js"
|
|
script = (
|
|
'const vm=require("vm"),fs=require("fs");const c={};'
|
|
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.w=EstWorkflows",c);'
|
|
"console.log(JSON.stringify(c.w.resources(JSON.parse(process.argv[2]))));"
|
|
)
|
|
data = {
|
|
"sources": [{"id": "s", "name": "자재"}],
|
|
"units": [
|
|
{"id": "a", "lines": [{"sourceId": "s", "quantity": 2}]},
|
|
{"id": "b", "lines": [{"type": "일위대가", "unitId": "a", "quantity": 3}]},
|
|
],
|
|
"estimate": [{"unitId": "b", "quantity": 4}],
|
|
}
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script, str(workflows), json.dumps(data)], text=True, encoding="utf-8"
|
|
)
|
|
)
|
|
assert result["rows"][0]["quantity"] == 24
|
|
assert result["errors"] == []
|