From b47c809300d2e73561e0b8e12ed3faaa19e0d126 Mon Sep 17 00:00:00 2001 From: zlwu Date: Thu, 3 Sep 2026 16:30:31 +0800 Subject: [PATCH] feat(backend): gate archive writes and mask plan prices by menu code Co-authored-by: Cursor --- backend/app/api/pl/plan.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/app/api/pl/plan.py b/backend/app/api/pl/plan.py index 427f3073f..c07219f4e 100644 --- a/backend/app/api/pl/plan.py +++ b/backend/app/api/pl/plan.py @@ -102,6 +102,11 @@ from app.services.pl_plan_archive_stop_service import ( sync_plan_products_from_stop_archive, ) from app.services.pl_plan_archive_export import build_plan_archive_export_rows +from app.services.pl_plan_price_mask import ( + mask_plan_with_products_prices, + user_can_archive_plan, + user_can_view_archive_prices, +) from app.services.pl_plan_list_attachments import load_plan_document_attachments_by_plan_ids from app.services.pl_plan_print_service import get_delivery_slip_print_context from app.services.pl_plan_stop_service import ( @@ -529,6 +534,7 @@ def list_plans( plan_ids=plan_ids, ) stops_by_plan = load_plan_stops_grouped(db, tenant_no, plan_ids) + can_price = user_can_view_archive_prices(db, user) result_list: list[PlanWithProducts] = [] for plan in plans: @@ -583,6 +589,7 @@ def list_plans( plan, stops_by_plan.get(plan.id, (None, [])), ) + plan_item = mask_plan_with_products_prices(plan_item, enabled=not can_price) result_list.append(plan_item) return PlanListResponse(total=total, list=result_list) @@ -740,6 +747,8 @@ def export_plans( transports_by_id = _load_transports_by_id(db, tenant_no, transport_ids) items = build_plan_archive_export_rows(db, tenant_no, plans, transports_by_id) cols = filter_export_columns(PLAN_ARCHIVE_EXPORT_COLUMNS, body.columns) + if not user_can_view_archive_prices(db, user): + cols = [c for c in cols if c.field not in ARCHIVE_PRICE_EXPORT_FIELDS] sheet_title = "计划归档" filename_stem = f"计划归档_{date_str}" else: @@ -1474,7 +1483,8 @@ def get_plan( result.demander_contracts = demander_contracts stops = load_plan_stops_grouped(db, tenant_no, [id]).get(id, (None, [])) attach_plan_stops_to_item(result, plan, stops) - return result + can_price = user_can_view_archive_prices(db, user) + return mask_plan_with_products_prices(result, enabled=not can_price) # ============================================================================= @@ -2040,6 +2050,9 @@ def cancel_plan_wait_notice( FORCE_FOLLOW_STATUS_PERM = "pl:plan:force-follow-status" +ARCHIVE_PRICE_EXPORT_FIELDS = frozenset( + {"unitPrice", "amount", "freightPrice", "freightAmount"} +) ALLOWED_FORCE_FOLLOW_STATUSES = frozenset( {"drafting", "wait_notice", "pending_arrange", "arranged", "completed", "cancelled"} ) @@ -2150,6 +2163,8 @@ def archive_plan_draft( user: DbUserContext = Depends(get_db_current_user), ) -> PlanWithProducts: """暂存归档填写内容:不改库存、不标已归档。""" + if not user_can_archive_plan(db, user): + raise HTTPException(status_code=403, detail="无运输计划归档操作权限") tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) plan = _get_plan(db, tenant_no, id, user) @@ -2228,6 +2243,8 @@ def archive_plan( db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> PlanWithProducts: + if not user_can_archive_plan(db, user): + raise HTTPException(status_code=403, detail="无运输计划归档操作权限") tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) plan = _get_plan(db, tenant_no, id, user)