feat: 동물약 바코드/APC 2줄 표시

- 대표바코드(CD_GOODS.BARCODE)만 표시 (없으면 '없음')
- APC: 02로 시작하는 단위바코드 별도 표시
- APC 없으면 'APC미지정' 빨간 점선 뱃지
- 동물약만 체크 시에만 2줄 표시 (일반약품은 1줄)
- 헤더: '바코드/APC'
This commit is contained in:
thug0bin 2026-03-04 15:55:04 +09:00
parent e95c08ef59
commit 6bb86f8780
2 changed files with 37 additions and 21 deletions

View File

@ -3335,27 +3335,28 @@ def api_products():
# 제품 검색 쿼리 - 사용약품만 옵션에 따라 JOIN 방식 변경
if in_stock_only:
# 최적화된 쿼리: 재고 있는 제품만 (IM_total INNER JOIN)
# 대표바코드(G.BARCODE) 없으면 단위바코드(CD_ITEM_UNIT_MEMBER) 사용
# 대표바코드만 표시 (없으면 빈값), APC는 02로 시작하는 것만
products_query = text(f"""
SELECT TOP {limit}
G.DrugCode as drug_code,
G.GoodsName as product_name,
COALESCE(NULLIF(G.BARCODE, ''), U.CD_CD_BARCODE, '') as barcode,
ISNULL(NULLIF(G.BARCODE, ''), '') as barcode,
G.Saleprice as sale_price,
G.Price as cost_price,
ISNULL(G.SplName, '') as supplier,
0 as is_set,
G.POS_BOON as pos_boon,
IT.IM_QT_sale_debit as stock,
ISNULL(POS.CD_NM_sale, '') as location
ISNULL(POS.CD_NM_sale, '') as location,
APC.CD_CD_BARCODE as apc_code
FROM CD_GOODS G
INNER JOIN IM_total IT ON G.DrugCode = IT.DrugCode AND IT.IM_QT_sale_debit > 0
LEFT JOIN CD_item_position POS ON G.DrugCode = POS.DrugCode
OUTER APPLY (
SELECT TOP 1 CD_CD_BARCODE
FROM CD_ITEM_UNIT_MEMBER
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
) U
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
) APC
WHERE 1=1
{animal_condition}
{search_condition}
@ -3369,22 +3370,23 @@ def api_products():
SELECT TOP {limit}
G.DrugCode as drug_code,
G.GoodsName as product_name,
COALESCE(NULLIF(G.BARCODE, ''), U.CD_CD_BARCODE, '') as barcode,
ISNULL(NULLIF(G.BARCODE, ''), '') as barcode,
G.Saleprice as sale_price,
G.Price as cost_price,
ISNULL(G.SplName, '') as supplier,
0 as is_set,
G.POS_BOON as pos_boon,
ISNULL(IT.IM_QT_sale_debit, 0) as stock,
ISNULL(POS.CD_NM_sale, '') as location
ISNULL(POS.CD_NM_sale, '') as location,
APC.CD_CD_BARCODE as apc_code
FROM CD_GOODS G
LEFT JOIN IM_total IT ON G.DrugCode = IT.DrugCode
LEFT JOIN CD_item_position POS ON G.DrugCode = POS.DrugCode
OUTER APPLY (
SELECT TOP 1 CD_CD_BARCODE
FROM CD_ITEM_UNIT_MEMBER
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
) U
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
) APC
WHERE G.POS_BOON = '010103'
{search_condition}
ORDER BY G.GoodsName
@ -3394,7 +3396,7 @@ def api_products():
SELECT TOP {limit}
G.DrugCode as drug_code,
G.GoodsName as product_name,
COALESCE(NULLIF(G.BARCODE, ''), U.CD_CD_BARCODE, '') as barcode,
ISNULL(NULLIF(G.BARCODE, ''), '') as barcode,
G.Saleprice as sale_price,
G.Price as cost_price,
CASE
@ -3405,20 +3407,21 @@ def api_products():
CASE WHEN SET_CHK.is_set = 1 THEN 1 ELSE 0 END as is_set,
G.POS_BOON as pos_boon,
ISNULL(IT.IM_QT_sale_debit, 0) as stock,
ISNULL(POS.CD_NM_sale, '') as location
ISNULL(POS.CD_NM_sale, '') as location,
APC.CD_CD_BARCODE as apc_code
FROM CD_GOODS G
LEFT JOIN IM_total IT ON G.DrugCode = IT.DrugCode
LEFT JOIN CD_item_position POS ON G.DrugCode = POS.DrugCode
OUTER APPLY (
SELECT TOP 1 CD_CD_BARCODE
FROM CD_ITEM_UNIT_MEMBER
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
) U
OUTER APPLY (
SELECT TOP 1 1 as is_set
FROM CD_item_set
WHERE SetCode = G.DrugCode AND DrugCode = 'SET0000'
) SET_CHK
OUTER APPLY (
SELECT TOP 1 CD_CD_BARCODE
FROM CD_ITEM_UNIT_MEMBER
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
) APC
WHERE 1=1
{search_condition}
ORDER BY G.GoodsName
@ -3452,6 +3455,9 @@ def api_products():
except:
pass
# APC 코드: 쿼리에서 조회한 것 또는 기존 로직에서 찾은 것
apc_code = getattr(row, 'apc_code', None) or apc
items.append({
'drug_code': row.drug_code or '',
'product_name': row.product_name or '',
@ -3463,7 +3469,7 @@ def api_products():
'is_animal_drug': is_animal,
'stock': int(row.stock) if row.stock else 0,
'location': row.location or '', # 위치
'apc': apc,
'apc': apc_code, # APC 코드 (02로 시작)
'category': None, # PostgreSQL에서 lazy fetch
'wholesaler_stock': None,
'thumbnail': None # 아래에서 채움

View File

@ -393,6 +393,15 @@
background: #d1fae5;
color: #065f46;
}
.code-apc {
background: #ede9fe;
color: #7c3aed;
}
.code-apc-na {
background: #fef2f2;
color: #dc2626;
border: 1px dashed #fca5a5;
}
.code-na {
background: #f1f5f9;
color: #94a3b8;
@ -883,7 +892,7 @@
<th style="width:50px;">이미지</th>
<th>상품명</th>
<th>상품코드</th>
<th>바코드</th>
<th>바코드/APC</th>
<th>위치</th>
<th>재고</th>
<th>판매가</th>
@ -1043,9 +1052,10 @@
<div class="product-supplier ${item.is_set ? 'set' : ''}">${escapeHtml(item.supplier) || ''}</div>
</td>
<td><span class="code code-drug">${item.drug_code}</span></td>
<td>${item.barcode
? `<span class="code code-barcode">${item.barcode}</span>`
: `<span class="code code-na">없음</span>`}</td>
<td>${item.is_animal_drug
? `<div>${item.barcode ? `<span class="code code-barcode">${item.barcode}</span>` : `<span class="code code-na">없음</span>`}</div>
<div style="margin-top:4px;">${item.apc ? `<span class="code code-apc">${item.apc}</span>` : `<span class="code code-apc-na">APC미지정</span>`}</div>`
: (item.barcode ? `<span class="code code-barcode">${item.barcode}</span>` : `<span class="code code-na">없음</span>`)}</td>
<td>${item.location
? `<span class="location-badge" onclick="openLocationModal('${item.drug_code}', '${escapeHtml(item.product_name)}', '${escapeHtml(item.location)}')">${escapeHtml(item.location)}</span>`
: `<span class="location-badge unset" onclick="openLocationModal('${item.drug_code}', '${escapeHtml(item.product_name)}', '')">미지정</span>`}</td>