import json,collections,xml.etree.ElementTree as ET,sys from pathlib import Path from decimal import Decimal P=Path('scripts/quotation-import') target=sys.argv[1] if len(sys.argv)>1 else 'local' assert target in ['local','remote'] plan=json.loads((P/f'{target}-plan.json').read_text(encoding='utf-8')) source=json.loads((P/'source.json').read_text(encoding='utf-8')) r=ET.parse(P/f'{target}-post-snapshot.xml') def records(tag):return [{e.tag:e.text or '' for e in x} for x in r.findall('./'+tag+'/row')] customers={x['id']:x for x in records('customers')} materials={x['id']:x for x in records('materials')} units={x['id']:x for x in records('units')} users={x['username']:x for x in records('users')} owner=users['zhuli'];assert owner['nickname'].strip()=='朱莉' and owner['status']=='0' headers={x['no']:x for x in records('quotations') if x['deleted']=='0'} lines=collections.defaultdict(list) for x in records('lines'): if x['deleted']=='0':lines[x['quotation_id']].append(x) permissions={(x['biz_type'],x['biz_id'],x['user_id'],x['level']) for x in records('permissions') if x['deleted']=='0'} assert len(headers)==345 and sum(len(v) for v in lines.values())==1702 assert len({h['customer_id'] for h in headers.values()})==344 original={q['no']:q for q in source['quotes']} total=Decimal(0) for q in plan['quotes']: h=headers[q['no']];s=original[q['no']] assert h['owner_user_id']==owner['id'] and h['status']=='0' assert customers[h['customer_id']]['name']==q['customer'] assert customers[h['customer_id']]['owner_user_id']==owner['id'] assert h['quotation_time']==q['quotation_time'] and h['valid_until']==q['valid_until'] assert Decimal(h['total_product_price'])==Decimal(q['total'])==Decimal(h['total_price']) assert h['tax_rate']==h['tax_price']=='' assert Decimal(h['discount_percent'])==Decimal(h['discount_price'])==0 assert h['business_id']==h['contact_id']=='' assert h['remark']==q['remark'] assert ('9',h['id'],owner['id'],'1') in permissions assert ('2',h['customer_id'],owner['id'],'1') in permissions items={materials[x['item_id']]['code']:x for x in lines[h['id']]} assert len(items)==len(q['items'])==len(s['items']) for x,sx in zip(q['items'],s['items']): d=items[x['item_code']] assert d['item_unit_id']==str(x['item_unit_id']) assert units[d['item_unit_id']]['name'].strip()==sx['unit'] assert d['remark']==x['remark'] and d['tax_percent']==d['tax_price']=='' for stored,field in [('item_price','original_price'),('quotation_price','price'),('count','count'),('total_price','total')]: assert Decimal(d[stored])==Decimal(sx[field]),(q['no'],x['line'],stored) total+=Decimal(h['total_price']) assert total==Decimal('15392805.71') for product in plan['new_products']: matches=[m for m in materials.values() if m['code']==product['code']] assert len(matches)==1 and matches[0]['specification']==product['specification'] and matches[0]['item_type']=='2' assert source['excluded_sheets']==['报价单汇总总表','编制说明'] result=f'PASS {target}: 345 quotations, 1702 lines, 344 customers, total {total}; exact source prices/quantities/amounts, dates, units, owner zhuli, draft status and scoped ownership permissions verified. Summary excluded.\n' (P/f'{target}-verification-result.txt').write_text(result,encoding='utf-8');print(result)