Преглед на файлове

feat(facetoface): 新增当面付代开通功能

代商家开通当面付收单产品,入口嵌入企业管理页面操作列。
后端实现 agent.create → facetoface.sign → agent.confirm 三步提交,
定时任务每30分钟自动轮询申请单状态。
alphah преди 1 седмица
родител
ревизия
b597cf0ec0
променени са 100 файла, в които са добавени 722 реда и са изтрити 10 реда
  1. 1 1
      backend/app/plugin/module_payment/__init__.py
  2. 21 0
      backend/app/plugin/module_payment/facetoface/__init__.py
  3. 163 0
      backend/app/plugin/module_payment/facetoface/controller.py
  4. 43 0
      backend/app/plugin/module_payment/facetoface/crud.py
  5. 12 0
      backend/app/plugin/module_payment/facetoface/enums.py
  6. 79 0
      backend/app/plugin/module_payment/facetoface/model.py
  7. 66 0
      backend/app/plugin/module_payment/facetoface/schema.py
  8. 329 0
      backend/app/plugin/module_payment/facetoface/service.py
  9. 0 0
      frontend/dist/css/index.C87XjJcL.css
  10. 0 0
      frontend/dist/css/system.sp6bqO9U.css
  11. 0 0
      frontend/dist/css/xml.BzmjZvX7.css
  12. 3 3
      frontend/dist/index.html
  13. 0 0
      frontend/dist/js/401.Bohubu6V.js
  14. 0 0
      frontend/dist/js/404.BCOe6gUw.js
  15. 0 0
      frontend/dist/js/AccountOverview.CsMP2ej5.js
  16. 0 0
      frontend/dist/js/ChatInput.BOTkECqC.js
  17. 0 0
      frontend/dist/js/ChatMessages.BqVjWx14.js
  18. 0 0
      frontend/dist/js/ChatNavbar.B7vq6l9_.js
  19. 1 1
      frontend/dist/js/ConfigInfoDrawer.DOwsjtUd.js
  20. 0 0
      frontend/dist/js/ConsumeDetail.B9B7mG8S.js
  21. 0 0
      frontend/dist/js/CreateTableDialog.NIelA4pP.js
  22. 0 0
      frontend/dist/js/DataDrawer.DHSCvnHU.js
  23. 0 0
      frontend/dist/js/DataDrawer.DeMcy8wU.js
  24. 0 0
      frontend/dist/js/DepartmentDetail.IX8nNF5d.js
  25. 0 0
      frontend/dist/js/DepartmentForm.GcE6P-YY.js
  26. 0 0
      frontend/dist/js/DeptTree.nzsxYta4.js
  27. 0 0
      frontend/dist/js/EdgeConfigPanel.CqmGyMAo.js
  28. 0 0
      frontend/dist/js/EmployeeForm.CenHCFpl.js
  29. 1 1
      frontend/dist/js/ExternalLink.Cn3YP1oI.js
  30. 0 0
      frontend/dist/js/GenBasicStep.27KhLIBy.js
  31. 0 0
      frontend/dist/js/GenCodeDrawer.BZ6p8b0P.js
  32. 0 0
      frontend/dist/js/GenCodeDrawer.D4kw7CkR.js
  33. 0 0
      frontend/dist/js/GenColumnsStep.BziHzEbG.js
  34. 0 0
      frontend/dist/js/GenPreviewStep.CbncBiUk.js
  35. 0 0
      frontend/dist/js/GencodeHelpPanel.Azeofp20.js
  36. 0 0
      frontend/dist/js/ImportDbTableDialog.DXZ1WUPp.js
  37. 0 0
      frontend/dist/js/InstitutionDetail.CGn4wsst.js
  38. 0 0
      frontend/dist/js/InstitutionDetail.RoEnI7gP.js
  39. 0 0
      frontend/dist/js/InstitutionForm.CgZk9XYe.js
  40. 0 0
      frontend/dist/js/InviteDialog.DhhBZRku.js
  41. 0 0
      frontend/dist/js/IssueBatchForm.Dtgx5mnv.js
  42. 0 0
      frontend/dist/js/Login.CKgAj_l2.js
  43. 0 0
      frontend/dist/js/MessageItem.B0q5UvpY.js
  44. 0 0
      frontend/dist/js/NodeConfigPanel.Dl_wDBb2.js
  45. 0 0
      frontend/dist/js/PageContent.kwKvxA-S.js
  46. 0 0
      frontend/dist/js/QuotaList.RBE7G8SP.js
  47. 0 0
      frontend/dist/js/QuotaList.wB5GldbV.js
  48. 0 0
      frontend/dist/js/RuleForm.CNSWNA58.js
  49. 0 0
      frontend/dist/js/RuleList.DpsWbUEC.js
  50. 0 0
      frontend/dist/js/RuleList.rx6vCgrT.js
  51. 0 0
      frontend/dist/js/ScopeDialog.B086Gh_W.js
  52. 0 0
      frontend/dist/js/Sidebar.CypHgURH.js
  53. 0 0
      frontend/dist/js/TransferDetail.DYjD7LMI.js
  54. 0 0
      frontend/dist/js/UserTableSelect.1De4CctK.js
  55. 0 0
      frontend/dist/js/WelcomeScreen.UILgR-rM.js
  56. 0 0
      frontend/dist/js/WorkflowDesignDrawer.DycohyV2.js
  57. 0 0
      frontend/dist/js/api.C_KCUm-p.js
  58. 0 0
      frontend/dist/js/element-plus.DYHj_7Hl.js
  59. 0 0
      frontend/dist/js/github.B0B3B1or.js
  60. 1 0
      frontend/dist/js/index copy.-8vOp1Bn.js
  61. 0 1
      frontend/dist/js/index copy.ByQOjuSt.js
  62. 0 0
      frontend/dist/js/index.-WglgRN2.js
  63. 0 0
      frontend/dist/js/index.0eAFd7Dw.js
  64. 0 0
      frontend/dist/js/index.2113wD7B.js
  65. 0 0
      frontend/dist/js/index.7a2lZ-RL.js
  66. 0 0
      frontend/dist/js/index.8LzqRHhY.js
  67. 0 1
      frontend/dist/js/index.B-TQyOp9.js
  68. 0 0
      frontend/dist/js/index.B0spg9hN.js
  69. 0 0
      frontend/dist/js/index.B1tf_lRg.js
  70. 0 0
      frontend/dist/js/index.B7VX8PXE.js
  71. 0 0
      frontend/dist/js/index.BGLlPl3d.js
  72. 0 0
      frontend/dist/js/index.BJY7utdf.js
  73. 0 0
      frontend/dist/js/index.B_-iaV5k.js
  74. 0 0
      frontend/dist/js/index.B_c9KDco.js
  75. 0 0
      frontend/dist/js/index.Bdr50yky.js
  76. 0 0
      frontend/dist/js/index.BevQ8M7-.js
  77. 0 0
      frontend/dist/js/index.BiXRzGoZ.js
  78. 0 0
      frontend/dist/js/index.Bl-ZxAAB.js
  79. 0 0
      frontend/dist/js/index.Bn6SvpZe.js
  80. 0 0
      frontend/dist/js/index.BqMt-nvY.js
  81. 1 1
      frontend/dist/js/index.BrfkZJnm.js
  82. 0 0
      frontend/dist/js/index.Bv09U9ef.js
  83. 0 0
      frontend/dist/js/index.ByPFzEOR.js
  84. 0 0
      frontend/dist/js/index.CHHtFWd9.js
  85. 0 0
      frontend/dist/js/index.CIOxBBPC.js
  86. 0 0
      frontend/dist/js/index.CPZfnKUs.js
  87. 0 0
      frontend/dist/js/index.CYKRX2m3.js
  88. 0 0
      frontend/dist/js/index.Cb8xBfNb.js
  89. 0 1
      frontend/dist/js/index.CiuTkDqZ.js
  90. 0 0
      frontend/dist/js/index.Ck5Ar80G.js
  91. 0 0
      frontend/dist/js/index.ClMdmbYS.js
  92. 0 0
      frontend/dist/js/index.CpgWCyD-.js
  93. 0 0
      frontend/dist/js/index.CqpG-3ZC.js
  94. 0 0
      frontend/dist/js/index.CslSkvD3.js
  95. 0 0
      frontend/dist/js/index.CubTWbO3.js
  96. 0 0
      frontend/dist/js/index.D-4wyap4.js
  97. 0 0
      frontend/dist/js/index.D0yQ8qyV.js
  98. 0 0
      frontend/dist/js/index.D1kcPmdc.js
  99. 0 0
      frontend/dist/js/index.DIAjPxV-.js
  100. 1 0
      frontend/dist/js/index.DMO2-R2P.js

