import json,re,csv from pathlib import Path from decimal import Decimal,ROUND_HALF_UP from datetime import datetime,timedelta P=Path(__file__).parent source=json.loads((P/'ledger-extracted.json').read_text(encoding='utf8')) db=json.loads((P/'before-ledger-database.json').read_text(encoding='utf8')) D=lambda v:Decimal(str(v or 0)) money=lambda v:D(v).quantize(Decimal('.01'),rounding=ROUND_HALF_UP) six=lambda v:D(v).quantize(Decimal('.000001'),rounding=ROUND_HALF_UP) sheet=source['sheets']['26.9月'];rows={r['row']:r['values'][:19] for r in sheet['rows'] if 4<=r['row']<=81} groups=[] for start,v in rows.items(): if v[14] is None:continue stop=start for merged in sheet['merged_ranges']: m=re.fullmatch(r'O(\d+):O(\d+)',merged) if m and int(m[1])==start:stop=int(m[2]) groups.append((start,stop)) assert sorted(r for a,b in groups for r in range(a,b+1))==list(rows) channels={str(r['values'][0]):r['values'][1] for r in source['sheets']['渠道分类']['rows'][1:] if r['values'][0] is not None} codes={str(i['code']):i for i in db['mdm_item']['rows']} byid={i['id']:i for i in db['mdm_item']['rows']} aliases={'22102180':964,'12100271':538,'12100273':539,'QZ000524':1522,'22102181':1001,'22102185':1001,'12100593':554} newitems={'QZ000515':{'name':'鲜冬虫夏草','specification':'2A级 (0.6-0.8克/根) *10根','unit_measure_id':8,'category_id':byid[1521]['category_id'],'item_type':2}, 'UA000009':{'name':'贝母粉','specification':'50克','unit_measure_id':31,'category_id':byid[1090]['category_id'],'item_type':2}, 'U2100796':{'name':'黑果枸杞','specification':'50克','unit_measure_id':31,'category_id':byid[1544]['category_id'],'item_type':2}} units={u['code']:u['id'] for u in db['mes_md_unit_measure']['rows']} customers={x['name']:x for x in db['crm_customer']['rows']} plan={'source':source['source'],'sha256':source['sha256'],'sheet':'26.9月','groups':[],'excluded':[],'new_customers':{},'new_items':newitems,'aliases':aliases,'rules':{'currency':'按合并金额四舍五入至分;原金额及尾差另存','dates':'有票按开票日补录反推业务日期;无票按备注日期;非原合同签约日期','plans':'仅未收余额,开票/销售日后30天测算,非约定账期','receivables':'用户确认到账日同开票日;无票电商按备注销售日;无发票附件不强制绑定invoice_id'}} for start,stop in groups: v=rows[start];gross=money(v[14]);original=';'.join(str(vv) for vv in v[17:19] if vv) if gross<0: plan['excluded'].append({'rows':[start,stop],'gross':str(gross),'original':original,'reason':'用户明确合并金额负数不录入'});continue addr=str(v[3]);name=channels.get(addr) if addr=='399':name='399零号店' elif start in [69,72,73,74,77,78]: name={69:'海南万茗堂科技有限公司',72:'广州北纬纯净食品有限公司',73:'广州北纬纯净食品有限公司',74:'广州北纬纯净食品有限公司',77:'青海南玻新能源科技有限公司',78:'青海爱嘉星际文化旅游有限公'}[start] elif not name:name='电商' # Preserve the truncated name as written; do not invent a registered full name. if name not in customers:plan['new_customers'][name]={'source_address':addr,'remark':f'九月销售台账补录;来源客户号{addr};原备注:{original}'[:500]} invoice=v[15]=='是';date_text=str(v[16]) if invoice: parts=date_text.split('.');day=datetime(2000+int(parts[0]),int(parts[1]),int(parts[2])) else: m=re.search(r'(2026)\.(\d+)\.(\d+)',original);assert m,(start,original) day=datetime(*map(int,m.groups())) nums=re.findall(r'(?0 assert sum(D(a['contract_total']) for a in lines)==gross plan['groups'].append(record) plan['totals']={k:str(sum(D(g[k]) for g in plan['groups'])) for k in ['net','tax','gross','received','outstanding','rounding_adjustment']} plan['counts']={'sales':len(plan['groups']),'lines':sum(len(g['lines']) for g in plan['groups']),'invoices':sum(g['invoice'] for g in plan['groups']),'receivables':sum(D(g['received'])>0 for g in plan['groups']),'plans':sum(D(g['outstanding'])>0 for g in plan['groups']),'physical_invoice_numbers':sum(len(g['invoice_numbers']) for g in plan['groups']),'new_customers':len(plan['new_customers']),'new_items':len(newitems)} (P/'import-plan.json').write_text(json.dumps(plan,ensure_ascii=False,indent=2),encoding='utf8') with (P/'九月销售导入核对清单.csv').open('w',encoding='utf-8-sig',newline='') as f: w=csv.writer(f);w.writerow(['行号','客户','原单号','合并金额原值','系统含税金额','净额','税额','尾差调整','发票号','回款','计划余额','计划测算日']) for g in plan['groups']:w.writerow([str(g['rows']),g['customer_name'],'/'.join(g['source_numbers']),g['gross_raw'],g['gross'],g['net'],g['tax'],g['rounding_adjustment'],'/'.join(g['invoice_numbers']),g['received'],g['outstanding'],g['plan_day'] if D(g['outstanding'])>0 else '']) print(json.dumps({'counts':plan['counts'],'totals':plan['totals'],'new_customers':list(plan['new_customers']),'excluded':plan['excluded']},ensure_ascii=False,indent=2))