feat: 단위바코드 갯수 뱃지 + QR 바코드 우선순위 수정
화면 표시: - 대표바코드 옆 빨간 뱃지로 단위바코드 갯수 표시 (카톡 스타일) - APC 없어도 단위바코드 있으면 POS 판매 가능함을 표시 QR 인쇄 우선순위: 1. 대표바코드 (있으면) 2. 단위바코드 첫 번째 (대표 없으면) 3. drug_code (fallback) 쿼리 추가: - UNIT_FIRST: 단위바코드 첫 번째 (조건 없이) - UNIT_CNT: 단위바코드 갯수
This commit is contained in:
parent
1c2bfd473b
commit
77c667e1f6
@ -3335,7 +3335,7 @@ def api_products():
|
|||||||
# 제품 검색 쿼리 - 사용약품만 옵션에 따라 JOIN 방식 변경
|
# 제품 검색 쿼리 - 사용약품만 옵션에 따라 JOIN 방식 변경
|
||||||
if in_stock_only:
|
if in_stock_only:
|
||||||
# 최적화된 쿼리: 재고 있는 제품만 (IM_total INNER JOIN)
|
# 최적화된 쿼리: 재고 있는 제품만 (IM_total INNER JOIN)
|
||||||
# 대표바코드만 표시 (없으면 빈값), APC는 02로 시작하는 것만
|
# 대표바코드만 표시, 단위바코드 첫번째/갯수, 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,
|
||||||
@ -3348,7 +3348,9 @@ def api_products():
|
|||||||
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
|
APC.CD_CD_BARCODE as apc_code,
|
||||||
|
UNIT_FIRST.CD_CD_BARCODE as unit_barcode,
|
||||||
|
ISNULL(UNIT_CNT.cnt, 0) as unit_barcode_count
|
||||||
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
|
||||||
@ -3357,6 +3359,16 @@ def api_products():
|
|||||||
FROM CD_ITEM_UNIT_MEMBER
|
FROM CD_ITEM_UNIT_MEMBER
|
||||||
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
|
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
|
||||||
) APC
|
) APC
|
||||||
|
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 != ''
|
||||||
|
) UNIT_FIRST
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT COUNT(*) as cnt
|
||||||
|
FROM CD_ITEM_UNIT_MEMBER
|
||||||
|
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
|
||||||
|
) UNIT_CNT
|
||||||
WHERE 1=1
|
WHERE 1=1
|
||||||
{animal_condition}
|
{animal_condition}
|
||||||
{search_condition}
|
{search_condition}
|
||||||
@ -3378,7 +3390,9 @@ def api_products():
|
|||||||
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
|
APC.CD_CD_BARCODE as apc_code,
|
||||||
|
UNIT_FIRST.CD_CD_BARCODE as unit_barcode,
|
||||||
|
ISNULL(UNIT_CNT.cnt, 0) as unit_barcode_count
|
||||||
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
|
||||||
@ -3387,6 +3401,16 @@ def api_products():
|
|||||||
FROM CD_ITEM_UNIT_MEMBER
|
FROM CD_ITEM_UNIT_MEMBER
|
||||||
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
|
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
|
||||||
) APC
|
) APC
|
||||||
|
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 != ''
|
||||||
|
) UNIT_FIRST
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT COUNT(*) as cnt
|
||||||
|
FROM CD_ITEM_UNIT_MEMBER
|
||||||
|
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
|
||||||
|
) UNIT_CNT
|
||||||
WHERE G.POS_BOON = '010103'
|
WHERE G.POS_BOON = '010103'
|
||||||
{search_condition}
|
{search_condition}
|
||||||
ORDER BY G.GoodsName
|
ORDER BY G.GoodsName
|
||||||
@ -3408,7 +3432,9 @@ def api_products():
|
|||||||
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
|
APC.CD_CD_BARCODE as apc_code,
|
||||||
|
UNIT_FIRST.CD_CD_BARCODE as unit_barcode,
|
||||||
|
ISNULL(UNIT_CNT.cnt, 0) as unit_barcode_count
|
||||||
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
|
||||||
@ -3422,6 +3448,16 @@ def api_products():
|
|||||||
FROM CD_ITEM_UNIT_MEMBER
|
FROM CD_ITEM_UNIT_MEMBER
|
||||||
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
|
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE LIKE '02%'
|
||||||
) APC
|
) APC
|
||||||
|
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 != ''
|
||||||
|
) UNIT_FIRST
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT COUNT(*) as cnt
|
||||||
|
FROM CD_ITEM_UNIT_MEMBER
|
||||||
|
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
|
||||||
|
) UNIT_CNT
|
||||||
WHERE 1=1
|
WHERE 1=1
|
||||||
{search_condition}
|
{search_condition}
|
||||||
ORDER BY G.GoodsName
|
ORDER BY G.GoodsName
|
||||||
@ -3456,8 +3492,12 @@ def api_products():
|
|||||||
# APC 코드: 쿼리에서 02%로 조회한 것만 사용 (바코드 대체 X)
|
# APC 코드: 쿼리에서 02%로 조회한 것만 사용 (바코드 대체 X)
|
||||||
apc_code = getattr(row, 'apc_code', None) or ''
|
apc_code = getattr(row, 'apc_code', None) or ''
|
||||||
|
|
||||||
# PostgreSQL 조회용 APC (분류/도매재고): apc 또는 apc_code 사용
|
# 단위바코드 (첫 번째, 갯수)
|
||||||
pg_apc = apc or apc_code
|
unit_barcode = getattr(row, 'unit_barcode', None) or ''
|
||||||
|
unit_barcode_count = getattr(row, 'unit_barcode_count', 0) or 0
|
||||||
|
|
||||||
|
# PostgreSQL 조회용 APC (분류/도매재고): apc 또는 apc_code 또는 unit_barcode
|
||||||
|
pg_apc = apc or apc_code or unit_barcode
|
||||||
|
|
||||||
items.append({
|
items.append({
|
||||||
'drug_code': row.drug_code or '',
|
'drug_code': row.drug_code or '',
|
||||||
@ -3471,6 +3511,8 @@ def api_products():
|
|||||||
'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_code, # UI용 APC 코드 (02로 시작하는 것만)
|
'apc': apc_code, # UI용 APC 코드 (02로 시작하는 것만)
|
||||||
|
'unit_barcode': unit_barcode, # 단위바코드 첫 번째 (QR용)
|
||||||
|
'unit_barcode_count': int(unit_barcode_count), # 단위바코드 갯수 (뱃지용)
|
||||||
'_pg_apc': pg_apc, # PostgreSQL 조회용 (내부용)
|
'_pg_apc': pg_apc, # PostgreSQL 조회용 (내부용)
|
||||||
'category': None, # PostgreSQL에서 lazy fetch
|
'category': None, # PostgreSQL에서 lazy fetch
|
||||||
'wholesaler_stock': None,
|
'wholesaler_stock': None,
|
||||||
|
|||||||
@ -402,6 +402,21 @@
|
|||||||
color: #dc2626;
|
color: #dc2626;
|
||||||
border: 1px dashed #fca5a5;
|
border: 1px dashed #fca5a5;
|
||||||
}
|
}
|
||||||
|
.unit-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 5px;
|
||||||
|
margin-left: 4px;
|
||||||
|
background: #ef4444;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 9px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
.code-na {
|
.code-na {
|
||||||
background: #f1f5f9;
|
background: #f1f5f9;
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
@ -1053,7 +1068,9 @@
|
|||||||
</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.is_animal_drug
|
<td>${item.is_animal_drug
|
||||||
? `<div>${item.barcode ? `<span class="code code-barcode">${item.barcode}</span>` : `<span class="code code-na">없음</span>`}</div>
|
? `<div>${item.barcode
|
||||||
|
? `<span class="code code-barcode">${item.barcode}</span>`
|
||||||
|
: `<span class="code code-na">없음</span>`}${item.unit_barcode_count > 0 ? `<span class="unit-badge" title="단위바코드 ${item.unit_barcode_count}개">${item.unit_barcode_count}</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>`
|
<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>
|
: (item.barcode ? `<span class="code code-barcode">${item.barcode}</span>` : `<span class="code code-na">없음</span>`)}</td>
|
||||||
<td>${item.location
|
<td>${item.location
|
||||||
@ -1089,11 +1106,14 @@
|
|||||||
const preview = document.getElementById('qrPreview');
|
const preview = document.getElementById('qrPreview');
|
||||||
const info = document.getElementById('qrInfo');
|
const info = document.getElementById('qrInfo');
|
||||||
|
|
||||||
|
// QR에 들어갈 바코드 우선순위: 대표바코드 > 단위바코드 > 제품코드
|
||||||
|
const qrBarcode = selectedItem.barcode || selectedItem.unit_barcode || selectedItem.drug_code || '';
|
||||||
|
|
||||||
preview.innerHTML = '<p style="color:#64748b;">미리보기 로딩 중...</p>';
|
preview.innerHTML = '<p style="color:#64748b;">미리보기 로딩 중...</p>';
|
||||||
info.innerHTML = `
|
info.innerHTML = `
|
||||||
<strong>${escapeHtml(selectedItem.product_name)}</strong><br>
|
<strong>${escapeHtml(selectedItem.product_name)}</strong><br>
|
||||||
<span style="color:#64748b;font-size:13px;">
|
<span style="color:#64748b;font-size:13px;">
|
||||||
바코드: ${selectedItem.barcode || selectedItem.drug_code || 'N/A'}<br>
|
바코드: ${qrBarcode || 'N/A'}<br>
|
||||||
가격: ${formatPrice(selectedItem.sale_price)}
|
가격: ${formatPrice(selectedItem.sale_price)}
|
||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
@ -1105,7 +1125,7 @@
|
|||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
drug_name: selectedItem.product_name,
|
drug_name: selectedItem.product_name,
|
||||||
barcode: selectedItem.barcode || '',
|
barcode: qrBarcode,
|
||||||
drug_code: selectedItem.drug_code || '',
|
drug_code: selectedItem.drug_code || '',
|
||||||
sale_price: selectedItem.sale_price || 0
|
sale_price: selectedItem.sale_price || 0
|
||||||
})
|
})
|
||||||
@ -1143,12 +1163,15 @@
|
|||||||
btn.textContent = `인쇄 중... (${i + 1}/${totalQty})`;
|
btn.textContent = `인쇄 중... (${i + 1}/${totalQty})`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// QR에 들어갈 바코드 우선순위: 대표바코드 > 단위바코드 > 제품코드
|
||||||
|
const qrBarcode = selectedItem.barcode || selectedItem.unit_barcode || selectedItem.drug_code || '';
|
||||||
|
|
||||||
const res = await fetch('/api/qr-print', {
|
const res = await fetch('/api/qr-print', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
drug_name: selectedItem.product_name,
|
drug_name: selectedItem.product_name,
|
||||||
barcode: selectedItem.barcode || '',
|
barcode: qrBarcode,
|
||||||
drug_code: selectedItem.drug_code || '',
|
drug_code: selectedItem.drug_code || '',
|
||||||
sale_price: selectedItem.sale_price || 0
|
sale_price: selectedItem.sale_price || 0
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user