estimate.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. from fastapi import APIRouter, Request, Depends, Form
  2. from fastapi.responses import HTMLResponse
  3. from sqlalchemy.orm import Session
  4. from sqlalchemy import text
  5. from database import SessionLocal, redis_client
  6. from services.template_service import build_template
  7. from services.price_service import apply_adjust
  8. import json
  9. import uuid
  10. router = APIRouter(prefix="/estimate", tags=["estimate"])
  11. # ================= DB =================
  12. def get_db():
  13. db = SessionLocal()
  14. try:
  15. yield db
  16. finally:
  17. db.close()
  18. # ================= 获取所有机型 =================
  19. @router.get("/simulate", response_class=HTMLResponse)
  20. def simulate(db: Session = Depends(get_db)):
  21. # 获取所有有模板的机型
  22. machines = db.execute(text("""
  23. SELECT machine_id, name
  24. FROM t_machine
  25. WHERE machine_id IN (SELECT DISTINCT machine_id FROM machine_temp)
  26. """)).fetchall()
  27. html = "<h2>估价模拟</h2>"
  28. html += '<form method="get" action="/estimate/simulate_one">'
  29. # 展示机型选择
  30. html += '<label>选择机型:</label><br>'
  31. html += '<select name="machine_id">'
  32. for machine in machines:
  33. html += f'<option value="{machine.machine_id}">{machine.name}</option>'
  34. html += '</select><br>'
  35. html += '<button type="submit">选择机型</button></form>'
  36. return html
  37. # ================= 获取机型模板和选项 =================
  38. @router.get("/simulate_one", response_class=HTMLResponse)
  39. def simulate(
  40. machine_id: int,
  41. db: Session = Depends(get_db)
  42. ):
  43. # 获取机型对应的模板
  44. row = db.execute(text("""
  45. SELECT estimate_packet
  46. FROM machine_temp
  47. WHERE machine_id=:mid
  48. """), {"mid": machine_id}).fetchone()
  49. tpl = json.loads(row.estimate_packet)
  50. html = f"<h2>估价模拟 - {tpl['templateId']}</h2>"
  51. html += '<form method="post" action="/estimate/simulate">'
  52. for step in tpl["template"]:
  53. html += f"<h3>{step['stepName']}</h3>"
  54. for p in step["properties"]:
  55. html += f"<b>{p['name']}</b><br>"
  56. for v in p["values"]:
  57. html += f"""
  58. <label>
  59. <input type="checkbox" name="option_ids" value="{v['valueId']}">
  60. {v['valueText']}
  61. </label><br>
  62. """
  63. html += f"""
  64. <input type="hidden" name="machine_id" value="{machine_id}">
  65. <br>
  66. <button type="submit">开始估价</button>
  67. </form>
  68. """
  69. return html
  70. # ================= 提交估价并计算价格 =================
  71. @router.post("/simulate", response_class=HTMLResponse)
  72. def simulate_calc(
  73. machine_id: int = Form(...),
  74. option_ids: list[str] = Form([]),
  75. db: Session = Depends(get_db)
  76. ):
  77. # 获取基准价格
  78. base_price_row = db.execute(text("""
  79. SELECT base_price
  80. FROM machine_base_price
  81. WHERE machine_id=:mid
  82. """), {"mid": machine_id}).fetchone()
  83. # base_price = base_price_row.base_price
  84. base_price = base_price_row.base_price if base_price_row else 1000.0
  85. # 获取选项因素
  86. factors = db.execute(text("""
  87. SELECT option_id, factor, absolute_deduct
  88. FROM price_option_factor
  89. WHERE option_id IN :ids
  90. """), {"ids": tuple(option_ids)}).fetchall()
  91. # 计算价格
  92. price = base_price
  93. for f in factors:
  94. if f.factor:
  95. price *= float(f.factor)
  96. if f.absolute_deduct:
  97. price -= float(f.absolute_deduct)
  98. # 逐步应用调节因子
  99. # 1. 获取全局调节因子
  100. global_adjust = db.execute(text("""
  101. SELECT factor FROM price_adjust_factor
  102. WHERE level='global'
  103. """)).fetchone()
  104. if global_adjust:
  105. price *= float(global_adjust.factor)
  106. # 2. 获取品牌调节因子
  107. brand_adjust = db.execute(text("""
  108. SELECT factor FROM price_adjust_factor
  109. WHERE level='brand'
  110. AND ref_id=(
  111. SELECT brand_id FROM t_machine WHERE machine_id=:mid
  112. )
  113. """), {"mid": machine_id}).fetchone()
  114. if brand_adjust:
  115. price *= float(brand_adjust.factor)
  116. # 3. 获取机型调节因子
  117. machine_adjust = db.execute(text("""
  118. SELECT factor FROM price_adjust_factor
  119. WHERE level='machine'
  120. AND ref_id=:mid
  121. """), {"mid": machine_id}).fetchone()
  122. if machine_adjust:
  123. price *= float(machine_adjust.factor)
  124. html = f"""
  125. <h2>估价结果</h2>
  126. 选择项:{",".join(option_ids)}<br>
  127. 估价:{round(price, 2)} 元<br>
  128. <a href="/simulate?machine_id={machine_id}">返回</a>
  129. """
  130. return html
  131. @router.post("/estimate2", response_class=HTMLResponse)
  132. def estimate_submit(
  133. request: Request,
  134. machine_id: int = Form(...),
  135. capacity: str = Form(...),
  136. option_ids: list[int] = Form([]),
  137. db: Session = Depends(get_db)
  138. ):
  139. # ---------- 基础价格 ----------
  140. base_price = db.execute(
  141. text("""
  142. SELECT base_price
  143. FROM machine_base_price
  144. WHERE machine_id=:mid AND capacity=:cap
  145. """),
  146. {"mid": machine_id, "cap": capacity}
  147. ).scalar() or 0
  148. price = float(base_price)
  149. # ---------- 扣减项 ----------
  150. for oid in option_ids:
  151. r = db.execute(
  152. text("""
  153. SELECT factor, absolute_deduct
  154. FROM price_option_factor
  155. WHERE option_id=:oid
  156. """),
  157. {"oid": oid}
  158. ).fetchone()
  159. if r:
  160. price = price * float(r.factor) - float(r.absolute_deduct)
  161. # ---------- 调节系数 ----------
  162. brand_id = db.execute(
  163. text("""
  164. SELECT brand_id
  165. FROM t_machine
  166. WHERE machine_id=:mid
  167. """),
  168. {"mid": machine_id}
  169. ).scalar()
  170. price = apply_adjust(db, machine_id, brand_id, price)
  171. price = round(max(price, 0), 2)
  172. # ---------- 估价版本 ----------
  173. version_no = str(uuid.uuid4())[:8]
  174. db.execute(
  175. text("""
  176. INSERT INTO estimate_record
  177. (machine_id, capacity, option_ids, final_price, version_no)
  178. VALUES (:m, :c, :o, :p, :v)
  179. """),
  180. {
  181. "m": machine_id,
  182. "c": capacity,
  183. "o": ",".join(map(str, option_ids)),
  184. "p": price,
  185. "v": version_no
  186. }
  187. )
  188. db.commit()
  189. # ---------- 模板(Redis 缓存) ----------
  190. cache_key = f"template:{machine_id}"
  191. cached = redis_client.get(cache_key) if redis_client else None
  192. if cached:
  193. template = json.loads(cached)
  194. else:
  195. rows = db.execute(text("SELECT * FROM release_option")).fetchall()
  196. template = build_template(rows)
  197. if redis_client:
  198. redis_client.set(cache_key, json.dumps(template), ex=3600)
  199. # ---------- 重新加载页面 ----------
  200. machines = db.execute(
  201. text("""
  202. SELECT machine_id, name
  203. FROM t_machine
  204. ORDER BY brand_name, name
  205. """)
  206. ).fetchall()
  207. capacities = db.execute(
  208. text("""
  209. SELECT capacity
  210. FROM machine_base_price
  211. WHERE machine_id=:mid
  212. """),
  213. {"mid": machine_id}
  214. ).fetchall()
  215. return request.app.state.templates.TemplateResponse(
  216. "estimate.html",
  217. {
  218. "request": request,
  219. "machines": machines,
  220. "capacities": capacities,
  221. "template": template,
  222. "price": price,
  223. "version": version_no,
  224. "selected_machine": machine_id,
  225. "selected_capacity": capacity
  226. }
  227. )