129 lines
5.3 KiB
Python
129 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
||
from __future__ import annotations
|
||
import csv, re, zipfile
|
||
from pathlib import Path
|
||
from reportlab.pdfgen import canvas
|
||
from reportlab.lib.pagesizes import A4
|
||
from reportlab.lib import colors
|
||
from reportlab.pdfbase import pdfmetrics
|
||
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
|
||
import json
|
||
|
||
ROOT=Path('/root/work/pharmit-ghidra')
|
||
JSONP=ROOT/'out/a4_receipt_template_candidates/quickreport_deep/PM_MAIN.EXE.a4receipt.05.02812c03.현금영수증 문의.bin.qr.objects.json'
|
||
RAW=ROOT/'out/a4_receipt_three_raw.txt'
|
||
OUT=ROOT/'out/a4_receipt_quickreport_render_samples'
|
||
OUT.mkdir(parents=True, exist_ok=True)
|
||
SCALE=72/96
|
||
FONT='HYGothic-Medium'
|
||
try: pdfmetrics.registerFont(UnicodeCIDFont(FONT))
|
||
except Exception: FONT='Helvetica'
|
||
|
||
# Actual SQL fields seen in a4_receipt_three_raw.txt / decomp SQL
|
||
FIELD_MAP={
|
||
'Q_paname':'PANAME','Q_InDate':'_DATE_FMT','Q_Days':'_DAYS','Q_PreSerial':'PRESERIAL',
|
||
'Q_Price_A':'TOT_C','Q_Price_p':'BON_C','Q_Price_c':'CHUNG_C','Q_s_FastMon':'BI_PRICE','Q_Price_n':'_PAY_TOTAL',
|
||
'Q_CardPrice':'CARD_C','Q_CashPrint':'PAPER_C','Q_Chah':'CHASH_C','Q_PersonNum':'PANUM',
|
||
'Q_Year':'_YEAR','Q_Month':'_MONTH','Q_day':'_DAY',
|
||
'Q_name':'_PHARM_NAME','Q_comcode':'_BIZNO','Q_add':'_PHARM_ADDR','Q_comname':'_PHARMACIST',
|
||
'qrlApproval_Num':'_CASH_APPROVAL','Q_Bag':'_BAG',
|
||
}
|
||
MONEY={'TOT_C','BON_C','CHUNG_C','BI_PRICE','_PAY_TOTAL','CARD_C','PAPER_C','CHASH_C'}
|
||
|
||
def money(v):
|
||
try: return f"{int(float(str(v).replace(',','') or '0')):,}"
|
||
except Exception: return str(v or '')
|
||
|
||
def read_sql_rows():
|
||
lines=RAW.read_text(encoding='utf-8',errors='replace').splitlines()
|
||
data=[ln for ln in lines if '|' in ln and not set(ln.replace('|','').strip()) <= {'-'}]
|
||
rows=[]
|
||
for r in csv.DictReader(data, delimiter='|'):
|
||
if r.get('CUSCODE') and not r['CUSCODE'].startswith('-'):
|
||
rows.append({k:(v or '').strip() for k,v in r.items()})
|
||
return rows
|
||
|
||
def enrich(r):
|
||
r=dict(r)
|
||
s=re.sub(r'\D','',r.get('INDATE',''))
|
||
if len(s)>=8:
|
||
r['_YEAR'],r['_MONTH'],r['_DAY']=s[:4],s[4:6],s[6:8]
|
||
r['_DATE_FMT']=f'{s[:4]}-{s[4:6]}-{s[6:8]}'
|
||
else:
|
||
r['_YEAR']=r['_MONTH']=r['_DAY']=''; r['_DATE_FMT']=r.get('INDATE','')
|
||
pay=sum(int(float(r.get(k) or 0)) for k in ['CARD_C','CHASH_C','PAPER_C'])
|
||
if pay==0:
|
||
pay=int(float(r.get('BON_C') or 0)); r['CHASH_C']=str(pay)
|
||
r['_PAY_TOTAL']=str(pay); r['_DAYS']=''; r['_BAG']=''
|
||
if '<EFBFBD>' in r.get('PANAME','') or not r.get('PANAME'): r['PANAME']='홍길동'
|
||
panum=re.sub(r'\D','',r.get('PANUM',''))
|
||
r['PANUM']=(panum[:6]+'-'+panum[6:7]+'******') if len(panum)>=7 else '050930-3******'
|
||
r['_PHARM_NAME']='샘플약국'; r['_BIZNO']='123-45-67890'; r['_PHARM_ADDR']='서울특별시 샘플구 약국로 1'; r['_PHARMACIST']='김약사'; r['_CASH_APPROVAL']=''
|
||
return r
|
||
|
||
def load_objects():
|
||
j=json.loads(JSONP.read_text(encoding='utf-8'))
|
||
q=[o for o in j['objects'] if o.get('class','').startswith('TQR')]
|
||
# First PM_MAIN 약제비 계산서·영수증 form: q indices 19..103 from object inspection.
|
||
objs=q[19:104]
|
||
return objs
|
||
|
||
def prop(o,k,default=''):
|
||
return o.get('props',{}).get(k,default)
|
||
|
||
def value(r,o):
|
||
name=o.get('name',''); cap=str(prop(o,'Caption','') or '')
|
||
fld=FIELD_MAP.get(name)
|
||
if fld:
|
||
v=r.get(fld,'')
|
||
return money(v) if fld in MONEY else str(v)
|
||
# hide design placeholders if equal to object name
|
||
if name.startswith('Q_') and cap==name: return ''
|
||
return cap
|
||
|
||
def draw_shape(c,o,H):
|
||
try:
|
||
x=float(prop(o,'Left',0))*SCALE; top=float(prop(o,'Top',0))*SCALE; w=float(prop(o,'Width',0))*SCALE; h=float(prop(o,'Height',0))*SCALE
|
||
except Exception: return
|
||
if w<=0 or h<=0: return
|
||
y=H-top-h
|
||
c.setStrokeColor(colors.HexColor('#333333')); c.setLineWidth(0.45)
|
||
if w<=1.6 or h<=1.6: c.line(x,y,x+w,y+h)
|
||
else: c.rect(x,y,w,h,stroke=1,fill=0)
|
||
|
||
def draw_text(c,r,o,H):
|
||
txt=value(r,o)
|
||
if not txt: return
|
||
try:
|
||
x=float(prop(o,'Left',0))*SCALE; top=float(prop(o,'Top',0))*SCALE; w=float(prop(o,'Width',0))*SCALE; h=float(prop(o,'Height',0))*SCALE
|
||
except Exception: return
|
||
y=H-top-h
|
||
fh=max(5.5,min(abs(float(prop(o,'Font.Height',-13)))*0.72,16))
|
||
c.setFont(FONT,fh); c.setFillColor(colors.black)
|
||
fld=FIELD_MAP.get(o.get('name',''))
|
||
if fld in MONEY:
|
||
c.drawRightString(x+w-2,y+max(1,(h-fh)/2)+1,txt)
|
||
else:
|
||
c.drawString(x+1,y+max(1,(h-fh)/2)+1,txt)
|
||
|
||
def render(row, idx, objs):
|
||
r=enrich(row)
|
||
out=OUT/f'a4_pm_main_receipt_sql_{idx}_{r["PRESERIAL"]}.pdf'
|
||
c=canvas.Canvas(str(out),pagesize=A4); W,H=A4
|
||
for o in objs:
|
||
if o.get('class')=='TQRShape': draw_shape(c,o,H)
|
||
for o in objs:
|
||
if o.get('class') in {'TQRLabel','TQRDBText'}: draw_text(c,r,o,H)
|
||
c.setFont(FONT,6); c.setFillColor(colors.HexColor('#777777'))
|
||
c.drawString(4,4,f'PM_MAIN QuickReport receipt mapped / CUSCODE={r.get("CUSCODE")} PRESERIAL={r.get("PRESERIAL")}')
|
||
c.save(); return out
|
||
|
||
def main():
|
||
rows=read_sql_rows(); objs=load_objects(); outs=[]
|
||
for i,r in enumerate(rows[:3],1): outs.append(render(r,i,objs))
|
||
z=ROOT/'out/a4_pm_main_receipt_sql_mapped_20260628.zip'
|
||
with zipfile.ZipFile(z,'w',zipfile.ZIP_DEFLATED) as zz:
|
||
for p in outs+[Path(__file__),JSONP,RAW]: zz.write(p,p.relative_to(ROOT))
|
||
print('\n'.join(map(str,outs))); print(z); print(z.stat().st_size)
|
||
if __name__=='__main__': main()
|