667 lines
20 KiB
JavaScript
667 lines
20 KiB
JavaScript
const EstModel = (() => {
|
|
const labels = {
|
|
Index: "구분",
|
|
Code: "코드",
|
|
Name: "품명",
|
|
Size: "규격",
|
|
Unite: "단위",
|
|
GN: "수량",
|
|
TU: "단가",
|
|
TC: "금액",
|
|
NU: "노무단가",
|
|
GU: "재료단가",
|
|
JU: "경비단가",
|
|
SU: "실적단가",
|
|
NC: "노무비",
|
|
GC: "재료비",
|
|
JC: "경비",
|
|
SC: "실적",
|
|
Level: "레벨",
|
|
LinkIndex: "연결종류",
|
|
CalcIndex: "계산구분",
|
|
SCheck: "결과비목",
|
|
OCheck: "기준비목",
|
|
SKind: "구간",
|
|
Diget: "자릿수코드",
|
|
DigetIndex: "처리방식",
|
|
Gangub: "맞춤자리",
|
|
Ratio: "조정률",
|
|
NachalRatio: "낙찰률",
|
|
ValueIndex: "단가선택",
|
|
SelectField: "선택출처",
|
|
NCGasu: "운전사계수",
|
|
LineData: "산출근거",
|
|
Gubun: "기호",
|
|
Cost: "원가금액",
|
|
Youl: "요율",
|
|
VSusic: "수식",
|
|
BaseField: "기초연결",
|
|
InputTC: "입력금액",
|
|
Precision: "줄 소수자리",
|
|
RoundMode: "줄 처리방식",
|
|
};
|
|
for (let i = 1; i <= 6; i++) {
|
|
labels[`Cost${i}`] = `출처${i}`;
|
|
labels[`Page${i}`] = `쪽수${i}`;
|
|
}
|
|
const numeric = new Set([
|
|
"Index",
|
|
"PkID",
|
|
"GN",
|
|
"TU",
|
|
"TC",
|
|
"NU",
|
|
"GU",
|
|
"JU",
|
|
"SU",
|
|
"NC",
|
|
"GC",
|
|
"JC",
|
|
"SC",
|
|
"Level",
|
|
"LinkIndex",
|
|
"CalcIndex",
|
|
"SCheck",
|
|
"OCheck",
|
|
"SKind",
|
|
"Diget",
|
|
"DigetIndex",
|
|
"Gangub",
|
|
"Ratio",
|
|
"NachalRatio",
|
|
"ValueIndex",
|
|
"SelectField",
|
|
"NCGasu",
|
|
"InputTC",
|
|
"Precision",
|
|
"RoundMode",
|
|
"Cost",
|
|
...Array.from({ length: 6 }, (_, i) => `Cost${i + 1}`),
|
|
]);
|
|
for (const key of [
|
|
"ANU",
|
|
"AGU",
|
|
"AJU",
|
|
"ASU",
|
|
"EscPlanned",
|
|
"EscActual",
|
|
"EscProgress",
|
|
"EscManualQuantity",
|
|
"EscConstruction",
|
|
"EscPS",
|
|
"EscBefore",
|
|
])
|
|
numeric.add(key);
|
|
for (const prefix of ["Jun", "Gum", "Sa", "Total"])
|
|
for (const field of ["GN", "TC", "Ratio", "NC", "GC", "JC", "SC"]) numeric.add(prefix + field);
|
|
for (const field of ["ContractCost", "JunCost", "GumCost", "SaCost", "TotalCost"])
|
|
numeric.add(field);
|
|
const definitions = {
|
|
EstMainDat: [
|
|
"Level",
|
|
"Code",
|
|
"Name",
|
|
"Size",
|
|
"Unite",
|
|
"GN",
|
|
"TU",
|
|
"NU",
|
|
"GU",
|
|
"JU",
|
|
"SU",
|
|
"TC",
|
|
"NC",
|
|
"GC",
|
|
"JC",
|
|
"SC",
|
|
"LinkIndex",
|
|
"CalcIndex",
|
|
"SCheck",
|
|
"OCheck",
|
|
"SKind",
|
|
"Gangub",
|
|
"Index",
|
|
"Diget",
|
|
"DigetIndex",
|
|
],
|
|
HeadMainDat: [
|
|
"Index",
|
|
"Code",
|
|
"Name",
|
|
"Size",
|
|
"Unite",
|
|
"GN",
|
|
"NU",
|
|
"GU",
|
|
"JU",
|
|
"SU",
|
|
"TC",
|
|
"NC",
|
|
"GC",
|
|
"JC",
|
|
"SC",
|
|
"LinkIndex",
|
|
"CalcIndex",
|
|
"SCheck",
|
|
"OCheck",
|
|
"SKind",
|
|
"Diget",
|
|
"DigetIndex",
|
|
],
|
|
CostMainDat: [
|
|
"Index",
|
|
"Code",
|
|
"LineData",
|
|
"Level",
|
|
"LinkIndex",
|
|
"SCheck",
|
|
"Precision",
|
|
"RoundMode",
|
|
"TC",
|
|
"NC",
|
|
"GC",
|
|
"JC",
|
|
"SC",
|
|
],
|
|
MachMainDat: [
|
|
"Index",
|
|
"Code",
|
|
"Name",
|
|
"Size",
|
|
"Unite",
|
|
"GN",
|
|
"TU",
|
|
"Ratio",
|
|
"TC",
|
|
"NC",
|
|
"GC",
|
|
"JC",
|
|
"NCGasu",
|
|
"SCheck",
|
|
"LinkIndex",
|
|
"CalcIndex",
|
|
],
|
|
MachGijunDat: ["Index", "Name", "TC", "Bigo"],
|
|
Wonga: [
|
|
"Gubun",
|
|
"Name",
|
|
"Youl",
|
|
"VSusic",
|
|
"Cost",
|
|
"BaseField",
|
|
"LinkIndex",
|
|
"CalcIndex",
|
|
"Diget",
|
|
"DigetIndex",
|
|
],
|
|
StuffcDat: [
|
|
"Code",
|
|
"Name",
|
|
"Size",
|
|
"Unite",
|
|
"ValueIndex",
|
|
"SelectField",
|
|
"Cost1",
|
|
"Cost2",
|
|
"Cost3",
|
|
"Cost4",
|
|
"Cost5",
|
|
"Cost6",
|
|
"TC",
|
|
"Ratio",
|
|
"NachalRatio",
|
|
"Diget",
|
|
"DigetIndex",
|
|
],
|
|
LaborcDat: [
|
|
"Code",
|
|
"Name",
|
|
"Unite",
|
|
"ValueIndex",
|
|
"SelectField",
|
|
"Cost1",
|
|
"Cost2",
|
|
"Cost3",
|
|
"Cost4",
|
|
"Cost5",
|
|
"Cost6",
|
|
"TC",
|
|
"Ratio",
|
|
"NachalRatio",
|
|
],
|
|
EstiMateDat: [
|
|
"Code",
|
|
"Name",
|
|
"Size",
|
|
"Unite",
|
|
"InputTC",
|
|
"TC",
|
|
"NC",
|
|
"GC",
|
|
"JC",
|
|
"SC",
|
|
"Ratio",
|
|
"NachalRatio",
|
|
],
|
|
SilJukDat: [
|
|
"Code",
|
|
"Name",
|
|
"Size",
|
|
"Unite",
|
|
"Cost1",
|
|
"TC",
|
|
"NC",
|
|
"GC",
|
|
"JC",
|
|
"SC",
|
|
"Ratio",
|
|
"NachalRatio",
|
|
],
|
|
};
|
|
function columns(table) {
|
|
return (
|
|
definitions[table] || ["Index", "Code", "Name", "Size", "Unite", "TC", "NC", "GC", "JC", "SC"]
|
|
);
|
|
}
|
|
function validate(project) {
|
|
if (
|
|
!project ||
|
|
project.schema !== "est-web-2" ||
|
|
typeof project.name !== "string" ||
|
|
!project.tables ||
|
|
typeof project.tables !== "object" ||
|
|
Array.isArray(project.tables)
|
|
)
|
|
throw Error("EST 웹앱 프로젝트 형식이 아닙니다.");
|
|
let records = 0;
|
|
for (const [table, rows] of Object.entries(project.tables)) {
|
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(table) || !Array.isArray(rows))
|
|
throw Error("테이블 형식 오류");
|
|
records += rows.length;
|
|
if (records > 500000) throw Error("레코드 수 한도 초과");
|
|
for (const row of rows) {
|
|
if (!row || typeof row !== "object" || Array.isArray(row)) throw Error("행 형식 오류");
|
|
for (const [key, value] of Object.entries(row)) {
|
|
if (["__proto__", "constructor", "prototype"].includes(key))
|
|
throw Error("허용하지 않는 필드 이름");
|
|
if (
|
|
(numeric.has(key) || (key.endsWith("_") && numeric.has(key.slice(0, -1)))) &&
|
|
value != null &&
|
|
(typeof value !== "number" ||
|
|
!Number.isFinite(value) ||
|
|
Math.abs(value) > Number.MAX_SAFE_INTEGER)
|
|
)
|
|
throw Error(`수치 필드 오류: ${table}.${key}`);
|
|
if (typeof value === "string" && value.length > 200000)
|
|
throw Error("문자열 길이 한도 초과");
|
|
}
|
|
}
|
|
}
|
|
project.options ||= {};
|
|
EstSchedule.validate(project);
|
|
EstReview.validate(project);
|
|
if (typeof project.options !== "object" || Array.isArray(project.options))
|
|
throw Error("계산 옵션 형식 오류");
|
|
const validNumber = (value, label) => {
|
|
if (
|
|
typeof value !== "number" ||
|
|
!Number.isFinite(value) ||
|
|
Math.abs(value) > Number.MAX_SAFE_INTEGER
|
|
)
|
|
throw Error(`수치 필드 오류: ${label}`);
|
|
};
|
|
if (project.options.EscDesignTotal != null) {
|
|
validNumber(project.options.EscDesignTotal, "조사 도급액");
|
|
if (project.options.EscDesignTotal < 0) throw Error("조사 도급액은 0 이상입니다.");
|
|
}
|
|
if (project.printSettings != null) {
|
|
const s = project.printSettings;
|
|
if (typeof s !== "object" || Array.isArray(s)) throw Error("인쇄 설정 형식 오류");
|
|
for (const [key, min, max] of [
|
|
["margin", 0, 50],
|
|
["marginTop", 0, 50],
|
|
["marginBottom", 0, 50],
|
|
["marginLeft", 0, 50],
|
|
["marginRight", 0, 50],
|
|
["font", 6, 20],
|
|
["rowsPerPage", 1, 100],
|
|
["startPage", 1, 99999],
|
|
])
|
|
if (s[key] != null) {
|
|
validNumber(s[key], key);
|
|
if (
|
|
s[key] < min ||
|
|
s[key] > max ||
|
|
(["rowsPerPage", "startPage"].includes(key) && !Number.isInteger(s[key]))
|
|
)
|
|
throw Error("인쇄 설정 범위 오류");
|
|
}
|
|
if (
|
|
(s.paper != null && !["A3", "A4"].includes(s.paper)) ||
|
|
(s.orientation != null && !["landscape", "portrait"].includes(s.orientation))
|
|
)
|
|
throw Error("인쇄 용지/방향 오류");
|
|
for (const key of ["form", "company", "notes"])
|
|
if (s[key] != null && (typeof s[key] !== "string" || s[key].length > 100000))
|
|
throw Error("인쇄 문구 형식 오류");
|
|
}
|
|
if (project.excelQuantityLinks != null) {
|
|
if (!Array.isArray(project.excelQuantityLinks)) throw Error("Excel 수량 연결 형식 오류");
|
|
const targets = new Set();
|
|
for (const link of project.excelQuantityLinks) {
|
|
if (
|
|
!link ||
|
|
!Number.isSafeInteger(link.target) ||
|
|
targets.has(link.target) ||
|
|
typeof link.file !== "string" ||
|
|
!link.file ||
|
|
link.file.length > 255 ||
|
|
typeof link.sheet !== "string" ||
|
|
!link.sheet ||
|
|
link.sheet.length > 255 ||
|
|
!EstQuantity.validExcelAddress(link.cell)
|
|
)
|
|
throw Error("Excel 수량 연결 오류");
|
|
targets.add(link.target);
|
|
}
|
|
}
|
|
if (project.documents != null) {
|
|
if (typeof project.documents !== "object" || Array.isArray(project.documents))
|
|
throw Error("문서 본문 형식 오류");
|
|
const extraForms = new Set([
|
|
"progress-summary",
|
|
"progress-costs",
|
|
"escalation-summary",
|
|
"escalation-costs",
|
|
"annual-summary",
|
|
"annual-items",
|
|
"change-costs",
|
|
"standard-usage",
|
|
"standard-change",
|
|
"estimate-usage",
|
|
"estimate-change",
|
|
"material-usage",
|
|
"material-change",
|
|
"labor-usage",
|
|
"labor-change",
|
|
"machine-usage",
|
|
"machine-change",
|
|
]);
|
|
for (const [key, value] of Object.entries(project.documents))
|
|
if (
|
|
(!/^(current|\d+)$/.test(key) && !extraForms.has(key)) ||
|
|
typeof value !== "string" ||
|
|
value.length > 100000
|
|
)
|
|
throw Error("문서 본문 형식 오류");
|
|
}
|
|
for (const key of ["quantities", "progress", "annual", "drawings", "attachments"]) {
|
|
if (project[key] === undefined) project[key] = [];
|
|
if (!Array.isArray(project[key])) throw Error(`${key} 형식 오류`);
|
|
}
|
|
const attachmentIds = new Set();
|
|
for (const item of project.attachments) {
|
|
if (
|
|
!item ||
|
|
typeof item.id !== "string" ||
|
|
!item.id ||
|
|
attachmentIds.has(item.id) ||
|
|
typeof item.name !== "string" ||
|
|
!item.name ||
|
|
item.name.length > 1000 ||
|
|
typeof item.type !== "string" ||
|
|
item.type.length > 1000 ||
|
|
!Number.isSafeInteger(item.size) ||
|
|
item.size < 0 ||
|
|
typeof item.data !== "string" ||
|
|
item.data.length % 4 ||
|
|
/[^A-Za-z0-9+/=]/.test(item.data)
|
|
)
|
|
throw Error("첨부파일 형식 오류");
|
|
const body = item.data.replace(/={1,2}$/, ""),
|
|
padding = item.data.length - body.length;
|
|
if (
|
|
body.includes("=") ||
|
|
body.length % 4 === 1 ||
|
|
(item.data.length / 4) * 3 - padding !== item.size
|
|
)
|
|
throw Error("첨부파일 데이터 오류");
|
|
attachmentIds.add(item.id);
|
|
}
|
|
const ids = new Set();
|
|
for (const round of project.progress) {
|
|
if (
|
|
!round ||
|
|
typeof round !== "object" ||
|
|
typeof round.id !== "string" ||
|
|
!round.id ||
|
|
ids.has(round.id)
|
|
)
|
|
throw Error("기성 회차 ID가 없거나 중복됩니다.");
|
|
ids.add(round.id);
|
|
for (const field of ["locked", "approved", "separateComponents"])
|
|
if (round[field] != null && typeof round[field] !== "boolean")
|
|
throw Error("기성 옵션 형식 오류");
|
|
if (round.name != null && typeof round.name !== "string")
|
|
throw Error("기성 회차 이름 형식 오류");
|
|
round.inputs ??= {};
|
|
if (typeof round.inputs !== "object" || Array.isArray(round.inputs))
|
|
throw Error("기성 입력 형식 오류");
|
|
round.inputs = Object.assign(Object.create(null), round.inputs);
|
|
for (const input of Object.values(round.inputs)) {
|
|
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
throw Error("기성 입력 행 형식 오류");
|
|
for (const prefix of ["Gum", "Sa"]) {
|
|
if (
|
|
input[prefix + "Mode"] != null &&
|
|
!["quantity", "amount", "ratio", "components"].includes(input[prefix + "Mode"])
|
|
)
|
|
throw Error("기성 입력방법 오류");
|
|
for (const field of ["", "NC", "GC", "JC", "SC"])
|
|
if (input[prefix + field] != null)
|
|
validNumber(input[prefix + field], "기성 " + prefix + field);
|
|
}
|
|
for (const field of ["QuantityDigits", "AmountDigits"])
|
|
if (input[field] != null) {
|
|
validNumber(input[field], field);
|
|
if (!Number.isInteger(input[field]) || input[field] < 0 || input[field] > 12)
|
|
throw Error("기성 자릿수 형식 오류");
|
|
}
|
|
}
|
|
if (
|
|
round.amountDigits != null &&
|
|
(!Number.isInteger(round.amountDigits) || round.amountDigits < 0 || round.amountDigits > 4)
|
|
)
|
|
throw Error("기성 금액 자릿수는 0~4입니다.");
|
|
if (round.contract) {
|
|
validate({
|
|
schema: "est-web-2",
|
|
name: "기성 계약",
|
|
tables: { EstMainDat: round.contract.rows, Wonga: round.contract.wonga },
|
|
options: round.contract.options,
|
|
});
|
|
}
|
|
if (round.result?.rows)
|
|
validate({
|
|
schema: "est-web-2",
|
|
name: "기성 결과",
|
|
tables: { EstMainDat: round.result.rows },
|
|
});
|
|
if (round.result)
|
|
for (const field of ["total", "ratio"])
|
|
if (round.result[field] != null) validNumber(round.result[field], "확정 기성 " + field);
|
|
}
|
|
for (const year of project.annual)
|
|
if (!year || typeof year.name !== "string" || typeof year.formula !== "string")
|
|
throw Error("연차 이름·수식 형식 오류");
|
|
if (project.baseline) {
|
|
if (!project.baseline.tables || !Array.isArray(project.baseline.tables.EstMainDat))
|
|
throw Error("당초 내역 형식 오류");
|
|
validate({ schema: "est-web-2", name: "당초", tables: project.baseline.tables });
|
|
}
|
|
return project;
|
|
}
|
|
function legacy(data) {
|
|
if (data.schema === "est-web-2") return validate(data);
|
|
if (!["sources", "units", "estimate", "costs"].every((key) => Array.isArray(data[key])))
|
|
throw Error("지원하지 않는 프로젝트 파일");
|
|
const project = EstNative.empty(),
|
|
mapping = {
|
|
재료비: ["StuffcDat", 5, "GC"],
|
|
노무비: ["LaborcDat", 6, "NC"],
|
|
경비: ["EstiMateDat", 101, "JC"],
|
|
실적: ["SilJukDat", 21, "SC"],
|
|
};
|
|
project.name = data.projectName || "이전 작업";
|
|
project.legacyOriginal = structuredClone(data);
|
|
for (const source of data.sources) {
|
|
const [table, kind, component] = mapping[source.kind] || mapping.재료비;
|
|
project.tables[table].push({
|
|
Code: source.id,
|
|
Name: source.name,
|
|
Size: source.spec,
|
|
Unite: source.unit,
|
|
TC: source.price,
|
|
InputTC: source.price,
|
|
[component]: source.price,
|
|
ValueIndex: 4,
|
|
});
|
|
source._nativeKind = kind;
|
|
}
|
|
data.units.forEach((unit, index) => {
|
|
project.tables.HeadMainDat.push({
|
|
Index: index + 1,
|
|
Code: unit.id,
|
|
Name: unit.name,
|
|
Unite: unit.unit,
|
|
});
|
|
unit.lines.forEach((line) => {
|
|
const source = data.sources.find((s) => s.id === line.sourceId);
|
|
project.tables.HeadMainDat.push({
|
|
Index: 0,
|
|
Code: line.type === "일위대가" ? line.unitId : line.sourceId,
|
|
Name: source?.name,
|
|
Size: line.base === "합계" ? "합계" : line.base,
|
|
Unite: line.type === "비율" ? "%" : source?.unit,
|
|
GN: line.quantity,
|
|
LinkIndex:
|
|
line.type === "일위대가" ? 103 : line.type === "비율" ? 58 : source?._nativeKind || 0,
|
|
SCheck: { 재료비: 1, 노무비: 2, 경비: 3 }[line.kind] || 0,
|
|
Diget: line.digits ? line.digits + 1 : 0,
|
|
DigetIndex: line.digits ? 2 : 0,
|
|
});
|
|
});
|
|
project.tables.HeadTitleDat.push({ Code: unit.id, Name: unit.name, Unite: unit.unit });
|
|
});
|
|
project.tables.EstMainDat = data.estimate.map((row, index) => ({
|
|
PkID: index + 1,
|
|
Index: 0,
|
|
Code: row.unitId,
|
|
Name: row.name,
|
|
GN: row.quantity,
|
|
LinkIndex: 103,
|
|
Level: 0,
|
|
NewItem: true,
|
|
}));
|
|
project.tables.EstMainDat.push({ Name: "순공사비", LinkIndex: 54 });
|
|
const bases = { 재료비: "GC", 노무비: "NC", 경비: "JC", 실적: "SC", 직접공사비: "TC" };
|
|
project.tables.Wonga = [{ Gubun: "기초", Name: "직접공사비", BaseField: "TC", Cost: 0 }];
|
|
const running = ["기초"];
|
|
for (const [index, row] of data.costs.entries()) {
|
|
const id = row.id || `추가${index + 1}`;
|
|
project.tables.Wonga.push({
|
|
Gubun: id,
|
|
Name: row.name,
|
|
Youl: `${row.rate}%`,
|
|
VSusic:
|
|
row.formula ||
|
|
`(${row.base === "누계" ? running.map((key) => `[${key}]`).join("+") : `[${bases[row.base] || "TC"}]`})*${row.rate}/100`,
|
|
});
|
|
running.push(id);
|
|
}
|
|
project.tables.Wonga.push({
|
|
Gubun: "도급계",
|
|
Name: "도급액",
|
|
LinkIndex: 55,
|
|
VSusic: running.map((key) => `[${key}]`).join("+"),
|
|
});
|
|
return validate(project);
|
|
}
|
|
function editCode(project, table, index, value) {
|
|
const rows = project.tables[table],
|
|
row = rows[index];
|
|
const grouped = ["HeadMainDat", "CostMainDat", "MachMainDat"].includes(table);
|
|
const kind = Number(
|
|
Object.entries(EstNative.sourceTables).find(([, name]) => name === table)?.[0] ||
|
|
{ HeadMainDat: 103, CostMainDat: 104, MachMainDat: 105 }[table],
|
|
);
|
|
if (!kind || (grouped && !(Number(row.Index) > 0))) {
|
|
row.Code = value;
|
|
return;
|
|
}
|
|
const code = String(value || "").trim(),
|
|
old = row.Code;
|
|
if (
|
|
!code ||
|
|
rows.some(
|
|
(other, at) =>
|
|
at !== index &&
|
|
(!grouped || Number(other.Index) > 0) &&
|
|
(other.Code === code || other.PreviousCodes?.includes(code)),
|
|
)
|
|
)
|
|
throw Error("비어 있거나 중복된 원천 코드입니다.");
|
|
if (old === code) return;
|
|
row.Code = code;
|
|
row.PreviousCodes = [...new Set([...(row.PreviousCodes || []), old].filter(Boolean))];
|
|
for (const list of Object.values(project.tables))
|
|
for (const linked of list)
|
|
if (old && Number(linked.LinkIndex) === kind && linked.Code === old) linked.Code = code;
|
|
if (grouped) {
|
|
const titles = project.tables[EstNative.sourceTables[kind]] || [];
|
|
const title = titles.find((item) => item.Code === old);
|
|
if (title) Object.assign(title, { Code: code, PreviousCodes: [...row.PreviousCodes] });
|
|
for (
|
|
let at = index;
|
|
at < rows.length && (at === index || !(Number(rows[at].Index) > 0));
|
|
at++
|
|
)
|
|
if (rows[at].TitleCode === old) rows[at].TitleCode = code;
|
|
}
|
|
}
|
|
function newRow(table, rows, project) {
|
|
const historical = [rows, project?.baseline?.tables?.[table] || []];
|
|
if (table === "EstMainDat")
|
|
for (const round of project?.progress || [])
|
|
historical.push(round.contract?.rows || [], round.result?.rows || []);
|
|
let max = historical.reduce(
|
|
(maximum, list) =>
|
|
list.reduce((value, row) => Math.max(value, Number(row.PkID) || 0), maximum),
|
|
0,
|
|
);
|
|
if (table === "EstMainDat")
|
|
for (const link of project?.excelQuantityLinks || []) max = Math.max(max, link.target);
|
|
const row = {
|
|
PkID: max + 1,
|
|
Index: 0,
|
|
Name: "새 항목",
|
|
NewItem: true,
|
|
GN: 1,
|
|
CalcIndex: 0,
|
|
LinkIndex: 0,
|
|
Level: 0,
|
|
};
|
|
if (["StuffcDat", "LaborcDat", "SilJukDat", "EstiMateDat"].includes(table))
|
|
Object.assign(row, {
|
|
Code: `U${Date.now().toString(36)}_${max + 1}`,
|
|
ValueIndex: 4,
|
|
InputTC: 0,
|
|
TC: 0,
|
|
});
|
|
if (table === "Wonga") Object.assign(row, { Gubun: `R${max + 1}`, VSusic: "", Cost: 0 });
|
|
if (table === "CostMainDat") Object.assign(row, { LineData: "Q = 1 + 1 = \x122\x12" });
|
|
return row;
|
|
}
|
|
return { labels, numeric, columns, validate, legacy, newRow, editCode };
|
|
})();
|