from decimal import Decimal from django.test import TestCase from django.utils import timezone from basic_info import models as basic_models from business import models as business_models, services from api_v1.views.business.payment.views import PaymentOrderSerializer from api_v1.views.business.receipt.views import ReceiptOrderSerializer from .fixtures import create_basic_fixtures class PaymentReceiptServiceTestCase(TestCase): def setUp(self): ( self.merchant, self.supplier, self.warehouse_strict, self.warehouse_relaxed, self.product, self.operator, ) = create_basic_fixtures() self.customer = basic_models.Customer.objects.create( merchant=self.merchant, name='客户B', mobile='13888888888', created_by=None, ) self.bank_account = basic_models.BankAccount.objects.create( merchant=self.merchant, name='测试账户', auto_number='BANK-SVC', ) def test_create_payment_order_and_approve(self): order = services.create_payment_order( merchant=self.merchant, supplier=self.supplier, payment_date=timezone.now().date(), amount='120.50', discount_amount='20.50', operator=self.operator, remarks='付款测试', bank_account=self.bank_account, markup='服务层附言', ) self.assertEqual(order.status, business_models.PaymentOrderStatusEnum.PENDING) self.assertEqual(order.bank_account, self.bank_account) self.assertEqual(order.markup, '服务层附言') self.assertEqual(order.amount, Decimal('121')) self.assertEqual(order.discount_amount, Decimal('21')) self.assertEqual(order.settlement_amount, Decimal('142')) reviewed = services.review_payment_order( payment_order=order, target_status=business_models.PaymentOrderStatusEnum.APPROVED, reviewed_by=self.operator, ) self.assertEqual(reviewed.status, business_models.PaymentOrderStatusEnum.APPROVED) balance = business_models.SupplierBalance.objects.get( merchant=self.merchant, supplier=self.supplier, ) self.assertEqual(balance.balance, Decimal('-142')) record = business_models.BalanceChangeRecord.objects.get( merchant=self.merchant, source_type=business_models.BalanceChangeSourceEnum.PAYMENT_ORDER, source_id=order.id, ) self.assertEqual(record.direction, business_models.BalanceChangeDirectionEnum.DECREASE) self.assertEqual(record.delta, Decimal('-142')) self.assertEqual(record.balance_after, balance.balance) with self.assertRaises(ValueError): services.review_payment_order( payment_order=order, target_status=business_models.PaymentOrderStatusEnum.CANCELLED, reviewed_by=self.operator, ) def test_create_receipt_order_and_cancel(self): order = services.create_receipt_order( merchant=self.merchant, customer=self.customer, receipt_date=timezone.now().date(), amount='80.00', discount_amount='5', operator=self.operator, remarks='收款测试', bank_account=self.bank_account, markup='收款附言', ) self.assertEqual(order.bank_account, self.bank_account) self.assertEqual(order.discount_amount, Decimal('5')) self.assertEqual(order.settlement_amount, Decimal('85')) cancelled = services.review_receipt_order( receipt_order=order, target_status=business_models.ReceiptOrderStatusEnum.CANCELLED, reviewed_by=self.operator, ) self.assertEqual(cancelled.status, business_models.ReceiptOrderStatusEnum.CANCELLED) self.assertFalse( business_models.BalanceChangeRecord.objects.filter( merchant=self.merchant, source_type=business_models.BalanceChangeSourceEnum.RECEIPT_ORDER, source_id=order.id, ).exists() ) def test_payment_amount_can_be_negative(self): order = services.create_payment_order( merchant=self.merchant, supplier=self.supplier, payment_date=timezone.now().date(), amount='-25.50', operator=self.operator, ) self.assertEqual(order.amount, Decimal('-26')) def test_payment_discount_can_exceed_amount(self): order = services.create_payment_order( merchant=self.merchant, supplier=self.supplier, payment_date=timezone.now().date(), amount='50', discount_amount='60', operator=self.operator, ) self.assertEqual(order.settlement_amount, Decimal('110')) def test_receipt_discount_can_exceed_amount(self): order = services.create_receipt_order( merchant=self.merchant, customer=self.customer, receipt_date=timezone.now().date(), amount='50', discount_amount='60', operator=self.operator, ) self.assertEqual(order.settlement_amount, Decimal('110')) def test_receipt_amount_can_be_negative(self): order = services.create_receipt_order( merchant=self.merchant, customer=self.customer, receipt_date=timezone.now().date(), amount='-40', operator=self.operator, ) self.assertEqual(order.amount, Decimal('-40')) def test_payment_order_rejects_foreign_bank_account(self): other_merchant = basic_models.Merchant.objects.create( name='无关商户', type=basic_models.MerchantTypeEnum.FACTORY, ) foreign_bank = basic_models.BankAccount.objects.create( merchant=other_merchant, name='其他账户', auto_number='BANK-FOREIGN', ) with self.assertRaises(ValueError): services.create_payment_order( merchant=self.merchant, supplier=self.supplier, payment_date=timezone.now().date(), amount='10', operator=self.operator, bank_account=foreign_bank, ) def test_receipt_order_rejects_foreign_bank_account(self): other_merchant = basic_models.Merchant.objects.create( name='第二商户', type=basic_models.MerchantTypeEnum.FACTORY, ) foreign_bank = basic_models.BankAccount.objects.create( merchant=other_merchant, name='第二账户', auto_number='BANK-SECOND', ) with self.assertRaises(ValueError): services.create_receipt_order( merchant=self.merchant, customer=self.customer, receipt_date=timezone.now().date(), amount='10', operator=self.operator, bank_account=foreign_bank, ) def test_receipt_approval_is_idempotent(self): order = services.create_receipt_order( merchant=self.merchant, customer=self.customer, receipt_date=timezone.now().date(), amount='10', operator=self.operator, ) services.review_receipt_order( receipt_order=order, target_status=business_models.ReceiptOrderStatusEnum.APPROVED, reviewed_by=self.operator, ) reviewed_again = services.review_receipt_order( receipt_order=order, target_status=business_models.ReceiptOrderStatusEnum.APPROVED, reviewed_by=self.operator, ) self.assertEqual(reviewed_again.status, business_models.ReceiptOrderStatusEnum.APPROVED) balance = business_models.CustomerBalance.objects.get( merchant=self.merchant, customer=self.customer, ) self.assertEqual(balance.balance, Decimal('-10')) records = business_models.BalanceChangeRecord.objects.filter( merchant=self.merchant, source_type=business_models.BalanceChangeSourceEnum.RECEIPT_ORDER, source_id=order.id, ) self.assertEqual(records.count(), 1) self.assertEqual(records.first().balance_after, balance.balance) def test_payment_approval_is_idempotent(self): order = services.create_payment_order( merchant=self.merchant, supplier=self.supplier, payment_date=timezone.now().date(), amount='75.00', operator=self.operator, ) services.review_payment_order( payment_order=order, target_status=business_models.PaymentOrderStatusEnum.APPROVED, reviewed_by=self.operator, ) services.review_payment_order( payment_order=order, target_status=business_models.PaymentOrderStatusEnum.APPROVED, reviewed_by=self.operator, ) balance = business_models.SupplierBalance.objects.get( merchant=self.merchant, supplier=self.supplier, ) self.assertEqual(balance.balance, Decimal('-75.00')) records = business_models.BalanceChangeRecord.objects.filter( merchant=self.merchant, source_type=business_models.BalanceChangeSourceEnum.PAYMENT_ORDER, source_id=order.id, ) self.assertEqual(records.count(), 1) self.assertEqual(records.first().balance_after, balance.balance) def test_payment_order_external_source_fields_default_and_serialized(self): order = services.create_payment_order( merchant=self.merchant, supplier=self.supplier, payment_date=timezone.now().date(), amount='88.00', operator=self.operator, ) self.assertFalse(order.is_external_source) self.assertIsNone(order.external_source_id) self.assertEqual(order.human_id, f'FK{order.created_at:%Y%m%d}{order.id:06d}') order.is_external_source = True order.external_source_id = 'XT20260001' order.save(update_fields=['is_external_source', 'external_source_id', 'updated_at']) order.refresh_from_db() serializer = PaymentOrderSerializer(order) self.assertTrue(serializer.data['is_external_source']) self.assertEqual(serializer.data['external_source_id'], 'XT20260001') self.assertEqual(serializer.data['human_id'], order.human_id) def test_receipt_order_external_source_fields_default_and_serialized(self): order = services.create_receipt_order( merchant=self.merchant, customer=self.customer, receipt_date=timezone.now().date(), amount='66.00', operator=self.operator, ) self.assertFalse(order.is_external_source) self.assertIsNone(order.external_source_id) self.assertEqual(order.human_id, f'SK{order.created_at:%Y%m%d}{order.id:06d}') order.is_external_source = True order.external_source_id = 'SK20225323' order.save(update_fields=['is_external_source', 'external_source_id', 'updated_at']) order.refresh_from_db() serializer = ReceiptOrderSerializer(order) self.assertTrue(serializer.data['is_external_source']) self.assertEqual(serializer.data['external_source_id'], 'SK20225323') self.assertEqual(serializer.data['human_id'], order.human_id)