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

440 lines
24 KiB
Python

import pytest
from fastapi import HTTPException
from types import SimpleNamespace
def test_generic_status_cannot_bypass_quota():
from app.services.pl_quota_business import validate_generic_status
with pytest.raises(HTTPException):
validate_generic_status(SimpleNamespace(transport_status='arranged', model_fields_set={'transport_status'}))
def test_business_hash_includes_non_quota_changes():
from app.services.pl_quota_business import business_hash
assert business_hash('arrange', 'p', {'freight': 1}) != business_hash('arrange', 'p', {'freight': 2})
def test_deadlock_retries_entire_transaction():
from app.services.pl_quota_business import retry_transaction
from sqlalchemy.exc import OperationalError
calls=[]
db=SimpleNamespace(rollback=lambda:calls.append('rollback'))
def work():
calls.append('work')
if calls.count('work') == 1:
raise OperationalError('', {}, Exception(1213, 'deadlock'))
return 42
assert retry_transaction(db, work) == 42
assert calls == ['work','rollback','work']
def test_arrange_schema_retains_quota_configuration():
from app.schemas.pl.transport import TransportArrange
value=TransportArrange(vehicleId='v', operationId='op', quotaConfiguration={'purchaseCertIds':['p']})
assert value.operation_id == 'op'
assert value.quota_configuration.purchase_cert_ids == ['p']
def test_ready_route_is_retired():
from app.api.pl.plan import patch_plan_hazard_cert_ready
with pytest.raises(HTTPException) as error:
patch_plan_hazard_cert_ready('p', None, None, None)
assert error.value.status_code == 410
from test_pl_quota_usage import case
@pytest.fixture
def business(case, monkeypatch):
from app.models import pl_models as m
from app.services import pl_quota_planning
from app.services.pl_quota_sources import extract_sources
from app.services.pl_quota_planning import plan_fingerprint
from app.schemas.pl.transport import TransportArrange
for model in [m.PlPlanProduct,m.PlPlanLoad,m.PlPlanLoadProduct,m.PlPlanDelivery,m.PlPlanDeliveryProduct]:
model.__table__.create(case.db.bind)
plan=case.db.get(m.PlPlan,'plan'); plan.license_plate='CAR'
case.db.add(m.PlPlanProduct(id='pp',plan_id='plan',product_id='product',product_no='SKU',product_name='酸',is_toxic=1,tenant_no='t'))
case.db.add(m.PlPlanLoad(id='load',plan_id='plan',tenant_no='t'))
case.db.add(m.PlPlanLoadProduct(id='sp',load_id='load',plan_id='plan',plan_product_id='pp',product_id='product',product_no='SKU',product_name='酸',plan_qty=29,unit='吨',contract_id='c',contract_product_id='cp',tenant_no='t',inbound_qty=0))
case.db.commit()
monkeypatch.setattr(pl_quota_planning,'plan_execution_lock_reason', lambda *a: None)
sources,_=extract_sources(case.db,case.user,'plan', measurement_version='per_stop_v2')
fingerprint=plan_fingerprint(sources)
payload=TransportArrange(vehicleId='v',operationId='business',quotaConfiguration={'purchaseCertIds':['p'],'transportCertIds':['t'],'conditions':[
{'certType':kind,'certId':cid,'certRevision':1,'sourceFingerprint':fingerprint,'partiesConfirmed':True,'routeConfirmed':True,'note':'已核对'}
for kind,cid in [('purchase','p'),('transport','t')]]})
return SimpleNamespace(**case.__dict__,plan=plan,payload=payload,models=m)
def test_real_source_reserve_zero_settlement(business):
from app.services.pl_quota_business import apply_plan_quota,settle_plan_quota
c=business
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 29
assert settle_plan_quota(c.db,c.user,c.plan,c.payload)
cert=c.db.get(c.m.PlPurchaseCert,'p')
assert cert.reserved_qty == 0 and cert.used_qty == 0
assert all(u.confirmation_snapshot['executed'] for u in c.db.query(c.m.PlPlanCertUsage).all())
def test_overage_preserves_actual_and_reserved(business):
from app.services.pl_quota_business import apply_plan_quota,settle_plan_quota
c=business
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
row=c.db.get(c.models.PlPlanLoadProduct,'sp');row.inbound_qty=101
assert not settle_plan_quota(c.db,c.user,c.plan,c.payload)
c.db.commit()
assert c.db.get(c.models.PlPlanLoadProduct,'sp').inbound_qty == 101
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 29
assert c.plan.archive_status == 'exception'
assert all(u.usage_status == 'exception' for u in c.db.query(c.m.PlPlanCertUsage).all())
def test_business_replay_skips_side_effect_and_changed_freight_conflicts(business):
from app.services.pl_quota_business import atomic_plan_operation
c=business;calls=[]
@atomic_plan_operation('test')
def operation(plan_id,payload,db,user):
calls.append(1)
db.get(c.models.PlPlan,plan_id).remark='business-write'
db.flush()
return {'ok':True}
assert operation('plan',c.payload,c.db,c.user) == {'ok':True}
assert operation('plan',c.payload,c.db,c.user) == {'ok':True}
assert calls == [1]
c.payload.freight_amount=8
with pytest.raises(HTTPException): operation('plan',c.payload,c.db,c.user)
assert calls == [1]
def test_failed_adjustment_retains_settled_state_for_retry(business):
from app.services.pl_quota_business import apply_plan_quota,settle_plan_quota
c=business
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
assert settle_plan_quota(c.db,c.user,c.plan,c.payload)
c.plan.archive_status='archived'
c.db.get(c.models.PlPlanLoadProduct,'sp').inbound_qty=101
assert not settle_plan_quota(c.db,c.user,c.plan,c.payload,adjustment=True)
assert all(u.usage_status == 'settled' for u in c.db.query(c.m.PlPlanCertUsage).all())
assert c.plan.archive_status == 'archived'
def test_generic_form_cannot_reset_formal_status():
from app.services.pl_quota_business import validate_generic_status
with pytest.raises(HTTPException):
validate_generic_status(SimpleNamespace(model_fields_set={'transport_status'},transport_status='pending'),
SimpleNamespace(transport_status='arranged'))
def test_stop_actual_zero_overwrites_old_aggregate(business):
from app.services.pl_plan_archive_stop_service import sync_plan_products_from_stop_archive
c=business
product=c.db.get(c.models.PlPlanProduct,'pp');product.actual_qty=9
c.db.add(c.models.PlPlanDelivery(id='delivery',plan_id='plan',tenant_no='t'))
c.db.add(c.models.PlPlanDeliveryProduct(id='dp',delivery_id='delivery',plan_id='plan',plan_product_id='pp',product_id='product',product_no='SKU',product_name='酸',plan_qty=29,outbound_qty=0,tenant_no='t'))
c.db.flush()
sync_plan_products_from_stop_archive(c.db,tenant_no='t',plan_id='plan',user_id='u')
assert product.actual_qty == 0
def test_legacy_ready_services_cannot_authorize_transport():
from app.services.pl_plan_service import set_hazard_cert_ready, validate_hazard_cert_ready_for_transport, maybe_auto_set_hazard_cert_ready
plan=SimpleNamespace(hazard_cert_ready=1)
with pytest.raises(HTTPException) as error:
validate_hazard_cert_ready_for_transport(None,'t',plan,[])
assert error.value.status_code == 410
with pytest.raises(HTTPException):
set_hazard_cert_ready(None,tenant_no='t',plan_id='p',ready=True,user_id='u')
plan.hazard_cert_ready=0
maybe_auto_set_hazard_cert_ready(None,tenant_no='t',plan=plan,products=[],user_id='u')
assert plan.hazard_cert_ready == 0
def test_business_failure_rolls_back_reservation_and_business_row(business):
from app.services.pl_quota_business import atomic_plan_operation, apply_plan_quota
c=business
@atomic_plan_operation('failed-business')
def operation(plan_id,payload,db,user):
plan=db.get(c.models.PlPlan,plan_id)
apply_plan_quota(db,user,plan,'reserve',payload)
plan.remark='must roll back'
raise HTTPException(409,'later downstream failure')
with pytest.raises(HTTPException): operation('plan',c.payload,c.db,c.user)
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 0
assert c.db.get(c.models.PlPlan,'plan').remark is None
assert c.db.query(c.m.PlCertOperation).count() == 0
def test_force_void_preserves_executed_guard_after_evidence_revoked(business, monkeypatch):
from app.api.pl import plan as api
from app.schemas.pl.cancel_reason import CancelReasonBody
from app.services.pl_quota_business import apply_plan_quota
c=business
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
monkeypatch.setattr(api,'_load_force_void_plan',lambda *a:('t',c.plan,'executed'))
monkeypatch.setattr(api,'apply_force_void_downstream',lambda *a,**k:None)
api.force_void_plan.__wrapped__('plan',CancelReasonBody(cancelReason='停止执行'),c.db,c.user)
with pytest.raises(HTTPException):
apply_plan_quota(c.db,c.user,c.plan,'release')
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 29
assert all(u.confirmation_snapshot['executed'] for u in c.db.query(c.m.PlPlanCertUsage).all())
def test_force_status_cannot_reopen_cancelled_plan(monkeypatch):
from app.api.pl import plan as api
from app.schemas.pl.plan import PlanForceFollowStatusBody
from unittest.mock import MagicMock
plan=SimpleNamespace(follow_status='cancelled',archive_status='cancelled',transport_status='pending')
user=SimpleNamespace(role=api.SUPER_ADMIN_ROLE,id='u',to_tenant_user_dict=lambda:{})
monkeypatch.setattr(api,'get_tenant_no',lambda *a:'t')
monkeypatch.setattr(api,'_get_plan',lambda *a,**k:plan)
with pytest.raises(HTTPException):
api.force_plan_follow_status('p',PlanForceFollowStatusBody(followStatus='pending_arrange'),MagicMock(),user)
def test_successful_retry_clears_active_settlement_error(business):
from app.services.pl_quota_business import apply_plan_quota,settle_plan_quota
c=business
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
row=c.db.get(c.models.PlPlanLoadProduct,'sp');row.inbound_qty=101
assert not settle_plan_quota(c.db,c.user,c.plan,c.payload)
row.inbound_qty=0
c.payload.operation_id='retry-success'
assert settle_plan_quota(c.db,c.user,c.plan,c.payload)
assert all('settlementError' not in u.confirmation_snapshot for u in c.db.query(c.m.PlPlanCertUsage).all())
def test_final_approval_reserves_with_autoflush_disabled(business, monkeypatch):
from app.api.pl import transport as api
from app.schemas.pl.transport import ApprovalRequest
c=business
for model in [c.models.PlTransport,c.models.PlTransportApproval,c.models.PlVehicle,c.models.PlCert,c.models.PlVehiclePersonnel,c.models.PlCarrier,c.models.PlPersonnel]: model.__table__.create(c.db.bind)
c.db.add(c.models.PlVehicle(id='v',vehicle_type='self',license_plate='CAR',tenant_no='t'))
transport=c.models.PlTransport(id='tr',transport_no='TR',tenant_no='t',approval_status='pending',vehicle_id='v')
c.db.add(transport)
c.db.add(c.models.PlTransportApproval(id='approval',transport_id='tr',approver_id='u',approver_name='U',approval_order=1,tenant_no='t'))
c.plan.transport_id='tr';c.plan.cert_configuration=c.payload.quota_configuration.model_dump(mode='json',by_alias=True)
c.db.commit()
c.user.to_tenant_user_dict=lambda:{}
monkeypatch.setattr(api,'get_tenant_no',lambda *a:'t')
api.process_approval('approval',ApprovalRequest(action='approve',operationId='final-approve'),c.db,c.user)
assert transport.approval_status == 'approved'
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 29
assert c.plan.transport_status == 'arranged'
def test_business_evidence_cannot_reference_foreign_tenant(business):
from app.models.crm_models import SysOss
from app.services.pl_quota_business import apply_plan_quota
c=business
SysOss.__table__.create(c.db.bind)
c.db.add(SysOss(id='foreign',tenant_no='other',url='foreign.pdf',create_by='u',service='minio'))
c.db.commit()
c.payload.quota_evidence_urls='foreign.pdf'
with pytest.raises(HTTPException) as error:
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
assert error.value.status_code == 403
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 0
@pytest.fixture
def approval_case(business, monkeypatch):
from app.api.pl import transport as api
c=business
for model in [c.models.PlTransport,c.models.PlTransportApproval,c.models.PlVehicle,c.models.PlCert,c.models.PlVehiclePersonnel,c.models.PlCarrier,c.models.PlPersonnel]: model.__table__.create(c.db.bind)
c.db.add(c.models.PlVehicle(id='v',vehicle_type='self',license_plate='CAR',tenant_no='t'))
c.transport=c.models.PlTransport(id='tr',transport_no='TR',tenant_no='t',approval_status='pending',vehicle_id='v',license_plate='CLIENT')
c.db.add(c.transport)
c.approval=c.models.PlTransportApproval(id='approval',transport_id='tr',approver_id='u',approver_name='U',approval_order=1,tenant_no='t')
c.db.add(c.approval)
c.plan.transport_id='tr';c.plan.cert_configuration=c.payload.quota_configuration.model_dump(mode='json',by_alias=True)
c.db.commit()
c.user.to_tenant_user_dict=lambda:{}
monkeypatch.setattr(api,'get_tenant_no',lambda *a:'t')
c.api=api
return c
@pytest.mark.parametrize('state',['cancelled','archived','exception','completed'])
def test_approval_cannot_reactivate_terminal_plan(approval_case,state):
from app.schemas.pl.transport import ApprovalRequest
c=approval_case
if state in ('archived','exception'): c.plan.archive_status=state
else: c.plan.follow_status=state
c.db.commit()
with pytest.raises(HTTPException): c.api.process_approval('approval',ApprovalRequest(action='approve'),c.db,c.user)
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 0
assert c.approval.status == 'pending'
def test_approval_requires_authoritative_vehicle_and_current_approver(approval_case):
from app.schemas.pl.transport import ApprovalRequest
c=approval_case
c.transport.vehicle_id=None;c.transport.license_plate='CAR';c.db.commit()
with pytest.raises(HTTPException): c.api.process_approval('approval',ApprovalRequest(action='approve'),c.db,c.user)
c.transport.vehicle_id='v';c.approval.approver_id='other';c.db.commit()
with pytest.raises(HTTPException) as error: c.api.process_approval('approval',ApprovalRequest(action='approve'),c.db,c.user)
assert error.value.status_code == 403
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 0
def test_approval_uses_actual_vehicle_not_client_plate(approval_case):
from app.schemas.pl.transport import ApprovalRequest
c=approval_case
c.api.process_approval('approval',ApprovalRequest(action='approve'),c.db,c.user)
assert c.transport.license_plate == c.plan.license_plate == 'CAR'
assert c.transport.resource_snapshot and c.plan.resource_snapshot
@pytest.mark.parametrize('foreign,deleted',[(True,False),(False,True)])
def test_vehicle_info_cannot_read_foreign_or_deleted_vehicle(approval_case,foreign,deleted):
c=approval_case
vehicle=c.db.get(c.models.PlVehicle,'v')
vehicle.tenant_no='foreign' if foreign else 't';vehicle.del_flag=int(deleted);c.db.commit()
with pytest.raises(HTTPException): c.api._get_vehicle_info(c.db,'v','t')
def test_authoritative_vehicle_change_invalidates_confirmation(approval_case):
from app.schemas.pl.transport import ApprovalRequest
c=approval_case
vehicle=c.db.get(c.models.PlVehicle,'v');vehicle.license_plate='OTHER';vehicle.trailer_no='TRAILER'
c.transport.license_plate='CAR';c.db.commit()
with pytest.raises(HTTPException): c.api.process_approval('approval',ApprovalRequest(action='approve'),c.db,c.user)
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 0
assert c.approval.status == 'pending'
def test_cancelled_released_plan_does_not_reserve_again(approval_case):
from app.schemas.pl.transport import ApprovalRequest
from app.services.pl_quota_business import apply_plan_quota
c=approval_case
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
apply_plan_quota(c.db,c.user,c.plan,'release')
c.plan.follow_status='cancelled';c.plan.archive_status='cancelled';c.transport.transport_status='cancelled'
c.db.commit()
with pytest.raises(HTTPException): c.api.process_approval('approval',ApprovalRequest(action='approve'),c.db,c.user)
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 0
assert c.plan.follow_status == 'cancelled'
@pytest.mark.parametrize('entry',['arrange','draft'])
@pytest.mark.parametrize('state',['cancelled','archived','exception','completed'])
def test_arrange_and_draft_reject_terminal_plan(approval_case,entry,state):
from app.schemas.pl.transport import TransportCreate
c=approval_case
c.models.PlPlanTransport.__table__.create(c.db.bind)
if state in ('archived','exception'): c.plan.archive_status=state
else: c.plan.follow_status=state
c.db.commit()
with pytest.raises(HTTPException) as error:
if entry=='arrange': c.api.arrange_transport('plan',c.payload,c.db,c.user)
else: c.api.create_transport(TransportCreate(planIds=['plan']),c.db,c.user)
assert error.value.status_code == 409
c.db.rollback()
assert c.db.get(c.m.PlPurchaseCert,'p').reserved_qty == 0
assert c.db.query(c.models.PlTransport).count() == 1
def test_single_plan_arrange_cannot_change_shared_transport_or_either_quota(approval_case, monkeypatch):
from app.services.pl_quota_business import apply_plan_quota
from app.services.pl_quota_sources import extract_sources
from app.services.pl_quota_planning import plan_fingerprint
from app.services import pl_plan_execution_lock
c=approval_case
c.models.PlPlanTransport.__table__.create(c.db.bind)
other=c.db.get(c.models.PlPlan,'other');other.license_plate='CAR';other.transport_id='tr'
c.db.add(c.models.PlPlanProduct(id='ppb',plan_id='other',product_id='product',product_no='SKU',product_name='酸',is_toxic=1,tenant_no='t'))
c.db.add(c.models.PlPlanLoad(id='loadb',plan_id='other',tenant_no='t'))
c.db.add(c.models.PlPlanLoadProduct(id='spb',load_id='loadb',plan_id='other',plan_product_id='ppb',product_id='product',product_no='SKU',product_name='酸',plan_qty=29,unit='吨',contract_id='c',contract_product_id='cp',tenant_no='t'))
c.db.flush()
payload_b=c.payload.model_copy(deep=True);payload_b.operation_id='reserve-b'
fingerprint=plan_fingerprint(extract_sources(c.db,c.user,'other', measurement_version='per_stop_v2')[0])
for condition in payload_b.quota_configuration.conditions: condition.source_fingerprint=fingerprint
apply_plan_quota(c.db,c.user,c.plan,'reserve',c.payload)
apply_plan_quota(c.db,c.user,other,'reserve',payload_b)
for plan in (c.plan,other):
plan.transport_status='arranged';plan.follow_status='arranged';plan.vehicle_id='v';plan.resource_snapshot='original-'+plan.id
c.db.add(c.models.PlPlanTransport(id='link-'+plan.id,plan_id=plan.id,transport_id='tr',tenant_no='t'))
c.transport.license_plate='CAR';c.transport.resource_snapshot='original-transport'
c.db.add(c.models.PlVehicle(id='new-vehicle',license_plate='NEW',vehicle_type='self',tenant_no='t'))
c.db.commit()
def snapshot():
models=[c.models.PlPlan,c.models.PlTransport,c.models.PlPlanTransport,c.m.PlPurchaseCert,c.m.PlTransportCert,c.m.PlPlanCertUsage,c.m.PlPlanCertUsageItem,c.m.PlCertOperation,c.m.PlCertQtyLog]
return {m.__tablename__:[{col.name:getattr(row,col.name) for col in m.__table__.columns} for row in c.db.query(m).order_by(m.id)] for m in models}
before=snapshot()
monkeypatch.setattr(pl_plan_execution_lock,'plan_execution_lock_reason',lambda *a:None)
payload=c.payload.model_copy(deep=True);payload.operation_id='change-shared';payload.vehicle_id='new-vehicle'
with pytest.raises(HTTPException) as error:
c.api.arrange_transport('plan',payload,c.db,c.user)
assert error.value.status_code == 409
assert '共享运输' in error.value.detail
assert snapshot() == before
def test_per_stop_reserves_both_certificates_and_settles_each_actual(business):
from app.services.pl_quota_planning import configuration_context
from app.services.pl_quota_business import apply_plan_quota, settle_plan_quota
c = business
c.db.get(c.models.PlPlanLoadProduct, 'sp').plan_qty = 100
c.db.get(c.models.PlPlanLoadProduct, 'sp').inbound_qty = 90
for index, qty in [(1, 40), (2, 60)]:
c.db.add(c.models.PlPlanDelivery(id=f'd{index}', plan_id='plan', tenant_no='t', seq=index))
c.db.add(c.models.PlPlanDeliveryProduct(id=f'dp{index}', delivery_id=f'd{index}', plan_id='plan', plan_product_id='pp', product_id='product', product_no='SKU', product_name='酸', plan_qty=qty, outbound_qty=qty-5, unit='吨', contract_id='c', contract_product_id='cp', tenant_no='t'))
for model, cid in [(c.m.PlPurchaseCert, 'p'), (c.m.PlTransportCert, 't')]:
c.db.get(model, cid).total_qty = 300
c.db.flush()
config = c.payload.quota_configuration
fingerprint = configuration_context(c.db, c.user, 'plan', config)['sourceFingerprint']
for condition in config.conditions: condition.source_fingerprint = fingerprint
apply_plan_quota(c.db, c.user, c.plan, 'reserve', c.payload)
assert c.plan.cert_configuration['measurementVersion'] == 'per_stop_v2'
assert all(u.confirmation_snapshot['measurementVersion'] == 'per_stop_v2' for u in c.db.query(c.m.PlPlanCertUsage))
assert all(i.source_snapshot['measurementVersion'] == 'per_stop_v2' for i in c.db.query(c.m.PlPlanCertUsageItem))
for model, cid in [(c.m.PlPurchaseCert, 'p'), (c.m.PlTransportCert, 't')]:
assert c.db.get(model, cid).reserved_qty == 200
assert settle_plan_quota(c.db, c.user, c.plan, c.payload)
for model, cid in [(c.m.PlPurchaseCert, 'p'), (c.m.PlTransportCert, 't')]:
assert c.db.get(model, cid).used_qty == 180
def test_arrange_pins_v2_before_state_changes(approval_case, monkeypatch):
from app.services import pl_plan_execution_lock, pl_contract_plan_qty
c = approval_case
c.models.PlPlanTransport.__table__.create(c.db.bind)
monkeypatch.setattr(pl_plan_execution_lock, 'plan_execution_lock_reason', lambda *a: None)
monkeypatch.setattr(pl_contract_plan_qty, 'auto_start_execute_approved_contracts', lambda *a, **kw: None)
c.models.PlPlanContract.__table__.create(c.db.bind)
# 检查正式安排路径抵达额度调用时,pending 计划的版本已经固定。
def check_quota(db, user, plan, action, payload):
assert plan.transport_status == 'arranged'
assert plan.cert_configuration.get('measurementVersion') == 'per_stop_v2'
raise RuntimeError('checked quota boundary')
monkeypatch.setattr(c.api, 'apply_plan_quota', check_quota)
with pytest.raises(RuntimeError, match='checked quota boundary'):
c.api.arrange_transport('plan', c.payload, c.db, c.user)
def test_supplement_new_certificate_keeps_server_measurement_version(business):
from datetime import date
from test_pl_quota_usage import _second_pair
from app.services.pl_quota_business import apply_plan_quota, settle_plan_quota
from app.services.pl_quota_planning import configuration_context
from app.schemas.pl.quota_planning import CertCondition
c = business
apply_plan_quota(c.db, c.user, c.plan, 'reserve', c.payload)
_second_pair(c)
for model, cid in [(c.m.PlPurchaseCert, 'p'), (c.m.PlTransportCert, 't')]:
c.db.get(model, cid).total_qty = 100
c.db.get(model, cid).valid_to = date(2000, 1, 1)
c.db.get(c.models.PlPlanLoadProduct, 'sp').inbound_qty = 35
c.db.flush()
config = c.payload.quota_configuration.model_copy(deep=True)
config.purchase_cert_ids = ['p2']; config.transport_cert_ids = ['t2']
fingerprint = configuration_context(c.db, c.user, 'plan', config)['sourceFingerprint']
config.conditions = [CertCondition(certType=kind, certId=cid, certRevision=1, sourceFingerprint=fingerprint, partiesConfirmed=True, routeConfirmed=True, note='核对补证') for kind, cid in [('purchase', 'p2'), ('transport', 't2')]]
payload = SimpleNamespace(operation_id='supplement', quota_allow_supplement=True, quota_configuration=config, quota_reason='实际超量', quota_source_note='磅单')
assert settle_plan_quota(c.db, c.user, c.plan, payload)
assert all(u.confirmation_snapshot.get('measurementVersion') == 'per_stop_v2' for u in c.db.query(c.m.PlPlanCertUsage))
assert configuration_context(c.db, c.user, 'plan', config)['measurementVersion'] == 'per_stop_v2'