- 지식DB REA 분석 32 실행계약 · 34 변경차수 잔여계산 작업자료(정적분석 · 원본 실행·수정 없음) - 실행: 중기 실행단가 합계·성분 비례·끝전·차액 성분 · 실행단가·수량 할증 · 계약 변환 옵션·적용제외 · 기성 공사 낙찰율 100% - 변경: 차수 목록·이어 셈 · 보험료 3모드(변경 2차 이후) · 방법 A/B 5항목 · 퇴직공제·건설기계·표준시장단가·노임 환산 갈래·JCD - 시험 693 통과 · 브라우저 3건 화면 전수 오류 0 · 총공사비 그대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
446 lines
19 KiB
JavaScript
446 lines
19 KiB
JavaScript
/* -*- coding: utf-8 -*- */
|
||
/**
|
||
* 계약 변환·계약 원가계산 (contract_calc.js) — 설계 내역 → 계약 내역(단가적용율 · 옵션 · 적용제외 공정)
|
||
* 근거: 지식DB STmate/REA 분석/32_실행계약단계_작업자료_20261114 (STmate.Cpm TwM_Mk_Cont 정적 분석)
|
||
* · 적용율 — 「일위대가/산출근거 생성」 켬 = 노무비 칸 하나를 노·재·경 모두에(0x556b02 · 0x5534f0 재·경 칸 숨김)
|
||
* 끔 = 노·재·경 따로 · 대표율 = MROUNDUD((노+재+경)/3, 3, 1)(0x556b59)
|
||
* · 코드별 갈래(0x554be0) — 대상 머리글자 MLSWBDXQ 만(0x556790) · 밖은 코드·단가 그대로
|
||
* 켬 + 율 0 → 아무 코드도 안 바꿈(조사가적용) · M·L·S·W = 「기초단가 적용」 켬일 때만
|
||
* · 새 머리글자 = 켬이면 제 글자 · 끔이면 모두 W(계약단가) · Q = 제 줄 단가만 고침(코드 그대로)
|
||
* · 새 코드 = 글자 + 다섯 자리(FormatFloat '00000') · 번호 = 설정의 글자별 마지막 번호 다음(0x555d8e)
|
||
* · 동일코드 개별생성 끔 = 같은 원래 코드는 처음 만든 새 코드 재사용(0x55504a)
|
||
* · 단가(0x5554f2~0x5556ed) — 대표율 100 = 그대로 · 대표율 0 = 단가 0(공내역 · 끔일 때만 닿음)
|
||
* · 세 율 같음 = 성분·총액 × 율/100 → OPTIMIZ(우선 플래그: B = CHBNM · D·Q = CHGNM · 그 밖 비목별, 0자리)
|
||
* · 세 율 다름 = 비목별 MROUNDUD(단가 × 율/100, 0, 0) · 계 = 합
|
||
* · 적용제외 = 고른 계표시 머리(T 레벨 ≥ 선택)부터 그 ENDCODE 까지(0x555f68) · 목록 기본 체크 = ITM_TYP 11·21·31·41·51·71·72
|
||
* · 비과세자재대 옵션 = 원가계산 C_NBUGAM·C_BUGA_M × 재료비율/100 1원 절사(0x556274) — 내역 줄과 무관
|
||
* · PNUM = 켬·율 0 → '조사가적용' · 그 밖 FormatFloat('0.00##', 대표율) + '% 적용'(0x55610e)
|
||
* 실물(울진 2025 STC): 계약 단가·금액 1원 · 직접비 · 원가계산 · PNUM '87.745% 적용'
|
||
* 앱 확장(원본 근거 없음 · 울진 관찰): 관급 구역 줄 제외(excludeGov) · 공종 단위 제외(excludeLeaves) · 단가 절사 자리(digits)
|
||
* 정답 대조: resources/tester/test_stmate_stage_contract.py · test_stmate_stage_contract_rea.py
|
||
*/
|
||
const StmateContract = (() => {
|
||
const Calc = (typeof window !== "undefined" && window.STMATE_CALC) || require("./calc.js");
|
||
const KF =
|
||
(typeof window !== "undefined" && window.STMATE_KANJUB_FACTORY) || require("./kanjub_calc.js");
|
||
const Change =
|
||
(typeof window !== "undefined" && window.STMATE_CHANGE) || require("./change_calc.js");
|
||
const { roundDown, roundHalfUp, roundUp } = Calc;
|
||
const applyRoundMode = (v, mode, d = 0) =>
|
||
mode === "up" ? roundUp(v, d) : mode === "half" ? roundHalfUp(v, d) : roundDown(v, d);
|
||
const calcKanjub = KF(Object.assign({}, Calc, { applyRoundMode }));
|
||
const n0 = (v) => Number(v) || 0;
|
||
const COMPS = [
|
||
["m", "M", "rateM"],
|
||
["l", "L", "rateL"],
|
||
["s", "S", "rateS"],
|
||
];
|
||
// 계약 원가계산에서 설계 금액을 그대로 쓰는 항목(D_ 표시) — 울진 2025 계약 SMT 실증 · 35번 §4
|
||
const CONTRACT_CARRY = ["KUNBO", "SLVBO", "YUNBO", "TBUGUM", "ANJUN"];
|
||
|
||
const Ex = () =>
|
||
(typeof window !== "undefined" && window.STMATE_EXEC) || require("./exec_calc.js");
|
||
|
||
const DEFAULT_OPTS = {
|
||
rateL: 100,
|
||
rateM: 100,
|
||
rateS: 100,
|
||
digits: 0,
|
||
excludeHeads: [],
|
||
excludeLeaves: [],
|
||
excludeGov: true,
|
||
sameCodeSeparate: false, // CBoxSetSame2DIF
|
||
baseRate: true, // CBoxSetHalMLSW — □ = M·L·S·W·Q 코드·단가 그대로
|
||
makeIlwi: true, // CBoxCreateILwi — √ 단일 율 · 제 글자 새 코드 / □ 노·재·경 따로 · 모두 W 코드
|
||
taxFree: true, // CBoxSetHal_NBUGAM — 원가계산 비과세자재대·면세품목 × 재료비율
|
||
chbnm: true, // 일위대가표 계산방식(START/CHBNM · 기본 참 = 비목별 먼저)
|
||
chgnm: true, // 단가산출근거 계산방식(START/CHGNM)
|
||
optnm: "SML", // 금액 보정순서(START/OPTNM)
|
||
};
|
||
// 적용제외 목록 기본 체크 ITM_TYP(0x55427b 비트표 0x55448c)
|
||
const DEFAULT_EXCLUDE_ITM = [11, 21, 31, 41, 51, 71, 72];
|
||
const TARGET = "MLSWBDXQ";
|
||
|
||
// 실제 쓰는 율 — 켬 = 노무비 칸 하나 · 끔 = 셋 따로(대표율 = 평균 소수 셋째 사사오입)
|
||
function effRates(o) {
|
||
// 기성 공사(PCLASS 6) — 1칸이 이미 계약 금액 · 낙찰율 다시 먹이지 않음(100%) · 기성 REA 33 작업 쪽지
|
||
if (isProgressProject(o.pclass))
|
||
return { L: 100, M: 100, S: 100, overall: 100, single: o.makeIlwi !== false };
|
||
if (o.makeIlwi !== false) {
|
||
const r = n0(o.rateL);
|
||
return { L: r, M: r, S: r, overall: r, single: true };
|
||
}
|
||
const L = n0(o.rateL),
|
||
M = n0(o.rateM),
|
||
S = n0(o.rateS);
|
||
return { L, M, S, overall: Ex().mroundud((L + M + S) / 3, 3, 1), single: false };
|
||
}
|
||
|
||
const isProgressProject = (pclass) => String(pclass ?? "").trim() === "6";
|
||
|
||
// 프로젝트 이름 칸 PNUM(0x55610e~0x55626f)
|
||
function pnumText(opts) {
|
||
const o = Object.assign({}, DEFAULT_OPTS, opts || {});
|
||
const R = effRates(o);
|
||
if (R.single && R.overall === 0) return "조사가적용";
|
||
const v = R.single ? R.overall : (n0(o.rateL) + n0(o.rateM) + n0(o.rateS)) / 3;
|
||
const t = (Math.round(v * 1e4) / 1e4).toFixed(4).replace(/0{1,2}$/, "");
|
||
return `${t}% 적용`;
|
||
}
|
||
|
||
// 머리글자별 기존 최대 코드 번호 — 새 계약 코드는 그 다음부터
|
||
function codeBaseFromCostn(costnRows) {
|
||
const base = {};
|
||
for (const r of costnRows || []) {
|
||
const m = /^([A-Z])(\d+)$/.exec(String(r.CODE || "").trim());
|
||
if (m) base[m[1]] = Math.max(base[m[1]] || 0, Number(m[2]));
|
||
}
|
||
return base;
|
||
}
|
||
|
||
// 적용제외 목록(cBx_LevValueChange 0x55409c) — 계표시 머리 T 레벨 ≥ 고른 레벨(1 소계 ~ 9 광역대계)
|
||
function levelHeads(tree, level = 1, itmOf = null) {
|
||
return tree.nodes
|
||
.filter((nd) => nd.kind === "head" && !nd.zone && nd.level >= Math.max(1, n0(level)))
|
||
.map((nd) => ({
|
||
recno: nd.recno,
|
||
itnum: nd.itnum,
|
||
name: nd.name,
|
||
level: nd.level,
|
||
defaultExcluded: DEFAULT_EXCLUDE_ITM.includes(n0(itmOf ? itmOf(nd) : nd.itmTyp)),
|
||
}));
|
||
}
|
||
|
||
// 적용제외 판정 — 고른 머리 아래 전부(SORTCODE~ENDCODE) · (앱) 고른 공종 · (앱) 관급 구역
|
||
function excludedSet(tree, o) {
|
||
const ex = new Set();
|
||
const heads = new Set((o.excludeHeads || []).map(String));
|
||
const leaves = new Set((o.excludeLeaves || []).map(String));
|
||
tree.nodes.forEach((nd, i) => {
|
||
if (nd.kind === "head" && heads.has(String(nd.recno))) {
|
||
const end = nd.closeIdx != null ? nd.closeIdx : tree.nodes.length;
|
||
for (let k = i; k <= end && k < tree.nodes.length; k++) ex.add(k);
|
||
}
|
||
if (nd.kind === "leaf") {
|
||
if (leaves.has(String(nd.id)) || leaves.has(String(nd.recno))) ex.add(i);
|
||
if (o.excludeGov && nd.gov) ex.add(i);
|
||
}
|
||
});
|
||
return ex;
|
||
}
|
||
|
||
// 코드 갈래(0x554be0) — keep(그대로) · inplace(Q 제 줄 단가만) · new(새 코드)
|
||
function actionOf(kind, o, R) {
|
||
if (!TARGET.includes(kind || "?")) return "keep";
|
||
if (R.single && R.overall === 0) return "keep";
|
||
if (kind === "Q") return o.baseRate === false ? "keep" : "inplace";
|
||
if ("MLSW".includes(kind)) return o.baseRate === false ? "keep" : "new";
|
||
return "new";
|
||
}
|
||
|
||
// 계약 단가(0x5554f2~0x5556ed · Q 는 0x554de1~0x554f98)
|
||
function contractUnit(d, kind, o, R, action) {
|
||
const E = Ex();
|
||
const u = { m: n0(d.mUnit), l: n0(d.lUnit), s: n0(d.sUnit), t: n0(d.tUnit) };
|
||
if (R.overall === 100) return { m: u.m, l: u.l, s: u.s };
|
||
if (R.overall === 0)
|
||
return action === "inplace" ? { m: u.m, l: u.l, s: u.s } : { m: 0, l: 0, s: 0 };
|
||
const dg = n0(o.digits);
|
||
if (R.L === R.overall && R.M === R.overall && R.S === R.overall) {
|
||
const r = R.overall / 100;
|
||
const pr =
|
||
action === "inplace" || kind === "D"
|
||
? o.chgnm !== false
|
||
: kind === "B"
|
||
? o.chbnm !== false
|
||
: true;
|
||
const g = E.optimiz(
|
||
{ T: u.t * r, L: u.l * r, M: u.m * r, S: u.s * r, A: 0 },
|
||
{ priority: pr, digits: dg, mode: 0, order: o.optnm },
|
||
);
|
||
return { m: g.M, l: g.L, s: g.S };
|
||
}
|
||
return {
|
||
m: E.mroundud((u.m * R.M) / 100, dg, 0),
|
||
l: E.mroundud((u.l * R.L) / 100, dg, 0),
|
||
s: E.mroundud((u.s * R.S) / 100, dg, 0),
|
||
};
|
||
}
|
||
|
||
// 설계 → 계약 변환 — 행별 단가·금액과 머리 합계 · 직접비·관급 합계
|
||
function convert(tree, opts, stage = 1, codeBase = null) {
|
||
const o = Object.assign({}, DEFAULT_OPTS, opts || {});
|
||
const R = effRates(o);
|
||
const ex = excludedSet(tree, o);
|
||
const counter = codeBase ? Object.assign({}, codeBase) : null;
|
||
const codeMap = {};
|
||
const nextCode = (p) => {
|
||
counter[p] = (counter[p] || 0) + 1;
|
||
return p + String(counter[p]).padStart(5, "0");
|
||
};
|
||
const nodes = tree.nodes.map((nd, i) => {
|
||
const out = Object.assign({}, nd, { st: {} });
|
||
if (nd.kind !== "leaf") return out;
|
||
const d = nd.st[stage] || {};
|
||
const excluded = ex.has(i);
|
||
const kind = String(nd.code || "")[0] || "";
|
||
const action = excluded ? "keep" : actionOf(kind, o, R);
|
||
const keep = action === "keep";
|
||
const unit = keep
|
||
? { m: n0(d.mUnit), l: n0(d.lUnit), s: n0(d.sUnit) }
|
||
: contractUnit(d, kind, o, R, action);
|
||
const qty = n0(d.qty);
|
||
const amt = {};
|
||
for (const [c] of COMPS) amt[c] = roundDown(unit[c] * qty, 0);
|
||
const tUnit = unit.m + unit.l + unit.s;
|
||
let newCode = nd.code;
|
||
// 대표율 0 · 끔 → W 코드 단가 0(공내역 · lbMsg 안내문)
|
||
const zeroed = action === "new" && !R.single && R.overall === 0;
|
||
if (action === "new" && nd.code) {
|
||
const letter = R.single ? kind : "W";
|
||
if (counter) {
|
||
if (!o.sameCodeSeparate && codeMap[nd.code]) newCode = codeMap[nd.code];
|
||
else {
|
||
newCode = nextCode(letter);
|
||
codeMap[nd.code] = newCode;
|
||
}
|
||
} else newCode = letter + nd.code.slice(1);
|
||
}
|
||
out.excluded = excluded;
|
||
out.kept = keep;
|
||
out.action = action;
|
||
out.zeroed = zeroed;
|
||
out.newCode = newCode;
|
||
out.design = d;
|
||
out.st[stage] = {
|
||
qty,
|
||
mUnit: unit.m,
|
||
lUnit: unit.l,
|
||
sUnit: unit.s,
|
||
tUnit,
|
||
mAmt: amt.m,
|
||
lAmt: amt.l,
|
||
sAmt: amt.s,
|
||
tAmt: amt.m + amt.l + amt.s,
|
||
};
|
||
return out;
|
||
});
|
||
nodes.forEach((nd) => nd.pct && (nd.pctQty = { [stage]: n0(nd.pctQty && nd.pctQty[stage]) }));
|
||
Change.applyPct(nodes, [stage]);
|
||
Change.sumHeads(nodes, [stage]);
|
||
let directM = 0,
|
||
directL = 0,
|
||
directS = 0,
|
||
govRawTotal = 0,
|
||
govFeeTotal = 0;
|
||
for (const nd of nodes) {
|
||
if (nd.kind !== "leaf" || nd.missing) continue;
|
||
const s = nd.st[stage];
|
||
if (nd.gov) {
|
||
if (nd.rowType === "gov_fee") govFeeTotal += s.tAmt;
|
||
else govRawTotal += s.tAmt;
|
||
} else ((directM += s.mAmt), (directL += s.lAmt), (directS += s.sAmt));
|
||
}
|
||
const directTotal = directM + directL + directS;
|
||
const totals = { directM, directL, directS, directT: directTotal, directTotal };
|
||
Object.assign(totals, { govRawTotal, govFeeTotal, govCombinedRaw: govRawTotal + govFeeTotal });
|
||
return { nodes, rows: rowsOf(nodes, stage), totals, codeMap, opts: o, stage };
|
||
}
|
||
|
||
function rowsOf(nodes, stage) {
|
||
return nodes
|
||
.filter((nd) => nd.kind !== "blank")
|
||
.map((nd) => {
|
||
const s = nd.st[stage] || {};
|
||
const r = { kind: nd.kind, recno: nd.recno, itnum: nd.itnum, name: nd.name };
|
||
Object.assign(r, {
|
||
mAmt: n0(s.mAmt),
|
||
lAmt: n0(s.lAmt),
|
||
sAmt: n0(s.sAmt),
|
||
tAmt: n0(s.tAmt),
|
||
});
|
||
if (nd.kind === "leaf")
|
||
Object.assign(r, {
|
||
id: nd.id,
|
||
code: nd.code,
|
||
newCode: nd.newCode,
|
||
excluded: nd.excluded,
|
||
zeroed: nd.zeroed,
|
||
qty: n0(s.qty),
|
||
unit: nd.unit,
|
||
spec: nd.spec,
|
||
mUnit: n0(s.mUnit),
|
||
lUnit: n0(s.lUnit),
|
||
sUnit: n0(s.sUnit),
|
||
tUnit: n0(s.tUnit),
|
||
dUnit: n0(nd.design && nd.design.tUnit),
|
||
dAmt: n0(nd.design && nd.design.tAmt),
|
||
});
|
||
return r;
|
||
});
|
||
}
|
||
|
||
// 계약 원가설정 기본 — 설계 설정 복사 · 당초액 유지 항목은 설계 금액 고정 · 이윤율만 따로
|
||
function defaultContractCfg(designCfg, designKj, opts = {}) {
|
||
const cfg = JSON.parse(JSON.stringify(designCfg || {}));
|
||
cfg.fixed = {};
|
||
for (const c of opts.carry || CONTRACT_CARRY)
|
||
if (designKj && designKj.itemMap[c]) cfg.fixed[c] = designKj.itemMap[c].amount;
|
||
if (opts.iyunRate != null)
|
||
cfg.rates = Object.assign({}, cfg.rates, { IYUN: n0(opts.iyunRate) });
|
||
cfg.anjunAuto = 1;
|
||
// 비과세자재대 옵션(0x556274~0x55653f) — 재료비율 ≥ 0 이면 [n_KANJUB] C_NBUGAM·C_BUGA_M × 율/100 → 1원 절사
|
||
if (opts.taxFree !== false && opts.rateM != null && n0(opts.rateM) >= 0) {
|
||
const f = (v) => Ex().mroundud((n0(v) * n0(opts.rateM)) / 100, 0, 0);
|
||
cfg.keys = Object.assign({}, cfg.keys);
|
||
for (const k of ["C_NBUGAM", "C_BUGA_M"])
|
||
if (cfg.keys[k] != null && cfg.keys[k] !== "") cfg.keys[k] = f(cfg.keys[k]);
|
||
if (cfg.vatExempt != null) cfg.vatExempt = f(cfg.vatExempt);
|
||
}
|
||
return cfg;
|
||
}
|
||
|
||
// 계약 원가계산서 — 계약 직접비 + 계약 원가설정
|
||
function contractKanjub(conv, cfg) {
|
||
return calcKanjub({ kanjubConfig: cfg }, conv.totals, null, cfg);
|
||
}
|
||
|
||
// 예상적용율 찾기 — 노·재·경 같은 율 · 0.001% 단위 · 목표 이하 최대
|
||
// what = "direct"(직접공사비) · "grand"(총공사비, cfg 필요)
|
||
function findRate(tree, opts, target, stage = 1, what = "direct", cfg = null) {
|
||
const value = (k) => {
|
||
const r = k / 1000;
|
||
const conv = convert(tree, Object.assign({}, opts, { rateL: r, rateM: r, rateS: r }), stage);
|
||
return what === "grand" && cfg
|
||
? contractKanjub(conv, cfg).grandTotal
|
||
: conv.totals.directTotal;
|
||
};
|
||
let lo = 0,
|
||
hi = 100000;
|
||
if (value(hi) <= target) return { rate: 100, value: value(hi) };
|
||
while (hi - lo > 1) {
|
||
const mid = (lo + hi) >> 1;
|
||
if (value(mid) <= n0(target)) lo = mid;
|
||
else hi = mid;
|
||
}
|
||
return { rate: lo / 1000, value: value(lo) };
|
||
}
|
||
|
||
// 적용금액 미리계산 — 비목별 설계·계약 합과 실제 적용율
|
||
function preview(conv) {
|
||
const d = { m: 0, l: 0, s: 0 },
|
||
c = { m: 0, l: 0, s: 0 };
|
||
for (const nd of conv.nodes) {
|
||
if (nd.kind !== "leaf" || nd.gov) continue;
|
||
for (const k of ["m", "l", "s"]) {
|
||
d[k] += n0(nd.design && nd.design[k + "Amt"]);
|
||
c[k] += n0(nd.st[conv.stage][k + "Amt"]);
|
||
}
|
||
}
|
||
const rate = (a, b) => (a ? Math.round((b / a) * 1e7) / 1e5 : 0);
|
||
return {
|
||
design: d,
|
||
contract: c,
|
||
rates: { m: rate(d.m, c.m), l: rate(d.l, c.l), s: rate(d.s, c.s) },
|
||
total: { design: d.m + d.l + d.s, contract: c.m + c.l + c.s },
|
||
};
|
||
}
|
||
|
||
// 낙찰율 열(NAGCD) 저장값 — 새 계약 코드마다 [코드, 설계 코드, 적용율, 단가]
|
||
function nagcdRows(conv) {
|
||
const seen = new Set(),
|
||
out = [];
|
||
for (const nd of conv.nodes) {
|
||
if (nd.kind !== "leaf" || nd.excluded || seen.has(nd.newCode)) continue;
|
||
seen.add(nd.newCode);
|
||
const s = nd.st[conv.stage];
|
||
const R = effRates(conv.opts);
|
||
out.push({
|
||
code: nd.newCode,
|
||
from: nd.code,
|
||
rates: { L: R.L, M: R.M, S: R.S },
|
||
unit: [s.mUnit, s.lUnit, s.sUnit],
|
||
design: [nd.design.mUnit, nd.design.lUnit, nd.design.sUnit],
|
||
});
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* 설계 → 계약자료 변환등록 저장 요청(wM_Conv_Mode · 26 §4 · 울진 2025 실증)
|
||
* 내역: 계약 공종 행 = 새 코드 · 비목 단가·금액(1칸) · 변경 수량 적용이면 QTY1 = 그 차수 수량
|
||
* 새 COSTN 행: 설계 코드의 명칭·규격·단위·분류 칸을 옮김
|
||
* 대가·산근(B·D·X) = 2칸 원단가(설계) · 1칸 계약단가 · NAGCD = 적용율 · UC(결과값에 낙찰율적용)·BCONT(계약단가) = T
|
||
* · 산출근거 글(COSTN.SMT)·구성표(BDQTY) 그대로 옮김
|
||
* 기초단가·계약단가 W = 1칸 = 2칸 = 계약단가(낙찰율 열 없음)
|
||
*/
|
||
const COPY_COLS = ["NAME", "SIZE", "UNIT", "BQCODE", "EQCODE", "JDCODE", "NAME2", "SIZE2"];
|
||
const COPY_MORE = ["SBMOK", "S_ADJ", "ADJ_M", "ADJ_L", "ADJ_S", "EDT_DATE", "WQCODE"];
|
||
function registerRows(conv, costnRows, memos = {}, bdqtyRows = []) {
|
||
const st = conv.stage;
|
||
const by = new Map((costnRows || []).map((r) => [String(r.CODE || "").trim(), r]));
|
||
const o = conv.opts;
|
||
// NAGCD = 대표율(0x555848) — B·D·X 새 코드에만
|
||
const nag = effRates(o).overall;
|
||
const boq = [],
|
||
costn = [],
|
||
memo = {},
|
||
bdqty = [],
|
||
made = new Set();
|
||
for (const nd of conv.nodes) {
|
||
if (nd.kind !== "leaf" || nd.recno == null || nd.kept) continue;
|
||
const s = nd.st[st];
|
||
const u = { _recno: nd.recno, BDCODE1: nd.newCode };
|
||
if (!nd.pct) {
|
||
Object.assign(u, { MAMT1: s.mUnit, LAMT1: s.lUnit, SAMT1: s.sUnit, TAMT1: s.tUnit });
|
||
Object.assign(u, { M_AMT1: s.mAmt, L_AMT1: s.lAmt, S_AMT1: s.sAmt, T_AMT1: s.tAmt });
|
||
if (st !== 1) u.QTY1 = s.qty;
|
||
}
|
||
boq.push(u);
|
||
if (made.has(nd.newCode) || by.has(nd.newCode) || nd.newCode === nd.code) continue;
|
||
made.add(nd.newCode);
|
||
const src = by.get(nd.code) || {};
|
||
const row = { CODE: nd.newCode };
|
||
COPY_COLS.forEach((k) => src[k] != null && (row[k] = src[k]));
|
||
const comp = /^[BDX]/.test(nd.newCode);
|
||
const d = nd.design || {};
|
||
const one = { M: s.mUnit, L: s.lUnit, S: s.sUnit, T: s.tUnit };
|
||
const two = comp ? { M: n0(d.mUnit), L: n0(d.lUnit), S: n0(d.sUnit), T: n0(d.tUnit) } : one;
|
||
for (const c of ["T", "M", "L", "S"])
|
||
Object.assign(row, { [`${c}AMT1`]: one[c], [`${c}AMT2`]: two[c] });
|
||
if (comp) {
|
||
COPY_MORE.forEach((k) => src[k] != null && (row[k] = src[k]));
|
||
Object.assign(row, { NAGCD: nag, UC: "T", BCONT: "T" });
|
||
const m = memos[nd.code];
|
||
if (m && String(m.text || "").trim()) memo[nd.newCode] = m.text;
|
||
(bdqtyRows || [])
|
||
.filter((q) => String(q.BDCODE || "").trim() === nd.code)
|
||
.forEach((q) => bdqty.push(Object.assign({}, q, { BDCODE: nd.newCode })));
|
||
}
|
||
costn.push(row);
|
||
}
|
||
return { boq, costn, memos: memo, bdqty };
|
||
}
|
||
|
||
return {
|
||
CONTRACT_CARRY,
|
||
DEFAULT_EXCLUDE_ITM,
|
||
effRates,
|
||
isProgressProject,
|
||
pnumText,
|
||
levelHeads,
|
||
registerRows,
|
||
DEFAULT_OPTS,
|
||
codeBaseFromCostn,
|
||
convert,
|
||
defaultContractCfg,
|
||
contractKanjub,
|
||
findRate,
|
||
preview,
|
||
nagcdRows,
|
||
};
|
||
})();
|
||
|
||
if (typeof window !== "undefined") window.STMATE_CONTRACT = StmateContract;
|
||
if (typeof module !== "undefined" && module.exports) module.exports = StmateContract;
|