inventory_id.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. """出入库单 id 生成 — 租户内纯数字自增,并发安全。"""
  2. from __future__ import annotations
  3. from sqlalchemy import text
  4. from sqlalchemy.exc import IntegrityError
  5. from sqlalchemy.orm import Session
  6. from app.core.utils import generate_uuid
  7. from app.models.db_models import DbInboundOutbound, DbSequence
  8. SEQ_NAME_INBOUND_OUTBOUND = "inbound_outbound"
  9. _NUMERIC_ID_PATTERN = r"^[0-9]+$"
  10. def max_numeric_inbound_outbound_id(db: Session, tenant_no: str) -> int:
  11. """当前租户下纯数字 id 的最大值(不含 UUID 等非数字主键)。"""
  12. result = db.execute(
  13. text(
  14. """
  15. SELECT COALESCE(MAX(CAST(id AS UNSIGNED)), 0) AS max_id
  16. FROM db_inbound_outbound
  17. WHERE tenant_no = :tenant_no
  18. AND del_flag = 0
  19. AND id REGEXP :pattern
  20. """
  21. ),
  22. {"tenant_no": tenant_no, "pattern": _NUMERIC_ID_PATTERN},
  23. ).scalar()
  24. return int(result or 0)
  25. def _ensure_sequence_row(db: Session, tenant_no: str) -> None:
  26. exists = (
  27. db.query(DbSequence.id)
  28. .filter(
  29. DbSequence.tenant_no == tenant_no,
  30. DbSequence.seq_name == SEQ_NAME_INBOUND_OUTBOUND,
  31. )
  32. .first()
  33. )
  34. if exists:
  35. return
  36. initial_next = max_numeric_inbound_outbound_id(db, tenant_no) + 1
  37. db.add(
  38. DbSequence(
  39. id=generate_uuid(),
  40. tenant_no=tenant_no,
  41. seq_name=SEQ_NAME_INBOUND_OUTBOUND,
  42. next_val=initial_next,
  43. )
  44. )
  45. db.flush()
  46. def _allocate_inbound_outbound_id(db: Session, tenant_no: str) -> str:
  47. _ensure_sequence_row(db, tenant_no)
  48. row = (
  49. db.query(DbSequence)
  50. .filter(
  51. DbSequence.tenant_no == tenant_no,
  52. DbSequence.seq_name == SEQ_NAME_INBOUND_OUTBOUND,
  53. )
  54. .with_for_update()
  55. .one()
  56. )
  57. assigned = int(row.next_val)
  58. row.next_val = assigned + 1
  59. db.flush()
  60. return str(assigned)
  61. def next_inbound_outbound_id(db: Session, tenant_no: str) -> str:
  62. """
  63. 分配下一条出入库单 id(纯数字字符串)。
  64. 使用 db_sequence 行锁 + next_val 递增,避免并发下重复 id。
  65. """
  66. last_error: IntegrityError | None = None
  67. for _ in range(3):
  68. try:
  69. return _allocate_inbound_outbound_id(db, tenant_no)
  70. except IntegrityError as exc:
  71. db.rollback()
  72. last_error = exc
  73. if last_error is not None:
  74. raise last_error
  75. return _allocate_inbound_outbound_id(db, tenant_no)