mappers.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. """地磅 ORM → API Read 模型映射。"""
  2. from __future__ import annotations
  3. import json
  4. from datetime import date, datetime, time
  5. from app.core.utils import format_datetime
  6. # 三方预生成出入库单「日期未填」占位(原系统 date="",DDL 为 NOT NULL DATE)
  7. EMPTY_INVENTORY_DATE = date(1970, 1, 1)
  8. from app.models.db_models import DbInboundOutbound, DbPurchasePlan, DbWeighingSlip
  9. from app.schemas.db.inventory import InboundOutboundRead
  10. from app.schemas.db.plans import PurchasePlanRead, PurchasePlanWithFillLink
  11. from app.schemas.db.weighing import WeighingSlipRead
  12. def fmt_date(value: date | datetime | str | None) -> str | None:
  13. if value is None:
  14. return None
  15. if isinstance(value, datetime):
  16. return value.strftime("%Y-%m-%d")
  17. if isinstance(value, date):
  18. return value.strftime("%Y-%m-%d")
  19. return str(value)
  20. def fmt_datetime(value: datetime | str | None) -> str | None:
  21. if value is None:
  22. return None
  23. if isinstance(value, datetime):
  24. return format_datetime(value)
  25. return str(value)
  26. def plan_to_read(row: DbPurchasePlan, *, executed_quantity: float | None = None) -> PurchasePlanRead:
  27. return PurchasePlanRead(
  28. id=row.id,
  29. planNo=row.plan_no,
  30. planDate=fmt_date(row.plan_date) or "",
  31. startDate=fmt_date(row.start_date),
  32. endDate=fmt_date(row.end_date),
  33. businessType=row.business_type,
  34. productId=row.product_id,
  35. product=row.product,
  36. specification=row.specification,
  37. plannedQuantity=row.planned_quantity,
  38. vehicleNo=row.vehicle_no or "",
  39. customerName=row.customer_name,
  40. remarks=row.remark,
  41. executionStatus=row.execution_status,
  42. executedAt=fmt_datetime(row.executed_at),
  43. lastWeighingSlipId=row.last_weighing_slip_id,
  44. executedQuantity=(
  45. executed_quantity if executed_quantity is not None else (row.executed_quantity or 0)
  46. ),
  47. autoGenerateEnabled=row.auto_generate_enabled or 0,
  48. autoGenerateEndMonth=row.auto_generate_end_month,
  49. autoGenerateLastMonth=row.auto_generate_last_month,
  50. isDeleted=row.del_flag,
  51. createdAt=format_datetime(row.create_time) or "",
  52. )
  53. def plan_to_read_with_fill_link(
  54. row: DbPurchasePlan,
  55. *,
  56. executed_quantity: float | None = None,
  57. fill_link_inventory_id: str | None = None,
  58. ) -> PurchasePlanWithFillLink:
  59. base = plan_to_read(row, executed_quantity=executed_quantity)
  60. return PurchasePlanWithFillLink(
  61. **base.model_dump(by_alias=False),
  62. fill_link_inventory_id=fill_link_inventory_id,
  63. )
  64. def is_empty_inventory_date(value: date | None) -> bool:
  65. return value is None or value == EMPTY_INVENTORY_DATE
  66. def inventory_date_to_api(value: date | None) -> str:
  67. if is_empty_inventory_date(value):
  68. return ""
  69. return fmt_date(value) or ""
  70. def _parse_site_feedback_images(raw: str | None) -> list[str] | None:
  71. if not raw:
  72. return None
  73. try:
  74. parsed = json.loads(raw)
  75. if isinstance(parsed, list):
  76. items = [str(x) for x in parsed if x]
  77. return items or None
  78. except (TypeError, json.JSONDecodeError):
  79. pass
  80. items = [part.strip() for part in raw.split(",") if part.strip()]
  81. return items or None
  82. def inventory_to_read(
  83. row: DbInboundOutbound,
  84. *,
  85. weighing: DbWeighingSlip | None = None,
  86. ) -> InboundOutboundRead:
  87. gross_weight = None
  88. tare_weight = None
  89. deduction = None
  90. net_weight = None
  91. gross_weight_time = None
  92. tare_weight_time = None
  93. has_weighing_slip = False
  94. if weighing:
  95. gross_weight = weighing.gross_weight
  96. tare_weight = weighing.tare_weight
  97. deduction = weighing.deduction
  98. net_weight = weighing.net_weight
  99. gross_weight_time = fmt_datetime(weighing.gross_weight_time)
  100. tare_time = fmt_datetime(weighing.tare_weight_time)
  101. tare_weight_time = tare_time if tare_time and tare_time.strip() else None
  102. has_weighing_slip = True
  103. return InboundOutboundRead(
  104. id=row.id,
  105. date=inventory_date_to_api(row.date),
  106. partnerCompany=row.partner_company,
  107. businessType=row.business_type,
  108. productId=row.product_id,
  109. product=row.product,
  110. specification=row.specification,
  111. quantity=row.quantity,
  112. planNo=row.plan_no,
  113. batchNo=row.batch_no,
  114. vehicleNo=row.vehicle_no,
  115. status=row.status,
  116. remarks=row.remark,
  117. purchasePlanId=row.purchase_plan_id,
  118. isDeleted=row.del_flag,
  119. createdAt=format_datetime(row.create_time) or "",
  120. grossWeight=gross_weight,
  121. tareWeight=tare_weight,
  122. deduction=deduction,
  123. netWeight=net_weight,
  124. grossWeightTime=gross_weight_time,
  125. tareWeightTime=tare_weight_time,
  126. hasWeighingSlip=has_weighing_slip,
  127. siteFeedbackRemark=row.site_feedback_remark,
  128. siteFeedbackImages=_parse_site_feedback_images(row.site_feedback_images),
  129. siteFeedbackTime=format_datetime(row.site_feedback_time) or None,
  130. )
  131. def weighing_to_read(row: DbWeighingSlip, *, inventory_date: str | None = None) -> WeighingSlipRead:
  132. return WeighingSlipRead(
  133. id=row.id,
  134. vehicleNo=row.vehicle_no,
  135. planNo=row.plan_no,
  136. productId=row.product_id,
  137. cargoName=row.cargo_name,
  138. specification=row.specification,
  139. businessType=row.business_type,
  140. batchNo=row.batch_no,
  141. supplier=row.supplier,
  142. consignee=row.consignee,
  143. grossWeight=row.gross_weight,
  144. grossWeightTime=fmt_datetime(row.gross_weight_time) or "",
  145. tareWeight=row.tare_weight,
  146. tareWeightTime=fmt_datetime(row.tare_weight_time) or "",
  147. deduction=row.deduction,
  148. netWeight=row.net_weight,
  149. plannedQuantity=row.planned_quantity,
  150. executedQuantity=row.executed_quantity,
  151. purchasePlanId=row.purchase_plan_id,
  152. inboundOutboundId=row.inbound_outbound_id,
  153. weighingPoint=row.weighing_point,
  154. remarks=row.remark,
  155. currentQuantity=row.current_quantity,
  156. isVoided=row.is_voided,
  157. createdAt=format_datetime(row.create_time) or "",
  158. inventoryDate=inventory_date,
  159. )
  160. def datetime_end_of_day(value: date) -> datetime:
  161. return datetime.combine(value, time(23, 59, 59))
  162. def parse_date_str(value: str | None) -> date | None:
  163. if not value:
  164. return None
  165. try:
  166. return datetime.strptime(value[:10], "%Y-%m-%d").date()
  167. except ValueError:
  168. return None
  169. def parse_datetime_str(value: str | None) -> datetime | None:
  170. if not value:
  171. return None
  172. for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d"):
  173. try:
  174. return datetime.strptime(value[:19] if fmt != "%Y-%m-%d" else value[:10], fmt)
  175. except ValueError:
  176. continue
  177. return None