enrollment_repositories.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. from sqlalchemy import select
  2. from app.domains.enrollment.models import (
  3. AsyncTask,
  4. EnrollmentDraft,
  5. EnrollmentOrder,
  6. OutboxEvent,
  7. PaymentTransaction,
  8. Policy,
  9. Quote,
  10. UserConfirmation,
  11. )
  12. from app.domains.enrollment.repository import EnrollmentRepository
  13. from app.infrastructure.mysql.core_models import (
  14. AsyncTaskRecord,
  15. EnrollmentDraftRecord,
  16. EnrollmentOrderRecord,
  17. OutboxEventRecord,
  18. PaymentTransactionRecord,
  19. PolicyRecord,
  20. QuoteRecord,
  21. UserConfirmationRecord,
  22. )
  23. from app.infrastructure.mysql.repositories import SessionFactory
  24. class SqlAlchemyEnrollmentRepository(EnrollmentRepository):
  25. def __init__(self, session_factory: SessionFactory) -> None:
  26. self._session_factory = session_factory
  27. def save_quote(self, quote: Quote) -> None:
  28. with self._session_factory() as session:
  29. session.merge(
  30. QuoteRecord(
  31. id=quote.id,
  32. h5_user_id=quote.user_id,
  33. product_id=quote.product_id,
  34. product_version_id=quote.product_version_id,
  35. plan_id=quote.plan_id,
  36. insured_age=quote.insured_age,
  37. insured_region_code=quote.insured_region_code,
  38. occupation_code=quote.occupation_code,
  39. relationship=quote.relationship,
  40. premium_cents=quote.premium_cents,
  41. currency=quote.currency,
  42. rule_version=quote.rule_version,
  43. rate_version=quote.rate_version,
  44. status=quote.status,
  45. expires_at=quote.expires_at,
  46. created_at=quote.created_at,
  47. )
  48. )
  49. session.commit()
  50. def get_quote(self, quote_id: str) -> Quote | None:
  51. with self._session_factory() as session:
  52. record = session.get(QuoteRecord, quote_id)
  53. return self._quote(record) if record else None
  54. def save_draft(self, draft: EnrollmentDraft) -> None:
  55. with self._session_factory() as session:
  56. session.merge(
  57. EnrollmentDraftRecord(
  58. id=draft.id,
  59. h5_user_id=draft.user_id,
  60. quote_id=draft.quote_id,
  61. applicant_json=draft.applicant,
  62. insured_json=draft.insured,
  63. contact_json=draft.contact,
  64. status=draft.status,
  65. expires_at=draft.expires_at,
  66. created_at=draft.created_at,
  67. )
  68. )
  69. session.commit()
  70. def get_draft(self, draft_id: str) -> EnrollmentDraft | None:
  71. with self._session_factory() as session:
  72. record = session.get(EnrollmentDraftRecord, draft_id)
  73. return self._draft(record) if record else None
  74. def save_confirmation(self, confirmation: UserConfirmation) -> None:
  75. with self._session_factory() as session:
  76. session.merge(
  77. UserConfirmationRecord(
  78. id=confirmation.id,
  79. h5_user_id=confirmation.user_id,
  80. draft_id=confirmation.draft_id,
  81. token_hash=confirmation.token_hash,
  82. status=confirmation.status,
  83. expires_at=confirmation.expires_at,
  84. created_at=confirmation.created_at,
  85. )
  86. )
  87. session.commit()
  88. def get_confirmation_by_token_hash(self, token_hash: str) -> UserConfirmation | None:
  89. with self._session_factory() as session:
  90. record = session.scalar(
  91. select(UserConfirmationRecord).where(
  92. UserConfirmationRecord.token_hash == token_hash
  93. )
  94. )
  95. return self._confirmation(record) if record else None
  96. def save_order(self, order: EnrollmentOrder) -> None:
  97. with self._session_factory() as session:
  98. session.merge(
  99. EnrollmentOrderRecord(
  100. id=order.id,
  101. order_no=order.order_no,
  102. h5_user_id=order.user_id,
  103. quote_id=order.quote_id,
  104. draft_id=order.draft_id,
  105. confirmation_id=order.confirmation_id,
  106. idempotency_key=order.idempotency_key,
  107. product_id=order.product_id,
  108. product_version_id=order.product_version_id,
  109. plan_id=order.plan_id,
  110. applicant_snapshot_json=order.applicant_snapshot,
  111. insured_snapshot_json=order.insured_snapshot,
  112. amount_cents=order.amount_cents,
  113. currency=order.currency,
  114. status=order.status,
  115. created_at=order.created_at,
  116. )
  117. )
  118. session.commit()
  119. def get_order(self, order_id: str) -> EnrollmentOrder | None:
  120. with self._session_factory() as session:
  121. record = session.get(EnrollmentOrderRecord, order_id)
  122. return self._order(record) if record else None
  123. def find_order_by_idempotency(
  124. self, user_id: str, idempotency_key: str
  125. ) -> EnrollmentOrder | None:
  126. with self._session_factory() as session:
  127. record = session.scalar(
  128. select(EnrollmentOrderRecord).where(
  129. EnrollmentOrderRecord.h5_user_id == user_id,
  130. EnrollmentOrderRecord.idempotency_key == idempotency_key,
  131. )
  132. )
  133. return self._order(record) if record else None
  134. def list_orders(self, user_id: str | None = None) -> list[EnrollmentOrder]:
  135. with self._session_factory() as session:
  136. statement = select(EnrollmentOrderRecord).order_by(
  137. EnrollmentOrderRecord.created_at.desc()
  138. )
  139. if user_id is not None:
  140. statement = statement.where(EnrollmentOrderRecord.h5_user_id == user_id)
  141. return [self._order(record) for record in session.scalars(statement)]
  142. def save_payment(self, payment: PaymentTransaction) -> None:
  143. with self._session_factory() as session:
  144. session.merge(
  145. PaymentTransactionRecord(
  146. id=payment.id,
  147. payment_no=payment.payment_no,
  148. order_id=payment.order_id,
  149. h5_user_id=payment.user_id,
  150. transaction_type=payment.transaction_type,
  151. provider=payment.provider,
  152. idempotency_key=payment.idempotency_key,
  153. amount_cents=payment.amount_cents,
  154. currency=payment.currency,
  155. status=payment.status,
  156. provider_transaction_no=payment.provider_transaction_no,
  157. succeeded_at=payment.succeeded_at,
  158. created_at=payment.created_at,
  159. )
  160. )
  161. session.commit()
  162. def get_payment(self, payment_id: str) -> PaymentTransaction | None:
  163. with self._session_factory() as session:
  164. record = session.get(PaymentTransactionRecord, payment_id)
  165. return self._payment(record) if record else None
  166. def get_payment_by_no(self, payment_no: str) -> PaymentTransaction | None:
  167. with self._session_factory() as session:
  168. record = session.scalar(
  169. select(PaymentTransactionRecord).where(
  170. PaymentTransactionRecord.payment_no == payment_no
  171. )
  172. )
  173. return self._payment(record) if record else None
  174. def get_payment_by_order(self, order_id: str) -> PaymentTransaction | None:
  175. with self._session_factory() as session:
  176. record = session.scalar(
  177. select(PaymentTransactionRecord).where(
  178. PaymentTransactionRecord.order_id == order_id
  179. )
  180. )
  181. return self._payment(record) if record else None
  182. def find_payment_by_idempotency(
  183. self, order_id: str, idempotency_key: str
  184. ) -> PaymentTransaction | None:
  185. with self._session_factory() as session:
  186. record = session.scalar(
  187. select(PaymentTransactionRecord).where(
  188. PaymentTransactionRecord.order_id == order_id,
  189. PaymentTransactionRecord.idempotency_key == idempotency_key,
  190. )
  191. )
  192. return self._payment(record) if record else None
  193. def save_policy(self, policy: Policy) -> None:
  194. with self._session_factory() as session:
  195. session.merge(
  196. PolicyRecord(
  197. id=policy.id,
  198. policy_no=policy.policy_no,
  199. order_id=policy.order_id,
  200. h5_user_id=policy.user_id,
  201. product_id=policy.product_id,
  202. product_version_id=policy.product_version_id,
  203. plan_id=policy.plan_id,
  204. premium_cents=policy.premium_cents,
  205. currency=policy.currency,
  206. coverage_start=policy.coverage_start,
  207. coverage_end=policy.coverage_end,
  208. status=policy.status,
  209. issued_at=policy.issued_at,
  210. )
  211. )
  212. session.commit()
  213. def get_policy_by_order(self, order_id: str) -> Policy | None:
  214. with self._session_factory() as session:
  215. record = session.scalar(select(PolicyRecord).where(PolicyRecord.order_id == order_id))
  216. return self._policy(record) if record else None
  217. def list_policies(self, user_id: str | None = None) -> list[Policy]:
  218. with self._session_factory() as session:
  219. statement = select(PolicyRecord).order_by(PolicyRecord.issued_at.desc())
  220. if user_id is not None:
  221. statement = statement.where(PolicyRecord.h5_user_id == user_id)
  222. return [self._policy(record) for record in session.scalars(statement)]
  223. def save_outbox_event(self, event: OutboxEvent) -> None:
  224. with self._session_factory() as session:
  225. session.merge(
  226. OutboxEventRecord(
  227. id=event.id,
  228. aggregate_type=event.aggregate_type,
  229. aggregate_id=event.aggregate_id,
  230. event_type=event.event_type,
  231. payload_json=event.payload,
  232. status=event.status,
  233. created_at=event.created_at,
  234. )
  235. )
  236. session.commit()
  237. def save_task(self, task: AsyncTask) -> None:
  238. with self._session_factory() as session:
  239. session.merge(
  240. AsyncTaskRecord(
  241. id=task.id,
  242. task_type=task.task_type,
  243. business_key=task.business_key,
  244. idempotency_key=task.idempotency_key,
  245. payload_json=task.payload,
  246. status=task.status,
  247. attempt_count=task.attempt_count,
  248. created_at=task.created_at,
  249. )
  250. )
  251. session.commit()
  252. def get_task_by_idempotency(self, idempotency_key: str) -> AsyncTask | None:
  253. with self._session_factory() as session:
  254. record = session.scalar(
  255. select(AsyncTaskRecord).where(AsyncTaskRecord.idempotency_key == idempotency_key)
  256. )
  257. if record is None:
  258. return None
  259. return AsyncTask(
  260. id=record.id,
  261. task_type=record.task_type,
  262. business_key=record.business_key,
  263. idempotency_key=record.idempotency_key,
  264. payload=dict(record.payload_json),
  265. status=record.status,
  266. attempt_count=record.attempt_count,
  267. created_at=record.created_at,
  268. )
  269. @staticmethod
  270. def _quote(record: QuoteRecord) -> Quote:
  271. return Quote(
  272. id=record.id,
  273. user_id=record.h5_user_id,
  274. product_id=record.product_id,
  275. product_version_id=record.product_version_id,
  276. plan_id=record.plan_id,
  277. insured_age=record.insured_age,
  278. insured_region_code=record.insured_region_code,
  279. occupation_code=record.occupation_code,
  280. relationship=record.relationship,
  281. premium_cents=record.premium_cents,
  282. currency=record.currency,
  283. rule_version=record.rule_version,
  284. rate_version=record.rate_version,
  285. status=record.status,
  286. expires_at=record.expires_at,
  287. created_at=record.created_at,
  288. )
  289. @staticmethod
  290. def _draft(record: EnrollmentDraftRecord) -> EnrollmentDraft:
  291. return EnrollmentDraft(
  292. id=record.id,
  293. user_id=record.h5_user_id,
  294. quote_id=record.quote_id,
  295. applicant=dict(record.applicant_json),
  296. insured=dict(record.insured_json),
  297. contact=dict(record.contact_json),
  298. status=record.status,
  299. expires_at=record.expires_at,
  300. created_at=record.created_at,
  301. )
  302. @staticmethod
  303. def _confirmation(record: UserConfirmationRecord) -> UserConfirmation:
  304. return UserConfirmation(
  305. id=record.id,
  306. user_id=record.h5_user_id,
  307. draft_id=record.draft_id,
  308. token_hash=record.token_hash,
  309. status=record.status,
  310. expires_at=record.expires_at,
  311. created_at=record.created_at,
  312. )
  313. @staticmethod
  314. def _order(record: EnrollmentOrderRecord) -> EnrollmentOrder:
  315. return EnrollmentOrder(
  316. id=record.id,
  317. order_no=record.order_no,
  318. user_id=record.h5_user_id,
  319. quote_id=record.quote_id,
  320. draft_id=record.draft_id,
  321. confirmation_id=record.confirmation_id,
  322. idempotency_key=record.idempotency_key,
  323. product_id=record.product_id,
  324. product_version_id=record.product_version_id,
  325. plan_id=record.plan_id,
  326. applicant_snapshot=dict(record.applicant_snapshot_json),
  327. insured_snapshot=dict(record.insured_snapshot_json),
  328. amount_cents=record.amount_cents,
  329. currency=record.currency,
  330. status=record.status,
  331. created_at=record.created_at,
  332. )
  333. @staticmethod
  334. def _payment(record: PaymentTransactionRecord) -> PaymentTransaction:
  335. return PaymentTransaction(
  336. id=record.id,
  337. payment_no=record.payment_no,
  338. order_id=record.order_id,
  339. user_id=record.h5_user_id,
  340. transaction_type=record.transaction_type,
  341. provider=record.provider,
  342. idempotency_key=record.idempotency_key,
  343. amount_cents=record.amount_cents,
  344. currency=record.currency,
  345. status=record.status,
  346. provider_transaction_no=record.provider_transaction_no,
  347. succeeded_at=record.succeeded_at,
  348. created_at=record.created_at,
  349. )
  350. @staticmethod
  351. def _policy(record: PolicyRecord) -> Policy:
  352. return Policy(
  353. id=record.id,
  354. policy_no=record.policy_no,
  355. order_id=record.order_id,
  356. user_id=record.h5_user_id,
  357. product_id=record.product_id,
  358. product_version_id=record.product_version_id,
  359. plan_id=record.plan_id,
  360. premium_cents=record.premium_cents,
  361. currency=record.currency,
  362. coverage_start=record.coverage_start,
  363. coverage_end=record.coverage_end,
  364. status=record.status,
  365. issued_at=record.issued_at,
  366. )