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
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)
|
|
|