+ 1 - 1
backend/app/plugin/module_payment/__init__.py

@@ -3,7 +3,7 @@ plugin/module_payment - 延迟导入,避免模块级全量 import 触发生产
 """
 import importlib
 
-_MODULES = ["employee", "enterprise", "expense", "notification", "points", "department", "openapi"]
+_MODULES = ["employee", "enterprise", "expense", "notification", "points", "department", "openapi", "facetoface"]
 
 
 def __getattr__(name):

+ 21 - 0
backend/app/plugin/module_payment/facetoface/__init__.py

@@ -0,0 +1,21 @@
+from .controller import FacetofaceRouter
+from .crud import FacetofaceCRUD
+from .enums import FacetofaceOrderStatus
+from .model import FacetofaceOrderModel
+from .schema import (
+    FacetofaceApplySchema,
+    FacetofaceOrderListOutSchema,
+    FacetofaceOrderOutSchema,
+)
+from .service import FacetofaceService
+
+__all__ = [
+    "FacetofaceRouter",
+    "FacetofaceCRUD",
+    "FacetofaceOrderModel",
+    "FacetofaceOrderStatus",
+    "FacetofaceApplySchema",
+    "FacetofaceOrderOutSchema",
+    "FacetofaceOrderListOutSchema",
+    "FacetofaceService",
+]

+ 163 - 0
backend/app/plugin/module_payment/facetoface/controller.py

@@ -0,0 +1,163 @@
+from typing import Annotated
+
+from apscheduler.schedulers.asyncio import AsyncIOScheduler
+from fastapi import APIRouter, Depends, Path, Query
+from fastapi.responses import JSONResponse
+
+from app.api.v1.module_system.auth.schema import AuthSchema
+from app.common.response import ResponseSchema, SuccessResponse
+from app.core.dependencies import AuthPermission
+from app.core.logger import log
+from app.core.router_class import OperationLogRoute
+
+from .schema import (
+    FacetofaceApplySchema,
+    FacetofaceOrderListOutSchema,
+    FacetofaceOrderOutSchema,
+)
+from .service import FacetofaceService
+
+FacetofaceRouter = APIRouter(
+    route_class=OperationLogRoute,
+    prefix="/facetoface",
+    tags=["当面付开通"],
+)
+
+
+@FacetofaceRouter.post(
+    "/apply",
+    summary="提交当面付开通申请",
+    description="代商家提交当面付开通申请(自动完成 create → sign → confirm 三步)",
+    response_model=ResponseSchema[FacetofaceOrderOutSchema],
+)
+async def apply_controller(
+    auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:apply"]))],
+    data: FacetofaceApplySchema,
+) -> JSONResponse:
+    result = await FacetofaceService.apply_service(auth=auth, data=data)
+    log.info(f"当面付开通申请已提交: batch_no={result.batch_no}, merchant={data.merchant_name}")
+    return SuccessResponse(data=result, msg="当面付开通申请已提交")
+
+
+@FacetofaceRouter.get(
+    "",
+    summary="查询申请单列表",
+    description="分页查询当面付开通申请单列表",
+    response_model=ResponseSchema[FacetofaceOrderListOutSchema],
+)
+async def list_controller(
+    auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:list"]))],
+    page_no: Annotated[int, Query(description="页码")] = 1,
+    page_size: Annotated[int, Query(description="每页数量")] = 20,
+    merchant_name: Annotated[str | None, Query(description="商户名称")] = None,
+    shop_name: Annotated[str | None, Query(description="店铺名称")] = None,
+    order_status: Annotated[str | None, Query(description="申请单状态")] = None,
+    start_time: Annotated[str | None, Query(description="开始时间")] = None,
+    end_time: Annotated[str | None, Query(description="结束时间")] = None,
+) -> JSONResponse:
+    search = {}
+    if merchant_name:
+        search["merchant_name"] = merchant_name
+    if shop_name:
+        search["shop_name"] = shop_name
+    if order_status:
+        search["order_status"] = order_status
+    if start_time:
+        search["start_time"] = start_time
+    if end_time:
+        search["end_time"] = end_time
+
+    result = await FacetofaceService.list_service(
+        auth=auth, page_no=page_no, page_size=page_size, search=search
+    )
+    return SuccessResponse(data=result, msg="查询成功")
+
+
+@FacetofaceRouter.get(
+    "/enterprises/status",
+    summary="批量查询企业当面付状态",
+    description="传入企业ID列表,返回各企业的当面付申请状态",
+)
+async def batch_status_controller(
+    auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:enterprise:list"]))],
+    ids: Annotated[str, Query(description="企业ID列表,逗号分隔")],
+) -> JSONResponse:
+    enterprise_ids = [eid.strip() for eid in ids.split(",") if eid.strip()]
+    result = await FacetofaceService.batch_status_service(auth=auth, enterprise_ids=enterprise_ids)
+    return SuccessResponse(data=result, msg="查询成功")
+
+
+@FacetofaceRouter.get(
+    "/enterprise/{enterprise_id}",
+    summary="按企业ID查询当面付申请单",
+    description="查询某个企业的当面付开通申请单",
+    response_model=ResponseSchema[FacetofaceOrderOutSchema],
+)
+async def get_by_enterprise_controller(
+    enterprise_id: Annotated[str, Path(description="企业ID")],
+    auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:list"]))],
+) -> JSONResponse:
+    result = await FacetofaceService.get_by_enterprise_service(auth=auth, enterprise_id=enterprise_id)
+    return SuccessResponse(data=result, msg="查询成功")
+
+
+@FacetofaceRouter.get(
+    "/{order_id}",
+    summary="查询申请单详情",
+    description="查询单个当面付开通申请单详情",
+    response_model=ResponseSchema[FacetofaceOrderOutSchema],
+)
+async def detail_controller(
+    order_id: Annotated[int, Path(description="申请单ID")],
+    auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:list"]))],
+) -> JSONResponse:
+    result = await FacetofaceService.detail_service(auth=auth, order_id=order_id)
+    return SuccessResponse(data=result, msg="查询成功")
+
+
+@FacetofaceRouter.post(
+    "/{order_id}/query",
+    summary="手动查询申请单状态",
+    description="手动触发查询支付宝申请单最新状态",
+    response_model=ResponseSchema[FacetofaceOrderOutSchema],
+)
+async def query_status_controller(
+    order_id: Annotated[int, Path(description="申请单ID")],
+    auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:query"]))],
+) -> JSONResponse:
+    result = await FacetofaceService.query_order_service(auth=auth, order_id=order_id)
+    log.info(f"手动查询当面付申请单状态: id={order_id}, status={result.order_status}")
+    return SuccessResponse(data=result, msg="查询成功")
+
+
+# ---------- 定时轮询任务 ----------
+
+_poll_scheduler = AsyncIOScheduler()
+_poll_scheduler_started = False
+
+
+@FacetofaceRouter.on_event("startup")
+async def start_facetoface_poll_scheduler():
+    """启动时注册当面付申请单状态轮询任务"""
+    global _poll_scheduler_started
+    if _poll_scheduler_started:
+        return
+    _poll_scheduler_started = True
+
+    _poll_scheduler.add_job(
+        FacetofaceService.poll_pending_orders,
+        "interval",
+        minutes=30,
+        max_instances=1,
+        id="facetoface_poll_orders",
+        replace_existing=True,
+    )
+    _poll_scheduler.start()
+    log.info("[当面付] 状态轮询定时任务已注册(每30分钟执行)")
+
+
+@FacetofaceRouter.on_event("shutdown")
+async def stop_facetoface_poll_scheduler():
+    if _poll_scheduler.running:
+        _poll_scheduler.shutdown(wait=False)
+        log.info("[当面付] 状态轮询定时任务已停止")

+ 43 - 0
backend/app/plugin/module_payment/facetoface/crud.py

@@ -0,0 +1,43 @@
+from datetime import datetime
+
+from sqlalchemy import select
+
+from app.api.v1.module_system.auth.schema import AuthSchema
+from app.core.base_crud import CRUDBase
+
+from .enums import FacetofaceOrderStatus
+from .model import FacetofaceOrderModel
+from .schema import FacetofaceApplySchema
+
+
+class FacetofaceCRUD(CRUDBase[FacetofaceOrderModel, FacetofaceApplySchema, FacetofaceApplySchema]):
+    """当面付申请单 CRUD"""
+
+    def __init__(self, auth: AuthSchema) -> None:
+        self.auth = auth
+        super().__init__(model=FacetofaceOrderModel, auth=auth)
+
+    async def get_by_batch_no(self, batch_no: str) -> FacetofaceOrderModel | None:
+        return await self.get(batch_no=batch_no)
+
+    async def get_by_enterprise_id(self, enterprise_id: str) -> FacetofaceOrderModel | None:
+        return await self.get(enterprise_id=enterprise_id)
+
+    async def get_pending_orders(self) -> list[FacetofaceOrderModel]:
+        """获取需要轮询状态的申请单(next_query_time <= now 且状态为待查询)"""
+        now = datetime.now()
+        pending_statuses = [
+            FacetofaceOrderStatus.SUBMITTED.value,
+            FacetofaceOrderStatus.MERCHANT_AUDITING.value,
+            FacetofaceOrderStatus.MERCHANT_CONFIRM.value,
+        ]
+        stmt = (
+            select(FacetofaceOrderModel)
+            .where(
+                FacetofaceOrderModel.order_status.in_(pending_statuses),
+                FacetofaceOrderModel.next_query_time <= now,
+            )
+            .order_by(FacetofaceOrderModel.next_query_time.asc())
+        )
+        result = await self.auth.db.execute(stmt)
+        return list(result.scalars().all())

+ 12 - 0
backend/app/plugin/module_payment/facetoface/enums.py

@@ -0,0 +1,12 @@
+from enum import Enum
+
+
+class FacetofaceOrderStatus(str, Enum):
+    """当面付申请单状态"""
+
+    INIT = "INIT"
+    SUBMITTED = "SUBMITTED"
+    MERCHANT_AUDITING = "MERCHANT_AUDITING"
+    MERCHANT_CONFIRM = "MERCHANT_CONFIRM"
+    SUCCESS = "SUCCESS"
+    CLOSED = "CLOSED"

+ 79 - 0
backend/app/plugin/module_payment/facetoface/model.py

@@ -0,0 +1,79 @@
+from datetime import datetime
+
+from sqlalchemy import Boolean, DateTime, Integer, String, Text
+from sqlalchemy.orm import Mapped, mapped_column
+
+from app.core.base_model import PaymentModelMixin, TenantMixin
+
+from .enums import FacetofaceOrderStatus
+
+
+class FacetofaceOrderModel(PaymentModelMixin, TenantMixin):
+    """当面付开通申请单"""
+
+    __tablename__ = "pay_facetoface_order"
+    __table_args__ = {"comment": "当面付开通申请单"}
+
+    enterprise_id: Mapped[str | None] = mapped_column(
+        String(64), unique=True, index=True, comment="关联企业ID"
+    )
+    batch_no: Mapped[str | None] = mapped_column(
+        String(64), unique=True, index=True, comment="支付宝事务编号"
+    )
+    order_no: Mapped[str | None] = mapped_column(
+        String(64), index=True, comment="支付宝申请单号"
+    )
+    order_status: Mapped[str] = mapped_column(
+        String(32),
+        default=FacetofaceOrderStatus.INIT.value,
+        index=True,
+        comment="申请单状态",
+    )
+
+    merchant_name: Mapped[str | None] = mapped_column(
+        String(128), comment="商户名称"
+    )
+    shop_name: Mapped[str | None] = mapped_column(
+        String(128), comment="店铺名称"
+    )
+    shop_address: Mapped[str | None] = mapped_column(
+        String(256), comment="店铺地址"
+    )
+    mcc_code: Mapped[str | None] = mapped_column(
+        String(32), comment="商户类别码"
+    )
+    rate: Mapped[str | None] = mapped_column(
+        String(16), comment="费率"
+    )
+    business_license_no: Mapped[str | None] = mapped_column(
+        String(64), comment="营业执照号"
+    )
+    business_license_mobile: Mapped[str | None] = mapped_column(
+        String(32), comment="联系手机号"
+    )
+    sign_and_auth: Mapped[bool] = mapped_column(
+        Boolean, default=False, comment="是否同时获取授权"
+    )
+
+    confirm_url: Mapped[str | None] = mapped_column(
+        Text, comment="商家确认链接"
+    )
+    app_auth_token: Mapped[str | None] = mapped_column(
+        String(128), comment="商家授权token"
+    )
+    reject_reason: Mapped[str | None] = mapped_column(
+        Text, comment="驳回原因"
+    )
+    remark: Mapped[str | None] = mapped_column(
+        Text, comment="备注"
+    )
+
+    last_query_time: Mapped[datetime | None] = mapped_column(
+        DateTime, comment="最后查询时间"
+    )
+    next_query_time: Mapped[datetime | None] = mapped_column(
+        DateTime, index=True, comment="下次查询时间"
+    )
+    query_count: Mapped[int] = mapped_column(
+        Integer, default=0, comment="已查询次数"
+    )

+ 66 - 0
backend/app/plugin/module_payment/facetoface/schema.py

@@ -0,0 +1,66 @@
+from datetime import datetime
+from typing import Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class FacetofaceApplySchema(BaseModel):
+    """当面付开通申请请求"""
+
+    enterprise_id: str = Field(description="关联企业ID")
+    merchant_name: str = Field(description="商户名称")
+    shop_name: str = Field(description="店铺名称")
+    shop_address: Optional[str] = Field(default=None, description="店铺地址")
+    mcc_code: Optional[str] = Field(default=None, description="商户类别码")
+    rate: Optional[str] = Field(default=None, description="费率,如 0.006")
+    business_license_no: Optional[str] = Field(default=None, description="营业执照号")
+    business_license_mobile: Optional[str] = Field(default=None, description="联系手机号")
+    sign_and_auth: bool = Field(default=False, description="是否同时获取商家授权")
+    remark: Optional[str] = Field(default=None, description="备注")
+
+
+class FacetofaceOrderOutSchema(BaseModel):
+    """当面付申请单详情响应"""
+
+    model_config = ConfigDict(from_attributes=True)
+
+    id: int = Field(description="主键ID")
+    enterprise_id: Optional[str] = Field(default=None, description="关联企业ID")
+    batch_no: Optional[str] = Field(default=None, description="事务编号")
+    order_no: Optional[str] = Field(default=None, description="申请单号")
+    order_status: str = Field(description="申请单状态")
+    merchant_name: Optional[str] = Field(default=None, description="商户名称")
+    shop_name: Optional[str] = Field(default=None, description="店铺名称")
+    shop_address: Optional[str] = Field(default=None, description="店铺地址")
+    mcc_code: Optional[str] = Field(default=None, description="商户类别码")
+    rate: Optional[str] = Field(default=None, description="费率")
+    business_license_no: Optional[str] = Field(default=None, description="营业执照号")
+    business_license_mobile: Optional[str] = Field(default=None, description="联系手机号")
+    sign_and_auth: bool = Field(default=False, description="是否同时获取授权")
+    confirm_url: Optional[str] = Field(default=None, description="商家确认链接")
+    app_auth_token: Optional[str] = Field(default=None, description="商家授权token")
+    reject_reason: Optional[str] = Field(default=None, description="驳回原因")
+    remark: Optional[str] = Field(default=None, description="备注")
+    last_query_time: Optional[datetime] = Field(default=None, description="最后查询时间")
+    next_query_time: Optional[datetime] = Field(default=None, description="下次查询时间")
+    query_count: int = Field(default=0, description="已查询次数")
+    created_time: datetime = Field(description="创建时间")
+    updated_time: datetime = Field(description="更新时间")
+
+
+class FacetofaceOrderListOutSchema(BaseModel):
+    """当面付申请单列表响应"""
+
+    model_config = ConfigDict(from_attributes=True)
+
+    id: int = Field(description="主键ID")
+    enterprise_id: Optional[str] = Field(default=None, description="关联企业ID")
+    batch_no: Optional[str] = Field(default=None, description="事务编号")
+    order_status: str = Field(description="申请单状态")
+    merchant_name: Optional[str] = Field(default=None, description="商户名称")
+    shop_name: Optional[str] = Field(default=None, description="店铺名称")
+    rate: Optional[str] = Field(default=None, description="费率")
+    confirm_url: Optional[str] = Field(default=None, description="商家确认链接")
+    reject_reason: Optional[str] = Field(default=None, description="驳回原因")
+    created_time: datetime = Field(description="创建时间")
+    updated_time: datetime = Field(description="更新时间")

+ 329 - 0
backend/app/plugin/module_payment/facetoface/service.py

@@ -0,0 +1,329 @@
+from datetime import datetime, timedelta
+
+from app.api.v1.module_system.auth.schema import AuthSchema
+from app.core.alipay import AlipayClient
+from app.core.exceptions import CustomException
+from app.core.logger import log
+
+from .crud import FacetofaceCRUD
+from .enums import FacetofaceOrderStatus
+from .schema import (
+    FacetofaceApplySchema,
+    FacetofaceOrderListOutSchema,
+    FacetofaceOrderOutSchema,
+)
+
+
+class FacetofaceService:
+    """当面付开通服务"""
+
+    @classmethod
+    async def apply_service(
+        cls, auth: AuthSchema, data: FacetofaceApplySchema
+    ) -> FacetofaceOrderOutSchema:
+        """
+        提交当面付开通申请
+
+        三步操作: agent.create → facetoface.sign → agent.confirm
+        """
+        from alipay.aop.api.domain.AlipayOpenAgentCreateModel import AlipayOpenAgentCreateModel
+        from alipay.aop.api.request.AlipayOpenAgentCreateRequest import AlipayOpenAgentCreateRequest
+        from alipay.aop.api.response.AlipayOpenAgentCreateResponse import AlipayOpenAgentCreateResponse
+        from alipay.aop.api.request.AlipayOpenAgentFacetofaceSignRequest import AlipayOpenAgentFacetofaceSignRequest
+        from alipay.aop.api.response.AlipayOpenAgentFacetofaceSignResponse import AlipayOpenAgentFacetofaceSignResponse
+        from alipay.aop.api.domain.AlipayOpenAgentConfirmModel import AlipayOpenAgentConfirmModel
+        from alipay.aop.api.request.AlipayOpenAgentConfirmRequest import AlipayOpenAgentConfirmRequest
+        from alipay.aop.api.response.AlipayOpenAgentConfirmResponse import AlipayOpenAgentConfirmResponse
+
+        client = AlipayClient.get_client()
+        crud = FacetofaceCRUD(auth)
+
+        # 检查该企业是否已有申请单
+        existing = await crud.get_by_enterprise_id(data.enterprise_id)
+        if existing and existing.order_status not in (
+            FacetofaceOrderStatus.CLOSED.value,
+        ):
+            raise CustomException(msg="该企业已有进行中的当面付申请")
+
+        # Step 1: 创建应用事务,获取 batch_no
+        create_model = AlipayOpenAgentCreateModel()
+        create_request = AlipayOpenAgentCreateRequest()
+        create_request.biz_model = create_model
+
+        response = client.execute(create_request)
+        if not response:
+            raise CustomException(msg="创建应用事务失败: 无响应")
+
+        create_result = AlipayOpenAgentCreateResponse()
+        create_result.parse_response_content(response)
+        if not create_result.is_success():
+            log.error(f"创建应用事务失败: {create_result.code} - {create_result.msg} - {create_result.sub_msg}")
+            raise CustomException(msg=f"创建应用事务失败: {create_result.sub_msg or create_result.msg}")
+
+        batch_no = create_result.batch_no
+        if not batch_no:
+            raise CustomException(msg="创建应用事务失败: 未返回 batch_no")
+
+        log.info(f"当面付申请 - Step1 创建事务成功: batch_no={batch_no}")
+
+        # Step 2: 提交当面付开通申请
+        sign_request = AlipayOpenAgentFacetofaceSignRequest()
+        sign_request.batch_no = batch_no
+        sign_request.shop_name = data.shop_name
+        if data.shop_address:
+            sign_request.shop_address = data.shop_address
+        if data.mcc_code:
+            sign_request.mcc_code = data.mcc_code
+        if data.rate:
+            sign_request.rate = data.rate
+        if data.business_license_no:
+            sign_request.business_license_no = data.business_license_no
+        if data.business_license_mobile:
+            sign_request.business_license_mobile = data.business_license_mobile
+        if data.sign_and_auth:
+            sign_request.sign_and_auth = "true"
+
+        response = client.execute(sign_request)
+        if not response:
+            raise CustomException(msg="提交当面付申请失败: 无响应")
+
+        sign_result = AlipayOpenAgentFacetofaceSignResponse()
+        sign_result.parse_response_content(response)
+        if not sign_result.is_success():
+            log.error(f"提交当面付申请失败: {sign_result.code} - {sign_result.msg} - {sign_result.sub_msg}")
+            raise CustomException(msg=f"提交当面付申请失败: {sign_result.sub_msg or sign_result.msg}")
+
+        log.info(f"当面付申请 - Step2 提交签约成功: batch_no={batch_no}")
+
+        # Step 3: 确认提交事务
+        confirm_model = AlipayOpenAgentConfirmModel()
+        confirm_model.batch_no = batch_no
+        confirm_request = AlipayOpenAgentConfirmRequest()
+        confirm_request.biz_model = confirm_model
+
+        response = client.execute(confirm_request)
+        if not response:
+            raise CustomException(msg="确认提交事务失败: 无响应")
+
+        confirm_result = AlipayOpenAgentConfirmResponse()
+        confirm_result.parse_response_content(response)
+        if not confirm_result.is_success():
+            log.error(f"确认提交事务失败: {confirm_result.code} - {confirm_result.msg} - {confirm_result.sub_msg}")
+            raise CustomException(msg=f"确认提交事务失败: {confirm_result.sub_msg or confirm_result.msg}")
+
+        log.info(f"当面付申请 - Step3 确认事务成功: batch_no={batch_no}")
+
+        # 保存申请单到数据库
+        now = datetime.now()
+        create_data = {
+            "enterprise_id": data.enterprise_id,
+            "batch_no": batch_no,
+            "order_status": FacetofaceOrderStatus.SUBMITTED.value,
+            "merchant_name": data.merchant_name,
+            "shop_name": data.shop_name,
+            "shop_address": data.shop_address,
+            "mcc_code": data.mcc_code,
+            "rate": data.rate,
+            "business_license_no": data.business_license_no,
+            "business_license_mobile": data.business_license_mobile,
+            "sign_and_auth": data.sign_and_auth,
+            "remark": data.remark,
+            "next_query_time": now + timedelta(minutes=5),
+            "query_count": 0,
+        }
+
+        # 如果该企业已有已关闭的申请单,更新而非新建
+        if existing:
+            obj = await crud.get(id=existing.id, preload=[])
+            if obj:
+                for key, value in create_data.items():
+                    if hasattr(obj, key):
+                        setattr(obj, key, value)
+                await auth.db.flush()
+                await auth.db.refresh(obj)
+                return FacetofaceOrderOutSchema.model_validate(obj)
+
+        order = await crud.create(create_data)
+        if not order:
+            raise CustomException(msg="保存申请单失败")
+
+        return FacetofaceOrderOutSchema.model_validate(order)
+
+    @classmethod
+    async def query_order_service(
+        cls, auth: AuthSchema, order_id: int
+    ) -> FacetofaceOrderOutSchema:
+        """手动查询单个申请单状态"""
+        crud = FacetofaceCRUD(auth)
+        order = await crud.get(id=order_id)
+        if not order:
+            raise CustomException(msg="申请单不存在")
+
+        if not order.batch_no:
+            raise CustomException(msg="申请单无事务编号,无法查询")
+
+        terminal_statuses = [
+            FacetofaceOrderStatus.SUCCESS.value,
+            FacetofaceOrderStatus.CLOSED.value,
+        ]
+        if order.order_status in terminal_statuses:
+            return FacetofaceOrderOutSchema.model_validate(order)
+
+        await cls._query_and_update_order(crud, order)
+        await auth.db.refresh(order)
+        return FacetofaceOrderOutSchema.model_validate(order)
+
+    @classmethod
+    async def _query_and_update_order(cls, crud: FacetofaceCRUD, order) -> None:
+        """查询支付宝接口并更新申请单状态"""
+        from alipay.aop.api.domain.AlipayOpenAgentOrderQueryModel import AlipayOpenAgentOrderQueryModel
+        from alipay.aop.api.request.AlipayOpenAgentOrderQueryRequest import AlipayOpenAgentOrderQueryRequest
+        from alipay.aop.api.response.AlipayOpenAgentOrderQueryResponse import AlipayOpenAgentOrderQueryResponse
+
+        client = AlipayClient.get_client()
+
+        query_model = AlipayOpenAgentOrderQueryModel()
+        query_model.batch_no = order.batch_no
+
+        query_request = AlipayOpenAgentOrderQueryRequest()
+        query_request.biz_model = query_model
+
+        try:
+            response = client.execute(query_request)
+        except Exception as e:
+            log.error(f"查询当面付申请单状态异常: batch_no={order.batch_no}, error={e}")
+            return
+
+        if not response:
+            log.warning(f"查询当面付申请单无响应: batch_no={order.batch_no}")
+            return
+
+        result = AlipayOpenAgentOrderQueryResponse()
+        result.parse_response_content(response)
+
+        if not result.is_success():
+            log.error(f"查询当面付申请单失败: batch_no={order.batch_no}, {result.code} - {result.sub_msg or result.msg}")
+            return
+
+        now = datetime.now()
+        update_data: dict = {
+            "last_query_time": now,
+            "query_count": order.query_count + 1,
+        }
+
+        if result.order_no:
+            update_data["order_no"] = result.order_no
+        if result.confirm_url:
+            update_data["confirm_url"] = result.confirm_url
+        if result.reject_reason:
+            update_data["reject_reason"] = result.reject_reason
+
+        alipay_status = result.order_status
+        if alipay_status:
+            if alipay_status == "MERCHANT_CONFIRM":
+                update_data["order_status"] = FacetofaceOrderStatus.MERCHANT_CONFIRM.value
+                # 等待商家确认,继续轮询
+                update_data["next_query_time"] = now + timedelta(hours=4)
+            elif alipay_status == "MERCHANT_AUDITING":
+                update_data["order_status"] = FacetofaceOrderStatus.MERCHANT_AUDITING.value
+                update_data["next_query_time"] = now + timedelta(hours=4)
+            elif alipay_status in ("MERCHANT_AGREED", "AGENT_BINDAPP_SUCCESS"):
+                update_data["order_status"] = FacetofaceOrderStatus.SUCCESS.value
+                update_data["next_query_time"] = None
+            elif alipay_status in ("MERCHANT_REJECTED", "MERCHANT_CANCELLED", "AUDIT_REJECTED", "AUDIT_FAILED"):
+                update_data["order_status"] = FacetofaceOrderStatus.CLOSED.value
+                update_data["next_query_time"] = None
+            else:
+                update_data["next_query_time"] = now + timedelta(hours=4)
+
+        log.info(f"当面付申请单状态更新: batch_no={order.batch_no}, alipay_status={alipay_status}, local_status={update_data.get('order_status', order.order_status)}")
+
+        obj = await crud.get(id=order.id, preload=[])
+        if obj:
+            for key, value in update_data.items():
+                if hasattr(obj, key):
+                    setattr(obj, key, value)
+            await crud.auth.db.flush()
+
+    @classmethod
+    async def batch_status_service(
+        cls, auth: AuthSchema, enterprise_ids: list[str]
+    ) -> dict[str, str | None]:
+        """批量查询企业当面付状态"""
+        crud = FacetofaceCRUD(auth)
+        result: dict[str, str | None] = {}
+        for eid in enterprise_ids:
+            order = await crud.get_by_enterprise_id(eid)
+            result[eid] = order.order_status if order else None
+        return result
+
+    @classmethod
+    async def get_by_enterprise_service(
+        cls, auth: AuthSchema, enterprise_id: str
+    ) -> FacetofaceOrderOutSchema | None:
+        """按企业ID查询当面付申请单"""
+        crud = FacetofaceCRUD(auth)
+        order = await crud.get_by_enterprise_id(enterprise_id)
+        if not order:
+            return None
+        return FacetofaceOrderOutSchema.model_validate(order)
+
+    @classmethod
+    async def list_service(
+        cls,
+        auth: AuthSchema,
+        page_no: int = 1,
+        page_size: int = 20,
+        search: dict | None = None,
+    ) -> dict:
+        """查询申请单列表"""
+        crud = FacetofaceCRUD(auth)
+        offset = (page_no - 1) * page_size
+        return await crud.page(
+            offset=offset,
+            limit=page_size,
+            order_by=[{"id": "desc"}],
+            search=search or {},
+            out_schema=FacetofaceOrderListOutSchema,
+        )
+
+    @classmethod
+    async def detail_service(
+        cls, auth: AuthSchema, order_id: int
+    ) -> FacetofaceOrderOutSchema:
+        """查询申请单详情"""
+        crud = FacetofaceCRUD(auth)
+        order = await crud.get(id=order_id)
+        if not order:
+            raise CustomException(msg="申请单不存在")
+        return FacetofaceOrderOutSchema.model_validate(order)
+
+    @classmethod
+    async def poll_pending_orders(cls) -> int:
+        """
+        轮询待处理的申请单(供定时任务调用)
+
+        返回本次处理的申请单数量
+        """
+        from app.core.database import async_db_session
+
+        async with async_db_session() as db:
+            from app.api.v1.module_system.auth.schema import AuthSchema
+            auth = AuthSchema(db=db, user=None, tenant_id=1)
+            crud = FacetofaceCRUD(auth)
+
+            orders = await crud.get_pending_orders()
+            if not orders:
+                return 0
+
+            count = 0
+            for order in orders:
+                try:
+                    await cls._query_and_update_order(crud, order)
+                    count += 1
+                except Exception as e:
+                    log.error(f"轮询当面付申请单异常: batch_no={order.batch_no}, error={e}")
+
+            await db.commit()
+            log.info(f"当面付申请单轮询完成: 处理 {count}/{len(orders)} 条")
+            return count

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/css/index.C87XjJcL.css


+ 0 - 0
frontend/dist/css/wechat.sp6bqO9U.css → frontend/dist/css/system.sp6bqO9U.css


+ 0 - 0
frontend/dist/css/sql.BzmjZvX7.css → frontend/dist/css/xml.BzmjZvX7.css


+ 3 - 3
frontend/dist/index.html

@@ -10,7 +10,7 @@
       content=""
     />
     <title>Pyament Platform</title>
-    <script type="module" crossorigin src="/js/index.RQPp-2Qw.js"></script>
+    <script type="module" crossorigin src="/js/index.DnzTpneR.js"></script>
     <link rel="modulepreload" crossorigin href="/js/dayjs.DCWbrgJ0.js">
     <link rel="modulepreload" crossorigin href="/js/@vue.DbmRtknU.js">
     <link rel="modulepreload" crossorigin href="/js/lodash-es.DaM9m3L-.js">
@@ -20,7 +20,7 @@
     <link rel="modulepreload" crossorigin href="/js/memoize-one.BAtLgO95.js">
     <link rel="modulepreload" crossorigin href="/js/normalize-wheel-es.TzhA1irr.js">
     <link rel="modulepreload" crossorigin href="/js/@floating-ui.8vigAAFV.js">
-    <link rel="modulepreload" crossorigin href="/js/element-plus.ea_38_Np.js">
+    <link rel="modulepreload" crossorigin href="/js/element-plus.DYHj_7Hl.js">
     <link rel="modulepreload" crossorigin href="/js/pinia.BlfmsheH.js">
     <link rel="modulepreload" crossorigin href="/js/@vueuse.Dnsd2JKX.js">
     <link rel="modulepreload" crossorigin href="/js/@intlify.DPMNdUn_.js">
@@ -30,7 +30,6 @@
     <link rel="modulepreload" crossorigin href="/js/codemirror.CYSLATvI.js">
     <link rel="modulepreload" crossorigin href="/js/vue.MGxsMDTR.js">
     <link rel="modulepreload" crossorigin href="/js/vue-web-terminal.D-rog7dz.js">
-    <link rel="modulepreload" crossorigin href="/js/vue-router.yxIMtaxH.js">
     <link rel="modulepreload" crossorigin href="/js/axios.Da-QW0H8.js">
     <link rel="modulepreload" crossorigin href="/js/es-errors.DTEWvbA_.js">
     <link rel="modulepreload" crossorigin href="/js/object-inspect.DvQZIv3_.js">
@@ -51,6 +50,7 @@
     <link rel="modulepreload" crossorigin href="/js/side-channel-weakmap.DpSeWE6i.js">
     <link rel="modulepreload" crossorigin href="/js/side-channel.4q28KFJj.js">
     <link rel="modulepreload" crossorigin href="/js/qs.BQjOrGHM.js">
+    <link rel="modulepreload" crossorigin href="/js/vue-router.yxIMtaxH.js">
     <link rel="modulepreload" crossorigin href="/js/pinia-plugin-persistedstate.COWkwNh5.js">
     <link rel="stylesheet" crossorigin href="/css/element-plus.C5wfcs9c.css">
     <link rel="stylesheet" crossorigin href="/css/nprogress.BgDCIyLK.css">

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/401.Bohubu6V.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/404.BCOe6gUw.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/AccountOverview.CsMP2ej5.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/ChatInput.BOTkECqC.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/ChatMessages.BqVjWx14.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/ChatNavbar.B7vq6l9_.js


+ 1 - 1
frontend/dist/js/ConfigInfoDrawer.SIupMd2B.js → frontend/dist/js/ConfigInfoDrawer.DOwsjtUd.js

@@ -1,4 +1,4 @@
-import{ao as e,d as l,L as a,q as i,ae as t,af as o,z as s,ad as n,A as u,t as r,a as d,J as p,P as c}from"./element-plus.ea_38_Np.js";import{a0 as m,_ as v,v as _,e as f,H as g}from"./index.RQPp-2Qw.js";import{u as y}from"./vue-i18n.DXLOBfKS.js";import{E as b}from"./size.B_US4txQ.js";import{M as h,bn as w,bq as j,bE as x,aZ as k,aL as V,u as I,I as D,bJ as P,F as S,s as z,bO as U,t as C,bb as B,aq as O,aU as F,aD as A,a_ as E,H as J,aX as T,v as q,bk as L,bL as N,q as $,aS as M}from"./@vue.DbmRtknU.js";import"./lodash-es.DaM9m3L-.js";import"./async-validator.j0i5Y79Y.js";import"./@popperjs.DxtSUbXb.js";import"./@ctrl.BEgk5vdO.js";import"./dayjs.DCWbrgJ0.js";import"./memoize-one.BAtLgO95.js";import"./normalize-wheel-es.TzhA1irr.js";import"./@floating-ui.8vigAAFV.js";import"./pinia.BlfmsheH.js";import"./@vueuse.Dnsd2JKX.js";import"./nprogress.BTjJXJ-u.js";import"./codemirror.CYSLATvI.js";import"./diff-match-patch.DkK4wJpa.js";import"./vue-web-terminal.D-rog7dz.js";import"./vue.MGxsMDTR.js";/* empty css                    */import"./vue-router.yxIMtaxH.js";import"./axios.Da-QW0H8.js";import"./qs.BQjOrGHM.js";import"./side-channel.4q28KFJj.js";import"./es-errors.DTEWvbA_.js";import"./object-inspect.DvQZIv3_.js";import"./side-channel-list.Do0-XmF5.js";import"./side-channel-map.DNHQ53lO.js";import"./get-intrinsic.Bbe5x-9b.js";import"./es-object-atoms.CyiuHMUS.js";import"./math-intrinsics.pM-JTNwN.js";import"./gopd.BudZp56J.js";import"./es-define-property.F0aoeP8o.js";import"./has-symbols.BcO-SUVM.js";import"./get-proto.Cb_fpw-j.js";import"./dunder-proto.WEH3rgQR.js";import"./call-bind-apply-helpers.DJjIjCF_.js";import"./function-bind.DrnB-baK.js";import"./hasown.BXcyoiLU.js";import"./call-bound.22gFUC2Q.js";import"./side-channel-weakmap.DpSeWE6i.js";import"./pinia-plugin-persistedstate.COWkwNh5.js";import"./@intlify.DPMNdUn_.js";import"./time.D1VVjq6s.js";const W={class:"single-image-upload"},H={key:0,class:"el-upload__tip"},K=v(h({__name:"SingleImageUpload",props:O({
+import{ao as e,d as l,L as a,q as i,ae as t,af as o,z as s,ad as n,A as u,t as r,a as d,J as p,P as c}from"./element-plus.DYHj_7Hl.js";import{P as m,_ as v,v as _,g as f,H as g}from"./index.DnzTpneR.js";import{u as y}from"./vue-i18n.DXLOBfKS.js";import{E as b}from"./size.63H7ew1V.js";import{M as h,bn as w,bq as j,bE as x,aZ as k,aL as V,u as I,I as D,bJ as P,F as S,s as z,bO as U,t as C,bb as B,aq as O,aU as F,aD as A,a_ as E,H as J,aX as T,v as q,bk as L,bL as N,q as $,aS as M}from"./@vue.DbmRtknU.js";import"./lodash-es.DaM9m3L-.js";import"./async-validator.j0i5Y79Y.js";import"./@popperjs.DxtSUbXb.js";import"./@ctrl.BEgk5vdO.js";import"./dayjs.DCWbrgJ0.js";import"./memoize-one.BAtLgO95.js";import"./normalize-wheel-es.TzhA1irr.js";import"./@floating-ui.8vigAAFV.js";import"./pinia.BlfmsheH.js";import"./@vueuse.Dnsd2JKX.js";import"./nprogress.BTjJXJ-u.js";import"./codemirror.CYSLATvI.js";import"./diff-match-patch.DkK4wJpa.js";import"./vue-web-terminal.D-rog7dz.js";import"./vue.MGxsMDTR.js";/* empty css                    */import"./axios.Da-QW0H8.js";import"./qs.BQjOrGHM.js";import"./side-channel.4q28KFJj.js";import"./es-errors.DTEWvbA_.js";import"./object-inspect.DvQZIv3_.js";import"./side-channel-list.Do0-XmF5.js";import"./side-channel-map.DNHQ53lO.js";import"./get-intrinsic.Bbe5x-9b.js";import"./es-object-atoms.CyiuHMUS.js";import"./math-intrinsics.pM-JTNwN.js";import"./gopd.BudZp56J.js";import"./es-define-property.F0aoeP8o.js";import"./has-symbols.BcO-SUVM.js";import"./get-proto.Cb_fpw-j.js";import"./dunder-proto.WEH3rgQR.js";import"./call-bind-apply-helpers.DJjIjCF_.js";import"./function-bind.DrnB-baK.js";import"./hasown.BXcyoiLU.js";import"./call-bound.22gFUC2Q.js";import"./side-channel-weakmap.DpSeWE6i.js";import"./vue-router.yxIMtaxH.js";import"./pinia-plugin-persistedstate.COWkwNh5.js";import"./@intlify.DPMNdUn_.js";import"./sql.CpSVJlG1.js";const W={class:"single-image-upload"},H={key:0,class:"el-upload__tip"},K=v(h({__name:"SingleImageUpload",props:O({
 /**
      * 请求携带的额外参数
      */

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/ConsumeDetail.B9B7mG8S.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/CreateTableDialog.NIelA4pP.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/DataDrawer.DHSCvnHU.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/DataDrawer.DeMcy8wU.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/DepartmentDetail.IX8nNF5d.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/DepartmentForm.GcE6P-YY.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/DeptTree.nzsxYta4.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/EdgeConfigPanel.CqmGyMAo.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/EmployeeForm.CenHCFpl.js


+ 1 - 1
frontend/dist/js/ExternalLink.DpWjd9Rj.js → frontend/dist/js/ExternalLink.Cn3YP1oI.js

@@ -1 +1 @@
-import{u as s}from"./vue-router.yxIMtaxH.js";import{M as i,aL as r,u as t,v as o,q as e}from"./@vue.DbmRtknU.js";import{_ as p}from"./index.RQPp-2Qw.js";import"./dayjs.DCWbrgJ0.js";import"./element-plus.ea_38_Np.js";import"./lodash-es.DaM9m3L-.js";import"./async-validator.j0i5Y79Y.js";import"./@popperjs.DxtSUbXb.js";import"./@ctrl.BEgk5vdO.js";import"./memoize-one.BAtLgO95.js";import"./normalize-wheel-es.TzhA1irr.js";import"./@floating-ui.8vigAAFV.js";import"./pinia.BlfmsheH.js";import"./@vueuse.Dnsd2JKX.js";import"./vue-i18n.DXLOBfKS.js";import"./@intlify.DPMNdUn_.js";import"./nprogress.BTjJXJ-u.js";import"./codemirror.CYSLATvI.js";import"./diff-match-patch.DkK4wJpa.js";import"./vue-web-terminal.D-rog7dz.js";import"./vue.MGxsMDTR.js";/* empty css                    */import"./axios.Da-QW0H8.js";import"./qs.BQjOrGHM.js";import"./side-channel.4q28KFJj.js";import"./es-errors.DTEWvbA_.js";import"./object-inspect.DvQZIv3_.js";import"./side-channel-list.Do0-XmF5.js";import"./side-channel-map.DNHQ53lO.js";import"./get-intrinsic.Bbe5x-9b.js";import"./es-object-atoms.CyiuHMUS.js";import"./math-intrinsics.pM-JTNwN.js";import"./gopd.BudZp56J.js";import"./es-define-property.F0aoeP8o.js";import"./has-symbols.BcO-SUVM.js";import"./get-proto.Cb_fpw-j.js";import"./dunder-proto.WEH3rgQR.js";import"./call-bind-apply-helpers.DJjIjCF_.js";import"./function-bind.DrnB-baK.js";import"./hasown.BXcyoiLU.js";import"./call-bound.22gFUC2Q.js";import"./side-channel-weakmap.DpSeWE6i.js";import"./pinia-plugin-persistedstate.COWkwNh5.js";const m={class:"external-link-container"},a=["src"],n=p(i({__name:"ExternalLink",setup(i){const p=s(),n=e(()=>{var s;const i=null==(s=p.meta)?void 0:s.params,r=null==i?void 0:i.find(s=>"url"===s.key);return(null==r?void 0:r.value)||""});return(s,i)=>(r(),t("div",m,[o("iframe",{src:n.value,class:"external-link-iframe",frameborder:"0",width:"100%",height:"100%"},null,8,a)]))}}),[["__scopeId","data-v-ddb74517"]]);export{n as default};
+import{u as s}from"./vue-router.yxIMtaxH.js";import{M as i,aL as r,u as t,v as o,q as e}from"./@vue.DbmRtknU.js";import{_ as p}from"./index.DnzTpneR.js";import"./dayjs.DCWbrgJ0.js";import"./element-plus.DYHj_7Hl.js";import"./lodash-es.DaM9m3L-.js";import"./async-validator.j0i5Y79Y.js";import"./@popperjs.DxtSUbXb.js";import"./@ctrl.BEgk5vdO.js";import"./memoize-one.BAtLgO95.js";import"./normalize-wheel-es.TzhA1irr.js";import"./@floating-ui.8vigAAFV.js";import"./pinia.BlfmsheH.js";import"./@vueuse.Dnsd2JKX.js";import"./vue-i18n.DXLOBfKS.js";import"./@intlify.DPMNdUn_.js";import"./nprogress.BTjJXJ-u.js";import"./codemirror.CYSLATvI.js";import"./diff-match-patch.DkK4wJpa.js";import"./vue-web-terminal.D-rog7dz.js";import"./vue.MGxsMDTR.js";/* empty css                    */import"./axios.Da-QW0H8.js";import"./qs.BQjOrGHM.js";import"./side-channel.4q28KFJj.js";import"./es-errors.DTEWvbA_.js";import"./object-inspect.DvQZIv3_.js";import"./side-channel-list.Do0-XmF5.js";import"./side-channel-map.DNHQ53lO.js";import"./get-intrinsic.Bbe5x-9b.js";import"./es-object-atoms.CyiuHMUS.js";import"./math-intrinsics.pM-JTNwN.js";import"./gopd.BudZp56J.js";import"./es-define-property.F0aoeP8o.js";import"./has-symbols.BcO-SUVM.js";import"./get-proto.Cb_fpw-j.js";import"./dunder-proto.WEH3rgQR.js";import"./call-bind-apply-helpers.DJjIjCF_.js";import"./function-bind.DrnB-baK.js";import"./hasown.BXcyoiLU.js";import"./call-bound.22gFUC2Q.js";import"./side-channel-weakmap.DpSeWE6i.js";import"./pinia-plugin-persistedstate.COWkwNh5.js";const m={class:"external-link-container"},a=["src"],n=p(i({__name:"ExternalLink",setup(i){const p=s(),n=e(()=>{var s;const i=null==(s=p.meta)?void 0:s.params,r=null==i?void 0:i.find(s=>"url"===s.key);return(null==r?void 0:r.value)||""});return(s,i)=>(r(),t("div",m,[o("iframe",{src:n.value,class:"external-link-iframe",frameborder:"0",width:"100%",height:"100%"},null,8,a)]))}}),[["__scopeId","data-v-ddb74517"]]);export{n as default};

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/GenBasicStep.27KhLIBy.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/GenCodeDrawer.BZ6p8b0P.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/GenCodeDrawer.D4kw7CkR.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/GenColumnsStep.BziHzEbG.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/GenPreviewStep.CbncBiUk.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/GencodeHelpPanel.Azeofp20.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/ImportDbTableDialog.DXZ1WUPp.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/InstitutionDetail.CGn4wsst.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/InstitutionDetail.RoEnI7gP.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/InstitutionForm.CgZk9XYe.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/InviteDialog.DhhBZRku.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/IssueBatchForm.Dtgx5mnv.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/Login.CKgAj_l2.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/MessageItem.B0q5UvpY.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/NodeConfigPanel.Dl_wDBb2.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/PageContent.kwKvxA-S.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/QuotaList.RBE7G8SP.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/QuotaList.wB5GldbV.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/RuleForm.CNSWNA58.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/RuleList.DpsWbUEC.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/RuleList.rx6vCgrT.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/ScopeDialog.B086Gh_W.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/Sidebar.CypHgURH.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/TransferDetail.DYjD7LMI.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/UserTableSelect.1De4CctK.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/WelcomeScreen.UILgR-rM.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/WorkflowDesignDrawer.DycohyV2.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/api.C_KCUm-p.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/element-plus.DYHj_7Hl.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/github.B0B3B1or.js


Файловите разлики са ограничени, защото са твърде много
+ 1 - 0
frontend/dist/js/index copy.-8vOp1Bn.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
frontend/dist/js/index copy.ByQOjuSt.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.-WglgRN2.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.0eAFd7Dw.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.2113wD7B.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.7a2lZ-RL.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.8LzqRHhY.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
frontend/dist/js/index.B-TQyOp9.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.B0spg9hN.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.B1tf_lRg.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.B7VX8PXE.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.BGLlPl3d.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.BJY7utdf.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.B_-iaV5k.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.B_c9KDco.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.Bdr50yky.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.BevQ8M7-.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.BiXRzGoZ.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.Bl-ZxAAB.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.Bn6SvpZe.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.BqMt-nvY.js


+ 1 - 1
frontend/dist/js/index.cjtdWSAu.js → frontend/dist/js/index.BrfkZJnm.js

@@ -1 +1 @@
-import{P as e}from"./vue-json-pretty.DEqWvEy3.js";import{M as t,aL as a,u as s,aw as r,s as o,bk as n,bb as l,q as u}from"./@vue.DbmRtknU.js";import{_ as p}from"./index.RQPp-2Qw.js";const i={key:1,class:"json-pretty-fallback"},y=p(t({__name:"index",props:{value:{type:[String,Object,Array,Number,Boolean],default:""},height:{type:String,default:"240px"}},setup(t){const p=t,y=u(()=>{const e=p.value;if("string"==typeof e)try{return JSON.parse(e)}catch{return e}return e}),c=u(()=>"object"==typeof y.value&&null!==y.value),d=u(()=>{const e=p.value;return"string"==typeof e?e:JSON.stringify(e,null,2)});return(u,p)=>(a(),s("div",{class:"json-pretty-wrapper",style:r({maxHeight:t.height})},[c.value?(a(),o(n(e),{key:0,data:y.value,"show-line":!0,"show-icon":!0,"show-double-quotes":!1,"show-length":!0,deep:3},null,8,["data"])):(a(),s("pre",i,l(d.value),1))],4))}}),[["__scopeId","data-v-a0d23118"]]);export{y as J};
+import{P as e}from"./vue-json-pretty.DEqWvEy3.js";import{M as t,aL as a,u as s,aw as r,s as o,bk as n,bb as l,q as u}from"./@vue.DbmRtknU.js";import{_ as p}from"./index.DnzTpneR.js";const i={key:1,class:"json-pretty-fallback"},y=p(t({__name:"index",props:{value:{type:[String,Object,Array,Number,Boolean],default:""},height:{type:String,default:"240px"}},setup(t){const p=t,y=u(()=>{const e=p.value;if("string"==typeof e)try{return JSON.parse(e)}catch{return e}return e}),c=u(()=>"object"==typeof y.value&&null!==y.value),d=u(()=>{const e=p.value;return"string"==typeof e?e:JSON.stringify(e,null,2)});return(u,p)=>(a(),s("div",{class:"json-pretty-wrapper",style:r({maxHeight:t.height})},[c.value?(a(),o(n(e),{key:0,data:y.value,"show-line":!0,"show-icon":!0,"show-double-quotes":!1,"show-length":!0,deep:3},null,8,["data"])):(a(),s("pre",i,l(d.value),1))],4))}}),[["__scopeId","data-v-a0d23118"]]);export{y as J};

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.Bv09U9ef.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.ByPFzEOR.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CHHtFWd9.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CIOxBBPC.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CPZfnKUs.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CYKRX2m3.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.Cb8xBfNb.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
frontend/dist/js/index.CiuTkDqZ.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.Ck5Ar80G.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.ClMdmbYS.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CpgWCyD-.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CqpG-3ZC.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CslSkvD3.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.CubTWbO3.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.D-4wyap4.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.D0yQ8qyV.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.D1kcPmdc.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
frontend/dist/js/index.DIAjPxV-.js


Файловите разлики са ограничени, защото са твърде много
+ 1 - 0
frontend/dist/js/index.DMO2-R2P.js


Някои файлове не бяха показани, защото твърде много файлове са промени