- 개발/테스트 스크립트를 dev_scripts/ 폴더로 이동 - 스크린샷을 screenshots/ 폴더로 이동 - 백업 파일 보존 (.backup) - 처방 관련 추가 스크립트 포함 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
193 lines
6.6 KiB
Python
193 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
재고 자산 금액 불일치 최종 분석
|
|
"""
|
|
|
|
import sqlite3
|
|
from datetime import datetime
|
|
|
|
def final_analysis():
|
|
conn = sqlite3.connect('database/kdrug.db')
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
|
|
print("=" * 80)
|
|
print("재고 자산 금액 불일치 최종 분석")
|
|
print("분석 시간:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
|
print("=" * 80)
|
|
print()
|
|
|
|
# 1. 현재 DB의 실제 재고 자산
|
|
print("📊 현재 데이터베이스 상태")
|
|
print("-" * 60)
|
|
|
|
cursor.execute("""
|
|
SELECT
|
|
SUM(quantity_onhand * unit_price_per_g) as total_value,
|
|
COUNT(*) as lot_count,
|
|
SUM(quantity_onhand) as total_quantity
|
|
FROM inventory_lots
|
|
WHERE is_depleted = 0 AND quantity_onhand > 0
|
|
""")
|
|
|
|
current = cursor.fetchone()
|
|
db_total = current['total_value'] or 0
|
|
|
|
print(f" DB 재고 자산: ₩{db_total:,.0f}")
|
|
print(f" 활성 LOT: {current['lot_count']}개")
|
|
print(f" 총 재고량: {current['total_quantity']:,.1f}g")
|
|
print()
|
|
|
|
# 2. 입고와 출고 분석
|
|
print("💼 입고/출고 분석")
|
|
print("-" * 60)
|
|
|
|
# 입고 총액
|
|
cursor.execute("""
|
|
SELECT SUM(line_total) as total_in
|
|
FROM purchase_receipt_lines
|
|
""")
|
|
total_in = cursor.fetchone()['total_in'] or 0
|
|
|
|
# 복합제 소비 금액
|
|
cursor.execute("""
|
|
SELECT SUM(cc.quantity_used * il.unit_price_per_g) as total_out
|
|
FROM compound_consumptions cc
|
|
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
|
""")
|
|
total_out = cursor.fetchone()['total_out'] or 0
|
|
|
|
print(f" 총 입고 금액: ₩{total_in:,.0f}")
|
|
print(f" 총 소비 금액: ₩{total_out:,.0f}")
|
|
print(f" 예상 잔액: ₩{total_in - total_out:,.0f}")
|
|
print()
|
|
|
|
# 3. 차이 분석
|
|
print("🔍 차이 분석 결과")
|
|
print("=" * 60)
|
|
print()
|
|
|
|
ui_value = 5875708 # 화면에 표시되는 금액
|
|
expected_value = total_in - total_out
|
|
|
|
print(f" 화면 표시 금액: ₩{ui_value:,.0f}")
|
|
print(f" DB 계산 금액: ₩{db_total:,.0f}")
|
|
print(f" 예상 금액 (입고-소비): ₩{expected_value:,.0f}")
|
|
print()
|
|
|
|
print(" 차이:")
|
|
print(f" 화면 vs DB: ₩{ui_value - db_total:,.0f}")
|
|
print(f" 화면 vs 예상: ₩{ui_value - expected_value:,.0f}")
|
|
print(f" DB vs 예상: ₩{db_total - expected_value:,.0f}")
|
|
print()
|
|
|
|
# 4. 가능한 원인 분석
|
|
print("❗ 불일치 원인 분석")
|
|
print("-" * 60)
|
|
|
|
# 4-1. 단가 차이 확인
|
|
cursor.execute("""
|
|
SELECT
|
|
prl.line_id,
|
|
h.herb_name,
|
|
prl.quantity_g as purchase_qty,
|
|
prl.unit_price_per_g as purchase_price,
|
|
prl.line_total as purchase_total,
|
|
il.quantity_onhand as current_qty,
|
|
il.unit_price_per_g as lot_price,
|
|
il.quantity_onhand * il.unit_price_per_g as current_value,
|
|
ABS(prl.unit_price_per_g - il.unit_price_per_g) as price_diff
|
|
FROM purchase_receipt_lines prl
|
|
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
|
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
|
WHERE il.is_depleted = 0 AND il.quantity_onhand > 0
|
|
AND ABS(prl.unit_price_per_g - il.unit_price_per_g) > 0.01
|
|
ORDER BY price_diff DESC
|
|
LIMIT 5
|
|
""")
|
|
|
|
price_diffs = cursor.fetchall()
|
|
if price_diffs:
|
|
print("\n ⚠️ 입고 단가와 LOT 단가가 다른 항목:")
|
|
for pd in price_diffs:
|
|
print(f" {pd['herb_name']}:")
|
|
print(f" 입고 단가: ₩{pd['purchase_price']:.2f}/g")
|
|
print(f" LOT 단가: ₩{pd['lot_price']:.2f}/g")
|
|
print(f" 차이: ₩{pd['price_diff']:.2f}/g")
|
|
|
|
# 4-2. 소비 후 남은 재고 확인
|
|
cursor.execute("""
|
|
SELECT
|
|
h.herb_name,
|
|
il.lot_number,
|
|
il.quantity_received as original_qty,
|
|
il.quantity_onhand as current_qty,
|
|
il.quantity_received - il.quantity_onhand as consumed_qty,
|
|
il.unit_price_per_g
|
|
FROM inventory_lots il
|
|
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
|
WHERE il.is_depleted = 0
|
|
AND il.quantity_received > il.quantity_onhand
|
|
ORDER BY (il.quantity_received - il.quantity_onhand) DESC
|
|
LIMIT 5
|
|
""")
|
|
|
|
consumed_lots = cursor.fetchall()
|
|
if consumed_lots:
|
|
print("\n 📉 소비된 재고가 있는 LOT (상위 5개):")
|
|
for cl in consumed_lots:
|
|
print(f" {cl['herb_name']} (LOT: {cl['lot_number']})")
|
|
print(f" 원래: {cl['original_qty']:,.0f}g → 현재: {cl['current_qty']:,.0f}g")
|
|
print(f" 소비: {cl['consumed_qty']:,.0f}g (₩{cl['consumed_qty'] * cl['unit_price_per_g']:,.0f})")
|
|
|
|
# 4-3. JavaScript 계산 로직 확인 필요
|
|
print("\n 💡 추가 확인 필요사항:")
|
|
print(" 1) 프론트엔드 JavaScript에서 재고 자산을 계산하는 로직")
|
|
print(" 2) 캐시 또는 세션 스토리지에 저장된 이전 값")
|
|
print(" 3) inventory_lots_v2 테이블 사용 여부")
|
|
|
|
# inventory_lots_v2 확인
|
|
cursor.execute("""
|
|
SELECT
|
|
SUM(quantity_onhand * unit_price_per_g) as v2_total,
|
|
COUNT(*) as v2_count
|
|
FROM inventory_lots_v2
|
|
WHERE is_depleted = 0 AND quantity_onhand > 0
|
|
""")
|
|
|
|
v2_result = cursor.fetchone()
|
|
if v2_result and v2_result['v2_count'] > 0:
|
|
v2_total = v2_result['v2_total'] or 0
|
|
print(f"\n ⚠️ inventory_lots_v2 테이블 데이터:")
|
|
print(f" 재고 자산: ₩{v2_total:,.0f}")
|
|
print(f" LOT 수: {v2_result['v2_count']}개")
|
|
|
|
if abs(v2_total - ui_value) < 100:
|
|
print(f" → 화면 금액과 일치할 가능성 높음!")
|
|
|
|
print()
|
|
|
|
# 5. 결론
|
|
print("📝 결론")
|
|
print("=" * 60)
|
|
|
|
diff = ui_value - db_total
|
|
if diff > 0:
|
|
print(f" 화면에 표시되는 금액(₩{ui_value:,.0f})이")
|
|
print(f" 실제 DB 금액(₩{db_total:,.0f})보다")
|
|
print(f" ₩{diff:,.0f} 더 많습니다.")
|
|
print()
|
|
print(" 가능한 원인:")
|
|
print(" 1) 프론트엔드에서 별도의 계산 로직 사용")
|
|
print(" 2) 캐시된 이전 데이터 표시")
|
|
print(" 3) inventory_lots_v2 테이블 참조")
|
|
print(" 4) 재고 보정 내역이 즉시 반영되지 않음")
|
|
else:
|
|
print(f" 실제 DB 금액이 화면 표시 금액보다 적습니다.")
|
|
|
|
conn.close()
|
|
|
|
if __name__ == "__main__":
|
|
final_analysis() |