- 개발/테스트 스크립트를 dev_scripts/ 폴더로 이동 - 스크린샷을 screenshots/ 폴더로 이동 - 백업 파일 보존 (.backup) - 처방 관련 추가 스크립트 포함 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
179 lines
6.7 KiB
Python
179 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
LOT이 생성되지 않은 입고 라인 확인
|
|
"""
|
|
|
|
import sqlite3
|
|
|
|
def check_missing_lots():
|
|
conn = sqlite3.connect('database/kdrug.db')
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
|
|
print("=" * 80)
|
|
print("LOT이 생성되지 않은 입고 라인 분석")
|
|
print("=" * 80)
|
|
print()
|
|
|
|
# 1. 전체 입고 라인과 LOT 매칭 상태
|
|
print("1. 입고 라인 - LOT 매칭 현황")
|
|
print("-" * 60)
|
|
|
|
cursor.execute("""
|
|
SELECT
|
|
COUNT(*) as total_lines,
|
|
SUM(CASE WHEN il.lot_id IS NOT NULL THEN 1 ELSE 0 END) as lines_with_lot,
|
|
SUM(CASE WHEN il.lot_id IS NULL THEN 1 ELSE 0 END) as lines_without_lot,
|
|
SUM(prl.line_total) as total_purchase_amount,
|
|
SUM(CASE WHEN il.lot_id IS NOT NULL THEN prl.line_total ELSE 0 END) as amount_with_lot,
|
|
SUM(CASE WHEN il.lot_id IS NULL THEN prl.line_total ELSE 0 END) as amount_without_lot
|
|
FROM purchase_receipt_lines prl
|
|
LEFT JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
|
""")
|
|
|
|
result = cursor.fetchone()
|
|
|
|
print(f" 총 입고 라인: {result['total_lines']}개")
|
|
print(f" ✅ LOT 생성됨: {result['lines_with_lot']}개 (₩{result['amount_with_lot']:,.0f})")
|
|
print(f" ❌ LOT 없음: {result['lines_without_lot']}개 (₩{result['amount_without_lot']:,.0f})")
|
|
print()
|
|
print(f" 총 입고 금액: ₩{result['total_purchase_amount']:,.0f}")
|
|
print(f" LOT 없는 금액: ₩{result['amount_without_lot']:,.0f}")
|
|
|
|
if result['amount_without_lot'] > 0:
|
|
print(f"\n ⚠️ LOT이 생성되지 않은 입고 금액이 ₩{result['amount_without_lot']:,.0f} 있습니다!")
|
|
print(" 이것이 DB 재고와 예상 재고 차이(₩55,500)의 원인일 가능성이 높습니다.")
|
|
|
|
# 2. LOT이 없는 입고 라인 상세
|
|
if result['lines_without_lot'] > 0:
|
|
print("\n2. LOT이 생성되지 않은 입고 라인 상세")
|
|
print("-" * 60)
|
|
|
|
cursor.execute("""
|
|
SELECT
|
|
pr.receipt_no,
|
|
pr.receipt_date,
|
|
h.herb_name,
|
|
prl.quantity_g,
|
|
prl.unit_price_per_g,
|
|
prl.line_total,
|
|
prl.lot_number,
|
|
prl.line_id
|
|
FROM purchase_receipt_lines prl
|
|
JOIN purchase_receipts pr ON prl.receipt_id = pr.receipt_id
|
|
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
|
LEFT JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
|
WHERE il.lot_id IS NULL
|
|
ORDER BY prl.line_total DESC
|
|
""")
|
|
|
|
missing_lots = cursor.fetchall()
|
|
|
|
total_missing_amount = 0
|
|
print("\n LOT이 생성되지 않은 입고 라인:")
|
|
for i, line in enumerate(missing_lots, 1):
|
|
print(f"\n {i}. {line['herb_name']}")
|
|
print(f" 입고장: {line['receipt_no']} ({line['receipt_date']})")
|
|
print(f" 수량: {line['quantity_g']:,.0f}g")
|
|
print(f" 단가: ₩{line['unit_price_per_g']:.2f}/g")
|
|
print(f" 금액: ₩{line['line_total']:,.0f}")
|
|
print(f" LOT번호: {line['lot_number'] or 'None'}")
|
|
print(f" Line ID: {line['line_id']}")
|
|
total_missing_amount += line['line_total']
|
|
|
|
print(f"\n 총 누락 금액: ₩{total_missing_amount:,.0f}")
|
|
|
|
# 3. 반대로 입고 라인 없는 LOT 확인
|
|
print("\n3. 입고 라인과 연결되지 않은 LOT")
|
|
print("-" * 60)
|
|
|
|
cursor.execute("""
|
|
SELECT
|
|
COUNT(*) as orphan_lots,
|
|
SUM(quantity_onhand * unit_price_per_g) as orphan_value,
|
|
SUM(quantity_onhand) as orphan_quantity
|
|
FROM inventory_lots
|
|
WHERE receipt_line_id IS NULL
|
|
AND is_depleted = 0
|
|
AND quantity_onhand > 0
|
|
""")
|
|
|
|
orphans = cursor.fetchone()
|
|
|
|
if orphans['orphan_lots'] > 0:
|
|
print(f" 입고 라인 없는 LOT: {orphans['orphan_lots']}개")
|
|
print(f" 해당 재고 가치: ₩{orphans['orphan_value']:,.0f}")
|
|
print(f" 해당 재고량: {orphans['orphan_quantity']:,.0f}g")
|
|
|
|
cursor.execute("""
|
|
SELECT
|
|
h.herb_name,
|
|
il.lot_number,
|
|
il.quantity_onhand,
|
|
il.unit_price_per_g,
|
|
il.quantity_onhand * il.unit_price_per_g as value,
|
|
il.received_date
|
|
FROM inventory_lots il
|
|
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
|
WHERE il.receipt_line_id IS NULL
|
|
AND il.is_depleted = 0
|
|
AND il.quantity_onhand > 0
|
|
ORDER BY value DESC
|
|
LIMIT 5
|
|
""")
|
|
|
|
orphan_lots = cursor.fetchall()
|
|
if orphan_lots:
|
|
print("\n 상위 5개 입고 라인 없는 LOT:")
|
|
for lot in orphan_lots:
|
|
print(f" - {lot['herb_name']} (LOT: {lot['lot_number']})")
|
|
print(f" 재고: {lot['quantity_onhand']:,.0f}g, 금액: ₩{lot['value']:,.0f}")
|
|
else:
|
|
print(" ✅ 모든 LOT이 입고 라인과 연결되어 있습니다.")
|
|
|
|
# 4. 금액 차이 분석
|
|
print("\n4. 금액 차이 최종 분석")
|
|
print("=" * 60)
|
|
|
|
# 현재 DB 재고
|
|
cursor.execute("""
|
|
SELECT SUM(quantity_onhand * unit_price_per_g) as total
|
|
FROM inventory_lots
|
|
WHERE is_depleted = 0 AND quantity_onhand > 0
|
|
""")
|
|
db_total = cursor.fetchone()['total'] or 0
|
|
|
|
# 총 입고 - 소비
|
|
cursor.execute("SELECT SUM(line_total) as total FROM purchase_receipt_lines")
|
|
total_in = cursor.fetchone()['total'] or 0
|
|
|
|
cursor.execute("""
|
|
SELECT SUM(cc.quantity_used * il.unit_price_per_g) as total
|
|
FROM compound_consumptions cc
|
|
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
|
""")
|
|
total_out = cursor.fetchone()['total'] or 0
|
|
|
|
expected = total_in - total_out
|
|
|
|
print(f" DB 재고 자산: ₩{db_total:,.0f}")
|
|
print(f" 예상 재고 (입고-소비): ₩{expected:,.0f}")
|
|
print(f" 차이: ₩{expected - db_total:,.0f}")
|
|
print()
|
|
|
|
if result['amount_without_lot'] > 0:
|
|
print(f" 💡 LOT 없는 입고 금액: ₩{result['amount_without_lot']:,.0f}")
|
|
adjusted_expected = (total_in - result['amount_without_lot']) - total_out
|
|
print(f" 📊 조정된 예상 재고: ₩{adjusted_expected:,.0f}")
|
|
print(f" 조정 후 차이: ₩{adjusted_expected - db_total:,.0f}")
|
|
|
|
if abs(adjusted_expected - db_total) < 1000:
|
|
print("\n ✅ LOT이 생성되지 않은 입고 라인을 제외하면 차이가 거의 없습니다!")
|
|
print(" 이것이 차이의 주요 원인입니다.")
|
|
|
|
conn.close()
|
|
|
|
if __name__ == "__main__":
|
|
check_missing_lots() |