智慧水务管理系统 - 精河县供水工程综合管理平台

test_billing_calculation.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. """
  2. 营业收费计算逻辑单元测试
  3. 覆盖计费逻辑、折扣计算和滞纳金计算
  4. """
  5. import unittest
  6. from unittest.mock import Mock, patch
  7. from datetime import datetime, timedelta, date
  8. import json
  9. from src.billing.models import Bill, Payment, Tariff, DiscountRule
  10. from src.billing.services import BillingService, PaymentService, DiscountService
  11. class TestTariff(unittest.TestCase):
  12. """费率模型测试"""
  13. def setUp(self):
  14. self.tariff_data = {
  15. "tariff_id": "water_tier_1",
  16. "name": "第一阶梯水价",
  17. "description": "居民用水第一阶梯",
  18. "unit_price": 3.5, # 元/立方米
  19. "currency": "CNY",
  20. "effective_date": "2026-01-01",
  21. "expiry_date": "2026-12-31",
  22. "consumption_tiers": [
  23. {"min": 0, "max": 12, "price": 3.5},
  24. {"min": 12, "max": 24, "price": 5.0},
  25. {"min": 24, "max": float('inf'), "price": 7.0}
  26. ]
  27. }
  28. def test_tariff_creation(self):
  29. """测试费率创建"""
  30. tariff = Tariff(self.tariff_data)
  31. self.assertEqual(tariff.tariff_id, self.tariff_data["tariff_id"])
  32. self.assertEqual(tariff.unit_price, 3.5)
  33. self.assertEqual(len(tariff.consumption_tiers), 3)
  34. # 验证阶梯价格
  35. self.assertEqual(tariff.get_price_for_consumption(10), 3.5) # 第一阶梯
  36. self.assertEqual(tariff.get_price_for_consumption(15), 5.0) # 第二阶梯
  37. self.assertEqual(tariff.get_price_for_consumption(30), 7.0) # 第三阶梯
  38. def test_tariff_validation(self):
  39. """测试费率验证"""
  40. tariff = Tariff(self.tariff_data)
  41. result = tariff.validate()
  42. self.assertTrue(result["valid"])
  43. # 测试无效费率(负价格)
  44. invalid_tariff_data = self.tariff_data.copy()
  45. invalid_tariff_data["unit_price"] = -1.0
  46. invalid_tariff = Tariff(invalid_tariff_data)
  47. result = invalid_tariff.validate()
  48. self.assertFalse(result["valid"])
  49. def test_tariff_effective_date(self):
  50. """测试费率有效期"""
  51. tariff = Tariff(self.tariff_data)
  52. # 在有效期内
  53. current_date = date(2026, 6, 16)
  54. self.assertTrue(tariff.is_effective_on(current_date))
  55. # 在有效期前
  56. future_date = date(2027, 1, 1)
  57. self.assertFalse(tariff.is_effective_on(future_date))
  58. class TestBill(unittest.TestCase):
  59. """账单模型测试"""
  60. def setUp(self):
  61. self.bill_data = {
  62. "bill_id": "bill_001",
  63. "customer_id": "customer_001",
  64. "account_number": "ACC123456",
  65. "billing_period": {
  66. "start_date": "2026-05-01",
  67. "end_date": "2026-05-31"
  68. },
  69. "consumption": {
  70. "water_consumption": 15.5, # 立方米
  71. "sewage_fee": 2.3,
  72. "other_fees": 10.0
  73. },
  74. "charges": [],
  75. "due_date": "2026-06-15",
  76. "status": "pending",
  77. "created_at": "2026-05-31T23:59:59Z"
  78. }
  79. def test_bill_creation(self):
  80. """测试账单创建"""
  81. bill = Bill(self.bill_data)
  82. self.assertEqual(bill.bill_id, self.bill_data["bill_id"])
  83. self.assertEqual(bill.customer_id, self.bill_data["customer_id"])
  84. self.assertEqual(bill.status, "pending")
  85. self.assertEqual(bill.consumption["water_consumption"], 15.5)
  86. # 验证日期
  87. self.assertTrue(datetime.fromisoformat(bill.billing_period["start_date"]))
  88. self.assertTrue(datetime.fromisoformat(bill.billing_period["end_date"]))
  89. def test_bill_calculation(self):
  90. """测试账单计算"""
  91. bill = Bill(self.bill_data)
  92. tariff = Tariff(self.tariff_data)
  93. # 计算水费
  94. water_charge = bill.calculate_water_charge(tariff)
  95. expected_water_fee = 12 * 3.5 + (15.5 - 12) * 5.0 # 阶梯计算
  96. self.assertAlmostEqual(water_charge, expected_water_fee, places=2)
  97. # 计算总费用
  98. total_charge = bill.calculate_total_charge(tariff)
  99. expected_total = expected_water_fee + 2.3 + 10.0
  100. self.assertAlmostEqual(total_charge, expected_total, places=2)
  101. def test_bill_status_transitions(self):
  102. """测试账单状态转换"""
  103. bill = Bill(self.bill_data)
  104. # 从pending到issued
  105. bill.issue_bill()
  106. self.assertEqual(bill.status, "issued")
  107. self.assertIsNotNone(bill.issued_at)
  108. # 从issued到overdue
  109. due_date = datetime.strptime(bill.due_date, "%Y-%m-%d").date()
  110. overdue_date = due_date + timedelta(days=1)
  111. bill.mark_as_overdue(overdue_date)
  112. self.assertEqual(bill.status, "overdue")
  113. # 从overdue到paid
  114. bill.mark_as_paid()
  115. self.assertEqual(bill.status, "paid")
  116. class TestBillingService(unittest.TestCase):
  117. """计费服务测试"""
  118. def setUp(self):
  119. self.billing_service = BillingService()
  120. @patch('src.billing.services.Tariff')
  121. @patch('src.billing.services.Bill')
  122. def test_generate_monthly_bill(self, mock_bill_class, mock_tariff_class):
  123. """测试生成月度账单"""
  124. customer_id = "customer_001"
  125. period = {"start_date": "2026-05-01", "end_date": "2026-05-31"}
  126. consumption_data = {"water_consumption": 15.5}
  127. mock_tariff = Mock()
  128. mock_tariff.get_price_for_consumption.return_value = 3.5
  129. mock_tariff_class.get_active_tariff.return_value = mock_tariff
  130. mock_bill = Mock()
  131. mock_bill.calculate_total_charge.return_value = 65.25
  132. mock_bill_class.return_value = mock_bill
  133. result = self.billing_service.generate_monthly_bill(customer_id, period, consumption_data)
  134. self.assertTrue(result["success"])
  135. self.assertEqual(result["total_amount"], 65.25)
  136. mock_bill_class.assert_called_once()
  137. @patch('src.billing.services.Tariff')
  138. def test_calculate_late_fee(self, mock_tariff):
  139. """测试滞纳金计算"""
  140. base_amount = 100.0
  141. due_date = date(2026, 6, 1)
  142. current_date = date(2026, 6, 16) # 15天滞纳
  143. late_fee_rate = 0.002 # 每天0.2%
  144. late_fee = self.billing_service.calculate_late_fee(
  145. base_amount, due_date, current_date, late_fee_rate
  146. )
  147. expected_late_fee = base_amount * late_fee_rate * 15
  148. self.assertAlmostEqual(late_fee, expected_late_fee, places=2)
  149. @patch('src.billing.services.Bill')
  150. def test_get_customer_bills(self, mock_bill):
  151. """测试获取客户账单"""
  152. customer_id = "customer_001"
  153. mock_bill.query.return_value = [
  154. {"bill_id": "bill_001", "amount": 100.0, "status": "paid"},
  155. {"bill_id": "bill_002", "amount": 150.0, "status": "pending"}
  156. ]
  157. bills = self.billing_service.get_customer_bills(customer_id)
  158. self.assertEqual(len(bills), 2)
  159. mock_bill.query.assert_called_with(customer_id=customer_id)
  160. def test_billing_validation(self):
  161. """测试计费验证"""
  162. bill_data = {
  163. "customer_id": "customer_001",
  164. "consumption": {"water_consumption": -5.0} # 负消费量
  165. }
  166. result = self.billing_service.validate_billing_data(bill_data)
  167. self.assertFalse(result["valid"])
  168. self.assertIn("Negative consumption", result["errors"])
  169. class TestDiscountService(unittest.TestCase):
  170. """折扣服务测试"""
  171. def setUp(self):
  172. self.discount_service = DiscountService()
  173. def test_early_payment_discount(self):
  174. """测试提前支付折扣"""
  175. bill_amount = 1000.0
  176. due_date = date(2026, 6, 30)
  177. payment_date = date(2026, 6, 15) # 提前15天
  178. discount_rate = 0.05 # 5%折扣
  179. discount = self.discount_service.calculate_early_payment_discount(
  180. bill_amount, due_date, payment_date, discount_rate
  181. )
  182. expected_discount = bill_amount * discount_rate
  183. self.assertEqual(discount, expected_discount)
  184. # 计算最终金额
  185. final_amount = self.discount_service.apply_discount(bill_amount, discount)
  186. self.assertEqual(final_amount, bill_amount - expected_discount)
  187. def test_bulk_discount(self):
  188. """测试批量支付折扣"""
  189. customer_id = "customer_001"
  190. bills = [
  191. {"bill_id": "bill_001", "amount": 500.0},
  192. {"bill_id": "bill_002", "amount": 1000.0},
  193. {"bill_id": "bill_003", "amount": 2000.0}
  194. ]
  195. # 批量总金额超过3000,享受3%折扣
  196. bulk_amount = sum(bill["amount"] for bill in bills)
  197. discount = self.discount_service.calculate_bulk_discount(customer_id, bills, 3000, 0.03)
  198. expected_discount = bulk_amount * 0.03
  199. self.assertEqual(discount, expected_discount)
  200. def test_seasonal_discount(self):
  201. """测试季节性折扣"""
  202. current_date = date(2026, 6, 16) # 夏季
  203. bill_amount = 500.0
  204. seasonal_discount_rate = 0.1 # 夏季10%折扣
  205. discount = self.discount_service.calculate_seasonal_discount(current_date, bill_amount, seasonal_discount_rate)
  206. expected_discount = bill_amount * seasonal_discount_rate
  207. self.assertEqual(discount, expected_discount)
  208. def test_discount_combination(self):
  209. """测试折扣组合"""
  210. base_amount = 1000.0
  211. # 提前支付折扣
  212. early_discount = 50.0
  213. # 季节性折扣
  214. seasonal_discount = 100.0
  215. # 应用折扣(通常不叠加,取最大值)
  216. final_discount = self.discount_service.apply_multiple_discounts(
  217. base_amount, [early_discount, seasonal_discount]
  218. )
  219. self.assertEqual(final_discount, seasonal_discount) # 取最大值
  220. final_amount = base_amount - final_discount
  221. self.assertEqual(final_amount, 900.0)
  222. class TestPaymentService(unittest.TestCase):
  223. """支付服务测试"""
  224. def setUp(self):
  225. self.payment_service = PaymentService()
  226. @patch('src.billing.services.Payment')
  227. def test_process_payment(self, mock_payment_class):
  228. """测试处理支付"""
  229. payment_data = {
  230. "bill_id": "bill_001",
  231. "amount": 500.0,
  232. "payment_method": "bank_transfer",
  233. "transaction_id": "txn_001"
  234. }
  235. mock_payment = Mock()
  236. mock_payment.process.return_value = {"success": True, "payment_id": "payment_001"}
  237. mock_payment_class.return_value = mock_payment
  238. result = self.payment_service.process_payment(payment_data)
  239. self.assertTrue(result["success"])
  240. self.assertEqual(result["payment_id"], "payment_001")
  241. mock_payment.process.assert_called_once()
  242. def test_payment_validation(self):
  243. """测试支付验证"""
  244. payment_data = {
  245. "bill_id": "bill_001",
  246. "amount": -100.0, # 负金额
  247. "payment_method": "invalid_method"
  248. }
  249. result = self.payment_service.validate_payment(payment_data)
  250. self.assertFalse(result["valid"])
  251. self.assertIn("Negative amount", result["errors"])
  252. self.assertIn("Invalid payment method", result["errors"])
  253. @patch('src.billing.services.Bill')
  254. def test_payment_reconciliation(self, mock_bill):
  255. """测试支付对账"""
  256. payment_data = {
  257. "bill_id": "bill_001",
  258. "amount": 500.0,
  259. "transaction_id": "txn_001"
  260. }
  261. mock_bill.get.return_value = {
  262. "bill_id": "bill_001",
  263. "amount_due": 500.0,
  264. "status": "pending"
  265. }
  266. result = self.payment_service.reconcile_payment(payment_data)
  267. self.assertTrue(result["success"])
  268. self.assertEqual(result["status"], "paid")
  269. mock_bill.update_status.assert_called_once_with("bill_001", "paid")
  270. if __name__ == '__main__':
  271. unittest.main()