Merge remote-tracking branches 'origin/CODEX' and 'origin/main_laptop_1' into sub_laptop_1
This commit is contained in:
@@ -108,6 +108,35 @@ def test_jdl_basic_tables_use_native_separators_encoding_and_preserve_direct_mon
|
||||
assert result["warnings"]
|
||||
|
||||
|
||||
def test_large_jdl_import_keeps_group_order_and_rejects_orphans_and_duplicates():
|
||||
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 n=3000,groups=Array.from({length:n},(_,i)=>({F_CODE:String(i),F_DES:'group '+i}));
|
||||
const parts=groups.slice().reverse().flatMap(g=>[{F_CODE:g.F_CODE,F_SEQ:'1',F_CODE1:'M',F_DES:'first',F_QQTY:1,F_MPRICE:3},
|
||||
{F_CODE:g.F_CODE,F_SEQ:'2',F_CODE1:'M',F_DES:'second',F_QQTY:2,F_MPRICE:3}]);
|
||||
function archive(gs,ps){return a.pack(new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
||||
['m.txt',table('m.txt',[{F_CODE:'M',F_DES:'material',F_MPRICE:3}])],['gongjong.txt',table('gongjong.txt',gs)],['mainpart.txt',table('mainpart.txt',ps)]]))}
|
||||
const result=j.read(archive(groups,parts),structuredClone(empty));let orphan=false,duplicate=false;
|
||||
try{j.read(archive(groups.slice(0,1),parts.slice(0,1)),structuredClone(empty))}catch{orphan=true}
|
||||
try{j.read(archive([groups[0],groups[0]],[]),structuredClone(empty))}catch{duplicate=true}
|
||||
const rows=result.tables.EstMainDat;
|
||||
process.stdout.write(JSON.stringify({count:rows.length,first:rows.slice(0,3).map(r=>r.Name),total:rows.at(-1).TC,orphan,duplicate}));"""
|
||||
result = json.loads(
|
||||
subprocess.check_output(
|
||||
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT, timeout=30
|
||||
)
|
||||
)
|
||||
assert result == {
|
||||
"count": 9001,
|
||||
"first": ["group 0", "first", "second"],
|
||||
"total": 27000,
|
||||
"orphan": True,
|
||||
"duplicate": True,
|
||||
}
|
||||
|
||||
|
||||
def test_unchanged_import_preserves_original_jdl_and_edited_work_is_not_silently_discarded():
|
||||
project = run("math", "EstNative.empty()")
|
||||
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),s=require('./tools/est-test/server.cjs');
|
||||
@@ -228,7 +257,51 @@ def test_jdl_unit_price_preserves_other_source_costs_as_fixed_resources():
|
||||
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
||||
)
|
||||
)
|
||||
assert result == {"parts": 4, "links": [101, 101, 101, 101], "total": 40}
|
||||
assert result == {"parts": 4, "links": [101, 101, 104, 101], "total": 40}
|
||||
|
||||
|
||||
def test_jdl_cost_title_survives_direct_and_unit_price_links():
|
||||
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='cost';
|
||||
p.tables.CostTitleDat=[{Code:'D',Name:'cost',GC:100,NC:20,JC:5,TC:125}];
|
||||
p.tables.HeadTitleDat=[{Code:'U',Name:'unit',GC:100,NC:20,JC:5,TC:125}];
|
||||
p.tables.HeadMainDat=[{Index:1,Code:'U'},
|
||||
{Index:0,Code:'D',Name:'cost',LinkIndex:104,GN:1,GU:100,NU:20,JU:5,GC:100,NC:20,JC:5,TC:125}];
|
||||
p.tables.EstMainDat=[{Code:'D',Name:'direct',LinkIndex:104,GN:2,GU:100,NU:20,JU:5,GC:200,NC:40,JC:10,TC:250},
|
||||
{Code:'U',Name:'unit',LinkIndex:103,GN:3,GU:100,NU:20,JU:5,GC:300,NC:60,JC:15,TC:375},{LinkIndex:54}];
|
||||
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,empty);
|
||||
process.stdout.write(JSON.stringify({danga:j.parse('danga.txt',files.get('danga.txt')).length,
|
||||
costs:q.tables.CostMainDat,unit:q.tables.HeadMainDat[1].LinkIndex,
|
||||
direct:q.tables.EstMainDat[1].LinkIndex,total:q.tables.EstMainDat.at(-1).TC,warnings:out.warnings}));"""
|
||||
result = json.loads(
|
||||
subprocess.check_output(
|
||||
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
||||
)
|
||||
)
|
||||
assert result["danga"] == 1
|
||||
assert [row["TC"] for row in result["costs"][1:]] == [100, 20, 5]
|
||||
assert result["unit"] == result["direct"] == 104
|
||||
assert result["total"] == 625
|
||||
assert result["warnings"]
|
||||
|
||||
|
||||
def test_jdl_duplicate_cost_titles_fall_back_without_wrong_link():
|
||||
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='duplicates';
|
||||
p.tables.CostTitleDat=[{Code:'D',Name:'one',GC:10},{Code:'D',Name:'two',GC:20}];
|
||||
p.tables.EstMainDat=[{Code:'D',Name:'item',LinkIndex:104,GN:2,GU:20,GC:40,TC:40},{LinkIndex:54}];
|
||||
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,empty);
|
||||
process.stdout.write(JSON.stringify({danga:j.parse('danga.txt',files.get('danga.txt')).length,
|
||||
kind:q.tables.EstMainDat[1].LinkIndex,total:q.tables.EstMainDat.at(-1).TC,
|
||||
warning:out.warnings.some(x=>x.includes('중복'))}));"""
|
||||
result = json.loads(
|
||||
subprocess.check_output(
|
||||
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
||||
)
|
||||
)
|
||||
assert result == {"danga": 0, "kind": 101, "total": 40, "warning": True}
|
||||
|
||||
|
||||
def test_jdl_nested_units_preserve_detail_or_warn_on_forward_reference():
|
||||
@@ -437,6 +510,32 @@ def test_jdl_complete_unit_preserves_difference_from_saved_title():
|
||||
assert [row["TC"] for row in result["titles"]] == [205.25, 30.12]
|
||||
|
||||
|
||||
def test_jdl_fixed_unit_detail_and_saved_difference_survive_edit_and_export():
|
||||
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_MPRICE:100}])],
|
||||
['unit.txt',table('unit.txt',[{F_CODE:'U1',F_DES:'unit',F_MPRICE:205.25}])],
|
||||
['unitpart.txt',table('unitpart.txt',[{F_CODE:'U1',F_SEQ:'1',F_CODE1:'M1',F_DES:'material',F_QQTY:2,F_MPRICE:100,F_MCOST:200}])],
|
||||
['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:205.25}])]]);
|
||||
const p=j.read(a.pack(files),empty);p.options.AutoCalc=true;p.options.SourceRefresh=true;
|
||||
p.tables.HeadMainDat[2].GC=7;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({parts:j.parse('unitpart.txt',entries.get('unitpart.txt')),
|
||||
rows:back.tables.HeadMainDat,total:back.tables.EstMainDat.at(-1).TC,errors:q.calculation.errors}));"""
|
||||
result = json.loads(
|
||||
subprocess.check_output(
|
||||
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
||||
)
|
||||
)
|
||||
assert len(result["parts"]) == 2
|
||||
assert [row["GC"] for row in result["rows"][1:3]] == [200, 7]
|
||||
assert result["total"] == 207
|
||||
assert not result["errors"]
|
||||
|
||||
|
||||
def test_jdl_unit_inconsistent_detail_keeps_recorded_amount_without_double_counting():
|
||||
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');
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
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"]
|
||||
@@ -18,6 +18,37 @@ def test_mean_sea_level_labels_do_not_break_variable_declarations():
|
||||
assert result["checks"][0]["calculated"] == 7.27
|
||||
|
||||
|
||||
def test_redundant_price_caption_keeps_quantity_and_source_updates():
|
||||
result = run(
|
||||
"math",
|
||||
"""(() => {
|
||||
const rows = [
|
||||
{Index:1, Code:'D1'},
|
||||
{Index:0, LineData:'Q = 7.35'},
|
||||
{Index:0, SCheck:-2, LinkIndex:5,
|
||||
LineData:'\\x0e황동주물\\x0e \\x0324,200\\x03C × Q = 24,200 × 7.35 = \\x12177,870\\x12'}
|
||||
];
|
||||
const initial=EstText.calculate(rows);
|
||||
rows[1].LineData='Q = 4';
|
||||
const changed=EstText.calculate(rows,{},()=>({GC:30000,TC:30000,Code:'M1',SourceKind:5}));
|
||||
const repeat=EstText.calculate(changed.rows,{},()=>({GC:30000,TC:30000,Code:'M1',SourceKind:5}));
|
||||
const conflict=structuredClone(rows);conflict.splice(1,0,{Index:0,LineData:'C = 2'});
|
||||
const declared=EstText.calculate(conflict);
|
||||
const mismatch=structuredClone(rows);mismatch[2].LineData=mismatch[2].LineData.replace('= 24,200','= 24,201');
|
||||
return {initial,changed,repeat,declared,mismatch:EstText.calculate(mismatch)};
|
||||
})()""",
|
||||
)
|
||||
assert result["initial"]["errors"] == []
|
||||
assert result["initial"]["rows"][2]["TC"] == 177870
|
||||
assert result["changed"]["errors"] == []
|
||||
assert result["changed"]["rows"][2]["TC"] == 120000
|
||||
assert result["changed"]["rows"][2]["ResourceFactor"] == 4
|
||||
assert result["repeat"]["errors"] == []
|
||||
assert result["repeat"]["rows"][2]["TC"] == 120000
|
||||
assert result["declared"]["errors"]
|
||||
assert result["mismatch"]["errors"]
|
||||
|
||||
|
||||
def test_equals_in_description_is_not_a_formula_separator():
|
||||
result = run(
|
||||
"math",
|
||||
|
||||
Reference in New Issue
Block a user