gaoluyang
13 小时以前 e40a608ef9bf51db294d8eefada6a8e2e80cebca
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
"""Correct sales order grouping, without duplicating contracts, invoices or payments."""
import sys,json,copy
from pathlib import Path
from decimal import Decimal,ROUND_HALF_UP
P=Path(__file__).parent
sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps'))
import pymysql
D=Decimal
money=lambda v:D(v).quantize(D('.01'),rounding=ROUND_HALF_UP)
plan=json.loads((P/'import-plan.json').read_text(encoding='utf8'))
old=json.loads((P/'import-result.json').read_text(encoding='utf8'))
assert not (P/'source-order-regroup-result.json').exists(),'Already corrected; verify instead of rerunning.'
c=pymysql.connect(host='36.213.150.237',port=10000,user='root',password=sys.stdin.readline().strip(),database='mom-hm',charset='utf8mb4',autocommit=False,cursorclass=pymysql.cursors.DictCursor)
result={'groups':[],'orders':[],'rule':'相同原单据号一个订单;合同和合并开票保留;正合并金额内的退货原单独立保留;负合并金额两组仍排除'}
try:
 with c.cursor() as q:
  q.execute('SELECT GET_LOCK(%s,10) locked,NOW() now,DATABASE() db',('sales-ledger-202609',));state=q.fetchone();assert state['locked']==1 and state['db']=='mom-hm';now=state['now']
  backup={'state':state,'tables':{}}
  protected=['crm_sale_quotation','crm_sale_quotation_product','crm_contract_product','crm_invoice','crm_receivable','crm_receivable_plan','crm_permission','mdm_item','mes_md_item','crm_customer']
  for t in ['erp_sale_order','erp_sale_order_items','crm_contract']+protected:
   q.execute('SELECT * FROM `'+t+'` ORDER BY id');backup['tables'][t]=q.fetchall()
  (P/'source-order-regroup-before.json').write_text(json.dumps(backup,ensure_ascii=False,default=str,indent=2),encoding='utf8')
  orders={x['id']:x for x in backup['tables']['erp_sale_order']};items={x['id']:x for x in backup['tables']['erp_sale_order_items']};contracts={x['id']:x for x in backup['tables']['crm_contract']}
  original_ids={g['order_id'] for g in old['groups']};all_source=set()
  for g in plan['groups']:
   assert not (all_source & set(g['source_numbers'])),'A source number crosses invoice groups; needs global consolidation.'
   all_source.update(g['source_numbers'])
  conflicts=[x for x in orders.values() if x['no'] in all_source and x['id'] not in original_ids];assert not conflicts,[(x['id'],x['no']) for x in conflicts]
  for g,prior in zip(plan['groups'],old['groups']):
   original=orders[prior['order_id']];contract=contracts[prior['contract_id']]
   assert original['no']=='XSDD-'+g['key'] and original['total_price']==D(g['gross']) and original['out_count']==0 and original['production_status']==0
   assert contract['order_id']==original['id'] and contract['total_price']==D(g['gross'])
   for a,l in zip(g['lines'],prior['lines']):
    line=items[l['order_item_id']];assert line['order_id']==original['id'] and line['count']==D(a['quantity']) and line['total_price']==D(a['net']) and line['tax_price']==D(a['tax']) and line['out_count']==0 and not line['mps_id']
   buckets=[]
   for no in g['source_numbers']:
    ls=[(a,l) for a,l in zip(g['lines'],prior['lines']) if a['source_no']==no]
    buckets.append({'source_no':no,'lines':ls,'gross':money(sum(D(a['gross_raw']) for a,l in ls))})
   # Preserve the original invoice-group amount; document the one-cent allocation explicitly.
   delta=D(g['gross'])-sum(b['gross'] for b in buckets)
   recipient=next(b for b in reversed(buckets) if b['gross']>0);recipient['gross']+=delta
   changed=[]
   for i,b in enumerate(buckets):
    no=b['source_no'];net=sum(D(a['net']) for a,l in b['lines']);tax=sum(D(a['tax']) for a,l in b['lines']);count=sum(D(a['quantity']) for a,l in b['lines']);gross=b['gross']
    marker='退货原单,负数量负金额保留;' if gross<0 else ''
    remark=f'九月台账{g["key"]};原单据号{no};同号明细归同一订单;{marker}关联合并合同{contract["no"]},开票组含税合计{g["gross"]}元;本单净税额与含税金额尾差{net+tax-gross}元;合并组分配尾差{delta if b is recipient else 0}元。'
    fields={'no':no,'total_count':count,'total_price':gross,'total_product_price':net,'total_tax_price':tax,'discount_price':net+tax-gross,'remark':remark[:255],'updater':'291','update_time':now}
    if i==0:
     oid=original['id'];q.execute('UPDATE erp_sale_order SET '+','.join('`'+k+'`=%s' for k in fields)+' WHERE id=%s',[*fields.values(),oid])
    else:
     fields={**original,**fields};fields.pop('id');fields['creator']='291';fields['create_time']=now
     q.execute('INSERT INTO erp_sale_order ('+','.join('`'+k+'`' for k in fields)+') VALUES ('+','.join(['%s']*len(fields))+')',list(fields.values()));oid=q.lastrowid
     ids=[l['order_item_id'] for a,l in b['lines']]
     q.execute('UPDATE erp_sale_order_items SET order_id=%s,updater=%s,update_time=%s WHERE id IN ('+','.join(['%s']*len(ids))+')',[oid,'291',now,*ids])
    entry={'source_no':no,'order_id':oid,'contract_id':contract['id'],'customer_id':original['customer_id'],'gross':str(gross),'net':str(net),'tax':str(tax),'count':str(count),'order_item_ids':[l['order_item_id'] for a,l in b['lines']],'source_rows':[a['row'] for a,l in b['lines']]};changed.append(entry);result['orders'].append(entry)
   q.execute('UPDATE crm_contract SET order_no=%s,updater=%s,update_time=%s WHERE id=%s',(buckets[0]['source_no'],'291',now,contract['id']))
   result['groups'].append({'key':g['key'],'primary_order_id':original['id'],'contract_id':contract['id'],'orders':changed,'invoice_id':prior['invoice_id'],'receivable_id':prior['receivable_id'],'plan_id':prior['plan_id']})
  # Validate all balances and line partitioning before commit.
  assert len(result['orders'])==24 and len({o['source_no'] for o in result['orders']})==24
  seen=set();total=D(0)
  for o in result['orders']:
   q.execute('SELECT * FROM erp_sale_order WHERE id=%s',(o['order_id'],));actual=q.fetchone()
   assert actual['no']==o['source_no'] and actual['total_price']==D(o['gross']) and actual['total_product_price']+actual['total_tax_price']-actual['discount_price']==actual['total_price']
   q.execute('SELECT * FROM erp_sale_order_items WHERE order_id=%s AND deleted=0',(o['order_id'],));ls=q.fetchall();ids={x['id'] for x in ls};assert ids==set(o['order_item_ids']) and not ids & seen;seen|=ids
   assert sum(x['count'] for x in ls)==actual['total_count'] and sum(x['total_price'] for x in ls)==actual['total_product_price'] and sum(x['tax_price'] for x in ls)==actual['total_tax_price']
   total+=actual['total_price']
  assert len(seen)==76 and total==D(plan['totals']['gross'])
  for group,g in zip(result['groups'],plan['groups']):assert sum(D(x['gross']) for x in group['orders'])==D(g['gross'])
  for t in protected:
   q.execute('SELECT * FROM `'+t+'` ORDER BY id');assert q.fetchall()==backup['tables'][t],('Unexpected changes',t)
  c.commit();result.update(committed=True,at=str(now),order_count=24,line_count=76,total=str(total))
  (P/'source-order-regroup-result.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf8')
  print(json.dumps({'committed':True,'orders':24,'lines':76,'total':str(total),'split_orders':[o for o in result['orders'] if o['source_no'] in ['26000393','26000406','26000369']]},ensure_ascii=False))
except Exception:c.rollback();raise
finally:
 with c.cursor() as q:q.execute('SELECT RELEASE_LOCK(%s)',('sales-ledger-202609',))
 c.close()