651 lines
32 KiB
Java
651 lines
32 KiB
Java
package com.erp.service;
|
|
|
|
import com.erp.common.BaseService;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
@Slf4j
|
|
public class BomService extends BaseService {
|
|
|
|
private static final String NS = "bom.";
|
|
|
|
private final CommonService commonService;
|
|
private final JdbcTemplate jdbcTemplate;
|
|
|
|
// ─── 기본 CRUD ───
|
|
|
|
public Map<String, Object> getBomList(Map<String, Object> params) {
|
|
commonService.applyCompanyCodeFilter(params);
|
|
commonService.applyPagination(params);
|
|
int totalCount = sqlSession.selectOne(NS + "getBomListCnt", params);
|
|
List<Map<String, Object>> list = sqlSession.selectList(NS + "getBomList", params);
|
|
return commonService.buildListResponse(list, totalCount, params);
|
|
}
|
|
|
|
public Map<String, Object> getBomInfo(Map<String, Object> params) {
|
|
commonService.applyCompanyCodeFilter(params);
|
|
return sqlSession.selectOne(NS + "getBomInfo", params);
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> insertBom(Map<String, Object> params) {
|
|
commonService.applyCompanyCodeFilter(params);
|
|
sqlSession.insert(NS + "insertBom", params);
|
|
return params;
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> updateBom(Map<String, Object> params) {
|
|
commonService.applyCompanyCodeFilter(params);
|
|
sqlSession.update(NS + "updateBom", params);
|
|
return params;
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> deleteBom(Map<String, Object> params) {
|
|
commonService.applyCompanyCodeFilter(params);
|
|
sqlSession.delete(NS + "deleteBom", params);
|
|
return params;
|
|
}
|
|
|
|
// ─── BOM 헤더 조회 (entity join 포함) ───
|
|
|
|
public Map<String, Object> getBomHeader(String bomId) {
|
|
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
|
|
"SELECT b.*, ii.item_name AS ii_item_name, ii.item_number, ii.division AS item_type," +
|
|
" COALESCE(b.unit, ii.unit) AS unit, ii.unit AS item_unit, ii.division, ii.size, ii.material" +
|
|
" FROM bom b LEFT JOIN item_info ii ON b.item_id = ii.id" +
|
|
" WHERE b.id = ?",
|
|
bomId
|
|
);
|
|
return rows.isEmpty() ? null : rows.get(0);
|
|
}
|
|
|
|
// ─── 이력 ───
|
|
|
|
public List<Map<String, Object>> getBomHistory(String bomId, String companyCode) {
|
|
if ("*".equals(companyCode)) {
|
|
return jdbcTemplate.queryForList(
|
|
"SELECT * FROM bom_history WHERE bom_id = ? ORDER BY changed_date DESC",
|
|
bomId
|
|
);
|
|
}
|
|
return jdbcTemplate.queryForList(
|
|
"SELECT * FROM bom_history WHERE bom_id = ? AND company_code = ? ORDER BY changed_date DESC",
|
|
bomId, companyCode
|
|
);
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> addBomHistory(String bomId, String companyCode, Map<String, Object> data) {
|
|
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_history (bom_id, revision, version, change_type, change_description, changed_by, company_code)" +
|
|
" VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *",
|
|
bomId,
|
|
data.get("revision"),
|
|
data.get("version"),
|
|
data.get("change_type"),
|
|
data.get("change_description"),
|
|
data.get("changed_by"),
|
|
companyCode
|
|
);
|
|
return rows.isEmpty() ? new HashMap<>() : rows.get(0);
|
|
}
|
|
|
|
// ─── 버전 관리 ───
|
|
|
|
public Map<String, Object> getBomVersions(String bomId, String companyCode) {
|
|
List<Map<String, Object>> versions;
|
|
if ("*".equals(companyCode)) {
|
|
versions = jdbcTemplate.queryForList(
|
|
"SELECT v.*, (SELECT COUNT(*) FROM bom_detail d WHERE d.version_id = v.id) AS detail_count" +
|
|
" FROM bom_version v WHERE v.bom_id = ? ORDER BY v.created_date DESC",
|
|
bomId
|
|
);
|
|
} else {
|
|
versions = jdbcTemplate.queryForList(
|
|
"SELECT v.*, (SELECT COUNT(*) FROM bom_detail d WHERE d.version_id = v.id) AS detail_count" +
|
|
" FROM bom_version v WHERE v.bom_id = ? AND v.company_code = ? ORDER BY v.created_date DESC",
|
|
bomId, companyCode
|
|
);
|
|
}
|
|
|
|
List<Map<String, Object>> bomRows = jdbcTemplate.queryForList(
|
|
"SELECT current_version_id FROM bom WHERE id = ?", bomId
|
|
);
|
|
Object currentVersionId = bomRows.isEmpty() ? null : bomRows.get(0).get("current_version_id");
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
result.put("versions", versions);
|
|
result.put("current_version_id", currentVersionId);
|
|
return result;
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> createBomVersion(String bomId, String companyCode, String createdBy, String versionName) {
|
|
List<Map<String, Object>> bomRows = jdbcTemplate.queryForList(
|
|
"SELECT * FROM bom WHERE id = ?", bomId
|
|
);
|
|
if (bomRows.isEmpty()) throw new IllegalArgumentException("BOM을 찾을 수 없습니다");
|
|
Map<String, Object> bomData = bomRows.get(0);
|
|
|
|
String finalVersionName = (versionName != null && !versionName.trim().isEmpty()) ? versionName.trim() : null;
|
|
if (finalVersionName == null) {
|
|
List<Map<String, Object>> countRows = jdbcTemplate.queryForList(
|
|
"SELECT COUNT(*)::int AS cnt FROM bom_version WHERE bom_id = ?", bomId
|
|
);
|
|
int cnt = countRows.isEmpty() ? 0 : ((Number) countRows.get(0).get("cnt")).intValue();
|
|
finalVersionName = (cnt + 1) + ".0";
|
|
}
|
|
|
|
List<Map<String, Object>> dupCheck = jdbcTemplate.queryForList(
|
|
"SELECT id FROM bom_version WHERE bom_id = ? AND version_name = ?", bomId, finalVersionName
|
|
);
|
|
if (!dupCheck.isEmpty()) throw new IllegalArgumentException("이미 존재하는 버전명입니다: " + finalVersionName);
|
|
|
|
Object revision = bomData.get("revision");
|
|
int revInt = revision == null ? 0 : (revision instanceof Number ? ((Number) revision).intValue() : 0);
|
|
|
|
List<Map<String, Object>> newVersionRows = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
|
|
" VALUES (?, ?, ?, 'developing', ?, ?) RETURNING *",
|
|
bomId, finalVersionName, revInt, createdBy, companyCode
|
|
);
|
|
if (newVersionRows.isEmpty()) throw new IllegalArgumentException("버전 생성 실패");
|
|
Map<String, Object> newVersion = newVersionRows.get(0);
|
|
Object newVersionId = newVersion.get("id");
|
|
|
|
Object sourceVersionId = bomData.get("current_version_id");
|
|
if (sourceVersionId != null) {
|
|
List<Map<String, Object>> sourceDetails = jdbcTemplate.queryForList(
|
|
"SELECT * FROM bom_detail WHERE bom_id = ? AND version_id = ? ORDER BY parent_detail_id NULLS FIRST, id",
|
|
bomId, sourceVersionId
|
|
);
|
|
|
|
Map<Object, Object> oldToNew = new HashMap<>();
|
|
for (Map<String, Object> d : sourceDetails) {
|
|
Object parentDetailId = d.get("parent_detail_id");
|
|
Object mappedParent = parentDetailId != null ? oldToNew.get(parentDetailId) : null;
|
|
|
|
List<Map<String, Object>> insertedRows = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_detail (bom_id, version_id, parent_detail_id, child_item_id, quantity, unit," +
|
|
" process_type, loss_rate, remark, level, base_qty, revision, seq_no, writer, company_code)" +
|
|
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
|
|
bomId, newVersionId, mappedParent, d.get("child_item_id"), d.get("quantity"), d.get("unit"),
|
|
d.get("process_type"), d.get("loss_rate"), d.get("remark"), d.get("level"),
|
|
d.get("base_qty"), d.get("revision"), d.get("seq_no"), d.get("writer"), companyCode
|
|
);
|
|
if (!insertedRows.isEmpty()) {
|
|
oldToNew.put(d.get("id"), insertedRows.get(0).get("id"));
|
|
}
|
|
}
|
|
}
|
|
|
|
jdbcTemplate.update(
|
|
"UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
|
|
finalVersionName, newVersionId, bomId
|
|
);
|
|
|
|
log.info("BOM 버전 생성 완료: bomId={}, versionName={}", bomId, finalVersionName);
|
|
return newVersion;
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> loadBomVersion(String bomId, String versionId, String companyCode) {
|
|
List<Map<String, Object>> verRows = jdbcTemplate.queryForList(
|
|
"SELECT * FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId
|
|
);
|
|
if (verRows.isEmpty()) throw new IllegalArgumentException("버전을 찾을 수 없습니다");
|
|
String versionName = (String) verRows.get(0).get("version_name");
|
|
|
|
jdbcTemplate.update(
|
|
"UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
|
|
versionName, versionId, bomId
|
|
);
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
result.put("restored", true);
|
|
result.put("version_name", versionName);
|
|
return result;
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> activateBomVersion(String bomId, String versionId) {
|
|
List<Map<String, Object>> verRows = jdbcTemplate.queryForList(
|
|
"SELECT version_name FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId
|
|
);
|
|
if (verRows.isEmpty()) throw new IllegalArgumentException("버전을 찾을 수 없습니다");
|
|
String versionName = (String) verRows.get(0).get("version_name");
|
|
|
|
jdbcTemplate.update(
|
|
"UPDATE bom_version SET status = 'inactive' WHERE bom_id = ? AND status = 'active'", bomId
|
|
);
|
|
jdbcTemplate.update("UPDATE bom_version SET status = 'active' WHERE id = ?", versionId);
|
|
jdbcTemplate.update(
|
|
"UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
|
|
versionName, versionId, bomId
|
|
);
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
result.put("activated", true);
|
|
result.put("version_name", versionName);
|
|
return result;
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> initializeBomVersion(String bomId, String companyCode, String createdBy) {
|
|
List<Map<String, Object>> bomRows = jdbcTemplate.queryForList("SELECT * FROM bom WHERE id = ?", bomId);
|
|
if (bomRows.isEmpty()) throw new IllegalArgumentException("BOM을 찾을 수 없습니다");
|
|
Map<String, Object> bomData = bomRows.get(0);
|
|
|
|
Object currentVersionId = bomData.get("current_version_id");
|
|
if (currentVersionId != null) {
|
|
jdbcTemplate.update(
|
|
"UPDATE bom_detail SET version_id = ? WHERE bom_id = ? AND version_id IS NULL",
|
|
currentVersionId, bomId
|
|
);
|
|
Map<String, Object> result = new HashMap<>();
|
|
result.put("version_id", currentVersionId);
|
|
result.put("created", false);
|
|
return result;
|
|
}
|
|
|
|
List<Map<String, Object>> existingVersions = jdbcTemplate.queryForList(
|
|
"SELECT id, version_name FROM bom_version WHERE bom_id = ? ORDER BY created_date ASC LIMIT 1", bomId
|
|
);
|
|
if (!existingVersions.isEmpty()) {
|
|
Object existId = existingVersions.get(0).get("id");
|
|
jdbcTemplate.update(
|
|
"UPDATE bom_detail SET version_id = ? WHERE bom_id = ? AND version_id IS NULL", existId, bomId
|
|
);
|
|
jdbcTemplate.update(
|
|
"UPDATE bom SET current_version_id = ? WHERE id = ? AND current_version_id IS NULL", existId, bomId
|
|
);
|
|
Map<String, Object> result = new HashMap<>();
|
|
result.put("version_id", existId);
|
|
result.put("created", false);
|
|
return result;
|
|
}
|
|
|
|
String versionName = bomData.get("version") != null ? String.valueOf(bomData.get("version")) : "1.0";
|
|
List<Map<String, Object>> versionRows = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
|
|
" VALUES (?, ?, 0, 'active', ?, ?) RETURNING id",
|
|
bomId, versionName, createdBy, companyCode
|
|
);
|
|
Object versionId = versionRows.isEmpty() ? null : versionRows.get(0).get("id");
|
|
|
|
jdbcTemplate.update(
|
|
"UPDATE bom_detail SET version_id = ? WHERE bom_id = ? AND version_id IS NULL", versionId, bomId
|
|
);
|
|
jdbcTemplate.update("UPDATE bom SET current_version_id = ? WHERE id = ?", versionId, bomId);
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
result.put("version_id", versionId);
|
|
result.put("version_name", versionName);
|
|
result.put("created", true);
|
|
return result;
|
|
}
|
|
|
|
@Transactional
|
|
public boolean deleteBomVersion(String bomId, String versionId) {
|
|
List<Map<String, Object>> checkRows = jdbcTemplate.queryForList(
|
|
"SELECT status FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId
|
|
);
|
|
if (checkRows.isEmpty()) throw new IllegalArgumentException("버전을 찾을 수 없습니다");
|
|
if ("active".equals(checkRows.get(0).get("status"))) {
|
|
throw new IllegalArgumentException("사용중인 버전은 삭제할 수 없습니다");
|
|
}
|
|
|
|
jdbcTemplate.update("DELETE FROM bom_detail WHERE bom_id = ? AND version_id = ?", bomId, versionId);
|
|
int deleted = jdbcTemplate.update("DELETE FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId);
|
|
return deleted > 0;
|
|
}
|
|
|
|
// ─── 엑셀 업로드/다운로드 ───
|
|
|
|
@Transactional
|
|
public Map<String, Object> createBomFromExcel(String companyCode, String userId, List<Map<String, Object>> rows) {
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
result.put("success", false);
|
|
result.put("inserted_count", 0);
|
|
result.put("skipped_count", 0);
|
|
result.put("errors", new ArrayList<String>());
|
|
result.put("unmatched_items", new ArrayList<String>());
|
|
|
|
@SuppressWarnings("unchecked")
|
|
List<String> errors = (List<String>) result.get("errors");
|
|
@SuppressWarnings("unchecked")
|
|
List<String> unmatchedItems = (List<String>) result.get("unmatched_items");
|
|
|
|
if (rows == null || rows.isEmpty()) {
|
|
errors.add("업로드할 데이터가 없습니다");
|
|
return result;
|
|
}
|
|
|
|
Map<String, Object> headerRow = rows.stream().filter(r -> toInt(r.get("level")) == 0).findFirst().orElse(null);
|
|
List<Map<String, Object>> detailRows = rows.stream().filter(r -> toInt(r.get("level")) > 0).collect(Collectors.toList());
|
|
|
|
if (headerRow == null) { errors.add("레벨 0(BOM 마스터) 행이 필요합니다"); return result; }
|
|
String headerItemNumber = headerRow.get("item_number") != null ? String.valueOf(headerRow.get("item_number")).trim() : "";
|
|
if (headerItemNumber.isEmpty()) { errors.add("레벨 0(BOM 마스터)의 품번은 필수입니다"); return result; }
|
|
if (detailRows.isEmpty()) { errors.add("하위품목이 없습니다"); return result; }
|
|
|
|
// 레벨 유효성 검사
|
|
for (int i = 0; i < rows.size(); i++) {
|
|
int level = toInt(rows.get(i).get("level"));
|
|
if (level < 0) errors.add((i + 1) + "행: 레벨은 0 이상이어야 합니다");
|
|
if (i > 0 && level > toInt(rows.get(i - 1).get("level")) + 1)
|
|
errors.add((i + 1) + "행: 레벨이 이전 행보다 2 이상 깊어질 수 없습니다");
|
|
if (level > 0 && (rows.get(i).get("item_number") == null || String.valueOf(rows.get(i).get("item_number")).trim().isEmpty()))
|
|
errors.add((i + 1) + "행: 품번은 필수입니다");
|
|
}
|
|
if (!errors.isEmpty()) return result;
|
|
|
|
// 모든 품번 일괄 조회
|
|
Set<String> allItemNumbers = new LinkedHashSet<>();
|
|
rows.stream().filter(r -> r.get("item_number") != null && !String.valueOf(r.get("item_number")).trim().isEmpty())
|
|
.forEach(r -> allItemNumbers.add(String.valueOf(r.get("item_number")).trim()));
|
|
|
|
String placeholders = allItemNumbers.stream().map(i -> "?").collect(Collectors.joining(", "));
|
|
List<Object> lookupArgs = new ArrayList<>();
|
|
lookupArgs.add(companyCode);
|
|
lookupArgs.addAll(allItemNumbers);
|
|
|
|
List<Map<String, Object>> itemLookup = jdbcTemplate.queryForList(
|
|
"SELECT id, item_number, item_name, unit FROM item_info WHERE company_code = ? AND item_number IN (" + placeholders + ")",
|
|
lookupArgs.toArray()
|
|
);
|
|
Map<String, Map<String, Object>> itemMap = new LinkedHashMap<>();
|
|
for (Map<String, Object> item : itemLookup) {
|
|
itemMap.put(String.valueOf(item.get("item_number")), item);
|
|
}
|
|
|
|
for (String num : allItemNumbers) {
|
|
if (!itemMap.containsKey(num)) unmatchedItems.add(num);
|
|
}
|
|
if (!unmatchedItems.isEmpty()) {
|
|
errors.add("매칭되지 않는 품번이 있습니다: " + String.join(", ", unmatchedItems));
|
|
return result;
|
|
}
|
|
|
|
// BOM 마스터 생성
|
|
Map<String, Object> headerItemInfo = itemMap.get(headerItemNumber);
|
|
|
|
List<Map<String, Object>> dupCheck = jdbcTemplate.queryForList(
|
|
"SELECT id FROM bom WHERE item_id = ? AND company_code = ? AND status = 'active'",
|
|
headerItemInfo.get("id"), companyCode
|
|
);
|
|
if (!dupCheck.isEmpty()) {
|
|
errors.add("해당 품목(" + headerItemNumber + ")으로 등록된 BOM이 이미 존재합니다");
|
|
return result;
|
|
}
|
|
|
|
double headerQty = toDouble(headerRow.getOrDefault("quantity", 1));
|
|
Object headerUnit = headerRow.get("unit") != null ? headerRow.get("unit") : headerItemInfo.get("unit");
|
|
|
|
List<Map<String, Object>> bomInsert = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom (item_id, item_code, item_name, base_qty, unit, version, status, remark, writer, company_code)" +
|
|
" VALUES (?, ?, ?, ?, ?, '1.0', 'active', ?, ?, ?) RETURNING id",
|
|
headerItemInfo.get("id"), headerItemNumber,
|
|
headerItemInfo.get("item_name"), String.valueOf(headerQty),
|
|
headerUnit, headerRow.get("remark"), userId, companyCode
|
|
);
|
|
Object newBomId = bomInsert.isEmpty() ? null : bomInsert.get(0).get("id");
|
|
|
|
List<Map<String, Object>> versionInsert = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
|
|
" VALUES (?, '1.0', 0, 'active', ?, ?) RETURNING id",
|
|
newBomId, userId, companyCode
|
|
);
|
|
Object versionId = versionInsert.isEmpty() ? null : versionInsert.get(0).get("id");
|
|
jdbcTemplate.update("UPDATE bom SET current_version_id = ? WHERE id = ?", versionId, newBomId);
|
|
|
|
// bom_detail INSERT
|
|
List<Object> levelStack = new ArrayList<>();
|
|
Map<Object, Integer> seqByParent = new LinkedHashMap<>();
|
|
int insertedCount = 0;
|
|
|
|
for (Map<String, Object> row : detailRows) {
|
|
int dbLevel = toInt(row.get("level")) - 1;
|
|
while (levelStack.size() > dbLevel) levelStack.remove(levelStack.size() - 1);
|
|
Object parentDetailId = levelStack.isEmpty() ? null : levelStack.get(levelStack.size() - 1);
|
|
Object parentKey = parentDetailId != null ? parentDetailId : "__root__";
|
|
int currentSeq = seqByParent.getOrDefault(parentKey, 0) + 1;
|
|
seqByParent.put(parentKey, currentSeq);
|
|
|
|
String rowItemNumber = String.valueOf(row.get("item_number")).trim();
|
|
Map<String, Object> itemInfo = itemMap.get(rowItemNumber);
|
|
Object rowUnit = row.get("unit") != null ? row.get("unit") : itemInfo.get("unit");
|
|
double rowQty = toDouble(row.getOrDefault("quantity", 1));
|
|
|
|
List<Map<String, Object>> detailInsert = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_detail (bom_id, version_id, parent_detail_id, child_item_id, level, seq_no, quantity, unit, loss_rate, process_type, remark, writer, company_code)" +
|
|
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, '0', ?, ?, ?, ?) RETURNING id",
|
|
newBomId, versionId, parentDetailId, itemInfo.get("id"),
|
|
String.valueOf(dbLevel), String.valueOf(currentSeq), String.valueOf(rowQty),
|
|
rowUnit, row.get("process_type"), row.get("remark"), userId, companyCode
|
|
);
|
|
if (!detailInsert.isEmpty()) levelStack.add(detailInsert.get(0).get("id"));
|
|
insertedCount++;
|
|
}
|
|
|
|
jdbcTemplate.update(
|
|
"INSERT INTO bom_history (bom_id, change_type, change_description, changed_by, company_code)" +
|
|
" VALUES (?, 'excel_upload', ?, ?, ?)",
|
|
newBomId, "엑셀 업로드로 BOM 생성 (하위품목 " + insertedCount + "건)", userId, companyCode
|
|
);
|
|
|
|
result.put("success", true);
|
|
result.put("inserted_count", insertedCount);
|
|
result.put("created_bom_id", newBomId);
|
|
return result;
|
|
}
|
|
|
|
@Transactional
|
|
public Map<String, Object> createBomVersionFromExcel(String bomId, String companyCode, String userId, List<Map<String, Object>> rows, String versionName) {
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
result.put("success", false);
|
|
result.put("inserted_count", 0);
|
|
result.put("skipped_count", 0);
|
|
result.put("errors", new ArrayList<String>());
|
|
result.put("unmatched_items", new ArrayList<String>());
|
|
|
|
@SuppressWarnings("unchecked")
|
|
List<String> errors = (List<String>) result.get("errors");
|
|
@SuppressWarnings("unchecked")
|
|
List<String> unmatchedItems = (List<String>) result.get("unmatched_items");
|
|
|
|
if (rows == null || rows.isEmpty()) { errors.add("업로드할 데이터가 없습니다"); return result; }
|
|
|
|
List<Map<String, Object>> detailRows = rows.stream().filter(r -> toInt(r.get("level")) > 0).collect(Collectors.toList());
|
|
result.put("skipped_count", rows.size() - detailRows.size());
|
|
if (detailRows.isEmpty()) { errors.add("하위품목이 없습니다"); return result; }
|
|
|
|
List<Map<String, Object>> bomCheck = jdbcTemplate.queryForList(
|
|
"SELECT id FROM bom WHERE id = ? AND company_code = ?", bomId, companyCode
|
|
);
|
|
if (bomCheck.isEmpty()) { errors.add("BOM을 찾을 수 없습니다"); return result; }
|
|
|
|
Set<String> uniqueItemNumbers = detailRows.stream()
|
|
.filter(r -> r.get("item_number") != null)
|
|
.map(r -> String.valueOf(r.get("item_number")).trim())
|
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
|
|
|
String placeholders = uniqueItemNumbers.stream().map(i -> "?").collect(Collectors.joining(", "));
|
|
List<Object> lookupArgs = new ArrayList<>();
|
|
lookupArgs.add(companyCode);
|
|
lookupArgs.addAll(uniqueItemNumbers);
|
|
|
|
List<Map<String, Object>> itemLookup = jdbcTemplate.queryForList(
|
|
"SELECT id, item_number, item_name, unit FROM item_info WHERE company_code = ? AND item_number IN (" + placeholders + ")",
|
|
lookupArgs.toArray()
|
|
);
|
|
Map<String, Map<String, Object>> itemMap = new LinkedHashMap<>();
|
|
for (Map<String, Object> item : itemLookup) itemMap.put(String.valueOf(item.get("item_number")), item);
|
|
for (String num : uniqueItemNumbers) if (!itemMap.containsKey(num)) unmatchedItems.add(num);
|
|
if (!unmatchedItems.isEmpty()) { errors.add("매칭되지 않는 품번이 있습니다: " + String.join(", ", unmatchedItems)); return result; }
|
|
|
|
String finalVersionName = (versionName != null && !versionName.trim().isEmpty()) ? versionName.trim() : null;
|
|
if (finalVersionName == null) {
|
|
List<Map<String, Object>> countRows = jdbcTemplate.queryForList(
|
|
"SELECT COUNT(*)::int AS cnt FROM bom_version WHERE bom_id = ?", bomId
|
|
);
|
|
int cnt = countRows.isEmpty() ? 0 : ((Number) countRows.get(0).get("cnt")).intValue();
|
|
finalVersionName = (cnt + 1) + ".0";
|
|
}
|
|
|
|
List<Map<String, Object>> dupCheck = jdbcTemplate.queryForList(
|
|
"SELECT id FROM bom_version WHERE bom_id = ? AND version_name = ?", bomId, finalVersionName
|
|
);
|
|
if (!dupCheck.isEmpty()) { errors.add("이미 존재하는 버전명입니다: " + finalVersionName); return result; }
|
|
|
|
List<Map<String, Object>> versionInsert = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
|
|
" VALUES (?, ?, 0, 'developing', ?, ?) RETURNING id",
|
|
bomId, finalVersionName, userId, companyCode
|
|
);
|
|
Object newVersionId = versionInsert.isEmpty() ? null : versionInsert.get(0).get("id");
|
|
|
|
List<Object> levelStack = new ArrayList<>();
|
|
Map<Object, Integer> seqByParent = new LinkedHashMap<>();
|
|
int insertedCount = 0;
|
|
|
|
for (Map<String, Object> row : detailRows) {
|
|
int dbLevel = toInt(row.get("level")) - 1;
|
|
while (levelStack.size() > dbLevel) levelStack.remove(levelStack.size() - 1);
|
|
Object parentDetailId = levelStack.isEmpty() ? null : levelStack.get(levelStack.size() - 1);
|
|
Object parentKey = parentDetailId != null ? parentDetailId : "__root__";
|
|
int currentSeq = seqByParent.getOrDefault(parentKey, 0) + 1;
|
|
seqByParent.put(parentKey, currentSeq);
|
|
|
|
String rowItemNumber = String.valueOf(row.get("item_number")).trim();
|
|
Map<String, Object> itemInfo = itemMap.get(rowItemNumber);
|
|
Object rowUnit = row.get("unit") != null ? row.get("unit") : itemInfo.get("unit");
|
|
double rowQty = toDouble(row.getOrDefault("quantity", 1));
|
|
|
|
List<Map<String, Object>> detailInsert = jdbcTemplate.queryForList(
|
|
"INSERT INTO bom_detail (bom_id, version_id, parent_detail_id, child_item_id, level, seq_no, quantity, unit, loss_rate, process_type, remark, writer, company_code)" +
|
|
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, '0', ?, ?, ?, ?) RETURNING id",
|
|
bomId, newVersionId, parentDetailId, itemInfo.get("id"),
|
|
String.valueOf(dbLevel), String.valueOf(currentSeq), String.valueOf(rowQty),
|
|
rowUnit, row.get("process_type"), row.get("remark"), userId, companyCode
|
|
);
|
|
if (!detailInsert.isEmpty()) levelStack.add(detailInsert.get(0).get("id"));
|
|
insertedCount++;
|
|
}
|
|
|
|
jdbcTemplate.update(
|
|
"UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
|
|
finalVersionName, newVersionId, bomId
|
|
);
|
|
jdbcTemplate.update(
|
|
"INSERT INTO bom_history (bom_id, change_type, change_description, changed_by, company_code)" +
|
|
" VALUES (?, 'excel_upload', ?, ?, ?)",
|
|
bomId, "엑셀 업로드로 새 버전 " + finalVersionName + " 생성 (하위품목 " + insertedCount + "건)", userId, companyCode
|
|
);
|
|
|
|
result.put("success", true);
|
|
result.put("inserted_count", insertedCount);
|
|
result.put("created_bom_id", bomId);
|
|
return result;
|
|
}
|
|
|
|
public List<Map<String, Object>> downloadBomExcelData(String bomId, String companyCode) {
|
|
List<Map<String, Object>> bomRows = jdbcTemplate.queryForList(
|
|
"SELECT b.*, ii.item_number, ii.item_name AS ii_item_name, ii.division, ii.unit AS item_unit" +
|
|
" FROM bom b LEFT JOIN item_info ii ON b.item_id = ii.id" +
|
|
" WHERE b.id = ? AND b.company_code = ?",
|
|
bomId, companyCode
|
|
);
|
|
if (bomRows.isEmpty()) return new ArrayList<>();
|
|
|
|
Map<String, Object> bomHeader = bomRows.get(0);
|
|
List<Map<String, Object>> flatList = new ArrayList<>();
|
|
|
|
Map<String, Object> headerEntry = new LinkedHashMap<>();
|
|
headerEntry.put("level", 0);
|
|
headerEntry.put("item_number", bomHeader.getOrDefault("item_number", ""));
|
|
headerEntry.put("item_name", bomHeader.getOrDefault("ii_item_name", bomHeader.getOrDefault("item_name", "")));
|
|
headerEntry.put("quantity", bomHeader.getOrDefault("base_qty", "1"));
|
|
headerEntry.put("unit", bomHeader.getOrDefault("item_unit", bomHeader.getOrDefault("unit", "")));
|
|
headerEntry.put("process_type", "");
|
|
headerEntry.put("remark", bomHeader.getOrDefault("remark", ""));
|
|
flatList.add(headerEntry);
|
|
|
|
Object versionId = bomHeader.get("current_version_id");
|
|
List<Map<String, Object>> details;
|
|
if (versionId != null) {
|
|
details = jdbcTemplate.queryForList(
|
|
"SELECT bd.*, ii.item_number, ii.item_name, ii.division, ii.unit AS item_unit, ii.size, ii.material" +
|
|
" FROM bom_detail bd LEFT JOIN item_info ii ON bd.child_item_id = ii.id" +
|
|
" WHERE bd.bom_id = ? AND bd.company_code = ? AND bd.version_id = ?" +
|
|
" ORDER BY bd.parent_detail_id NULLS FIRST, CAST(COALESCE(bd.seq_no, '0') AS int)",
|
|
bomId, companyCode, versionId
|
|
);
|
|
} else {
|
|
details = jdbcTemplate.queryForList(
|
|
"SELECT bd.*, ii.item_number, ii.item_name, ii.division, ii.unit AS item_unit, ii.size, ii.material" +
|
|
" FROM bom_detail bd LEFT JOIN item_info ii ON bd.child_item_id = ii.id" +
|
|
" WHERE bd.bom_id = ? AND bd.company_code = ? AND bd.version_id IS NULL" +
|
|
" ORDER BY bd.parent_detail_id NULLS FIRST, CAST(COALESCE(bd.seq_no, '0') AS int)",
|
|
bomId, companyCode
|
|
);
|
|
}
|
|
|
|
Map<Object, List<Map<String, Object>>> childrenMap = new LinkedHashMap<>();
|
|
List<Map<String, Object>> roots = new ArrayList<>();
|
|
for (Map<String, Object> d : details) {
|
|
Object parentId = d.get("parent_detail_id");
|
|
if (parentId == null) {
|
|
roots.add(d);
|
|
} else {
|
|
childrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(d);
|
|
}
|
|
}
|
|
|
|
dfsBomDetail(roots, 1, childrenMap, flatList);
|
|
return flatList;
|
|
}
|
|
|
|
private void dfsBomDetail(List<Map<String, Object>> nodes, int depth,
|
|
Map<Object, List<Map<String, Object>>> childrenMap,
|
|
List<Map<String, Object>> flatList) {
|
|
for (Map<String, Object> node : nodes) {
|
|
Map<String, Object> entry = new LinkedHashMap<>();
|
|
entry.put("level", depth);
|
|
entry.put("item_number", node.getOrDefault("item_number", ""));
|
|
entry.put("item_name", node.getOrDefault("item_name", ""));
|
|
entry.put("quantity", node.getOrDefault("quantity", "1"));
|
|
entry.put("unit", node.get("unit") != null ? node.get("unit") : node.getOrDefault("item_unit", ""));
|
|
entry.put("process_type", node.getOrDefault("process_type", ""));
|
|
entry.put("remark", node.getOrDefault("remark", ""));
|
|
flatList.add(entry);
|
|
List<Map<String, Object>> children = childrenMap.getOrDefault(node.get("id"), new ArrayList<>());
|
|
if (!children.isEmpty()) dfsBomDetail(children, depth + 1, childrenMap, flatList);
|
|
}
|
|
}
|
|
|
|
// ─── 유틸리티 ───
|
|
|
|
private double toDouble(Object val) {
|
|
if (val == null) return 0.0;
|
|
if (val instanceof Number) return ((Number) val).doubleValue();
|
|
try { return Double.parseDouble(val.toString()); } catch (Exception e) { return 0.0; }
|
|
}
|
|
|
|
private int toInt(Object val) {
|
|
if (val == null) return 0;
|
|
if (val instanceof Number) return ((Number) val).intValue();
|
|
try { return Integer.parseInt(val.toString()); } catch (Exception e) { return 0; }
|
|
}
|
|
}
|