"""业务表单参照只返回必要字段,不借下拉权限开放整个资料接口。""" from sqlalchemy import func, select from app.core.exceptions import BadRequestError, ForbiddenError from app.models import ( Business, BusinessRole, CalcTemplateDefinition, Customer, CustomerRatingGrade, Personnel, Product, Shipment, ) from app.services.authorization_service import calculate OPTIONS = { "managers": (Personnel, ("id", "personnel_name", "business_role_id", "status"), ( "base:personnel:view", "base:personnel:create", "base:personnel:update", )), "rating_grades": (CustomerRatingGrade, ("id", "grade_name", "min_score", "max_score", "sort_order", "remark"), ( "base:customers:view", "base:customers:rating", "business:ratings:view", )), "personnel": (Personnel, ("id", "personnel_name", "business_role_id", "status"), ( "base:personnel:view", "base:customers:view", "base:customers:create", "base:customers:update", "base:customers:ownership", "business:businesses:view", "business:businesses:create", "business:businesses:update", "business:businesses:ownership", "payroll:prepare", "adjustments:create", "payroll:view", "reports:personal:view", "reports:department:view", "reports:business:view", "reports:payments:view", )), "customers": (Customer, ("id", "company_name", "customer_type"), ( "base:customers:view", "business:businesses:view", "business:businesses:create", "business:businesses:update", "reports:business:view", )), "products": (Product, ("id", "product_name", "spec_model", "unit"), ( "base:products:view", "business:businesses:create", "business:businesses:update", "business:shipments:create", "business:shipments:update", )), "businesses": (Business, ("id", "business_no", "status"), ( "business:businesses:view", "business:shipments:view", "business:shipments:create", "business:shipments:update", "business:receipts:view", "business:receipts:create", "business:receipts:update", )), "shipments": (Shipment, ("id", "shipment_no", "business_id", "ship_date"), ( "business:shipments:view", "business:receipts:view", "business:receipts:create", "business:receipts:update", )), "business_roles": (BusinessRole, ("id", "role_code", "role_name", "status"), ( "base:personnel:view", "base:personnel:create", "base:personnel:update", "system:business_roles:view", "system:params:view", "reports:personal:view", "reports:department:view", "reports:business:view", "reports:payments:view", )), } def payroll_options(session, actor, business_id, *, page_num=1, page_size=200): """核算资料所需的人员及发货明细,不开放人员薪酬或联系方式。""" from app.core.exceptions import NotFoundError from app.models import ShipmentItem, TPlusBusinessDocument, TPlusSourceLink, TPlusSyncRecord from app.services.authorization_service import require_permission from app.services.tplus.guidance import latest_guidance_facts, resolve_guidance if page_num < 1 or not 1 <= page_size <= 200: raise ValueError("无效的核算参照分页") require_permission(session, actor, "payroll:view") business = session.get(Business, business_id) if business is None: raise NotFoundError("业务不存在") source_customer = (business.integration_json or {}).get("customer_source_id") offset = (page_num - 1) * page_size people = session.execute(select(Personnel.id, Personnel.personnel_name).order_by(Personnel.id) .offset(offset).limit(page_size)).mappings() lines_query = select(ShipmentItem.id, Shipment.shipment_no, Shipment.ship_date, ShipmentItem.product_name, ShipmentItem.amount, ShipmentItem.unit).join( Shipment, Shipment.id == ShipmentItem.shipment_id).where(Shipment.business_id == business_id) lines = list(session.execute(lines_query.order_by(Shipment.id, ShipmentItem.id).offset(offset).limit(page_size)).mappings()) source = (business.integration_json or {}).get("source_database") candidates = {} if source and lines: documents = session.scalars(select(TPlusBusinessDocument).where( TPlusBusinessDocument.business_id == business_id, TPlusBusinessDocument.source_database == source, TPlusBusinessDocument.entity_type == "shipment_items", TPlusBusinessDocument.status == "APPLIED", TPlusBusinessDocument.target_id.in_([row["id"] for row in lines]), )) for document in documents: candidates.setdefault(document.target_id, []).append(document) person_sources = {str(data["source_personnel_id"]) for values in candidates.values() for doc in values if (data := doc.data_json or {}).get("source_personnel_id") is not None} links = dict(session.execute(select(TPlusSourceLink.source_id, Personnel.id).join( Personnel, Personnel.id == TPlusSourceLink.target_id).where( TPlusSourceLink.source_database == source, TPlusSourceLink.entity_type == "personnel", TPlusSourceLink.source_id.in_(person_sources))).all()) if person_sources else {} record_ids = {doc.record_id for values in candidates.values() for doc in values} records = {r.id: r for r in session.scalars(select(TPlusSyncRecord).where( TPlusSyncRecord.id.in_(record_ids)))} if record_ids else {} guidance_facts = latest_guidance_facts(session, source) if record_ids else {} def source_details(row): matches = candidates.get(row["id"], []) document = matches[0] if len(matches) == 1 else None data = (document.data_json or {}) if document else {} source_id = data.get("source_personnel_id") record = records.get(document.record_id) if document else None product_id = record.payload_json.get("idinventory") if record else None status, guidance = resolve_guidance(guidance_facts, str(product_id), document.source_id, row["ship_date"].strftime("%Y-%m"), row["unit"]) if product_id is not None else ("MISSING", None) if guidance: status = "FINALIZED_" + guidance["basis"] return {"source_personnel_name": data.get("source_personnel_name"), "matched_personnel_id": links.get(str(source_id)) if source_id is not None else None, "unit": row["unit"], "guidance_status": status, "guidance_unit_cost": guidance["amount"] if status == "FINALIZED_UNIT" else None, "guidance_line_total": guidance["amount"] if status == "FINALIZED_LINE_TOTAL" else None} return {"business_key": f"customer:{source_customer}" if source_customer is not None else None, "personnel": [dict(row) for row in people], "personnel_total": session.scalar(select(func.count()).select_from(Personnel)), "shipments_total": session.scalar(select(func.count()).select_from(ShipmentItem).join( Shipment, Shipment.id == ShipmentItem.shipment_id).where(Shipment.business_id == business_id)), "shipments": [{"id": row["id"], "shipment_no": row["shipment_no"], "ship_date": str(row["ship_date"]), "product_name": row["product_name"], "amount": str(row["amount"]), **source_details(row)} for row in lines]} def options(session, actor, resource, *, business_id=None, search=None): definition = OPTIONS.get(resource) if definition is None: raise BadRequestError("不支持的参照类型") model, fields, callers = definition if not set(calculate(session, actor)["permissions"]) & set(callers): raise ForbiddenError("没有使用此参照数据的业务权限") statement = select(*(getattr(model, f) for f in fields)) if resource == "managers": statement = ( statement.join(BusinessRole, BusinessRole.id == Personnel.business_role_id) .join(CalcTemplateDefinition, CalcTemplateDefinition.template_code == BusinessRole.calc_template) .where( Personnel.status == 1, BusinessRole.status == 1, CalcTemplateDefinition.formula_type == "manager_share", ) ) if business_id is not None and model == Shipment: statement = statement.where(Shipment.business_id == business_id) if search: name = getattr(model, fields[1]) escaped = search.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") statement = statement.where(name.like(f"%{escaped}%", escape="\\")) order = (model.sort_order, model.id) if model == CustomerRatingGrade else (model.id,) rows = session.execute(statement.order_by(*order).limit(200)).mappings() return [dict(row) for row in rows]