Files
shanxia2.0/backend/tests/test_pl_quota_supplement.py
T

194 lines
14 KiB
Python

from datetime import date
from decimal import Decimal as D
import pytest
from test_pl_quota_usage import case, run, request, _second_pair
def test_supplement_preserves_original_order_and_identity(case):
c=case; _second_pair(c)
sources=request()['sources']; sources[0]['quantity']='30'
run(c,sources=sources,purchase_cert_ids=['p','p2'],transport_cert_ids=['t','t2'])
ids={i.id for i in c.db.query(c.m.PlPlanCertUsageItem).all()}
for model in (c.m.PlPurchaseCert,c.m.PlTransportCert): c.db.get(model,'p' if model==c.m.PlPurchaseCert else 't').total_qty=100
c.db.flush()
for index,(actual,expected) in enumerate([('35',[25,10]),('22',[20,2]),('36',[26,10])]):
kw=dict(operation_id='s'+str(index),action='settle' if index==0 else 'actual_adjust',sources=sources,allow_supplement=True,actual_by_source={'load:sp':actual},reason='更正',source_note='磅单')
result=run(c,**kw)
assert run(c,**kw)==result
assert [c.db.get(c.m.PlPurchaseCert,p).used_qty for p in ['p','p2']]==expected
assert {i.id for i in c.db.query(c.m.PlPlanCertUsageItem).all()}==ids
assert sorted(i.allocated_source_qty for i in c.db.query(c.m.PlPlanCertUsageItem).all())==[10,10,20,20]
def test_expired_original_new_supplement_and_rollback(case):
c=case; _second_pair(c)
c.db.get(c.m.PlPurchaseCert,'p').total_qty=100; c.db.get(c.m.PlTransportCert,'t').total_qty=100; c.db.flush()
run(c)
c.db.get(c.m.PlPurchaseCert,'p').valid_to=date(2000,1,1); c.db.get(c.m.PlTransportCert,'t').valid_to=date(2000,1,1); c.db.flush()
kw=dict(reason='超量补证',source_note='磅单',operation_id='supp',action='settle',allow_supplement=True,purchase_cert_ids=['p2'],transport_cert_ids=['t2'],actual_by_source={'load:sp':'35'})
c.db.get(c.m.PlTransportCert,'t2').total_qty=5; c.db.flush()
with pytest.raises(ValueError): run(c,**kw)
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty==29
assert c.db.query(c.m.PlPlanCertUsage).count()==2
c.db.get(c.m.PlTransportCert,'t2').total_qty=100; c.db.flush()
run(c,**kw)
assert c.db.get(c.m.PlPurchaseCert,'p').used_qty==29
assert c.db.get(c.m.PlPurchaseCert,'p2').used_qty==6
run(c,operation_id='down',action='actual_adjust',actual_by_source={'load:sp':'18'},reason='更正',source_note='磅单')
assert c.db.get(c.m.PlPurchaseCert,'p2').used_qty==0
@pytest.mark.parametrize('failure',['expiry','vehicle','scope','opening','once','tenant','reference','reason','evidence'])
def test_supplement_current_qualification_is_atomic(case,failure):
c=case; _second_pair(c)
c.db.get(c.m.PlPurchaseCert,'p').total_qty=100; c.db.get(c.m.PlTransportCert,'t').total_qty=100; c.db.flush()
run(c)
kw=dict(operation_id='supp',action='settle',allow_supplement=True,purchase_cert_ids=['p2'],transport_cert_ids=['t2'],actual_by_source={'load:sp':'35'},reason='补量',source_note='磅单')
if failure=='expiry': c.db.get(c.m.PlPurchaseCert,'p2').valid_to=date(2000,1,1)
if failure=='vehicle': kw['vehicle_no']='wrong'
if failure=='scope': c.db.get(c.m.PlContractCertProduct,'sp2').scope_status='inactive'
if failure=='opening': c.db.get(c.m.PlCertQtyLog,'openingp2').del_flag=1
if failure=='once':
c.db.get(c.m.PlPurchaseCert,'p2').valid_use_text='一次有效'; c.db.get(c.m.PlPurchaseCert,'p2').opening_single_use_consumed=1
if failure=='tenant': c.db.get(c.m.PlPurchaseCert,'p2').tenant_no='foreign'
if failure=='reference': c.db.get(c.m.PlTransportCertPurchaseRef,'ref2').purchase_cert_id='p'
if failure=='reason': kw['reason']=''
if failure=='evidence': kw['source_note']=''
c.db.flush()
from fastapi import HTTPException
with pytest.raises((ValueError,HTTPException)): run(c,**kw)
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty==29
assert c.db.get(c.m.PlPurchaseCert,'p').used_qty==0
assert c.db.query(c.m.PlCertOperation).filter_by(operation_id='supp').count()==0
assert c.db.query(c.m.PlPlanCertUsage).count()==2
def test_supplement_foreign_unit_requires_fresh_manual_and_keeps_audit(case):
c=case; c.db.get(c.m.PlTransportCert,'t').quantity_unit='kg'; c.db.flush()
def manual(sq,pq):
return [dict(permit_kind='transport',source_id='load:sp',permit_id='t',source_quantity=sq,permit_quantity=pq,note='人工核实')]
run(c,manual_allocations=manual('29','58'))
kw=dict(operation_id='supp',action='settle',allow_supplement=True,actual_by_source={'load:sp':'35'},reason='补量',source_note='磅单',manual_actuals=manual('29','55'))
with pytest.raises(ValueError): run(c,**kw)
result=run(c,**kw,manual_allocations=manual('6','10'))
assert result['supplementCerts']==[{'certType':'purchase','certId':'p'},{'certType':'transport','certId':'t'}]
assert c.db.get(c.m.PlTransportCert,'t').used_qty==65
log=c.db.query(c.m.PlCertQtyLog).filter_by(operation_id='supp',cert_type='transport').one()
assert log.event_snapshot['supplement']['extraActualBySource']=={'load:sp':'6'}
assert log.event_snapshot['supplement']['allocations'][0]['permitQuantity']=='10'
with pytest.raises(ValueError):
run(c,operation_id='again',action='actual_adjust',allow_supplement=True,actual_by_source={'load:sp':'36'},reason='更正',source_note='磅单')
assert c.db.get(c.m.PlTransportCert,'t').used_qty==65
def test_supplement_does_not_borrow_other_source_reservation(case):
c=case
first=request()['sources'][0]; first['quantity']='20'
second={**first,'source_stop_product_id':'sp2','quantity':'10'}
c.db.get(c.m.PlPurchaseCert,'p').total_qty=30; c.db.get(c.m.PlTransportCert,'t').total_qty=30; c.db.flush()
run(c,sources=[first,second])
# 来源二未使用的10不能扩成来源一的原始容量,必须先经过当前证件资格校验。
c.db.get(c.m.PlPurchaseCert,'p').valid_to=date(2000,1,1); c.db.flush()
with pytest.raises(ValueError,match='有效日期'):
run(c,operation_id='supp',action='settle',sources=[first,second],allow_supplement=True,actual_by_source={'load:sp':'25','load:sp2':'0'},reason='补量',source_note='磅单')
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty==30
def test_once_supplement_same_plan_keeps_single_qualification(case):
c=case; c.db.get(c.m.PlPurchaseCert,'p').valid_use_text='一次有效'; c.db.flush()
run(c)
run(c,operation_id='supp',action='settle',allow_supplement=True,actual_by_source={'load:sp':'35'},reason='补量',source_note='磅单')
assert c.db.get(c.m.PlPurchaseCert,'p').used_qty==35
with pytest.raises(ValueError,match='一次有效'): run(c,operation_id='other',plan_id='other')
def test_permit_only_manual_excess_requires_current_validation(case):
c=case; c.db.get(c.m.PlTransportCert,'t').quantity_unit='kg'; c.db.flush()
def manual(q): return [dict(permit_kind='transport',source_id='load:sp',permit_id='t',source_quantity='29',permit_quantity=q,note='重新称重')]
run(c,manual_allocations=manual('58'))
kw=dict(operation_id='permit-extra',action='settle',allow_supplement=True,actual_by_source={'load:sp':'29'},manual_actuals=manual('65'),purchase_cert_ids=[],transport_cert_ids=['t'],reason='重新核实',source_note='凭证')
# 原购证本次无新增额度,历史过期仍可结转原量。
c.db.get(c.m.PlPurchaseCert,'p').valid_to=date(2000,1,1); c.db.flush()
result=run(c,**kw)
assert c.db.get(c.m.PlTransportCert,'t').used_qty==65
assert c.db.get(c.m.PlPurchaseCert,'p').used_qty==29
assert result['supplementCerts']==[{'certType':'transport','certId':'t'}]
item=c.db.query(c.m.PlPlanCertUsageItem).filter_by(source_stop_product_id='sp',source_unit='吨').all()
assert sorted(i.planned_cert_qty for i in item)==[29,58]
log=c.db.query(c.m.PlCertQtyLog).filter_by(operation_id='permit-extra',cert_type='transport').one()
assert log.event_snapshot['supplement']['permitOnlyAllocations'][0]['permitQuantity']=='7'
c.db.get(c.m.PlTransportCert,'t').valid_to=date(2000,1,1); c.db.flush()
with pytest.raises(ValueError,match='有效日期'):
run(c,**{**kw,'operation_id':'fail','action':'actual_adjust','manual_actuals':manual('66')})
assert c.db.get(c.m.PlTransportCert,'t').used_qty==65
def test_explicit_foreign_unit_split_can_supplement_new_cert_without_source_growth(case):
c=case; _second_pair(c)
c.db.get(c.m.PlPurchaseCert,'p').total_qty=100
for pid in ('t','t2'): c.db.get(c.m.PlTransportCert,pid).quantity_unit='kg'; c.db.get(c.m.PlTransportCert,pid).total_qty=100
c.db.flush()
def manual(pid,sq,pq): return dict(permit_kind='transport',source_id='load:sp',permit_id=pid,source_quantity=sq,permit_quantity=pq,note='明确人工拆分')
run(c,manual_allocations=[manual('t','29','58')])
run(c,operation_id='split',action='settle',allow_supplement=True,purchase_cert_ids=['p2'],transport_cert_ids=['t2'],actual_by_source={'load:sp':'29'},manual_actuals=[manual('t','24','48')],manual_allocations=[manual('t2','5','11')],reason='更正分配',source_note='凭证')
assert [c.db.get(c.m.PlPurchaseCert,pid).used_qty for pid in ('p','p2')]==[24,5]
assert [c.db.get(c.m.PlTransportCert,pid).used_qty for pid in ('t','t2')]==[48,11]
assert sum(i.actual_allocated_source_qty for i in c.db.query(c.m.PlPlanCertUsageItem).all())==58
@pytest.mark.parametrize('failure',['not_selected','capacity','scope','no_opt_in'])
def test_permit_only_excess_failures_leave_original_reservation(case,failure):
c=case; c.db.get(c.m.PlTransportCert,'t').quantity_unit='kg'; c.db.flush()
def manual(q): return [dict(permit_kind='transport',source_id='load:sp',permit_id='t',source_quantity='29',permit_quantity=q,note='人工核实')]
run(c,manual_allocations=manual('58'))
kw=dict(operation_id='extra',action='settle',allow_supplement=True,actual_by_source={'load:sp':'29'},manual_actuals=manual('65'),reason='超量',source_note='凭证')
if failure=='not_selected': kw['transport_cert_ids']=[]
if failure=='capacity': c.db.get(c.m.PlTransportCert,'t').total_qty=60
if failure=='scope': c.db.get(c.m.PlContractCertProduct,'st').scope_status='inactive'
if failure=='no_opt_in': kw['allow_supplement']=False
c.db.flush()
with pytest.raises(ValueError): run(c,**kw)
assert c.db.get(c.m.PlTransportCert,'t').reserved_qty==58
assert c.db.get(c.m.PlTransportCert,'t').used_qty==0
def test_same_unit_manual_supplement_cannot_bypass_original_order(case):
c=case; _second_pair(c)
sources=request()['sources']; sources[0]['quantity']='30'
run(c,sources=sources,purchase_cert_ids=['p','p2'],transport_cert_ids=['t','t2'])
manual=[dict(permit_kind=kind,source_id='load:sp',permit_id=pid,source_quantity='18',permit_quantity='18',note='试图重新安排') for kind,pid in [('purchase','p2'),('transport','t2')]]
with pytest.raises(ValueError,match='异单位'):
run(c,operation_id='invalid-split',action='settle',sources=sources,allow_supplement=True,purchase_cert_ids=['p2'],transport_cert_ids=['t2'],actual_by_source={'load:sp':'18'},manual_allocations=manual,reason='调整',source_note='凭证')
assert [c.db.get(c.m.PlPurchaseCert,pid).reserved_qty for pid in ('p','p2')]==[20,10]
run(c,operation_id='normal',action='settle',sources=sources,actual_by_source={'load:sp':'18'})
assert [c.db.get(c.m.PlPurchaseCert,pid).used_qty for pid in ('p','p2')]==[18,0]
def test_historical_zero_capacity_foreign_cert_cannot_enable_same_unit_reallocation(case):
c=case; _second_pair(c)
sources=request()['sources']; sources[0]['quantity']='30'
run(c,sources=sources,purchase_cert_ids=['p','p2'],transport_cert_ids=['t','t2'])
original=c.db.get(c.m.PlTransportCert,'t2')
values={col.name:getattr(original,col.name) for col in c.m.PlTransportCert.__table__.columns}
values.update(id='t3',cert_no='t3',quantity_unit='kg',reserved_qty=0,used_qty=0)
c.db.add(c.m.PlTransportCert(**values))
c.db.add(c.m.PlContractCertLink(id='lt3',contract_id='c',contract_no_snapshot='c',cert_type='transport',cert_id='t3',tenant_no='t'))
c.db.add(c.m.PlContractCertProduct(id='st3',contract_cert_link_id='lt3',contract_product_id='cp',tenant_no='t'))
c.db.add(c.m.PlTransportCertPurchaseRef(id='ref3',transport_cert_id='t3',reference_type='purchase_permit',reference_no='p2',purchase_cert_id='p2',tenant_no='t'))
original_log=c.db.get(c.m.PlCertQtyLog,'openingt2')
log={col.name:getattr(original_log,col.name) for col in c.m.PlCertQtyLog.__table__.columns}
log.update(id='openingt3',operation_id='openingt3',cert_id='t3',quantity_unit='kg')
c.db.add(c.m.PlCertQtyLog(**log)); c.db.flush()
def manual(kind,pid,sq,pq):
return dict(permit_kind=kind,source_id='load:sp',permit_id=pid,source_quantity=str(sq),permit_quantity=str(pq),note='人工确认')
run(c,operation_id='first-extra',action='settle',sources=sources,allow_supplement=True,purchase_cert_ids=['p2'],transport_cert_ids=['t3'],actual_by_source={'load:sp':'35'},manual_allocations=[manual('transport','t3',5,10)],reason='补量',source_note='凭证')
# 增加合法购证引用,确保拒绝来自原顺序校验,而非最终双证不匹配。
c.db.add(c.m.PlTransportCertPurchaseRef(id='ref2-p',transport_cert_id='t2',reference_type='purchase_permit',reference_no='p',purchase_cert_id='p',tenant_no='t')); c.db.flush()
with pytest.raises(ValueError,match='顺序'):
run(c,operation_id='invalid-manual-adjust',action='actual_adjust',sources=sources,actual_by_source={'load:sp':'18'},manual_actuals=[manual('transport','t',8,8),manual('transport','t2',10,10),manual('transport','t3',0,0)],reason='调整',source_note='凭证')
assert [c.db.get(c.m.PlTransportCert,pid).used_qty for pid in ('t','t2','t3')]==[20,10,10]
with pytest.raises(ValueError,match='异单位'):
run(c,operation_id='invalid-adjust',action='actual_adjust',sources=sources,allow_supplement=True,purchase_cert_ids=['p2'],transport_cert_ids=['t2','t3'],actual_by_source={'load:sp':'18'},manual_actuals=[manual('transport',pid,0,0) for pid in ('t','t2','t3')],manual_allocations=[manual('purchase','p2',18,18),manual('transport','t2',18,18),manual('transport','t3',0,0)],reason='调整',source_note='凭证')
assert [c.db.get(c.m.PlPurchaseCert,pid).used_qty for pid in ('p','p2')]==[20,15]
run(c,operation_id='valid-adjust',action='actual_adjust',sources=sources,actual_by_source={'load:sp':'18'},manual_actuals=[manual('transport','t',18,18),manual('transport','t2',0,0),manual('transport','t3',0,0)],reason='调整',source_note='凭证')
assert [c.db.get(c.m.PlPurchaseCert,pid).used_qty for pid in ('p','p2')]==[18,0]