From c786fbabd7e6865968b1b236a9eb3ac78e0ea088 Mon Sep 17 00:00:00 2001 From: king Date: Tue, 16 Jun 2026 11:35:40 +0900 Subject: [PATCH] =?UTF-8?q?feat(malaysia):=20=EC=BD=A4=EB=B3=B4=20?= =?UTF-8?q?=EC=B6=9C=EA=B3=A0=EB=A5=BC=20=EC=BD=A4=EB=B3=B4=20=EC=9E=AC?= =?UTF-8?q?=EA=B3=A0=ED=98=84=ED=99=A9=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - set_movement(콤보 단위 출고 원장) 테이블 신설 + 마이그레이션 SQL - create_set_out: 낱개 OUT 과 함께 콤보 OUT 1행 기록(같은 트랜잭션) - 콤보 재고 = 최근 재고조사 콤보수량 − 재고조사일 이후 콤보 출고 - 재고현황 콤보 패널: 조사/출고/재고 3열 표시 - reset_all_data 에 set_movement 삭제 포함 배포: malaysia_stock_db_set_movement.sql 적용(테이블+GRANT) 후 코드 배포. Co-Authored-By: Claude Opus 4.8 --- app/modules/malaysia/db.py | 51 +++++++++++++++++-- .../malaysia/templates/malaysia/index.html | 13 +++-- scripts/sql/malaysia_stock_db_init.sql | 27 +++++++++- .../sql/malaysia_stock_db_set_movement.sql | 26 ++++++++++ 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 scripts/sql/malaysia_stock_db_set_movement.sql diff --git a/app/modules/malaysia/db.py b/app/modules/malaysia/db.py index 981563f..c6dd9a6 100644 --- a/app/modules/malaysia/db.py +++ b/app/modules/malaysia/db.py @@ -269,6 +269,25 @@ class MalaysiaStockStore: ), ).fetchone() results.append(self._serialize(row)) + # 콤보(세트) 단위 출고 원장 — 콤보 재고현황 차감용. + conn.execute( + """ + INSERT INTO set_movement + (movement_date, warehouse_code, set_code, movement_type, + qty, ref_type, ref_no, memo, created_by) + VALUES (%s,%s,%s,'OUT',%s,%s,%s,%s,%s) + """, + ( + movement_date.strip(), + warehouse_code.strip(), + sc, + q, + ref_t, + (ref_no or "").strip(), + (memo or f"{sc} {q}세트 출고").strip(), + (created_by or "").lower().strip(), + ), + ) return results def list_movements( @@ -507,10 +526,15 @@ class MalaysiaStockStore: raise KeyError(stocktake_id) def latest_set_quantities(self, *, warehouse_code: str) -> dict[str, Any]: - """가장 최근(취소 제외) 재고조사에 입력된 콤보(MY-) 수량. + """콤보(MY-) 단위 재고현황 — 우측 패널용. - 반환: {"stocktake_date": str|None, "rows": [{set_code,set_name,qty}]} - 재고현황 우측 패널용 — 세트 단위로 입력된 콤보 재고를 그대로 보여줌. + 콤보는 movement 테이블(낱개 전용)에 직접 안 들어가므로, 재고조사 입력을 + 기준점으로 set_movement(콤보 출고 원장)를 차감해 현재 콤보 재고를 낸다. + + 콤보재고 = 최근(취소 제외) 재고조사 콤보수량 − 재고조사일 이후 콤보 OUT 합 + + 반환: {"stocktake_date": str|None, + "rows": [{set_code, set_name, stocktake_qty, out_qty, qty}]} """ with self._pool.connection() as conn: head = conn.execute( @@ -527,6 +551,19 @@ class MalaysiaStockStore: "ORDER BY sku_code ASC", (head["id"],), ).fetchall() + # 재고조사일 이후의 콤보 출고(OUT)/조정(ADJUST) 합. 재고조사 시점 + # 물리 카운트가 기준이므로, 그 이후 출고만 차감한다. + out_rows = conn.execute( + """ + SELECT set_code, + SUM(CASE WHEN movement_type = 'OUT' THEN qty ELSE -qty END) AS out_qty + FROM set_movement + WHERE warehouse_code = %s AND movement_date > %s + GROUP BY set_code + """, + (warehouse_code, head["stocktake_date"]), + ).fetchall() + out_map = {r["set_code"]: int(r["out_qty"] or 0) for r in out_rows} names = self.item_name_map() d = head["stocktake_date"] date_str = d.isoformat() if isinstance(d, date) else str(d) @@ -534,7 +571,9 @@ class MalaysiaStockStore: { "set_code": r["sku_code"], "set_name": names.get(r["sku_code"], r["sku_code"]), - "qty": int(r["qty"]), + "stocktake_qty": int(r["qty"]), + "out_qty": out_map.get(r["sku_code"], 0), + "qty": int(r["qty"]) - out_map.get(r["sku_code"], 0), } for r in lines ] @@ -883,6 +922,7 @@ class MalaysiaStockStore: """전체 창고의 재고 수량 데이터 초기화. 삭제 대상: stock_movement(입고/출고/조정/조사반영) + + set_movement(콤보 출고 원장) + daily_stocktake/daily_stocktake_line(재고조사). 보존 대상: warehouses, malaysia_items(마스터). 한 트랜잭션으로 처리. 반환: 삭제 건수. @@ -890,9 +930,10 @@ class MalaysiaStockStore: with self._pool.connection() as conn: with conn.transaction(): mv = conn.execute("DELETE FROM stock_movement").rowcount or 0 + sm = conn.execute("DELETE FROM set_movement").rowcount or 0 ln = conn.execute("DELETE FROM daily_stocktake_line").rowcount or 0 hd = conn.execute("DELETE FROM daily_stocktake").rowcount or 0 - return {"movements": mv, "stocktake_lines": ln, "stocktakes": hd} + return {"movements": mv, "set_movements": sm, "stocktake_lines": ln, "stocktakes": hd} # ════════════════════════════════════════════════════════════ # 직렬화 diff --git a/app/modules/malaysia/templates/malaysia/index.html b/app/modules/malaysia/templates/malaysia/index.html index 0dcfc9c..1c7560e 100644 --- a/app/modules/malaysia/templates/malaysia/index.html +++ b/app/modules/malaysia/templates/malaysia/index.html @@ -59,21 +59,28 @@

