Files
Aislo/resources/tester/test_est_jdl_special.py
T

198 lines
12 KiB
Python

import json
import subprocess
from pathlib import Path
from test_est_rea_parity import run
ROOT = Path(__file__).resolve().parents[2]
def test_jdl_special_unit_detail_with_matching_totals_is_editable():
project = run("math", "EstNative.empty()")
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
const empty=JSON.parse(fs.readFileSync(0,'utf8')),blank=structuredClone(empty);
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
['m.txt',table('m.txt',[{F_CODE:'M1',F_DES:'material',F_MPRICE:100}])],
['unit.txt',table('unit.txt',[{F_CODE:'U1',F_DES:'unit',F_MPRICE:300,F_LPRICE:50}])],
['unitpart.txt',table('unitpart.txt',[{F_CODE:'U1',F_SEQ:'1',F_CODE1:'M1',F_DES:'normal',F_QQTY:2,F_MPRICE:100,F_MCOST:200},
{F_CODE:'U1',F_SEQ:'2',F_CODE1:'M1',F_DES:'special',F_QQTY:1,F_MCOST:100,F_LCOST:50,F_SPROPER:'1'}])],
['gongjong.txt',table('gongjong.txt',[{F_CODE:'01',F_DES:'work'}])],
['mainpart.txt',table('mainpart.txt',[{F_CODE:'01',F_SEQ:'1',F_CODE1:'U1',F_DES:'unit',F_QQTY:1,F_MPRICE:300,F_LPRICE:50}])]]);
const p=j.read(a.pack(files),empty),before=structuredClone(p.tables.HeadMainDat);p.options.AutoCalc=true;p.options.SourceRefresh=true;
p.tables.HeadMainDat[2].GC=110;const q=s.calculate(p),out=j.output(q),back=j.read(out.bytes,blank);
process.stdout.write(JSON.stringify({before,title:q.tables.HeadTitleDat[0],rows:back.tables.HeadMainDat,
total:back.tables.EstMainDat.at(-1).TC,warnings:p.importWarnings,errors:q.calculation.errors}));"""
result = json.loads(
subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
)
)
assert len(result["before"]) == 3
assert result["before"][2]["Name"] == "special"
assert result["before"][2]["CalcIndex"] == 1
assert result["title"]["TC"] == 360
assert result["rows"][2]["GC"] == 110
assert result["rows"][2]["NC"] == 50
assert result["total"] == 360
assert any("특수 구성" in warning for warning in result["warnings"])
assert not result["errors"]
def test_jdl_special_unit_detail_without_source_keeps_recorded_amount():
project = run("math", "EstNative.empty()")
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json');
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
['unit.txt',table('unit.txt',[{F_CODE:'U1',F_DES:'unit',F_MPRICE:70}])],
['unitpart.txt',table('unitpart.txt',[{F_CODE:'U1',F_SEQ:'1',F_CODE1:'missing',F_DES:'special',F_QQTY:1,F_MCOST:70,F_SPROPER:'1'}])]]);
const p=j.read(a.pack(files),empty);process.stdout.write(JSON.stringify({rows:p.tables.HeadMainDat,warnings:p.importWarnings}));"""
result = json.loads(
subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
)
)
assert len(result["rows"]) == 2
assert result["rows"][1]["GC"] == 70
assert result["rows"][1]["LinkIndex"] == 0
assert result["rows"][1]["JdlSourceCode"] == "missing"
assert any("특수 구성" in warning for warning in result["warnings"])
def test_jdl_special_machine_detail_is_fixed_only_when_amounts_match():
project = run("math", "EstNative.empty()")
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
const empty=JSON.parse(fs.readFileSync(0,'utf8')),blank=structuredClone(empty);
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
['junit.txt',table('junit.txt',[{F_CODE:'J1',F_DES:'machine',F_MPRICE:100,F_LPRICE:20},
{F_CODE:'J2',F_DES:'mismatch',F_MPRICE:50},{F_CODE:'J3',F_DES:'percent',F_MPRICE:50},
{F_CODE:'J4',F_DES:'weighted',F_MPRICE:100}])],
['jpart.txt',table('jpart.txt',[{F_CODE:'J1',F_SEQ:'1',F_DES:'normal',F_QQTY:1,F_MPRICE:60},
{F_CODE:'J1',F_SEQ:'2',F_DES:'special',F_QQTY:1,F_MPRICE:40,F_LPRICE:20,F_SPROPER:'1'},
{F_CODE:'J2',F_SEQ:'1',F_DES:'mismatch',F_QQTY:1,F_MPRICE:10,F_SPROPER:'1'},
{F_CODE:'J3',F_SEQ:'1',F_DES:'percent',F_UNIT:'%',F_QQTY:1,F_MPRICE:50,F_SPROPER:'1'},
{F_CODE:'J4',F_SEQ:'1',F_DES:'weighted part',F_QQTY:2,F_MPRICE:50,F_SPROPER:'1'}])]]);
const p=j.read(a.pack(files),empty),before=structuredClone(p.tables.MachMainDat);p.options.AutoCalc=true;
p.tables.MachMainDat[2].GC=50;const q=s.calculate(p),out=j.output(q),back=j.read(out.bytes,blank);
process.stdout.write(JSON.stringify({before,titles:q.tables.MachTitleDat,back:back.tables.MachMainDat,
warnings:p.importWarnings,errors:q.calculation.errors}));"""
result = json.loads(
subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
)
)
assert result["before"][2]["Name"] == "special"
assert result["before"][2]["CalcIndex"] == 1
assert result["titles"][0]["TC"] == 130
assert result["back"][2]["GC"] == 50
assert result["titles"][3]["TC"] == 100
assert "상세 산식 미변환" in result["before"][4]["Name"]
assert "상세 산식 미변환" in result["before"][6]["Name"]
assert any("특수 기계 상세" in warning for warning in result["warnings"])
assert not result["errors"]
def test_jdl_machine_percent_detail_round_trips_fixed_amounts():
project = run("math", "EstNative.empty()")
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
const empty=JSON.parse(fs.readFileSync(0,'utf8')),blank=structuredClone(empty);
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
['junit.txt',table('junit.txt',[{F_CODE:'J1',F_DES:'machine',F_MPRICE:5},{F_CODE:'J2',F_DES:'ambiguous',F_MPRICE:50}])],
['jpart.txt',table('jpart.txt',[{F_CODE:'J1',F_SEQ:'1',F_DES:'percent',F_UNIT:'%',F_QQTY:10,F_MPRICE:50,F_SPROPER:'1'},
{F_CODE:'J2',F_SEQ:'1',F_DES:'uncertain',F_UNIT:'%',F_QQTY:1,F_MPRICE:50}])]]);
const p=j.read(a.pack(files),empty),before=structuredClone(p.tables.MachMainDat);p.options.AutoCalc=true;
p.tables.MachMainDat[1].GC=6;const q=s.calculate(p),out=j.output(q),entries=a.unpack(out.bytes),back=j.read(out.bytes,blank);
process.stdout.write(JSON.stringify({before,parts:j.parse('jpart.txt',entries.get('jpart.txt')),
rows:back.tables.MachMainDat,errors:q.calculation.errors}));"""
result = json.loads(
subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
)
)
assert result["before"][1]["GC"] == 5
assert result["before"][1]["GN"] == 10
assert result["before"][1]["JdlSourceQuantity"] == 10
assert result["parts"][0]["F_UNIT"] == "%"
assert result["parts"][0]["F_QQTY"] == 100
assert result["rows"][1]["GC"] == 6
assert "상세 산식 미변환" in result["before"][-1]["Name"]
assert not result["errors"]
def test_jdl_fixed_unit_percent_exports_a_consistent_quantity():
project = run("math", "EstNative.empty()")
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);p.name='percent';
p.tables.HeadTitleDat=[{Code:'U',Name:'unit',GC:10,TC:10}];
p.tables.HeadMainDat=[{Index:1,Code:'U'},
{Index:0,Name:'fixed percent',Unite:'%',CalcIndex:1,JdlSummary:true,GC:10,TC:10}];
p.tables.EstMainDat=[{Code:'U',Name:'unit',LinkIndex:103,GN:2,GU:10,GC:20,TC:20},{LinkIndex:54}];
const out=j.output(p),files=a.unpack(out.bytes),parts=j.parse('unitpart.txt',files.get('unitpart.txt')),
q=j.read(out.bytes,empty);
process.stdout.write(JSON.stringify({part:parts[0],row:q.tables.HeadMainDat[1],total:q.tables.EstMainDat.at(-1).TC}));"""
result = json.loads(
subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
)
)
assert result["part"]["F_UNIT"] == "%"
assert result["part"]["F_QQTY"] == 100
assert result["part"]["F_MPRICE"] == result["part"]["F_MCOST"] == 10
assert result["row"]["GC"] == 10
assert result["total"] == 20
def test_jdl_estimate_percent_quantity_and_fixed_amount_round_trip():
project = json.loads((ROOT / "tools/est-test/sample.json").read_text(encoding="utf-8"))
for table in project["tables"]:
project["tables"][table] = []
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
const p=JSON.parse(fs.readFileSync(0,'utf8')),blank=structuredClone(p);
p.tables.StuffcDat=[{Code:'M',Name:'material',GC:200,TC:200}];
p.tables.EstMainDat=[{Code:'M',Name:'rate',Unite:'%',LinkIndex:5,GN:10,GU:200,GC:20,TC:20},
{Name:'fixed',Unite:'%',CalcIndex:1,GC:30,TC:30},{LinkIndex:54}];
const out=j.output(p),files=a.unpack(out.bytes),parts=j.parse('mainpart.txt',files.get('mainpart.txt')),
back=j.read(out.bytes,blank);
process.stdout.write(JSON.stringify({parts,rows:back.tables.EstMainDat}));"""
result = json.loads(
subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
)
)
assert [part["F_QQTY"] for part in result["parts"]] == [10, 100]
assert [row["TC"] for row in result["rows"] if not row.get("Level")] == [20, 30, 50]
def test_jdl_estimate_preserves_components_when_total_matches():
project = json.loads((ROOT / "tools/est-test/sample.json").read_text(encoding="utf-8"))
for table in project["tables"]:
project["tables"][table] = []
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs');
const p=JSON.parse(fs.readFileSync(0,'utf8')),blank=structuredClone(p);
p.tables.StuffcDat=[{Code:'M',Name:'material',GC:10,TC:10}];
p.tables.EstMainDat=[{Code:'M',Name:'adjusted',LinkIndex:5,GN:2,GU:10,GC:0,NC:20,TC:20},{LinkIndex:54}];
const q=j.read(j.output(p).bytes,blank);process.stdout.write(JSON.stringify(q.tables.EstMainDat.at(-1)));"""
result = json.loads(
subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
)
)
assert result["GC"] == 0
assert result["NC"] == 20
def test_jdl_rejects_case_insensitive_duplicate_tables():
project = json.loads((ROOT / "tools/est-test/sample.json").read_text(encoding="utf-8"))
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
const p=JSON.parse(fs.readFileSync(0,'utf8'));
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tfirst\\t02\\x05\\r\\n')],
['MAIN.TXT',Buffer.from('0\\tsecond\\t02\\x05\\r\\n')]]);
try { j.read(a.pack(files),p); process.stdout.write('accepted'); }
catch(error) { process.stdout.write(error.message); }"""
result = subprocess.check_output(
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT, text=False
).decode("utf-8")
assert "중복" in result