"""Equivalent date formats preserve CRM tier identity; isolated SQLite only.""" from datetime import date, datetime import pymysql import pytest from sqlalchemy import create_engine from sqlalchemy.orm import Session from app.crud.crm import contract_product as cp from app.models.crm_models import CrmContractProduct, CrmContractProductPriceTier from app.models.pl_models import PlPlan, PlPlanLoadProduct, PlPlanDeliveryProduct from app.schemas.crm.contract import ContractUpdateBody @pytest.fixture def db(monkeypatch): def forbidden(*args, **kwargs): raise AssertionError('Business MySQL connections are forbidden') monkeypatch.setattr(pymysql, 'connect', forbidden) engine = create_engine('sqlite://') for model in (CrmContractProduct, CrmContractProductPriceTier, PlPlan, PlPlanLoadProduct, PlPlanDeliveryProduct): model.__table__.create(engine) with Session(engine, autoflush=False) as session: yield session engine.dispose() def seed(db): parent = CrmContractProduct(id='line', tenant_no='t', contract_id='contract', product_id='sku', product_name='Product', unit='吨', package_spec='桶', quantity=1, price=10, create_time=datetime(2020, 1, 1), create_by='creator') db.add(parent) db.flush() return parent def tier(db, row_id='tier', **extra): values = dict(id=row_id, tenant_no='t', contract_id='contract', contract_product_id='line', start_date=date(2026, 1, 1), end_date=date(2026, 2, 1), price=10, remark='keep', letter_oss_ids='["letter"]', create_time=datetime(2020, 1, 1), create_by='creator') values.update(extra) row = CrmContractProductPriceTier(**values) db.add(row) db.flush() return row def tier_body(**kwargs): values = dict(startDate='2026-01-01', endDate='2026-02-01', price=20) values.update(kwargs) return values def save(db, tiers): request = ContractUpdateBody(id='contract', customerId='customer', contractType='sale', products=[{'id': 'line', 'priceTiers': tiers}]) payload = request.model_dump(by_alias=True, exclude_unset=True) cp.sync_contract_products(db, tenant_no='t', contract_id='contract', products=payload['products'], user_id='editor') db.flush() @pytest.mark.parametrize('start,end', [ ('2026-01-01 00:00:00', '2026-02-01 17:05:09'), (' 2026-01-01 ', ' 2026-02-01 '), ('2026-1-1', '2026-2-1'), ('2026-01-01', '2026-02-01'), ]) def test_missing_tier_id_uses_canonical_day_and_keeps_metadata(db, start, end): parent = seed(db) old = tier(db) for price in (20, 30): save(db, [tier_body(startDate=start, endDate=end, price=price)]) assert db.query(CrmContractProductPriceTier).count() == 1 assert old.del_flag == 0 and old.price == 30 and old.remark == 'keep' assert old.letter_oss_ids == '["letter"]' assert old.create_by == 'creator' and old.create_time == datetime(2020, 1, 1) assert parent.id == 'line' and parent.del_flag == 0 and parent.unit == '吨' @pytest.mark.parametrize('start', ['bad', '', None, '2026-02-30']) def test_bad_anonymous_dates_rejected_before_any_changes(db, start): seed(db) old = tier(db) with pytest.raises(ValueError): save(db, [tier_body(id='tier', price=99), tier_body(startDate=start)]) assert not db.new and not db.dirty and old.price == 10 def test_canonical_duplicate_old_rows_are_ambiguous(db): seed(db) tier(db) tier(db, 'duplicate') with pytest.raises(ValueError): save(db, [tier_body(startDate='2026-01-01 00:00:00')]) assert not db.new and not db.dirty def test_two_anonymous_rows_cannot_claim_same_existing_interval(db): seed(db) tier(db) with pytest.raises(ValueError): save(db, [tier_body(), tier_body(startDate='2026-01-01 00:00:00')]) assert not db.new and not db.dirty @pytest.mark.parametrize('scope', [{'tenant_no': 'other'}, {'contract_id': 'other'}, {'contract_product_id': 'other'}, {'del_flag': 1}]) def test_explicit_tier_id_must_be_active_and_in_scope(db, scope): seed(db) tier(db, **scope) with pytest.raises(ValueError): save(db, [tier_body(id='tier')]) assert not db.new and not db.dirty def test_explicit_partial_tier_patch_and_collection_omission(db): seed(db) old = tier(db) save(db, [{'id': 'tier', 'price': 25}]) cp.sync_contract_products(db, tenant_no='t', contract_id='contract', products=[{'id': 'line', 'quantity': 2}]) db.flush() assert old.del_flag == 0 and old.price == 25 and old.start_date == date(2026, 1, 1) save(db, []) assert old.del_flag == 1 def test_genuinely_new_interval_is_added_and_removed_interval_soft_deleted(db): seed(db) old = tier(db) save(db, [tier_body(startDate='2026-03-01 00:00:00', endDate='2026-04-01 00:00:00')]) active = db.query(CrmContractProductPriceTier).filter_by(del_flag=0).one() assert old.del_flag == 1 and active.id != old.id assert active.start_date == date(2026, 3, 1) and active.end_date == date(2026, 4, 1)