You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

157 lines
6.2 KiB

from __future__ import annotations
from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask.typing import ResponseReturnValue
from app.extensions import db
from app.services import (
check_share_token,
get_household_or_none,
household_has_active_gift_records,
is_head_name_taken,
list_enabled_options,
list_households,
normalize_search_term,
parse_admin_form,
recalculate_household_gift_summary,
serialize_admin_edit_snapshot,
write_audit_log,
)
share_bp = Blueprint("share", __name__, url_prefix="/s")
def _is_partial_request(expected_partial: str) -> bool:
requested_partial = request.args.get("partial", "").strip()
header_partial = request.headers.get("X-HW-Partial", "").strip()
return requested_partial == expected_partial or header_partial == expected_partial
@share_bp.get("/<string:token>")
def search_page(token: str) -> ResponseReturnValue:
check_result = check_share_token(token)
if not check_result.ok:
return render_template("share/invalid.html", reason=check_result.reason), 404
search_term = request.args.get("q", "").strip()
households = list_households(search_term=search_term, limit=20) if normalize_search_term(search_term) else []
template_name = "share/_results.html" if _is_partial_request("results") else "share/search.html"
return render_template(
template_name,
token=token,
search_term=search_term,
households=households,
)
@share_bp.get("/<string:token>/households/<int:household_id>/edit")
def edit_page(token: str, household_id: int) -> ResponseReturnValue:
check_result = check_share_token(token)
if not check_result.ok:
return render_template("share/invalid.html", reason=check_result.reason), 404
household = get_household_or_none(household_id)
if household is None:
flash("未找到要修改的户信息。", "error")
return redirect(url_for("share.search_page", token=token))
return render_template(
"share/edit.html",
token=token,
household=household,
search_term=request.args.get("q", "").strip(),
relation_category_options=list_enabled_options("relation_category"),
tag_options=list_enabled_options("tag"),
gift_method_options=list_enabled_options("gift_method"),
gift_scene_options=list_enabled_options("gift_scene"),
)
@share_bp.post("/<string:token>/households/<int:household_id>/edit")
def update_household(token: str, household_id: int) -> ResponseReturnValue:
check_result = check_share_token(token)
if not check_result.ok:
return render_template("share/invalid.html", reason=check_result.reason), 404
household = get_household_or_none(household_id)
if household is None:
flash("未找到要修改的户信息。", "error")
return redirect(url_for("share.search_page", token=token))
search_term = request.form.get("q", "").strip()
redirect_to_search = url_for("share.search_page", token=token, q=search_term) if search_term else url_for("share.search_page", token=token)
redirect_to_self = (
url_for("share.edit_page", token=token, household_id=household_id, q=search_term)
if search_term
else url_for("share.edit_page", token=token, household_id=household_id)
)
submitted_version = request.form.get("version", "").strip()
try:
version = int(submitted_version)
except ValueError:
flash("表单版本无效,请重新打开该户信息后再试。", "error")
return redirect(redirect_to_self)
if version != household.version:
flash("该户信息已被其他人更新,请刷新后重新确认再保存。", "warning")
return redirect(redirect_to_self)
relation_category_options = list_enabled_options("relation_category")
tag_options = list_enabled_options("tag")
gift_method_options = list_enabled_options("gift_method")
gift_scene_options = list_enabled_options("gift_scene")
payload, errors = parse_admin_form(
dict(request.form),
raw_tag_option_ids=request.form.getlist("tag_option_ids_json"),
valid_relation_category_ids={option.id for option in relation_category_options},
relation_detail_parent_map={},
valid_tag_ids={option.id for option in tag_options},
valid_method_ids={option.id for option in gift_method_options},
valid_scene_ids={option.id for option in gift_scene_options},
)
if errors:
for error in errors:
flash(error, "error")
return redirect(redirect_to_self)
head_name = str(payload["head_name"])
if is_head_name_taken(head_name, excluding_household_id=household_id):
flash("户主姓名已存在,请修改后重试。", "error")
return redirect(redirect_to_self)
if household_has_active_gift_records(household_id):
payload.pop("total_gift_amount", None)
payload.pop("gift_method_option_id", None)
payload.pop("gift_scene_option_id", None)
before_snapshot = serialize_admin_edit_snapshot(household)
for field_name, value in payload.items():
setattr(household, field_name, value)
if household_has_active_gift_records(household_id):
recalculate_household_gift_summary(household)
if serialize_admin_edit_snapshot(household) == before_snapshot:
flash("没有检测到需要保存的变化。", "warning")
db.session.rollback()
return redirect(redirect_to_search)
household.version += 1
share_link = check_result.link
write_audit_log(
action_type="update_household_share_link",
target_type="household",
actor_username=f"share:{share_link.token}" if share_link is not None else "share",
target_id=household.id,
target_display_name=household.head_name,
before_data=before_snapshot,
after_data=serialize_admin_edit_snapshot(household) | {
"share_link_id": share_link.id if share_link is not None else None,
"share_link_label": share_link.label if share_link is not None else None,
},
commit=False,
)
db.session.commit()
flash(f"已保存 {household.head_name} 的信息。", "success")
return redirect(redirect_to_search)