"""历史派车补派脚本:预览只读、差量、幂等及事务。""" from datetime import date import pytest from sqlalchemy import create_engine from sqlalchemy.orm import Session from app.models.pl_dispatch_calendar import PlDispatchCalendar as Record from scripts.refresh_dispatch_calendar_site import refresh @pytest.fixture def db(): engine = create_engine('sqlite://') Record.__table__.create(engine) with Session(engine, autoflush=False, expire_on_commit=False) as session: yield session engine.dispose() def source(db, id='s1', **values): data = dict(id=id, tenant_no='t1', calendar_kind='acid', dispatch_date=date(2026, 10, 28), vehicle_id='v1', license_plate='鄂E12345', product_id='p1', product_name='盐酸', origin='山峡', destination='武汉', create_by='u1', create_dept_id='d1', site_sync_enabled=0) data.update(values) row = Record(**data) db.add(row) db.commit() return row def run(db, apply=False): return refresh(db, tenant='t1', start=date(2026, 10, 28), end=date(2026, 10, 30), operator='calendar-site-refresh', apply=apply) @pytest.mark.parametrize('kind', ['acid', 'alkali', 'other']) def test_preview_backfill_and_repeat_preserve_identity(db, kind): row = source(db, calendar_kind=kind) assert run(db)['counts']['created'] == 1 assert db.query(Record).count() == 1 and row.site_sync_enabled == 0 assert run(db, True)['counts']['created'] == 1 target = db.query(Record).filter(Record.source_record_id == row.id).one() identity = (target.id, target.create_time, target.create_by, target.update_time) assert row.site_sync_enabled == 1 and target.document_type == '出库计划' assert run(db, True)['counts']['unchanged'] == 1 assert (target.id, target.create_time, target.create_by, target.update_time) == identity assert db.query(Record).count() == 2 def test_scope_and_matching(db): source(db, 'before', dispatch_date=date(2026, 10, 27)) source(db, 'after', dispatch_date=date(2026, 10, 31)) source(db, 'foreign', tenant_no='t2') source(db, 'manual', calendar_kind='site') source(db, 'deleted', del_flag=1) source(db, 'nomatch', origin='武汉', destination='合肥') source(db, 'inbound', destination='云栋', origin='合肥', dispatch_date=date(2026, 10, 29)) source(db, 'both', destination='云栋', dispatch_date=date(2026, 10, 30)) result = run(db, True) targets = db.query(Record).filter(Record.source_record_id.isnot(None)).all() assert {r.source_record_id: r.document_type for r in targets} == {'inbound': '入库计划', 'both': '出入库计划'} assert result['counts']['created'] == 2 assert db.get(Record, 'nomatch').site_sync_enabled == 0 assert db.get(Record, 'manual').origin == '山峡' def test_update_soft_delete_restore_and_enable_history_with_existing_target(db): row = source(db) run(db, True) target = db.query(Record).filter(Record.source_record_id == row.id).one() identity = (target.id, target.create_time) row.destination = '云栋' row.quantity = '32.50' db.commit() assert run(db, True)['counts']['updated'] == 1 assert target.document_type == '出入库计划' and target.quantity == '32.50' row.del_flag = 1 db.commit() assert run(db, True)['counts']['removed'] == 1 assert target.del_flag == 1 row.del_flag = 0 row.site_sync_enabled = 0 db.commit() assert run(db, True)['counts']['restored'] == 1 assert row.site_sync_enabled == 1 and target.del_flag == 0 assert (target.id, target.create_time) == identity def test_failure_rolls_back_all_changes(db, monkeypatch): import scripts.refresh_dispatch_calendar_site as script row = source(db) original = script.svc._sync_site_record def fail(*args): original(*args) args[0].flush() raise RuntimeError('forced failure') monkeypatch.setattr(script.svc, '_sync_site_record', fail) with pytest.raises(RuntimeError): run(db, True) assert db.query(Record).count() == 1 assert db.get(Record, row.id).site_sync_enabled == 0 def test_reversed_date_range_is_rejected(db): with pytest.raises(ValueError): refresh(db, tenant='t1', start=date(2026, 10, 30), end=date(2026, 10, 28), operator='calendar-site-refresh', apply=True) def test_preview_rejects_disabled_source_with_stale_automatic_target(db): row = source(db) run(db, True) row.site_sync_enabled = 0 row.origin = '武汉' db.commit() with pytest.raises(ValueError, match='未启用同步'): run(db)