| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- """
- 营业收费计算逻辑单元测试
- 覆盖计费逻辑、折扣计算和滞纳金计算
- """
- import unittest
- from unittest.mock import Mock, patch
- from datetime import datetime, timedelta, date
- import json
- from src.billing.models import Bill, Payment, Tariff, DiscountRule
- from src.billing.services import BillingService, PaymentService, DiscountService
-
-
- class TestTariff(unittest.TestCase):
- """费率模型测试"""
-
- def setUp(self):
- self.tariff_data = {
- "tariff_id": "water_tier_1",
- "name": "第一阶梯水价",
- "description": "居民用水第一阶梯",
- "unit_price": 3.5, # 元/立方米
- "currency": "CNY",
- "effective_date": "2026-01-01",
- "expiry_date": "2026-12-31",
- "consumption_tiers": [
- {"min": 0, "max": 12, "price": 3.5},
- {"min": 12, "max": 24, "price": 5.0},
- {"min": 24, "max": float('inf'), "price": 7.0}
- ]
- }
-
- def test_tariff_creation(self):
- """测试费率创建"""
- tariff = Tariff(self.tariff_data)
-
- self.assertEqual(tariff.tariff_id, self.tariff_data["tariff_id"])
- self.assertEqual(tariff.unit_price, 3.5)
- self.assertEqual(len(tariff.consumption_tiers), 3)
-
- # 验证阶梯价格
- self.assertEqual(tariff.get_price_for_consumption(10), 3.5) # 第一阶梯
- self.assertEqual(tariff.get_price_for_consumption(15), 5.0) # 第二阶梯
- self.assertEqual(tariff.get_price_for_consumption(30), 7.0) # 第三阶梯
-
- def test_tariff_validation(self):
- """测试费率验证"""
- tariff = Tariff(self.tariff_data)
- result = tariff.validate()
- self.assertTrue(result["valid"])
-
- # 测试无效费率(负价格)
- invalid_tariff_data = self.tariff_data.copy()
- invalid_tariff_data["unit_price"] = -1.0
- invalid_tariff = Tariff(invalid_tariff_data)
- result = invalid_tariff.validate()
- self.assertFalse(result["valid"])
-
- def test_tariff_effective_date(self):
- """测试费率有效期"""
- tariff = Tariff(self.tariff_data)
-
- # 在有效期内
- current_date = date(2026, 6, 16)
- self.assertTrue(tariff.is_effective_on(current_date))
-
- # 在有效期前
- future_date = date(2027, 1, 1)
- self.assertFalse(tariff.is_effective_on(future_date))
-
-
- class TestBill(unittest.TestCase):
- """账单模型测试"""
-
- def setUp(self):
- self.bill_data = {
- "bill_id": "bill_001",
- "customer_id": "customer_001",
- "account_number": "ACC123456",
- "billing_period": {
- "start_date": "2026-05-01",
- "end_date": "2026-05-31"
- },
- "consumption": {
- "water_consumption": 15.5, # 立方米
- "sewage_fee": 2.3,
- "other_fees": 10.0
- },
- "charges": [],
- "due_date": "2026-06-15",
- "status": "pending",
- "created_at": "2026-05-31T23:59:59Z"
- }
-
- def test_bill_creation(self):
- """测试账单创建"""
- bill = Bill(self.bill_data)
-
- self.assertEqual(bill.bill_id, self.bill_data["bill_id"])
- self.assertEqual(bill.customer_id, self.bill_data["customer_id"])
- self.assertEqual(bill.status, "pending")
- self.assertEqual(bill.consumption["water_consumption"], 15.5)
-
- # 验证日期
- self.assertTrue(datetime.fromisoformat(bill.billing_period["start_date"]))
- self.assertTrue(datetime.fromisoformat(bill.billing_period["end_date"]))
-
- def test_bill_calculation(self):
- """测试账单计算"""
- bill = Bill(self.bill_data)
- tariff = Tariff(self.tariff_data)
-
- # 计算水费
- water_charge = bill.calculate_water_charge(tariff)
- expected_water_fee = 12 * 3.5 + (15.5 - 12) * 5.0 # 阶梯计算
- self.assertAlmostEqual(water_charge, expected_water_fee, places=2)
-
- # 计算总费用
- total_charge = bill.calculate_total_charge(tariff)
- expected_total = expected_water_fee + 2.3 + 10.0
- self.assertAlmostEqual(total_charge, expected_total, places=2)
-
- def test_bill_status_transitions(self):
- """测试账单状态转换"""
- bill = Bill(self.bill_data)
-
- # 从pending到issued
- bill.issue_bill()
- self.assertEqual(bill.status, "issued")
- self.assertIsNotNone(bill.issued_at)
-
- # 从issued到overdue
- due_date = datetime.strptime(bill.due_date, "%Y-%m-%d").date()
- overdue_date = due_date + timedelta(days=1)
- bill.mark_as_overdue(overdue_date)
- self.assertEqual(bill.status, "overdue")
-
- # 从overdue到paid
- bill.mark_as_paid()
- self.assertEqual(bill.status, "paid")
-
-
- class TestBillingService(unittest.TestCase):
- """计费服务测试"""
-
- def setUp(self):
- self.billing_service = BillingService()
-
- @patch('src.billing.services.Tariff')
- @patch('src.billing.services.Bill')
- def test_generate_monthly_bill(self, mock_bill_class, mock_tariff_class):
- """测试生成月度账单"""
- customer_id = "customer_001"
- period = {"start_date": "2026-05-01", "end_date": "2026-05-31"}
- consumption_data = {"water_consumption": 15.5}
-
- mock_tariff = Mock()
- mock_tariff.get_price_for_consumption.return_value = 3.5
- mock_tariff_class.get_active_tariff.return_value = mock_tariff
-
- mock_bill = Mock()
- mock_bill.calculate_total_charge.return_value = 65.25
- mock_bill_class.return_value = mock_bill
-
- result = self.billing_service.generate_monthly_bill(customer_id, period, consumption_data)
-
- self.assertTrue(result["success"])
- self.assertEqual(result["total_amount"], 65.25)
- mock_bill_class.assert_called_once()
-
- @patch('src.billing.services.Tariff')
- def test_calculate_late_fee(self, mock_tariff):
- """测试滞纳金计算"""
- base_amount = 100.0
- due_date = date(2026, 6, 1)
- current_date = date(2026, 6, 16) # 15天滞纳
- late_fee_rate = 0.002 # 每天0.2%
-
- late_fee = self.billing_service.calculate_late_fee(
- base_amount, due_date, current_date, late_fee_rate
- )
- expected_late_fee = base_amount * late_fee_rate * 15
- self.assertAlmostEqual(late_fee, expected_late_fee, places=2)
-
- @patch('src.billing.services.Bill')
- def test_get_customer_bills(self, mock_bill):
- """测试获取客户账单"""
- customer_id = "customer_001"
-
- mock_bill.query.return_value = [
- {"bill_id": "bill_001", "amount": 100.0, "status": "paid"},
- {"bill_id": "bill_002", "amount": 150.0, "status": "pending"}
- ]
-
- bills = self.billing_service.get_customer_bills(customer_id)
- self.assertEqual(len(bills), 2)
- mock_bill.query.assert_called_with(customer_id=customer_id)
-
- def test_billing_validation(self):
- """测试计费验证"""
- bill_data = {
- "customer_id": "customer_001",
- "consumption": {"water_consumption": -5.0} # 负消费量
- }
-
- result = self.billing_service.validate_billing_data(bill_data)
- self.assertFalse(result["valid"])
- self.assertIn("Negative consumption", result["errors"])
-
-
- class TestDiscountService(unittest.TestCase):
- """折扣服务测试"""
-
- def setUp(self):
- self.discount_service = DiscountService()
-
- def test_early_payment_discount(self):
- """测试提前支付折扣"""
- bill_amount = 1000.0
- due_date = date(2026, 6, 30)
- payment_date = date(2026, 6, 15) # 提前15天
- discount_rate = 0.05 # 5%折扣
-
- discount = self.discount_service.calculate_early_payment_discount(
- bill_amount, due_date, payment_date, discount_rate
- )
- expected_discount = bill_amount * discount_rate
- self.assertEqual(discount, expected_discount)
-
- # 计算最终金额
- final_amount = self.discount_service.apply_discount(bill_amount, discount)
- self.assertEqual(final_amount, bill_amount - expected_discount)
-
- def test_bulk_discount(self):
- """测试批量支付折扣"""
- customer_id = "customer_001"
- bills = [
- {"bill_id": "bill_001", "amount": 500.0},
- {"bill_id": "bill_002", "amount": 1000.0},
- {"bill_id": "bill_003", "amount": 2000.0}
- ]
-
- # 批量总金额超过3000,享受3%折扣
- bulk_amount = sum(bill["amount"] for bill in bills)
- discount = self.discount_service.calculate_bulk_discount(customer_id, bills, 3000, 0.03)
- expected_discount = bulk_amount * 0.03
- self.assertEqual(discount, expected_discount)
-
- def test_seasonal_discount(self):
- """测试季节性折扣"""
- current_date = date(2026, 6, 16) # 夏季
- bill_amount = 500.0
- seasonal_discount_rate = 0.1 # 夏季10%折扣
-
- discount = self.discount_service.calculate_seasonal_discount(current_date, bill_amount, seasonal_discount_rate)
- expected_discount = bill_amount * seasonal_discount_rate
- self.assertEqual(discount, expected_discount)
-
- def test_discount_combination(self):
- """测试折扣组合"""
- base_amount = 1000.0
-
- # 提前支付折扣
- early_discount = 50.0
-
- # 季节性折扣
- seasonal_discount = 100.0
-
- # 应用折扣(通常不叠加,取最大值)
- final_discount = self.discount_service.apply_multiple_discounts(
- base_amount, [early_discount, seasonal_discount]
- )
- self.assertEqual(final_discount, seasonal_discount) # 取最大值
-
- final_amount = base_amount - final_discount
- self.assertEqual(final_amount, 900.0)
-
-
- class TestPaymentService(unittest.TestCase):
- """支付服务测试"""
-
- def setUp(self):
- self.payment_service = PaymentService()
-
- @patch('src.billing.services.Payment')
- def test_process_payment(self, mock_payment_class):
- """测试处理支付"""
- payment_data = {
- "bill_id": "bill_001",
- "amount": 500.0,
- "payment_method": "bank_transfer",
- "transaction_id": "txn_001"
- }
-
- mock_payment = Mock()
- mock_payment.process.return_value = {"success": True, "payment_id": "payment_001"}
- mock_payment_class.return_value = mock_payment
-
- result = self.payment_service.process_payment(payment_data)
-
- self.assertTrue(result["success"])
- self.assertEqual(result["payment_id"], "payment_001")
- mock_payment.process.assert_called_once()
-
- def test_payment_validation(self):
- """测试支付验证"""
- payment_data = {
- "bill_id": "bill_001",
- "amount": -100.0, # 负金额
- "payment_method": "invalid_method"
- }
-
- result = self.payment_service.validate_payment(payment_data)
- self.assertFalse(result["valid"])
- self.assertIn("Negative amount", result["errors"])
- self.assertIn("Invalid payment method", result["errors"])
-
- @patch('src.billing.services.Bill')
- def test_payment_reconciliation(self, mock_bill):
- """测试支付对账"""
- payment_data = {
- "bill_id": "bill_001",
- "amount": 500.0,
- "transaction_id": "txn_001"
- }
-
- mock_bill.get.return_value = {
- "bill_id": "bill_001",
- "amount_due": 500.0,
- "status": "pending"
- }
-
- result = self.payment_service.reconcile_payment(payment_data)
-
- self.assertTrue(result["success"])
- self.assertEqual(result["status"], "paid")
- mock_bill.update_status.assert_called_once_with("bill_001", "paid")
-
-
- if __name__ == '__main__':
- unittest.main()
|