from decimal import Decimal from django.test import TestCase from apps.sam.models import Contract, Customer from apps.sam.serializers import ContractRecordSerializer from rest_framework.exceptions import ParseError class ContractSettlementTests(TestCase): def test_sales_contract_record_updates_received_summary(self): customer = Customer.objects.create( name='客户A', contact='张三', contact_phone='13800000001', ) contract = Contract.objects.create( name='销售合同A', number='SC-001', amount=1000, customer=customer, sign_date='2026-04-20', ) from apps.sam.models import ContractRecord ContractRecord.objects.create( contract=contract, record_date='2026-04-21', amount=Decimal('300.00'), stage_type=ContractRecord.STAGE_FIRST, ) ContractRecord.objects.create( contract=contract, record_date='2026-04-22', amount=Decimal('200.00'), stage_type=ContractRecord.STAGE_MIDDLE, ) contract.refresh_from_db() self.assertEqual(contract.received_amount, Decimal('500.00')) self.assertEqual(contract.unreceived_amount, Decimal('500.00')) self.assertEqual(contract.receive_progress, Decimal('50.00')) self.assertEqual(contract.status, Contract.STATUS_ACTIVE) def test_sales_contract_record_delete_refreshes_summary_and_is_physical(self): customer = Customer.objects.create( name='客户B', contact='李四', contact_phone='13800000002', ) contract = Contract.objects.create( name='销售合同B', number='SC-002', amount=1000, customer=customer, sign_date='2026-04-20', ) from apps.sam.models import ContractRecord record = ContractRecord.objects.create( contract=contract, record_date='2026-04-21', amount=Decimal('300.00'), stage_type=ContractRecord.STAGE_FIRST, ) record.delete() contract.refresh_from_db() self.assertEqual(contract.received_amount, Decimal('0.00')) self.assertEqual(contract.unreceived_amount, Decimal('1000.00')) self.assertEqual(contract.receive_progress, Decimal('0.00')) self.assertEqual(contract.status, Contract.STATUS_DRAFT) self.assertFalse(ContractRecord._base_manager.filter(pk=record.pk).exists()) def test_sales_contract_delete_is_physical(self): customer = Customer.objects.create( name='客户C', contact='王五', contact_phone='13800000003', ) contract = Contract.objects.create( name='销售合同C', number='SC-003', amount=500, customer=customer, sign_date='2026-04-20', ) contract.delete() self.assertFalse(Contract._base_manager.filter(pk=contract.pk).exists()) def test_sales_contract_status_auto_transitions_by_records(self): customer = Customer.objects.create( name='客户D', contact='赵六', contact_phone='13800000004', ) contract = Contract.objects.create( name='销售合同D', number='SC-004', amount=1000, customer=customer, sign_date='2026-04-20', ) from apps.sam.models import ContractRecord self.assertEqual(contract.status, Contract.STATUS_DRAFT) first_record = ContractRecord.objects.create( contract=contract, record_date='2026-04-21', amount=Decimal('300.00'), stage_type=ContractRecord.STAGE_FIRST, ) contract.refresh_from_db() self.assertEqual(contract.status, Contract.STATUS_ACTIVE) ContractRecord.objects.create( contract=contract, record_date='2026-04-22', amount=Decimal('700.00'), stage_type=ContractRecord.STAGE_FINAL, ) contract.refresh_from_db() self.assertEqual(contract.status, Contract.STATUS_DONE) first_record.delete() contract.refresh_from_db() self.assertEqual(contract.status, Contract.STATUS_ACTIVE) def test_sales_terminated_contract_forbids_record_changes(self): customer = Customer.objects.create( name='客户E', contact='孙七', contact_phone='13800000005', ) contract = Contract.objects.create( name='销售合同E', number='SC-005', amount=1000, customer=customer, sign_date='2026-04-20', status=Contract.STATUS_TERMINATED, ) serializer = ContractRecordSerializer(data={ 'contract': contract.id, 'record_date': '2026-04-21', 'amount': '100.00', 'stage_type': 10, 'pay_method': 10, }) self.assertFalse(serializer.is_valid()) self.assertIn('合同已终止,不可操作到款流水', str(serializer.errors)) contract.status = Contract.STATUS_DRAFT contract.save(refresh_settlement=False) from apps.sam.models import ContractRecord record = ContractRecord.objects.create( contract=contract, record_date='2026-04-20', amount=Decimal('100.00'), stage_type=ContractRecord.STAGE_OTHER, ) contract.status = Contract.STATUS_TERMINATED contract.save(refresh_settlement=False) with self.assertRaises(ParseError): from apps.sam.views import ContractRecordViewSet viewset = ContractRecordViewSet() viewset.request = None viewset.perform_destroy(record)