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

View File

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