콤보 재고 (세트 단위)

- {% if set_stocktake_date %}최근 재고조사 {{ set_stocktake_date }} 기준 입력 수량{% else %}재고조사 입력 없음{% endif %} + {% if set_stocktake_date %}콤보재고 = 재고조사({{ set_stocktake_date }}) − 이후 출고{% else %}재고조사 입력 없음{% endif %}
- + + + + + + {% for s in set_rows %} + + {% endfor %} {% if not set_rows %} - + {% endif %}
Set CodeSet NameQty
Set CodeSet Name조사출고재고
{{ s.set_code }} {{ s.set_name }}{{ "{:,}".format(s.stocktake_qty) }}{% if s.out_qty %}−{{ "{:,}".format(s.out_qty) }}{% else %}{% endif %} {{ "{:,}".format(s.qty) }}
콤보 재고 입력이 없습니다.
콤보 재고 입력이 없습니다.
diff --git a/scripts/sql/malaysia_stock_db_init.sql b/scripts/sql/malaysia_stock_db_init.sql index 0b6ad26..a5c58d8 100644 --- a/scripts/sql/malaysia_stock_db_init.sql +++ b/scripts/sql/malaysia_stock_db_init.sql @@ -165,6 +165,31 @@ CREATE INDEX IF NOT EXISTS idx_stock_mv_date ON stock_movement (movement_date CREATE INDEX IF NOT EXISTS idx_stock_mv_type ON stock_movement (movement_type); CREATE INDEX IF NOT EXISTS idx_stock_mv_ref ON stock_movement (ref_type, ref_no); +-- ════════════════════════════════════════════════════════════ +-- 4-b) 콤보(세트) 출고 원장 (세트 단위) +-- set_code 는 MY- 만. 세트 출고 시 stock_movement(낱개 분해)와 함께 +-- 여기에 콤보 단위 OUT 을 1행 기록한다(같은 트랜잭션). +-- 콤보 재고현황 = 최근 재고조사 콤보수량 − 재고조사일 이후 OUT 합. +-- 콤보 입고는 업무상 불가(낱개로만 입고) → OUT/ADJUST 만 허용. +-- ════════════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS set_movement ( + id BIGSERIAL PRIMARY KEY, + movement_date DATE NOT NULL, + warehouse_code TEXT NOT NULL REFERENCES warehouses(warehouse_code), + set_code TEXT NOT NULL CHECK (set_code LIKE 'MY-%'), + movement_type TEXT NOT NULL DEFAULT 'OUT' CHECK (movement_type IN ('OUT', 'ADJUST')), + qty INTEGER NOT NULL, + ref_type TEXT NOT NULL DEFAULT '', + ref_no TEXT NOT NULL DEFAULT '', + memo TEXT NOT NULL DEFAULT '', + created_by TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + -- OUT 은 양수만. ADJUST 만 음수 허용. + CHECK ((movement_type = 'OUT' AND qty > 0) OR movement_type = 'ADJUST') +); +CREATE INDEX IF NOT EXISTS idx_set_mv_wh_set ON set_movement (warehouse_code, set_code); +CREATE INDEX IF NOT EXISTS idx_set_mv_date ON set_movement (movement_date); + -- ════════════════════════════════════════════════════════════ -- 5) 일일 재고조사 (헤더) -- 같은 날짜 + 같은 창고에 finalized 는 1건만 허용(부분 유니크 인덱스). @@ -279,7 +304,7 @@ ON CONFLICT (item_code) DO NOTHING; -- ════════════════════════════════════════════════════════════ GRANT USAGE ON SCHEMA public TO malaysia_app; GRANT SELECT, INSERT, UPDATE, DELETE ON - warehouses, malaysia_items, set_bom, stock_movement, + warehouses, malaysia_items, set_bom, stock_movement, set_movement, daily_stocktake, daily_stocktake_line, daily_stocktake_rack TO malaysia_app; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO malaysia_app; diff --git a/scripts/sql/malaysia_stock_db_set_movement.sql b/scripts/sql/malaysia_stock_db_set_movement.sql new file mode 100644 index 0000000..4141681 --- /dev/null +++ b/scripts/sql/malaysia_stock_db_set_movement.sql @@ -0,0 +1,26 @@ +-- ════════════════════════════════════════════════════════════ +-- 마이그레이션: 콤보(세트) 출고 원장 set_movement 추가 +-- 기존 운영 malaysia_stock_db 에 적용(신규 설치는 init 에 이미 포함). +-- superuser/owner 로 실행. 멱등(IF NOT EXISTS). +-- ════════════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS set_movement ( + id BIGSERIAL PRIMARY KEY, + movement_date DATE NOT NULL, + warehouse_code TEXT NOT NULL REFERENCES warehouses(warehouse_code), + set_code TEXT NOT NULL CHECK (set_code LIKE 'MY-%'), + movement_type TEXT NOT NULL DEFAULT 'OUT' CHECK (movement_type IN ('OUT', 'ADJUST')), + qty INTEGER NOT NULL, + ref_type TEXT NOT NULL DEFAULT '', + ref_no TEXT NOT NULL DEFAULT '', + memo TEXT NOT NULL DEFAULT '', + created_by TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CHECK ((movement_type = 'OUT' AND qty > 0) OR movement_type = 'ADJUST') +); +CREATE INDEX IF NOT EXISTS idx_set_mv_wh_set ON set_movement (warehouse_code, set_code); +CREATE INDEX IF NOT EXISTS idx_set_mv_date ON set_movement (movement_date); + +GRANT SELECT, INSERT, UPDATE, DELETE ON set_movement TO malaysia_app; +GRANT USAGE, SELECT ON SEQUENCE set_movement_id_seq TO malaysia_app; + +SELECT 'set_movement ready' AS status;