diff --git a/backend/gui/pos_sales_gui.py b/backend/gui/pos_sales_gui.py index ef34a27..65f262c 100644 --- a/backend/gui/pos_sales_gui.py +++ b/backend/gui/pos_sales_gui.py @@ -76,7 +76,9 @@ class SalesQueryThread(QThread): M.SL_MY_sale, ISNULL(M.SL_NM_custom, '[비고객]') AS customer_name, ISNULL(S.card_total, 0) AS card_total, - ISNULL(S.cash_total, 0) AS cash_total + ISNULL(S.cash_total, 0) AS cash_total, + ISNULL(M.SL_MY_total, 0) AS total_amount, + ISNULL(M.SL_MY_discount, 0) AS discount FROM SALE_MAIN M OUTER APPLY ( SELECT TOP 1 @@ -94,7 +96,7 @@ class SalesQueryThread(QThread): sales_list = [] for row in rows: - order_no, insert_time, sale_amount, customer, card_total, cash_total = row + order_no, insert_time, sale_amount, customer, card_total, cash_total, total_amount, discount = row # 품목 수 조회 (SALE_SUB) mssql_cursor.execute(""" @@ -144,10 +146,15 @@ class SalesQueryThread(QThread): pay_method = '' paid = (card_amt + cash_amt) > 0 + disc_amt = float(discount) if discount else 0.0 + total_amt = float(total_amount) if total_amount else 0.0 + sales_list.append({ 'order_no': order_no, 'time': insert_time.strftime('%H:%M') if insert_time else '--:--', 'amount': float(sale_amount) if sale_amount else 0.0, + 'discount': disc_amt, + 'total_before_dc': total_amt, 'customer': customer, 'pay_method': pay_method, 'paid': paid, @@ -833,8 +840,20 @@ class POSSalesGUI(QMainWindow): self.sales_table.setItem(row, COL['time'], QTableWidgetItem(sale['time'])) - # 금액 (우측 정렬, 천단위 콤마) - amount_item = QTableWidgetItem(f"{sale['amount']:,.0f}원") + # 금액 (우측 정렬, 천단위 콤마, 할인 표시) + if sale['discount'] > 0: + amount_item = QTableWidgetItem(f"{sale['amount']:,.0f}원 (-{sale['discount']:,.0f})") + amount_item.setForeground(QColor('#E65100')) + f = QFont() + f.setBold(True) + amount_item.setFont(f) + amount_item.setToolTip( + f"원가: {sale['total_before_dc']:,.0f}원\n" + f"할인: -{sale['discount']:,.0f}원\n" + f"결제: {sale['amount']:,.0f}원" + ) + else: + amount_item = QTableWidgetItem(f"{sale['amount']:,.0f}원") amount_item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) self.sales_table.setItem(row, COL['amount'], amount_item) @@ -937,12 +956,14 @@ class POSSalesGUI(QMainWindow): def on_cell_clicked(self, row, column): """테이블 셀 클릭 이벤트 - 적립 사용자 클릭 시 마일리지 내역 표시""" - # 컬럼 5(적립자명), 6(전화번호), 7(적립포인트) 중 하나를 클릭했는지 확인 - if column not in [5, 6, 7]: + # SALES_COLUMNS 기반 인덱스 사용 + COL = {key: i for i, (_, _, key) in enumerate(self.SALES_COLUMNS)} + mileage_cols = [COL['claimed_name'], COL['claimed_phone'], COL['claimed_points']] + if column not in mileage_cols: return - # 전화번호 가져오기 (6번 컬럼) - phone_item = self.sales_table.item(row, 6) + # 전화번호 가져오기 + phone_item = self.sales_table.item(row, COL['claimed_phone']) if not phone_item or not phone_item.text(): # 적립 사용자가 없는 경우 return diff --git a/docs/결제수납구조.md b/docs/결제수납구조.md new file mode 100644 index 0000000..85ada02 --- /dev/null +++ b/docs/결제수납구조.md @@ -0,0 +1,147 @@ +# PIT3000 결제/수납/할인 데이터 구조 + +## 핵심 테이블 관계 + +``` +SALE_MAIN (판매) + └── SL_NO_order (PK, 주문번호) + │ + ├── SALE_SUB (품목 상세) — SL_NO_order로 조인 + │ + └── CD_SUNAB (수납/결제) — CD_SUNAB.PRESERIAL = SALE_MAIN.SL_NO_order +``` + +**주의**: `CD_SUNAB.PRESERIAL`은 `SALE_MAIN.SL_NO_order`(주문번호)와 매칭됨. +`SALE_MAIN.PRESERIAL`(처방번호)과는 다른 키임. + +--- + +## SALE_MAIN 금액 컬럼 + +| 컬럼 | 설명 | 예시 | +|------|------|------| +| `SL_MY_total` | 원가 (할인 전) | 60,000 | +| `SL_MY_discount` | 할인 금액 | 6,000 | +| `SL_MY_sale` | **실판매가** (= total - discount) | 54,000 | +| `SL_MY_recive` | 수납금액 (부가세 제외 추정) | 49,091 | +| `SL_MY_credit` | 외상 금액 | 0 | +| `SL_MY_dis_ratio` | 할인율 | 0 (미사용) | + +### 금액 관계 +``` +SL_MY_sale = SL_MY_total - SL_MY_discount +SL_MY_recive ≈ SL_MY_sale / 1.1 (부가세 제외 금액 추정) +``` + +### 할인 빈도 +- 대부분의 거래: discount = 0 (할인 없음) +- 할인 적용 건: 하루 2~5건 정도 (직원 할인, 대량 구매 등) +- 할인 규모: 1,000원 ~ 수십만원까지 다양 + +--- + +## CD_SUNAB 결제수단 컬럼 + +### 금액 기반 결제수단 구분 +단일 구분 컬럼이 없음. **금액이 0보다 크면 해당 결제수단 사용**. + +| 구분 | 카드 | 현금 | 외상 | +|------|------|------|------| +| 조제(ETC, 전문의약품) | `ETC_CARD` | `ETC_CASH` | `ETC_PAPER` | +| OTC(일반의약품) | `OTC_CARD` | `OTC_CASH` | `OTC_PAPER` | + +### 결제수단 판별 로직 +```python +card_total = ETC_CARD + OTC_CARD +cash_total = ETC_CASH + OTC_CASH + +if card_total > 0 and cash_total > 0: + 결제수단 = "카드+현금" +elif card_total > 0: + 결제수단 = "카드" +elif cash_total > 0: + 결제수단 = "현금" +else: + 결제수단 = "-" (미수납 또는 외상) +``` + +### 카드 상세 정보 +| 컬럼 | 설명 | 예시 | +|------|------|------| +| `pMCHDATA` | 카드사 이름 | 비씨카드사, NH농협카드 | +| `PCardName` | 카드사 이름 (별도) | KB국민카드 | +| `pAPPROVAL_NUM` | 카드 승인번호 | 72139919 | +| `pCARDINMODE` | 카드 입력 방식 | 1 (IC칩) | +| `pTRDTYPE` | 거래 유형 | D1 (일반승인) | +| `Appr_Gubun` | 승인 구분 | 9 (정상승인) | +| `pCANCEL_NUM` | 취소 승인번호 | (취소 시) | + +### 현금 상세 정보 +| 컬럼 | 설명 | 예시 | +|------|------|------| +| `nCASHINMODE` | 현금영수증 입력 방식 | 1, 2 (빈값=미발행) | +| `nAPPROVAL_NUM` | 현금영수증 승인번호 | | +| `nCHK_GUBUN` | 현금 체크 구분 | TASA | + +--- + +## GUI 표시 방식 + +### 결제 컬럼 +- **카드**: 파란색 (#1976D2) +- **현금**: 주황색 (#E65100) +- **카드+현금**: 보라색 (#7B1FA2) +- **-**: 회색 (수납 정보 없음) + +### 수납 컬럼 +- **✓**: 녹색 (card + cash > 0) +- **-**: 회색 (미수납) + +### 할인 표시 +- 할인 없는 건: `12,000원` (기본) +- 할인 있는 건: `54,000원 (-6,000)` 주황색 볼드 +- 마우스 툴팁: 원가 / 할인 / 결제 상세 + +--- + +## SQL 쿼리 (GUI에서 사용) + +```sql +SELECT + M.SL_NO_order, + M.InsertTime, + M.SL_MY_sale, + ISNULL(M.SL_NM_custom, '[비고객]') AS customer_name, + ISNULL(S.card_total, 0) AS card_total, + ISNULL(S.cash_total, 0) AS cash_total, + ISNULL(M.SL_MY_total, 0) AS total_amount, + ISNULL(M.SL_MY_discount, 0) AS discount +FROM SALE_MAIN M +OUTER APPLY ( + SELECT TOP 1 + ISNULL(ETC_CARD, 0) + ISNULL(OTC_CARD, 0) AS card_total, + ISNULL(ETC_CASH, 0) + ISNULL(OTC_CASH, 0) AS cash_total + FROM CD_SUNAB + WHERE PRESERIAL = M.SL_NO_order +) S +WHERE M.SL_DT_appl = ? +ORDER BY M.InsertTime DESC +``` + +--- + +## 카드사 분포 (전체 데이터 기준) + +| 카드사 | 건수 | +|--------|------| +| KB국민카드 | 6,106 | +| NH농협카드 | 5,172 | +| 비씨카드사 | 4,900 | +| 하나카드 | 4,880 | +| 신한카드 | 3,210 | +| 삼성카드사 | 2,100 | +| 현대카드사 | 1,960 | +| 우리카드 | 1,285 | +| 롯데카드사 | 837 | +| 카카오페이 | 57 | +| 모바일상품권 | 11 |