| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843 |
- from fastapi import APIRouter, Request, Depends, Form
- from fastapi.responses import HTMLResponse
- from sqlalchemy.orm import Session
- from sqlalchemy import text
- from database import SessionLocal, redis_client
- from services.template_service import build_template
- from services.price_service import apply_adjust
- from fastapi.templating import Jinja2Templates
- import json
- import uuid
- router = APIRouter(prefix="/estimate", tags=["estimate"])
- templates = Jinja2Templates(directory="templates")
- # ================= DB =================
- def get_db():
- db = SessionLocal()
- try:
- yield db
- finally:
- db.close()
- # ================= 获取所有机型 =================
- @router.get("/simulate", response_class=HTMLResponse)
- def simulate(db: Session = Depends(get_db)):
- # 获取所有有模板的机型
- machines = db.execute(text("""
- SELECT machine_id, name
- FROM t_machine
- WHERE machine_id IN (SELECT DISTINCT machine_id FROM machine_temp)
- """)).fetchall()
- html = "<h2>估价模拟</h2>"
- html += '<form method="get" action="/estimate/simulate_one">'
- # 展示机型选择
- html += '<label>选择机型:</label><br>'
- html += '<select name="machine_id">'
- for machine in machines:
- html += f'<option value="{machine.machine_id}">{machine.name}</option>'
- html += '</select><br>'
- html += '<button type="submit">选择机型</button></form>'
- return html
- # ================= 获取机型模板和选项 =================
- @router.get("/simulate_one_del", response_class=HTMLResponse)
- def simulate(
- machine_id: int,
- db: Session = Depends(get_db)
- ):
- # 获取机型对应的模板
- row = db.execute(text("""
- SELECT estimate_packet
- FROM machine_temp
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- html = f"<h2>估价模拟 - {tpl['templateId']}</h2>"
- html += '<form method="post" action="/estimate/simulate">'
- for step in tpl["template"]:
- html += f"<h3>{step['stepName']}</h3>"
- for p in step["properties"]:
- html += f"<b>{p['name']}</b><br>"
- for v in p["values"]:
- html += f"""
- <label>
- <input type="checkbox" name="option_ids" value="{v['valueId']}">
- {v['valueText']}
- </label><br>
- """
- html += f"""
- <input type="hidden" name="machine_id" value="{machine_id}">
- <br>
- <button type="submit">开始估价</button>
- </form>
- """
- return html
- @router.get("/simulate_one", response_class=HTMLResponse)
- def simulate_one_del2(
- request: Request,
- machine_id: int,
- db: Session = Depends(get_db)
- ):
- row = db.execute(text("""
- SELECT m.name AS machine_name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- if not row:
- return HTMLResponse("未找到机型模板", status_code=404)
-
- # print("*************row****************")
- # print(row)
- tpl = json.loads(row.estimate_packet)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.machine_name,
- "tpl": tpl
- }
- )
- # ================= 提交估价并计算价格 =================
- @router.post("/simulate_del", response_class=HTMLResponse)
- def simulate_calc(
- machine_id: int = Form(...),
- option_ids: list[str] = Form([]),
- db: Session = Depends(get_db)
- ):
- # 获取基准价格
- base_price_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- # base_price = base_price_row.base_price
- base_price = base_price_row.base_price if base_price_row else 1000.0
- # 获取选项因素
- factors = db.execute(text("""
- SELECT option_id, factor, absolute_deduct
- FROM price_option_factor
- WHERE option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- # 计算价格
- price = base_price
- for f in factors:
- if f.factor:
- price *= (1-float(f.factor))
- if f.absolute_deduct:
- price -= float(f.absolute_deduct)
- # 逐步应用调节因子
- # 1. 获取全局调节因子
- global_adjust = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='global'
- """)).fetchone()
- if global_adjust:
- price *= float(global_adjust.factor)
- # 2. 获取品牌调节因子
- brand_adjust = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id FROM t_machine WHERE machine_id=:mid
- )
- """), {"mid": machine_id}).fetchone()
- if brand_adjust:
- price *= float(brand_adjust.factor)
- # 3. 获取机型调节因子
- machine_adjust = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- """), {"mid": machine_id}).fetchone()
- if machine_adjust:
- price *= float(machine_adjust.factor)
- html = f"""
- <h2>估价结果</h2>
- 选择项:{",".join(option_ids)}<br>
- 估价:{round(price, 2)} 元<br>
- <a href="/simulate?machine_id={machine_id}">返回</a>
- """
- return html
- @router.post("/simulate_de3", response_class=HTMLResponse)
- def simulate_calc(
- request: Request,
- machine_id: int = Form(...),
- option_ids: list[str] = Form([]),
- db: Session = Depends(get_db)
- ):
- # 机型名称
- machine = db.execute(text("""
- SELECT name, brand_name
- FROM t_machine
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- # 模板
- row = db.execute(text("""
- SELECT estimate_packet
- FROM machine_temp
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- # ========== 基准价 ==========
- base_price_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- base_price = float(base_price_row.base_price) if base_price_row else 1000.0
- price = base_price
- detail_rows = []
- # ========== 选项扣减 ==========
- if option_ids:
- factors = db.execute(text("""
- SELECT
- pf.option_id,
- pf.factor,
- pf.absolute_deduct,
- ro.option_name
- FROM price_option_factor pf
- LEFT JOIN release_option ro
- ON pf.option_id = ro.option_id
- WHERE pf.option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- else:
- factors = []
- for f in factors:
- before = price
- if f.factor is not None:
- price *= float(f.factor)
- if f.absolute_deduct is not None:
- price -= float(f.absolute_deduct)
- detail_rows.append({
- "type": "option",
- "name": f.option_name or str(f.option_id),
- "option_id": f.option_id,
- "factor": f.factor,
- "absolute_deduct": f.absolute_deduct,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ========== 全局调节 ==========
- global_adjust = db.execute(text("""
- SELECT factor
- FROM price_adjust_factor
- WHERE level='global'
- LIMIT 1
- """)).fetchone()
- if global_adjust:
- before = price
- price *= float(global_adjust.factor)
- detail_rows.append({
- "type": "adjust",
- "name": "全局调节",
- "factor": global_adjust.factor,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ========== 品牌调节 ==========
- brand_adjust = db.execute(text("""
- SELECT factor
- FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id FROM t_machine WHERE machine_id=:mid
- )
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if brand_adjust:
- before = price
- price *= float(brand_adjust.factor)
- detail_rows.append({
- "type": "adjust",
- "name": "品牌调节",
- "factor": brand_adjust.factor,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ========== 机型调节 ==========
- machine_adjust = db.execute(text("""
- SELECT factor
- FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if machine_adjust:
- before = price
- price *= float(machine_adjust.factor)
- detail_rows.append({
- "type": "adjust",
- "name": "机型调节",
- "factor": machine_adjust.factor,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- final_price = round(price, 2)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "tpl": tpl,
- "machine": machine,
- "machine_id": machine_id,
- "selected_option_ids": option_ids,
- "base_price": round(base_price, 2),
- "detail_rows": detail_rows,
- "final_price": final_price
- }
- )
- @router.get("/simulate_one", response_class=HTMLResponse)
- def simulate_one(
- request: Request,
- machine_id: int,
- db: Session = Depends(get_db)
- ):
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.name,
- "tpl": tpl,
- "result": None
- }
- )
- # ================= 估价 =================
- @router.post("/simulate666", response_class=HTMLResponse)
- async def simulate_calc(
- request: Request,
- machine_id: int = Form(...),
- db: Session = Depends(get_db)
- ):
- form = await request.form()
- # ---------------- 收集 option ----------------
- option_ids = []
- for k, v in form.multi_items():
- if k.startswith("option_"):
- option_ids.append(str(v))
- # ---------------- 重新取模板与机型名 ----------------
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- if not row:
- return HTMLResponse("机型模板不存在")
- tpl = json.loads(row.estimate_packet)
- # ---------------- 构造 valueId -> valueText 映射 ----------------
- value_name_map = {}
- for step in tpl["template"]:
- for p in step["properties"]:
- for v in p["values"]:
- value_name_map[str(v["valueId"])] = v["valueText"]
- # ---------------- 基准价 ----------------
- base_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- base_price = float(base_row.base_price) if base_row else 1000.0
- # ---------------- 选项因子 ----------------
- detail_rows = []
- if option_ids:
- rows = db.execute(text("""
- SELECT option_id, factor, absolute_deduct
- FROM price_option_factor
- WHERE option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- else:
- rows = []
- price = base_price
- for r in rows:
- before = price
- if r.factor is not None:
- price = price * float(r.factor)
- if r.absolute_deduct is not None:
- price = price - float(r.absolute_deduct)
- detail_rows.append({
- "option_id": r.option_id,
- "option_name": value_name_map.get(str(r.option_id), str(r.option_id)),
- "factor": r.factor,
- "absolute": r.absolute_deduct,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ---------------- 调节因子 ----------------
- # 全局
- g = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='global'
- LIMIT 1
- """)).fetchone()
- if g:
- before = price
- price = price * float(g.factor)
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "全局调节",
- "factor": g.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 品牌
- b = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id
- FROM t_machine
- WHERE machine_id=:mid
- )
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if b:
- before = price
- price = price * float(b.factor)
- detail_rows.append({
- "option_id": "BRAND",
- "option_name": "品牌调节",
- "factor": b.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 机型
- m = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if m:
- before = price
- price = price * float(m.factor)
- detail_rows.append({
- "option_id": "MACHINE",
- "option_name": "机型调节",
- "factor": m.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.name,
- "tpl": tpl,
- "result": {
- "base_price": round(base_price, 2),
- "final_price": round(price, 2),
- "details": detail_rows,
- "selected": option_ids
- }
- }
- )
- @router.post("/simulate", response_class=HTMLResponse)
- async def simulate_calc(
- request: Request,
- machine_id: int = Form(...),
- db: Session = Depends(get_db)
- ):
- form = await request.form()
- # 收集所有 option
- option_ids = []
- for k, v in form.multi_items():
- if k.startswith("option_"):
- option_ids.append(str(v))
- # ---------------- 重新取模板与机型名 ----------------
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- if not row:
- return HTMLResponse("机型模板不存在")
- tpl = json.loads(row.estimate_packet)
- # ---------------- 构造 valueId -> valueText 映射 ----------------
- value_name_map = {}
- for step in tpl["template"]:
- for p in step["properties"]:
- for v in p["values"]:
- value_name_map[str(v["valueId"])] = v["valueText"]
- # ---------------- 基准价 ----------------
- base_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- base_price_input = form.get("base_price")
- if base_price_input and str(base_price_input).strip():
- base_price = float(base_price_input)
- else:
- base_price = float(base_row.base_price) if base_row else 1
- # ---------------- 选项因子 ----------------
- detail_rows = []
- if option_ids:
- rows = db.execute(text("""
- SELECT option_id, factor, absolute_deduct
- FROM price_option_factor
- WHERE option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- else:
- rows = []
- price = base_price
- total_deduct_rate = 0
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "-- 以下是扣减比例,累加",
- "factor": 0,
- "absolute": None,
- "before": None,
- "after": None
- })
- for r in rows:
- before = price
- if r.factor:
- total_deduct_rate += float(r.factor)
- # price = price * (1-float(r.factor))
- # if r.absolute_deduct:
- # price = price - float(r.absolute_deduct)
- detail_rows.append({
- "option_id": r.option_id,
- "option_name": value_name_map.get(str(r.option_id), str(r.option_id)),
- "factor": r.factor,
- "absolute": r.absolute_deduct,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- if total_deduct_rate:
- price = price * (1 - total_deduct_rate)
- price = max(price, 0.0)
- # ---------------- 调节因子 ----------------
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "-- 以下是调节因子,累乘",
- "factor": 0,
- "absolute": None,
- "before": None,
- "after": None
- })
- # 全局
- g = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='global'
- LIMIT 1
- """)).fetchone()
- if g:
- before = price
- price = price * float(g.factor)
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "全局调节系数",
- "factor": g.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 品牌
- b = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id
- FROM t_machine
- WHERE machine_id=:mid
- )
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if b:
- before = price
- price = price * float(b.factor)
- detail_rows.append({
- "option_id": "BRAND",
- "option_name": "品牌调节系数",
- "factor": b.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 机型
- m = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if m:
- before = price
- price = price * float(m.factor)
- detail_rows.append({
- "option_id": "MACHINE",
- "option_name": "机型调节系数",
- "factor": m.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 重新取模板与机型名
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.name,
- "tpl": tpl,
- "result": {
- "base_price": round(base_price, 2),
- "final_price": round(price, 2),
- "details": detail_rows,
- "selected": option_ids
- }
- }
- )
- @router.post("/estimate2", response_class=HTMLResponse)
- def estimate_submit(
- request: Request,
- machine_id: int = Form(...),
- capacity: str = Form(...),
- option_ids: list[int] = Form([]),
- db: Session = Depends(get_db)
- ):
- # ---------- 基础价格 ----------
- base_price = db.execute(
- text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid AND capacity=:cap
- """),
- {"mid": machine_id, "cap": capacity}
- ).scalar() or 0
- price = float(base_price)
- # ---------- 扣减项 ----------
- for oid in option_ids:
- r = db.execute(
- text("""
- SELECT factor, absolute_deduct
- FROM price_option_factor
- WHERE option_id=:oid
- """),
- {"oid": oid}
- ).fetchone()
- if r:
- price = price * float(r.factor) - float(r.absolute_deduct)
- # ---------- 调节系数 ----------
- brand_id = db.execute(
- text("""
- SELECT brand_id
- FROM t_machine
- WHERE machine_id=:mid
- """),
- {"mid": machine_id}
- ).scalar()
- price = apply_adjust(db, machine_id, brand_id, price)
- price = round(max(price, 0), 2)
- # ---------- 估价版本 ----------
- version_no = str(uuid.uuid4())[:8]
- db.execute(
- text("""
- INSERT INTO estimate_record
- (machine_id, capacity, option_ids, final_price, version_no)
- VALUES (:m, :c, :o, :p, :v)
- """),
- {
- "m": machine_id,
- "c": capacity,
- "o": ",".join(map(str, option_ids)),
- "p": price,
- "v": version_no
- }
- )
- db.commit()
- # ---------- 模板(Redis 缓存) ----------
- cache_key = f"template:{machine_id}"
- cached = redis_client.get(cache_key) if redis_client else None
- if cached:
- template = json.loads(cached)
- else:
- rows = db.execute(text("SELECT * FROM release_option")).fetchall()
- template = build_template(rows)
- if redis_client:
- redis_client.set(cache_key, json.dumps(template), ex=3600)
- # ---------- 重新加载页面 ----------
- machines = db.execute(
- text("""
- SELECT machine_id, name
- FROM t_machine
- ORDER BY brand_name, name
- """)
- ).fetchall()
- capacities = db.execute(
- text("""
- SELECT capacity
- FROM machine_base_price
- WHERE machine_id=:mid
- """),
- {"mid": machine_id}
- ).fetchall()
- return request.app.state.templates.TemplateResponse(
- "estimate.html",
- {
- "request": request,
- "machines": machines,
- "capacities": capacities,
- "template": template,
- "price": price,
- "version": version_no,
- "selected_machine": machine_id,
- "selected_capacity": capacity
- }
- )
|