feat(cupang): 발주 업로드 결과를 출고일별로 분리
- 업로드 API가 (출고일, 센터, 제품)으로 합산해 groups[] 로 반환 - 화면 상단에 출고일 탭 — 날짜마다 ①직접입력 / ②박스요약 / ③센터분배 상태를 따로 보관하고 탭으로 전환 (박스 계산은 날짜 × 센터 단위로 실행) - 확정은 현재 날짜만 대상. 남은 날짜가 있으면 달력으로 나가지 않고 다음 탭으로 이동 - 업로드 결과 카드에 출고일별 센터 수·수량 요약 추가 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -769,8 +769,8 @@ async def box_calc_upload(
|
||||
warnings: list[str] = []
|
||||
file_infos: list[dict[str, Any]] = []
|
||||
ship_dates: list[str] = []
|
||||
# (센터명, 제품코드) → 수량
|
||||
agg: dict[tuple[str, str], dict[str, Any]] = {}
|
||||
# (출고일, 센터명, 제품코드) → 수량
|
||||
agg: dict[tuple[str, str, str], dict[str, Any]] = {}
|
||||
unknown_codes: set[str] = set()
|
||||
unknown_centers: set[str] = set()
|
||||
no_rule: set[str] = set()
|
||||
@@ -793,6 +793,7 @@ async def box_calc_upload(
|
||||
else:
|
||||
warnings.append(f"{name}: F13 입고예정일을 읽지 못했습니다.")
|
||||
|
||||
ship_iso = ship.isoformat() if ship else ""
|
||||
used = 0
|
||||
for row in parsed["rows"]:
|
||||
prod = by_coupang.get(row["coupang_item_code"])
|
||||
@@ -805,10 +806,11 @@ async def box_calc_upload(
|
||||
code = prod["product_code"]
|
||||
if code not in rules:
|
||||
no_rule.add(f'{prod["product_name"]}({code})')
|
||||
key = (cname, code)
|
||||
key = (ship_iso, cname, code)
|
||||
cell = agg.setdefault(
|
||||
key,
|
||||
{
|
||||
"ship_date": ship_iso,
|
||||
"center_name": cname,
|
||||
"product_code": code,
|
||||
"product_name": prod["product_name"],
|
||||
@@ -823,7 +825,7 @@ async def box_calc_upload(
|
||||
{
|
||||
"filename": name,
|
||||
"arrival_date": arrival.isoformat() if arrival else "",
|
||||
"ship_date": ship.isoformat() if ship else "",
|
||||
"ship_date": ship_iso,
|
||||
"rows": len(parsed["rows"]),
|
||||
"quantity": used,
|
||||
}
|
||||
@@ -841,12 +843,12 @@ async def box_calc_upload(
|
||||
warnings.append("박스 입수량 미설정: " + ", ".join(sorted(no_rule)))
|
||||
|
||||
uniq_dates = sorted(set(ship_dates))
|
||||
if len(uniq_dates) > 1:
|
||||
warnings.append("파일마다 출고일이 다릅니다: " + ", ".join(uniq_dates))
|
||||
|
||||
grouped: dict[str, dict[str, Any]] = {}
|
||||
for (cname, _code), cell in agg.items():
|
||||
g = grouped.setdefault(
|
||||
# 출고일 → 센터 → 품목. 업로드한 파일의 출고일이 다르면 날짜별로 나뉜다.
|
||||
by_date: dict[str, dict[str, dict[str, Any]]] = {}
|
||||
for (ship_iso, cname, _code), cell in agg.items():
|
||||
centers_of_date = by_date.setdefault(ship_iso, {})
|
||||
g = centers_of_date.setdefault(
|
||||
cname,
|
||||
{
|
||||
"center_name": cname,
|
||||
@@ -862,8 +864,24 @@ async def box_calc_upload(
|
||||
"quantity": cell["quantity"],
|
||||
}
|
||||
)
|
||||
for g in grouped.values():
|
||||
g["items"].sort(key=lambda it: it["product_code"])
|
||||
|
||||
groups: list[dict[str, Any]] = []
|
||||
for ship_iso in sorted(by_date):
|
||||
centers_of_date = by_date[ship_iso]
|
||||
for g in centers_of_date.values():
|
||||
g["items"].sort(key=lambda it: it["product_code"])
|
||||
groups.append(
|
||||
{
|
||||
"ship_date": ship_iso,
|
||||
"centers": sorted(centers_of_date.values(), key=lambda g: g["center_name"]),
|
||||
"files": [f for f in file_infos if f["ship_date"] == ship_iso],
|
||||
"quantity": sum(
|
||||
it["quantity"]
|
||||
for g in centers_of_date.values()
|
||||
for it in g["items"]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
@@ -871,7 +889,7 @@ async def box_calc_upload(
|
||||
"ship_date": uniq_dates[0] if uniq_dates else "",
|
||||
"ship_dates": uniq_dates,
|
||||
"files": file_infos,
|
||||
"centers": sorted(grouped.values(), key=lambda g: g["center_name"]),
|
||||
"groups": groups,
|
||||
"warnings": warnings,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901i" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901j" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
@@ -22,6 +22,12 @@
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<!-- 업로드한 발주서의 출고일이 여러 개면 날짜별로 작업 영역을 나눈다 -->
|
||||
<div class="cpg-date-tabs" id="cpg-date-tabs" hidden>
|
||||
<span class="cpg-date-tabs-label">출고일</span>
|
||||
<div class="cpg-date-tabs-list" id="cpg-date-tabs-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="cpg-calc3">
|
||||
|
||||
<!-- ① 박스 계산 — 제품명 + 수량만 입력 -->
|
||||
@@ -359,16 +365,116 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── 출고일별 작업 세트 ────────────────────────────
|
||||
// 업로드한 발주서의 출고일이 다르면 날짜마다 ①②③ 상태를 따로 들고,
|
||||
// 탭으로 전환한다. 확정은 현재 보고 있는 날짜만 대상으로 한다.
|
||||
var sets = {}; // "YYYY-MM-DD" -> 상태 스냅샷
|
||||
var dateOrder = []; // 탭 순서
|
||||
var activeDate = ""; // "" = 업로드 전(수동 입력 모드)
|
||||
var tabsBar = document.getElementById("cpg-date-tabs");
|
||||
var tabsList = document.getElementById("cpg-date-tabs-list");
|
||||
|
||||
function centersOf(allocObj) {
|
||||
return Object.keys(allocObj || {}).filter(function (cid) {
|
||||
return (allocObj[cid] || []).length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
function boxesOf(allocObj) {
|
||||
var n = 0;
|
||||
Object.keys(allocObj || {}).forEach(function (cid) {
|
||||
(allocObj[cid] || []).forEach(function (a) { n += a.count; });
|
||||
});
|
||||
return n;
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
if (!tabsBar) return;
|
||||
if (dateOrder.length < 1) { tabsBar.hidden = true; tabsList.innerHTML = ""; return; }
|
||||
tabsBar.hidden = false;
|
||||
tabsList.innerHTML = dateOrder.map(function (d) {
|
||||
var st = sets[d] || {};
|
||||
var cn = centersOf(st.alloc).length;
|
||||
var bx = boxesOf(st.alloc);
|
||||
return '<button type="button" class="cpg-date-tab' + (d === activeDate ? " is-active" : "") + '"' +
|
||||
' data-date="' + esc(d) + '">' +
|
||||
'<strong>' + esc(d || "날짜 미확인") + "</strong>" +
|
||||
'<span>센터 ' + cn + "곳 · " + bx + "박스</span>" +
|
||||
"</button>";
|
||||
}).join("");
|
||||
}
|
||||
|
||||
// 현재 화면 상태를 활성 날짜 세트에 저장
|
||||
function saveActive() {
|
||||
if (!activeDate) return;
|
||||
sets[activeDate] = {
|
||||
items: collect(),
|
||||
calc: { results: calc.results, mixes: calc.mixes },
|
||||
units: units, labels: labels, contents: contents,
|
||||
openCenters: openCenters.slice(), alloc: alloc, methods: methods,
|
||||
stamped: stamped, allStamped: allStamped
|
||||
};
|
||||
}
|
||||
|
||||
// 날짜 세트를 화면(①②③)에 올린다
|
||||
function loadDate(d) {
|
||||
var st = sets[d];
|
||||
if (!st) return;
|
||||
activeDate = d;
|
||||
uploadShipDate = d;
|
||||
calc.results = st.calc.results;
|
||||
calc.mixes = st.calc.mixes;
|
||||
units = st.units; labels = st.labels; contents = st.contents;
|
||||
openCenters = st.openCenters.slice();
|
||||
alloc = st.alloc; methods = st.methods;
|
||||
stamped = st.stamped; allStamped = st.allStamped;
|
||||
|
||||
tbody.innerHTML = "";
|
||||
(st.items || []).forEach(function (it) { addRow(it.product_code, it.quantity); });
|
||||
if (!(st.items || []).length) addRow();
|
||||
|
||||
render();
|
||||
renderTabs();
|
||||
}
|
||||
|
||||
if (tabsList) {
|
||||
tabsList.addEventListener("click", function (e) {
|
||||
var btn = e.target.closest("[data-date]");
|
||||
if (!btn) return;
|
||||
var d = btn.getAttribute("data-date");
|
||||
if (d === activeDate) return;
|
||||
saveActive();
|
||||
loadDate(d);
|
||||
msg.textContent = "출고일 " + (d || "미확인") + " 작업으로 전환했습니다.";
|
||||
});
|
||||
}
|
||||
|
||||
// 센터별 계산 결과를 하나의 ② 요약으로 합치고, 각 박스를 그 센터에 자동 배분한다.
|
||||
function applyUploaded(groups, perCenter) {
|
||||
// (한 출고일 안에서만 합친다 — 박스는 센터 단위로 포장)
|
||||
function buildDataset(groups, perCenter) {
|
||||
var mergedProds = {}; // code -> 합산 결과
|
||||
var mergedMixes = {}; // box_name -> {box_name, boxes:[]}
|
||||
var autoAlloc = []; // [{cid, key, count}]
|
||||
var mixOwner = {}; // mixKey -> 센터명
|
||||
var st = {
|
||||
items: [], calc: { results: [], mixes: [] },
|
||||
units: {}, labels: {}, contents: {},
|
||||
openCenters: [], alloc: {}, methods: {},
|
||||
stamped: {}, allStamped: false
|
||||
};
|
||||
var mixOwner = {};
|
||||
|
||||
function put(cid, key, count) {
|
||||
if (count <= 0) return;
|
||||
st.alloc[cid] = st.alloc[cid] || [];
|
||||
var same = st.alloc[cid].filter(function (a) { return a.key === key; })[0];
|
||||
if (same) { same.count += count; return; }
|
||||
seq += 1;
|
||||
st.alloc[cid].push({ id: seq, key: key, count: count });
|
||||
}
|
||||
|
||||
groups.forEach(function (g, gi) {
|
||||
var cid = String(g.center_id);
|
||||
var data = perCenter[gi] || {};
|
||||
if (st.openCenters.indexOf(cid) < 0) st.openCenters.push(cid);
|
||||
(data.results || []).filter(function (r) { return r.configured; }).forEach(function (r) {
|
||||
var m = mergedProds[r.product_code];
|
||||
if (!m) {
|
||||
@@ -383,9 +489,7 @@
|
||||
m.full_boxes += r.full_boxes;
|
||||
m.remainder_units += r.remainder_units;
|
||||
m.required_boxes += r.required_boxes;
|
||||
if (r.full_boxes > 0) {
|
||||
autoAlloc.push({ cid: cid, key: prodKey(r.product_code), count: r.full_boxes });
|
||||
}
|
||||
put(cid, prodKey(r.product_code), r.full_boxes);
|
||||
});
|
||||
(data.mixes || []).forEach(function (mx) {
|
||||
var bucket = mergedMixes[mx.box_name];
|
||||
@@ -395,50 +499,39 @@
|
||||
bucket.boxes.push(b);
|
||||
var k = mixKey(mx.box_name, idx);
|
||||
mixOwner[k] = g.center_name;
|
||||
autoAlloc.push({ cid: cid, key: k, count: 1 });
|
||||
put(cid, k, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
calc.results = Object.keys(mergedProds).map(function (k) { return mergedProds[k]; });
|
||||
calc.mixes = Object.keys(mergedMixes).map(function (k) { return mergedMixes[k]; });
|
||||
st.calc.results = Object.keys(mergedProds).map(function (k) { return mergedProds[k]; });
|
||||
st.calc.mixes = Object.keys(mergedMixes).map(function (k) { return mergedMixes[k]; });
|
||||
|
||||
units = {}; labels = {}; contents = {};
|
||||
calc.results.forEach(function (r) {
|
||||
units[prodKey(r.product_code)] = r.units_per_box;
|
||||
labels[prodKey(r.product_code)] = r.product_name;
|
||||
st.calc.results.forEach(function (r) {
|
||||
st.units[prodKey(r.product_code)] = r.units_per_box;
|
||||
st.labels[prodKey(r.product_code)] = r.product_name;
|
||||
});
|
||||
calc.mixes.forEach(function (m) {
|
||||
st.calc.mixes.forEach(function (m) {
|
||||
m.boxes.forEach(function (b, i) {
|
||||
var n = 0;
|
||||
b.items.forEach(function (it) { n += it.quantity; });
|
||||
var k = mixKey(m.box_name, i);
|
||||
units[k] = n;
|
||||
labels[k] = m.box_name + " 혼합 #" + (i + 1) +
|
||||
(mixOwner[k] ? " · " + mixOwner[k] : "");
|
||||
contents[k] = b.items.map(function (it) {
|
||||
st.units[k] = n;
|
||||
st.labels[k] = m.box_name + " 혼합 #" + (i + 1) +
|
||||
(mixOwner[k] ? " · " + mixOwner[k] : "");
|
||||
st.contents[k] = b.items.map(function (it) {
|
||||
return { product_code: it.product_code, product_name: it.product_name, quantity: it.quantity };
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ③ 센터 열고 자동 배분
|
||||
alloc = {}; stamped = {}; allStamped = false;
|
||||
openCenters = [];
|
||||
groups.forEach(function (g) {
|
||||
var cid = String(g.center_id);
|
||||
if (openCenters.indexOf(cid) < 0) openCenters.push(cid);
|
||||
});
|
||||
autoAlloc.forEach(function (a) { addAlloc(a.cid, a.key, a.count); });
|
||||
|
||||
// ① 표에는 제품별 합계를 채운다(수정 후 재계산 가능).
|
||||
tbody.innerHTML = "";
|
||||
calc.results.slice().sort(function (a, b) {
|
||||
st.items = st.calc.results.slice().sort(function (a, b) {
|
||||
return String(a.product_name).localeCompare(String(b.product_name), "ko");
|
||||
}).forEach(function (r) { addRow(r.product_code, r.quantity); });
|
||||
if (!calc.results.length) addRow();
|
||||
}).map(function (r) {
|
||||
return { product_code: r.product_code, quantity: r.quantity };
|
||||
});
|
||||
|
||||
render();
|
||||
return st;
|
||||
}
|
||||
|
||||
function renderPoResult(data, skipped) {
|
||||
@@ -448,6 +541,12 @@
|
||||
'<span class="erp-badge erp-badge-neutral">파일 ' + ((data.files || []).length) + "개</span>" +
|
||||
'<span class="erp-badge erp-badge-neutral">센터 ' + ((data.centers || []).length) + "곳</span>" +
|
||||
"</div>";
|
||||
if ((data.groups || []).length > 1) {
|
||||
html += '<ul class="cpg-po-dates">' + data.groups.map(function (g) {
|
||||
return "<li><b>" + esc(g.ship_date || "날짜 미확인") + "</b>" +
|
||||
"<span>센터 " + ((g.centers || []).length) + "곳 · " + (g.quantity || 0) + "개</span></li>";
|
||||
}).join("") + "</ul>";
|
||||
}
|
||||
html += '<ul class="cpg-po-files">';
|
||||
(data.files || []).forEach(function (f) {
|
||||
html += "<li><span>" + esc(f.filename) + "</span>" +
|
||||
@@ -484,28 +583,63 @@
|
||||
});
|
||||
})
|
||||
.then(function (data) {
|
||||
uploadShipDate = data.ship_date || "";
|
||||
var groups = (data.centers || []).filter(function (g) { return g.center_id != null; });
|
||||
var skipped = (data.centers || []).filter(function (g) { return g.center_id == null; })
|
||||
.map(function (g) { return g.center_name; });
|
||||
if (!groups.length) {
|
||||
renderPoResult(data, skipped);
|
||||
var dateGroups = (data.groups || []).map(function (g) {
|
||||
return {
|
||||
ship_date: g.ship_date || "",
|
||||
centers: (g.centers || []).filter(function (c) { return c.center_id != null; }),
|
||||
skipped: (g.centers || []).filter(function (c) { return c.center_id == null; })
|
||||
.map(function (c) { return c.center_name; })
|
||||
};
|
||||
}).filter(function (g) { return g.centers.length; });
|
||||
|
||||
var allSkipped = [];
|
||||
(data.groups || []).forEach(function (g) {
|
||||
(g.centers || []).forEach(function (c) {
|
||||
if (c.center_id == null && allSkipped.indexOf(c.center_name) < 0) {
|
||||
allSkipped.push(c.center_name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!dateGroups.length) {
|
||||
renderPoResult(data, allSkipped);
|
||||
poMsg.textContent = "배분할 수 있는 센터가 없습니다.";
|
||||
return;
|
||||
}
|
||||
return Promise.all(groups.map(function (g) {
|
||||
return calcForItems(g.items.map(function (it) {
|
||||
return { product_code: it.product_code, quantity: it.quantity };
|
||||
}));
|
||||
})).then(function (perCenter) {
|
||||
applyUploaded(groups, perCenter);
|
||||
renderPoResult(data, skipped);
|
||||
var pieces = 0;
|
||||
groups.forEach(function (g) {
|
||||
g.items.forEach(function (it) { pieces += it.quantity; });
|
||||
|
||||
// 날짜 × 센터 단위로 박스 계산 (박스는 센터별로 포장되므로)
|
||||
var jobs = [];
|
||||
dateGroups.forEach(function (g) {
|
||||
g.centers.forEach(function (c) {
|
||||
jobs.push(calcForItems(c.items.map(function (it) {
|
||||
return { product_code: it.product_code, quantity: it.quantity };
|
||||
})));
|
||||
});
|
||||
poMsg.textContent = "분석 완료 — 센터 " + groups.length + "곳 · " + pieces + "개 자동 배분";
|
||||
msg.textContent = "업로드 결과로 계산했습니다. 센터마다 출고방식을 고르면 확정할 수 있습니다.";
|
||||
});
|
||||
|
||||
return Promise.all(jobs).then(function (all) {
|
||||
var at = 0;
|
||||
sets = {}; dateOrder = []; activeDate = "";
|
||||
dateGroups.forEach(function (g) {
|
||||
var per = all.slice(at, at + g.centers.length);
|
||||
at += g.centers.length;
|
||||
sets[g.ship_date] = buildDataset(g.centers, per);
|
||||
dateOrder.push(g.ship_date);
|
||||
});
|
||||
loadDate(dateOrder[0]);
|
||||
renderPoResult(data, allSkipped);
|
||||
var pieces = 0, centerCount = 0;
|
||||
dateGroups.forEach(function (g) {
|
||||
centerCount += g.centers.length;
|
||||
g.centers.forEach(function (c) {
|
||||
c.items.forEach(function (it) { pieces += it.quantity; });
|
||||
});
|
||||
});
|
||||
poMsg.textContent = "분석 완료 — 출고일 " + dateOrder.length + "개 · 센터 " +
|
||||
centerCount + "곳 · " + pieces + "개 자동 배분";
|
||||
msg.textContent = dateOrder.length > 1
|
||||
? "출고일이 " + dateOrder.length + "개입니다. 위 탭에서 날짜를 골라 확정하세요."
|
||||
: "업로드 결과로 계산했습니다. 센터마다 출고방식을 고르면 확정할 수 있습니다.";
|
||||
});
|
||||
})
|
||||
.catch(function (err) { poMsg.textContent = (err && err.message) || "업로드 실패"; })
|
||||
@@ -1158,6 +1292,20 @@
|
||||
})
|
||||
.then(function (data) {
|
||||
var d = (data && data.ship_date) || picked;
|
||||
// 업로드한 출고일이 여러 개면 남은 날짜 작업을 이어서 한다.
|
||||
var idx = dateOrder.indexOf(activeDate);
|
||||
if (idx >= 0 && dateOrder.length > 1) {
|
||||
var done = activeDate;
|
||||
dateOrder.splice(idx, 1);
|
||||
delete sets[done];
|
||||
closeConfirm();
|
||||
activeDate = "";
|
||||
loadDate(dateOrder[Math.min(idx, dateOrder.length - 1)]);
|
||||
msg.textContent = done + " 출고 확정 완료 — 남은 출고일 " + dateOrder.length + "개";
|
||||
poMsg.textContent = done + " 확정됨. 남은 날짜: " + dateOrder.join(", ");
|
||||
cfmOk.disabled = false;
|
||||
return;
|
||||
}
|
||||
var parts = d.split("-");
|
||||
// 달력으로 이동해 방금 만든 출고 묶음을 보여준다.
|
||||
window.location.href = "/cupang/?year=" + parts[0] + "&month=" + parseInt(parts[1], 10) + "&date=" + d;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901i" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901j" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901i" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901j" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901i" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901j" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901i" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901j" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901i" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901j" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901i" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901j" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
Reference in New Issue
Block a user