From 8e2dd8b9f01539f9801e7871324e106fe3d7f846 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sun, 17 May 2026 08:36:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../page-2026-05-16T05-37-34-857Z.yml | 5 + .../page-2026-05-16T05-37-57-758Z.yml | 119 + .../page-2026-05-16T05-40-18-880Z.yml | 29 + .../page-2026-05-16T05-40-31-785Z.yml | 29 + .../page-2026-05-16T05-41-00-624Z.yml | 119 + .../page-2026-05-16T05-41-36-020Z.yml | 29 + .../page-2026-05-16T05-41-45-619Z.yml | 29 + .../page-2026-05-16T05-42-20-871Z.yml | 119 + .../zkxw-cell-storage-modules-overview.html | 151 + .../zkxw-installment-warning-rule.html | 76 + .../78964-1778664283/state/server-stopped | 1 + .../78964-1778664283/state/server.pid | 1 + AGENTS.md | 9 +- backend/.env.example | 6 +- backend/README.md | 2 +- backend/app.pid | 2 +- backend/app/api/__init__.py | 12 + backend/app/api/admin.py | 158 +- backend/app/api/basic_info.py | 860 +++++ backend/app/api/collection_protocol.py | 513 +++ backend/app/api/ethnic.py | 24 + backend/app/api/leave.py | 205 ++ backend/app/api/menu.py | 100 +- backend/app/api/operations.py | 598 +++ backend/app/api/public.py | 2 +- backend/app/api/workflow.py | 514 +++ backend/app/core/config.py | 2 +- backend/app/crud/cell_sample_type.py | 174 + backend/app/crud/clean_room.py | 344 ++ backend/app/crud/collection_protocol.py | 1580 ++++++++ backend/app/crud/collection_protocol_no.py | 339 ++ backend/app/crud/collection_region.py | 308 ++ backend/app/crud/customer.py | 194 + backend/app/crud/dept.py | 2 +- backend/app/crud/dict_data.py | 4 +- backend/app/crud/dict_type.py | 2 +- backend/app/crud/ethnic.py | 30 + backend/app/crud/exhibition_appointment.py | 238 ++ backend/app/crud/express_company.py | 2 +- backend/app/crud/item_unit.py | 2 +- backend/app/crud/leave.py | 144 + backend/app/crud/menu.py | 2 +- backend/app/crud/post.py | 2 +- backend/app/crud/role.py | 13 +- backend/app/crud/sample_code.py | 341 ++ backend/app/crud/site.py | 2 +- backend/app/crud/storage_facility.py | 356 ++ backend/app/crud/tenant.py | 2 +- backend/app/crud/user.py | 94 +- backend/app/crud/workflow.py | 760 ++++ backend/app/main.py | 8 +- backend/app/models/models.py | 701 +++- backend/app/schemas/attachment.py | 13 + backend/app/schemas/leave.py | 40 + backend/app/schemas/workflow.py | 115 + backend/app/services/web_auth_roles.py | 2 +- backend/app/services/workflow_callback.py | 106 + backend/app/services/workflow_engine.py | 389 ++ backend/requirements.txt | 1 + .../tests/test_basic_info_tenant_ownership.py | 254 ++ .../test_collection_protocol_conventions.py | 249 ++ .../test_collection_protocol_save_bundle.py | 83 + backend/tests/test_ethnic.py | 17 + ...-protocol-party-customer-apgar-decimal.sql | 4 + ...er-biz-protocol-sample-status-nullable.sql | 3 + docs/sql/sys-ethnic.sql | 83 + docs/sql/zkxw-init-dept-data.sql | 20 - docs/sql/zkxw-init-dict-data.sql | 24 - docs/sql/zkxw-init-menu-data.sql | 24 - docs/sql/zkxw-init-role-data.sql | 28 - docs/sql/zkxw-init-site-post-unit-express.sql | 26 - docs/sql/zkxw-init-user-data.sql | 24 - docs/sql/zkxw-requirements-v0.3-schema.sql | 1638 --------- docs/sql/zkxw.sql | 2550 +++++++++++++ ...026-05-13-cell-storage-protocol-modules.md | 59 + .../2026-05-13-flow-designer-enhancements.md | 535 +++ ...3-flow-designer-enterprise-enhancements.md | 612 ++++ .../plans/2026-05-13-workflow-module.md | 2502 +++++++++++++ .../plans/2026-05-15-operations-support.md | 618 ++++ .../2026-05-15-storage-facility-redesign.md | 924 +++++ ...13-cell-storage-protocol-modules-design.md | 4 +- .../2026-05-13-workflow-module-design.md | 351 ++ .../2026-05-15-operations-support-design.md | 48 + docs/中科兴皖需求V0.3.md | 57 +- login-success.png | Bin 0 -> 77493 bytes .../basic-info-management/.openspec.yaml | 2 + .../changes/basic-info-management/design.md | 77 + .../changes/basic-info-management/proposal.md | 34 + .../specs/cell-sample-type-management/spec.md | 37 + .../collection-protocol-no-management/spec.md | 37 + .../spec.md | 33 + .../specs/customer-management/spec.md | 47 + .../specs/sample-code-management/spec.md | 37 + .../changes/basic-info-management/tasks.md | 35 + .../.openspec.yaml | 2 + .../cell-storage-protocol-modules/design.md | 43 + .../cell-storage-protocol-modules/proposal.md | 32 + .../cell-storage-collection-protocol/spec.md | 33 + .../cell-storage-installment-warning/spec.md | 33 + .../specs/cell-storage-protocol-audit/spec.md | 37 + .../cell-storage-receipt-confirm/spec.md | 33 + .../specs/cell-storage-refund-confirm/spec.md | 28 + .../cell-storage-protocol-modules/tasks.md | 39 + openspec/changes/dict-management/proposal.md | 4 +- .../dict-management/specs/dict-crud/spec.md | 6 +- .../specs/api-conventions/spec.md | 4 +- .../specs/coding-conventions/spec.md | 2 +- .../specs/ui-conventions/spec.md | 411 +++ web-admin/package.json | 1 + web-admin/pnpm-lock.yaml | 30 + web-admin/public/avatart.jpg | Bin 0 -> 16907 bytes web-admin/public/logo1.jpg | Bin 411065 -> 0 bytes web-admin/public/logo1_副本.jpg | Bin 411065 -> 0 bytes web-admin/src/App.vue | 2 +- web-admin/src/api/admin.ts | 586 ++- web-admin/src/api/collection-protocol.ts | 134 + web-admin/src/api/ethnic.ts | 15 + web-admin/src/api/leave.ts | 78 + web-admin/src/api/workflow.ts | 206 ++ web-admin/src/assets/styles/core/app.scss | 11 +- .../src/assets/styles/core/el-light.scss | 3 +- web-admin/src/assets/styles/core/el-ui.scss | 54 + .../business/system-view-dialog/index.vue | 211 ++ .../components/core/forms/art-form/index.vue | 18 +- .../core/forms/art-search-bar/index.vue | 18 +- .../art-menus/art-sidebar-menu/index.vue | 8 + .../art-menus/art-sidebar-menu/style.scss | 8 + .../art-menus/art-sidebar-menu/theme.scss | 15 +- .../widget/SidebarSubmenu.vue | 14 +- .../art-settings-panel/widget/SettingItem.vue | 8 +- .../core/layouts/art-work-tab/index.vue | 12 +- web-admin/src/router/index.ts | 2 +- web-admin/src/router/modules/sqxp.ts | 255 +- web-admin/src/store/modules/worktab.ts | 14 +- web-admin/src/types/api.d.ts | 231 +- web-admin/src/types/router/index.ts | 4 +- web-admin/src/utils/fetch-paged-list.ts | 6 +- web-admin/src/utils/http/index.ts | 2 +- web-admin/src/utils/navigation/jump.ts | 5 + web-admin/src/utils/sort-order.ts | 5 +- web-admin/src/utils/tree-path.ts | 17 +- web-admin/src/utils/ui/ep-icon-map.ts | 12 +- web-admin/src/utils/user-avatar.ts | 8 +- .../basic-info/cell-sample-type/index.vue | 403 +++ .../collection-protocol-no/index.vue | 684 ++++ .../basic-info/collection-region/index.vue | 665 ++++ .../src/views/basic-info/customer/index.vue | 431 +++ .../views/basic-info/sample-code/index.vue | 710 ++++ .../installment-warning/index.vue | 107 + .../cell-storage/payment-confirm/index.vue | 125 + .../cell-storage/protocol-audit/index.vue | 410 +++ .../protocol/ProtocolAttachmentReadonly.vue | 208 ++ .../protocol/ProtocolFormDialog.vue | 3198 +++++++++++++++++ .../protocol/ProtocolViewDialog.vue | 865 +++++ .../src/views/cell-storage/protocol/index.vue | 410 +++ .../cell-storage/refund-confirm/index.vue | 124 + .../src/views/dashboard/console/index.vue | 10 +- .../src/views/operations/clean-room/index.vue | 829 +++++ web-admin/src/views/operations/demo/index.vue | 35 + .../exhibition-appointment/index.vue | 363 ++ .../operations/storage-facility/index.vue | 1285 +++++++ web-admin/src/views/system/category/index.vue | 64 +- web-admin/src/views/system/dept/index.vue | 266 +- web-admin/src/views/system/dict/index.vue | 122 +- web-admin/src/views/system/express/index.vue | 76 +- web-admin/src/views/system/leave/index.vue | 331 ++ web-admin/src/views/system/menu/index.vue | 110 +- web-admin/src/views/system/order/index.vue | 18 +- web-admin/src/views/system/post/index.vue | 515 ++- web-admin/src/views/system/product/index.vue | 31 +- web-admin/src/views/system/role/index.vue | 550 ++- web-admin/src/views/system/site/index.vue | 241 +- web-admin/src/views/system/tenant/index.vue | 84 +- web-admin/src/views/system/unit/index.vue | 76 +- web-admin/src/views/system/user/detail.vue | 12 +- web-admin/src/views/system/user/index.vue | 85 +- .../views/system/user/modules/user-dialog.vue | 5 +- .../workflow/components/ApprovalDialog.vue | 288 ++ .../workflow/components/ProcessStatusTag.vue | 25 + .../system/workflow/designer/FlowDesigner.vue | 1641 +++++++++ .../system/workflow/my-applications/index.vue | 518 +++ .../views/system/workflow/process/index.vue | 352 ++ .../system/workflow/task-completed/index.vue | 532 +++ .../system/workflow/task-pending/index.vue | 545 +++ 代码助手.md | 125 - 民族.sql | 118 + 186 files changed, 38821 insertions(+), 2571 deletions(-) create mode 100644 .playwright-mcp/page-2026-05-16T05-37-34-857Z.yml create mode 100644 .playwright-mcp/page-2026-05-16T05-37-57-758Z.yml create mode 100644 .playwright-mcp/page-2026-05-16T05-40-18-880Z.yml create mode 100644 .playwright-mcp/page-2026-05-16T05-40-31-785Z.yml create mode 100644 .playwright-mcp/page-2026-05-16T05-41-00-624Z.yml create mode 100644 .playwright-mcp/page-2026-05-16T05-41-36-020Z.yml create mode 100644 .playwright-mcp/page-2026-05-16T05-41-45-619Z.yml create mode 100644 .playwright-mcp/page-2026-05-16T05-42-20-871Z.yml create mode 100644 .superpowers/brainstorm/78964-1778664283/content/zkxw-cell-storage-modules-overview.html create mode 100644 .superpowers/brainstorm/78964-1778664283/content/zkxw-installment-warning-rule.html create mode 100644 .superpowers/brainstorm/78964-1778664283/state/server-stopped create mode 100644 .superpowers/brainstorm/78964-1778664283/state/server.pid create mode 100644 backend/app/api/basic_info.py create mode 100644 backend/app/api/collection_protocol.py create mode 100644 backend/app/api/ethnic.py create mode 100644 backend/app/api/leave.py create mode 100644 backend/app/api/operations.py create mode 100644 backend/app/api/workflow.py create mode 100644 backend/app/crud/cell_sample_type.py create mode 100644 backend/app/crud/clean_room.py create mode 100644 backend/app/crud/collection_protocol.py create mode 100644 backend/app/crud/collection_protocol_no.py create mode 100644 backend/app/crud/collection_region.py create mode 100644 backend/app/crud/customer.py create mode 100644 backend/app/crud/ethnic.py create mode 100644 backend/app/crud/exhibition_appointment.py create mode 100644 backend/app/crud/leave.py create mode 100644 backend/app/crud/sample_code.py create mode 100644 backend/app/crud/storage_facility.py create mode 100644 backend/app/crud/workflow.py create mode 100644 backend/app/schemas/attachment.py create mode 100644 backend/app/schemas/leave.py create mode 100644 backend/app/schemas/workflow.py create mode 100644 backend/app/services/workflow_callback.py create mode 100644 backend/app/services/workflow_engine.py create mode 100644 backend/tests/test_basic_info_tenant_ownership.py create mode 100644 backend/tests/test_collection_protocol_conventions.py create mode 100644 backend/tests/test_collection_protocol_save_bundle.py create mode 100644 backend/tests/test_ethnic.py create mode 100644 docs/sql/alter-biz-protocol-party-customer-apgar-decimal.sql create mode 100644 docs/sql/alter-biz-protocol-sample-status-nullable.sql create mode 100644 docs/sql/sys-ethnic.sql delete mode 100644 docs/sql/zkxw-init-dept-data.sql delete mode 100644 docs/sql/zkxw-init-dict-data.sql delete mode 100644 docs/sql/zkxw-init-menu-data.sql delete mode 100644 docs/sql/zkxw-init-role-data.sql delete mode 100644 docs/sql/zkxw-init-site-post-unit-express.sql delete mode 100644 docs/sql/zkxw-init-user-data.sql delete mode 100644 docs/sql/zkxw-requirements-v0.3-schema.sql create mode 100644 docs/sql/zkxw.sql create mode 100644 docs/superpowers/plans/2026-05-13-cell-storage-protocol-modules.md create mode 100644 docs/superpowers/plans/2026-05-13-flow-designer-enhancements.md create mode 100644 docs/superpowers/plans/2026-05-13-flow-designer-enterprise-enhancements.md create mode 100644 docs/superpowers/plans/2026-05-13-workflow-module.md create mode 100644 docs/superpowers/plans/2026-05-15-operations-support.md create mode 100644 docs/superpowers/plans/2026-05-15-storage-facility-redesign.md create mode 100644 docs/superpowers/specs/2026-05-13-workflow-module-design.md create mode 100644 docs/superpowers/specs/2026-05-15-operations-support-design.md create mode 100644 login-success.png create mode 100644 openspec/changes/basic-info-management/.openspec.yaml create mode 100644 openspec/changes/basic-info-management/design.md create mode 100644 openspec/changes/basic-info-management/proposal.md create mode 100644 openspec/changes/basic-info-management/specs/cell-sample-type-management/spec.md create mode 100644 openspec/changes/basic-info-management/specs/collection-protocol-no-management/spec.md create mode 100644 openspec/changes/basic-info-management/specs/collection-region-hospital-management/spec.md create mode 100644 openspec/changes/basic-info-management/specs/customer-management/spec.md create mode 100644 openspec/changes/basic-info-management/specs/sample-code-management/spec.md create mode 100644 openspec/changes/basic-info-management/tasks.md create mode 100644 openspec/changes/cell-storage-protocol-modules/.openspec.yaml create mode 100644 openspec/changes/cell-storage-protocol-modules/design.md create mode 100644 openspec/changes/cell-storage-protocol-modules/proposal.md create mode 100644 openspec/changes/cell-storage-protocol-modules/specs/cell-storage-collection-protocol/spec.md create mode 100644 openspec/changes/cell-storage-protocol-modules/specs/cell-storage-installment-warning/spec.md create mode 100644 openspec/changes/cell-storage-protocol-modules/specs/cell-storage-protocol-audit/spec.md create mode 100644 openspec/changes/cell-storage-protocol-modules/specs/cell-storage-receipt-confirm/spec.md create mode 100644 openspec/changes/cell-storage-protocol-modules/specs/cell-storage-refund-confirm/spec.md create mode 100644 openspec/changes/cell-storage-protocol-modules/tasks.md create mode 100644 web-admin/public/avatart.jpg delete mode 100644 web-admin/public/logo1.jpg delete mode 100644 web-admin/public/logo1_副本.jpg create mode 100644 web-admin/src/api/collection-protocol.ts create mode 100644 web-admin/src/api/ethnic.ts create mode 100644 web-admin/src/api/leave.ts create mode 100644 web-admin/src/api/workflow.ts create mode 100644 web-admin/src/components/business/system-view-dialog/index.vue create mode 100644 web-admin/src/views/basic-info/cell-sample-type/index.vue create mode 100644 web-admin/src/views/basic-info/collection-protocol-no/index.vue create mode 100644 web-admin/src/views/basic-info/collection-region/index.vue create mode 100644 web-admin/src/views/basic-info/customer/index.vue create mode 100644 web-admin/src/views/basic-info/sample-code/index.vue create mode 100644 web-admin/src/views/cell-storage/installment-warning/index.vue create mode 100644 web-admin/src/views/cell-storage/payment-confirm/index.vue create mode 100644 web-admin/src/views/cell-storage/protocol-audit/index.vue create mode 100644 web-admin/src/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue create mode 100644 web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue create mode 100644 web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue create mode 100644 web-admin/src/views/cell-storage/protocol/index.vue create mode 100644 web-admin/src/views/cell-storage/refund-confirm/index.vue create mode 100644 web-admin/src/views/operations/clean-room/index.vue create mode 100644 web-admin/src/views/operations/demo/index.vue create mode 100644 web-admin/src/views/operations/exhibition-appointment/index.vue create mode 100644 web-admin/src/views/operations/storage-facility/index.vue create mode 100644 web-admin/src/views/system/leave/index.vue create mode 100644 web-admin/src/views/system/workflow/components/ApprovalDialog.vue create mode 100644 web-admin/src/views/system/workflow/components/ProcessStatusTag.vue create mode 100644 web-admin/src/views/system/workflow/designer/FlowDesigner.vue create mode 100644 web-admin/src/views/system/workflow/my-applications/index.vue create mode 100644 web-admin/src/views/system/workflow/process/index.vue create mode 100644 web-admin/src/views/system/workflow/task-completed/index.vue create mode 100644 web-admin/src/views/system/workflow/task-pending/index.vue delete mode 100644 代码助手.md create mode 100644 民族.sql diff --git a/.playwright-mcp/page-2026-05-16T05-37-34-857Z.yml b/.playwright-mcp/page-2026-05-16T05-37-34-857Z.yml new file mode 100644 index 00000000..e6aca3cc --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-37-34-857Z.yml @@ -0,0 +1,5 @@ +- generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-16T05-37-57-758Z.yml b/.playwright-mcp/page-2026-05-16T05-37-57-758Z.yml new file mode 100644 index 00000000..146b6085 --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-37-57-758Z.yml @@ -0,0 +1,119 @@ +- generic [active] [ref=e13]: + - generic [ref=e64]: + - complementary [ref=e65]: + - generic [ref=e67]: + - generic [ref=e68] [cursor=pointer]: + - img "中科兴皖" [ref=e70] + - paragraph [ref=e71]: 中科兴皖 + - menubar [ref=e75]: + - menuitem "工作台" [ref=e76] [cursor=pointer]: + - generic [ref=e78]: 工作台 + - menuitem "运营支撑" [expanded] [ref=e79]: + - generic [ref=e80] [cursor=pointer]: + - img [ref=e83] + - generic [ref=e85]: 运营支撑 + - img [ref=e87] + - menu [ref=e89]: + - menuitem "洁净室管理" [ref=e90] [cursor=pointer]: + - generic [ref=e91]: 洁净室管理 + - menuitem "库位管理" [ref=e92] [cursor=pointer]: + - generic [ref=e93]: 库位管理 + - menuitem "展馆预约" [ref=e94] [cursor=pointer]: + - generic [ref=e95]: 展馆预约 + - menuitem "demo" [ref=e96] [cursor=pointer]: + - generic [ref=e97]: demo + - menuitem "基础信息管理" [expanded] [ref=e98]: + - generic [ref=e99] [cursor=pointer]: + - img [ref=e102] + - generic [ref=e104]: 基础信息管理 + - img [ref=e106] + - menu [ref=e108]: + - menuitem "客户管理" [ref=e109] [cursor=pointer]: + - generic [ref=e110]: 客户管理 + - menuitem "细胞样本类型管理" [ref=e111] [cursor=pointer]: + - generic [ref=e112]: 细胞样本类型管理 + - menuitem "采集地区及医院管理" [ref=e113] [cursor=pointer]: + - generic [ref=e114]: 采集地区及医院管理 + - menuitem "采集协议号管理" [ref=e115] [cursor=pointer]: + - generic [ref=e116]: 采集协议号管理 + - menuitem "样本编码管理" [ref=e117] [cursor=pointer]: + - generic [ref=e118]: 样本编码管理 + - menuitem "细胞存储管理" [expanded] [ref=e119]: + - generic [ref=e120] [cursor=pointer]: + - img [ref=e123] + - generic [ref=e125]: 细胞存储管理 + - img [ref=e127] + - menu [ref=e129]: + - menuitem "采集协议单" [ref=e130] [cursor=pointer]: + - generic [ref=e131]: 采集协议单 + - menuitem "采集协议单审核" [ref=e132] [cursor=pointer]: + - generic [ref=e133]: 采集协议单审核 + - menuitem "收款确认(财务)" [ref=e134] [cursor=pointer]: + - generic [ref=e135]: 收款确认(财务) + - menuitem "退费确认(财务)" [ref=e136] [cursor=pointer]: + - generic [ref=e137]: 退费确认(财务) + - menuitem "分期缴费预警" [ref=e138] [cursor=pointer]: + - generic [ref=e139]: 分期缴费预警 + - menuitem "工作流管理" [expanded] [ref=e140]: + - generic [ref=e141] [cursor=pointer]: + - img [ref=e144] + - generic [ref=e147]: 工作流管理 + - img [ref=e149] + - menu [ref=e151]: + - menuitem "流程配置" [ref=e152] [cursor=pointer]: + - generic [ref=e153]: 流程配置 + - menuitem "待办中心" [ref=e154] [cursor=pointer]: + - generic [ref=e155]: 待办中心 + - menuitem "已办中心" [ref=e156] [cursor=pointer]: + - generic [ref=e157]: 已办中心 + - menuitem "我的申请" [ref=e158] [cursor=pointer]: + - generic [ref=e159]: 我的申请 + - menuitem "请假申请" [ref=e160] [cursor=pointer]: + - generic [ref=e161]: 请假申请 + - main [ref=e162]: + - generic [ref=e164]: + - generic [ref=e165]: + - generic [ref=e166]: + - img [ref=e169] [cursor=pointer] + - img [ref=e173] [cursor=pointer] + - navigation "breadcrumb" [ref=e175]: + - list [ref=e176]: + - listitem [ref=e177]: + - generic [ref=e179]: 工作台 + - generic [ref=e180]: + - img [ref=e183] [cursor=pointer] + - img [ref=e187] [cursor=pointer] + - generic [ref=e188]: + - list [ref=e190]: + - listitem [ref=e191] [cursor=pointer]: 工作台 + - img [ref=e195] [cursor=pointer] + - generic [ref=e199]: + - generic [ref=e200]: + - heading "工作台" [level=1] [ref=e201] + - paragraph [ref=e202]: 以下为固定参考页,新增管理端界面请对照布局与类名;视觉以文档为准。 + - generic [ref=e203]: + - heading "快速入口" [level=2] [ref=e205]: 快速入口 + - generic [ref=e207]: + - paragraph [ref=e208]: "详见仓库根目录 `.cursorrules` 中「页面级 UI 模板(固定五页)」。" + - generic [ref=e209]: + - button "商品单位" [ref=e210] [cursor=pointer]: + - generic [ref=e211]: 商品单位 + - button "用户管理" [ref=e212] [cursor=pointer]: + - generic [ref=e213]: 用户管理 + - button "商品分类" [ref=e214] [cursor=pointer]: + - generic [ref=e215]: 商品分类 + - button "租户管理" [ref=e216] [cursor=pointer]: + - generic [ref=e217]: 租户管理 + - button "客户管理" [ref=e218] [cursor=pointer]: + - generic [ref=e219]: 客户管理 + - generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] + - alert [ref=e220]: + - img [ref=e222] + - generic [ref=e224]: + - heading "登录成功" [level=2] [ref=e225] + - paragraph [ref=e227]: 欢迎回来,系统管理员! + - img [ref=e229] [cursor=pointer] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-16T05-40-18-880Z.yml b/.playwright-mcp/page-2026-05-16T05-40-18-880Z.yml new file mode 100644 index 00000000..1c2a6a10 --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-40-18-880Z.yml @@ -0,0 +1,29 @@ +- generic [active] [ref=e13]: + - generic [ref=e231]: + - generic [ref=e233]: + - img "中科兴皖" [ref=e235] + - heading "一体化管理门户" [level=1] [ref=e236] + - paragraph [ref=e237]: 安全 · 高效 · 可扩展 + - generic [ref=e238]: + - generic [ref=e240]: 系统管理 + - generic [ref=e242]: 权限配置 + - generic [ref=e244]: 数据统计 + - generic [ref=e246]: + - generic [ref=e247]: + - heading "欢迎登录" [level=2] [ref=e248] + - paragraph [ref=e249]: 请输入您的账号信息 + - generic [ref=e250]: + - generic [ref=e254]: + - img [ref=e257] + - textbox "请输入用户名" [ref=e259] + - generic [ref=e263]: + - img [ref=e266] + - textbox "请输入密码" [ref=e269] + - button "登 录" [ref=e272] [cursor=pointer]: + - generic [ref=e273]: 登 录 + - generic [ref=e274]: 中科兴皖 © 2026 + - generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-16T05-40-31-785Z.yml b/.playwright-mcp/page-2026-05-16T05-40-31-785Z.yml new file mode 100644 index 00000000..1c2a6a10 --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-40-31-785Z.yml @@ -0,0 +1,29 @@ +- generic [active] [ref=e13]: + - generic [ref=e231]: + - generic [ref=e233]: + - img "中科兴皖" [ref=e235] + - heading "一体化管理门户" [level=1] [ref=e236] + - paragraph [ref=e237]: 安全 · 高效 · 可扩展 + - generic [ref=e238]: + - generic [ref=e240]: 系统管理 + - generic [ref=e242]: 权限配置 + - generic [ref=e244]: 数据统计 + - generic [ref=e246]: + - generic [ref=e247]: + - heading "欢迎登录" [level=2] [ref=e248] + - paragraph [ref=e249]: 请输入您的账号信息 + - generic [ref=e250]: + - generic [ref=e254]: + - img [ref=e257] + - textbox "请输入用户名" [ref=e259] + - generic [ref=e263]: + - img [ref=e266] + - textbox "请输入密码" [ref=e269] + - button "登 录" [ref=e272] [cursor=pointer]: + - generic [ref=e273]: 登 录 + - generic [ref=e274]: 中科兴皖 © 2026 + - generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-16T05-41-00-624Z.yml b/.playwright-mcp/page-2026-05-16T05-41-00-624Z.yml new file mode 100644 index 00000000..3358df39 --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-41-00-624Z.yml @@ -0,0 +1,119 @@ +- generic [active] [ref=e13]: + - generic [ref=e280]: + - complementary [ref=e281]: + - generic [ref=e283]: + - generic [ref=e284] [cursor=pointer]: + - img "中科兴皖" [ref=e286] + - paragraph [ref=e287]: 中科兴皖 + - menubar [ref=e291]: + - menuitem "工作台" [ref=e292] [cursor=pointer]: + - generic [ref=e294]: 工作台 + - menuitem "运营支撑" [expanded] [ref=e295]: + - generic [ref=e296] [cursor=pointer]: + - img [ref=e299] + - generic [ref=e301]: 运营支撑 + - img [ref=e303] + - menu [ref=e305]: + - menuitem "洁净室管理" [ref=e306] [cursor=pointer]: + - generic [ref=e307]: 洁净室管理 + - menuitem "库位管理" [ref=e308] [cursor=pointer]: + - generic [ref=e309]: 库位管理 + - menuitem "展馆预约" [ref=e310] [cursor=pointer]: + - generic [ref=e311]: 展馆预约 + - menuitem "demo" [ref=e312] [cursor=pointer]: + - generic [ref=e313]: demo + - menuitem "基础信息管理" [expanded] [ref=e314]: + - generic [ref=e315] [cursor=pointer]: + - img [ref=e318] + - generic [ref=e320]: 基础信息管理 + - img [ref=e322] + - menu [ref=e324]: + - menuitem "客户管理" [ref=e325] [cursor=pointer]: + - generic [ref=e326]: 客户管理 + - menuitem "细胞样本类型管理" [ref=e327] [cursor=pointer]: + - generic [ref=e328]: 细胞样本类型管理 + - menuitem "采集地区及医院管理" [ref=e329] [cursor=pointer]: + - generic [ref=e330]: 采集地区及医院管理 + - menuitem "采集协议号管理" [ref=e331] [cursor=pointer]: + - generic [ref=e332]: 采集协议号管理 + - menuitem "样本编码管理" [ref=e333] [cursor=pointer]: + - generic [ref=e334]: 样本编码管理 + - menuitem "细胞存储管理" [expanded] [ref=e335]: + - generic [ref=e336] [cursor=pointer]: + - img [ref=e339] + - generic [ref=e341]: 细胞存储管理 + - img [ref=e343] + - menu [ref=e345]: + - menuitem "采集协议单" [ref=e346] [cursor=pointer]: + - generic [ref=e347]: 采集协议单 + - menuitem "采集协议单审核" [ref=e348] [cursor=pointer]: + - generic [ref=e349]: 采集协议单审核 + - menuitem "收款确认(财务)" [ref=e350] [cursor=pointer]: + - generic [ref=e351]: 收款确认(财务) + - menuitem "退费确认(财务)" [ref=e352] [cursor=pointer]: + - generic [ref=e353]: 退费确认(财务) + - menuitem "分期缴费预警" [ref=e354] [cursor=pointer]: + - generic [ref=e355]: 分期缴费预警 + - menuitem "工作流管理" [expanded] [ref=e356]: + - generic [ref=e357] [cursor=pointer]: + - img [ref=e360] + - generic [ref=e363]: 工作流管理 + - img [ref=e365] + - menu [ref=e367]: + - menuitem "流程配置" [ref=e368] [cursor=pointer]: + - generic [ref=e369]: 流程配置 + - menuitem "待办中心" [ref=e370] [cursor=pointer]: + - generic [ref=e371]: 待办中心 + - menuitem "已办中心" [ref=e372] [cursor=pointer]: + - generic [ref=e373]: 已办中心 + - menuitem "我的申请" [ref=e374] [cursor=pointer]: + - generic [ref=e375]: 我的申请 + - menuitem "请假申请" [ref=e376] [cursor=pointer]: + - generic [ref=e377]: 请假申请 + - main [ref=e378]: + - generic [ref=e380]: + - generic [ref=e381]: + - generic [ref=e382]: + - img [ref=e385] [cursor=pointer] + - img [ref=e389] [cursor=pointer] + - navigation "breadcrumb" [ref=e391]: + - list [ref=e392]: + - listitem [ref=e393]: + - generic [ref=e395]: 工作台 + - generic [ref=e396]: + - img [ref=e399] [cursor=pointer] + - img [ref=e403] [cursor=pointer] + - generic [ref=e404]: + - list [ref=e406]: + - listitem [ref=e407] [cursor=pointer]: 工作台 + - img [ref=e411] [cursor=pointer] + - generic [ref=e415]: + - generic [ref=e416]: + - heading "工作台" [level=1] [ref=e417] + - paragraph [ref=e418]: 以下为固定参考页,新增管理端界面请对照布局与类名;视觉以文档为准。 + - generic [ref=e419]: + - heading "快速入口" [level=2] [ref=e421]: 快速入口 + - generic [ref=e423]: + - paragraph [ref=e424]: "详见仓库根目录 `.cursorrules` 中「页面级 UI 模板(固定五页)」。" + - generic [ref=e425]: + - button "商品单位" [ref=e426] [cursor=pointer]: + - generic [ref=e427]: 商品单位 + - button "用户管理" [ref=e428] [cursor=pointer]: + - generic [ref=e429]: 用户管理 + - button "商品分类" [ref=e430] [cursor=pointer]: + - generic [ref=e431]: 商品分类 + - button "租户管理" [ref=e432] [cursor=pointer]: + - generic [ref=e433]: 租户管理 + - button "客户管理" [ref=e434] [cursor=pointer]: + - generic [ref=e435]: 客户管理 + - generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] + - alert [ref=e436]: + - img [ref=e438] + - generic [ref=e440]: + - heading "登录成功" [level=2] [ref=e441] + - paragraph [ref=e443]: 欢迎回来,系统管理员! + - img [ref=e445] [cursor=pointer] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-16T05-41-36-020Z.yml b/.playwright-mcp/page-2026-05-16T05-41-36-020Z.yml new file mode 100644 index 00000000..2fecfa76 --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-41-36-020Z.yml @@ -0,0 +1,29 @@ +- generic [active] [ref=e13]: + - generic [ref=e447]: + - generic [ref=e449]: + - img "中科兴皖" [ref=e451] + - heading "一体化管理门户" [level=1] [ref=e452] + - paragraph [ref=e453]: 安全 · 高效 · 可扩展 + - generic [ref=e454]: + - generic [ref=e456]: 系统管理 + - generic [ref=e458]: 权限配置 + - generic [ref=e460]: 数据统计 + - generic [ref=e462]: + - generic [ref=e463]: + - heading "欢迎登录" [level=2] [ref=e464] + - paragraph [ref=e465]: 请输入您的账号信息 + - generic [ref=e466]: + - generic [ref=e470]: + - img [ref=e473] + - textbox "请输入用户名" [ref=e475] + - generic [ref=e479]: + - img [ref=e482] + - textbox "请输入密码" [ref=e485] + - button "登 录" [ref=e488] [cursor=pointer]: + - generic [ref=e489]: 登 录 + - generic [ref=e490]: 中科兴皖 © 2026 + - generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-16T05-41-45-619Z.yml b/.playwright-mcp/page-2026-05-16T05-41-45-619Z.yml new file mode 100644 index 00000000..2fecfa76 --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-41-45-619Z.yml @@ -0,0 +1,29 @@ +- generic [active] [ref=e13]: + - generic [ref=e447]: + - generic [ref=e449]: + - img "中科兴皖" [ref=e451] + - heading "一体化管理门户" [level=1] [ref=e452] + - paragraph [ref=e453]: 安全 · 高效 · 可扩展 + - generic [ref=e454]: + - generic [ref=e456]: 系统管理 + - generic [ref=e458]: 权限配置 + - generic [ref=e460]: 数据统计 + - generic [ref=e462]: + - generic [ref=e463]: + - heading "欢迎登录" [level=2] [ref=e464] + - paragraph [ref=e465]: 请输入您的账号信息 + - generic [ref=e466]: + - generic [ref=e470]: + - img [ref=e473] + - textbox "请输入用户名" [ref=e475] + - generic [ref=e479]: + - img [ref=e482] + - textbox "请输入密码" [ref=e485] + - button "登 录" [ref=e488] [cursor=pointer]: + - generic [ref=e489]: 登 录 + - generic [ref=e490]: 中科兴皖 © 2026 + - generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-16T05-42-20-871Z.yml b/.playwright-mcp/page-2026-05-16T05-42-20-871Z.yml new file mode 100644 index 00000000..453ee949 --- /dev/null +++ b/.playwright-mcp/page-2026-05-16T05-42-20-871Z.yml @@ -0,0 +1,119 @@ +- generic [active] [ref=e13]: + - generic [ref=e496]: + - complementary [ref=e497]: + - generic [ref=e499]: + - generic [ref=e500] [cursor=pointer]: + - img "中科兴皖" [ref=e502] + - paragraph [ref=e503]: 中科兴皖 + - menubar [ref=e507]: + - menuitem "工作台" [ref=e508] [cursor=pointer]: + - generic [ref=e510]: 工作台 + - menuitem "运营支撑" [expanded] [ref=e511]: + - generic [ref=e512] [cursor=pointer]: + - img [ref=e515] + - generic [ref=e517]: 运营支撑 + - img [ref=e519] + - menu [ref=e521]: + - menuitem "洁净室管理" [ref=e522] [cursor=pointer]: + - generic [ref=e523]: 洁净室管理 + - menuitem "库位管理" [ref=e524] [cursor=pointer]: + - generic [ref=e525]: 库位管理 + - menuitem "展馆预约" [ref=e526] [cursor=pointer]: + - generic [ref=e527]: 展馆预约 + - menuitem "demo" [ref=e528] [cursor=pointer]: + - generic [ref=e529]: demo + - menuitem "基础信息管理" [expanded] [ref=e530]: + - generic [ref=e531] [cursor=pointer]: + - img [ref=e534] + - generic [ref=e536]: 基础信息管理 + - img [ref=e538] + - menu [ref=e540]: + - menuitem "客户管理" [ref=e541] [cursor=pointer]: + - generic [ref=e542]: 客户管理 + - menuitem "细胞样本类型管理" [ref=e543] [cursor=pointer]: + - generic [ref=e544]: 细胞样本类型管理 + - menuitem "采集地区及医院管理" [ref=e545] [cursor=pointer]: + - generic [ref=e546]: 采集地区及医院管理 + - menuitem "采集协议号管理" [ref=e547] [cursor=pointer]: + - generic [ref=e548]: 采集协议号管理 + - menuitem "样本编码管理" [ref=e549] [cursor=pointer]: + - generic [ref=e550]: 样本编码管理 + - menuitem "细胞存储管理" [expanded] [ref=e551]: + - generic [ref=e552] [cursor=pointer]: + - img [ref=e555] + - generic [ref=e557]: 细胞存储管理 + - img [ref=e559] + - menu [ref=e561]: + - menuitem "采集协议单" [ref=e562] [cursor=pointer]: + - generic [ref=e563]: 采集协议单 + - menuitem "采集协议单审核" [ref=e564] [cursor=pointer]: + - generic [ref=e565]: 采集协议单审核 + - menuitem "收款确认(财务)" [ref=e566] [cursor=pointer]: + - generic [ref=e567]: 收款确认(财务) + - menuitem "退费确认(财务)" [ref=e568] [cursor=pointer]: + - generic [ref=e569]: 退费确认(财务) + - menuitem "分期缴费预警" [ref=e570] [cursor=pointer]: + - generic [ref=e571]: 分期缴费预警 + - menuitem "工作流管理" [expanded] [ref=e572]: + - generic [ref=e573] [cursor=pointer]: + - img [ref=e576] + - generic [ref=e579]: 工作流管理 + - img [ref=e581] + - menu [ref=e583]: + - menuitem "流程配置" [ref=e584] [cursor=pointer]: + - generic [ref=e585]: 流程配置 + - menuitem "待办中心" [ref=e586] [cursor=pointer]: + - generic [ref=e587]: 待办中心 + - menuitem "已办中心" [ref=e588] [cursor=pointer]: + - generic [ref=e589]: 已办中心 + - menuitem "我的申请" [ref=e590] [cursor=pointer]: + - generic [ref=e591]: 我的申请 + - menuitem "请假申请" [ref=e592] [cursor=pointer]: + - generic [ref=e593]: 请假申请 + - main [ref=e594]: + - generic [ref=e596]: + - generic [ref=e597]: + - generic [ref=e598]: + - img [ref=e601] [cursor=pointer] + - img [ref=e605] [cursor=pointer] + - navigation "breadcrumb" [ref=e607]: + - list [ref=e608]: + - listitem [ref=e609]: + - generic [ref=e611]: 工作台 + - generic [ref=e612]: + - img [ref=e615] [cursor=pointer] + - img [ref=e619] [cursor=pointer] + - generic [ref=e620]: + - list [ref=e622]: + - listitem [ref=e623] [cursor=pointer]: 工作台 + - img [ref=e627] [cursor=pointer] + - generic [ref=e631]: + - generic [ref=e632]: + - heading "工作台" [level=1] [ref=e633] + - paragraph [ref=e634]: 以下为固定参考页,新增管理端界面请对照布局与类名;视觉以文档为准。 + - generic [ref=e635]: + - heading "快速入口" [level=2] [ref=e637]: 快速入口 + - generic [ref=e639]: + - paragraph [ref=e640]: "详见仓库根目录 `.cursorrules` 中「页面级 UI 模板(固定五页)」。" + - generic [ref=e641]: + - button "商品单位" [ref=e642] [cursor=pointer]: + - generic [ref=e643]: 商品单位 + - button "用户管理" [ref=e644] [cursor=pointer]: + - generic [ref=e645]: 用户管理 + - button "商品分类" [ref=e646] [cursor=pointer]: + - generic [ref=e647]: 商品分类 + - button "租户管理" [ref=e648] [cursor=pointer]: + - generic [ref=e649]: 租户管理 + - button "客户管理" [ref=e650] [cursor=pointer]: + - generic [ref=e651]: 客户管理 + - generic [ref=e1]: + - generic "Toggle devtools panel" [ref=e2] [cursor=pointer]: + - img [ref=e3] + - generic "Toggle Component Inspector" [ref=e8] [cursor=pointer]: + - img [ref=e9] + - alert [ref=e652]: + - img [ref=e654] + - generic [ref=e656]: + - heading "登录成功" [level=2] [ref=e657] + - paragraph [ref=e659]: 欢迎回来,系统管理员! + - img [ref=e661] [cursor=pointer] \ No newline at end of file diff --git a/.superpowers/brainstorm/78964-1778664283/content/zkxw-cell-storage-modules-overview.html b/.superpowers/brainstorm/78964-1778664283/content/zkxw-cell-storage-modules-overview.html new file mode 100644 index 00000000..081ca34a --- /dev/null +++ b/.superpowers/brainstorm/78964-1778664283/content/zkxw-cell-storage-modules-overview.html @@ -0,0 +1,151 @@ +

细胞存储管理(2.2.6 ~ 2.2.10)模块总览

+

用于对齐:菜单结构、核心数据表、关键状态流转与后续页面原型拆分

+ +
+
菜单(本次范围)
+
+
+
+
+
2.2.6 采集协议单
+
+
+
列表:协议号 / 状态 / 采集专员 / 登记日期 / 创建时间
+
详情抽屉/弹窗(基础信息 + 样本 + 缴费 + 附件)
+
+
+
+
+
+

采集协议单

+

主表:biz_collection_protocol,关联采集信息/样本/运输/当事人/缴费等子表

+
+
+ +
+
+
+
2.2.7 采集协议单审核
+
+
+
列表:待审核 / 已审核(通过/退回)
+
审核弹窗:审核意见 + 操作(通过/退回)
+
+
+
+
+
+

协议单审核

+

表:biz_protocol_audit(按协议单维度记录审核结果/备注/审核人)

+
+
+ +
+
+
+
2.2.8 收款确认(财务)
+
+
+
列表:待确认缴费(按缴费明细)
+
确认弹窗:确认附件 + 备注
+
+
+
+
+
+

收款确认

+

表:biz_protocol_payment_detail(pay_status=dfj/jfqrz/yjf 等)

+
+
+ +
+
+
+
2.2.9 退费确认(财务)
+
+
+
列表:待确认退费(按缴费明细)
+
确认弹窗:退费附件 + 备注
+
+
+
+
+
+

退费确认

+

表:同上(pay_status=tfqrz/ytf)+ 财务确认附件字段

+
+
+ +
+
+
+
2.2.10 分期缴费预警
+
+
+
列表:即将到期/已逾期(按分期明细)
+
快捷操作:提醒/标记处理(可选)
+
+
+
+
+
+

分期缴费预警

+

字段:installment_due_date(明细应缴日)+ 状态过滤(未缴费/确认中)

+
+
+
+
+ +
+
关键状态流转(建议先对齐)
+
+
+
协议单状态(biz_collection_protocol.protocol_status)
+
+
+

dsx 待生效 → sxz 生效中 →(到期)→ ygq 已过期

+

任意阶段可 → yzf 已作废(毁约/异常终止/作废)

+

可选:→ zky 转科研(后续再做,当前仅保留字段/入口)

+
+
+
+ +
+
缴费明细状态(biz_protocol_payment_detail.pay_status)
+
+
+

dfj 待缴费 → jfqrz 缴费确认中 → yjf 已缴费

+

tfqrz 退费确认中 → ytf 已退费(通常由业务发起后进入)

+

收款确认页面主要处理:jfqrz → yjf;退费确认页面主要处理:tfqrz → ytf

+
+
+
+
+
+ +
+
下一步我需要你确认的 1 个问题
+
+
审核模块(2.2.7)的定义边界
+
+
+
+
A
+
+

仅“协议附件/缴费截图”审核

+

审核通过后仅写 biz_protocol_audit 记录,不改协议单主状态(状态仍由业务流程控制)

+
+
+
+
B
+
+

审核通过会驱动协议单生效

+

审核通过时同步将 protocol_status 从 dsx 推到 sxz(并记录生效时间/描述)

+
+
+
+

你在浏览器里点选 A 或 B 即可(也可以回终端用文字说明)。

+
+
+
+ diff --git a/.superpowers/brainstorm/78964-1778664283/content/zkxw-installment-warning-rule.html b/.superpowers/brainstorm/78964-1778664283/content/zkxw-installment-warning-rule.html new file mode 100644 index 00000000..4feaf09e --- /dev/null +++ b/.superpowers/brainstorm/78964-1778664283/content/zkxw-installment-warning-rule.html @@ -0,0 +1,76 @@ +

2.2.10 分期缴费用户预警:规则确认

+

目标:确定“预警/逾期”的判定口径,便于后端列表筛选与前端状态标签统一

+ +
+
数据来源(已确定)
+
+
biz_protocol_payment_detail
+
+
+

按 installment_due_date(YYYYMMDD)计算;仅展示分期明细(installment_no 不为空)

+

过滤状态:通常只看 dfj(待缴费)与(可选)jfqrz(确认中)

+
+
+
+
+ +
+
请选择预警口径(单选)
+
+
+
A
+
+

预警=未来 7 天内到期;逾期=已超过到期日

+

最常见口径;页面分两块:即将到期(0~7天) / 已逾期(<0天)

+
+
+
+
B
+
+

预警=未来 3 天内到期;逾期=已超过到期日

+

更紧迫;减少列表噪音

+
+
+
+
C
+
+

预警=未来 15 天内到期;逾期=已超过到期日

+

更早提醒;适合人工跟进周期长的场景

+
+
+
+
D
+
+

预警天数可配置(字典/配置表/环境变量)

+

灵活,但实现与运维成本更高;需要补一处配置入口

+
+
+
+
+ +
+
另外 1 个小点(默认建议)
+
+
确认:是否把“确认中 jfqrz”也纳入预警列表?
+
+
+
+
1
+
+

不纳入

+

预警只针对“待缴费 dfj”,避免和财务确认页面重复

+
+
+
+
2
+
+

纳入

+

把“待缴费 + 确认中”都算未完成付款,有助于跟进

+
+
+
+

你可以点 A/B/C/D,再点 1 或 2;也可以直接在终端回复(例如:A + 1)。

+
+
+
+ diff --git a/.superpowers/brainstorm/78964-1778664283/state/server-stopped b/.superpowers/brainstorm/78964-1778664283/state/server-stopped new file mode 100644 index 00000000..d3c18b35 --- /dev/null +++ b/.superpowers/brainstorm/78964-1778664283/state/server-stopped @@ -0,0 +1 @@ +{"reason":"owner process exited","timestamp":1778664344125} diff --git a/.superpowers/brainstorm/78964-1778664283/state/server.pid b/.superpowers/brainstorm/78964-1778664283/state/server.pid new file mode 100644 index 00000000..c91ad7a0 --- /dev/null +++ b/.superpowers/brainstorm/78964-1778664283/state/server.pid @@ -0,0 +1 @@ +78976 diff --git a/AGENTS.md b/AGENTS.md index 54bbe5c0..53c549c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,11 @@ Full specs: `openspec/changes/zkxw-engineering-standards/specs/*/spec.md`. Below - **Dialog grid**: Default 2-column (`.form-grid-2`). Max 5 columns. Full-width fields use `.form-col-span-2` (`grid-column: 1 / -1`). Narrow viewport SHALL fallback to fewer columns. - **List panel heading**: `panel-top` → `h2.title-bar-heading` SHALL show primary-color vertical bar via `::before`. Ambiguous titles (e.g. "租户" containing "户") SHALL be registered in `App.vue` `panelTitleBarTextRules`. - **Miniprogram media**: Thumbnails 1:1 `aspectFill`; carousel 4:3 `aspectFill`; detail images `widthFix`. Min tap target 44×44px. +- **Sidebar menu icons**: First-level menu items SHALL show icons before the name; second-level and deeper items SHALL NOT show icons (text only). Implemented via `level === 0` check in `SidebarSubmenu.vue`. +- **Tab bar (WorkTab) icons**: Tab bar tabs SHALL NOT show icons — only text titles. `meta.icon` is for sidebar only. +- **Form field annotations**: Field hints (e.g. "数值越小越靠前") SHALL appear as small text after the label via ``, NOT beside the input control. Style class: `.form-label-tip` (12px, `--mint-text-muted`). +- **Attachment upload card layout**: Upload trigger SHALL be a card (120×96px, dashed border `#a7f3d0`) placed inline as the first item in the same flex row as uploaded attachment cards. Hint text below the row. See `openspec/.../ui-conventions/spec.md` "附件上传卡片化布局" for full spec + CSS reference. +- **Sort fields (sort_order/orderNum)**: web-admin 列表、新增弹窗、编辑弹窗中默认不展示排序字段。后续根据功能需要自行添加。 - **Template pages** — new pages SHALL match these exactly: - Standard list: `src/views/system/unit/index.vue` - Complex list: `src/views/system/order/index.vue` @@ -36,7 +41,7 @@ Full specs: `openspec/changes/zkxw-engineering-standards/specs/*/spec.md`. Below - **Tenant isolation**: All business data queries SHALL filter by current user's `tenant_no`. All write/update endpoints SHALL set `tenant_no` to the current user's tenant. MUST NOT trust client-submitted tenant overrides (unless multi-tenant admin API with security review). - **Data permission**: All business queries SHALL apply data scope filtering based on the union of all roles assigned to the current user (menu + data permissions both use union semantics). -- **Default sort**: Lists SHALL sort by `order_num ASC, create_time DESC` when no custom sort is specified. +- **Default sort**: Lists SHALL sort by `create_time DESC` when no custom sort is specified. - **Route registration**: New route modules SHALL be registered in `app/api/__init__.py` AND `app/main.py`. SHALL NOT bypass auth/crypto/audit middleware. ### Database Conventions @@ -108,7 +113,7 @@ cp .env.example .env.local # fill in DB, Redis, MinIO, etc. - Auth: JWT + RSA-encrypted login. Login RSA key auto-generated or set via `LOGIN_RSA_PRIVATE_KEY_PEM` env var. - API encryption middleware: symmetric crypto layer controlled by `API_ENCRYPT_*` env vars. Excluded paths listed in `API_ENCRYPT_EXCLUDE_PATHS`. - Config: pydantic-settings (`app/core/config.py`), reads from `.env` / `.env.local` / `.env.production`. -- Database init SQL: `docs/sql/zkxw-requirements-v0.3-schema.sql`. Default admin: `admin` / `111111`, role `super_admin`. +- Database init / schema baseline: `docs/sql/zkxw.sql`(从当前库导出的全量 SQL)。Default admin: `admin` / `111111`, role `super_admin`. - Swagger docs at `/docs`, ReDoc at `/redoc`. - Tests: minimal, only `tests/test_api_crypto.py`. Run with `pytest`. diff --git a/backend/.env.example b/backend/.env.example index 6cdcdd18..29b67a3e 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,4 @@ -# 数据库连接配置示例(V0.3 库名示例见 docs/sql/zkxw-requirements-v0.3-schema.sql) +# 数据库连接配置示例(库名/表结构以 docs/sql/zkxw.sql 全量导出为准) # DATABASE_URL 格式:mysql+pymysql://user:password@host:port/dbname DATABASE_URL=mysql+pymysql://root:password@localhost:3306/zkxw @@ -43,6 +43,10 @@ DATABASE_URL=mysql+pymysql://root:password@localhost:3306/zkxw # MINIO_SECURE=false # MINIO_BASE_URL=https://www.chengyantek.cn/sanqiang +# 全量 API 请求日志(写入 sys_api_request_log;无表时会 warning「api_request_log persist failed」) +# 解决二选一:① 在库中执行 docs/sql/optional-sys-api-request-log.sql 建表 ② 关闭写入: +# API_REQUEST_LOG_ENABLED=false + # 微信支付配置示例 # 小程序支付使用 JSAPI(trade_type=JSAPI) # WXPAY_MCHID=1900000109 diff --git a/backend/README.md b/backend/README.md index d8b35612..b021c416 100644 --- a/backend/README.md +++ b/backend/README.md @@ -2,7 +2,7 @@ 基于 FastAPI 的精简后端:**Web 鉴权**(登录 RSA、登录、刷新 Token、登出、当前用户、菜单 code)、**静态运营菜单树**、**运营端路由占位**。原叁强鲜配业务 API 已移除,可按模块在 `app/api/admin.py` 等位置扩展。 -数据库最小初始化见:`docs/sql/zkxw-backend-skeleton.sql`(默认账号 `admin` / `111111`,角色 `super_admin`)。 +数据库结构与种子数据基线见:`docs/sql/zkxw.sql`(全库导出;默认账号 `admin` / `111111`,角色 `super_admin`)。 ## 技术栈 diff --git a/backend/app.pid b/backend/app.pid index 27a5dbe7..eccd6364 100644 --- a/backend/app.pid +++ b/backend/app.pid @@ -1 +1 @@ -9695 +92212 diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py index e5083be4..cca9f093 100644 --- a/backend/app/api/__init__.py +++ b/backend/app/api/__init__.py @@ -2,9 +2,21 @@ from app.api.public import router as public_router from app.api.menu import router as menu_router from app.api.admin import router as admin_router +from app.api.workflow import router as workflow_router +from app.api.basic_info import router as basic_info_router +from app.api.collection_protocol import router as collection_protocol_router +from app.api.leave import router as leave_router +from app.api.operations import router as operations_router +from app.api.ethnic import router as ethnic_router __all__ = [ "public_router", "menu_router", "admin_router", + "workflow_router", + "basic_info_router", + "collection_protocol_router", + "leave_router", + "operations_router", + "ethnic_router", ] diff --git a/backend/app/api/admin.py b/backend/app/api/admin.py index a0d15399..eba6709b 100644 --- a/backend/app/api/admin.py +++ b/backend/app/api/admin.py @@ -1,13 +1,17 @@ """运营端管理接口(租户、角色下拉等)""" +import os +import uuid from typing import Any, List, Optional -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, Query, UploadFile, File, Form from pydantic import BaseModel, ConfigDict, Field from sqlalchemy.orm import Session +from app.core.config import settings from app.core.database import get_db from app.core.menu_guard import require_admin_or_super_admin from app.core.response import success_response, error_response +from app.core.storage import storage from app.crud import tenant as tenant_svc from app.crud import dict_type as dict_type_svc from app.crud import dict_data as dict_data_svc @@ -19,7 +23,7 @@ from app.crud import express_company as express_company_svc from app.crud import menu as menu_svc from app.crud import role as role_svc from app.crud import user as user_svc -from app.models.models import SysRole, SysUser +from app.models.models import BizAttachment, SysRole, SysUser from app.services.web_auth_roles import _map_role_name_to_web router = APIRouter(prefix="/web/admin", tags=["运营端管理"]) @@ -39,7 +43,7 @@ async def list_roles_for_select( rows: List[SysRole] = ( db.query(SysRole) .filter(SysRole.del_flag == 0, SysRole.status == "1") - .order_by(SysRole.order_num.asc()) + .order_by(SysRole.create_time.desc()) .all() ) seen: set[str] = set() @@ -62,6 +66,37 @@ async def list_roles_for_select( return success_response(out) +@router.get("/users/options") +async def list_users_for_select( + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + """返回用户下拉选项(label=昵称,value=用户ID),供流程设计器审批人选择使用。""" + tenant_no = _user.get("tenant_no", "") + q = db.query(SysUser).filter(SysUser.del_flag == 0, SysUser.status == "1") + if tenant_no: + q = q.filter(SysUser.tenant_no == tenant_no) + rows = q.order_by(SysUser.create_time.desc()).all() + out = [{"label": (r.nick_name or r.user_no or ""), "value": r.id} for r in rows] + return success_response(out) + + +@router.get("/roles/options") +async def list_roles_for_designer( + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + """返回角色下拉选项(label=角色名,value=角色ID UUID),供流程设计器审批人选择使用。 + 与 /roles 不同,此端点返回角色 UUID 作为 value(引擎解析审批人需要 UUID)。""" + tenant_no = _user.get("tenant_no", "") + q = db.query(SysRole).filter(SysRole.del_flag == 0, SysRole.status == "1") + if tenant_no: + q = q.filter(SysRole.tenant_no == tenant_no) + rows = q.order_by(SysRole.create_time.desc()).all() + out = [{"label": (r.role_name or "").strip(), "value": r.id} for r in rows if (r.role_name or "").strip()] + return success_response(out) + + # --------------------------------------------------------------------------- # 租户管理 # --------------------------------------------------------------------------- @@ -239,6 +274,22 @@ async def list_users( return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) +@router.get("/users/{user_id}") +async def get_user_by_id( + user_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + row = user_svc.get_user(db, user_id) + if not row: + return error_response(404, "用户不存在") + if row.tenant_no != tenant_no and user.get("role") != "super_admin": + return error_response(403, "无权查看该用户") + data = user_svc.get_user_with_relations(db, user_id) + return success_response(data) + + @router.post("/users") async def create_user( body: UserCreateBody, @@ -862,6 +913,46 @@ async def delete_post( return error_response(400, str(e)) +class PostUsersUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + user_ids: List[str] = Field(default_factory=list, alias="userIds") + + +@router.get("/posts/{post_id}/users") +async def list_post_bound_users( + post_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + post = post_svc.get_post(db, post_id) + if not post or post.tenant_no != tenant_no: + return error_response(404, "职位不存在") + ids = user_svc.list_user_ids_bound_to_post(db, post_id, tenant_no) + return success_response({"userIds": ids}) + + +@router.put("/posts/{post_id}/users") +async def update_post_bound_users( + post_id: str, + body: PostUsersUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + post = post_svc.get_post(db, post_id) + if not post or post.tenant_no != tenant_no: + return error_response(404, "职位不存在") + try: + user_svc.sync_post_user_bindings( + db, post_id, tenant_no, body.user_ids, user.get("user_id") + ) + return success_response(None, message="保存成功") + except ValueError as e: + return error_response(400, str(e)) + + # --------------------------------------------------------------------------- # 物品单位管理 # --------------------------------------------------------------------------- @@ -1340,3 +1431,64 @@ async def update_role_data_scope( return success_response(None, message="更新成功") except ValueError as e: return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 附件上传 +# --------------------------------------------------------------------------- + + +def _format_file_size(size_bytes: int) -> str: + if size_bytes < 1024: + return f"{size_bytes} B" + elif size_bytes < 1024 * 1024: + return f"{size_bytes / 1024:.1f} KB" + else: + return f"{size_bytes / (1024 * 1024):.1f} MB" + + +@router.post("/attachment/upload") +async def upload_attachment( + file: UploadFile = File(...), + folder: Optional[str] = Form(""), + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + if not file or not file.filename: + return error_response(400, "请选择文件") + try: + file_content = await file.read() + full_url = storage.upload_file(file_content, file.filename, folder or "") + base_url = settings.MINIO_BASE_URL.rstrip("/") if settings.MINIO_BASE_URL else "" + if base_url and full_url.startswith(base_url + "/"): + file_url = full_url[len(base_url) + 1:] + else: + file_url = full_url + file_size = len(file_content) + file_ext = os.path.splitext(file.filename)[1].lstrip(".") if file.filename else "" + file_type = file.content_type or "" + tenant_no = _get_tenant_no(db, user) + record = BizAttachment( + id=uuid.uuid4().hex, + original_name=file.filename, + file_ext=file_ext, + file_url=file_url, + file_size=file_size, + file_type=file_type, + tenant_no=tenant_no, + create_by=user.get("user_id"), + ) + db.add(record) + db.commit() + db.refresh(record) + return success_response({ + "id": record.id, + "fileName": record.original_name, + "fileUrl": record.file_url, + "fileSize": record.file_size, + "fileSizeDisplay": _format_file_size(record.file_size or 0), + "fileType": record.file_type, + }) + except Exception as e: + db.rollback() + return error_response(500, f"上传失败: {str(e)}") diff --git a/backend/app/api/basic_info.py b/backend/app/api/basic_info.py new file mode 100644 index 00000000..92241a6a --- /dev/null +++ b/backend/app/api/basic_info.py @@ -0,0 +1,860 @@ +"""基础信息管理 API(客户、样本类型、采集地区/医院、协议号池、样本编码池)""" +from typing import Any, List, Optional + +from fastapi import APIRouter, Depends, Query +from pydantic import BaseModel, ConfigDict, Field +from sqlalchemy.orm import Session + +from app.core.database import get_db +from app.core.menu_guard import require_admin_or_super_admin +from app.core.response import error_response, success_response +from app.crud import cell_sample_type as cell_type_svc +from app.crud import collection_region as region_svc +from app.crud import collection_protocol_no as protocol_no_svc +from app.crud import customer as customer_svc +from app.crud import sample_code as sample_code_svc +from app.models.models import SysUser + +router = APIRouter(prefix="/web/admin", tags=["基础信息管理"]) + + +def _get_tenant_no(db: Session, user: dict) -> str: + user_id = user.get("user_id") + if not user_id: + raise PermissionError("用户身份信息缺失,无法确定租户") + sys_user = db.query(SysUser).filter(SysUser.id == user_id).first() + if not sys_user or not sys_user.tenant_no: + raise PermissionError("用户租户信息缺失") + return sys_user.tenant_no + + +# --------------------------------------------------------------------------- +# 客户 +# --------------------------------------------------------------------------- + + +class CustomerCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + customer_name: str = Field(..., alias="customerName") + sex: Optional[str] = Field("2", alias="sex") + id_card_no: Optional[str] = Field(None, alias="idCardNo") + age: Optional[int] = None + phone_no: Optional[str] = Field(None, alias="phoneNo") + customer_tags: Optional[str] = Field(None, alias="customerTags") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class CustomerUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + customer_name: Optional[str] = Field(None, alias="customerName") + sex: Optional[str] = None + id_card_no: Optional[str] = Field(None, alias="idCardNo") + age: Optional[int] = None + phone_no: Optional[str] = Field(None, alias="phoneNo") + customer_tags: Optional[str] = Field(None, alias="customerTags") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/customers") +async def list_customers( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = customer_svc.list_customers(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no) + tp = customer_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/customers") +async def create_customer( + body: CustomerCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = customer_svc.create_customer( + db, + customer_name=body.customer_name, + sex=body.sex or "2", + id_card_no=body.id_card_no, + age=body.age, + phone_no=body.phone_no, + customer_tags=body.customer_tags, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(customer_svc._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/customers/{customer_id}") +async def update_customer( + customer_id: str, + body: CustomerUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = customer_svc.update_customer( + db, + customer_id, + customer_name=payload.get("customer_name"), + sex=payload.get("sex"), + id_card_no=payload.get("id_card_no"), + age=payload.get("age"), + phone_no=payload.get("phone_no"), + customer_tags=payload.get("customer_tags"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(customer_svc._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/customers/{customer_id}") +async def delete_customer( + customer_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + customer_svc.soft_delete_customer(db, customer_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 细胞样本类型 +# --------------------------------------------------------------------------- + + +class CellSampleTypeCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + type_code: str = Field(..., alias="typeCode") + type_name: str = Field(..., alias="typeName") + storage_years: Optional[int] = Field(None, alias="storageYears") + storage_price: Optional[float] = Field(None, alias="storagePrice") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class CellSampleTypeUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + type_code: Optional[str] = Field(None, alias="typeCode") + type_name: Optional[str] = Field(None, alias="typeName") + storage_years: Optional[int] = Field(None, alias="storageYears") + storage_price: Optional[float] = Field(None, alias="storagePrice") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/cell-sample-types") +async def list_cell_sample_types( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = cell_type_svc.list_cell_sample_types( + db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no + ) + tp = cell_type_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/cell-sample-types") +async def create_cell_sample_type( + body: CellSampleTypeCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = cell_type_svc.create_cell_sample_type( + db, + type_code=body.type_code, + type_name=body.type_name, + storage_years=body.storage_years, + storage_price=body.storage_price, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(cell_type_svc._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/cell-sample-types/{row_id}") +async def update_cell_sample_type( + row_id: str, + body: CellSampleTypeUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = cell_type_svc.update_cell_sample_type( + db, + row_id, + type_code=payload.get("type_code"), + type_name=payload.get("type_name"), + storage_years=payload.get("storage_years"), + storage_price=payload.get("storage_price"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(cell_type_svc._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/cell-sample-types/{row_id}") +async def delete_cell_sample_type( + row_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + cell_type_svc.soft_delete_cell_sample_type(db, row_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 采集地区 +# --------------------------------------------------------------------------- + + +class CollectionRegionCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + region_name: str = Field(..., alias="regionName") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class CollectionRegionUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + region_name: Optional[str] = Field(None, alias="regionName") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/collection-regions") +async def list_collection_regions( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = region_svc.list_collection_regions( + db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no + ) + tp = region_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/collection-regions") +async def create_collection_region( + body: CollectionRegionCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = region_svc.create_collection_region( + db, + region_name=body.region_name, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(region_svc._region_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/collection-regions/{region_id}") +async def update_collection_region( + region_id: str, + body: CollectionRegionUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = region_svc.update_collection_region( + db, + region_id, + region_name=payload.get("region_name"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(region_svc._region_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/collection-regions/{region_id}") +async def delete_collection_region( + region_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + region_svc.soft_delete_collection_region(db, region_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 采集地区 — 下属医院 +# --------------------------------------------------------------------------- + + +class HospitalCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + hospital_name: str = Field(..., alias="hospitalName") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class HospitalUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + hospital_name: Optional[str] = Field(None, alias="hospitalName") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/collection-regions/{region_id}/hospitals") +async def list_hospitals( + region_id: str, + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + items, total = region_svc.list_hospitals( + db, region_id=region_id, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no + ) + tp = region_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + except ValueError as e: + return error_response(400, str(e)) + + +@router.post("/collection-regions/{region_id}/hospitals") +async def create_hospital( + region_id: str, + body: HospitalCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = region_svc.create_hospital( + db, + region_id=region_id, + hospital_name=body.hospital_name, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(region_svc._hospital_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/collection-regions/{region_id}/hospitals/{hospital_id}") +async def update_hospital( + region_id: str, + hospital_id: str, + body: HospitalUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + h = region_svc.get_hospital(db, hospital_id, tenant_no=tenant_no) + if not h or h.region_id != region_id: + return error_response(400, "医院不存在") + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = region_svc.update_hospital( + db, + hospital_id, + hospital_name=payload.get("hospital_name"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(region_svc._hospital_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/collection-regions/{region_id}/hospitals/{hospital_id}") +async def delete_hospital( + region_id: str, + hospital_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + h = region_svc.get_hospital(db, hospital_id, tenant_no=tenant_no) + if not h or h.region_id != region_id: + return error_response(400, "医院不存在") + region_svc.soft_delete_hospital(db, hospital_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 采集协议号池 +# --------------------------------------------------------------------------- + + +class ProtocolNoCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + protocol_no: str = Field(..., alias="protocolNo") + issued: int = 0 + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class ProtocolNoUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + protocol_no: Optional[str] = Field(None, alias="protocolNo") + issued: Optional[int] = None + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + issued_user_id: Optional[str] = Field(None, alias="issuedUserId") + issued_user_name: Optional[str] = Field(None, alias="issuedUserName") + issued_time: Optional[str] = Field(None, alias="issuedTime") + + +@router.get("/collection-protocol-nos") +async def list_protocol_nos( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = protocol_no_svc.list_protocol_nos( + db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no + ) + tp = protocol_no_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +class ProtocolNoBatchGenerateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + prefix: str = Field("ZKZX", alias="prefix") + middle: str = Field("34", alias="middle") + seq_start: int = Field(..., alias="seqStart") + seq_end: int = Field(..., alias="seqEnd") + digit_count: int = Field(6, alias="digitCount") + + +class ProtocolNoBatchIssueBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + ids: List[str] = Field(..., alias="ids") + issued_user_id: str = Field(..., alias="issuedUserId") + issued_user_name: Optional[str] = Field(None, alias="issuedUserName") + + +@router.post("/collection-protocol-nos/batch-generate") +async def batch_generate_protocol_nos( + body: ProtocolNoBatchGenerateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + result = protocol_no_svc.batch_generate_protocol_nos( + db, + prefix=body.prefix, + middle=body.middle, + seq_start=body.seq_start, + seq_end=body.seq_end, + digit_count=body.digit_count, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + msg = f"成功生成 {result['created']} 条" + if result['skipped'] > 0: + msg += f",跳过已存在 {result['skipped']} 条" + return success_response(result, message=msg) + except ValueError as e: + return error_response(400, str(e)) + + +@router.post("/collection-protocol-nos/batch-issue") +async def batch_issue_protocol_nos( + body: ProtocolNoBatchIssueBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + result = protocol_no_svc.batch_issue_protocol_nos( + db, + ids=body.ids, + issued_user_id=body.issued_user_id, + issued_user_name=body.issued_user_name, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + msg = f"成功下发 {result['issuedCount']} 条" + if result['alreadyIssued']: + msg += f",{len(result['alreadyIssued'])} 条已下发被跳过" + return success_response(result, message=msg) + except ValueError as e: + return error_response(400, str(e)) + + +class ProtocolNoBatchRevokeBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + ids: List[str] = Field(..., alias="ids") + + +@router.post("/collection-protocol-nos/batch-revoke") +async def batch_revoke_protocol_nos( + body: ProtocolNoBatchRevokeBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + result = protocol_no_svc.batch_revoke_protocol_nos( + db, + ids=body.ids, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + msg = f"成功收回 {result['revokedCount']} 条" + if result["notIssued"]: + msg += f",{len(result['notIssued'])} 条未下发已跳过" + return success_response(result, message=msg) + except ValueError as e: + return error_response(400, str(e)) + + +@router.post("/collection-protocol-nos") +async def create_protocol_no( + body: ProtocolNoCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = protocol_no_svc.create_protocol_no( + db, + protocol_no=body.protocol_no, + issued=body.issued, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(protocol_no_svc._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/collection-protocol-nos/{row_id}") +async def update_protocol_no( + row_id: str, + body: ProtocolNoUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = protocol_no_svc.update_protocol_no( + db, + row_id, + protocol_no=payload.get("protocol_no"), + issued=payload.get("issued"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + issued_user_id=payload.get("issued_user_id"), + issued_user_name=payload.get("issued_user_name"), + issued_time=payload.get("issued_time"), + ) + return success_response(protocol_no_svc._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/collection-protocol-nos/{row_id}") +async def delete_protocol_no( + row_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + protocol_no_svc.soft_delete_protocol_no(db, row_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 样本编码池 +# --------------------------------------------------------------------------- + + +class SampleCodeCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + sample_code: str = Field(..., alias="sampleCode") + issued: int = 0 + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class SampleCodeUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + sample_code: Optional[str] = Field(None, alias="sampleCode") + issued: Optional[int] = None + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + issued_user_id: Optional[str] = Field(None, alias="issuedUserId") + issued_user_name: Optional[str] = Field(None, alias="issuedUserName") + issued_time: Optional[str] = Field(None, alias="issuedTime") + + +class SampleCodeBatchGenerateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + sample_code_type: str = Field(..., alias="sampleCodeType") + middle: str = Field("34", alias="middle") + seq_start: int = Field(..., alias="seqStart") + seq_end: int = Field(..., alias="seqEnd") + digit_count: int = Field(6, alias="digitCount") + + +class SampleCodeBatchIssueBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + ids: List[str] = Field(..., alias="ids") + issued_user_id: str = Field(..., alias="issuedUserId") + issued_user_name: Optional[str] = Field(None, alias="issuedUserName") + + +@router.post("/sample-codes/batch-generate") +async def batch_generate_sample_codes( + body: SampleCodeBatchGenerateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + result = sample_code_svc.batch_generate_sample_codes( + db, + sample_code_type=body.sample_code_type, + middle=body.middle, + seq_start=body.seq_start, + seq_end=body.seq_end, + digit_count=body.digit_count, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + msg = f"成功生成 {result['created']} 条" + if result["skipped"] > 0: + msg += f",跳过已存在 {result['skipped']} 条" + return success_response(result, message=msg) + except ValueError as e: + return error_response(400, str(e)) + + +@router.post("/sample-codes/batch-issue") +async def batch_issue_sample_codes( + body: SampleCodeBatchIssueBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + result = sample_code_svc.batch_issue_sample_codes( + db, + ids=body.ids, + issued_user_id=body.issued_user_id, + issued_user_name=body.issued_user_name, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + msg = f"成功下发 {result['issuedCount']} 条" + if result["alreadyIssued"]: + msg += f",{len(result['alreadyIssued'])} 条已下发被跳过" + return success_response(result, message=msg) + except ValueError as e: + return error_response(400, str(e)) + + +class SampleCodeBatchRevokeBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + ids: List[str] = Field(..., alias="ids") + + +@router.post("/sample-codes/batch-revoke") +async def batch_revoke_sample_codes( + body: SampleCodeBatchRevokeBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + result = sample_code_svc.batch_revoke_sample_codes( + db, + ids=body.ids, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + msg = f"成功收回 {result['revokedCount']} 条" + if result["notIssued"]: + msg += f",{len(result['notIssued'])} 条未下发已跳过" + return success_response(result, message=msg) + except ValueError as e: + return error_response(400, str(e)) + + +@router.get("/sample-codes") +async def list_sample_codes( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = sample_code_svc.list_sample_codes( + db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no + ) + tp = sample_code_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/sample-codes") +async def create_sample_code( + body: SampleCodeCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = sample_code_svc.create_sample_code( + db, + sample_code=body.sample_code, + issued=body.issued, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(sample_code_svc._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/sample-codes/{row_id}") +async def update_sample_code( + row_id: str, + body: SampleCodeUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = sample_code_svc.update_sample_code( + db, + row_id, + sample_code=payload.get("sample_code"), + issued=payload.get("issued"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + issued_user_id=payload.get("issued_user_id"), + issued_user_name=payload.get("issued_user_name"), + issued_time=payload.get("issued_time"), + ) + return success_response(sample_code_svc._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/sample-codes/{row_id}") +async def delete_sample_code( + row_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + sample_code_svc.soft_delete_sample_code(db, row_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) diff --git a/backend/app/api/collection_protocol.py b/backend/app/api/collection_protocol.py new file mode 100644 index 00000000..e65ff1b3 --- /dev/null +++ b/backend/app/api/collection_protocol.py @@ -0,0 +1,513 @@ +"""细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API""" +from typing import Any, Dict, List, Optional + +from fastapi import APIRouter, Depends, Query +from pydantic import BaseModel, ConfigDict, Field +from sqlalchemy.orm import Session + +from sqlalchemy import and_ as sa_and + +from app.core.database import get_db +from app.core.menu_guard import require_admin_or_super_admin +from app.core.response import error_response, success_response +from app.core.utils import parse_datetime +from app.crud import collection_protocol as cp_svc +from app.crud import customer as customer_crud +from app.models.models import BizCollectionProtocol, BizCustomer, SysUser + +router = APIRouter(prefix="/web/admin", tags=["细胞存储"]) + + +def _get_tenant_no(db: Session, user: dict) -> str: + user_id = user.get("user_id") + if not user_id: + raise PermissionError("用户身份信息缺失,无法确定租户") + sys_user = db.query(SysUser).filter(SysUser.id == user_id).first() + if not sys_user or not sys_user.tenant_no: + raise PermissionError("用户租户信息缺失") + return sys_user.tenant_no + + +def _get_current_user_id(user: dict) -> str: + user_id = user.get("user_id") + if not user_id: + raise PermissionError("用户身份信息缺失") + return str(user_id) + + +_CAMEL_TO_SNAKE_DATETIME = { + "newbornDeliveryTime": "newborn_delivery_time", + "sampleCollectTime": "sample_collect_time", + "maternalBloodCollectTime": "maternal_blood_collect_time", +} + + +def _parse_datetime_fields(d: dict, camel_keys: list) -> None: + for camel in camel_keys: + val = d.get(camel) + if val is not None and isinstance(val, str): + snake = _CAMEL_TO_SNAKE_DATETIME.get(camel, camel) + parsed = parse_datetime(val) + if parsed: + d.pop(camel, None) + d[snake] = parsed + else: + d.pop(camel, None) + d[snake] = None + + +def _ensure_customer_for_mother(db: Session, party_customer: dict, tenant_no: str, user_id: str) -> dict: + """If mother info exists but no customerId, create a customer record and link it.""" + if not party_customer: + return party_customer + customer_id = party_customer.get("customer_id") or party_customer.get("customerId") + mother_name = (party_customer.get("mother_name") or party_customer.get("motherName") or "").strip() + if customer_id or not mother_name: + return party_customer + # Build customer fields from mother info + mother_phone = (party_customer.get("mother_phone") or party_customer.get("motherPhone") or "").strip() or None + mother_id_card = (party_customer.get("mother_id_card") or party_customer.get("motherIdCard") or "").strip() or None + + # Check if customer with this id_card already exists + if mother_id_card and customer_crud.id_card_taken(db, mother_id_card, tenant_no): + existing = db.query(BizCustomer).filter( + sa_and( + BizCustomer.id_card_no == mother_id_card, + BizCustomer.tenant_no == tenant_no, + BizCustomer.del_flag == 0, + ) + ).first() + if existing: + party_customer["customer_id"] = existing.id + return party_customer + + # Create new customer + try: + obj = customer_crud.create_customer( + db, + customer_name=mother_name, + sex="1", # female (mother) + id_card_no=mother_id_card, + phone_no=mother_phone, + tenant_no=tenant_no, + user_id=user_id, + ) + party_customer["customer_id"] = obj.id + except ValueError: + # If creation fails (e.g. duplicate), try to find existing + pass + return party_customer + + +# --------------------------------------------------------------------------- +# 采集协议单 +# --------------------------------------------------------------------------- + + +@router.get("/collection-protocols") +async def list_collection_protocols( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + protocolStatus: Optional[str] = Query(None, alias="protocolStatus"), + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + current_user_id = _get_current_user_id(user) + items, total = cp_svc.list_collection_protocols( + db, + page=page, + page_size=pageSize, + keyword=keyword, + protocol_status=protocolStatus, + tenant_no=tenant_no, + scope_collector_user_id=current_user_id, + ) + tp = cp_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.get("/collection-protocols/{protocol_id}") +async def get_collection_protocol_detail( + protocol_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + current_user_id = _get_current_user_id(user) + bundle = cp_svc.get_protocol_detail_bundle( + db, protocol_id, tenant_no, scope_collector_user_id=current_user_id + ) + if not bundle: + return error_response(404, "协议单不存在或无权查看") + return success_response(bundle) + + +class ProtocolCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + protocol_no: str = Field(..., alias="protocolNo") + protocol_no_id: Optional[str] = Field(None, alias="protocolNoId") + protocol_status: str = Field("dsx", alias="protocolStatus") + protocol_attachments: Optional[str] = Field(None, alias="protocolAttachments") + register_date: Optional[str] = Field(None, alias="registerDate") + collector_user_id: Optional[str] = Field(None, alias="collectorUserId") + remark: Optional[str] = None + party_customer: Optional[dict] = Field(None, alias="partyCustomer") + collection_info: Optional[dict] = Field(None, alias="collectionInfo") + samples: Optional[list] = Field(None, alias="samples") + payment: Optional[dict] = Field(None, alias="payment") + payment_details: Optional[list] = Field(None, alias="paymentDetails") + attachments: Optional[list] = Field(None, alias="attachments") + storage_cert: Optional[dict] = Field(None, alias="storageCert") + + +class ProtocolUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + protocol_no: Optional[str] = Field(None, alias="protocolNo") + protocol_status: Optional[str] = Field(None, alias="protocolStatus") + protocol_attachments: Optional[str] = Field(None, alias="protocolAttachments") + register_date: Optional[str] = Field(None, alias="registerDate") + effective_time: Optional[str] = Field(None, alias="effectiveTime") + effective_desc: Optional[str] = Field(None, alias="effectiveDesc") + expire_time: Optional[str] = Field(None, alias="expireTime") + expire_desc: Optional[str] = Field(None, alias="expireDesc") + void_time: Optional[str] = Field(None, alias="voidTime") + void_desc: Optional[str] = Field(None, alias="voidDesc") + to_research_time: Optional[str] = Field(None, alias="toResearchTime") + to_research_desc: Optional[str] = Field(None, alias="toResearchDesc") + collector_user_id: Optional[str] = Field(None, alias="collectorUserId") + remark: Optional[str] = None + party_customer: Optional[dict] = Field(None, alias="partyCustomer") + collection_info: Optional[dict] = Field(None, alias="collectionInfo") + samples: Optional[list] = Field(None, alias="samples") + payment: Optional[dict] = Field(None, alias="payment") + payment_details: Optional[list] = Field(None, alias="paymentDetails") + attachments: Optional[list] = Field(None, alias="attachments") + storage_cert: Optional[dict] = Field(None, alias="storageCert") + + +def _resolve_protocol_attachments( + protocol_attachments: Optional[str], + attachments: Optional[list], +) -> Optional[str]: + from_urls = cp_svc.attachments_to_urls(attachments) + if from_urls: + return from_urls + return protocol_attachments + + +def _persist_protocol_sub_resources( + db: Session, + protocol_id: str, + tenant_no: str, + user_id: str, + *, + party_customer: Optional[dict], + collection_info: Optional[dict], + samples: Optional[list], + payment: Optional[dict], + payment_details: Optional[list], + storage_cert: Optional[dict], +) -> None: + if party_customer: + _parse_datetime_fields(party_customer, ["newbornDeliveryTime"]) + party_customer = _ensure_customer_for_mother(db, party_customer, tenant_no, user_id) + cp_svc.upsert_party_customer(db, protocol_id, tenant_no, user_id, **party_customer) + if collection_info: + _parse_datetime_fields(collection_info, ["sampleCollectTime", "maternalBloodCollectTime"]) + cp_svc.upsert_collection_info(db, protocol_id, tenant_no, user_id, **collection_info) + if samples is not None: + cp_svc.save_samples(db, protocol_id, tenant_no, user_id, samples) + if payment: + cp_svc.upsert_payment(db, protocol_id, tenant_no, user_id, **payment) + if payment_details is not None: + pay = cp_svc.get_payment_by_protocol_id(db, protocol_id, tenant_no) + if not pay and payment: + pay = cp_svc.upsert_payment(db, protocol_id, tenant_no, user_id, **payment) + if pay: + cp_svc.save_payment_details(db, pay.id, tenant_no, user_id, payment_details) + if storage_cert: + cp_svc.upsert_storage_cert(db, protocol_id, tenant_no, user_id, **storage_cert) + + +@router.post("/collection-protocols") +async def create_collection_protocol( + body: ProtocolCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + user_id = _get_current_user_id(user) + try: + protocol_attachments = _resolve_protocol_attachments( + body.protocol_attachments, + body.attachments, + ) + obj = cp_svc.create_collection_protocol( + db, + protocol_no=body.protocol_no, + protocol_no_id=body.protocol_no_id, + protocol_status=body.protocol_status, + protocol_attachments=protocol_attachments, + register_date=body.register_date, + collector_user_id=user_id, + remark=body.remark, + tenant_no=tenant_no, + user_id=user_id, + ) + protocol_id = obj.id + _persist_protocol_sub_resources( + db, + protocol_id, + tenant_no, + user_id, + party_customer=body.party_customer, + collection_info=body.collection_info, + samples=body.samples, + payment=body.payment, + payment_details=body.payment_details, + storage_cert=body.storage_cert, + ) + return success_response(cp_svc._protocol_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/collection-protocols/{protocol_id}") +async def update_collection_protocol( + protocol_id: str, + body: ProtocolUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + user_id = _get_current_user_id(user) + try: + payload = body.model_dump(exclude_unset=True, by_alias=False) + sub_keys = { + "party_customer", "collection_info", "samples", + "payment", "payment_details", "attachments", "storage_cert", + } + main_payload = {k: v for k, v in payload.items() if k not in sub_keys} + main_payload.pop("collector_user_id", None) + if body.attachments is not None: + resolved = _resolve_protocol_attachments( + main_payload.get("protocol_attachments"), + body.attachments, + ) + if resolved is not None: + main_payload["protocol_attachments"] = resolved + obj = cp_svc.update_collection_protocol( + db, + protocol_id, + tenant_no=tenant_no, + user_id=user_id, + scope_collector_user_id=user_id, + **{k: v for k, v in main_payload.items() if v is not None}, + ) + _persist_protocol_sub_resources( + db, + protocol_id, + tenant_no, + user_id, + party_customer=body.party_customer, + collection_info=body.collection_info, + samples=body.samples, + payment=body.payment, + payment_details=body.payment_details, + storage_cert=body.storage_cert, + ) + return success_response(cp_svc._protocol_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/collection-protocols/{protocol_id}") +async def delete_collection_protocol( + protocol_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + user_id = _get_current_user_id(user) + try: + cp_svc.soft_delete_collection_protocol( + db, + protocol_id, + tenant_no=tenant_no, + scope_collector_user_id=user_id, + user_id=user_id, + ) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 审核 +# --------------------------------------------------------------------------- + + +@router.get("/collection-protocol-audits") +async def list_collection_protocol_audits( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + auditStatus: Optional[str] = Query(None, alias="auditStatus"), + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = cp_svc.list_audits( + db, + page=page, + page_size=pageSize, + keyword=keyword, + audit_status=auditStatus, + tenant_no=tenant_no, + ) + tp = cp_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +class AuditCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + audit_status: str = Field(..., alias="auditStatus") + audit_remark: Optional[str] = Field(None, alias="auditRemark") + + +@router.post("/collection-protocols/{protocol_id}/audit") +async def submit_collection_protocol_audit( + protocol_id: str, + body: AuditCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + try: + cp_svc.create_audit( + db, + collection_protocol_id=protocol_id, + audit_status=body.audit_status, + audit_remark=body.audit_remark, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + p = db.query(BizCollectionProtocol).filter(BizCollectionProtocol.id == protocol_id).first() + pn = (p.protocol_no if p else "") or "" + return success_response({"protocolNo": pn}, message="审核已提交") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 收款确认 +# --------------------------------------------------------------------------- + + +@router.get("/protocol-payment-details/receipt-confirm") +async def list_receipt_confirm( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = cp_svc.list_receipt_confirm_details(db, page=page, page_size=pageSize, tenant_no=tenant_no) + tp = cp_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +class FinanceConfirmBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + finance_confirm_attachments: Optional[str] = Field(None, alias="financeConfirmAttachments") + + +@router.post("/protocol-payment-details/{detail_id}/receipt-confirm") +async def post_receipt_confirm( + detail_id: str, + body: FinanceConfirmBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + try: + cp_svc.confirm_receipt( + db, + detail_id=detail_id, + tenant_no=tenant_no, + user_id=user.get("user_id"), + finance_confirm_attachments=body.finance_confirm_attachments, + ) + return success_response(None, message="确认成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 退费确认 +# --------------------------------------------------------------------------- + + +@router.get("/protocol-payment-details/refund-confirm") +async def list_refund_confirm( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = cp_svc.list_refund_confirm_details(db, page=page, page_size=pageSize, tenant_no=tenant_no) + tp = cp_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/protocol-payment-details/{detail_id}/refund-confirm") +async def post_refund_confirm( + detail_id: str, + body: FinanceConfirmBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + try: + cp_svc.confirm_refund( + db, + detail_id=detail_id, + tenant_no=tenant_no, + user_id=user.get("user_id"), + finance_confirm_attachments=body.finance_confirm_attachments, + ) + return success_response(None, message="确认成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 分期预警 +# --------------------------------------------------------------------------- + + +@router.get("/protocol-payment-details/installment-warning") +async def list_installment_warning( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + warningType: str = Query("all", alias="warningType"), + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = cp_svc.list_installment_warning( + db, + page=page, + page_size=pageSize, + warning_type=warningType, + tenant_no=tenant_no, + ) + tp = cp_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) diff --git a/backend/app/api/ethnic.py b/backend/app/api/ethnic.py new file mode 100644 index 00000000..67c25193 --- /dev/null +++ b/backend/app/api/ethnic.py @@ -0,0 +1,24 @@ +"""民族码表(下拉检索)""" +from typing import Optional + +from fastapi import APIRouter, Depends, Query +from sqlalchemy.orm import Session + +from app.core.database import get_db +from app.core.menu_guard import require_admin_or_super_admin +from app.core.response import success_response +from app.crud import ethnic as ethnic_svc + +router = APIRouter(prefix="/web/admin", tags=["民族码表"]) + + +@router.get("/ethnics") +async def list_ethnics_for_select( + keyword: Optional[str] = Query(None, description="民族名称模糊检索"), + limit: int = Query(200, ge=1, le=500), + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + """返回民族码表选项(按排序号升序)。""" + rows = ethnic_svc.list_ethnics(db, keyword=keyword, limit=limit) + return success_response({"list": rows}) diff --git a/backend/app/api/leave.py b/backend/app/api/leave.py new file mode 100644 index 00000000..8ddb48eb --- /dev/null +++ b/backend/app/api/leave.py @@ -0,0 +1,205 @@ +"""请假申请 API — 工作流测试""" +from __future__ import annotations + +import logging +from typing import Optional + +from fastapi import APIRouter, Depends, Query +from sqlalchemy.orm import Session + +from app.core.menu_guard import require_admin_or_super_admin +from app.core.database import get_db +from app.core.response import success_response, error_response +from app.crud import leave as leave_crud +from app.crud.leave import total_pages +from app.schemas.leave import LeaveCreate, LeaveUpdate + +logger = logging.getLogger(__name__) + +router = APIRouter(prefix="/web/admin/leave", tags=["请假管理"]) + +_LEAVE_TYPE_MAP = {"personal": "事假", "sick": "病假", "annual": "年假", "other": "其他"} + + +def _leave_local_callback(db: Session, business_id: str, event: str) -> None: + if not business_id: + return + if event == "completed": + leave_crud.update_leave_status(db, business_id, "approved") + elif event == "rejected": + leave_crud.update_leave_status(db, business_id, "rejected") + db.commit() + + +from app.services.workflow_callback import register_local_callback +register_local_callback("/api/admin/leave/callback", _leave_local_callback) + + +@router.get("") +async def list_leaves( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100), + keyword: Optional[str] = Query(None), + status: Optional[str] = Query(None), + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _user.get("tenant_no", "") + user_id = _user.get("user_id") or _user.get("id", "") + items, total = leave_crud.list_leaves( + db, page=page, page_size=pageSize, keyword=keyword, status=status, + tenant_no=tenant_no, create_by=user_id, + ) + for item in items: + item["leaveTypeLabel"] = _LEAVE_TYPE_MAP.get(item.get("leaveType", ""), item.get("leaveType", "")) + return success_response({ + "list": items, "total": total, "page": page, "pageSize": pageSize, + "totalPages": total_pages(total, pageSize), + }) + + +@router.post("") +async def create_leave( + body: LeaveCreate, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _user.get("tenant_no", "") + user_id = _user.get("user_id") or _user.get("id", "") + dept_id = _user.get("deptId", "") + row = leave_crud.create_leave( + db, + leave_type=body.leaveType, + start_date=body.startDate, + end_date=body.endDate, + days=body.days, + reason=body.reason or "", + tenant_no=tenant_no, + user_id=user_id, + dept_id=dept_id, + remark=body.remark or "", + ) + db.commit() + return success_response(leave_crud._row_to_dict(row)) + + +@router.post("/{leave_id}/submit") +async def submit_leave( + leave_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + from app.models.models import WfProcessDef + from app.services.workflow_engine import WorkflowEngine + + tenant_no = _user.get("tenant_no", "") + user_id = _user.get("user_id") or _user.get("id", "") + + leave = leave_crud.get_leave(db, leave_id) + if not leave: + return error_response(404, "请假记录不存在") + + if leave["status"] not in ("draft", "rejected"): + return error_response(400, "只有草稿或已驳回的请假单可以提交") + + defn = db.query(WfProcessDef).filter( + WfProcessDef.process_code == "leave_request", + WfProcessDef.del_flag == 0, + WfProcessDef.status == "1", + ).first() + if not defn: + return error_response(400, "未找到请假审批流程(leave_request),请先在流程配置中创建并发布") + + variables = { + "days": leave["days"], + "leave_days": leave["days"], + "leaveType": leave["leaveType"], + "applicant": user_id, + } + + try: + engine = WorkflowEngine(db) + instance = engine.start_process( + process_def_id=defn.id, + business_type="leave", + business_id=leave_id, + starter_user_id=user_id, + variables=variables, + callback_url="/api/admin/leave/callback", + tenant_no=tenant_no, + user_id=user_id, + ) + leave_crud.update_leave_status(db, leave_id, "pending", wf_instance_id=instance.id, user_id=user_id) + db.commit() + return success_response({"instanceId": instance.id, "status": "pending"}) + except Exception as e: + db.rollback() + logger.exception("发起请假审批失败: %s", e) + return error_response(500, f"发起审批失败: {e}") + + +@router.post("/callback") +async def leave_workflow_callback( + body: dict, + db: Session = Depends(get_db), +): + logger.info("请假工作流回调: %s", body) + business_id = body.get("businessId", "") + event = body.get("event", "") + if not business_id: + return success_response({"received": True}) + + if event == "completed": + leave_crud.update_leave_status(db, business_id, "approved") + elif event == "rejected": + leave_crud.update_leave_status(db, business_id, "rejected") + db.commit() + return success_response({"received": True}) + + +@router.get("/{leave_id}") +async def get_leave( + leave_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + item = leave_crud.get_leave(db, leave_id) + if not item: + return error_response(404, "请假记录不存在") + item["leaveTypeLabel"] = _LEAVE_TYPE_MAP.get(item.get("leaveType", ""), item.get("leaveType", "")) + return success_response(item) + + +@router.delete("/{leave_id}") +async def delete_leave( + leave_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + user_id = _user.get("user_id") or _user.get("id", "") + leave = leave_crud.get_leave(db, leave_id) + if not leave: + return error_response(404, "请假记录不存在") + if leave["status"] == "pending": + return error_response(400, "审批中的请假单不能删除") + leave_crud.soft_delete_leave(db, leave_id, user_id=user_id) + db.commit() + return success_response(None) + + +@router.put("/{leave_id}") +async def update_leave( + leave_id: str, + body: LeaveUpdate, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + user_id = _user.get("user_id") or _user.get("id", "") + leave = leave_crud.get_leave(db, leave_id) + if not leave: + return error_response(404, "请假记录不存在") + if leave["status"] not in ("draft", "rejected"): + return error_response(400, "只有草稿或已驳回状态可以编辑") + leave_crud.update_leave(db, leave_id, user_id=user_id, **body.model_dump(exclude_none=True)) + db.commit() + return success_response(leave_crud.get_leave(db, leave_id)) diff --git a/backend/app/api/menu.py b/backend/app/api/menu.py index 86bcfced..763ce147 100644 --- a/backend/app/api/menu.py +++ b/backend/app/api/menu.py @@ -6,7 +6,7 @@ from app.core.response import success_response router = APIRouter(prefix="/web/menu", tags=["菜单管理"]) -# 与 web-admin/src/router/modules/sqxp.ts 一致:「Demo」参考页 +「系统管理」 +# 与 web-admin/src/router/modules/sqxp.ts 一致:Demo、系统管理、基础信息管理 等 STATIC_MENU_TREE = [ { "id": "10000000000000000000000000000002", @@ -98,6 +98,104 @@ STATIC_MENU_TREE = [ }, ], }, + { + "id": "40000000000000000000000000000001", + "name": "BasicInfo", + "path": "/basic-info", + "component": "/index/index", + "meta": { + "title": "基础信息管理", + "icon": "ri:folder-user-line", + "keepAlive": False, + "roles": ["admin"], + }, + "children": [ + { + "id": "40000000000000000000000000000002", + "name": "BizCustomer", + "path": "customer", + "component": "/basic-info/customer/index", + "meta": {"title": "客户管理", "keepAlive": True}, + }, + { + "id": "40000000000000000000000000000003", + "name": "BizCellSampleType", + "path": "cell-sample-type", + "component": "/basic-info/cell-sample-type/index", + "meta": {"title": "细胞样本类型管理", "keepAlive": True}, + }, + { + "id": "40000000000000000000000000000004", + "name": "BizCollectionRegion", + "path": "collection-region", + "component": "/basic-info/collection-region/index", + "meta": {"title": "采集地区及医院管理", "keepAlive": True}, + }, + { + "id": "40000000000000000000000000000005", + "name": "BizCollectionProtocolNo", + "path": "collection-protocol-no", + "component": "/basic-info/collection-protocol-no/index", + "meta": {"title": "采集协议号管理", "keepAlive": True}, + }, + { + "id": "40000000000000000000000000000006", + "name": "BizSampleCode", + "path": "sample-code", + "component": "/basic-info/sample-code/index", + "meta": {"title": "样本编码管理", "keepAlive": True}, + }, + ], + }, + { + "id": "50000000000000000000000000000001", + "name": "CellStorage", + "path": "/cell-storage", + "component": "/index/index", + "meta": { + "title": "细胞存储管理", + "icon": "ri:test-tube-line", + "keepAlive": False, + "roles": ["admin"], + }, + "children": [ + { + "id": "50000000000000000000000000000002", + "name": "CellStorageProtocol", + "path": "protocol", + "component": "/cell-storage/protocol/index", + "meta": {"title": "采集协议单", "keepAlive": True}, + }, + { + "id": "50000000000000000000000000000003", + "name": "CellStorageProtocolAudit", + "path": "protocol-audit", + "component": "/cell-storage/protocol-audit/index", + "meta": {"title": "采集协议单审核", "keepAlive": True}, + }, + { + "id": "50000000000000000000000000000004", + "name": "CellStoragePaymentConfirm", + "path": "payment-confirm", + "component": "/cell-storage/payment-confirm/index", + "meta": {"title": "采集协议单收款确认(财务确认)", "keepAlive": True}, + }, + { + "id": "50000000000000000000000000000005", + "name": "CellStorageRefundConfirm", + "path": "refund-confirm", + "component": "/cell-storage/refund-confirm/index", + "meta": {"title": "采集协议单退费确认(财务确认)", "keepAlive": True}, + }, + { + "id": "50000000000000000000000000000006", + "name": "CellStorageInstallmentWarning", + "path": "installment-warning", + "component": "/cell-storage/installment-warning/index", + "meta": {"title": "分期缴费用户预警模块", "keepAlive": True}, + }, + ], + }, ] diff --git a/backend/app/api/operations.py b/backend/app/api/operations.py new file mode 100644 index 00000000..5ec2bd0f --- /dev/null +++ b/backend/app/api/operations.py @@ -0,0 +1,598 @@ +"""运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约)""" +from datetime import datetime +from typing import Any, Optional + +from fastapi import APIRouter, Depends, Query +from pydantic import BaseModel, ConfigDict, Field +from sqlalchemy.orm import Session + +from app.core.database import get_db +from app.core.menu_guard import require_admin_or_super_admin +from app.core.response import error_response, success_response +from app.crud import clean_room as clean_room_svc +from app.crud import exhibition_appointment as exhibition_svc +from app.crud import storage_facility as storage_facility_svc +from app.models.models import SysUser + +router = APIRouter(prefix="/web/admin", tags=["运营支撑"]) + + +def _get_tenant_no(db: Session, user: dict) -> str: + user_id = user.get("user_id") + if not user_id: + raise PermissionError("用户身份信息缺失,无法确定租户") + sys_user = db.query(SysUser).filter(SysUser.id == user_id).first() + if not sys_user or not sys_user.tenant_no: + raise PermissionError("用户租户信息缺失") + return sys_user.tenant_no + + +# --------------------------------------------------------------------------- +# 洁净室 +# --------------------------------------------------------------------------- + + +class CleanRoomCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + room_code: str = Field(..., alias="roomCode") + room_name: str = Field(..., alias="roomName") + room_grade: Optional[str] = Field(None, alias="roomGrade") + room_status: Optional[str] = Field("zc", alias="roomStatus") + area_sqm: Optional[float] = Field(None, alias="areaSqm") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class CleanRoomUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + room_code: Optional[str] = Field(None, alias="roomCode") + room_name: Optional[str] = Field(None, alias="roomName") + room_grade: Optional[str] = Field(None, alias="roomGrade") + room_status: Optional[str] = Field(None, alias="roomStatus") + area_sqm: Optional[float] = Field(None, alias="areaSqm") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/clean-rooms") +async def list_clean_rooms( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = clean_room_svc.list_clean_rooms(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no) + tp = clean_room_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/clean-rooms") +async def create_clean_room( + body: CleanRoomCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = clean_room_svc.create_clean_room( + db, + room_code=body.room_code, + room_name=body.room_name, + room_grade=body.room_grade, + room_status=body.room_status or "zc", + area_sqm=body.area_sqm, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(clean_room_svc._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/clean-rooms/{room_id}") +async def update_clean_room( + room_id: str, + body: CleanRoomUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = clean_room_svc.update_clean_room( + db, + room_id, + room_code=payload.get("room_code"), + room_name=payload.get("room_name"), + room_grade=payload.get("room_grade"), + room_status=payload.get("room_status"), + area_sqm=payload.get("area_sqm"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(clean_room_svc._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/clean-rooms/{room_id}") +async def delete_clean_room( + room_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + clean_room_svc.soft_delete_clean_room(db, room_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 进出记录 +# --------------------------------------------------------------------------- + + +class AccessLogCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + clean_room_id: str = Field(..., alias="cleanRoomId") + person_name: Optional[str] = Field(None, alias="personName") + dept_name: Optional[str] = Field(None, alias="deptName") + enter_time: Optional[str] = Field(None, alias="enterTime") + purpose: Optional[str] = None + leave_time: Optional[str] = Field(None, alias="leaveTime") + body_temp: Optional[str] = Field(None, alias="bodyTemp") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class AccessLogUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + person_name: Optional[str] = Field(None, alias="personName") + dept_name: Optional[str] = Field(None, alias="deptName") + enter_time: Optional[str] = Field(None, alias="enterTime") + purpose: Optional[str] = None + leave_time: Optional[str] = Field(None, alias="leaveTime") + body_temp: Optional[str] = Field(None, alias="bodyTemp") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +def _parse_optional_dt(value: Optional[str]) -> Optional[datetime]: + if not value: + return None + return datetime.fromisoformat(value) + + +@router.get("/clean-room-access-logs") +async def list_access_logs( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + cleanRoomId: str = Query(..., alias="cleanRoomId"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = clean_room_svc.list_access_logs(db, page=page, page_size=pageSize, clean_room_id=cleanRoomId, keyword=keyword, tenant_no=tenant_no) + tp = clean_room_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/clean-room-access-logs") +async def create_access_log( + body: AccessLogCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = clean_room_svc.create_access_log( + db, + clean_room_id=body.clean_room_id, + person_name=body.person_name, + dept_name=body.dept_name, + enter_time=_parse_optional_dt(body.enter_time), + purpose=body.purpose, + leave_time=_parse_optional_dt(body.leave_time), + body_temp=body.body_temp, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id_param=user.get("user_id"), + ) + return success_response(clean_room_svc._access_log_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/clean-room-access-logs/{log_id}") +async def update_access_log( + log_id: str, + body: AccessLogUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = clean_room_svc.update_access_log( + db, + log_id, + person_name=payload.get("person_name"), + dept_name=payload.get("dept_name"), + enter_time=_parse_optional_dt(payload.get("enter_time")) if "enter_time" in payload else None, + purpose=payload.get("purpose"), + leave_time=_parse_optional_dt(payload.get("leave_time")) if "leave_time" in payload else None, + body_temp=payload.get("body_temp"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id_param=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(clean_room_svc._access_log_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/clean-room-access-logs/{log_id}") +async def delete_access_log( + log_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + clean_room_svc.soft_delete_access_log(db, log_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 仓储设施 +# --------------------------------------------------------------------------- + + +class StorageFacilityCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + parent_id: str = Field("0", alias="parentId") + node_name: str = Field(..., alias="nodeName") + node_type: Optional[str] = Field(None, alias="nodeType") + is_leaf: Optional[int] = Field(0, alias="isLeaf") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class StorageFacilityUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + node_name: Optional[str] = Field(None, alias="nodeName") + node_type: Optional[str] = Field(None, alias="nodeType") + is_leaf: Optional[int] = Field(None, alias="isLeaf") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/storage-facilities") +async def list_storage_facilities( + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items = storage_facility_svc.list_all_facilities(db, tenant_no=tenant_no, keyword=keyword) + return success_response(items) + + +@router.post("/storage-facilities") +async def create_storage_facility( + body: StorageFacilityCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = storage_facility_svc.create_facility( + db, + parent_id=body.parent_id, + node_name=body.node_name, + node_type=body.node_type, + is_leaf=body.is_leaf, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(storage_facility_svc._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/storage-facilities/{node_id}") +async def update_storage_facility( + node_id: str, + body: StorageFacilityUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = storage_facility_svc.update_facility( + db, + node_id, + node_name=payload.get("node_name"), + node_type=payload.get("node_type"), + is_leaf=payload.get("is_leaf"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(storage_facility_svc._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/storage-facilities/{node_id}") +async def delete_storage_facility( + node_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + storage_facility_svc.soft_delete_facility(db, node_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 储位 +# --------------------------------------------------------------------------- + + +class StorageSlotCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + facility_node_id: str = Field(..., alias="facilityNodeId") + slot_code: Optional[str] = Field(None, alias="slotCode") + slot_status: Optional[str] = Field(None, alias="slotStatus") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class StorageSlotBatchCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + facility_node_id: str = Field(..., alias="facilityNodeId") + rows: int = Field(..., ge=1, le=50) + cols: int = Field(..., ge=1, le=50) + code_prefix: Optional[str] = Field("", alias="codePrefix") + + +class StorageSlotUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + slot_code: Optional[str] = Field(None, alias="slotCode") + slot_status: Optional[str] = Field(None, alias="slotStatus") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/storage-facility-slots") +async def list_storage_slots( + facilityNodeId: str = Query(..., alias="facilityNodeId"), + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items = storage_facility_svc.list_slots(db, facility_node_id=facilityNodeId, tenant_no=tenant_no) + return success_response(items) + + +@router.post("/storage-facility-slots/batch") +async def batch_create_storage_slots( + body: StorageSlotBatchCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + items = storage_facility_svc.batch_create_slots( + db, + facility_node_id=body.facility_node_id, + rows=body.rows, + cols=body.cols, + code_prefix=body.code_prefix, + tenant_no=tenant_no, + user_id=user.get("user_id"), + ) + return success_response(items, message=f"批量创建 {len(items)} 个格子成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.post("/storage-facility-slots") +async def create_storage_slot( + body: StorageSlotCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = storage_facility_svc.create_slot( + db, + facility_node_id=body.facility_node_id, + slot_code=body.slot_code, + slot_status=body.slot_status, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(storage_facility_svc._slot_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/storage-facility-slots/{slot_id}") +async def update_storage_slot( + slot_id: str, + body: StorageSlotUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = storage_facility_svc.update_slot( + db, + slot_id, + slot_code=payload.get("slot_code"), + slot_status=payload.get("slot_status"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=tenant_no, + ) + return success_response(storage_facility_svc._slot_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/storage-facility-slots/{slot_id}") +async def delete_storage_slot( + slot_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + storage_facility_svc.soft_delete_slot(db, slot_id, tenant_no=tenant_no) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 展厅预约 +# --------------------------------------------------------------------------- + + +class ExhibitionAppointmentCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + appointment_no: Optional[str] = Field(None, alias="appointmentNo") + visit_org: Optional[str] = Field(None, alias="visitOrg") + contact_name: Optional[str] = Field(None, alias="contactName") + visit_date: Optional[str] = Field(None, alias="visitDate") + visitor_count: Optional[int] = Field(None, alias="visitorCount") + reception_user_id: Optional[str] = Field(None, alias="receptionUserId") + order_num: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class ExhibitionAppointmentUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + visit_org: Optional[str] = Field(None, alias="visitOrg") + contact_name: Optional[str] = Field(None, alias="contactName") + visit_date: Optional[str] = Field(None, alias="visitDate") + visitor_count: Optional[int] = Field(None, alias="visitorCount") + reception_user_id: Optional[str] = Field(None, alias="receptionUserId") + order_num: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +@router.get("/exhibition-appointments") +async def list_exhibition_appointments( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _get_tenant_no(db, user) + items, total = exhibition_svc.list_exhibition_appointments(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no) + tp = exhibition_svc.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/exhibition-appointments") +async def create_exhibition_appointment( + body: ExhibitionAppointmentCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + obj = exhibition_svc.create_exhibition_appointment( + db, + appointment_no=body.appointment_no, + visit_org=body.visit_org, + contact_name=body.contact_name, + visit_date=body.visit_date, + visitor_count=body.visitor_count, + reception_user_id=body.reception_user_id, + tenant_no=tenant_no, + order_num=body.order_num, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(exhibition_svc._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/exhibition-appointments/{appointment_id}") +async def update_exhibition_appointment( + appointment_id: str, + body: ExhibitionAppointmentUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = _get_tenant_no(db, user) + payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False) + obj = exhibition_svc.update_exhibition_appointment( + db, + appointment_id, + visit_org=payload.get("visit_org"), + contact_name=payload.get("contact_name"), + visit_date=payload.get("visit_date"), + visitor_count=payload.get("visitor_count"), + reception_user_id=payload.get("reception_user_id"), + order_num=payload.get("order_num"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + ) + return success_response(exhibition_svc._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/exhibition-appointments/{appointment_id}") +async def delete_exhibition_appointment( + appointment_id: str, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + exhibition_svc.soft_delete_exhibition_appointment(db, appointment_id) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) diff --git a/backend/app/api/public.py b/backend/app/api/public.py index bb66764d..8ac08f3a 100644 --- a/backend/app/api/public.py +++ b/backend/app/api/public.py @@ -1,4 +1,4 @@ -"""公共接口(Web 登录、刷新 Token、登出;用户模型对齐 zkxw-requirements-v0.3-schema.sql)""" +"""公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线)""" from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Request diff --git a/backend/app/api/workflow.py b/backend/app/api/workflow.py new file mode 100644 index 00000000..7b29dc68 --- /dev/null +++ b/backend/app/api/workflow.py @@ -0,0 +1,514 @@ +"""工作流管理接口""" +from typing import Any, Dict, List, Optional + +from fastapi import APIRouter, Depends, Query +from pydantic import AliasChoices, BaseModel, ConfigDict, Field +from sqlalchemy.orm import Session + +from app.core.database import get_db +from app.core.menu_guard import require_admin_or_super_admin +from app.core.response import success_response, error_response +from app.crud import workflow as wf_crud +from app.models.models import SysUser, WfNode, WfProcessDef, WfProcessVersion, WfTask +from app.services.workflow_engine import WorkflowEngine + +router = APIRouter(prefix="/web/admin/workflow", tags=["工作流管理"]) + +# --------------------------------------------------------------------------- +# 业务类型 → 中文名称映射 +# --------------------------------------------------------------------------- + +_BUSINESS_TYPE_LABELS: Dict[str, str] = { + "leave": "请假申请", +} + + +def _business_type_label(code: Optional[str]) -> str: + if not code: + return "" + return _BUSINESS_TYPE_LABELS.get(code, code) + + +# --------------------------------------------------------------------------- +# 业务表单字段提供者注册表 +# 新增业务类型时,只需添加一个 provider 函数并注册即可,前端无需改动 +# --------------------------------------------------------------------------- + +import logging +_form_field_logger = logging.getLogger("workflow.form_fields") + + +def _leave_form_fields(db: Session, business_id: str) -> List[Dict[str, str]]: + from app.crud import leave as leave_crud + item = leave_crud.get_leave(db, business_id) + if not item: + return [] + leave_type_map = {"personal": "事假", "sick": "病假", "annual": "年假", "other": "其他"} + fields = [ + {"label": "请假类型", "value": leave_type_map.get(item.get("leaveType", ""), item.get("leaveType", ""))}, + {"label": "请假天数", "value": f"{item.get('days', 0)} 天" if item.get("days") else "--"}, + {"label": "开始日期", "value": item.get("startDate", "") or "--"}, + {"label": "结束日期", "value": item.get("endDate", "") or "--"}, + ] + if item.get("reason"): + fields.append({"label": "请假原因", "value": item["reason"]}) + if item.get("remark"): + fields.append({"label": "备注", "value": item["remark"]}) + return fields + + +_BUSINESS_FORM_FIELD_PROVIDERS: Dict[str, Any] = { + "leave": _leave_form_fields, +} + + +def _get_business_form_fields(db: Session, business_type: Optional[str], business_id: Optional[str]) -> List[Dict[str, str]]: + if not business_type or not business_id: + return [] + provider = _BUSINESS_FORM_FIELD_PROVIDERS.get(business_type) + if not provider: + return [] + try: + return provider(db, business_id) + except Exception as e: + _form_field_logger.warning("加载业务表单字段失败: type=%s id=%s err=%s", business_type, business_id, e) + return [] + + +def _enrich_task_list(db: Session, items: List[Dict[str, Any]]) -> None: + starter_ids = {it.get("starterUserId") for it in items if it.get("starterUserId")} + user_name_map: Dict[str, str] = {} + if starter_ids: + rows = db.query(SysUser.id, SysUser.nick_name).filter(SysUser.id.in_(starter_ids)).all() + user_name_map = {r.id: r.nick_name for r in rows} + for it in items: + sid = it.get("starterUserId") + it["starterUserName"] = user_name_map.get(sid, sid or "") + it["businessTypeLabel"] = _business_type_label(it.get("businessType")) + + +# --------------------------------------------------------------------------- +# Pydantic request bodies +# --------------------------------------------------------------------------- + + +class ProcessDefCreateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + processCode: str = Field(..., alias="processCode") + processName: str = Field(..., alias="processName") + status: Optional[str] = Field("1", alias="status") + orderNum: Optional[int] = Field(0, alias="orderNum") + remark: Optional[str] = None + + +class ProcessDefUpdateBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + processCode: Optional[str] = Field(None, alias="processCode") + processName: Optional[str] = Field(None, alias="processName") + status: Optional[str] = Field(None, alias="status") + orderNum: Optional[int] = Field(None, alias="orderNum") + remark: Optional[str] = None + + +class PublishVersionBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + graphJson: Optional[str] = Field(None, alias="graphJson") + nodes: List[Dict[str, Any]] = Field(default_factory=list, alias="nodes") + transitions: List[Dict[str, Any]] = Field(default_factory=list, alias="transitions") + remark: Optional[str] = None + + +class StartInstanceBody(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + processCode: str = Field(..., alias="processCode") + businessType: Optional[str] = Field(None, alias="businessType") + businessId: Optional[str] = Field(None, alias="businessId") + variables: Optional[Dict[str, Any]] = None + callbackUrl: Optional[str] = Field(None, alias="callbackUrl") + + +class TaskActionBody(BaseModel): + """审批意见:前端传 commentText,兼容 comment。""" + + model_config = ConfigDict(populate_by_name=True) + + comment: Optional[str] = Field( + default=None, + validation_alias=AliasChoices("comment", "commentText"), + ) + + +# --------------------------------------------------------------------------- +# 流程定义 CRUD +# --------------------------------------------------------------------------- + + +@router.get("/defs") +async def list_process_defs( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + status: Optional[str] = None, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _user.get("tenant_no", "zkxw") + items, total = wf_crud.list_process_defs( + db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no + ) + tp = wf_crud.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.post("/defs") +async def create_process_def( + body: ProcessDefCreateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = user.get("tenant_no", "zkxw") + obj = wf_crud.create_process_def( + db, + process_code=body.processCode, + process_name=body.processName, + tenant_no=tenant_no, + status=body.status, + order_num=body.orderNum, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response(wf_crud._row_to_dict(obj), message="创建成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.put("/defs/{def_id}") +async def update_process_def( + def_id: str, + body: ProcessDefUpdateBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + payload: Dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=True) + obj = wf_crud.update_process_def( + db, + def_id, + process_code=payload.get("processCode"), + process_name=payload.get("processName"), + status=payload.get("status"), + order_num=payload.get("orderNum"), + remark=payload.get("remark"), + user_id=user.get("user_id"), + tenant_no=user.get("tenant_no", "zkxw"), + ) + return success_response(wf_crud._row_to_dict(obj), message="更新成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.delete("/defs/{def_id}") +async def delete_process_def( + def_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + wf_crud.soft_delete_process_def(db, def_id) + return success_response(None, message="删除成功") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 版本发布 / 查看 +# --------------------------------------------------------------------------- + + +@router.post("/defs/{def_id}/publish") +async def publish_version( + def_id: str, + body: PublishVersionBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = user.get("tenant_no", "zkxw") + ver = wf_crud.publish_version( + db, + process_def_id=def_id, + graph_json=body.graphJson or "", + nodes=body.nodes, + transitions=body.transitions, + tenant_no=tenant_no, + remark=body.remark, + user_id=user.get("user_id"), + ) + return success_response({"versionId": ver.id, "versionNo": ver.version_no}, message="发布成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.get("/defs/{def_id}/versions") +async def list_versions( + def_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tenant_no = _user.get("tenant_no", "zkxw") + items = wf_crud.list_versions(db, def_id, tenant_no=tenant_no) + return success_response(items) + + +@router.get("/defs/{def_id}/designer") +async def get_designer_data( + def_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + ver = wf_crud.get_latest_version(db, def_id) + if not ver: + return success_response({"graphJson": None, "nodes": [], "transitions": []}) + nodes = wf_crud.get_nodes_by_version(db, ver.id) + transitions = wf_crud.get_transitions_by_version(db, ver.id) + return success_response({ + "graphJson": ver.graph_json, + "versionId": ver.id, + "versionNo": ver.version_no, + "nodes": [ + {"nodeKey": n.node_key, "nodeType": n.node_type, "nodeName": n.node_name, "configJson": n.config_json} + for n in nodes + ], + "transitions": [ + {"fromNodeKey": t.from_node_key, "toNodeKey": t.to_node_key, "conditionExpr": t.condition_expr} + for t in transitions + ], + }) + + +# --------------------------------------------------------------------------- +# 流程实例 +# --------------------------------------------------------------------------- + + +@router.post("/instances") +async def start_instance( + body: StartInstanceBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = user.get("tenant_no", "zkxw") + user_id = user.get("user_id") or user.get("id") + defn = ( + db.query(WfProcessDef) + .filter( + WfProcessDef.process_code == body.processCode, + WfProcessDef.del_flag == 0, + ) + .first() + ) + if not defn: + return error_response(404, "流程定义不存在") + engine = WorkflowEngine(db) + instance = engine.start_process( + process_def_id=defn.id, + business_type=body.businessType, + business_id=body.businessId, + starter_user_id=user_id, + variables=body.variables, + callback_url=body.callbackUrl, + tenant_no=tenant_no, + user_id=user_id, + ) + return success_response({ + "instanceId": instance.id, + "status": instance.status, + }, message="发起成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.get("/instances/my") +async def list_my_instances( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + status: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + user_id = user.get("user_id") or user.get("id") + tenant_no = user.get("tenant_no", "zkxw") + items, total = wf_crud.list_my_instances( + db, user_id, page=page, page_size=pageSize, + keyword=keyword, status=status, tenant_no=tenant_no, + ) + _enrich_task_list(db, items) + tp = wf_crud.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.get("/instances/{instance_id}") +async def get_instance_detail( + instance_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + instance = wf_crud.get_instance(db, instance_id) + if not instance: + return error_response(404, "流程实例不存在") + tasks = db.query(WfTask).filter( + WfTask.instance_id == instance_id, + WfTask.del_flag == 0, + ).order_by(WfTask.create_time.asc()).all() + + # 查询节点名称映射 node_key → node_name + node_name_map: Dict[str, str] = {} + if instance.process_version_id: + for n in wf_crud.get_nodes_by_version(db, instance.process_version_id): + node_name_map[n.node_key] = n.node_name or n.node_key + + # 批量查询用户名 + user_ids = {t.assignee_user_id for t in tasks if t.assignee_user_id} + if instance.starter_user_id: + user_ids.add(instance.starter_user_id) + user_name_map: Dict[str, str] = {} + if user_ids: + rows = db.query(SysUser.id, SysUser.nick_name).filter(SysUser.id.in_(user_ids)).all() + user_name_map = {r.id: r.nick_name for r in rows} + + return success_response({ + "id": instance.id, + "businessType": instance.business_type, + "businessTypeLabel": _business_type_label(instance.business_type), + "businessId": instance.business_id, + "businessFormFields": _get_business_form_fields(db, instance.business_type, instance.business_id), + "starterUserId": instance.starter_user_id, + "starterUserName": user_name_map.get(instance.starter_user_id, instance.starter_user_id or ""), + "status": instance.status, + "variablesJson": instance.variables_json, + "callbackUrl": instance.callback_url, + "createTime": instance.create_time.strftime("%Y-%m-%d %H:%M:%S") if instance.create_time else "", + "tasks": [ + { + "id": t.id, + "nodeKey": t.node_key, + "nodeName": node_name_map.get(t.node_key, t.node_key or ""), + "assigneeUserId": t.assignee_user_id, + "assigneeUserName": user_name_map.get(t.assignee_user_id, t.assignee_user_id or ""), + "status": t.status, + "commentText": t.comment_text, + "createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "", + "updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "", + } + for t in tasks + ], + }) + + +# --------------------------------------------------------------------------- +# 待办 / 已办 +# --------------------------------------------------------------------------- + + +@router.get("/tasks/pending") +async def list_pending_tasks( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + user_id = user.get("user_id") or user.get("id") + tenant_no = user.get("tenant_no", "zkxw") + items, total = wf_crud.list_pending_tasks( + db, user_id, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no + ) + _enrich_task_list(db, items) + tp = wf_crud.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +@router.get("/tasks/completed") +async def list_completed_tasks( + page: int = Query(1, ge=1), + pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), + keyword: Optional[str] = None, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + user_id = user.get("user_id") or user.get("id") + tenant_no = user.get("tenant_no", "zkxw") + items, total = wf_crud.list_completed_tasks( + db, user_id, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no + ) + _enrich_task_list(db, items) + tp = wf_crud.total_pages(total, pageSize) + return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}) + + +# --------------------------------------------------------------------------- +# 审批 / 驳回 +# --------------------------------------------------------------------------- + + +@router.post("/tasks/{task_id}/approve") +async def approve_task( + task_id: str, + body: TaskActionBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = user.get("tenant_no", "zkxw") + engine = WorkflowEngine(db) + instance = engine.approve_task(task_id, body.comment or "", tenant_no) + return success_response({"instanceId": instance.id, "status": instance.status}, message="审批成功") + except ValueError as e: + return error_response(400, str(e)) + + +@router.post("/tasks/{task_id}/reject") +async def reject_task( + task_id: str, + body: TaskActionBody, + user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + try: + tenant_no = user.get("tenant_no", "zkxw") + engine = WorkflowEngine(db) + instance = engine.reject_task(task_id, body.comment or "", tenant_no) + return success_response({"instanceId": instance.id, "status": instance.status}, message="已驳回") + except ValueError as e: + return error_response(400, str(e)) + + +# --------------------------------------------------------------------------- +# 按业务查询所有审批历史(跨实例) +# --------------------------------------------------------------------------- + + +@router.get("/instances/by-business/{business_type}/{business_id}") +async def get_business_all_tasks( + business_type: str, + business_id: str, + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + tasks = wf_crud.get_all_tasks_for_business(db, business_type, business_id) + + business_form_fields = _get_business_form_fields(db, business_type, business_id) + + return success_response({ + "businessType": business_type, + "businessId": business_id, + "businessFormFields": business_form_fields, + "tasks": tasks, + }) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index d92c06d4..2485f95f 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -100,7 +100,7 @@ class Settings(BaseSettings): WX_TEMPLATE_REFUND_RESULT: str = "" # 退款结果通知模板ID WX_TEMPLATE_DELIVERY_WARNING: str = "" # 配送时效预警模板ID - # 与 docs/sql/zkxw-requirements-v0.3-schema.sql 一致:登录按租户 + sys_user.user_no + # 与 docs/sql/zkxw.sql 基线一致:登录按租户 + sys_user.user_no DEFAULT_TENANT_NO: str = "zkxw" # 用户未绑定 sys_user_role 时,Web 管理端默认角色(建议 super_admin 便于开发全菜单) WEB_AUTH_DEFAULT_ROLE: str = "super_admin" diff --git a/backend/app/crud/cell_sample_type.py b/backend/app/crud/cell_sample_type.py new file mode 100644 index 00000000..b2a26e8c --- /dev/null +++ b/backend/app/crud/cell_sample_type.py @@ -0,0 +1,174 @@ +"""细胞样本类型 CRUD(biz_cell_sample_type)""" +from __future__ import annotations + +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizCellSampleType + + +def _row_to_dict(r: BizCellSampleType) -> Dict[str, Any]: + sp = r.storage_price + return { + "id": r.id, + "typeCode": r.type_code, + "typeName": r.type_name, + "storageYears": r.storage_years, + "storagePrice": float(sp) if sp is not None else None, + "tenantNo": r.tenant_no, + "orderNum": r.order_num if r.order_num is not None else 0, + "remark": r.remark or "", + "createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "", + "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "", + } + + +def type_code_exists(db: Session, code: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool: + c = (code or "").strip() + if not c: + return False + q = db.query(BizCellSampleType).filter( + and_(BizCellSampleType.type_code == c, BizCellSampleType.tenant_no == tenant_no, BizCellSampleType.del_flag == 0) + ) + if exclude_id: + q = q.filter(BizCellSampleType.id != exclude_id) + return q.first() is not None + + +def list_cell_sample_types( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizCellSampleType).filter(BizCellSampleType.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizCellSampleType.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(or_(BizCellSampleType.type_code.like(like), BizCellSampleType.type_name.like(like))) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCellSampleType.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCellSampleType.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_row_to_dict(x) for x in rows], int(total) + + +def get_cell_sample_type(db: Session, row_id: str, tenant_no: Optional[str] = None) -> Optional[BizCellSampleType]: + q = db.query(BizCellSampleType).filter(and_(BizCellSampleType.id == row_id, BizCellSampleType.del_flag == 0)) + if tenant_no: + q = q.filter(BizCellSampleType.tenant_no == tenant_no) + return q.first() + + +def create_cell_sample_type( + db: Session, + *, + type_code: str, + type_name: str, + storage_years: Optional[int] = None, + storage_price: Optional[float] = None, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizCellSampleType: + tc = (type_code or "").strip() + if not tc: + raise ValueError("样本类型编码不能为空") + if type_code_exists(db, tc, tenant_no): + raise ValueError("样本类型编码已存在") + obj = BizCellSampleType( + id=generate_uuid(), + type_code=tc, + type_name=(type_name or "").strip() or tc, + storage_years=storage_years, + storage_price=storage_price, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_cell_sample_type( + db: Session, + row_id: str, + *, + type_code: Optional[str] = None, + type_name: Optional[str] = None, + storage_years: Optional[int] = None, + storage_price: Optional[float] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizCellSampleType: + obj = get_cell_sample_type(db, row_id, tenant_no=tenant_no) + if not obj: + raise ValueError("样本类型不存在") + if type_code is not None: + tc = type_code.strip() + if not tc: + raise ValueError("样本类型编码不能为空") + if tc != obj.type_code and type_code_exists(db, tc, obj.tenant_no, exclude_id=row_id): + raise ValueError("样本类型编码已存在") + obj.type_code = tc + if type_name is not None: + obj.type_name = (type_name or "").strip() or obj.type_name + if storage_years is not None: + obj.storage_years = storage_years + if storage_price is not None: + obj.storage_price = storage_price + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = remark.strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_cell_sample_type(db: Session, row_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_cell_sample_type(db, row_id, tenant_no=tenant_no) + if not obj: + raise ValueError("样本类型不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/crud/clean_room.py b/backend/app/crud/clean_room.py new file mode 100644 index 00000000..aee83df9 --- /dev/null +++ b/backend/app/crud/clean_room.py @@ -0,0 +1,344 @@ +"""洁净室及进出记录 CRUD(biz_clean_room + biz_clean_room_access_log)""" +from __future__ import annotations + +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizCleanRoom, BizCleanRoomAccessLog + + +# --------------------------------------------------------------------------- +# 洁净室 +# --------------------------------------------------------------------------- + +def _row_to_dict(c: BizCleanRoom) -> Dict[str, Any]: + return { + "id": c.id, + "roomCode": c.room_code, + "roomName": c.room_name, + "roomGrade": c.room_grade, + "roomStatus": c.room_status, + "areaSqm": float(c.area_sqm) if c.area_sqm is not None else 0.0, + "tenantNo": c.tenant_no, + "orderNum": c.order_num if c.order_num is not None else 0, + "remark": c.remark or "", + "createTime": c.create_time.strftime("%Y-%m-%d %H:%M:%S") if c.create_time else "", + "updateTime": c.update_time.strftime("%Y-%m-%d %H:%M:%S") if c.update_time else "", + } + + +def list_clean_rooms( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizCleanRoom).filter(BizCleanRoom.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizCleanRoom.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(or_(BizCleanRoom.room_code.like(like), BizCleanRoom.room_name.like(like))) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCleanRoom.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCleanRoom.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_row_to_dict(x) for x in rows], int(total) + + +def get_clean_room(db: Session, room_id: str, tenant_no: Optional[str] = None) -> Optional[BizCleanRoom]: + q = db.query(BizCleanRoom).filter(and_(BizCleanRoom.id == room_id, BizCleanRoom.del_flag == 0)) + if tenant_no: + q = q.filter(BizCleanRoom.tenant_no == tenant_no) + return q.first() + + +def room_code_exists(db: Session, code: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool: + q = db.query(BizCleanRoom).filter( + and_(BizCleanRoom.room_code == code.strip(), BizCleanRoom.tenant_no == tenant_no, BizCleanRoom.del_flag == 0) + ) + if exclude_id: + q = q.filter(BizCleanRoom.id != exclude_id) + return q.first() is not None + + +def create_clean_room( + db: Session, + *, + room_code: str, + room_name: str, + room_grade: Optional[str] = None, + room_status: Optional[str] = None, + area_sqm: Optional[Any] = None, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizCleanRoom: + rc = (room_code or "").strip() + if not rc: + raise ValueError("洁净室编码不能为空") + if room_code_exists(db, rc, tenant_no): + raise ValueError("洁净室编码已存在") + obj = BizCleanRoom( + id=generate_uuid(), + room_code=rc, + room_name=(room_name or "").strip() or rc, + room_grade=(room_grade or "").strip() or None, + room_status=(room_status or "").strip() or None, + area_sqm=area_sqm if area_sqm is not None else 0, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_clean_room( + db: Session, + room_id: str, + *, + room_code: Optional[str] = None, + room_name: Optional[str] = None, + room_grade: Optional[str] = None, + room_status: Optional[str] = None, + area_sqm: Optional[Any] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizCleanRoom: + obj = get_clean_room(db, room_id, tenant_no=tenant_no) + if not obj: + raise ValueError("洁净室不存在") + if room_code is not None: + new_code = room_code.strip() + if not new_code: + raise ValueError("洁净室编码不能为空") + if new_code != obj.room_code and room_code_exists(db, new_code, obj.tenant_no, exclude_id=room_id): + raise ValueError("洁净室编码已存在") + obj.room_code = new_code + if room_name is not None: + obj.room_name = (room_name or "").strip() or obj.room_name + if room_grade is not None: + obj.room_grade = (room_grade or "").strip() or None + if room_status is not None: + obj.room_status = (room_status or "").strip() or None + if area_sqm is not None: + obj.area_sqm = area_sqm + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = (remark or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_clean_room(db: Session, room_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_clean_room(db, room_id, tenant_no=tenant_no) + if not obj: + raise ValueError("洁净室不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +# --------------------------------------------------------------------------- +# 进出记录 +# --------------------------------------------------------------------------- + +def _access_log_to_dict(log: BizCleanRoomAccessLog) -> Dict[str, Any]: + return { + "id": log.id, + "cleanRoomId": log.clean_room_id, + "userId": log.user_id, + "personName": log.person_name, + "deptName": log.dept_name, + "enterTime": log.enter_time.strftime("%Y-%m-%d %H:%M:%S") if log.enter_time else "", + "purpose": log.purpose or "", + "leaveTime": log.leave_time.strftime("%Y-%m-%d %H:%M:%S") if log.leave_time else "", + "bodyTemp": log.body_temp or "", + "tenantNo": log.tenant_no, + "orderNum": log.order_num if log.order_num is not None else 0, + "remark": log.remark or "", + "createTime": log.create_time.strftime("%Y-%m-%d %H:%M:%S") if log.create_time else "", + "updateTime": log.update_time.strftime("%Y-%m-%d %H:%M:%S") if log.update_time else "", + } + + +def list_access_logs( + db: Session, + *, + page: int = 1, + page_size: int = 10, + clean_room_id: Optional[str] = None, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizCleanRoomAccessLog).filter(BizCleanRoomAccessLog.del_flag == 0) + if clean_room_id: + q0 = q0.filter(BizCleanRoomAccessLog.clean_room_id == clean_room_id) + if tenant_no: + q0 = q0.filter(BizCleanRoomAccessLog.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(or_( + BizCleanRoomAccessLog.person_name.like(like), + BizCleanRoomAccessLog.dept_name.like(like), + )) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCleanRoomAccessLog.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCleanRoomAccessLog.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_access_log_to_dict(x) for x in rows], int(total) + + +def get_access_log(db: Session, log_id: str, tenant_no: Optional[str] = None) -> Optional[BizCleanRoomAccessLog]: + q = db.query(BizCleanRoomAccessLog).filter(and_(BizCleanRoomAccessLog.id == log_id, BizCleanRoomAccessLog.del_flag == 0)) + if tenant_no: + q = q.filter(BizCleanRoomAccessLog.tenant_no == tenant_no) + return q.first() + + +def create_access_log( + db: Session, + *, + clean_room_id: str, + user_id: Optional[str] = None, + person_name: Optional[str] = None, + dept_name: Optional[str] = None, + enter_time: Optional[datetime] = None, + purpose: Optional[str] = None, + leave_time: Optional[datetime] = None, + body_temp: Optional[str] = None, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id_param: Optional[str] = None, +) -> BizCleanRoomAccessLog: + cr_id = (clean_room_id or "").strip() + if not cr_id: + raise ValueError("洁净室ID不能为空") + obj = BizCleanRoomAccessLog( + id=generate_uuid(), + clean_room_id=cr_id, + user_id=(user_id or "").strip() or None, + person_name=(person_name or "").strip() or None, + dept_name=(dept_name or "").strip() or None, + enter_time=enter_time or datetime.now(), + purpose=(purpose or "").strip() or None, + leave_time=leave_time, + body_temp=(body_temp or "").strip() or None, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id_param, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id_param, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_access_log( + db: Session, + log_id: str, + *, + person_name: Optional[str] = None, + dept_name: Optional[str] = None, + enter_time: Optional[datetime] = None, + purpose: Optional[str] = None, + leave_time: Optional[datetime] = None, + body_temp: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id_param: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizCleanRoomAccessLog: + obj = get_access_log(db, log_id, tenant_no=tenant_no) + if not obj: + raise ValueError("进出记录不存在") + if person_name is not None: + obj.person_name = (person_name or "").strip() or None + if dept_name is not None: + obj.dept_name = (dept_name or "").strip() or None + if enter_time is not None: + obj.enter_time = enter_time + if purpose is not None: + obj.purpose = (purpose or "").strip() or None + if leave_time is not None: + obj.leave_time = leave_time + if body_temp is not None: + obj.body_temp = (body_temp or "").strip() or None + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = (remark or "").strip() or None + obj.update_by = user_id_param + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_access_log(db: Session, log_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_access_log(db, log_id, tenant_no=tenant_no) + if not obj: + raise ValueError("进出记录不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/crud/collection_protocol.py b/backend/app/crud/collection_protocol.py new file mode 100644 index 00000000..aa5e7a4c --- /dev/null +++ b/backend/app/crud/collection_protocol.py @@ -0,0 +1,1580 @@ +"""采集协议单 / 审核 / 缴费明细 CRUD(细胞存储 2.2.6~2.2.10)""" +from __future__ import annotations + +import math +from datetime import datetime, timedelta +from decimal import Decimal +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid, parse_datetime +from app.crud.base import camel_to_snake +from app.models.models import ( + BizAttachment, + BizCollectionProtocol, + BizProtocolAudit, + BizProtocolCollectionInfo, + BizProtocolPartyCustomer, + BizProtocolPayment, + BizProtocolPaymentDetail, + BizProtocolSample, + BizProtocolSampleTransport, + BizProtocolStorageCert, + BizCollectionRegion, + BizCollectionHospital, + SysUser, +) + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) + + +def _fmt_dt(t: Optional[datetime]) -> str: + return t.strftime("%Y-%m-%d %H:%M:%S") if t else "" + + +def _dec(v: Any) -> Optional[float]: + if v is None: + return None + if isinstance(v, Decimal): + return float(v) + return float(v) + + +def _optional_decimal(v: Any) -> Optional[Decimal]: + if v is None or v == "": + return None + return Decimal(str(v)) + + +def _today_yyyymmdd() -> str: + return datetime.now().strftime("%Y%m%d") + + +def _normalize_yyyymmdd_date(val: Any) -> Optional[str]: + """将 YYYYMMDD / YYYY-MM-DD 等格式规范为 8 位日期字符串。""" + if val is None: + return None + s = str(val).strip() + if not s: + return None + compact = s.replace("-", "").replace("/", "")[:8] + if len(compact) == 8 and compact.isdigit(): + return compact + return None + + +_SNAKE_ALIASES = { + "apgar1min": "apgar_1min", + "apgar5min": "apgar_5min", +} + + +def _normalize_fields(fields: Optional[dict]) -> dict: + """将前端 camelCase 字段名转为数据库 snake_case。""" + if not fields: + return {} + out: Dict[str, Any] = {} + for k, v in fields.items(): + sk = _SNAKE_ALIASES.get(k) or camel_to_snake(k) + out[sk] = v + return out + + +def attachments_to_urls(attachments: Any) -> Optional[str]: + """附件列表或字符串 → 逗号分隔 URL(MinIO)。""" + if attachments is None: + return None + if isinstance(attachments, str): + return attachments.strip() or None + if isinstance(attachments, list): + urls: List[str] = [] + for item in attachments: + if isinstance(item, dict): + u = (item.get("fileUrl") or item.get("file_url") or "").strip() + if u: + urls.append(u) + elif isinstance(item, str) and item.strip(): + urls.append(item.strip()) + return ",".join(urls) if urls else None + return None + + +def _normalize_attachment_url_key(url: str) -> str: + u = (url or "").strip() + if not u: + return "" + if "?" in u: + u = u.split("?", 1)[0] + return u.lstrip("/") + + +def _format_attachment_file_size(size_bytes: int) -> str: + if size_bytes < 1024: + return f"{size_bytes} B" + if size_bytes < 1024 * 1024: + return f"{size_bytes / 1024:.1f} KB" + return f"{size_bytes / (1024 * 1024):.1f} MB" + + +def _lookup_attachment_by_url( + db: Session, + url: str, + tenant_no: Optional[str], +) -> Optional[BizAttachment]: + key = _normalize_attachment_url_key(url) + if not key: + return None + q = db.query(BizAttachment).filter(BizAttachment.del_flag == 0) + if tenant_no: + q = q.filter(BizAttachment.tenant_no == tenant_no) + row = q.filter(BizAttachment.file_url == key).first() + if row: + return row + tail = key.rsplit("/", 1)[-1] + if tail: + row = ( + q.filter(BizAttachment.file_url.like(f"%/{tail}")) + .order_by(BizAttachment.create_time.desc()) + .first() + ) + return row + + +def urls_to_attachment_list( + urls_str: Optional[str], + db: Optional[Session] = None, + tenant_no: Optional[str] = None, +) -> List[Dict[str, Any]]: + """逗号分隔 URL → 前端附件卡片结构;有 db 时从 biz_attachment 取原始文件名。""" + if not urls_str: + return [] + result: List[Dict[str, Any]] = [] + for url in urls_str.split(","): + u = url.strip() + if not u: + continue + att = _lookup_attachment_by_url(db, u, tenant_no) if db else None + if att: + size = att.file_size or 0 + result.append( + { + "id": att.id, + "fileUrl": u, + "fileName": att.original_name or u.rsplit("/", 1)[-1], + "fileSize": size, + "fileSizeDisplay": _format_attachment_file_size(size), + "fileType": att.file_type or "", + } + ) + else: + name = u.rsplit("/", 1)[-1] if "/" in u else u + result.append({"fileUrl": u, "fileName": name, "fileSize": 0}) + return result + + +def _protocol_to_dict(p: BizCollectionProtocol) -> Dict[str, Any]: + return { + "id": p.id, + "protocolNoId": p.protocol_no_id or "", + "protocolNo": p.protocol_no or "", + "protocolStatus": p.protocol_status or "", + "protocolAttachments": p.protocol_attachments or "", + "registerDate": (p.register_date or "").strip() if p.register_date else "", + "effectiveTime": _fmt_dt(p.effective_time), + "effectiveDesc": p.effective_desc or "", + "expireTime": _fmt_dt(p.expire_time), + "expireDesc": p.expire_desc or "", + "voidTime": _fmt_dt(p.void_time), + "voidDesc": p.void_desc or "", + "toResearchTime": _fmt_dt(p.to_research_time), + "toResearchDesc": p.to_research_desc or "", + "collectorUserId": p.collector_user_id or "", + "tenantNo": p.tenant_no, + "orderNum": p.order_num if p.order_num is not None else 0, + "remark": p.remark or "", + "createTime": _fmt_dt(p.create_time), + "updateTime": _fmt_dt(p.update_time), + } + + +def _payment_to_dict(p: BizProtocolPayment) -> Dict[str, Any]: + return { + "id": p.id, + "collectionProtocolId": p.collection_protocol_id, + "payMode": p.pay_mode or "", + "contractAmount": _dec(p.contract_amount), + "installmentCount": p.installment_count, + "installmentIntervalMonths": p.installment_interval_months, + "lumpSumDeadline": (p.lump_sum_deadline or "").strip() if p.lump_sum_deadline else "", + "installmentStartDate": (p.installment_start_date or "").strip() if p.installment_start_date else "", + "tenantNo": p.tenant_no, + "orderNum": p.order_num if p.order_num is not None else 0, + "remark": p.remark or "", + "createTime": _fmt_dt(p.create_time), + "updateTime": _fmt_dt(p.update_time), + } + + +def _detail_to_dict( + d: BizProtocolPaymentDetail, + *, + protocol_no: str = "", + collection_protocol_id: str = "", +) -> Dict[str, Any]: + return { + "id": d.id, + "paymentId": d.payment_id, + "collectionProtocolId": collection_protocol_id, + "protocolNo": protocol_no, + "transferType": d.transfer_type or "", + "dueAmount": _dec(d.due_amount), + "transferAmount": _dec(d.transfer_amount), + "transferTime": _fmt_dt(d.transfer_time), + "transferAttachments": d.transfer_attachments or "", + "financeConfirmAttachments": d.finance_confirm_attachments or "", + "payStatus": d.pay_status or "", + "installmentNo": d.installment_no, + "installmentDueDate": (d.installment_due_date or "").strip() if d.installment_due_date else "", + "tenantNo": d.tenant_no, + "orderNum": d.order_num if d.order_num is not None else 0, + "remark": d.remark or "", + "createTime": _fmt_dt(d.create_time), + "updateTime": _fmt_dt(d.update_time), + } + + +def _audit_to_dict( + a: BizProtocolAudit, + *, + protocol_no: str = "", +) -> Dict[str, Any]: + return { + "id": a.id, + "collectionProtocolId": a.collection_protocol_id, + "protocolNo": protocol_no, + "auditorUserId": a.auditor_user_id or "", + "auditRemark": a.audit_remark or "", + "auditStatus": a.audit_status or "", + "tenantNo": a.tenant_no, + "orderNum": a.order_num if a.order_num is not None else 0, + "remark": a.remark or "", + "createTime": _fmt_dt(a.create_time), + "updateTime": _fmt_dt(a.update_time), + } + + +def _party_customer_to_dict(p: BizProtocolPartyCustomer) -> Dict[str, Any]: + return { + "id": p.id, + "collectionProtocolId": p.collection_protocol_id, + "customerId": p.customer_id or "", + "motherName": p.mother_name or "", + "motherPhone": p.mother_phone or "", + "motherIdCard": p.mother_id_card or "", + "motherBirthYm": (p.mother_birth_ym or "").strip() if p.mother_birth_ym else "", + "motherEthnic": p.mother_ethnic or "", + "motherAddress": p.mother_address or "", + "fatherName": p.father_name or "", + "fatherPhone": p.father_phone or "", + "fatherIdCard": p.father_id_card or "", + "newbornName": p.newborn_name or "", + "newbornIdCard": p.newborn_id_card or "", + "newbornDeliveryTime": _fmt_dt(p.newborn_delivery_time), + "newbornSex": p.newborn_sex or "", + "deliveryMode": p.delivery_mode or "", + "birthOrder": p.birth_order, + "newbornWeightGram": p.newborn_weight_gram, + "apgar1min": _dec(p.apgar_1min), + "apgar5min": _dec(p.apgar_5min), + "collectorUserId": p.collector_user_id or "", + "tenantNo": p.tenant_no, + "orderNum": p.order_num if p.order_num is not None else 0, + "remark": p.remark or "", + "createTime": _fmt_dt(p.create_time), + "updateTime": _fmt_dt(p.update_time), + } + + +def _collection_info_to_dict(c: BizProtocolCollectionInfo) -> Dict[str, Any]: + return { + "id": c.id, + "collectionProtocolId": c.collection_protocol_id, + "regionId": c.region_id or "", + "hospitalId": c.hospital_id or "", + "medicalStaff": c.medical_staff or "", + "salesUserId": c.sales_user_id or "", + "sampleCollectTime": _fmt_dt(c.sample_collect_time), + "maternalBloodCollectTime": _fmt_dt(c.maternal_blood_collect_time), + "tenantNo": c.tenant_no, + "orderNum": c.order_num if c.order_num is not None else 0, + "remark": c.remark or "", + "createTime": _fmt_dt(c.create_time), + "updateTime": _fmt_dt(c.update_time), + } + + +def _sample_to_dict(s: BizProtocolSample) -> Dict[str, Any]: + return { + "id": s.id, + "collectionProtocolId": s.collection_protocol_id, + "sampleTypeId": s.sample_type_id or "", + "sampleCodeId": s.sample_code_id or "", + "sampleCode": s.sample_code or "", + "collectAmount": _dec(s.collect_amount), + "unitId": s.unit_id or "", + "price": _dec(s.price), + "storageYears": s.storage_years, + "sampleStatus": (s.sample_status or "").strip() if (s.sample_status or "").strip() else "", + "storageInDate": (s.storage_in_date or "").strip() if s.storage_in_date else "", + "detectStartDate": (s.detect_start_date or "").strip() if s.detect_start_date else "", + "voidDate": (s.void_date or "").strip() if s.void_date else "", + "storageExpireDate": (s.storage_expire_date or "").strip() if s.storage_expire_date else "", + "tenantNo": s.tenant_no, + "orderNum": s.order_num if s.order_num is not None else 0, + "remark": s.remark or "", + "createTime": _fmt_dt(s.create_time), + "updateTime": _fmt_dt(s.update_time), + } + + +def _transport_to_dict(t: BizProtocolSampleTransport) -> Dict[str, Any]: + return { + "id": t.id, + "collectionProtocolId": t.collection_protocol_id, + "sampleId": t.sample_id, + "transportType": t.transport_type or "", + "expressCompanyId": t.express_company_id or "", + "waybillNo": t.waybill_no or "", + "destSiteId": t.dest_site_id or "", + "expressFee": _dec(t.express_fee), + "etaTime": _fmt_dt(t.eta_time), + "carrierName": t.carrier_name or "", + "carrierPhone": t.carrier_phone or "", + "remark": t.remark or "", + "orderNum": t.order_num if t.order_num is not None else 0, + "createTime": _fmt_dt(t.create_time), + "updateTime": _fmt_dt(t.update_time), + } + + +def _storage_cert_to_dict(s: BizProtocolStorageCert) -> Dict[str, Any]: + return { + "id": s.id, + "collectionProtocolId": s.collection_protocol_id, + "makeStatus": s.make_status or "0", + "certImageUrls": s.cert_image_urls or "", + "makeDate": (s.make_date or "").strip() if s.make_date else "", + "tenantNo": s.tenant_no, + "orderNum": s.order_num if s.order_num is not None else 0, + "remark": s.remark or "", + "createTime": _fmt_dt(s.create_time), + "updateTime": _fmt_dt(s.update_time), + } + + +def list_collection_protocols( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + protocol_status: Optional[str] = None, + tenant_no: Optional[str] = None, + scope_collector_user_id: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizCollectionProtocol).filter(BizCollectionProtocol.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizCollectionProtocol.tenant_no == tenant_no) + if scope_collector_user_id: + q0 = q0.filter( + BizCollectionProtocol.collector_user_id == scope_collector_user_id + ) + if protocol_status: + q0 = q0.filter(BizCollectionProtocol.protocol_status == protocol_status.strip()) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter( + or_( + BizCollectionProtocol.protocol_no.like(like), + BizCollectionProtocol.remark.like(like), + ) + ) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCollectionProtocol.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCollectionProtocol.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + + # 批量加载子表数据,避免 N+1 查询 + protocol_ids = [r.id for r in rows] + pc_map: Dict[str, Dict[str, Any]] = {} + ci_map: Dict[str, Dict[str, Any]] = {} + if protocol_ids: + pc_rows = ( + db.query(BizProtocolPartyCustomer) + .filter( + BizProtocolPartyCustomer.collection_protocol_id.in_(protocol_ids), + BizProtocolPartyCustomer.del_flag == 0, + ) + .all() + ) + for pc in pc_rows: + pc_map[pc.collection_protocol_id] = _party_customer_to_dict(pc) + + ci_rows = ( + db.query(BizProtocolCollectionInfo) + .filter( + BizProtocolCollectionInfo.collection_protocol_id.in_(protocol_ids), + BizProtocolCollectionInfo.del_flag == 0, + ) + .all() + ) + for ci in ci_rows: + ci_map[ci.collection_protocol_id] = _collection_info_to_dict(ci) + + # 批量解析地区/医院名称 + region_ids = list({ci_map.get(pid, {}).get("regionId", "") for pid in protocol_ids} - {""}) + hospital_ids = list({ci_map.get(pid, {}).get("hospitalId", "") for pid in protocol_ids} - {""}) + region_name_map: Dict[str, str] = {} + hospital_name_map: Dict[str, str] = {} + if region_ids: + for r in db.query(BizCollectionRegion.id, BizCollectionRegion.region_name).filter(BizCollectionRegion.id.in_(region_ids)).all(): + region_name_map[r.id] = r.region_name or "" + if hospital_ids: + for h in db.query(BizCollectionHospital.id, BizCollectionHospital.hospital_name).filter(BizCollectionHospital.id.in_(hospital_ids)).all(): + hospital_name_map[h.id] = h.hospital_name or "" + + # 批量解析销售人员名称 + sales_user_ids = list({ci_map.get(pid, {}).get("salesUserId", "") for pid in protocol_ids} - {""}) + collector_user_ids = list({_protocol_to_dict(r).get("collectorUserId", "") for r in rows} - {""}) + all_user_ids = list(set(sales_user_ids + collector_user_ids)) + user_name_map: Dict[str, str] = {} + if all_user_ids: + for u in db.query(SysUser.id, SysUser.nick_name).filter(SysUser.id.in_(all_user_ids)).all(): + user_name_map[u.id] = u.nick_name or "" + + # 合并主表 + 子表数据 + result = [] + for r in rows: + d = _protocol_to_dict(r) + pc = pc_map.get(r.id) + ci = ci_map.get(r.id) + d["collectorUserName"] = user_name_map.get(d.get("collectorUserId", ""), "") + if pc: + d["motherName"] = pc.get("motherName", "") + d["motherPhone"] = pc.get("motherPhone", "") + d["motherIdCard"] = pc.get("motherIdCard", "") + d["motherBirthYm"] = pc.get("motherBirthYm", "") + d["motherEthnic"] = pc.get("motherEthnic", "") + d["fatherName"] = pc.get("fatherName", "") + d["fatherPhone"] = pc.get("fatherPhone", "") + d["newbornDeliveryTime"] = pc.get("newbornDeliveryTime", "") + d["newbornSex"] = pc.get("newbornSex", "") + d["deliveryMode"] = pc.get("deliveryMode", "") + d["birthOrder"] = pc.get("birthOrder") + d["newbornWeightGram"] = pc.get("newbornWeightGram") + else: + d["motherName"] = "" + d["motherPhone"] = "" + d["motherIdCard"] = "" + d["motherBirthYm"] = "" + d["motherEthnic"] = "" + d["fatherName"] = "" + d["fatherPhone"] = "" + d["newbornDeliveryTime"] = "" + d["newbornSex"] = "" + d["deliveryMode"] = "" + d["birthOrder"] = None + d["newbornWeightGram"] = None + if ci: + d["regionName"] = region_name_map.get(ci.get("regionId", ""), "") + d["hospitalName"] = hospital_name_map.get(ci.get("hospitalId", ""), "") + d["salesUserName"] = user_name_map.get(ci.get("salesUserId", ""), "") + d["sampleCollectTime"] = ci.get("sampleCollectTime", "") + else: + d["regionName"] = "" + d["hospitalName"] = "" + d["salesUserName"] = "" + d["sampleCollectTime"] = "" + result.append(d) + return result, int(total) + + +def get_collection_protocol( + db: Session, + protocol_id: str, + tenant_no: str, + *, + scope_collector_user_id: Optional[str] = None, +) -> Optional[BizCollectionProtocol]: + filters = [ + BizCollectionProtocol.id == protocol_id, + BizCollectionProtocol.del_flag == 0, + BizCollectionProtocol.tenant_no == tenant_no, + ] + if scope_collector_user_id: + filters.append( + BizCollectionProtocol.collector_user_id == scope_collector_user_id + ) + return db.query(BizCollectionProtocol).filter(and_(*filters)).first() + + +def get_payment_by_protocol_id(db: Session, collection_protocol_id: str, tenant_no: str) -> Optional[BizProtocolPayment]: + return ( + db.query(BizProtocolPayment) + .filter( + and_( + BizProtocolPayment.collection_protocol_id == collection_protocol_id, + BizProtocolPayment.tenant_no == tenant_no, + BizProtocolPayment.del_flag == 0, + ) + ) + .first() + ) + + +def get_protocol_detail_bundle( + db: Session, + protocol_id: str, + tenant_no: str, + *, + scope_collector_user_id: Optional[str] = None, +) -> Optional[Dict[str, Any]]: + p = get_collection_protocol( + db, protocol_id, tenant_no, scope_collector_user_id=scope_collector_user_id + ) + if not p: + return None + pay = get_payment_by_protocol_id(db, protocol_id, tenant_no) + pc = get_party_customer(db, protocol_id, tenant_no) + ci = get_collection_info(db, protocol_id, tenant_no) + s_rows = list_samples(db, protocol_id, tenant_no) + sc = get_storage_cert(db, protocol_id, tenant_no) + out: Dict[str, Any] = {"protocol": _protocol_to_dict(p)} + out["payment"] = _payment_to_dict(pay) if pay else None + out["partyCustomer"] = _party_customer_to_dict(pc) if pc else None + out["collectionInfo"] = _collection_info_to_dict(ci) if ci else None + transport_rows = list_sample_transports(db, protocol_id, tenant_no) + transport_by_sample = {t.sample_id: _transport_to_dict(t) for t in transport_rows} + samples_out: List[Dict[str, Any]] = [] + for s in s_rows: + item = _sample_to_dict(s) + item["transport"] = transport_by_sample.get(s.id) + samples_out.append(item) + out["samples"] = samples_out + out["storageCert"] = _storage_cert_to_dict(sc) if sc else None + out["attachments"] = urls_to_attachment_list(p.protocol_attachments, db, tenant_no) + if pay: + detail_rows = list_payment_details(db, pay.id, tenant_no) + details_out: List[Dict[str, Any]] = [] + for d in detail_rows: + item = _detail_to_dict( + d, + protocol_no=p.protocol_no or "", + collection_protocol_id=protocol_id, + ) + item["attachments"] = urls_to_attachment_list(d.transfer_attachments, db, tenant_no) + details_out.append(item) + out["paymentDetails"] = details_out + else: + out["paymentDetails"] = [] + return out + + +def list_audits( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + audit_status: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + q_base = ( + db.query(BizProtocolAudit, BizCollectionProtocol.protocol_no) + .join( + BizCollectionProtocol, + and_( + BizProtocolAudit.collection_protocol_id == BizCollectionProtocol.id, + BizCollectionProtocol.del_flag == 0, + ), + ) + .filter(BizProtocolAudit.del_flag == 0) + ) + if tenant_no: + q_base = q_base.filter(BizProtocolAudit.tenant_no == tenant_no) + if audit_status: + q_base = q_base.filter(BizProtocolAudit.audit_status == audit_status.strip()) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q_base = q_base.filter( + or_(BizCollectionProtocol.protocol_no.like(like), BizProtocolAudit.audit_remark.like(like)) + ) + + total = q_base.order_by(None).with_entities(func.count(BizProtocolAudit.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + q_base.order_by(BizProtocolAudit.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + out = [_audit_to_dict(a, protocol_no=pn or "") for a, pn in rows] + return out, int(total) + + +def create_audit( + db: Session, + *, + collection_protocol_id: str, + audit_status: str, + audit_remark: Optional[str], + tenant_no: str, + user_id: Optional[str], +) -> BizProtocolAudit: + p = get_collection_protocol(db, collection_protocol_id, tenant_no) + if not p: + raise ValueError("协议单不存在") + st = (audit_status or "").strip() + if st not in ("shtg", "shth"): + raise ValueError("审核状态无效") + obj = BizProtocolAudit( + id=generate_uuid(), + collection_protocol_id=collection_protocol_id, + auditor_user_id=user_id, + audit_remark=(audit_remark or "").strip() or None, + audit_status=st, + tenant_no=tenant_no, + order_num=0, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def _detail_query_with_protocol( + db: Session, + tenant_no: str, + pay_status_filter: str, +): + return ( + db.query(BizProtocolPaymentDetail, BizProtocolPayment, BizCollectionProtocol.protocol_no) + .join(BizProtocolPayment, BizProtocolPaymentDetail.payment_id == BizProtocolPayment.id) + .join(BizCollectionProtocol, BizProtocolPayment.collection_protocol_id == BizCollectionProtocol.id) + .filter( + BizProtocolPaymentDetail.del_flag == 0, + BizProtocolPayment.del_flag == 0, + BizCollectionProtocol.del_flag == 0, + BizProtocolPaymentDetail.tenant_no == tenant_no, + BizProtocolPaymentDetail.pay_status == pay_status_filter, + ) + ) + + +def list_receipt_confirm_details( + db: Session, + *, + page: int = 1, + page_size: int = 10, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + if not tenant_no: + return [], 0 + q_base = _detail_query_with_protocol(db, tenant_no, "jfqrz") + total = q_base.order_by(None).with_entities(func.count(BizProtocolPaymentDetail.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + q_base.order_by(BizProtocolPaymentDetail.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + out = [ + _detail_to_dict( + d, + protocol_no=pn or "", + collection_protocol_id=pay.collection_protocol_id if pay else "", + ) + for d, pay, pn in rows + ] + return out, int(total) + + +def confirm_receipt( + db: Session, + *, + detail_id: str, + tenant_no: str, + user_id: Optional[str], + finance_confirm_attachments: Optional[str] = None, +) -> BizProtocolPaymentDetail: + d = ( + db.query(BizProtocolPaymentDetail) + .filter( + and_( + BizProtocolPaymentDetail.id == detail_id, + BizProtocolPaymentDetail.tenant_no == tenant_no, + BizProtocolPaymentDetail.del_flag == 0, + ) + ) + .first() + ) + if not d: + raise ValueError("缴费明细不存在") + if d.pay_status != "jfqrz": + if d.pay_status == "yjf": + raise ValueError("该明细已确认收款") + raise ValueError("当前状态不可确认收款") + d.pay_status = "yjf" + if finance_confirm_attachments is not None: + d.finance_confirm_attachments = finance_confirm_attachments.strip() or None + d.update_by = user_id + d.update_time = datetime.now() + db.add(d) + db.commit() + db.refresh(d) + return d + + +def list_refund_confirm_details( + db: Session, + *, + page: int = 1, + page_size: int = 10, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + if not tenant_no: + return [], 0 + q_base = _detail_query_with_protocol(db, tenant_no, "tfqrz") + total = q_base.order_by(None).with_entities(func.count(BizProtocolPaymentDetail.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + q_base.order_by(BizProtocolPaymentDetail.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + out = [ + _detail_to_dict( + d, + protocol_no=pn or "", + collection_protocol_id=pay.collection_protocol_id if pay else "", + ) + for d, pay, pn in rows + ] + return out, int(total) + + +def confirm_refund( + db: Session, + *, + detail_id: str, + tenant_no: str, + user_id: Optional[str], + finance_confirm_attachments: Optional[str] = None, +) -> BizProtocolPaymentDetail: + d = ( + db.query(BizProtocolPaymentDetail) + .filter( + and_( + BizProtocolPaymentDetail.id == detail_id, + BizProtocolPaymentDetail.tenant_no == tenant_no, + BizProtocolPaymentDetail.del_flag == 0, + ) + ) + .first() + ) + if not d: + raise ValueError("缴费明细不存在") + if d.pay_status != "tfqrz": + if d.pay_status == "ytf": + raise ValueError("该明细已确认退费") + raise ValueError("当前状态不可确认退费") + d.pay_status = "ytf" + if finance_confirm_attachments is not None: + d.finance_confirm_attachments = finance_confirm_attachments.strip() or None + d.update_by = user_id + d.update_time = datetime.now() + db.add(d) + db.commit() + db.refresh(d) + return d + + +def _classify_installment_warning(due: str, today: str) -> str: + """overdue | warn | upcoming — due 为 YYYYMMDD""" + if not due or len(due) != 8 or not due.isdigit(): + return "unknown" + if due < today: + return "overdue" + last_warn = (datetime.strptime(today, "%Y%m%d") + timedelta(days=15)).strftime("%Y%m%d") + if today <= due <= last_warn: + return "warn" + return "upcoming" + + +def list_installment_warning( + db: Session, + *, + page: int = 1, + page_size: int = 10, + warning_type: str = "all", + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + if not tenant_no: + return [], 0 + today = _today_yyyymmdd() + q0 = _detail_query_with_protocol(db, tenant_no, "dfj").filter( + BizProtocolPaymentDetail.installment_no.isnot(None), + ) + + # 拉取当前租户下候选(分期号非空);日期过滤在内存中完成以保证与 classify 一致 + rows_all = q0.order_by(BizProtocolPaymentDetail.installment_due_date.asc()).all() + flattened: List[Dict[str, Any]] = [] + for d, pay, pn in rows_all: + due_raw = (d.installment_due_date or "").strip() if d.installment_due_date else "" + wt = _classify_installment_warning(due_raw, today) + item = _detail_to_dict( + d, + protocol_no=pn or "", + collection_protocol_id=pay.collection_protocol_id if pay else "", + ) + item["warningType"] = wt + wt_filter = (warning_type or "all").strip().lower() + if wt_filter == "overdue" and wt != "overdue": + continue + if wt_filter == "warn" and wt != "warn": + continue + if wt_filter == "all": + pass + elif wt_filter not in ("all", "warn", "overdue"): + pass + flattened.append(item) + + total = len(flattened) + page = max(1, page) + page_size = min(max(1, page_size), 100) + start = (page - 1) * page_size + page_rows = flattened[start : start + page_size] + return page_rows, total + + +def create_collection_protocol( + db: Session, + *, + protocol_no_id: Optional[str] = None, + protocol_no: str = "", + protocol_status: str = "dsx", + protocol_attachments: Optional[str] = None, + register_date: Optional[str] = None, + collector_user_id: Optional[str] = None, + remark: Optional[str] = None, + tenant_no: str = "zkxw", + user_id: Optional[str] = None, +) -> BizCollectionProtocol: + pn = (protocol_no or "").strip() + if not pn: + raise ValueError("协议编号不能为空") + dup = ( + db.query(BizCollectionProtocol) + .filter( + and_( + BizCollectionProtocol.protocol_no == pn, + BizCollectionProtocol.tenant_no == tenant_no, + BizCollectionProtocol.del_flag == 0, + ) + ) + .first() + ) + if dup: + raise ValueError("协议编号已存在") + obj = BizCollectionProtocol( + id=generate_uuid(), + protocol_no_id=(protocol_no_id or "").strip() or None, + protocol_no=pn, + protocol_status=protocol_status.strip() or "dsx", + protocol_attachments=(protocol_attachments or "").strip() or None, + register_date=(register_date or "").strip() or None, + collector_user_id=(collector_user_id or "").strip() or None, + tenant_no=tenant_no, + order_num=0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_collection_protocol( + db: Session, + protocol_id: str, + *, + tenant_no: str, + user_id: Optional[str] = None, + scope_collector_user_id: Optional[str] = None, + protocol_no: Optional[str] = None, + protocol_status: Optional[str] = None, + protocol_attachments: Optional[str] = None, + register_date: Optional[str] = None, + effective_time: Optional[datetime] = None, + effective_desc: Optional[str] = None, + expire_time: Optional[datetime] = None, + expire_desc: Optional[str] = None, + void_time: Optional[datetime] = None, + void_desc: Optional[str] = None, + to_research_time: Optional[datetime] = None, + to_research_desc: Optional[str] = None, + collector_user_id: Optional[str] = None, + remark: Optional[str] = None, +) -> BizCollectionProtocol: + obj = get_collection_protocol( + db, protocol_id, tenant_no, scope_collector_user_id=scope_collector_user_id + ) + if not obj: + raise ValueError("协议单不存在或无权操作") + if protocol_no is not None: + pn = protocol_no.strip() + if not pn: + raise ValueError("协议编号不能为空") + if pn != obj.protocol_no: + dup = ( + db.query(BizCollectionProtocol) + .filter( + and_( + BizCollectionProtocol.protocol_no == pn, + BizCollectionProtocol.tenant_no == tenant_no, + BizCollectionProtocol.del_flag == 0, + BizCollectionProtocol.id != protocol_id, + ) + ) + .first() + ) + if dup: + raise ValueError("协议编号已存在") + obj.protocol_no = pn + if protocol_status is not None: + obj.protocol_status = protocol_status.strip() + if protocol_attachments is not None: + obj.protocol_attachments = protocol_attachments.strip() or None + if register_date is not None: + obj.register_date = register_date.strip() or None + if effective_time is not None: + obj.effective_time = effective_time + if effective_desc is not None: + obj.effective_desc = effective_desc.strip() or None + if expire_time is not None: + obj.expire_time = expire_time + if expire_desc is not None: + obj.expire_desc = expire_desc.strip() or None + if void_time is not None: + obj.void_time = void_time + if void_desc is not None: + obj.void_desc = void_desc.strip() or None + if to_research_time is not None: + obj.to_research_time = to_research_time + if to_research_desc is not None: + obj.to_research_desc = to_research_desc.strip() or None + if collector_user_id is not None: + obj.collector_user_id = collector_user_id.strip() or None + if remark is not None: + obj.remark = remark.strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_collection_protocol( + db: Session, + protocol_id: str, + tenant_no: str, + user_id: Optional[str] = None, + scope_collector_user_id: Optional[str] = None, +) -> None: + obj = get_collection_protocol( + db, protocol_id, tenant_no, scope_collector_user_id=scope_collector_user_id + ) + if not obj: + raise ValueError("协议单不存在或无权操作") + obj.del_flag = 1 + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +# --------------------------------------------------------------------------- +# 子表:当事人客户 / 采集信息 / 样本 / 储存证书 +# --------------------------------------------------------------------------- + + +def get_party_customer(db: Session, collection_protocol_id: str, tenant_no: str) -> Optional[BizProtocolPartyCustomer]: + return ( + db.query(BizProtocolPartyCustomer) + .filter( + and_( + BizProtocolPartyCustomer.collection_protocol_id == collection_protocol_id, + BizProtocolPartyCustomer.tenant_no == tenant_no, + BizProtocolPartyCustomer.del_flag == 0, + ) + ) + .first() + ) + + +def upsert_party_customer( + db: Session, + collection_protocol_id: str, + tenant_no: str, + user_id: Optional[str], + **fields, +) -> BizProtocolPartyCustomer: + fields = _normalize_fields(fields) + obj = get_party_customer(db, collection_protocol_id, tenant_no) + _STR_FIELDS = [ + "customer_id", "mother_name", "mother_phone", "mother_id_card", + "mother_birth_ym", "mother_ethnic", "mother_address", + "father_name", "father_phone", "father_id_card", + "newborn_name", "newborn_id_card", + "newborn_sex", "delivery_mode", "collector_user_id", + ] + _INT_FIELDS = ["birth_order", "newborn_weight_gram"] + _DEC_FIELDS = ["apgar_1min", "apgar_5min"] + _DT_FIELDS = ["newborn_delivery_time"] + if obj: + for k in _STR_FIELDS: + if k in fields: + setattr(obj, k, (fields[k] or "").strip() or None) + for k in _INT_FIELDS: + if k in fields: + setattr(obj, k, fields[k]) + for k in _DEC_FIELDS: + if k in fields: + setattr(obj, k, _optional_decimal(fields[k])) + for k in _DT_FIELDS: + if k in fields: + setattr(obj, k, fields[k]) + if "remark" in fields: + obj.remark = (fields["remark"] or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + obj = BizProtocolPartyCustomer( + id=generate_uuid(), + collection_protocol_id=collection_protocol_id, + tenant_no=tenant_no, + order_num=0, + remark=(fields.get("remark") or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + for k in _STR_FIELDS: + if k in fields: + setattr(obj, k, (fields[k] or "").strip() or None) + for k in _INT_FIELDS: + if k in fields: + setattr(obj, k, fields[k]) + for k in _DEC_FIELDS: + if k in fields: + setattr(obj, k, _optional_decimal(fields[k])) + for k in _DT_FIELDS: + if k in fields: + setattr(obj, k, fields[k]) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def get_collection_info(db: Session, collection_protocol_id: str, tenant_no: str) -> Optional[BizProtocolCollectionInfo]: + return ( + db.query(BizProtocolCollectionInfo) + .filter( + and_( + BizProtocolCollectionInfo.collection_protocol_id == collection_protocol_id, + BizProtocolCollectionInfo.tenant_no == tenant_no, + BizProtocolCollectionInfo.del_flag == 0, + ) + ) + .first() + ) + + +def upsert_collection_info( + db: Session, + collection_protocol_id: str, + tenant_no: str, + user_id: Optional[str], + **fields, +) -> BizProtocolCollectionInfo: + fields = _normalize_fields(fields) + obj = get_collection_info(db, collection_protocol_id, tenant_no) + _STR_FIELDS = ["region_id", "hospital_id", "medical_staff", "sales_user_id"] + _DT_FIELDS = ["sample_collect_time", "maternal_blood_collect_time"] + if obj: + for k in _STR_FIELDS: + if k in fields: + setattr(obj, k, (fields[k] or "").strip() or None) + for k in _DT_FIELDS: + if k in fields: + setattr(obj, k, fields[k]) + if "remark" in fields: + obj.remark = (fields["remark"] or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + obj = BizProtocolCollectionInfo( + id=generate_uuid(), + collection_protocol_id=collection_protocol_id, + tenant_no=tenant_no, + order_num=0, + remark=(fields.get("remark") or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + for k in _STR_FIELDS: + if k in fields: + setattr(obj, k, (fields[k] or "").strip() or None) + for k in _DT_FIELDS: + if k in fields: + setattr(obj, k, fields[k]) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def list_samples(db: Session, collection_protocol_id: str, tenant_no: str) -> List[BizProtocolSample]: + return ( + db.query(BizProtocolSample) + .filter( + and_( + BizProtocolSample.collection_protocol_id == collection_protocol_id, + BizProtocolSample.tenant_no == tenant_no, + BizProtocolSample.del_flag == 0, + ) + ) + .order_by(BizProtocolSample.create_time.asc()) + .all() + ) + + +def save_samples( + db: Session, + collection_protocol_id: str, + tenant_no: str, + user_id: Optional[str], + samples: list, +) -> List[BizProtocolSample]: + existing = list_samples(db, collection_protocol_id, tenant_no) + for s in existing: + s.del_flag = 1 + s.update_by = user_id + s.update_time = datetime.now() + db.add(s) + if existing: + db.flush() + _STR_FIELDS = [ + "sample_type_id", "sample_code_id", "sample_code", "unit_id", + "sample_status", "storage_in_date", "detect_start_date", + "void_date", "storage_expire_date", + ] + _DEC_FIELDS = ["collect_amount", "price"] + _INT_FIELDS = ["storage_years"] + result = [] + for idx, raw in enumerate(samples): + raw = _normalize_fields(raw) + obj = BizProtocolSample( + id=generate_uuid(), + collection_protocol_id=collection_protocol_id, + tenant_no=tenant_no, + order_num=idx, + remark=(raw.get("remark") or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + for k in _STR_FIELDS: + if k == "sample_status": + continue + if k in raw: + setattr(obj, k, (raw[k] or "").strip() or None) + # 样本状态:显式支持 null/空字符串,避免 ORM/库默认值 ycj 回填 + if "sample_status" in raw: + status_val = raw.get("sample_status") + obj.sample_status = ( + str(status_val).strip() if status_val is not None and str(status_val).strip() else None + ) + for k in _DEC_FIELDS: + if k in raw and raw[k] is not None: + setattr(obj, k, Decimal(str(raw[k]))) + for k in _INT_FIELDS: + if k in raw: + setattr(obj, k, raw[k]) + db.add(obj) + result.append(obj) + db.commit() + for o in result: + db.refresh(o) + save_sample_transports(db, collection_protocol_id, tenant_no, user_id, result, samples) + return result + + +def list_sample_transports( + db: Session, + collection_protocol_id: str, + tenant_no: str, +) -> List[BizProtocolSampleTransport]: + return ( + db.query(BizProtocolSampleTransport) + .filter( + and_( + BizProtocolSampleTransport.collection_protocol_id == collection_protocol_id, + BizProtocolSampleTransport.tenant_no == tenant_no, + BizProtocolSampleTransport.del_flag == 0, + ) + ) + .order_by(BizProtocolSampleTransport.order_num.asc(), BizProtocolSampleTransport.create_time.asc()) + .all() + ) + + +def _soft_delete_transports_for_protocol( + db: Session, + collection_protocol_id: str, + tenant_no: str, + user_id: Optional[str], +) -> None: + rows = list_sample_transports(db, collection_protocol_id, tenant_no) + for r in rows: + r.del_flag = 1 + r.update_by = user_id + r.update_time = datetime.now() + db.add(r) + if rows: + db.flush() + + +def save_sample_transports( + db: Session, + collection_protocol_id: str, + tenant_no: str, + user_id: Optional[str], + saved_samples: List[BizProtocolSample], + input_samples: list, +) -> None: + _soft_delete_transports_for_protocol(db, collection_protocol_id, tenant_no, user_id) + for idx, sample_obj in enumerate(saved_samples): + if idx >= len(input_samples): + break + raw = _normalize_fields(input_samples[idx] if isinstance(input_samples[idx], dict) else {}) + transport_raw = raw.get("transport") + if not isinstance(transport_raw, dict): + continue + tnorm = _normalize_fields(transport_raw) + transport_type = (tnorm.get("transport_type") or "").strip() + if not transport_type: + continue + + eta_time = tnorm.get("eta_time") + if isinstance(eta_time, str): + eta_time = parse_datetime(eta_time) + + express_fee = tnorm.get("express_fee") + if express_fee is not None and express_fee != "": + express_fee = Decimal(str(express_fee)) + else: + express_fee = None + + obj = BizProtocolSampleTransport( + id=generate_uuid(), + collection_protocol_id=collection_protocol_id, + sample_id=sample_obj.id, + transport_type=transport_type, + express_company_id=(tnorm.get("express_company_id") or "").strip() or None, + waybill_no=(tnorm.get("waybill_no") or "").strip() or None, + dest_site_id=(tnorm.get("dest_site_id") or "").strip() or None, + express_fee=express_fee, + eta_time=eta_time, + carrier_name=(tnorm.get("carrier_name") or "").strip() or None, + carrier_phone=(tnorm.get("carrier_phone") or "").strip() or None, + tenant_no=tenant_no, + order_num=idx, + remark=(tnorm.get("remark") or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + + +def get_storage_cert(db: Session, collection_protocol_id: str, tenant_no: str) -> Optional[BizProtocolStorageCert]: + return ( + db.query(BizProtocolStorageCert) + .filter( + and_( + BizProtocolStorageCert.collection_protocol_id == collection_protocol_id, + BizProtocolStorageCert.tenant_no == tenant_no, + BizProtocolStorageCert.del_flag == 0, + ) + ) + .first() + ) + + +def list_payment_details( + db: Session, + payment_id: str, + tenant_no: str, +) -> List[BizProtocolPaymentDetail]: + return ( + db.query(BizProtocolPaymentDetail) + .filter( + and_( + BizProtocolPaymentDetail.payment_id == payment_id, + BizProtocolPaymentDetail.tenant_no == tenant_no, + BizProtocolPaymentDetail.del_flag == 0, + ) + ) + .order_by( + BizProtocolPaymentDetail.order_num.asc(), + BizProtocolPaymentDetail.create_time.asc(), + ) + .all() + ) + + +def upsert_payment( + db: Session, + collection_protocol_id: str, + tenant_no: str, + user_id: Optional[str], + **fields, +) -> BizProtocolPayment: + fields = _normalize_fields(fields) + obj = get_payment_by_protocol_id(db, collection_protocol_id, tenant_no) + _STR_FIELDS = ["pay_mode"] + _STR_DATE_FIELDS = ["lump_sum_deadline", "installment_start_date"] + _DEC_FIELDS = ["contract_amount"] + _INT_FIELDS = ["installment_count", "installment_interval_months"] + + def _apply(target: BizProtocolPayment) -> None: + for k in _STR_FIELDS: + if k in fields: + setattr(target, k, (fields[k] or "").strip() or None) + for k in _STR_DATE_FIELDS: + if k in fields: + setattr(target, k, _normalize_yyyymmdd_date(fields[k])) + for k in _DEC_FIELDS: + if k in fields and fields[k] is not None: + setattr(target, k, Decimal(str(fields[k]))) + for k in _INT_FIELDS: + if k in fields: + setattr(target, k, fields[k]) + if "remark" in fields: + target.remark = (fields["remark"] or "").strip() or None + + if obj: + _apply(obj) + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + pay_mode = (fields.get("pay_mode") or "").strip() or "dj" + obj = BizProtocolPayment( + id=generate_uuid(), + collection_protocol_id=collection_protocol_id, + pay_mode=pay_mode, + tenant_no=tenant_no, + order_num=0, + remark=(fields.get("remark") or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + _apply(obj) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def save_payment_details( + db: Session, + payment_id: str, + tenant_no: str, + user_id: Optional[str], + details: list, +) -> List[BizProtocolPaymentDetail]: + existing = list_payment_details(db, payment_id, tenant_no) + for d in existing: + d.del_flag = 1 + d.update_by = user_id + d.update_time = datetime.now() + db.add(d) + if existing: + db.flush() + + _STR_FIELDS = ["transfer_type", "pay_status"] + _STR_DATE_FIELDS = ["installment_due_date"] + _DEC_FIELDS = ["due_amount", "transfer_amount"] + _INT_FIELDS = ["installment_no"] + result: List[BizProtocolPaymentDetail] = [] + for idx, raw in enumerate(details or []): + raw = _normalize_fields(raw) + att_src = raw.pop("attachments", None) + if att_src is None: + att_src = raw.get("transfer_attachments") + transfer_att = attachments_to_urls(att_src) + + transfer_time = raw.get("transfer_time") + if isinstance(transfer_time, str): + transfer_time = parse_datetime(transfer_time) + + installment_no = raw.get("installment_no") + if installment_no is None and raw.get("installment_due_date"): + installment_no = idx + 1 + + obj = BizProtocolPaymentDetail( + id=generate_uuid(), + payment_id=payment_id, + tenant_no=tenant_no, + order_num=idx, + remark=(raw.get("remark") or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + transfer_attachments=transfer_att, + transfer_time=transfer_time, + installment_no=installment_no, + ) + for k in _STR_FIELDS: + if k in raw: + setattr(obj, k, (raw[k] or "").strip() or None) + for k in _STR_DATE_FIELDS: + if k in raw: + setattr(obj, k, _normalize_yyyymmdd_date(raw[k])) + for k in _DEC_FIELDS: + if k in raw and raw[k] is not None: + setattr(obj, k, Decimal(str(raw[k]))) + for k in _INT_FIELDS: + if k in raw and raw[k] is not None: + setattr(obj, k, raw[k]) + if "pay_status" not in raw or not (raw.get("pay_status") or "").strip(): + obj.pay_status = "dfj" + db.add(obj) + result.append(obj) + db.commit() + for o in result: + db.refresh(o) + return result + + +def upsert_storage_cert( + db: Session, + collection_protocol_id: str, + tenant_no: str, + user_id: Optional[str], + **fields, +) -> BizProtocolStorageCert: + fields = _normalize_fields(fields) + obj = get_storage_cert(db, collection_protocol_id, tenant_no) + _STR_FIELDS = ["make_status", "cert_image_urls", "make_date"] + if obj: + for k in _STR_FIELDS: + if k in fields: + setattr(obj, k, (fields[k] or "").strip() or None) + if "remark" in fields: + obj.remark = (fields["remark"] or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + obj = BizProtocolStorageCert( + id=generate_uuid(), + collection_protocol_id=collection_protocol_id, + tenant_no=tenant_no, + order_num=0, + remark=(fields.get("remark") or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + for k in _STR_FIELDS: + if k in fields: + setattr(obj, k, (fields[k] or "").strip() or None) + db.add(obj) + db.commit() + db.refresh(obj) + return obj diff --git a/backend/app/crud/collection_protocol_no.py b/backend/app/crud/collection_protocol_no.py new file mode 100644 index 00000000..24f33cb9 --- /dev/null +++ b/backend/app/crud/collection_protocol_no.py @@ -0,0 +1,339 @@ +"""采集协议号池 CRUD(biz_collection_protocol_no)""" +from __future__ import annotations + +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizCollectionProtocolNo + + +def _row_to_dict(r: BizCollectionProtocolNo) -> Dict[str, Any]: + return { + "id": r.id, + "protocolNo": r.protocol_no, + "issued": int(r.issued or 0), + "issuedUserId": r.issued_user_id or "", + "issuedUserName": r.issued_user_name or "", + "issuedTime": r.issued_time.strftime("%Y-%m-%d %H:%M:%S") if r.issued_time else "", + "tenantNo": r.tenant_no, + "orderNum": r.order_num if r.order_num is not None else 0, + "remark": r.remark or "", + "createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "", + "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "", + } + + +def protocol_no_exists(db: Session, no: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool: + v = (no or "").strip() + if not v: + return False + q = db.query(BizCollectionProtocolNo).filter( + and_( + BizCollectionProtocolNo.protocol_no == v, + BizCollectionProtocolNo.tenant_no == tenant_no, + BizCollectionProtocolNo.del_flag == 0, + ) + ) + if exclude_id: + q = q.filter(BizCollectionProtocolNo.id != exclude_id) + return q.first() is not None + + +def list_protocol_nos( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizCollectionProtocolNo).filter(BizCollectionProtocolNo.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizCollectionProtocolNo.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(or_(BizCollectionProtocolNo.protocol_no.like(like), BizCollectionProtocolNo.remark.like(like))) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCollectionProtocolNo.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCollectionProtocolNo.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_row_to_dict(x) for x in rows], int(total) + + +def get_protocol_no(db: Session, row_id: str, tenant_no: Optional[str] = None) -> Optional[BizCollectionProtocolNo]: + q = db.query(BizCollectionProtocolNo).filter(and_(BizCollectionProtocolNo.id == row_id, BizCollectionProtocolNo.del_flag == 0)) + if tenant_no: + q = q.filter(BizCollectionProtocolNo.tenant_no == tenant_no) + return q.first() + + +def create_protocol_no( + db: Session, + *, + protocol_no: str, + issued: int = 0, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizCollectionProtocolNo: + pn = (protocol_no or "").strip() + if not pn: + raise ValueError("采集协议号不能为空") + if protocol_no_exists(db, pn, tenant_no): + raise ValueError("采集协议号已存在") + obj = BizCollectionProtocolNo( + id=generate_uuid(), + protocol_no=pn, + issued=1 if issued else 0, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_protocol_no( + db: Session, + row_id: str, + *, + protocol_no: Optional[str] = None, + issued: Optional[int] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, + issued_user_id: Optional[str] = None, + issued_user_name: Optional[str] = None, + issued_time: Optional[str] = None, +) -> BizCollectionProtocolNo: + obj = get_protocol_no(db, row_id, tenant_no=tenant_no) + if not obj: + raise ValueError("记录不存在") + if protocol_no is not None: + pn = protocol_no.strip() + if not pn: + raise ValueError("采集协议号不能为空") + if pn != obj.protocol_no and protocol_no_exists(db, pn, obj.tenant_no, exclude_id=row_id): + raise ValueError("采集协议号已存在") + obj.protocol_no = pn + if issued is not None: + obj.issued = 1 if issued else 0 + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = remark.strip() or None + if issued_user_id is not None: + obj.issued_user_id = issued_user_id or None + if issued_user_name is not None: + obj.issued_user_name = issued_user_name or None + if issued_time is not None: + obj.issued_time = issued_time or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_protocol_no(db: Session, row_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_protocol_no(db, row_id, tenant_no=tenant_no) + if not obj: + raise ValueError("记录不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def batch_generate_protocol_nos( + db: Session, + *, + prefix: str, + middle: str, + seq_start: int, + seq_end: int, + digit_count: int = 6, + tenant_no: str = "zkxw", + user_id: Optional[str] = None, +) -> Dict[str, Any]: + """批量生成协议号: 前缀 + 中间段 + 补零序号""" + p = (prefix or "").strip() + m = (middle or "").strip() + if not p: + raise ValueError("固定前缀不能为空") + if seq_start < 0 or seq_end < 0: + raise ValueError("序号不能为负数") + if seq_start > seq_end: + raise ValueError("开始序号不能大于结束序号") + if digit_count < 1: + raise ValueError("序号位数不能小于1") + count = seq_end - seq_start + 1 + if count > 10000: + raise ValueError("单次批量生成不能超过10000条") + + generated_nos = [] + for i in range(seq_start, seq_end + 1): + seq_str = str(i).zfill(digit_count) + full_no = f"{p}{m}{seq_str}" + generated_nos.append(full_no) + + # Batch check existing + existing = set( + row.protocol_no + for row in db.query(BizCollectionProtocolNo.protocol_no) + .filter( + BizCollectionProtocolNo.tenant_no == tenant_no, + BizCollectionProtocolNo.del_flag == 0, + BizCollectionProtocolNo.protocol_no.in_(generated_nos), + ) + .all() + ) + + now = datetime.now() + created = [] + skipped = [] + for no in generated_nos: + if no in existing: + skipped.append(no) + continue + obj = BizCollectionProtocolNo( + id=generate_uuid(), + protocol_no=no, + issued=0, + tenant_no=tenant_no, + order_num=0, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=now, + update_by=user_id, + update_time=now, + del_flag=0, + ) + created.append(obj) + + if created: + db.bulk_save_objects(created) + db.commit() + + return { + "created": len(created), + "skipped": len(skipped), + "skippedNos": skipped, + } + + +def batch_issue_protocol_nos( + db: Session, + *, + ids: List[str], + issued_user_id: str, + issued_user_name: Optional[str] = None, + tenant_no: Optional[str] = None, + user_id: Optional[str] = None, +) -> Dict[str, Any]: + """批量下发协议号""" + if not ids: + raise ValueError("请选择要下发的协议号") + uid = (issued_user_id or "").strip() + if not uid: + raise ValueError("请选择下发用户") + + now = datetime.now() + issued_count = 0 + already_issued = [] + + for row_id in ids: + obj = get_protocol_no(db, row_id, tenant_no=tenant_no) + if not obj: + continue + if obj.issued == 1: + already_issued.append(obj.protocol_no) + continue + obj.issued = 1 + obj.issued_user_id = uid + obj.issued_user_name = (issued_user_name or "").strip() or None + obj.issued_time = now + obj.update_by = user_id + obj.update_time = now + db.add(obj) + issued_count += 1 + + if issued_count > 0: + db.commit() + + return { + "issuedCount": issued_count, + "alreadyIssued": already_issued, + } + + +def batch_revoke_protocol_nos( + db: Session, + *, + ids: List[str], + tenant_no: Optional[str] = None, + user_id: Optional[str] = None, +) -> Dict[str, Any]: + if not ids: + raise ValueError("请选择要收回的协议号") + + now = datetime.now() + revoked_count = 0 + not_issued = [] + + for row_id in ids: + obj = get_protocol_no(db, row_id, tenant_no=tenant_no) + if not obj: + continue + if obj.issued != 1: + not_issued.append(obj.protocol_no) + continue + obj.issued = 0 + obj.issued_user_id = None + obj.issued_user_name = None + obj.issued_time = None + obj.update_by = user_id + obj.update_time = now + db.add(obj) + revoked_count += 1 + + if revoked_count > 0: + db.commit() + + return { + "revokedCount": revoked_count, + "notIssued": not_issued, + } + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/crud/collection_region.py b/backend/app/crud/collection_region.py new file mode 100644 index 00000000..94d1971c --- /dev/null +++ b/backend/app/crud/collection_region.py @@ -0,0 +1,308 @@ +"""采集地区及下属医院 CRUD""" +from __future__ import annotations + +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizCollectionHospital, BizCollectionRegion + + +def _region_to_dict(r: BizCollectionRegion, hospital_names: Optional[List[str]] = None) -> Dict[str, Any]: + return { + "id": r.id, + "regionName": r.region_name, + "hospitalNames": hospital_names or [], + "tenantNo": r.tenant_no, + "orderNum": r.order_num if r.order_num is not None else 0, + "remark": r.remark or "", + "createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "", + "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "", + } + + +def _hospital_to_dict(h: BizCollectionHospital) -> Dict[str, Any]: + return { + "id": h.id, + "regionId": h.region_id, + "hospitalName": h.hospital_name, + "tenantNo": h.tenant_no, + "orderNum": h.order_num if h.order_num is not None else 0, + "remark": h.remark or "", + "createTime": h.create_time.strftime("%Y-%m-%d %H:%M:%S") if h.create_time else "", + "updateTime": h.update_time.strftime("%Y-%m-%d %H:%M:%S") if h.update_time else "", + } + + +def list_collection_regions( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizCollectionRegion).filter(BizCollectionRegion.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizCollectionRegion.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(or_(BizCollectionRegion.region_name.like(like), BizCollectionRegion.remark.like(like))) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCollectionRegion.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCollectionRegion.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + + # 批量查询所有 region 的医院名称,避免 N+1 + region_ids = [r.id for r in rows] + hospital_map: Dict[str, List[str]] = {} + if region_ids: + hosp_rows = ( + db.query(BizCollectionHospital.region_id, BizCollectionHospital.hospital_name) + .filter( + and_( + BizCollectionHospital.region_id.in_(region_ids), + BizCollectionHospital.del_flag == 0, + ) + ) + .order_by(BizCollectionHospital.order_num.asc(), BizCollectionHospital.create_time.desc()) + .all() + ) + for h in hosp_rows: + hospital_map.setdefault(h.region_id, []).append(h.hospital_name) + + return [_region_to_dict(x, hospital_names=hospital_map.get(x.id, [])) for x in rows], int(total) + + +def get_collection_region(db: Session, region_id: str, tenant_no: Optional[str] = None) -> Optional[BizCollectionRegion]: + q = db.query(BizCollectionRegion).filter(and_(BizCollectionRegion.id == region_id, BizCollectionRegion.del_flag == 0)) + if tenant_no: + q = q.filter(BizCollectionRegion.tenant_no == tenant_no) + return q.first() + + +def create_collection_region( + db: Session, + *, + region_name: str, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizCollectionRegion: + name = (region_name or "").strip() + if not name: + raise ValueError("地区名称不能为空") + obj = BizCollectionRegion( + id=generate_uuid(), + region_name=name, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_collection_region( + db: Session, + region_id: str, + *, + region_name: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizCollectionRegion: + obj = get_collection_region(db, region_id, tenant_no=tenant_no) + if not obj: + raise ValueError("采集地区不存在") + if region_name is not None: + n = region_name.strip() + if not n: + raise ValueError("地区名称不能为空") + obj.region_name = n + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = remark.strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_collection_region(db: Session, region_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_collection_region(db, region_id, tenant_no=tenant_no) + if not obj: + raise ValueError("采集地区不存在") + now = datetime.now() + obj.del_flag = 1 + obj.update_time = now + db.add(obj) + subs = ( + db.query(BizCollectionHospital) + .filter( + and_( + BizCollectionHospital.region_id == region_id, + BizCollectionHospital.del_flag == 0, + ) + ) + .all() + ) + for h in subs: + h.del_flag = 1 + h.update_time = now + db.add(h) + db.commit() + + +def list_hospitals( + db: Session, + *, + region_id: str, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + region = get_collection_region(db, region_id, tenant_no=tenant_no) + if not region: + raise ValueError("采集地区不存在") + + def _filtered(): + q0 = db.query(BizCollectionHospital).filter( + and_(BizCollectionHospital.region_id == region_id, BizCollectionHospital.del_flag == 0) + ) + if tenant_no: + q0 = q0.filter(BizCollectionHospital.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(BizCollectionHospital.hospital_name.like(like)) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCollectionHospital.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCollectionHospital.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_hospital_to_dict(x) for x in rows], int(total) + + +def get_hospital(db: Session, hospital_id: str, tenant_no: Optional[str] = None) -> Optional[BizCollectionHospital]: + q = db.query(BizCollectionHospital).filter(and_(BizCollectionHospital.id == hospital_id, BizCollectionHospital.del_flag == 0)) + if tenant_no: + q = q.filter(BizCollectionHospital.tenant_no == tenant_no) + return q.first() + + +def create_hospital( + db: Session, + *, + region_id: str, + hospital_name: str, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizCollectionHospital: + region = get_collection_region(db, region_id, tenant_no=tenant_no) + if not region: + raise ValueError("采集地区不存在") + hn = (hospital_name or "").strip() + if not hn: + raise ValueError("医院名称不能为空") + obj = BizCollectionHospital( + id=generate_uuid(), + region_id=region_id, + hospital_name=hn, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_hospital( + db: Session, + hospital_id: str, + *, + hospital_name: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizCollectionHospital: + obj = get_hospital(db, hospital_id, tenant_no=tenant_no) + if not obj: + raise ValueError("医院不存在") + if hospital_name is not None: + hn = hospital_name.strip() + if not hn: + raise ValueError("医院名称不能为空") + obj.hospital_name = hn + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = remark.strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_hospital(db: Session, hospital_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_hospital(db, hospital_id, tenant_no=tenant_no) + if not obj: + raise ValueError("医院不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/crud/customer.py b/backend/app/crud/customer.py new file mode 100644 index 00000000..bd9e5205 --- /dev/null +++ b/backend/app/crud/customer.py @@ -0,0 +1,194 @@ +"""客户 CRUD(biz_customer)""" +from __future__ import annotations + +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizCustomer + + +def _row_to_dict(c: BizCustomer) -> Dict[str, Any]: + return { + "id": c.id, + "customerName": c.customer_name, + "sex": (c.sex or "2").strip() if c.sex else "2", + "idCardNo": c.id_card_no or "", + "age": c.age, + "phoneNo": c.phone_no or "", + "customerTags": c.customer_tags or "", + "tenantNo": c.tenant_no, + "orderNum": c.order_num if c.order_num is not None else 0, + "remark": c.remark or "", + "createTime": c.create_time.strftime("%Y-%m-%d %H:%M:%S") if c.create_time else "", + "updateTime": c.update_time.strftime("%Y-%m-%d %H:%M:%S") if c.update_time else "", + } + + +def id_card_taken(db: Session, id_card: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool: + ic = (id_card or "").strip() + if not ic: + return False + q = db.query(BizCustomer).filter( + and_(BizCustomer.id_card_no == ic, BizCustomer.tenant_no == tenant_no, BizCustomer.del_flag == 0) + ) + if exclude_id: + q = q.filter(BizCustomer.id != exclude_id) + return q.first() is not None + + +def list_customers( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizCustomer).filter(BizCustomer.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizCustomer.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter( + or_( + BizCustomer.customer_name.like(like), + BizCustomer.phone_no.like(like), + BizCustomer.id_card_no.like(like), + BizCustomer.customer_tags.like(like), + ) + ) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizCustomer.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizCustomer.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_row_to_dict(x) for x in rows], int(total) + + +def get_customer(db: Session, customer_id: str, tenant_no: Optional[str] = None) -> Optional[BizCustomer]: + q = db.query(BizCustomer).filter(and_(BizCustomer.id == customer_id, BizCustomer.del_flag == 0)) + if tenant_no: + q = q.filter(BizCustomer.tenant_no == tenant_no) + return q.first() + + +def create_customer( + db: Session, + *, + customer_name: str, + sex: str = "2", + id_card_no: Optional[str] = None, + age: Optional[int] = None, + phone_no: Optional[str] = None, + customer_tags: Optional[str] = None, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizCustomer: + name = (customer_name or "").strip() + if not name: + raise ValueError("客户姓名不能为空") + ic = (id_card_no or "").strip() or None + if ic and id_card_taken(db, ic, tenant_no): + raise ValueError("该身份证号已存在客户,请勿重复创建") + obj = BizCustomer( + id=generate_uuid(), + customer_name=name, + sex=(sex or "2")[:1] if sex else "2", + id_card_no=ic, + age=age, + phone_no=(phone_no or "").strip() or None, + customer_tags=(customer_tags or "").strip() or None, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_customer( + db: Session, + customer_id: str, + *, + customer_name: Optional[str] = None, + sex: Optional[str] = None, + id_card_no: Optional[str] = None, + age: Optional[int] = None, + phone_no: Optional[str] = None, + customer_tags: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizCustomer: + obj = get_customer(db, customer_id, tenant_no=tenant_no) + if not obj: + raise ValueError("客户不存在") + if customer_name is not None: + n = customer_name.strip() + if not n: + raise ValueError("客户姓名不能为空") + obj.customer_name = n + if sex is not None: + obj.sex = (sex or "2")[:1] + if id_card_no is not None: + ic = id_card_no.strip() or None + if ic and id_card_taken(db, ic, obj.tenant_no, exclude_id=customer_id): + raise ValueError("该身份证号已被其他客户使用") + obj.id_card_no = ic + if age is not None: + obj.age = age + if phone_no is not None: + obj.phone_no = phone_no.strip() or None + if customer_tags is not None: + obj.customer_tags = customer_tags.strip() or None + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = remark.strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_customer(db: Session, customer_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_customer(db, customer_id, tenant_no=tenant_no) + if not obj: + raise ValueError("客户不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/crud/dept.py b/backend/app/crud/dept.py index 826e8cac..1482212e 100644 --- a/backend/app/crud/dept.py +++ b/backend/app/crud/dept.py @@ -48,7 +48,7 @@ def list_depts( if kw: like = f"%{kw}%" q = q.filter(SysDept.dept_name.like(like)) - rows = q.order_by(SysDept.order_num.asc(), SysDept.create_time.desc()).all() + rows = q.order_by(SysDept.create_time.desc()).all() return [_row_to_dict(x) for x in rows] diff --git a/backend/app/crud/dict_data.py b/backend/app/crud/dict_data.py index 85865a9d..036439a4 100644 --- a/backend/app/crud/dict_data.py +++ b/backend/app/crud/dict_data.py @@ -52,7 +52,7 @@ def list_dict_data( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(SysDictData.order_num.asc(), SysDictData.create_time.desc()) +.order_by(SysDictData.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() @@ -147,7 +147,7 @@ def list_dict_data_options( ) if tenant_no: q = q.filter(SysDictData.tenant_no == tenant_no) - rows = q.order_by(SysDictData.order_num.asc(), SysDictData.create_time.desc()).all() + rows = q.order_by(SysDictData.create_time.desc()).all() return [{"label": r.dict_label, "value": r.dict_value} for r in rows] diff --git a/backend/app/crud/dict_type.py b/backend/app/crud/dict_type.py index 8b28ea68..953b983e 100644 --- a/backend/app/crud/dict_type.py +++ b/backend/app/crud/dict_type.py @@ -48,7 +48,7 @@ def list_dict_types( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(SysDictType.order_num.asc(), SysDictType.create_time.desc()) + .order_by(SysDictType.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() diff --git a/backend/app/crud/ethnic.py b/backend/app/crud/ethnic.py new file mode 100644 index 00000000..0614ea87 --- /dev/null +++ b/backend/app/crud/ethnic.py @@ -0,0 +1,30 @@ +"""民族码表 CRUD(全局 sys_ethnic)""" +from typing import Any, Dict, List, Optional + +from sqlalchemy.orm import Session + +from app.models.models import SysEthnic + + +def _row_to_dict(row: SysEthnic) -> Dict[str, Any]: + return { + "id": row.id, + "ethnicName": row.ethnic_name or "", + "ethnicCode": row.ethnic_code or "", + "orderNum": row.order_num, + } + + +def list_ethnics( + db: Session, + *, + keyword: Optional[str] = None, + limit: int = 200, +) -> List[Dict[str, Any]]: + q = db.query(SysEthnic).filter(SysEthnic.del_flag == 0) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q = q.filter(SysEthnic.ethnic_name.like(like)) + rows = q.order_by(SysEthnic.order_num.asc(), SysEthnic.ethnic_code.asc()).limit(max(1, min(limit, 500))).all() + return [_row_to_dict(x) for x in rows] diff --git a/backend/app/crud/exhibition_appointment.py b/backend/app/crud/exhibition_appointment.py new file mode 100644 index 00000000..9bad235a --- /dev/null +++ b/backend/app/crud/exhibition_appointment.py @@ -0,0 +1,238 @@ +"""展览预约 CRUD(biz_exhibition_appointment)""" +from __future__ import annotations + +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizExhibitionAppointment + + +def _format_visit_date(val: Any) -> str: + """格式化 visit_date 为 YYYY-MM-DD HH:mm:ss 字符串""" + if val is None: + return "" + if isinstance(val, datetime): + return val.strftime("%Y-%m-%d %H:%M:%S") + return str(val) + + +def _row_to_dict(u: BizExhibitionAppointment) -> Dict[str, Any]: + return { + "id": u.id, + "appointmentNo": u.appointment_no, + "visitOrg": u.visit_org, + "contactName": u.contact_name, + "visitDate": _format_visit_date(u.visit_date), + "visitorCount": u.visitor_count, + "receptionUserId": u.reception_user_id, + "tenantNo": u.tenant_no, + "orderNum": u.order_num if u.order_num is not None else 0, + "remark": u.remark or "", + "createTime": u.create_time.strftime("%Y-%m-%d %H:%M:%S") if u.create_time else "", + "updateTime": u.update_time.strftime("%Y-%m-%d %H:%M:%S") if u.update_time else "", + } + + +def list_exhibition_appointments( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizExhibitionAppointment).filter(BizExhibitionAppointment.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizExhibitionAppointment.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter( + or_( + BizExhibitionAppointment.appointment_no.like(like), + BizExhibitionAppointment.visit_org.like(like), + BizExhibitionAppointment.contact_name.like(like), + ) + ) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizExhibitionAppointment.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizExhibitionAppointment.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_row_to_dict(x) for x in rows], int(total) + + +def get_exhibition_appointment(db: Session, appointment_id: str) -> Optional[BizExhibitionAppointment]: + return ( + db.query(BizExhibitionAppointment) + .filter(and_(BizExhibitionAppointment.id == appointment_id, BizExhibitionAppointment.del_flag == 0)) + .first() + ) + + +def appointment_no_exists(db: Session, appointment_no: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool: + q = db.query(BizExhibitionAppointment).filter( + and_( + BizExhibitionAppointment.appointment_no == appointment_no.strip(), + BizExhibitionAppointment.tenant_no == tenant_no, + BizExhibitionAppointment.del_flag == 0, + ) + ) + if exclude_id: + q = q.filter(BizExhibitionAppointment.id != exclude_id) + return q.first() is not None + + +def _generate_appointment_no(db: Session, tenant_no: str) -> str: + """生成预约编号:YYYYMMDD + 6位序号(如 20260515000001)""" + today_str = datetime.now().strftime("%Y%m%d") + prefix = today_str + last_row = ( + db.query(BizExhibitionAppointment.appointment_no) + .filter( + BizExhibitionAppointment.tenant_no == tenant_no, + BizExhibitionAppointment.appointment_no.like(f"{prefix}%"), + BizExhibitionAppointment.del_flag == 0, + ) + .order_by(BizExhibitionAppointment.appointment_no.desc()) + .first() + ) + if last_row and last_row[0]: + try: + seq = int(last_row[0][len(prefix):]) + 1 + except (ValueError, IndexError): + seq = 1 + else: + seq = 1 + return f"{prefix}{seq:06d}" + + +def create_exhibition_appointment( + db: Session, + *, + appointment_no: Optional[str] = None, + visit_org: str, + contact_name: str, + visit_date: Optional[str] = None, + visitor_count: int, + reception_user_id: Optional[str] = None, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizExhibitionAppointment: + no = (appointment_no or "").strip() + if not no: + no = _generate_appointment_no(db, tenant_no) + else: + if appointment_no_exists(db, no, tenant_no): + raise ValueError("预约编号已存在") + visit_date_val: Optional[datetime] = None + if visit_date: + try: + visit_date_val = datetime.fromisoformat(visit_date) + except (ValueError, TypeError): + visit_date_val = None + obj = BizExhibitionAppointment( + id=generate_uuid(), + appointment_no=no, + visit_org=(visit_org or "").strip(), + contact_name=(contact_name or "").strip(), + visit_date=visit_date_val, + visitor_count=visitor_count if visitor_count is not None else 0, + reception_user_id=reception_user_id or "", + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_exhibition_appointment( + db: Session, + appointment_id: str, + *, + appointment_no: Optional[str] = None, + visit_org: Optional[str] = None, + contact_name: Optional[str] = None, + visit_date: Optional[str] = None, + visitor_count: Optional[int] = None, + reception_user_id: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizExhibitionAppointment: + obj = get_exhibition_appointment(db, appointment_id) + if not obj: + raise ValueError("展览预约不存在") + if appointment_no is not None: + new_no = appointment_no.strip() + if not new_no: + raise ValueError("预约编号不能为空") + if new_no != obj.appointment_no and appointment_no_exists(db, new_no, obj.tenant_no, exclude_id=appointment_id): + raise ValueError("预约编号已存在") + obj.appointment_no = new_no + if visit_org is not None: + obj.visit_org = (visit_org or "").strip() + if contact_name is not None: + obj.contact_name = (contact_name or "").strip() + if visit_date is not None: + if visit_date: + try: + obj.visit_date = datetime.fromisoformat(visit_date) + except (ValueError, TypeError): + obj.visit_date = None + else: + obj.visit_date = None + if visitor_count is not None: + obj.visitor_count = visitor_count + if reception_user_id is not None: + obj.reception_user_id = reception_user_id or "" + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = (remark or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_exhibition_appointment(db: Session, appointment_id: str) -> None: + obj = get_exhibition_appointment(db, appointment_id) + if not obj: + raise ValueError("展览预约不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/crud/express_company.py b/backend/app/crud/express_company.py index 09fe5318..12967bda 100644 --- a/backend/app/crud/express_company.py +++ b/backend/app/crud/express_company.py @@ -48,7 +48,7 @@ def list_express_companies( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(BizExpressCompany.order_num.asc(), BizExpressCompany.create_time.desc()) + .order_by(BizExpressCompany.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() diff --git a/backend/app/crud/item_unit.py b/backend/app/crud/item_unit.py index db3f9006..d4e3c517 100644 --- a/backend/app/crud/item_unit.py +++ b/backend/app/crud/item_unit.py @@ -48,7 +48,7 @@ def list_item_units( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(BizItemUnit.order_num.asc(), BizItemUnit.create_time.desc()) + .order_by(BizItemUnit.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() diff --git a/backend/app/crud/leave.py b/backend/app/crud/leave.py new file mode 100644 index 00000000..6643a21f --- /dev/null +++ b/backend/app/crud/leave.py @@ -0,0 +1,144 @@ +"""请假申请 CRUD""" +from __future__ import annotations + +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizLeaveRequest + + +def total_pages(total: int, page_size: int) -> int: + return (total + page_size - 1) // page_size if page_size else 0 + + +def _row_to_dict(row: BizLeaveRequest) -> Dict[str, Any]: + return { + "id": row.id, + "leaveType": row.leave_type, + "startDate": row.start_date, + "endDate": row.end_date, + "days": row.days, + "reason": row.reason or "", + "wfInstanceId": row.wf_instance_id or "", + "status": row.status, + "tenantNo": row.tenant_no, + "orderNum": row.order_num if row.order_num is not None else 0, + "remark": row.remark or "", + "createBy": row.create_by or "", + "createTime": row.create_time.strftime("%Y-%m-%d %H:%M:%S") if row.create_time else "", + "updateBy": row.update_by or "", + "updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "", + } + + +def list_leaves( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + status: Optional[str] = None, + tenant_no: Optional[str] = None, + create_by: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + q = db.query(BizLeaveRequest).filter(BizLeaveRequest.del_flag == 0) + if tenant_no: + q = q.filter(BizLeaveRequest.tenant_no == tenant_no) + if status: + q = q.filter(BizLeaveRequest.status == status) + if create_by: + q = q.filter(BizLeaveRequest.create_by == create_by) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q = q.filter(or_(BizLeaveRequest.reason.like(like), BizLeaveRequest.leave_type.like(like))) + total = q.count() + rows = ( + q.order_by(BizLeaveRequest.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_row_to_dict(r) for r in rows], total + + +def get_leave(db: Session, leave_id: str) -> Optional[Dict[str, Any]]: + row = db.query(BizLeaveRequest).filter(BizLeaveRequest.id == leave_id, BizLeaveRequest.del_flag == 0).first() + return _row_to_dict(row) if row else None + + +def create_leave( + db: Session, + *, + leave_type: str, + start_date: str, + end_date: str, + days: int, + reason: str = "", + tenant_no: str = "zkxw", + user_id: str = "", + dept_id: str = "", + remark: str = "", +) -> BizLeaveRequest: + row = BizLeaveRequest( + id=generate_uuid(), + leave_type=leave_type, + start_date=start_date, + end_date=end_date, + days=days, + reason=reason, + status="draft", + tenant_no=tenant_no, + create_by=user_id, + create_dept_id=dept_id, + remark=remark, + ) + db.add(row) + db.flush() + return row + + +def update_leave(db: Session, leave_id: str, *, user_id: str = "", **fields) -> None: + row = db.query(BizLeaveRequest).filter(BizLeaveRequest.id == leave_id, BizLeaveRequest.del_flag == 0).first() + if not row: + return + mapping = { + "leaveType": "leave_type", + "startDate": "start_date", + "endDate": "end_date", + "days": "days", + "reason": "reason", + "remark": "remark", + } + for camel, snake in mapping.items(): + if camel in fields and fields[camel] is not None: + setattr(row, snake, fields[camel]) + row.update_by = user_id + row.update_time = datetime.now() + db.flush() + + +def update_leave_status(db: Session, leave_id: str, status: str, wf_instance_id: str = "", user_id: str = "") -> None: + row = db.query(BizLeaveRequest).filter(BizLeaveRequest.id == leave_id, BizLeaveRequest.del_flag == 0).first() + if not row: + return + row.status = status + if wf_instance_id: + row.wf_instance_id = wf_instance_id + row.update_by = user_id + row.update_time = datetime.now() + db.flush() + + +def soft_delete_leave(db: Session, leave_id: str, user_id: str = "") -> None: + row = db.query(BizLeaveRequest).filter(BizLeaveRequest.id == leave_id, BizLeaveRequest.del_flag == 0).first() + if not row: + return + row.del_flag = 1 + row.update_by = user_id + row.update_time = datetime.now() + db.flush() diff --git a/backend/app/crud/menu.py b/backend/app/crud/menu.py index ae252d25..137aaf7c 100644 --- a/backend/app/crud/menu.py +++ b/backend/app/crud/menu.py @@ -51,7 +51,7 @@ def list_menus( if kw: like = f"%{kw}%" q = q.filter(SysMenu.menu_name.like(like)) - rows = q.order_by(SysMenu.order_num.asc(), SysMenu.create_time.desc()).all() + rows = q.order_by(SysMenu.create_time.desc()).all() return [_row_to_dict(x) for x in rows] diff --git a/backend/app/crud/post.py b/backend/app/crud/post.py index d9c05b15..6916a660 100644 --- a/backend/app/crud/post.py +++ b/backend/app/crud/post.py @@ -49,7 +49,7 @@ def list_posts( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(SysPost.order_num.asc(), SysPost.create_time.desc()) + .order_by(SysPost.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() diff --git a/backend/app/crud/role.py b/backend/app/crud/role.py index 37ef6b4f..4ab4e6f1 100644 --- a/backend/app/crud/role.py +++ b/backend/app/crud/role.py @@ -65,7 +65,7 @@ def list_roles( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(SysRole.order_num.asc(), SysRole.create_time.desc()) + .order_by(SysRole.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() @@ -207,13 +207,12 @@ def update_role_menus( tenant_no: str, user_id: Optional[str] = None, ) -> None: - # soft-delete old - db.query(SysRoleMenu).filter( - and_(SysRoleMenu.role_id == role_id, SysRoleMenu.del_flag == 0) - ).update({"del_flag": 1, "update_time": datetime.now()}) - # insert new + # uk_role_menu 为 (role_id, menu_id, del_flag):先全量软删再插入会在再次保存时 + # 出现两条 (role_id, menu_id, del_flag=1),触发 1062。关联表采用整角色物理清理后重建。 + unique_ids = list(dict.fromkeys(menu_ids or [])) + db.query(SysRoleMenu).filter(SysRoleMenu.role_id == role_id).delete(synchronize_session=False) now = datetime.now() - for mid in menu_ids: + for mid in unique_ids: obj = SysRoleMenu( id=generate_uuid(), role_id=role_id, diff --git a/backend/app/crud/sample_code.py b/backend/app/crud/sample_code.py new file mode 100644 index 00000000..c4923626 --- /dev/null +++ b/backend/app/crud/sample_code.py @@ -0,0 +1,341 @@ +"""样本编码池 CRUD(biz_sample_code)""" +from __future__ import annotations + +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizSampleCode + + +def _row_to_dict(r: BizSampleCode) -> Dict[str, Any]: + return { + "id": r.id, + "sampleCode": r.sample_code, + "sampleCodeType": r.sample_code_type or "", + "issued": int(r.issued or 0), + "issuedUserId": r.issued_user_id or "", + "issuedUserName": r.issued_user_name or "", + "issuedTime": r.issued_time.strftime("%Y-%m-%d %H:%M:%S") if r.issued_time else "", + "tenantNo": r.tenant_no, + "orderNum": r.order_num if r.order_num is not None else 0, + "remark": r.remark or "", + "createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "", + "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "", + } + + +def sample_code_exists(db: Session, code: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool: + v = (code or "").strip() + if not v: + return False + q = db.query(BizSampleCode).filter( + and_(BizSampleCode.sample_code == v, BizSampleCode.tenant_no == tenant_no, BizSampleCode.del_flag == 0) + ) + if exclude_id: + q = q.filter(BizSampleCode.id != exclude_id) + return q.first() is not None + + +def list_sample_codes( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(BizSampleCode).filter(BizSampleCode.del_flag == 0) + if tenant_no: + q0 = q0.filter(BizSampleCode.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(or_(BizSampleCode.sample_code.like(like), BizSampleCode.remark.like(like))) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(BizSampleCode.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(BizSampleCode.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + return [_row_to_dict(x) for x in rows], int(total) + + +def get_sample_code(db: Session, row_id: str, tenant_no: Optional[str] = None) -> Optional[BizSampleCode]: + q = db.query(BizSampleCode).filter(and_(BizSampleCode.id == row_id, BizSampleCode.del_flag == 0)) + if tenant_no: + q = q.filter(BizSampleCode.tenant_no == tenant_no) + return q.first() + + +def create_sample_code( + db: Session, + *, + sample_code: str, + issued: int = 0, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizSampleCode: + sc = (sample_code or "").strip() + if not sc: + raise ValueError("样本编码不能为空") + if sample_code_exists(db, sc, tenant_no): + raise ValueError("样本编码已存在") + obj = BizSampleCode( + id=generate_uuid(), + sample_code=sc, + issued=1 if issued else 0, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_sample_code( + db: Session, + row_id: str, + *, + sample_code: Optional[str] = None, + issued: Optional[int] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, + issued_user_id: Optional[str] = None, + issued_user_name: Optional[str] = None, + issued_time: Optional[str] = None, +) -> BizSampleCode: + obj = get_sample_code(db, row_id, tenant_no=tenant_no) + if not obj: + raise ValueError("记录不存在") + if sample_code is not None: + sc = sample_code.strip() + if not sc: + raise ValueError("样本编码不能为空") + if sc != obj.sample_code and sample_code_exists(db, sc, obj.tenant_no, exclude_id=row_id): + raise ValueError("样本编码已存在") + obj.sample_code = sc + if issued is not None: + obj.issued = 1 if issued else 0 + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = remark.strip() or None + if issued_user_id is not None: + obj.issued_user_id = issued_user_id or None + if issued_user_name is not None: + obj.issued_user_name = issued_user_name or None + if issued_time is not None: + obj.issued_time = issued_time or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_sample_code(db: Session, row_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_sample_code(db, row_id, tenant_no=tenant_no) + if not obj: + raise ValueError("记录不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def batch_generate_sample_codes( + db: Session, + *, + sample_code_type: str, + middle: str, + seq_start: int, + seq_end: int, + digit_count: int = 6, + tenant_no: str = "zkxw", + user_id: Optional[str] = None, +) -> Dict[str, Any]: + """批量生成样本编码: 类型前缀 + 中间段 + 补零序号""" + t = (sample_code_type or "").strip() + if t not in ("zxbm", "qdbm"): + raise ValueError("样本编码类型无效,仅支持 zxbm/qdbm") + m = (middle or "").strip() + if not m: + raise ValueError("中间段不能为空") + if seq_start < 0 or seq_end < 0: + raise ValueError("序号不能为负数") + if seq_start > seq_end: + raise ValueError("开始序号不能大于结束序号") + if digit_count < 1: + raise ValueError("序号位数不能小于1") + count = seq_end - seq_start + 1 + if count > 10000: + raise ValueError("单次批量生成不能超过10000条") + + prefix_map = {"zxbm": "ZKZX", "qdbm": "ZKQD"} + prefix = prefix_map[t] + + generated_codes = [] + for i in range(seq_start, seq_end + 1): + seq_str = str(i).zfill(digit_count) + full_code = f"{prefix}{m}{seq_str}" + generated_codes.append(full_code) + + existing = set( + row.sample_code + for row in db.query(BizSampleCode.sample_code) + .filter( + BizSampleCode.tenant_no == tenant_no, + BizSampleCode.del_flag == 0, + BizSampleCode.sample_code.in_(generated_codes), + ) + .all() + ) + + now = datetime.now() + created = [] + skipped = [] + for code in generated_codes: + if code in existing: + skipped.append(code) + continue + obj = BizSampleCode( + id=generate_uuid(), + sample_code=code, + sample_code_type=t, + issued=0, + tenant_no=tenant_no, + order_num=0, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=now, + update_by=user_id, + update_time=now, + del_flag=0, + ) + created.append(obj) + + if created: + db.bulk_save_objects(created) + db.commit() + + return { + "created": len(created), + "skipped": len(skipped), + "skippedCodes": skipped, + } + + +def batch_issue_sample_codes( + db: Session, + *, + ids: List[str], + issued_user_id: str, + issued_user_name: Optional[str] = None, + tenant_no: Optional[str] = None, + user_id: Optional[str] = None, +) -> Dict[str, Any]: + """批量下发样本编码""" + if not ids: + raise ValueError("请选择要下发的样本编码") + uid = (issued_user_id or "").strip() + if not uid: + raise ValueError("请选择下发用户") + + now = datetime.now() + issued_count = 0 + already_issued = [] + + for row_id in ids: + obj = get_sample_code(db, row_id, tenant_no=tenant_no) + if not obj: + continue + if obj.issued == 1: + already_issued.append(obj.sample_code) + continue + obj.issued = 1 + obj.issued_user_id = uid + obj.issued_user_name = (issued_user_name or "").strip() or None + obj.issued_time = now + obj.update_by = user_id + obj.update_time = now + db.add(obj) + issued_count += 1 + + if issued_count > 0: + db.commit() + + return { + "issuedCount": issued_count, + "alreadyIssued": already_issued, + } + + +def batch_revoke_sample_codes( + db: Session, + *, + ids: List[str], + tenant_no: Optional[str] = None, + user_id: Optional[str] = None, +) -> Dict[str, Any]: + if not ids: + raise ValueError("请选择要收回的样本编码") + + now = datetime.now() + revoked_count = 0 + not_issued = [] + + for row_id in ids: + obj = get_sample_code(db, row_id, tenant_no=tenant_no) + if not obj: + continue + if obj.issued != 1: + not_issued.append(obj.sample_code) + continue + obj.issued = 0 + obj.issued_user_id = None + obj.issued_user_name = None + obj.issued_time = None + obj.update_by = user_id + obj.update_time = now + db.add(obj) + revoked_count += 1 + + if revoked_count > 0: + db.commit() + + return { + "revokedCount": revoked_count, + "notIssued": not_issued, + } + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/crud/site.py b/backend/app/crud/site.py index 4b38d98f..37f7a182 100644 --- a/backend/app/crud/site.py +++ b/backend/app/crud/site.py @@ -54,7 +54,7 @@ def list_sites( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(SysSite.order_num.asc(), SysSite.create_time.desc()) + .order_by(SysSite.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() diff --git a/backend/app/crud/storage_facility.py b/backend/app/crud/storage_facility.py new file mode 100644 index 00000000..085fe25e --- /dev/null +++ b/backend/app/crud/storage_facility.py @@ -0,0 +1,356 @@ +"""存储设施(树结构)+ 小格 CRUD(biz_storage_facility / biz_storage_facility_slot)""" +from __future__ import annotations + +from datetime import datetime +from typing import Any, Dict, List, Optional + +from sqlalchemy import and_, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import BizStorageFacility, BizStorageFacilitySlot + + +# --------------------------------------------------------------------------- +# Facility node helpers +# --------------------------------------------------------------------------- + + +def _row_to_dict(f: BizStorageFacility) -> Dict[str, Any]: + return { + "id": f.id, + "parentId": f.parent_id, + "nodeName": f.node_name, + "nodeType": f.node_type or "", + "isLeaf": 1 if f.is_leaf else 0, + "tenantNo": f.tenant_no, + "orderNum": f.order_num if f.order_num is not None else 0, + "remark": f.remark or "", + "createTime": f.create_time.strftime("%Y-%m-%d %H:%M:%S") if f.create_time else "", + "updateTime": f.update_time.strftime("%Y-%m-%d %H:%M:%S") if f.update_time else "", + } + + +def list_all_facilities( + db: Session, + *, + tenant_no: str, + keyword: Optional[str] = None, +) -> List[Dict[str, Any]]: + """Return FLAT list of all non-deleted facility nodes (no pagination, no tree building).""" + q = db.query(BizStorageFacility).filter(BizStorageFacility.del_flag == 0) + if tenant_no: + q = q.filter(BizStorageFacility.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q = q.filter(BizStorageFacility.node_name.like(like)) + rows = q.order_by(BizStorageFacility.order_num.asc(), BizStorageFacility.create_time.asc()).all() + return [_row_to_dict(x) for x in rows] + + +def get_facility(db: Session, node_id: str, tenant_no: Optional[str] = None) -> Optional[BizStorageFacility]: + q = db.query(BizStorageFacility).filter( + and_(BizStorageFacility.id == node_id, BizStorageFacility.del_flag == 0) + ) + if tenant_no: + q = q.filter(BizStorageFacility.tenant_no == tenant_no) + return q.first() + + +def get_children(db: Session, parent_id: str, tenant_no: str) -> List[BizStorageFacility]: + """Direct children of a node.""" + return ( + db.query(BizStorageFacility) + .filter( + and_( + BizStorageFacility.parent_id == parent_id, + BizStorageFacility.del_flag == 0, + BizStorageFacility.tenant_no == tenant_no, + ) + ) + .order_by(BizStorageFacility.order_num.asc(), BizStorageFacility.create_time.asc()) + .all() + ) + + +def collect_descendant_ids(db: Session, node_id: str, tenant_no: str) -> List[str]: + """Iteratively collect ALL descendant node IDs (stack-based, no recursion).""" + ids: List[str] = [] + stack: List[str] = [node_id] + while stack: + current = stack.pop() + children = get_children(db, current, tenant_no) + for child in children: + ids.append(child.id) + stack.append(child.id) + return ids + + +def create_facility( + db: Session, + *, + parent_id: str = "0", + node_name: str, + node_type: Optional[str] = None, + is_leaf: int = 0, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizStorageFacility: + name = (node_name or "").strip() + if not name: + raise ValueError("节点名称不能为空") + obj = BizStorageFacility( + id=generate_uuid(), + parent_id=parent_id or "0", + node_name=name, + node_type=(node_type or "").strip() or None, + is_leaf=is_leaf, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_facility( + db: Session, + node_id: str, + *, + node_name: Optional[str] = None, + node_type: Optional[str] = None, + is_leaf: Optional[int] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizStorageFacility: + obj = get_facility(db, node_id, tenant_no=tenant_no) + if not obj: + raise ValueError("存储设施节点不存在") + if node_name is not None: + name = (node_name or "").strip() + if not name: + raise ValueError("节点名称不能为空") + obj.node_name = name + if node_type is not None: + obj.node_type = (node_type or "").strip() or None + if is_leaf is not None: + obj.is_leaf = is_leaf + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = (remark or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_facility(db: Session, node_id: str, tenant_no: Optional[str] = None) -> None: + """Cascade soft-delete: node + all descendants + their slots.""" + node = get_facility(db, node_id, tenant_no=tenant_no) + if not node: + raise ValueError("存储设施节点不存在") + # Collect all descendant IDs + all_ids = [node_id] + collect_descendant_ids(db, node_id, node.tenant_no) + now = datetime.now() + # Soft-delete all slots belonging to these nodes + db.query(BizStorageFacilitySlot).filter( + BizStorageFacilitySlot.facility_node_id.in_(all_ids), + BizStorageFacilitySlot.del_flag == 0, + ).update({"del_flag": 1, "update_time": now}, synchronize_session=False) + # Soft-delete all facility nodes + db.query(BizStorageFacility).filter( + BizStorageFacility.id.in_(all_ids), + BizStorageFacility.del_flag == 0, + ).update({"del_flag": 1, "update_time": now}, synchronize_session=False) + db.commit() + + +# --------------------------------------------------------------------------- +# Slot helpers +# --------------------------------------------------------------------------- + + +def _slot_to_dict(s: BizStorageFacilitySlot) -> Dict[str, Any]: + return { + "id": s.id, + "facilityNodeId": s.facility_node_id, + "slotCode": s.slot_code or "", + "slotStatus": s.slot_status or "", + "tenantNo": s.tenant_no, + "orderNum": s.order_num if s.order_num is not None else 0, + "remark": s.remark or "", + "createTime": s.create_time.strftime("%Y-%m-%d %H:%M:%S") if s.create_time else "", + "updateTime": s.update_time.strftime("%Y-%m-%d %H:%M:%S") if s.update_time else "", + } + + +def list_slots( + db: Session, + *, + facility_node_id: str, + tenant_no: Optional[str] = None, +) -> List[Dict[str, Any]]: + q = db.query(BizStorageFacilitySlot).filter( + and_( + BizStorageFacilitySlot.facility_node_id == facility_node_id, + BizStorageFacilitySlot.del_flag == 0, + ) + ) + if tenant_no: + q = q.filter(BizStorageFacilitySlot.tenant_no == tenant_no) + rows = q.order_by(BizStorageFacilitySlot.order_num.asc(), BizStorageFacilitySlot.create_time.asc()).all() + return [_slot_to_dict(x) for x in rows] + + +def get_slot(db: Session, slot_id: str, tenant_no: Optional[str] = None) -> Optional[BizStorageFacilitySlot]: + q = db.query(BizStorageFacilitySlot).filter( + and_(BizStorageFacilitySlot.id == slot_id, BizStorageFacilitySlot.del_flag == 0) + ) + if tenant_no: + q = q.filter(BizStorageFacilitySlot.tenant_no == tenant_no) + return q.first() + + +def create_slot( + db: Session, + *, + facility_node_id: str, + slot_code: Optional[str] = None, + slot_status: Optional[str] = None, + tenant_no: str = "zkxw", + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> BizStorageFacilitySlot: + fid = (facility_node_id or "").strip() + if not fid: + raise ValueError("所属设施节点不能为空") + obj = BizStorageFacilitySlot( + id=generate_uuid(), + facility_node_id=fid, + slot_code=(slot_code or "").strip() or None, + slot_status=(slot_status or "").strip() or None, + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_slot( + db: Session, + slot_id: str, + *, + slot_code: Optional[str] = None, + slot_status: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> BizStorageFacilitySlot: + obj = get_slot(db, slot_id, tenant_no=tenant_no) + if not obj: + raise ValueError("存储设施小格不存在") + if slot_code is not None: + obj.slot_code = (slot_code or "").strip() or None + if slot_status is not None: + obj.slot_status = (slot_status or "").strip() or None + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = (remark or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_slot(db: Session, slot_id: str, tenant_no: Optional[str] = None) -> None: + obj = get_slot(db, slot_id, tenant_no=tenant_no) + if not obj: + raise ValueError("存储设施小格不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def batch_create_slots( + db: Session, + *, + facility_node_id: str, + rows: int, + cols: int, + code_prefix: str = "", + tenant_no: str = "zkxw", + user_id: Optional[str] = None, +) -> List[Dict[str, Any]]: + fid = (facility_node_id or "").strip() + if not fid: + raise ValueError("所属设施节点不能为空") + if rows < 1 or cols < 1: + raise ValueError("行数和列数必须大于 0") + if rows > 50 or cols > 50: + raise ValueError("单次最多生成 50×50 = 2500 个格子") + node = db.query(BizStorageFacility).filter( + and_(BizStorageFacility.id == fid, BizStorageFacility.del_flag == 0) + ).first() + if not node: + raise ValueError("所属设施节点不存在") + if not node.is_leaf: + raise ValueError("只能为叶子节点创建格子") + prefix = (code_prefix or "").strip() + now = datetime.now() + slots: List[BizStorageFacilitySlot] = [] + for r in range(1, rows + 1): + for c in range(1, cols + 1): + code = f"{prefix}{r}-{c}" if prefix else f"{r}-{c}" + order = (r - 1) * cols + c + obj = BizStorageFacilitySlot( + id=generate_uuid(), + facility_node_id=fid, + slot_code=code, + slot_status="空", + tenant_no=tenant_no, + order_num=order, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=now, + update_by=user_id, + update_time=now, + del_flag=0, + ) + db.add(obj) + slots.append(obj) + db.commit() + for s in slots: + db.refresh(s) + return [_slot_to_dict(s) for s in slots] diff --git a/backend/app/crud/tenant.py b/backend/app/crud/tenant.py index a4004790..8b24a0ed 100644 --- a/backend/app/crud/tenant.py +++ b/backend/app/crud/tenant.py @@ -47,7 +47,7 @@ def list_tenants( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(SysTenant.order_num.asc(), SysTenant.create_time.desc()) + .order_by(SysTenant.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() diff --git a/backend/app/crud/user.py b/backend/app/crud/user.py index 9f65a9db..5df57a35 100644 --- a/backend/app/crud/user.py +++ b/backend/app/crud/user.py @@ -90,7 +90,7 @@ def list_users( page_size = min(max(1, page_size), 100) rows = ( _filtered() - .order_by(SysUser.order_num.asc(), SysUser.create_time.desc()) + .order_by(SysUser.create_time.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() @@ -114,6 +114,91 @@ def get_user(db: Session, user_id: str) -> Optional[SysUser]: ) +def list_post_ids_for_user(db: Session, user_id: str) -> List[str]: + """用户当前有效职位 ID 列表。""" + return [ + r.post_id + for r in db.query(SysUserPost) + .filter(and_(SysUserPost.user_id == user_id, SysUserPost.del_flag == 0)) + .all() + ] + + +def set_user_post_ids_only( + db: Session, + user_id: str, + tenant_no: str, + post_ids: List[str], + updater_id: Optional[str], +) -> None: + """仅替换 sys_user_post,不影响 sys_user_role。""" + now = datetime.now() + uniq = [p for p in dict.fromkeys(post_ids or []) if p] + db.query(SysUserPost).filter( + and_(SysUserPost.user_id == user_id, SysUserPost.del_flag == 0) + ).update({"del_flag": 1, "update_time": now}, synchronize_session=False) + for pid in uniq: + db.add( + SysUserPost( + id=generate_uuid(), + user_id=user_id, + post_id=pid, + tenant_no=tenant_no, + order_num=0, + remark=None, + create_by=updater_id, + create_dept_id="", + create_time=now, + update_by=updater_id, + update_time=now, + del_flag=0, + ) + ) + + +def list_user_ids_bound_to_post(db: Session, post_id: str, tenant_no: str) -> List[str]: + rows = ( + db.query(SysUserPost.user_id) + .filter( + and_( + SysUserPost.post_id == post_id, + SysUserPost.tenant_no == tenant_no, + SysUserPost.del_flag == 0, + ) + ) + .all() + ) + return [r[0] for r in rows] + + +def sync_post_user_bindings( + db: Session, + post_id: str, + tenant_no: str, + user_ids: List[str], + updater_id: Optional[str], +) -> None: + """将本职位绑定的用户集合设为 user_ids(只增删本职位,保留用户其它职位与角色)。""" + ordered = list(dict.fromkeys([uid for uid in (user_ids or []) if uid])) + for uid in ordered: + u = get_user(db, uid) + if not u or u.tenant_no != tenant_no: + raise ValueError("存在无效用户或用户不属于当前租户") + + current = set(list_user_ids_bound_to_post(db, post_id, tenant_no)) + target = set(ordered) + affected = current | target + for uid in affected: + u = get_user(db, uid) + if not u or u.tenant_no != tenant_no: + continue + existing = list_post_ids_for_user(db, uid) + without = [p for p in existing if p != post_id] + new_posts = without + [post_id] if uid in target else without + set_user_post_ids_only(db, uid, tenant_no, new_posts, updater_id) + db.commit() + + def get_user_with_relations(db: Session, user_id: str) -> Optional[Dict[str, Any]]: u = get_user(db, user_id) if not u: @@ -133,12 +218,7 @@ def get_user_with_relations(db: Session, user_id: str) -> Optional[Dict[str, Any .filter(and_(SysUserRole.user_id == user_id, SysUserRole.del_flag == 0)) .all() ] - post_ids = [ - p.post_id - for p in db.query(SysUserPost) - .filter(and_(SysUserPost.user_id == user_id, SysUserPost.del_flag == 0)) - .all() - ] + post_ids = list_post_ids_for_user(db, user_id) d["roleIds"] = role_ids d["postIds"] = post_ids return d diff --git a/backend/app/crud/workflow.py b/backend/app/crud/workflow.py new file mode 100644 index 00000000..9fc3ffdf --- /dev/null +++ b/backend/app/crud/workflow.py @@ -0,0 +1,760 @@ +"""工作流 CRUD(wf_process_def / wf_process_version / wf_node / wf_transition / wf_process_instance / wf_task)""" +from __future__ import annotations + +import json +import math +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple + +from sqlalchemy import and_, func, or_ +from sqlalchemy.orm import Session + +from app.core.utils import generate_uuid +from app.models.models import ( + SysUser, + WfNode, + WfProcessDef, + WfProcessInstance, + WfProcessVersion, + WfTask, + WfTransition, +) + + +# --------------------------------------------------------------------------- +# 流程定义 wf_process_def +# --------------------------------------------------------------------------- + + +def _row_to_dict(t: WfProcessDef) -> Dict[str, Any]: + return { + "id": t.id, + "processCode": t.process_code, + "processName": t.process_name, + "status": t.status, + "tenantNo": t.tenant_no, + "orderNum": t.order_num if t.order_num is not None else 0, + "remark": t.remark or "", + "createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "", + "updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "", + } + + +def list_process_defs( + db: Session, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + status: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + def _filtered(): + q0 = db.query(WfProcessDef).filter(WfProcessDef.del_flag == 0) + if tenant_no: + q0 = q0.filter(WfProcessDef.tenant_no == tenant_no) + if status is not None: + q0 = q0.filter(WfProcessDef.status == status) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter(or_(WfProcessDef.process_name.like(like), WfProcessDef.process_code.like(like))) + return q0 + + total = _filtered().order_by(None).with_entities(func.count(WfProcessDef.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + _filtered() + .order_by(WfProcessDef.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + + result = [] + for r in rows: + d = _row_to_dict(r) + ver = ( + db.query(WfProcessVersion) + .filter( + and_( + WfProcessVersion.process_def_id == r.id, + WfProcessVersion.is_latest == "1", + WfProcessVersion.del_flag == 0, + ) + ) + .order_by(WfProcessVersion.version_no.desc()) + .first() + ) + d["latestVersion"] = ver.version_no if ver else None + result.append(d) + return result, int(total) + + +def get_process_def(db: Session, def_id: str) -> Optional[WfProcessDef]: + return ( + db.query(WfProcessDef) + .filter(and_(WfProcessDef.id == def_id, WfProcessDef.del_flag == 0)) + .first() + ) + + +def process_code_exists( + db: Session, code: str, tenant_no: str, exclude_id: Optional[str] = None +) -> bool: + q = db.query(WfProcessDef).filter( + and_(WfProcessDef.process_code == code.strip(), WfProcessDef.tenant_no == tenant_no, WfProcessDef.del_flag == 0) + ) + if exclude_id: + q = q.filter(WfProcessDef.id != exclude_id) + return q.first() is not None + + +def create_process_def( + db: Session, + *, + process_code: str, + process_name: str, + tenant_no: str, + status: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> WfProcessDef: + code = (process_code or "").strip() + if not code: + raise ValueError("流程编码不能为空") + if process_code_exists(db, code, tenant_no): + raise ValueError("流程编码已存在") + obj = WfProcessDef( + id=generate_uuid(), + process_code=code, + process_name=(process_name or "").strip(), + status=status or "1", + tenant_no=tenant_no, + order_num=order_num if order_num is not None else 0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_process_def( + db: Session, + def_id: str, + *, + process_code: Optional[str] = None, + process_name: Optional[str] = None, + status: Optional[str] = None, + order_num: Optional[int] = None, + remark: Optional[str] = None, + user_id: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> WfProcessDef: + obj = get_process_def(db, def_id) + if not obj: + raise ValueError("流程定义不存在") + if process_code is not None: + new_code = process_code.strip() + if not new_code: + raise ValueError("流程编码不能为空") + tn = tenant_no or obj.tenant_no + if new_code != obj.process_code and process_code_exists(db, new_code, tn, exclude_id=def_id): + raise ValueError("流程编码已存在") + obj.process_code = new_code + if process_name is not None: + obj.process_name = (process_name or "").strip() + if status is not None: + obj.status = status + if order_num is not None: + obj.order_num = order_num + if remark is not None: + obj.remark = (remark or "").strip() or None + obj.update_by = user_id + obj.update_time = datetime.now() + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def soft_delete_process_def(db: Session, def_id: str) -> None: + obj = get_process_def(db, def_id) + if not obj: + raise ValueError("流程定义不存在") + obj.del_flag = 1 + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +# --------------------------------------------------------------------------- +# 流程版本 wf_process_version +# --------------------------------------------------------------------------- + + +def publish_version( + db: Session, + process_def_id: str, + graph_json: str, + nodes: List[Dict[str, Any]], + transitions: List[Dict[str, Any]], + tenant_no: str, + remark: Optional[str] = None, + user_id: Optional[str] = None, +) -> WfProcessVersion: + defn = get_process_def(db, process_def_id) + if not defn: + raise ValueError("流程定义不存在") + + db.query(WfProcessVersion).filter( + and_( + WfProcessVersion.process_def_id == process_def_id, + WfProcessVersion.is_latest == "1", + WfProcessVersion.del_flag == 0, + ) + ).update({"is_latest": "0", "update_time": datetime.now()}, synchronize_session=False) + + max_ver = ( + db.query(func.max(WfProcessVersion.version_no)) + .filter(and_(WfProcessVersion.process_def_id == process_def_id, WfProcessVersion.del_flag == 0)) + .scalar() + ) or 0 + new_ver_no = max_ver + 1 + + ver = WfProcessVersion( + id=generate_uuid(), + process_def_id=process_def_id, + version_no=new_ver_no, + graph_json=graph_json, + is_latest="1", + tenant_no=tenant_no, + order_num=0, + remark=(remark or "").strip() or None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(ver) + db.flush() + + for idx, n in enumerate(nodes): + node = WfNode( + id=generate_uuid(), + process_version_id=ver.id, + node_key=n.get("nodeKey", ""), + node_type=n.get("nodeType", ""), + node_name=n.get("nodeName"), + config_json=n.get("configJson"), + tenant_no=tenant_no, + order_num=idx, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(node) + + for idx, t in enumerate(transitions): + trans = WfTransition( + id=generate_uuid(), + process_version_id=ver.id, + from_node_key=t.get("fromNodeKey", ""), + to_node_key=t.get("toNodeKey", ""), + condition_expr=t.get("conditionExpr"), + tenant_no=tenant_no, + order_num=idx, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(trans) + + db.commit() + db.refresh(ver) + return ver + + +def get_latest_version(db: Session, process_def_id: str) -> Optional[WfProcessVersion]: + return ( + db.query(WfProcessVersion) + .filter( + and_( + WfProcessVersion.process_def_id == process_def_id, + WfProcessVersion.is_latest == "1", + WfProcessVersion.del_flag == 0, + ) + ) + .first() + ) + + +def _version_to_dict(v: WfProcessVersion) -> Dict[str, Any]: + return { + "id": v.id, + "versionNo": v.version_no, + "isLatest": v.is_latest, + "remark": v.remark or "", + "createTime": v.create_time.strftime("%Y-%m-%d %H:%M:%S") if v.create_time else "", + "updateTime": v.update_time.strftime("%Y-%m-%d %H:%M:%S") if v.update_time else "", + } + + +def list_versions( + db: Session, process_def_id: str, tenant_no: Optional[str] = None +) -> List[Dict[str, Any]]: + q = db.query(WfProcessVersion).filter( + and_(WfProcessVersion.process_def_id == process_def_id, WfProcessVersion.del_flag == 0) + ) + if tenant_no: + q = q.filter(WfProcessVersion.tenant_no == tenant_no) + rows = q.order_by(WfProcessVersion.version_no.desc()).all() + return [_version_to_dict(v) for v in rows] + + +def get_nodes_by_version(db: Session, version_id: str) -> List[WfNode]: + return ( + db.query(WfNode) + .filter(and_(WfNode.process_version_id == version_id, WfNode.del_flag == 0)) + .order_by(WfNode.order_num.asc()) + .all() + ) + + +def get_transitions_by_version(db: Session, version_id: str) -> List[WfTransition]: + return ( + db.query(WfTransition) + .filter(and_(WfTransition.process_version_id == version_id, WfTransition.del_flag == 0)) + .order_by(WfTransition.order_num.asc()) + .all() + ) + + +# --------------------------------------------------------------------------- +# 流程实例 wf_process_instance +# --------------------------------------------------------------------------- + + +def create_instance( + db: Session, + *, + process_version_id: str, + business_type: Optional[str] = None, + business_id: Optional[str] = None, + starter_user_id: Optional[str] = None, + variables: Optional[Dict[str, Any]] = None, + status: Optional[str] = None, + callback_url: Optional[str] = None, + tenant_no: Optional[str] = None, + user_id: Optional[str] = None, +) -> WfProcessInstance: + obj = WfProcessInstance( + id=generate_uuid(), + process_version_id=process_version_id, + business_type=business_type, + business_id=business_id, + starter_user_id=starter_user_id, + variables_json=json.dumps(variables, ensure_ascii=False) if variables else None, + status=status or "running", + callback_url=callback_url, + tenant_no=tenant_no or "zkxw", + order_num=0, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def get_instance(db: Session, instance_id: str) -> Optional[WfProcessInstance]: + return ( + db.query(WfProcessInstance) + .filter(and_(WfProcessInstance.id == instance_id, WfProcessInstance.del_flag == 0)) + .first() + ) + + +def update_instance_status(db: Session, instance_id: str, status: str) -> None: + obj = get_instance(db, instance_id) + if not obj: + raise ValueError("流程实例不存在") + obj.status = status + obj.update_time = datetime.now() + db.add(obj) + db.commit() + + +def list_my_instances( + db: Session, + user_id: str, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + status: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + from sqlalchemy import func as sa_func + + latest_per_biz = ( + db.query( + WfProcessInstance.business_id, + sa_func.max(WfProcessInstance.create_time).label("max_ct"), + ) + .filter( + and_( + WfProcessInstance.starter_user_id == user_id, + WfProcessInstance.del_flag == 0, + WfProcessInstance.business_id != None, + WfProcessInstance.business_id != "", + ) + ) + .group_by(WfProcessInstance.business_id) + .subquery() + ) + + q0 = ( + db.query(WfProcessInstance, WfProcessVersion, WfProcessDef) + .join(WfProcessVersion, WfProcessInstance.process_version_id == WfProcessVersion.id) + .join(WfProcessDef, WfProcessVersion.process_def_id == WfProcessDef.id) + .join( + latest_per_biz, + and_( + WfProcessInstance.business_id == latest_per_biz.c.business_id, + WfProcessInstance.create_time == latest_per_biz.c.max_ct, + ), + ) + .filter( + and_( + WfProcessInstance.starter_user_id == user_id, + WfProcessInstance.del_flag == 0, + WfProcessVersion.del_flag == 0, + WfProcessDef.del_flag == 0, + ) + ) + ) + if tenant_no: + q0 = q0.filter(WfProcessInstance.tenant_no == tenant_no) + if status: + q0 = q0.filter(WfProcessInstance.status == status) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter( + or_(WfProcessDef.process_name.like(like), WfProcessInstance.business_id.like(like)) + ) + + total = q0.order_by(None).with_entities(func.count(WfProcessInstance.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + q0.order_by(WfProcessInstance.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + result = [] + for inst, ver, defn in rows: + result.append({ + "id": inst.id, + "processName": defn.process_name, + "businessType": inst.business_type, + "businessId": inst.business_id, + "starterUserId": inst.starter_user_id, + "status": inst.status, + "createTime": inst.create_time.strftime("%Y-%m-%d %H:%M:%S") if inst.create_time else "", + "updateTime": inst.update_time.strftime("%Y-%m-%d %H:%M:%S") if inst.update_time else "", + }) + return result, int(total) + + +def get_all_tasks_for_business( + db: Session, + business_type: str, + business_id: str, +) -> List[Dict[str, Any]]: + instances = ( + db.query(WfProcessInstance) + .filter( + and_( + WfProcessInstance.business_type == business_type, + WfProcessInstance.business_id == business_id, + WfProcessInstance.del_flag == 0, + ) + ) + .order_by(WfProcessInstance.create_time.asc()) + .all() + ) + + all_tasks: List[Dict[str, Any]] = [] + for inst in instances: + node_name_map: Dict[str, str] = {} + if inst.process_version_id: + for n in get_nodes_by_version(db, inst.process_version_id): + node_name_map[n.node_key] = n.node_name or n.node_key + + tasks = ( + db.query(WfTask) + .filter(and_(WfTask.instance_id == inst.id, WfTask.del_flag == 0)) + .order_by(WfTask.create_time.asc()) + .all() + ) + + user_ids = {t.assignee_user_id for t in tasks if t.assignee_user_id} + user_name_map: Dict[str, str] = {} + if user_ids: + rows = db.query(SysUser.id, SysUser.nick_name).filter(SysUser.id.in_(user_ids)).all() + user_name_map = {r.id: r.nick_name for r in rows} + + for t in tasks: + all_tasks.append({ + "id": t.id, + "instanceId": inst.id, + "instanceStatus": inst.status, + "nodeKey": t.node_key, + "nodeName": node_name_map.get(t.node_key, t.node_key or ""), + "assigneeUserId": t.assignee_user_id, + "assigneeUserName": user_name_map.get(t.assignee_user_id, t.assignee_user_id or ""), + "status": t.status, + "commentText": t.comment_text, + "createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "", + "updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "", + }) + + return all_tasks + + +# --------------------------------------------------------------------------- +# 流程任务 wf_task +# --------------------------------------------------------------------------- + + +def create_task( + db: Session, + *, + instance_id: str, + node_key: Optional[str] = None, + assignee_user_id: Optional[str] = None, + tenant_no: Optional[str] = None, + user_id: Optional[str] = None, +) -> WfTask: + obj = WfTask( + id=generate_uuid(), + instance_id=instance_id, + node_key=node_key, + assignee_user_id=assignee_user_id, + status="pending", + tenant_no=tenant_no or "zkxw", + order_num=0, + remark=None, + create_by=user_id, + create_dept_id="", + create_time=datetime.now(), + update_by=user_id, + update_time=datetime.now(), + del_flag=0, + ) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def get_task(db: Session, task_id: str) -> Optional[WfTask]: + return ( + db.query(WfTask) + .filter(and_(WfTask.id == task_id, WfTask.del_flag == 0)) + .first() + ) + + +def get_pending_tasks_by_node(db: Session, instance_id: str, node_key: str) -> List[WfTask]: + return ( + db.query(WfTask) + .filter( + and_( + WfTask.instance_id == instance_id, + WfTask.node_key == node_key, + WfTask.status == "pending", + WfTask.del_flag == 0, + ) + ) + .all() + ) + + +def _task_row_to_dict( + t: WfTask, + process_name: Optional[str] = None, + business_type: Optional[str] = None, + business_id: Optional[str] = None, + starter_user_id: Optional[str] = None, + instance_status: Optional[str] = None, + instance_create_time: Optional[str] = None, +) -> Dict[str, Any]: + return { + "id": t.id, + "instanceId": t.instance_id, + "nodeKey": t.node_key, + "assigneeUserId": t.assignee_user_id, + "status": t.status, + "commentText": t.comment_text, + "createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "", + "updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "", + "processName": process_name, + "businessType": business_type, + "businessId": business_id, + "starterUserId": starter_user_id, + "instanceStatus": instance_status, + "instanceCreateTime": instance_create_time, + } + + +def list_pending_tasks( + db: Session, + user_id: str, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + q0 = ( + db.query(WfTask, WfProcessInstance, WfProcessVersion, WfProcessDef) + .join(WfProcessInstance, WfTask.instance_id == WfProcessInstance.id) + .join(WfProcessVersion, WfProcessInstance.process_version_id == WfProcessVersion.id) + .join(WfProcessDef, WfProcessVersion.process_def_id == WfProcessDef.id) + .filter( + and_( + WfTask.assignee_user_id == user_id, + WfTask.status == "pending", + WfTask.del_flag == 0, + WfProcessInstance.del_flag == 0, + WfProcessVersion.del_flag == 0, + WfProcessDef.del_flag == 0, + ) + ) + ) + if tenant_no: + q0 = q0.filter(WfTask.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter( + or_(WfProcessDef.process_name.like(like), WfProcessDef.process_code.like(like)) + ) + + total = q0.order_by(None).with_entities(func.count(WfTask.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + q0.order_by(WfTask.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + result = [] + for task, inst, ver, defn in rows: + result.append( + _task_row_to_dict( + task, + process_name=defn.process_name, + business_type=inst.business_type, + business_id=inst.business_id, + starter_user_id=inst.starter_user_id, + instance_status=inst.status, + instance_create_time=inst.create_time.strftime("%Y-%m-%d %H:%M:%S") if inst.create_time else "", + ) + ) + return result, int(total) + + +def list_completed_tasks( + db: Session, + user_id: str, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + tenant_no: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + q0 = ( + db.query(WfTask, WfProcessInstance, WfProcessVersion, WfProcessDef) + .join(WfProcessInstance, WfTask.instance_id == WfProcessInstance.id) + .join(WfProcessVersion, WfProcessInstance.process_version_id == WfProcessVersion.id) + .join(WfProcessDef, WfProcessVersion.process_def_id == WfProcessDef.id) + .filter( + and_( + WfTask.assignee_user_id == user_id, + WfTask.status.in_(["approved", "rejected"]), + WfTask.del_flag == 0, + WfProcessInstance.del_flag == 0, + WfProcessVersion.del_flag == 0, + WfProcessDef.del_flag == 0, + ) + ) + ) + if tenant_no: + q0 = q0.filter(WfTask.tenant_no == tenant_no) + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter( + or_(WfProcessDef.process_name.like(like), WfProcessDef.process_code.like(like)) + ) + + total = q0.order_by(None).with_entities(func.count(WfTask.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + q0.order_by(WfTask.update_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + result = [] + for task, inst, ver, defn in rows: + result.append( + _task_row_to_dict( + task, + process_name=defn.process_name, + business_type=inst.business_type, + business_id=inst.business_id, + starter_user_id=inst.starter_user_id, + instance_status=inst.status, + instance_create_time=inst.create_time.strftime("%Y-%m-%d %H:%M:%S") if inst.create_time else "", + ) + ) + return result, int(total) + + +def total_pages(total: int, page_size: int) -> int: + if total <= 0: + return 0 + return int(math.ceil(total / float(page_size))) diff --git a/backend/app/main.py b/backend/app/main.py index cec49f25..895c43ce 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -10,7 +10,7 @@ from app.core.database import SessionLocal from app.core.api_request_log_middleware import ApiRequestLogMiddleware from app.core.api_crypto_middleware import ApiCryptoMiddleware from app.models import models # noqa: F401 -from app.api import public_router, menu_router, admin_router +from app.api import public_router, menu_router, admin_router, workflow_router, basic_info_router, collection_protocol_router, leave_router, operations_router, ethnic_router @asynccontextmanager @@ -61,6 +61,12 @@ app.add_middleware(ApiCryptoMiddleware) app.include_router(public_router, prefix=settings.API_V1_PREFIX) app.include_router(menu_router, prefix=settings.API_V1_PREFIX) app.include_router(admin_router, prefix=settings.API_V1_PREFIX) +app.include_router(workflow_router, prefix=settings.API_V1_PREFIX) +app.include_router(basic_info_router, prefix=settings.API_V1_PREFIX) +app.include_router(collection_protocol_router, prefix=settings.API_V1_PREFIX) +app.include_router(leave_router, prefix=settings.API_V1_PREFIX) +app.include_router(operations_router, prefix=settings.API_V1_PREFIX) +app.include_router(ethnic_router, prefix=settings.API_V1_PREFIX) logger = logging.getLogger("sqxp") diff --git a/backend/app/models/models.py b/backend/app/models/models.py index 79e974dc..a1cc113d 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -1,8 +1,8 @@ -"""数据模型(与 docs/sql/zkxw-requirements-v0.3-schema.sql 对齐的核心系统表)""" +"""数据模型(与 docs/sql/zkxw.sql 全库导出基线对齐的核心系统表)""" from datetime import datetime import enum -from sqlalchemy import Column, String, DateTime, Integer, Text, CHAR, SmallInteger +from sqlalchemy import Column, String, DateTime, Integer, Text, CHAR, SmallInteger, Numeric from app.core.database import Base @@ -294,6 +294,132 @@ class BizExpressCompany(Base): del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") +class BizCustomer(Base): + """客户 biz_customer""" + + __tablename__ = "biz_customer" + + id = Column(String(64), primary_key=True, comment="主键ID") + customer_name = Column(String(100), nullable=False, comment="客户姓名") + sex = Column(CHAR(1), nullable=True, default="2", comment="性别(0男1女2未知)") + id_card_no = Column(String(32), nullable=True, comment="身份证号") + age = Column(Integer, nullable=True, comment="年龄") + phone_no = Column(String(32), nullable=True, comment="联系电话") + customer_tags = Column(String(512), nullable=True, comment="客户标签,逗号分隔") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizCellSampleType(Base): + """细胞样本类型 biz_cell_sample_type""" + + __tablename__ = "biz_cell_sample_type" + + id = Column(String(64), primary_key=True, comment="主键ID") + type_code = Column(String(64), nullable=False, comment="样本类型编码") + type_name = Column(String(200), nullable=False, comment="样本类型名称") + storage_years = Column(Integer, nullable=True, comment="样本存储年份") + storage_price = Column(Numeric(18, 2), nullable=True, comment="样本存储价格(元)") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizCollectionRegion(Base): + """采集地区 biz_collection_region""" + + __tablename__ = "biz_collection_region" + + id = Column(String(64), primary_key=True, comment="主键ID") + region_name = Column(String(200), nullable=False, comment="地区名称") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizCollectionHospital(Base): + """采集地区下属医院 biz_collection_hospital""" + + __tablename__ = "biz_collection_hospital" + + id = Column(String(64), primary_key=True, comment="主键ID") + region_id = Column(String(64), nullable=False, comment="采集地区ID") + hospital_name = Column(String(200), nullable=False, comment="医院名称") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizCollectionProtocolNo(Base): + """采集协议号池 biz_collection_protocol_no""" + + __tablename__ = "biz_collection_protocol_no" + + id = Column(String(64), primary_key=True, comment="主键ID") + protocol_no = Column(String(32), nullable=False, comment="采集协议号") + issued = Column(SmallInteger, nullable=False, default=0, comment="是否下发(1是0否)") + issued_user_id = Column(String(64), nullable=True, comment="下发用户ID") + issued_user_name = Column(String(100), nullable=True, comment="下发用户姓名(冗余)") + issued_time = Column(DateTime, nullable=True, comment="下发时间") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizSampleCode(Base): + """样本编码池 biz_sample_code""" + + __tablename__ = "biz_sample_code" + + id = Column(String(64), primary_key=True, comment="主键ID") + sample_code = Column(String(32), nullable=False, comment="样本编码") + sample_code_type = Column(String(16), nullable=True, comment="样本编码类型 zxbm造血编码 qdbm脐带编码") + issued = Column(SmallInteger, nullable=False, default=0, comment="是否下发(1是0否)") + issued_user_id = Column(String(64), nullable=True, comment="下发用户ID") + issued_user_name = Column(String(100), nullable=True, comment="下发用户姓名(冗余)") + issued_time = Column(DateTime, nullable=True, comment="下发时间") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + # ============================================================================= # 字典管理(sys_dict_type / sys_dict_data) # ============================================================================= @@ -318,6 +444,25 @@ class SysDictType(Base): del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") +class SysEthnic(Base): + """民族码表 sys_ethnic(国标 GB/T 3304,全局)""" + + __tablename__ = "sys_ethnic" + + id = Column(String(64), primary_key=True, comment="主键ID") + ethnic_name = Column(String(64), nullable=False, default="", comment="民族名称") + ethnic_code = Column(String(16), nullable=False, default="", comment="民族编码") + tenant_no = Column(String(64), nullable=False, default="", comment="租户编号(空=全局)") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + class SysDictData(Base): """字典数据 sys_dict_data""" @@ -393,3 +538,555 @@ class SysApiRequestLogHis(Base): update_at = Column(DateTime, nullable=True, comment="修改时间") update_by = Column(String(32), nullable=True, comment="修改人ID") his_create_at = Column(DateTime, default=datetime.now, comment="历史记录创建时间") + + +# ============================================================================= +# 业务表(biz_leave_request) +# ============================================================================= + + +class BizLeaveRequest(Base): + """请假申请 biz_leave_request""" + + __tablename__ = "biz_leave_request" + + id = Column(String(64), primary_key=True, comment="主键ID") + leave_type = Column(String(32), nullable=False, default="personal", comment="请假类型: personal/sick/annual/other") + start_date = Column(String(20), nullable=False, comment="开始日期") + end_date = Column(String(20), nullable=False, comment="结束日期") + days = Column(Integer, nullable=False, default=1, comment="请假天数") + reason = Column(String(500), nullable=True, comment="请假原因") + wf_instance_id = Column(String(64), nullable=True, comment="流程实例ID") + status = Column(String(32), nullable=False, default="draft", comment="状态: draft/pending/approved/rejected/cancelled") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除") + + +# ============================================================================= +# 工作流(wf_process_def / wf_process_version / wf_node / wf_transition / wf_process_instance / wf_task) +# ============================================================================= + + +class WfProcessDef(Base): + """流程定义 wf_process_def""" + + __tablename__ = "wf_process_def" + + id = Column(String(64), primary_key=True, comment="主键ID") + process_code = Column(String(64), nullable=False, comment="流程编码") + process_name = Column(String(200), nullable=False, comment="流程名称") + status = Column(CHAR(1), nullable=False, default="1", comment="1启用 0停用") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除") + + +class WfProcessVersion(Base): + """流程定义版本 wf_process_version""" + + __tablename__ = "wf_process_version" + + id = Column(String(64), primary_key=True, comment="主键ID") + process_def_id = Column(String(64), nullable=False, comment="流程定义ID") + version_no = Column(Integer, nullable=False, comment="版本号,递增") + graph_json = Column(Text, nullable=True, comment="节点与连线等配置(JSON 字符串)") + is_latest = Column(CHAR(1), nullable=True, default="0", comment="是否最新版本(1是 0否)") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除") + + +class WfNode(Base): + """流程节点 wf_node""" + + __tablename__ = "wf_node" + + id = Column(String(64), primary_key=True, comment="主键ID") + process_version_id = Column(String(64), nullable=False, comment="流程版本ID") + node_key = Column(String(64), nullable=False, comment="节点唯一键(画布内)") + node_type = Column(String(32), nullable=False, comment="start/end/approval/condition 等") + node_name = Column(String(200), nullable=True, comment="节点名称") + config_json = Column(Text, nullable=True, comment="审批人/SQL/会签策略等配置") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除") + + +class WfTransition(Base): + """流程连线/转移 wf_transition""" + + __tablename__ = "wf_transition" + + id = Column(String(64), primary_key=True, comment="主键ID") + process_version_id = Column(String(64), nullable=False, comment="流程版本ID") + from_node_key = Column(String(64), nullable=False, comment="起始节点key") + to_node_key = Column(String(64), nullable=False, comment="目标节点key") + condition_expr = Column(String(512), nullable=True, comment="条件表达式或分支说明") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除") + + +class WfProcessInstance(Base): + """流程实例 wf_process_instance""" + + __tablename__ = "wf_process_instance" + + id = Column(String(64), primary_key=True, comment="主键ID") + process_version_id = Column(String(64), nullable=False, comment="发起时锁定的流程版本ID") + business_type = Column(String(64), nullable=True, comment="业务类型") + business_id = Column(String(64), nullable=True, comment="业务主键") + starter_user_id = Column(String(64), nullable=True, comment="发起人") + variables_json = Column(Text, nullable=True, comment="流程变量 JSON") + status = Column(String(32), nullable=True, comment="running/completed/rejected 等") + callback_url = Column(String(512), nullable=True, comment="业务回调地址") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除") + + +class WfTask(Base): + """流程任务/待办 wf_task""" + + __tablename__ = "wf_task" + + id = Column(String(64), primary_key=True, comment="主键ID") + instance_id = Column(String(64), nullable=False, comment="流程实例ID") + node_key = Column(String(64), nullable=True, comment="当前节点key") + assignee_user_id = Column(String(64), nullable=True, comment="待办人") + status = Column(String(32), nullable=True, comment="pending/approved/rejected 等") + comment_text = Column(String(512), nullable=True, comment="审批意见") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除") + + +# ============================================================================= +# 细胞存储 — 采集协议单(biz_collection_protocol / audit / payment) +# ============================================================================= + + +class BizCollectionProtocol(Base): + """采集协议单 biz_collection_protocol""" + + __tablename__ = "biz_collection_protocol" + + id = Column(String(64), primary_key=True, comment="主键ID") + protocol_no_id = Column(String(64), nullable=True, comment="采集协议号表ID") + protocol_no = Column(String(32), nullable=False, comment="采集协议号展示") + protocol_status = Column(String(16), nullable=False, default="dsx", comment="协议状态") + protocol_attachments = Column(String(4000), nullable=True, comment="协议附件") + register_date = Column(CHAR(8), nullable=True, comment="登记日期YYYYMMDD") + effective_time = Column(DateTime, nullable=True, comment="协议生效时间") + effective_desc = Column(String(512), nullable=True, comment="协议生效描述") + expire_time = Column(DateTime, nullable=True, comment="协议过期时间") + expire_desc = Column(String(512), nullable=True, comment="协议过期描述") + void_time = Column(DateTime, nullable=True, comment="协议作废时间") + void_desc = Column(String(512), nullable=True, comment="协议作废描述") + to_research_time = Column(DateTime, nullable=True, comment="协议转科研时间") + to_research_desc = Column(String(512), nullable=True, comment="协议转科研描述") + collector_user_id = Column(String(64), nullable=True, comment="采集专员用户ID") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注信息") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolAudit(Base): + """采集协议单-审核记录 biz_protocol_audit""" + + __tablename__ = "biz_protocol_audit" + + id = Column(String(64), primary_key=True, comment="主键ID") + collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID") + auditor_user_id = Column(String(64), nullable=True, comment="审核人") + audit_remark = Column(String(1000), nullable=True, comment="审核备注") + audit_status = Column(String(16), nullable=False, comment="审核状态 shtg/shth") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolPayment(Base): + """采集协议单-缴费主表 biz_protocol_payment""" + + __tablename__ = "biz_protocol_payment" + + id = Column(String(64), primary_key=True, comment="主键ID") + collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID") + pay_mode = Column(String(8), nullable=False, comment="dj趸交 fq分期") + contract_amount = Column(Numeric(18, 2), nullable=True, comment="合同金额") + installment_count = Column(Integer, nullable=True, comment="分期期数") + installment_interval_months = Column(Integer, nullable=True, comment="每期间隔(月)") + lump_sum_deadline = Column(CHAR(8), nullable=True, comment="趸交截止时间YYYYMMDD") + installment_start_date = Column(CHAR(8), nullable=True, comment="分期开始YYYYMMDD") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolPaymentDetail(Base): + """采集协议单-缴费明细 biz_protocol_payment_detail""" + + __tablename__ = "biz_protocol_payment_detail" + + id = Column(String(64), primary_key=True, comment="主键ID") + payment_id = Column(String(64), nullable=False, comment="缴费主表ID") + transfer_type = Column(String(16), nullable=True, comment="转账方式") + due_amount = Column(Numeric(18, 2), nullable=True, comment="应缴费金额") + transfer_amount = Column(Numeric(18, 2), nullable=True, comment="转账金额") + transfer_time = Column(DateTime, nullable=True, comment="转账时间") + transfer_attachments = Column(String(4000), nullable=True, comment="转账附件") + finance_confirm_attachments = Column(String(4000), nullable=True, comment="财务确认附件") + pay_status = Column(String(32), nullable=False, default="dfj", comment="缴费状态") + installment_no = Column(Integer, nullable=True, comment="分期期序号") + installment_due_date = Column(CHAR(8), nullable=True, comment="分期应缴日期YYYYMMDD") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolPartyCustomer(Base): + """采集协议单-当事人客户信息 biz_protocol_party_customer""" + + __tablename__ = "biz_protocol_party_customer" + + id = Column(String(64), primary_key=True, comment="主键ID") + collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID") + customer_id = Column(String(64), nullable=True, comment="客户ID") + mother_name = Column(String(100), nullable=True, comment="母亲姓名") + mother_phone = Column(String(32), nullable=True, comment="母亲电话") + mother_id_card = Column(String(32), nullable=True, comment="母亲身份证号") + mother_birth_ym = Column(CHAR(6), nullable=True, comment="母亲出生年月YYYYMM") + mother_ethnic = Column(String(32), nullable=True, comment="母亲民族") + mother_address = Column(String(512), nullable=True, comment="母亲地址") + father_name = Column(String(100), nullable=True, comment="父亲姓名") + father_phone = Column(String(32), nullable=True, comment="父亲电话") + father_id_card = Column(String(32), nullable=True, comment="父亲身份证号") + newborn_name = Column(String(100), nullable=True, comment="新生儿姓名") + newborn_id_card = Column(String(32), nullable=True, comment="新生儿身份证号") + newborn_delivery_time = Column(DateTime, nullable=True, comment="新生儿分娩时间") + newborn_sex = Column(CHAR(1), nullable=True, comment="新生儿性别") + delivery_mode = Column(String(16), nullable=True, comment="分娩方式") + birth_order = Column(Integer, nullable=True, comment="出生顺序") + newborn_weight_gram = Column(Integer, nullable=True, comment="新生儿体重(克)") + apgar_1min = Column(Numeric(4, 1), nullable=True, comment="Apgar 1分钟评分") + apgar_5min = Column(Numeric(4, 1), nullable=True, comment="Apgar 5分钟评分") + collector_user_id = Column(String(64), nullable=True, comment="采集专员用户ID") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolCollectionInfo(Base): + """采集协议单-采集信息 biz_protocol_collection_info""" + + __tablename__ = "biz_protocol_collection_info" + + id = Column(String(64), primary_key=True, comment="主键ID") + collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID") + region_id = Column(String(64), nullable=True, comment="区域ID") + hospital_id = Column(String(64), nullable=True, comment="医院ID") + medical_staff = Column(String(200), nullable=True, comment="医护人员") + sales_user_id = Column(String(64), nullable=True, comment="销售人员用户ID") + sample_collect_time = Column(DateTime, nullable=True, comment="样本采集时间") + maternal_blood_collect_time = Column(DateTime, nullable=True, comment="母血采集时间") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolSample(Base): + """采集协议单-样本信息 biz_protocol_sample""" + + __tablename__ = "biz_protocol_sample" + + id = Column(String(64), primary_key=True, comment="主键ID") + collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID") + sample_type_id = Column(String(64), nullable=True, comment="样本类型ID") + sample_code_id = Column(String(64), nullable=True, comment="样本编码ID") + sample_code = Column(String(32), nullable=True, comment="样本编码") + collect_amount = Column(Numeric(18, 4), nullable=True, comment="采集量") + unit_id = Column(String(64), nullable=True, comment="单位ID") + price = Column(Numeric(18, 2), nullable=True, comment="价格") + storage_years = Column(Integer, nullable=True, comment="存储年限") + sample_status = Column(String(16), nullable=True, comment="样本状态") + storage_in_date = Column(CHAR(8), nullable=True, comment="入库日期YYYYMMDD") + detect_start_date = Column(CHAR(8), nullable=True, comment="检测开始日期YYYYMMDD") + void_date = Column(CHAR(8), nullable=True, comment="作废日期YYYYMMDD") + storage_expire_date = Column(CHAR(8), nullable=True, comment="存储到期日期YYYYMMDD") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolSampleTransport(Base): + """采集协议单-样本运输 biz_protocol_sample_transport""" + + __tablename__ = "biz_protocol_sample_transport" + + id = Column(String(64), primary_key=True, comment="主键ID") + collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID") + sample_id = Column(String(64), nullable=False, comment="样本ID") + transport_type = Column(String(16), nullable=False, comment="kdys快递运输 ryxd人员携带") + express_company_id = Column(String(64), nullable=True, comment="快递公司ID") + waybill_no = Column(String(128), nullable=True, comment="快递单号") + dest_site_id = Column(String(64), nullable=True, comment="目的站点ID") + express_fee = Column(Numeric(18, 2), nullable=True, comment="快递费用(元)") + eta_time = Column(DateTime, nullable=True, comment="预计到达时间") + carrier_name = Column(String(100), nullable=True, comment="携带人员姓名") + carrier_phone = Column(String(32), nullable=True, comment="携带人员联系电话") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizAttachment(Base): + """业务附件 biz_attachment""" + + __tablename__ = "biz_attachment" + + id = Column(String(64), primary_key=True, comment="主键ID") + business_type = Column(String(50), nullable=True, comment="业务类型") + business_id = Column(String(64), nullable=True, comment="业务主键ID") + original_name = Column(String(200), nullable=False, comment="原始文件名") + file_ext = Column(String(20), nullable=True, comment="文件后缀名") + file_url = Column(String(500), nullable=False, comment="MinIO路径后缀") + file_size = Column(Integer, nullable=True, comment="文件大小(字节)") + file_type = Column(String(50), nullable=True, comment="MIME类型") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizProtocolStorageCert(Base): + """采集协议单-储存证书 biz_protocol_storage_cert""" + + __tablename__ = "biz_protocol_storage_cert" + + id = Column(String(64), primary_key=True, comment="主键ID") + collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID") + make_status = Column(CHAR(1), nullable=True, default="0", comment="制作状态 0-未制作 1-已制作") + cert_image_urls = Column(String(4000), nullable=True, comment="证书图片URLs") + make_date = Column(CHAR(8), nullable=True, comment="制作日期YYYYMMDD") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +# ============================================================================= +# V0.4 运营支撑(2.4 洁净室管理 / 库位管理 / 展馆预约) +# ============================================================================= + + +class BizCleanRoom(Base): + """洁净室 biz_clean_room""" + + __tablename__ = "biz_clean_room" + + id = Column(String(64), primary_key=True, comment="主键ID") + room_code = Column(String(64), nullable=False, comment="编号") + room_name = Column(String(200), nullable=False, comment="名称") + room_grade = Column(String(8), nullable=True, comment="等级 A/B/C/D") + room_status = Column(String(16), nullable=False, default="zc", comment="zc正常 yc异常") + area_sqm = Column(Numeric(12, 2), nullable=True, comment="面积m²") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizCleanRoomAccessLog(Base): + """洁净室出入记录 biz_clean_room_access_log""" + + __tablename__ = "biz_clean_room_access_log" + + id = Column(String(64), primary_key=True, comment="主键ID") + clean_room_id = Column(String(64), nullable=False, comment="洁净室ID") + user_id = Column(String(64), nullable=True, comment="人员用户ID") + person_name = Column(String(100), nullable=True, comment="人员姓名(冗余)") + dept_name = Column(String(200), nullable=True, comment="部门") + enter_time = Column(DateTime, nullable=False, comment="进入时间") + purpose = Column(String(512), nullable=True, comment="目的") + leave_time = Column(DateTime, nullable=True, comment="离开时间") + body_temp = Column(String(32), nullable=True, comment="体温") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizStorageFacility(Base): + """存储设施树 biz_storage_facility""" + + __tablename__ = "biz_storage_facility" + + id = Column(String(64), primary_key=True, comment="主键ID") + parent_id = Column(String(64), nullable=False, default="0", comment="父节点ID,0根") + node_name = Column(String(200), nullable=False, comment="节点名称(罐/层/盒等)") + node_type = Column(String(32), nullable=True, comment="facility/layer/box 等扩展") + is_leaf = Column(SmallInteger, nullable=False, default=0, comment="是否叶子存储位 1是") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizStorageFacilitySlot(Base): + """存储设施小格 biz_storage_facility_slot""" + + __tablename__ = "biz_storage_facility_slot" + + id = Column(String(64), primary_key=True, comment="主键ID") + facility_node_id = Column(String(64), nullable=False, comment="设施树叶子节点ID") + slot_code = Column(String(64), nullable=True, comment="小格编码/坐标") + slot_status = Column(String(16), nullable=True, comment="占用状态等扩展") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") + + +class BizExhibitionAppointment(Base): + """展馆预约 biz_exhibition_appointment""" + + __tablename__ = "biz_exhibition_appointment" + + id = Column(String(64), primary_key=True, comment="主键ID") + appointment_no = Column(String(64), nullable=False, comment="预约编号") + visit_org = Column(String(200), nullable=True, comment="来访单位") + contact_name = Column(String(100), nullable=True, comment="联系人") + visit_date = Column(DateTime, nullable=True, comment="到访日期") + visitor_count = Column(Integer, nullable=True, comment="人数") + reception_user_id = Column(String(64), nullable=True, comment="接待人员用户ID") + tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号") + order_num = Column(Integer, nullable=True, comment="排序号") + remark = Column(String(512), nullable=True, comment="备注") + create_by = Column(String(64), nullable=True, comment="创建人id") + create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID") + create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间") + update_by = Column(String(64), nullable=True, comment="更新人id") + update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间") + del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记") diff --git a/backend/app/schemas/attachment.py b/backend/app/schemas/attachment.py new file mode 100644 index 00000000..86ae7c38 --- /dev/null +++ b/backend/app/schemas/attachment.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel, ConfigDict +from typing import Optional + + +class AttachmentUploadResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: str + fileName: str + fileUrl: str + fileSize: Optional[int] = None + fileSizeDisplay: Optional[str] = None + fileType: Optional[str] = None diff --git a/backend/app/schemas/leave.py b/backend/app/schemas/leave.py new file mode 100644 index 00000000..9243366b --- /dev/null +++ b/backend/app/schemas/leave.py @@ -0,0 +1,40 @@ +"""Pydantic schemas - 请假申请""" +from typing import Optional + +from pydantic import BaseModel, Field + + +class LeaveCreate(BaseModel): + leaveType: str = Field(..., description="请假类型: personal/sick/annual/other") + startDate: str = Field(..., description="开始日期") + endDate: str = Field(..., description="结束日期") + days: int = Field(..., description="请假天数") + reason: Optional[str] = Field(None, description="请假原因") + remark: Optional[str] = Field(None, description="备注") + + +class LeaveUpdate(BaseModel): + leaveType: Optional[str] = Field(None, description="请假类型") + startDate: Optional[str] = Field(None, description="开始日期") + endDate: Optional[str] = Field(None, description="结束日期") + days: Optional[int] = Field(None, description="请假天数") + reason: Optional[str] = Field(None, description="请假原因") + remark: Optional[str] = Field(None, description="备注") + + +class LeaveResponse(BaseModel): + id: str + leaveType: Optional[str] = None + startDate: Optional[str] = None + endDate: Optional[str] = None + days: Optional[int] = None + reason: Optional[str] = None + wfInstanceId: Optional[str] = None + status: Optional[str] = None + createBy: Optional[str] = None + createTime: Optional[str] = None + updateBy: Optional[str] = None + updateTime: Optional[str] = None + + class Config: + from_attributes = True diff --git a/backend/app/schemas/workflow.py b/backend/app/schemas/workflow.py new file mode 100644 index 00000000..f41a7f6c --- /dev/null +++ b/backend/app/schemas/workflow.py @@ -0,0 +1,115 @@ +"""Pydantic schemas - 工作流""" +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel, Field + + +class ProcessDefCreate(BaseModel): + processCode: str = Field(..., description="流程编码") + processName: str = Field(..., description="流程名称") + status: Optional[str] = Field("1", description="1启用 0停用") + orderNum: Optional[int] = Field(None, description="排序号") + remark: Optional[str] = Field(None, description="备注") + + +class ProcessDefUpdate(BaseModel): + processCode: Optional[str] = Field(None, description="流程编码") + processName: Optional[str] = Field(None, description="流程名称") + status: Optional[str] = Field(None, description="1启用 0停用") + orderNum: Optional[int] = Field(None, description="排序号") + remark: Optional[str] = Field(None, description="备注") + + +class ProcessDefResponse(BaseModel): + id: str + processCode: str + processName: str + status: str + latestVersion: Optional[int] = None + tenantNo: Optional[str] = None + orderNum: Optional[int] = None + remark: Optional[str] = None + createTime: Optional[str] = None + updateTime: Optional[str] = None + + class Config: + from_attributes = True + + +class NodeData(BaseModel): + nodeKey: str = Field(..., description="节点唯一键") + nodeType: str = Field(..., description="start/end/approval/condition 等") + nodeName: Optional[str] = Field(None, description="节点名称") + configJson: Optional[str] = Field(None, description="审批人/SQL/会签策略等配置") + + +class TransitionData(BaseModel): + fromNodeKey: str = Field(..., description="起始节点key") + toNodeKey: str = Field(..., description="目标节点key") + conditionExpr: Optional[str] = Field(None, description="条件表达式或分支说明") + + +class PublishVersionRequest(BaseModel): + graphJson: str = Field(..., description="节点与连线等配置JSON") + nodes: List[NodeData] = Field(..., description="节点列表") + transitions: List[TransitionData] = Field(default_factory=list, description="连线列表") + remark: Optional[str] = Field(None, description="备注") + + +class VersionResponse(BaseModel): + id: str + versionNo: int + isLatest: Optional[str] = None + remark: Optional[str] = None + createTime: Optional[str] = None + updateTime: Optional[str] = None + + class Config: + from_attributes = True + + +class StartInstanceRequest(BaseModel): + processCode: str = Field(..., description="流程编码") + businessType: Optional[str] = Field(None, description="业务类型") + businessId: Optional[str] = Field(None, description="业务主键") + variables: Optional[Dict[str, Any]] = Field(default_factory=dict, description="流程变量") + callbackUrl: Optional[str] = Field(None, description="业务回调地址") + + +class ApproveRequest(BaseModel): + commentText: Optional[str] = Field(None, description="审批意见") + + +class TaskResponse(BaseModel): + id: str + instanceId: str + nodeKey: Optional[str] = None + assigneeUserId: Optional[str] = None + status: Optional[str] = None + commentText: Optional[str] = None + createTime: Optional[str] = None + updateTime: Optional[str] = None + processName: Optional[str] = None + businessType: Optional[str] = None + businessId: Optional[str] = None + starterUserId: Optional[str] = None + instanceStatus: Optional[str] = None + instanceCreateTime: Optional[str] = None + + class Config: + from_attributes = True + + +class InstanceResponse(BaseModel): + id: str + processVersionId: str + businessType: Optional[str] = None + businessId: Optional[str] = None + starterUserId: Optional[str] = None + status: Optional[str] = None + createTime: Optional[str] = None + updateTime: Optional[str] = None + tasks: List[Dict[str, Any]] = Field(default_factory=list) + + class Config: + from_attributes = True diff --git a/backend/app/services/web_auth_roles.py b/backend/app/services/web_auth_roles.py index 71225f84..d358c511 100644 --- a/backend/app/services/web_auth_roles.py +++ b/backend/app/services/web_auth_roles.py @@ -38,7 +38,7 @@ def resolve_web_role_for_user(db: Session, user_id: str) -> str: SysRole.status == "1", SysRole.tenant_no == tenant, ) - .order_by(SysRole.order_num.asc()) + .order_by(SysRole.create_time.desc()) .all() ) names = [str(r[0]).strip() for r in rows if r and r[0]] diff --git a/backend/app/services/workflow_callback.py b/backend/app/services/workflow_callback.py new file mode 100644 index 00000000..31ba8906 --- /dev/null +++ b/backend/app/services/workflow_callback.py @@ -0,0 +1,106 @@ +"""工作流回调服务 —— 流程状态变更时通知业务系统""" +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional + +import requests + +if TYPE_CHECKING: + from app.models.models import WfProcessInstance + +logger = logging.getLogger("sqxp.workflow_callback") + +# --------------------------------------------------------------------------- +# 本地回调注册表:内部路径(以 / 开头)直接在进程内调用,不走 HTTP +# --------------------------------------------------------------------------- + +_LocalCallbackFn = Callable[[Any, str, str], None] # (db, business_id, event) -> None + +_LOCAL_CALLBACKS: Dict[str, _LocalCallbackFn] = {} + + +def register_local_callback(path: str, fn: _LocalCallbackFn) -> None: + """注册本地回调函数。当 callback_url 以 path 开头时直接调用 fn。""" + _LOCAL_CALLBACKS[path] = fn + + +def _dispatch_local(db: Any, instance: WfProcessInstance, event: str) -> bool: + """尝试本地直调。返回 True 表示已处理,False 表示无匹配的本地回调。""" + url = instance.callback_url or "" + if not url.startswith("/"): + return False + for prefix, fn in _LOCAL_CALLBACKS.items(): + if url.startswith(prefix): + try: + fn(db, instance.business_id or "", event) + logger.info( + "工作流本地回调成功: event=%s instance=%s path=%s", + event, + instance.id, + url, + ) + return True + except Exception: + logger.exception( + "工作流本地回调失败: event=%s instance=%s path=%s", + event, + instance.id, + url, + ) + return True # 已匹配但执行失败,不再走 HTTP + return False + + +# --------------------------------------------------------------------------- +# trigger_callback(对外入口) +# --------------------------------------------------------------------------- + + +def trigger_callback(db: Any, instance: WfProcessInstance, event: str) -> None: + """流程实例状态变更后,向业务系统发送回调通知。 + + 优先级: + 1. 如果 callback_url 是内部路径(以 / 开头),优先尝试本地直调 + 2. 本地无匹配时,回退到 HTTP 回调(需完整 URL) + + Args: + db: 数据库会话。 + instance: 流程实例 ORM 对象。 + event: 事件类型,如 approved / rejected / completed。 + """ + if not instance.callback_url: + return + + # 尝试本地直调 + if _dispatch_local(db, instance, event): + return + + # 回退到 HTTP 回调 + payload: Dict[str, Any] = { + "event": event, + "instanceId": instance.id, + "businessType": instance.business_type, + "businessId": instance.business_id, + "status": instance.status, + } + try: + resp = requests.post( + instance.callback_url, + json=payload, + timeout=10, + headers={"Content-Type": "application/json"}, + ) + logger.info( + "工作流HTTP回调成功: event=%s instance=%s status=%d", + event, + instance.id, + resp.status_code, + ) + except Exception: + logger.exception( + "工作流HTTP回调失败: event=%s instance=%s url=%s", + event, + instance.id, + instance.callback_url, + ) diff --git a/backend/app/services/workflow_engine.py b/backend/app/services/workflow_engine.py new file mode 100644 index 00000000..589a2b4c --- /dev/null +++ b/backend/app/services/workflow_engine.py @@ -0,0 +1,389 @@ +"""工作流引擎 —— 读取节点与连线执行审批流转""" +from __future__ import annotations + +import json +import logging +import re +from datetime import datetime +from typing import Any, Dict, List, Optional + +from sqlalchemy import text +from sqlalchemy.orm import Session + +from app.crud import workflow as wf_crud +from app.models.models import ( + SysUser, + SysUserRole, + WfNode, + WfProcessInstance, + WfTask, + WfTransition, +) +from app.services.workflow_callback import trigger_callback + +logger = logging.getLogger("sqxp.workflow_engine") + +_FORBIDDEN_SQL_KEYWORDS = re.compile( + r"\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE)\b", re.IGNORECASE +) + + +class WorkflowEngine: + def __init__(self, db: Session): + self.db = db + + # ------------------------------------------------------------------ + # start_process + # ------------------------------------------------------------------ + + def start_process( + self, + *, + process_def_id: str, + business_type: Optional[str] = None, + business_id: Optional[str] = None, + starter_user_id: Optional[str] = None, + variables: Optional[Dict[str, Any]] = None, + callback_url: Optional[str] = None, + tenant_no: Optional[str] = None, + user_id: Optional[str] = None, + ) -> WfProcessInstance: + version = wf_crud.get_latest_version(self.db, process_def_id) + if not version: + raise ValueError("流程定义没有可用版本,请先发布") + instance = wf_crud.create_instance( + self.db, + process_version_id=version.id, + business_type=business_type, + business_id=business_id, + starter_user_id=starter_user_id, + variables=variables, + callback_url=callback_url, + tenant_no=tenant_no, + user_id=user_id, + ) + nodes = wf_crud.get_nodes_by_version(self.db, version.id) + start_node = next((n for n in nodes if n.node_type == "start"), None) + if not start_node: + raise ValueError("流程缺少开始节点") + self._advance(instance.id, start_node.node_key, tenant_no or "zkxw") + self.db.refresh(instance) + return instance + + # ------------------------------------------------------------------ + # approve_task + # ------------------------------------------------------------------ + + def approve_task(self, task_id: str, comment: str, tenant_no: str) -> WfProcessInstance: + task = wf_crud.get_task(self.db, task_id) + if not task: + raise ValueError("任务不存在") + if task.status != "pending": + raise ValueError(f"任务状态为 {task.status},无法审批") + + task.status = "approved" + task.comment_text = comment + task.update_time = datetime.now() + self.db.add(task) + self.db.commit() + + instance = wf_crud.get_instance(self.db, task.instance_id) + if not instance: + raise ValueError("流程实例不存在") + + version_id = instance.process_version_id + nodes = wf_crud.get_nodes_by_version(self.db, version_id) + current_node = next( + (n for n in nodes if n.node_key == task.node_key), None + ) + config = self._parse_node_config(current_node) + pass_type = config.get("passType", "any") + + if pass_type == "any": + others = wf_crud.get_pending_tasks_by_node( + self.db, task.instance_id, task.node_key + ) + for t in others: + t.status = "withdrawn" + t.update_time = datetime.now() + self.db.add(t) + self.db.commit() + self._advance(instance.id, task.node_key, tenant_no) + else: + remaining = wf_crud.get_pending_tasks_by_node( + self.db, task.instance_id, task.node_key + ) + if not remaining: + self._advance(instance.id, task.node_key, tenant_no) + + self.db.refresh(instance) + return instance + + # ------------------------------------------------------------------ + # reject_task + # ------------------------------------------------------------------ + + def reject_task(self, task_id: str, comment: str, tenant_no: str) -> WfProcessInstance: + task = wf_crud.get_task(self.db, task_id) + if not task: + raise ValueError("任务不存在") + if task.status != "pending": + raise ValueError(f"任务状态为 {task.status},无法驳回") + + task.status = "rejected" + task.comment_text = comment + task.update_time = datetime.now() + self.db.add(task) + self.db.commit() + + others = wf_crud.get_pending_tasks_by_node( + self.db, task.instance_id, task.node_key + ) + for t in others: + t.status = "withdrawn" + t.update_time = datetime.now() + self.db.add(t) + self.db.commit() + + instance = wf_crud.get_instance(self.db, task.instance_id) + if instance: + wf_crud.update_instance_status(self.db, instance.id, "rejected") + self.db.refresh(instance) + trigger_callback(self.db, instance, "rejected") + + return instance + + # ------------------------------------------------------------------ + # _advance (internal) + # ------------------------------------------------------------------ + + def _advance( + self, instance_id: str, from_node_key: str, tenant_no: str + ) -> None: + instance = wf_crud.get_instance(self.db, instance_id) + if not instance: + return + if instance.status != "running": + return + + version_id = instance.process_version_id + nodes = wf_crud.get_nodes_by_version(self.db, version_id) + transitions = wf_crud.get_transitions_by_version(self.db, version_id) + variables = self._parse_variables(instance) + + current_node = next( + (n for n in nodes if n.node_key == from_node_key), None + ) + if not current_node: + return + + out_edges = [ + t for t in transitions if t.from_node_key == from_node_key + ] + if not out_edges: + return + + if current_node.node_type in ("start", "approval"): + target_key = out_edges[0].to_node_key + elif current_node.node_type == "condition": + target_key = self._evaluate_condition(out_edges, variables) + else: + target_key = out_edges[0].to_node_key + + next_node = next((n for n in nodes if n.node_key == target_key), None) + if next_node: + self._execute_node(instance_id, next_node, variables, tenant_no) + + # ------------------------------------------------------------------ + # _execute_node + # ------------------------------------------------------------------ + + def _execute_node( + self, + instance_id: str, + node: WfNode, + variables: Dict[str, Any], + tenant_no: str, + ) -> None: + if node.node_type == "end": + wf_crud.update_instance_status(self.db, instance_id, "completed") + instance = wf_crud.get_instance(self.db, instance_id) + if instance: + trigger_callback(self.db, instance, "completed") + return + + if node.node_type == "approval": + config = self._parse_node_config(node) + user_ids = self._resolve_approvers(config, variables, tenant_no) + for uid in user_ids: + wf_crud.create_task( + self.db, + instance_id=instance_id, + node_key=node.node_key, + assignee_user_id=uid, + tenant_no=tenant_no, + ) + return + + if node.node_type == "condition": + self._advance(instance_id, node.node_key, tenant_no) + return + + self._advance(instance_id, node.node_key, tenant_no) + + # ------------------------------------------------------------------ + # _resolve_approvers + # ------------------------------------------------------------------ + + def _resolve_approvers( + self, + config: Dict[str, Any], + variables: Dict[str, Any], + tenant_no: str, + ) -> List[str]: + strategy = config.get("strategy", "users") + + if strategy == "users": + return config.get("userIds", []) + + if strategy == "roles": + role_ids = config.get("roleIds", []) + if not role_ids: + return [] + rows = ( + self.db.query(SysUser.id) + .join(SysUserRole, SysUserRole.user_id == SysUser.id) + .filter( + SysUserRole.role_id.in_(role_ids), + SysUser.del_flag == 0, + SysUser.status == "1", + ) + .all() + ) + return [r.id for r in rows] + + if strategy == "sql": + sql_template = config.get("sql", "") + if not sql_template or not sql_template.strip().upper().startswith("SELECT"): + raise ValueError("SQL 审批人策略必须以 SELECT 开头") + if _FORBIDDEN_SQL_KEYWORDS.search(sql_template): + raise ValueError("SQL 审批人策略包含禁止的关键字") + try: + result = self.db.execute(text(sql_template), variables or {}) + return [str(row[0]) for row in result.fetchall()] + except Exception as exc: + logger.exception("SQL 审批人解析失败: %s", exc) + raise ValueError(f"SQL 审批人解析失败: {exc}") from exc + + return [] + + # ------------------------------------------------------------------ + # _evaluate_condition + # ------------------------------------------------------------------ + + def _evaluate_condition( + self, out_edges: List[WfTransition], variables: Dict[str, Any] + ) -> str: + unconditional_fallback = out_edges[-1].to_node_key if out_edges else "" + + for edge in out_edges: + cond_str = edge.condition_expr + if not cond_str: + continue + try: + rules = json.loads(cond_str) + except (json.JSONDecodeError, TypeError): + continue + if self._check_rules(rules, variables): + return edge.to_node_key + + return unconditional_fallback + + # ------------------------------------------------------------------ + # _check_rules (recursive) + # ------------------------------------------------------------------ + + def _check_rules(self, rules: Any, variables: Dict[str, Any]) -> bool: + if isinstance(rules, list): + return any(self._check_rules(r, variables) for r in rules) + + if isinstance(rules, dict): + if "and" in rules: + return all(self._check_rules(r, variables) for r in rules["and"]) + if "or" in rules: + return any(self._check_rules(r, variables) for r in rules["or"]) + if "rules" in rules: + return self._check_rules(rules["rules"], variables) + if "field" in rules: + actual = self._get_variable(rules["field"], variables) + op = rules.get("op", "==") + expected = rules.get("value") + return self._compare(actual, op, expected) + + return False + + # ------------------------------------------------------------------ + # _compare + # ------------------------------------------------------------------ + + @staticmethod + def _compare(actual: Any, op: str, expected: Any) -> bool: + try: + if op == "==": + return str(actual) == str(expected) + if op == "!=": + return str(actual) != str(expected) + if op == ">": + return float(actual) > float(expected) + if op == "<": + return float(actual) < float(expected) + if op == ">=": + return float(actual) >= float(expected) + if op == "<=": + return float(actual) <= float(expected) + if op == "in": + return str(actual) in ([str(e) for e in expected] if isinstance(expected, list) else str(expected)) + if op == "not_in": + return str(actual) not in ([str(e) for e in expected] if isinstance(expected, list) else str(expected)) + if op == "contains": + return str(expected) in str(actual) + except (ValueError, TypeError): + return False + return False + + # ------------------------------------------------------------------ + # _get_variable (dot-notation) + # ------------------------------------------------------------------ + + @staticmethod + def _get_variable(field: str, variables: Dict[str, Any]) -> Any: + parts = field.split(".") + current: Any = variables + for part in parts: + if isinstance(current, dict): + current = current.get(part) + else: + return None + return current + + # ------------------------------------------------------------------ + # helpers + # ------------------------------------------------------------------ + + @staticmethod + def _parse_node_config(node: Optional[WfNode]) -> Dict[str, Any]: + if not node or not node.config_json: + return {} + try: + return json.loads(node.config_json) + except (json.JSONDecodeError, TypeError): + return {} + + @staticmethod + def _parse_variables(instance: WfProcessInstance) -> Dict[str, Any]: + if not instance.variables_json: + return {} + try: + return json.loads(instance.variables_json) + except (json.JSONDecodeError, TypeError): + return {} diff --git a/backend/requirements.txt b/backend/requirements.txt index e4054aa6..678308b8 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -15,3 +15,4 @@ python-dotenv==1.0.0 apscheduler==3.10.4 minio==7.2.20 Pillow>=10.0.0 +requests>=2.31.0 diff --git a/backend/tests/test_basic_info_tenant_ownership.py b/backend/tests/test_basic_info_tenant_ownership.py new file mode 100644 index 00000000..c3ed3340 --- /dev/null +++ b/backend/tests/test_basic_info_tenant_ownership.py @@ -0,0 +1,254 @@ +""" +Tests for tenant ownership validation in basic-info CRUD operations. + +Per AGENTS.md: "All business queries SHALL filter by current user's tenant_no." +Per audit: customer, cell_sample_type, collection_protocol_no, sample_code +CRUD get_*() functions do NOT filter by tenant_no, allowing cross-tenant +update/delete if the record ID is known. + +These tests verify that: +1. get_*() functions accept and filter by tenant_no +2. update_*() functions reject cross-tenant modifications +3. soft_delete_*() functions reject cross-tenant deletions + +The collection_region module already correctly implements tenant_no filtering +and serves as the reference pattern. +""" + +import unittest +from unittest.mock import MagicMock, patch + +# We test the CRUD function signatures and behavior without a real database +# by patching the SQLAlchemy session and model queries. + + +class TestCustomerTenantOwnership(unittest.TestCase): + """Customer CRUD should enforce tenant_no on update/delete.""" + + def test_get_customer_signature_accepts_tenant_no(self): + """get_customer() should accept an optional tenant_no parameter.""" + from app.crud.customer import get_customer + + import inspect + sig = inspect.signature(get_customer) + params = list(sig.parameters.keys()) + # Currently: ['db', 'customer_id'] — should add 'tenant_no' + self.assertIn("tenant_no", params, "get_customer() must accept tenant_no parameter") + + def test_get_customer_filters_by_tenant_no(self): + """get_customer() should filter query by tenant_no when provided.""" + from app.crud.customer import get_customer + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + # Should not raise, and should add tenant_no filter + result = get_customer(mock_db, "some-id", tenant_no="tenant-A") + mock_db.query.assert_called_once() + # filter() should be called at least twice (del_flag + tenant_no) + self.assertGreaterEqual(mock_query.filter.call_count, 2) + + def test_update_customer_rejects_cross_tenant(self): + """update_customer() should raise when record belongs to different tenant.""" + from app.crud.customer import update_customer + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None # tenant_no filter blocks access + + with self.assertRaises(ValueError) as ctx: + update_customer( + mock_db, + "cross-tenant-id", + customer_name="Hacked", + user_id="user-A", + tenant_no="tenant-A", # record belongs to tenant-B + ) + self.assertIn("不存在", str(ctx.exception)) + + def test_soft_delete_customer_rejects_cross_tenant(self): + """soft_delete_customer() should raise when record belongs to different tenant.""" + from app.crud.customer import soft_delete_customer + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None # tenant_no filter blocks access + + with self.assertRaises(ValueError): + soft_delete_customer(mock_db, "cross-tenant-id", tenant_no="tenant-A") + + +class TestCellSampleTypeTenantOwnership(unittest.TestCase): + """CellSampleType CRUD should enforce tenant_no on update/delete.""" + + def test_get_cell_sample_type_signature_accepts_tenant_no(self): + """get_cell_sample_type() should accept an optional tenant_no parameter.""" + from app.crud.cell_sample_type import get_cell_sample_type + + import inspect + sig = inspect.signature(get_cell_sample_type) + params = list(sig.parameters.keys()) + self.assertIn("tenant_no", params, "get_cell_sample_type() must accept tenant_no parameter") + + def test_update_cell_sample_type_rejects_cross_tenant(self): + """update_cell_sample_type() should raise when record belongs to different tenant.""" + from app.crud.cell_sample_type import update_cell_sample_type + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + with self.assertRaises(ValueError) as ctx: + update_cell_sample_type( + mock_db, + "cross-tenant-id", + type_name="Hacked", + user_id="user-A", + tenant_no="tenant-A", + ) + self.assertIn("不存在", str(ctx.exception)) + + def test_soft_delete_cell_sample_type_rejects_cross_tenant(self): + """soft_delete_cell_sample_type() should raise when record belongs to different tenant.""" + from app.crud.cell_sample_type import soft_delete_cell_sample_type + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + with self.assertRaises(ValueError): + soft_delete_cell_sample_type(mock_db, "cross-tenant-id", tenant_no="tenant-A") + + +class TestProtocolNoTenantOwnership(unittest.TestCase): + """CollectionProtocolNo CRUD should enforce tenant_no on update/delete.""" + + def test_get_protocol_no_signature_accepts_tenant_no(self): + """get_protocol_no() should accept an optional tenant_no parameter.""" + from app.crud.collection_protocol_no import get_protocol_no + + import inspect + sig = inspect.signature(get_protocol_no) + params = list(sig.parameters.keys()) + self.assertIn("tenant_no", params, "get_protocol_no() must accept tenant_no parameter") + + def test_update_protocol_no_rejects_cross_tenant(self): + """update_protocol_no() should raise when record belongs to different tenant.""" + from app.crud.collection_protocol_no import update_protocol_no + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + with self.assertRaises(ValueError) as ctx: + update_protocol_no( + mock_db, + "cross-tenant-id", + remark="Hacked", + user_id="user-A", + tenant_no="tenant-A", + ) + self.assertIn("不存在", str(ctx.exception)) + + def test_soft_delete_protocol_no_rejects_cross_tenant(self): + """soft_delete_protocol_no() should raise when record belongs to different tenant.""" + from app.crud.collection_protocol_no import soft_delete_protocol_no + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + with self.assertRaises(ValueError): + soft_delete_protocol_no(mock_db, "cross-tenant-id", tenant_no="tenant-A") + + +class TestSampleCodeTenantOwnership(unittest.TestCase): + """SampleCode CRUD should enforce tenant_no on update/delete.""" + + def test_get_sample_code_signature_accepts_tenant_no(self): + """get_sample_code() should accept an optional tenant_no parameter.""" + from app.crud.sample_code import get_sample_code + + import inspect + sig = inspect.signature(get_sample_code) + params = list(sig.parameters.keys()) + self.assertIn("tenant_no", params, "get_sample_code() must accept tenant_no parameter") + + def test_update_sample_code_rejects_cross_tenant(self): + """update_sample_code() should raise when record belongs to different tenant.""" + from app.crud.sample_code import update_sample_code + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + with self.assertRaises(ValueError) as ctx: + update_sample_code( + mock_db, + "cross-tenant-id", + remark="Hacked", + user_id="user-A", + tenant_no="tenant-A", + ) + self.assertIn("不存在", str(ctx.exception)) + + def test_soft_delete_sample_code_rejects_cross_tenant(self): + """soft_delete_sample_code() should raise when record belongs to different tenant.""" + from app.crud.sample_code import soft_delete_sample_code + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + with self.assertRaises(ValueError): + soft_delete_sample_code(mock_db, "cross-tenant-id", tenant_no="tenant-A") + + +class TestGetTenantNoFallback(unittest.TestCase): + """_get_tenant_no() should not silently fall back to hardcoded value.""" + + def test_get_tenant_no_raises_on_missing_user(self): + """When user_id is missing from token, should raise rather than default.""" + from app.api.basic_info import _get_tenant_no + + mock_db = MagicMock() + user = {} # no user_id + with self.assertRaises((ValueError, PermissionError)): + _get_tenant_no(mock_db, user) + + def test_get_tenant_no_raises_on_missing_sys_user(self): + """When SysUser record not found in DB, should raise rather than default.""" + from app.api.basic_info import _get_tenant_no + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None # SysUser not found + + user = {"user_id": "some-id"} + with self.assertRaises((ValueError, PermissionError)): + _get_tenant_no(mock_db, user) + + +if __name__ == "__main__": + unittest.main() diff --git a/backend/tests/test_collection_protocol_conventions.py b/backend/tests/test_collection_protocol_conventions.py new file mode 100644 index 00000000..95efb078 --- /dev/null +++ b/backend/tests/test_collection_protocol_conventions.py @@ -0,0 +1,249 @@ +""" +Tests for collection_protocol API/CRUD convention compliance. + +Per AGENTS.md engineering standards: +1. _get_tenant_no() MUST NOT silently fall back to hardcoded "zkxw" + — must raise PermissionError when user_id missing or SysUser not found. +2. All list queries SHALL sort by order_num ASC, create_time DESC + (unless a business-specific sort is justified in the design doc). +3. list_audits, list_receipt_confirm_details, list_refund_confirm_details + currently only sort by create_time DESC — missing order_num ASC prefix. +""" + +import unittest +from unittest.mock import MagicMock, patch, call + + +# --------------------------------------------------------------------------- +# H1: _get_tenant_no must raise PermissionError (not fall back to "zkxw") +# --------------------------------------------------------------------------- + + +class TestCollectionProtocolGetTenantNo(unittest.TestCase): + """collection_protocol._get_tenant_no should match basic_info pattern.""" + + def test_raises_on_missing_user_id(self): + """When user dict has no user_id, must raise PermissionError.""" + from app.api.collection_protocol import _get_tenant_no + + mock_db = MagicMock() + user = {} # no user_id key + with self.assertRaises(PermissionError): + _get_tenant_no(mock_db, user) + + def test_raises_on_none_user_id(self): + """When user_id is None, must raise PermissionError.""" + from app.api.collection_protocol import _get_tenant_no + + mock_db = MagicMock() + user = {"user_id": None} + with self.assertRaises(PermissionError): + _get_tenant_no(mock_db, user) + + def test_raises_on_empty_user_id(self): + """When user_id is empty string, must raise PermissionError.""" + from app.api.collection_protocol import _get_tenant_no + + mock_db = MagicMock() + user = {"user_id": ""} + with self.assertRaises(PermissionError): + _get_tenant_no(mock_db, user) + + def test_raises_on_missing_sys_user(self): + """When SysUser record not found in DB, must raise PermissionError.""" + from app.api.collection_protocol import _get_tenant_no + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None # SysUser not found + + user = {"user_id": "some-id"} + with self.assertRaises(PermissionError): + _get_tenant_no(mock_db, user) + + def test_raises_on_sys_user_no_tenant(self): + """When SysUser exists but tenant_no is None, must raise PermissionError.""" + from app.api.collection_protocol import _get_tenant_no + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_user = MagicMock() + mock_user.tenant_no = None + mock_query.first.return_value = mock_user + + user = {"user_id": "some-id"} + with self.assertRaises(PermissionError): + _get_tenant_no(mock_db, user) + + def test_returns_tenant_no_on_valid_user(self): + """When SysUser has valid tenant_no, should return it.""" + from app.api.collection_protocol import _get_tenant_no + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_user = MagicMock() + mock_user.tenant_no = "tenant-ABC" + mock_query.first.return_value = mock_user + + result = _get_tenant_no(mock_db, {"user_id": "user-1"}) + self.assertEqual(result, "tenant-ABC") + + def test_does_not_return_hardcoded_zkxw(self): + """Must never return the hardcoded fallback 'zkxw'.""" + from app.api.collection_protocol import _get_tenant_no + + mock_db = MagicMock() + mock_query = MagicMock() + mock_db.query.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.first.return_value = None + + user = {"user_id": "missing-user"} + with self.assertRaises(PermissionError) as ctx: + _get_tenant_no(mock_db, user) + # Ensure it raised, not returned "zkxw" + self.assertNotIsInstance(ctx.exception, type(None)) + + +# --------------------------------------------------------------------------- +# M1: Sort order — list_audits should include order_num ASC +# --------------------------------------------------------------------------- + + +class TestListAuditsSortOrder(unittest.TestCase): + """list_audits should sort by order_num ASC, create_time DESC.""" + + @patch("app.crud.collection_protocol.BizProtocolAudit") + @patch("app.crud.collection_protocol.BizCollectionProtocol") + def test_list_audits_includes_order_num_in_sort(self, MockProtocol, MockAudit): + """order_by should include order_num ASC before create_time DESC.""" + from app.crud.collection_protocol import list_audits + + mock_db = MagicMock() + mock_query = MagicMock() + mock_query.join.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.order_by.return_value = mock_query + mock_query.with_entities.return_value = mock_query + mock_query.scalar.return_value = 0 + mock_query.offset.return_value = mock_query + mock_query.limit.return_value = mock_query + mock_query.all.return_value = [] + mock_db.query.return_value = mock_query + + list_audits(mock_db, tenant_no="t1") + + # Check that order_by was called at least once for the data query + # The sort call should include order_num ASC + order_by_calls = mock_query.order_by.call_args_list + self.assertGreaterEqual(len(order_by_calls), 1, "order_by should be called for data query") + + # The data query's order_by should contain order_num.asc() + # We check the last order_by call (the data query, not the count query) + last_sort_call = order_by_calls[-1] + sort_args = last_sort_call[0] # positional args + # Should have at least 2 args: order_num ASC, create_time DESC + self.assertGreaterEqual( + len(sort_args), 2, + f"order_by should have at least 2 args (order_num ASC, create_time DESC), got: {sort_args}" + ) + + +# --------------------------------------------------------------------------- +# M1: Sort order — list_receipt_confirm_details should include order_num ASC +# --------------------------------------------------------------------------- + + +class TestListReceiptConfirmSortOrder(unittest.TestCase): + """list_receipt_confirm_details should sort by order_num ASC, create_time DESC.""" + + @patch("app.crud.collection_protocol.BizProtocolPaymentDetail") + @patch("app.crud.collection_protocol.BizProtocolPayment") + @patch("app.crud.collection_protocol.BizCollectionProtocol") + def test_receipt_confirm_includes_order_num_in_sort(self, MockProtocol, MockPayment, MockDetail): + """order_by should include order_num ASC before create_time DESC.""" + from app.crud.collection_protocol import list_receipt_confirm_details + + mock_db = MagicMock() + mock_query = MagicMock() + mock_query.join.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.order_by.return_value = mock_query + mock_query.with_entities.return_value = mock_query + mock_query.scalar.return_value = 0 + mock_query.offset.return_value = mock_query + mock_query.limit.return_value = mock_query + mock_query.all.return_value = [] + mock_db.query.return_value = mock_query + + list_receipt_confirm_details(mock_db, tenant_no="t1") + + order_by_calls = mock_query.order_by.call_args_list + self.assertGreaterEqual(len(order_by_calls), 1) + + last_sort_call = order_by_calls[-1] + sort_args = last_sort_call[0] + self.assertGreaterEqual( + len(sort_args), 2, + f"order_by should have at least 2 args, got: {sort_args}" + ) + + +# --------------------------------------------------------------------------- +# M1: Sort order — list_refund_confirm_details should include order_num ASC +# --------------------------------------------------------------------------- + + +class TestListRefundConfirmSortOrder(unittest.TestCase): + """list_refund_confirm_details should sort by order_num ASC, create_time DESC.""" + + @patch("app.crud.collection_protocol.BizProtocolPaymentDetail") + @patch("app.crud.collection_protocol.BizProtocolPayment") + @patch("app.crud.collection_protocol.BizCollectionProtocol") + def test_refund_confirm_includes_order_num_in_sort(self, MockProtocol, MockPayment, MockDetail): + """order_by should include order_num ASC before create_time DESC.""" + from app.crud.collection_protocol import list_refund_confirm_details + + mock_db = MagicMock() + mock_query = MagicMock() + mock_query.join.return_value = mock_query + mock_query.filter.return_value = mock_query + mock_query.order_by.return_value = mock_query + mock_query.with_entities.return_value = mock_query + mock_query.scalar.return_value = 0 + mock_query.offset.return_value = mock_query + mock_query.limit.return_value = mock_query + mock_query.all.return_value = [] + mock_db.query.return_value = mock_query + + list_refund_confirm_details(mock_db, tenant_no="t1") + + order_by_calls = mock_query.order_by.call_args_list + self.assertGreaterEqual(len(order_by_calls), 1) + + last_sort_call = order_by_calls[-1] + sort_args = last_sort_call[0] + self.assertGreaterEqual( + len(sort_args), 2, + f"order_by should have at least 2 args, got: {sort_args}" + ) + + +class TestNormalizeYyyymmddDate(unittest.TestCase): + def test_compact_and_dashed(self): + from app.crud.collection_protocol import _normalize_yyyymmdd_date + + self.assertEqual(_normalize_yyyymmdd_date("20260506"), "20260506") + self.assertEqual(_normalize_yyyymmdd_date("2026-05-06"), "20260506") + self.assertIsNone(_normalize_yyyymmdd_date("")) + self.assertIsNone(_normalize_yyyymmdd_date(None)) + + +if __name__ == "__main__": + unittest.main() diff --git a/backend/tests/test_collection_protocol_save_bundle.py b/backend/tests/test_collection_protocol_save_bundle.py new file mode 100644 index 00000000..848e3126 --- /dev/null +++ b/backend/tests/test_collection_protocol_save_bundle.py @@ -0,0 +1,83 @@ +"""采集协议单创建/更新子表持久化与字段名规范化测试。""" + +import unittest +from unittest.mock import MagicMock, patch + +from app.crud.collection_protocol import ( + _normalize_fields, + attachments_to_urls, + urls_to_attachment_list, + upsert_party_customer, +) + + +class TestNormalizeFields(unittest.TestCase): + def test_camel_case_to_snake(self): + raw = { + "customerId": "c1", + "motherName": "张三", + "motherBirthYm": "199001", + "apgar1min": 9.5, + "apgar5min": 10, + } + out = _normalize_fields(raw) + self.assertEqual(out["customer_id"], "c1") + self.assertEqual(out["mother_name"], "张三") + self.assertEqual(out["mother_birth_ym"], "199001") + self.assertEqual(out["apgar_1min"], 9.5) + self.assertEqual(out["apgar_5min"], 10) + + +class TestAttachmentsHelpers(unittest.TestCase): + def test_attachments_to_urls(self): + urls = attachments_to_urls( + [{"fileUrl": "http://a/1.jpg"}, {"fileUrl": "http://a/2.jpg"}] + ) + self.assertEqual(urls, "http://a/1.jpg,http://a/2.jpg") + + def test_urls_to_attachment_list(self): + items = urls_to_attachment_list("http://x/a.pdf,http://x/b.png") + self.assertEqual(len(items), 2) + self.assertEqual(items[0]["fileUrl"], "http://x/a.pdf") + + def test_urls_to_attachment_list_uses_original_name_from_db(self): + db = MagicMock() + att = MagicMock() + att.id = "att-1" + att.original_name = "合同扫描件.pdf" + att.file_url = "protocol/2025/05/16/abc.pdf" + att.file_size = 2048 + att.file_type = "application/pdf" + q = MagicMock() + db.query.return_value = q + q.filter.return_value = q + q.first.return_value = att + items = urls_to_attachment_list("protocol/2025/05/16/abc.pdf", db, "t1") + self.assertEqual(len(items), 1) + self.assertEqual(items[0]["fileName"], "合同扫描件.pdf") + self.assertEqual(items[0]["id"], "att-1") + + +class TestUpsertPartyCustomer(unittest.TestCase): + @patch("app.crud.collection_protocol.get_party_customer") + def test_upsert_applies_camel_case_fields(self, mock_get): + mock_get.return_value = None + db = MagicMock() + fields = { + "motherName": "李四", + "motherPhone": "13800000000", + "motherIdCard": "110101199001011234", + "motherBirthYm": "199001", + "fatherName": "王五", + } + upsert_party_customer(db, "proto-1", "t1", "u1", **fields) + added = db.add.call_args[0][0] + self.assertEqual(added.mother_name, "李四") + self.assertEqual(added.mother_phone, "13800000000") + self.assertEqual(added.mother_id_card, "110101199001011234") + self.assertEqual(added.mother_birth_ym, "199001") + self.assertEqual(added.father_name, "王五") + + +if __name__ == "__main__": + unittest.main() diff --git a/backend/tests/test_ethnic.py b/backend/tests/test_ethnic.py new file mode 100644 index 00000000..dadf47af --- /dev/null +++ b/backend/tests/test_ethnic.py @@ -0,0 +1,17 @@ +"""民族码表 CRUD 冒烟测试""" +from app.core.database import SessionLocal +from app.crud.ethnic import list_ethnics + + +def test_list_ethnics_has_han(): + db = SessionLocal() + try: + rows = list_ethnics(db) + assert len(rows) >= 56 + han = next((r for r in rows if r["ethnicName"] == "汉族"), None) + assert han is not None + assert han["ethnicCode"] == "01" + filtered = list_ethnics(db, keyword="蒙古") + assert any(r["ethnicName"] == "蒙古族" for r in filtered) + finally: + db.close() diff --git a/docs/sql/alter-biz-protocol-party-customer-apgar-decimal.sql b/docs/sql/alter-biz-protocol-party-customer-apgar-decimal.sql new file mode 100644 index 00000000..030d4fb2 --- /dev/null +++ b/docs/sql/alter-biz-protocol-party-customer-apgar-decimal.sql @@ -0,0 +1,4 @@ +-- 采集协议单当事人:新生儿 Apgar 评分支持一位小数 +ALTER TABLE `biz_protocol_party_customer` + MODIFY COLUMN `apgar_1min` decimal(4,1) DEFAULT NULL COMMENT '新生儿评分1分钟', + MODIFY COLUMN `apgar_5min` decimal(4,1) DEFAULT NULL COMMENT '新生儿评分5分钟'; diff --git a/docs/sql/alter-biz-protocol-sample-status-nullable.sql b/docs/sql/alter-biz-protocol-sample-status-nullable.sql new file mode 100644 index 00000000..bacaea3f --- /dev/null +++ b/docs/sql/alter-biz-protocol-sample-status-nullable.sql @@ -0,0 +1,3 @@ +-- 样本状态允许为空,移除默认「已采集」,与前端清空行为一致 +ALTER TABLE `biz_protocol_sample` + MODIFY COLUMN `sample_status` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'ycj已采集 jcz检测中 yrk已入库 yzf已作废'; diff --git a/docs/sql/sys-ethnic.sql b/docs/sql/sys-ethnic.sql new file mode 100644 index 00000000..65777cb5 --- /dev/null +++ b/docs/sql/sys-ethnic.sql @@ -0,0 +1,83 @@ +-- 民族码表(国标 GB/T 3304,仅数字编码条目,不含字母编码重复项) +-- 执行前请确认库中无同名表;与 docs/sql/zkxw.sql 全量基线可并存,亦可合并进全量导出 + +DROP TABLE IF EXISTS `sys_ethnic`; +CREATE TABLE `sys_ethnic` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `ethnic_name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '民族名称', + `ethnic_code` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '民族编码', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '租户编号(空=全局码表)', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_sys_ethnic_code` (`ethnic_code`,`del_flag`), + KEY `idx_sys_ethnic_name` (`ethnic_name`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='民族码表'; + +BEGIN; +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-01', '汉族', '01', '', 1, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-02', '蒙古族', '02', '', 2, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-03', '回族', '03', '', 3, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-04', '藏族', '04', '', 4, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-05', '维吾尔族', '05', '', 5, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-06', '苗族', '06', '', 6, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-07', '彝族', '07', '', 7, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-08', '壮族', '08', '', 8, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-09', '布依族', '09', '', 9, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-10', '朝鲜族', '10', '', 10, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-11', '满族', '11', '', 11, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-12', '侗族', '12', '', 12, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-13', '瑶族', '13', '', 13, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-14', '白族', '14', '', 14, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-15', '土家族', '15', '', 15, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-16', '哈尼族', '16', '', 16, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-17', '哈萨克族', '17', '', 17, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-18', '傣族', '18', '', 18, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-19', '黎族', '19', '', 19, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-20', '傈僳族', '20', '', 20, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-21', '佤族', '21', '', 21, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-22', '畲族', '22', '', 22, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-23', '高山族', '23', '', 23, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-24', '拉祜族', '24', '', 24, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-25', '水族', '25', '', 25, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-26', '东乡族', '26', '', 26, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-27', '纳西族', '27', '', 27, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-28', '景颇族', '28', '', 28, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-29', '柯尔克孜族', '29', '', 29, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-30', '土族', '30', '', 30, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-31', '达斡尔族', '31', '', 31, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-32', '仫佬族', '32', '', 32, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-33', '羌族', '33', '', 33, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-34', '布朗族', '34', '', 34, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-35', '撒拉族', '35', '', 35, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-36', '毛难族', '36', '', 36, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-37', '仡佬族', '37', '', 37, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-38', '锡伯族', '38', '', 38, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-39', '阿昌族', '39', '', 39, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-40', '普米族', '40', '', 40, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-41', '塔吉克族', '41', '', 41, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-42', '怒族', '42', '', 42, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-43', '乌孜别克族', '43', '', 43, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-44', '俄罗斯族', '44', '', 44, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-45', '鄂温克族', '45', '', 45, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-46', '德昂族', '46', '', 46, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-47', '保安族', '47', '', 47, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-48', '裕固族', '48', '', 48, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-49', '京族', '49', '', 49, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-50', '塔塔尔族', '50', '', 50, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-51', '独龙族', '51', '', 51, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-52', '鄂伦春族', '52', '', 52, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-53', '赫哲族', '53', '', 53, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-54', '门巴族', '54', '', 54, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-55', '珞巴族', '55', '', 55, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-56', '基诺族', '56', '', 56, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-81', '穿青人族', '81', '', 57, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-97', '其他', '97', '', 58, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-98', '外国血统中国籍人士', '98', '', 59, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +COMMIT; diff --git a/docs/sql/zkxw-init-dept-data.sql b/docs/sql/zkxw-init-dept-data.sql deleted file mode 100644 index 4328679b..00000000 --- a/docs/sql/zkxw-init-dept-data.sql +++ /dev/null @@ -1,20 +0,0 @@ --- 组织管理初始化数据(需求 2.1.3) --- 市场营销部门下设:采集部、临床部、中试服务部、支持中心、客服部 - -SET @marketing_id = UUID(); - -INSERT INTO sys_dept (id, parent_id, dept_name, dept_category_code, status, leader, leader_phone, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), '0', '中科兴皖', 'gs', '1', NULL, NULL, 'zkxw', 0, '', 0), -(@marketing_id, '0', '市场营销部门', 'bm', '1', NULL, NULL, 'zkxw', 1, '', 0), -(UUID(), '0', '技术研发部', 'bm', '1', NULL, NULL, 'zkxw', 2, '', 0), -(UUID(), '0', '生产部', 'bm', '1', NULL, NULL, 'zkxw', 3, '', 0), -(UUID(), '0', '质量安全部', 'bm', '1', NULL, NULL, 'zkxw', 4, '', 0), -(UUID(), '0', '财务部', 'bm', '1', NULL, NULL, 'zkxw', 5, '', 0), -(UUID(), '0', '综合部', 'bm', '1', NULL, NULL, 'zkxw', 6, '', 0); - -INSERT INTO sys_dept (id, parent_id, dept_name, dept_category_code, status, leader, leader_phone, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), @marketing_id, '采集部', 'bm', '1', NULL, NULL, 'zkxw', 1, '', 0), -(UUID(), @marketing_id, '临床部', 'bm', '1', NULL, NULL, 'zkxw', 2, '', 0), -(UUID(), @marketing_id, '中试服务部', 'bm', '1', NULL, NULL, 'zkxw', 3, '', 0), -(UUID(), @marketing_id, '支持中心', 'bm', '1', NULL, NULL, 'zkxw', 4, '', 0), -(UUID(), @marketing_id, '客服部', 'bm', '1', NULL, NULL, 'zkxw', 5, '', 0); diff --git a/docs/sql/zkxw-init-dict-data.sql b/docs/sql/zkxw-init-dict-data.sql deleted file mode 100644 index 29bbcde8..00000000 --- a/docs/sql/zkxw-init-dict-data.sql +++ /dev/null @@ -1,24 +0,0 @@ --- ============================================================ --- 字典管理初始数据(sys_dict_type / sys_dict_data) --- 执行前提:zkxw-requirements-v0.3-schema.sql 已建表 --- ============================================================ - --- 字典类型 -INSERT INTO sys_dict_type (id, dict_name, dict_type, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), '组织类别编码', 'zzlbbm', 'zkxw', 1, '', 0), -(UUID(), '站点类型', 'zdlx', 'zkxw', 2, '', 0), -(UUID(), '站点业务范围', 'zdywfw', 'zkxw', 3, '', 0), -(UUID(), '部门状态', 'bmzt', 'zkxw', 4, '', 0); - --- 字典数据 -INSERT INTO sys_dict_data (id, dict_label, dict_value, dict_type, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), '公司', 'gs', 'zzlbbm', 'zkxw', 1, '', 0), -(UUID(), '分公司', 'fgs', 'zzlbbm', 'zkxw', 2, '', 0), -(UUID(), '部门', 'bm', 'zzlbbm', 'zkxw', 3, '', 0), -(UUID(), '中心基地', 'zxjd', 'zdlx', 'zkxw', 1, '', 0), -(UUID(), '样本采集', 'ybcj', 'zdywfw', 'zkxw', 1, '', 0), -(UUID(), '样本制备', 'ybzb', 'zdywfw', 'zkxw', 2, '', 0), -(UUID(), '质量检测', 'zljc', 'zdywfw', 'zkxw', 3, '', 0), -(UUID(), '细胞存储', 'xbcc', 'zdywfw', 'zkxw', 4, '', 0), -(UUID(), '停用', '0', 'bmzt', 'zkxw', 1, '', 0), -(UUID(), '正常', '1', 'bmzt', 'zkxw', 2, '', 0); diff --git a/docs/sql/zkxw-init-menu-data.sql b/docs/sql/zkxw-init-menu-data.sql deleted file mode 100644 index 1ddd36d9..00000000 --- a/docs/sql/zkxw-init-menu-data.sql +++ /dev/null @@ -1,24 +0,0 @@ --- 2.1.6 菜单管理初始化数据 --- Web端根节点与移动端根节点,parent_id = 0 - --- 根节点:Web端菜单、移动端菜单 -INSERT INTO sys_menu (id, menu_name, parent_id, menu_type, path, component, visible, status, tenant_no, order_num, create_dept_id, del_flag) VALUES -('1', 'Web端菜单', '0', 'M', '/system', NULL, '1', '1', 'zkxw', 1, '', 0), -('2', '移动端菜单', '0', 'M', '/mini', NULL, '1', '1', 'zkxw', 2, '', 0); - --- Web端菜单 - 系统管理目录 -INSERT INTO sys_menu (id, menu_name, parent_id, menu_type, path, component, perms, visible, status, tenant_no, order_num, create_dept_id, del_flag) VALUES -('100', '系统管理', '1', 'M', '/system', '/index/index', NULL, '1', '1', 'zkxw', 1, '', 0); - --- 系统管理子菜单 -INSERT INTO sys_menu (id, menu_name, parent_id, menu_type, path, component, perms, visible, status, tenant_no, order_num, create_dept_id, del_flag) VALUES -('101', '租户管理', '100', 'C', 'tenant', '/system/tenant/index', 'system:tenant:list', '1', '1', 'zkxw', 1, '', 0), -('102', '字典管理', '100', 'C', 'dict', '/system/dict/index', 'system:dict:list', '1', '1', 'zkxw', 2, '', 0), -('103', '组织管理', '100', 'C', 'dept', '/system/dept/index', 'system:dept:list', '1', '1', 'zkxw', 3, '', 0), -('104', '用户管理', '100', 'C', 'user', '/system/user/index', 'system:user:list', '1', '1', 'zkxw', 4, '', 0), -('105', '角色管理', '100', 'C', 'role', '/system/role/index', 'system:role:list', '1', '1', 'zkxw', 5, '', 0), -('106', '菜单管理', '100', 'C', 'menu', '/system/menu/index', 'system:menu:list', '1', '1', 'zkxw', 6, '', 0), -('107', '站点管理', '100', 'C', 'site', '/system/site/index', 'system:site:list', '1', '1', 'zkxw', 7, '', 0), -('108', '职位管理', '100', 'C', 'post', '/system/post/index', 'system:post:list', '1', '1', 'zkxw', 8, '', 0), -('109', '物品单位', '100', 'C', 'unit', '/system/unit/index', 'system:unit:list', '1', '1', 'zkxw', 9, '', 0), -('110', '快递公司', '100', 'C', 'express', '/system/express/index', 'system:express:list', '1', '1', 'zkxw', 10, '', 0); diff --git a/docs/sql/zkxw-init-role-data.sql b/docs/sql/zkxw-init-role-data.sql deleted file mode 100644 index 210a8af1..00000000 --- a/docs/sql/zkxw-init-role-data.sql +++ /dev/null @@ -1,28 +0,0 @@ --- V0.3 角色初始数据 --- sys_role_data_scope_detail 建表语句 -CREATE TABLE IF NOT EXISTS `sys_role_data_scope_detail` ( - `id` varchar(64) NOT NULL COMMENT '主键ID', - `role_id` varchar(64) NOT NULL COMMENT '角色ID', - `scope_type` varchar(20) NOT NULL COMMENT '范围类型:dept/user', - `scope_value` varchar(100) NOT NULL COMMENT '范围值:部门ID/用户ID', - `tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` int DEFAULT NULL COMMENT '排序号', - `remark` varchar(512) DEFAULT NULL COMMENT '备注', - `create_by` varchar(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` varchar(64) DEFAULT NULL COMMENT '更新人id', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` smallint NOT NULL DEFAULT '0' COMMENT '删除标记', - PRIMARY KEY (`id`), - KEY `idx_role_id` (`role_id`), - KEY `idx_tenant_no` (`tenant_no`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色自定义数据权限明细'; - --- 角色初始数据 -INSERT INTO sys_role (id, role_name, data_scope, status, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), '超级管理员', '1', '1', 'zkxw', 1, '', 0), -(UUID(), '运营管理员', '1', '1', 'zkxw', 2, '', 0), -(UUID(), '采集专员', '2', '1', 'zkxw', 3, '', 0), -(UUID(), '市场支持专员', '2', '1', 'zkxw', 4, '', 0), -(UUID(), '质检专员', '2', '1', 'zkxw', 5, '', 0); diff --git a/docs/sql/zkxw-init-site-post-unit-express.sql b/docs/sql/zkxw-init-site-post-unit-express.sql deleted file mode 100644 index 3cac3946..00000000 --- a/docs/sql/zkxw-init-site-post-unit-express.sql +++ /dev/null @@ -1,26 +0,0 @@ --- 站点、职位、物品单位、快递公司 初始化数据 --- 对应模型:SysSite, SysPost, BizItemUnit, BizExpressCompany - --- 站点 -INSERT INTO sys_site (id, site_name, site_code, site_type, status, address, contact_name, contact_phone, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), '中科兴皖中心基地', 'zkxw-zxjd', 'zxjd', '1', '', NULL, NULL, 'zkxw', 1, '', 0); - --- 职位 -INSERT INTO sys_post (id, post_name, post_code, status, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), '采集专员', 'cjzy', '1', 'zkxw', 1, '', 0), -(UUID(), '市场支持专员', 'sczczy', '1', 'zkxw', 2, '', 0), -(UUID(), '质检专员', 'zjzy', '1', 'zkxw', 3, '', 0); - --- 物品单位 -INSERT INTO biz_item_unit (id, unit_code, unit_name, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), 'g', '克', 'zkxw', 1, '', 0), -(UUID(), 'ml', '毫升', 'zkxw', 2, '', 0), -(UUID(), 'ge', '个', 'zkxw', 3, '', 0), -(UUID(), 'fen', '份', 'zkxw', 4, '', 0); - --- 快递公司 -INSERT INTO biz_express_company (id, express_code, express_name, tenant_no, order_num, create_dept_id, del_flag) VALUES -(UUID(), 'sf', '顺丰速运', 'zkxw', 1, '', 0), -(UUID(), 'jd', '京东快递', 'zkxw', 2, '', 0), -(UUID(), 'zt', '中通快递', 'zkxw', 3, '', 0), -(UUID(), 'yt', '圆通速递', 'zkxw', 4, '', 0); diff --git a/docs/sql/zkxw-init-user-data.sql b/docs/sql/zkxw-init-user-data.sql deleted file mode 100644 index 3ed4dbc6..00000000 --- a/docs/sql/zkxw-init-user-data.sql +++ /dev/null @@ -1,24 +0,0 @@ --- zkxw-init-user-data.sql --- V0.3 用户管理初始化数据(测试用户) --- 默认 admin 用户已在骨架 SQL 中创建,此处补充测试账号 --- 注意:密码需通过后端 API 创建以获得正确的 bcrypt hash - -INSERT INTO sys_user ( - id, dept_id, user_no, nick_name, email, phone_no, sex, - password, status, tenant_no, order_num, - create_dept_id, create_by, create_time, update_by, update_time, del_flag -) VALUES -( - REPLACE(UUID(),'-',''), - (SELECT id FROM sys_dept WHERE dept_name='市场营销部' AND del_flag=0 LIMIT 1), - 'zhangsan', '张三', 'zhangsan@zkxw.com', '13800000001', '0', - '$2b$12$placeholder_hash_use_api_to_set', '1', 'zkxw', 2, - '', NULL, NOW(), NULL, NOW(), 0 -), -( - REPLACE(UUID(),'-',''), - (SELECT id FROM sys_dept WHERE dept_name='市场营销部' AND del_flag=0 LIMIT 1), - 'lisi', '李四', 'lisi@zkxw.com', '13800000002', '1', - '$2b$12$placeholder_hash_use_api_to_set', '1', 'zkxw', 3, - '', NULL, NOW(), NULL, NOW(), 0 -); diff --git a/docs/sql/zkxw-requirements-v0.3-schema.sql b/docs/sql/zkxw-requirements-v0.3-schema.sql deleted file mode 100644 index c9df5f6e..00000000 --- a/docs/sql/zkxw-requirements-v0.3-schema.sql +++ /dev/null @@ -1,1638 +0,0 @@ --- ============================================================================= --- 中科兴皖需求 V0.3 表模型 DDL(MySQL 5.7) --- 依据:docs/中科兴皖需求V0.3.md 第 2、3 节 --- 说明: --- 1) id 为 VARCHAR(64),由应用生成 UUID;未启用外键约束,便于分步执行与迁移。 --- 2) 公共字段顺序:业务字段 → tenant_no → order_num → remark → create_* → update_* → del_flag --- 3) 文档标注「暂不开发」的模块(科研捐赠、临床应用、物料、财务、生产、质检)未建表。 --- ============================================================================= - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ----------------------------------------------------------------------------- --- 公共列片段(复制到各表;此处仅作文档说明,实际已展开写入各 CREATE TABLE) --- tenant_no, order_num, remark, create_by, create_dept_id, create_time, --- update_by, update_time, del_flag --- ----------------------------------------------------------------------------- - --- ========== 2.1.1 租户管理 ========== -DROP TABLE IF EXISTS `sys_tenant`; -CREATE TABLE `sys_tenant` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `tenant_no` VARCHAR(64) NOT NULL COMMENT '租户编号(业务编码,如 zkxw)', - `tenant_name` VARCHAR(200) NOT NULL COMMENT '租户名称', - `contact_name` VARCHAR(100) DEFAULT NULL COMMENT '联系人姓名', - `contact_phone` VARCHAR(32) DEFAULT NULL COMMENT '联系电话', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_sys_tenant_no` (`tenant_no`, `del_flag`), - KEY `idx_sys_tenant_del` (`del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户信息'; - --- ========== 2.1.2 字典管理 ========== -DROP TABLE IF EXISTS `sys_dict_type`; -CREATE TABLE `sys_dict_type` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `dict_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典名称', - `dict_type` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典类型编码', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_dict_type` (`tenant_no`, `dict_type`, `del_flag`), - KEY `idx_dict_type_tenant` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='字典类型'; - -DROP TABLE IF EXISTS `sys_dict_data`; -CREATE TABLE `sys_dict_data` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `dict_label` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典标签', - `dict_value` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典键值', - `dict_type` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典类型', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_dict_data_type` (`tenant_no`, `dict_type`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='字典数据'; - --- ========== 2.1.3 组织管理 ========== -DROP TABLE IF EXISTS `sys_dept`; -CREATE TABLE `sys_dept` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `parent_id` VARCHAR(64) NOT NULL DEFAULT '0' COMMENT '父组织id,0为根', - `dept_name` VARCHAR(200) NOT NULL COMMENT '组织名称', - `dept_category_code` VARCHAR(64) DEFAULT NULL COMMENT '组织类别编码(字典 zzlbbm)', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '部门状态(字典 bmzt:0停用 1正常)', - `leader` VARCHAR(100) DEFAULT NULL COMMENT '部门负责人', - `leader_phone` VARCHAR(32) DEFAULT NULL COMMENT '部门负责人联系电话', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_dept_parent` (`parent_id`, `del_flag`), - KEY `idx_dept_tenant` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='组织/部门'; - --- ========== 2.1.4 用户管理 ========== -DROP TABLE IF EXISTS `sys_user`; -CREATE TABLE `sys_user` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `dept_id` VARCHAR(64) DEFAULT NULL COMMENT '部门ID', - `user_no` VARCHAR(30) NOT NULL COMMENT '用户账号', - `nick_name` VARCHAR(30) NOT NULL COMMENT '用户名', - `email` VARCHAR(50) DEFAULT '' COMMENT '用户邮箱', - `phone_no` VARCHAR(11) DEFAULT '' COMMENT '手机号码', - `sex` CHAR(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', - `avatar` VARCHAR(512) DEFAULT NULL COMMENT '头像(minio完整地址)', - `password` VARCHAR(100) DEFAULT '' COMMENT '密码', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '帐号状态(1正常 0停用)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_user_no` (`tenant_no`, `user_no`, `del_flag`), - KEY `idx_user_dept` (`dept_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户'; - --- ========== 2.1.5 角色管理 ========== -DROP TABLE IF EXISTS `sys_role`; -CREATE TABLE `sys_role` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `role_name` VARCHAR(30) NOT NULL COMMENT '角色名称', - `data_scope` CHAR(1) DEFAULT '1' COMMENT '数据权限(1全部 2本部门 3本部门及以下 4仅本人 5自定义)', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '角色状态(1正常 0停用)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_role_tenant` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色'; - -DROP TABLE IF EXISTS `sys_user_role`; -CREATE TABLE `sys_user_role` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `user_id` VARCHAR(64) NOT NULL COMMENT '用户ID', - `role_id` VARCHAR(64) NOT NULL COMMENT '角色ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_user_role` (`user_id`, `role_id`, `del_flag`), - KEY `idx_user_role_role` (`role_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户-角色关联'; - -DROP TABLE IF EXISTS `sys_role_menu`; -CREATE TABLE `sys_role_menu` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `role_id` VARCHAR(64) NOT NULL COMMENT '角色ID', - `menu_id` VARCHAR(64) NOT NULL COMMENT '菜单ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_role_menu` (`role_id`, `menu_id`, `del_flag`), - KEY `idx_role_menu_menu` (`menu_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色-菜单关联'; - -DROP TABLE IF EXISTS `sys_role_data_scope_detail`; -CREATE TABLE `sys_role_data_scope_detail` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `role_id` VARCHAR(64) NOT NULL COMMENT '角色ID', - `scope_type` VARCHAR(20) NOT NULL COMMENT '范围类型:dept部门 user用户', - `scope_value` VARCHAR(100) NOT NULL COMMENT '范围值:部门ID/用户ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_role_scope_role` (`role_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色自定义数据权限明细'; - --- ========== 2.1.6 菜单管理 ========== -DROP TABLE IF EXISTS `sys_menu`; -CREATE TABLE `sys_menu` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `menu_name` VARCHAR(50) NOT NULL COMMENT '菜单名称', - `parent_id` VARCHAR(64) NOT NULL DEFAULT '0' COMMENT '父菜单ID,0为根', - `menu_type` CHAR(1) DEFAULT 'M' COMMENT '类型 M目录 C菜单 F按钮(扩展)', - `path` VARCHAR(200) DEFAULT '' COMMENT '路由路径(扩展)', - `component` VARCHAR(255) DEFAULT NULL COMMENT '组件路径(扩展)', - `perms` VARCHAR(100) DEFAULT NULL COMMENT '权限标识(扩展)', - `is_frame` INT(1) DEFAULT 1 COMMENT '是否外链(1是 0否)', - `visible` CHAR(1) DEFAULT '1' COMMENT '显示状态(1显示 0隐藏)', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '菜单状态(1正常 0停用)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_menu_parent` (`parent_id`, `del_flag`), - KEY `idx_menu_tenant` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单'; - --- ========== 2.1.7 站点管理 ========== -DROP TABLE IF EXISTS `sys_site`; -CREATE TABLE `sys_site` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `site_name` VARCHAR(200) NOT NULL COMMENT '站点名称', - `site_code` VARCHAR(64) NOT NULL COMMENT '站点编码', - `site_type` VARCHAR(64) DEFAULT NULL COMMENT '站点类型(字典 zdlx)', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '状态(1正常 0停用)', - `address` VARCHAR(512) DEFAULT NULL COMMENT '详细地址', - `contact_name` VARCHAR(100) DEFAULT NULL COMMENT '联系人', - `contact_phone` VARCHAR(32) DEFAULT NULL COMMENT '联系电话', - `business_scope` VARCHAR(256) DEFAULT NULL COMMENT '业务范围(字典 zdywfw,多值可逗号分隔)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_site_code` (`tenant_no`, `site_code`, `del_flag`), - KEY `idx_site_tenant` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点'; - --- ========== 2.1.8 职位管理 ========== -DROP TABLE IF EXISTS `sys_post`; -CREATE TABLE `sys_post` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `post_name` VARCHAR(100) NOT NULL COMMENT '职位名称', - `post_code` VARCHAR(64) NOT NULL COMMENT '职位编码', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '状态(1正常 0停用)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_post_code` (`tenant_no`, `post_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='职位'; - -DROP TABLE IF EXISTS `sys_user_post`; -CREATE TABLE `sys_user_post` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `user_id` VARCHAR(64) NOT NULL COMMENT '用户ID', - `post_id` VARCHAR(64) NOT NULL COMMENT '职位ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_user_post` (`user_id`, `post_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户-职位关联'; - --- ========== 2.1.9 物品单位 ========== -DROP TABLE IF EXISTS `biz_item_unit`; -CREATE TABLE `biz_item_unit` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `unit_code` VARCHAR(64) NOT NULL COMMENT '单位编码', - `unit_name` VARCHAR(100) NOT NULL COMMENT '单位名称', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_item_unit` (`tenant_no`, `unit_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物品单位'; - --- ========== 2.1.10 快递公司 ========== -DROP TABLE IF EXISTS `biz_express_company`; -CREATE TABLE `biz_express_company` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `express_code` VARCHAR(64) NOT NULL COMMENT '快递公司编码', - `express_name` VARCHAR(200) NOT NULL COMMENT '快递公司名称', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_express` (`tenant_no`, `express_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='快递公司'; - --- ========== 2.1.11 工作流(概要模型,可随实现扩展) ========== -DROP TABLE IF EXISTS `wf_process_def`; -CREATE TABLE `wf_process_def` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `process_code` VARCHAR(64) NOT NULL COMMENT '流程编码', - `process_name` VARCHAR(200) NOT NULL COMMENT '流程名称', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '1启用 0停用', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_wf_def` (`tenant_no`, `process_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程定义'; - -DROP TABLE IF EXISTS `wf_process_version`; -CREATE TABLE `wf_process_version` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `process_def_id` VARCHAR(64) NOT NULL COMMENT '流程定义ID', - `version_no` INT NOT NULL COMMENT '版本号,递增', - `graph_json` MEDIUMTEXT COMMENT '节点与连线等配置(JSON 字符串)', - `is_latest` CHAR(1) DEFAULT '0' COMMENT '是否最新版本(1是 0否)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_wf_ver_def` (`process_def_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程定义版本'; - -DROP TABLE IF EXISTS `wf_node`; -CREATE TABLE `wf_node` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `process_version_id` VARCHAR(64) NOT NULL COMMENT '流程版本ID', - `node_key` VARCHAR(64) NOT NULL COMMENT '节点唯一键(画布内)', - `node_type` VARCHAR(32) NOT NULL COMMENT 'start/end/approval/condition 等', - `node_name` VARCHAR(200) DEFAULT NULL COMMENT '节点名称', - `config_json` MEDIUMTEXT COMMENT '审批人/SQL/会签策略等配置', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_wf_node_ver` (`process_version_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程节点'; - -DROP TABLE IF EXISTS `wf_transition`; -CREATE TABLE `wf_transition` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `process_version_id` VARCHAR(64) NOT NULL COMMENT '流程版本ID', - `from_node_key` VARCHAR(64) NOT NULL COMMENT '起始节点key', - `to_node_key` VARCHAR(64) NOT NULL COMMENT '目标节点key', - `condition_expr` VARCHAR(512) DEFAULT NULL COMMENT '条件表达式或分支说明', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_wf_trans_ver` (`process_version_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程连线/转移'; - -DROP TABLE IF EXISTS `wf_process_instance`; -CREATE TABLE `wf_process_instance` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `process_version_id` VARCHAR(64) NOT NULL COMMENT '发起时锁定的流程版本ID', - `business_type` VARCHAR(64) DEFAULT NULL COMMENT '业务类型', - `business_id` VARCHAR(64) DEFAULT NULL COMMENT '业务主键', - `starter_user_id` VARCHAR(64) DEFAULT NULL COMMENT '发起人', - `variables_json` MEDIUMTEXT COMMENT '流程变量 JSON', - `status` VARCHAR(32) DEFAULT NULL COMMENT 'running/completed/rejected 等', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_wf_inst_biz` (`business_type`, `business_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程实例'; - -DROP TABLE IF EXISTS `wf_task`; -CREATE TABLE `wf_task` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `instance_id` VARCHAR(64) NOT NULL COMMENT '流程实例ID', - `node_key` VARCHAR(64) DEFAULT NULL COMMENT '当前节点key', - `assignee_user_id` VARCHAR(64) DEFAULT NULL COMMENT '待办人', - `status` VARCHAR(32) DEFAULT NULL COMMENT 'pending/approved/rejected 等', - `comment_text` VARCHAR(512) DEFAULT NULL COMMENT '审批意见', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_wf_task_inst` (`instance_id`, `del_flag`), - KEY `idx_wf_task_user` (`assignee_user_id`, `status`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程任务/待办'; - --- ========== 2.2.1 客户管理 customer ========== -DROP TABLE IF EXISTS `biz_customer`; -CREATE TABLE `biz_customer` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `customer_name` VARCHAR(100) NOT NULL COMMENT '客户姓名', - `sex` CHAR(1) DEFAULT '2' COMMENT '性别(0男 1女 2未知)', - `id_card_no` VARCHAR(32) DEFAULT NULL COMMENT '身份证号', - `age` INT DEFAULT NULL COMMENT '年龄', - `phone_no` VARCHAR(32) DEFAULT NULL COMMENT '联系电话', - `customer_tags` VARCHAR(512) DEFAULT NULL COMMENT '客户标签,逗号分隔', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_customer_idcard` (`tenant_no`, `id_card_no`, `del_flag`), - KEY `idx_customer_name` (`tenant_no`, `customer_name`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户信息'; - -DROP TABLE IF EXISTS `biz_customer_follow`; -CREATE TABLE `biz_customer_follow` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `customer_id` VARCHAR(64) NOT NULL COMMENT '客户ID', - `follow_time` DATETIME NOT NULL COMMENT '随访时间', - `follow_desc` VARCHAR(2000) DEFAULT NULL COMMENT '随访描述', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_follow_customer` (`customer_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户随访'; - --- ========== 2.2.2 细胞样本类型 ========== -DROP TABLE IF EXISTS `biz_cell_sample_type`; -CREATE TABLE `biz_cell_sample_type` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `type_code` VARCHAR(64) NOT NULL COMMENT '样本类型编码', - `type_name` VARCHAR(200) NOT NULL COMMENT '样本类型名称', - `storage_years` INT DEFAULT NULL COMMENT '样本存储年份', - `storage_price` DECIMAL(18,2) DEFAULT NULL COMMENT '样本存储价格(元)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_sample_type` (`tenant_no`, `type_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞样本类型'; - --- ========== 2.2.3 采集地区及医院(1 地区 : N 医院) ========== -DROP TABLE IF EXISTS `biz_collection_hospital`; -DROP TABLE IF EXISTS `biz_collection_region`; -CREATE TABLE `biz_collection_region` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `region_name` VARCHAR(200) NOT NULL COMMENT '地区名称', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_region_tenant` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集地区'; - -CREATE TABLE `biz_collection_hospital` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `region_id` VARCHAR(64) NOT NULL COMMENT '采集地区ID', - `hospital_name` VARCHAR(200) NOT NULL COMMENT '医院名称', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_hospital_region` (`region_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集地区下属医院'; - --- ========== 2.2.4 采集协议号 ========== -DROP TABLE IF EXISTS `biz_collection_protocol_no`; -CREATE TABLE `biz_collection_protocol_no` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `protocol_no` VARCHAR(32) NOT NULL COMMENT '采集协议号', - `issued` TINYINT NOT NULL DEFAULT 0 COMMENT '是否下发(1是 0否)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_protocol_no` (`tenant_no`, `protocol_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议号池'; - --- ========== 2.2.5 样本编码 ========== -DROP TABLE IF EXISTS `biz_sample_code`; -CREATE TABLE `biz_sample_code` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `sample_code` VARCHAR(32) NOT NULL COMMENT '样本编码', - `issued` TINYINT NOT NULL DEFAULT 0 COMMENT '是否下发(1是 0否)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_sample_code` (`tenant_no`, `sample_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本编码池'; - --- ========== 2.2.6 采集协议单及子表 ========== -DROP TABLE IF EXISTS `biz_protocol_operation_log`; -DROP TABLE IF EXISTS `biz_protocol_storage_cert`; -DROP TABLE IF EXISTS `biz_protocol_fullmoon_todo`; -DROP TABLE IF EXISTS `biz_protocol_audit`; -DROP TABLE IF EXISTS `biz_protocol_party_customer`; -DROP TABLE IF EXISTS `biz_protocol_payment_detail`; -DROP TABLE IF EXISTS `biz_protocol_payment`; -DROP TABLE IF EXISTS `biz_protocol_sample_transport`; -DROP TABLE IF EXISTS `biz_protocol_sample`; -DROP TABLE IF EXISTS `biz_protocol_collection_info`; -DROP TABLE IF EXISTS `biz_collection_protocol`; - -CREATE TABLE `biz_collection_protocol` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `protocol_no_id` VARCHAR(64) DEFAULT NULL COMMENT '采集协议号表ID(选用号段)', - `protocol_no` VARCHAR(32) NOT NULL COMMENT '采集协议号展示', - `protocol_status` VARCHAR(16) NOT NULL DEFAULT 'dsx' COMMENT 'dsx待生效 sxz生效中 ygq已过期 yzf已作废 zky转科研', - `protocol_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '协议附件 minio 地址逗号分隔', - `register_date` CHAR(8) DEFAULT NULL COMMENT '登记日期YYYYMMDD', - `effective_time` DATETIME DEFAULT NULL COMMENT '协议生效时间', - `effective_desc` VARCHAR(512) DEFAULT NULL COMMENT '协议生效描述', - `expire_time` DATETIME DEFAULT NULL COMMENT '协议过期时间', - `expire_desc` VARCHAR(512) DEFAULT NULL COMMENT '协议过期描述', - `void_time` DATETIME DEFAULT NULL COMMENT '协议作废时间', - `void_desc` VARCHAR(512) DEFAULT NULL COMMENT '协议作废描述', - `to_research_time` DATETIME DEFAULT NULL COMMENT '协议转科研时间', - `to_research_desc` VARCHAR(512) DEFAULT NULL COMMENT '协议转科研描述', - `collector_user_id` VARCHAR(64) DEFAULT NULL COMMENT '采集专员用户ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_cp_status` (`tenant_no`, `protocol_status`, `del_flag`), - KEY `idx_cp_no` (`tenant_no`, `protocol_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单'; - -CREATE TABLE `biz_protocol_collection_info` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `region_id` VARCHAR(64) DEFAULT NULL COMMENT '采集地区ID', - `hospital_id` VARCHAR(64) DEFAULT NULL COMMENT '采集医院ID', - `medical_staff` VARCHAR(200) DEFAULT NULL COMMENT '采集医护', - `sales_user_id` VARCHAR(64) DEFAULT NULL COMMENT '销售人员用户ID', - `sample_collect_time` DATETIME DEFAULT NULL COMMENT '样本采集时间', - `maternal_blood_collect_time` DATETIME DEFAULT NULL COMMENT '母血采集时间', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_coll_one` (`collection_protocol_id`, `del_flag`), - KEY `idx_coll_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-采集信息'; - -CREATE TABLE `biz_protocol_sample` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `sample_type_id` VARCHAR(64) DEFAULT NULL COMMENT '样本类型ID', - `sample_code_id` VARCHAR(64) DEFAULT NULL COMMENT '样本编码表ID', - `sample_code` VARCHAR(32) DEFAULT NULL COMMENT '样本编码展示', - `collect_amount` DECIMAL(18,4) DEFAULT NULL COMMENT '采集量', - `unit_id` VARCHAR(64) DEFAULT NULL COMMENT '物品单位ID', - `price` DECIMAL(18,2) DEFAULT NULL COMMENT '价格', - `storage_years` INT DEFAULT NULL COMMENT '存储年限(年)', - `sample_status` VARCHAR(16) NOT NULL DEFAULT 'ycj' COMMENT 'ycj已采集 jcz检测中 yrk已入库 yzf已作废', - `storage_in_date` CHAR(8) DEFAULT NULL COMMENT '入库日期YYYYMMDD', - `detect_start_date` CHAR(8) DEFAULT NULL COMMENT '检测开始日期', - `void_date` CHAR(8) DEFAULT NULL COMMENT '作废日期', - `storage_expire_date` CHAR(8) DEFAULT NULL COMMENT '存储到期日期=入库+年限', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_sample_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-样本信息'; - -CREATE TABLE `biz_protocol_sample_transport` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `sample_id` VARCHAR(64) NOT NULL COMMENT '样本ID', - `transport_type` VARCHAR(16) NOT NULL COMMENT 'kdys快递运输 ryxd人员携带', - `express_company_id` VARCHAR(64) DEFAULT NULL COMMENT '快递公司ID', - `waybill_no` VARCHAR(128) DEFAULT NULL COMMENT '快递单号', - `dest_site_id` VARCHAR(64) DEFAULT NULL COMMENT '目的站点ID', - `express_fee` DECIMAL(18,2) DEFAULT NULL COMMENT '快递费用(元)', - `eta_time` DATETIME DEFAULT NULL COMMENT '预计到达时间', - `carrier_name` VARCHAR(100) DEFAULT NULL COMMENT '携带人员姓名', - `carrier_phone` VARCHAR(32) DEFAULT NULL COMMENT '携带人员联系电话', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_transport_sample` (`sample_id`, `del_flag`), - KEY `idx_transport_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-样本运输'; - -CREATE TABLE `biz_protocol_payment` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `pay_mode` VARCHAR(8) NOT NULL COMMENT 'dj趸交 fq分期', - `contract_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '合同金额', - `installment_count` INT DEFAULT NULL COMMENT '分期期数', - `installment_interval_months` INT DEFAULT NULL COMMENT '每期间隔(月)', - `lump_sum_deadline` CHAR(8) DEFAULT NULL COMMENT '趸交截止时间YYYYMMDD', - `installment_start_date` CHAR(8) DEFAULT NULL COMMENT '分期开始时间YYYYMMDD', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_payment_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-缴费主表'; - -CREATE TABLE `biz_protocol_payment_detail` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `payment_id` VARCHAR(64) NOT NULL COMMENT '缴费主表ID', - `transfer_type` VARCHAR(16) DEFAULT NULL COMMENT 'wxzz微信 zzbzz支付宝 yhkzz银行卡 xj现金', - `transfer_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '转账金额', - `transfer_time` DATETIME DEFAULT NULL COMMENT '转账时间', - `transfer_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '转账附件逗号分隔', - `finance_confirm_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '财务确认附件逗号分隔', - `pay_status` VARCHAR(32) NOT NULL DEFAULT 'dfj' COMMENT 'dfj待缴费 jfqrz缴费确认中 yjf已缴费 tfqrz退费确认中 ytf已退费', - `installment_no` INT DEFAULT NULL COMMENT '分期期序号,趸交为空', - `installment_due_date` CHAR(8) DEFAULT NULL COMMENT '分期应缴费时间YYYYMMDD', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_payd_pay` (`payment_id`, `del_flag`), - KEY `idx_payd_status` (`tenant_no`, `pay_status`, `del_flag`), - KEY `idx_payd_due` (`installment_due_date`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-缴费明细'; - -CREATE TABLE `biz_protocol_party_customer` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `customer_id` VARCHAR(64) DEFAULT NULL COMMENT '关联客户表 customer', - `mother_name` VARCHAR(100) DEFAULT NULL COMMENT '母亲姓名', - `mother_phone` VARCHAR(32) DEFAULT NULL COMMENT '母亲联系电话', - `mother_id_card` VARCHAR(32) DEFAULT NULL COMMENT '母亲身份证号', - `mother_birth_ym` CHAR(6) DEFAULT NULL COMMENT '母亲出生年月YYYYMM', - `mother_ethnic` VARCHAR(32) DEFAULT NULL COMMENT '母亲民族', - `mother_address` VARCHAR(512) DEFAULT NULL COMMENT '母亲家庭住址', - `father_name` VARCHAR(100) DEFAULT NULL COMMENT '父亲姓名', - `father_phone` VARCHAR(32) DEFAULT NULL COMMENT '父亲电话', - `father_id_card` VARCHAR(32) DEFAULT NULL COMMENT '父亲身份证号', - `newborn_delivery_time` DATETIME DEFAULT NULL COMMENT '新生儿分娩时间', - `newborn_sex` CHAR(1) DEFAULT NULL COMMENT '1男 0女', - `delivery_mode` VARCHAR(16) DEFAULT NULL COMMENT 'sc顺产 pgc剖宫产', - `birth_order` INT DEFAULT NULL COMMENT '胎次', - `newborn_weight_gram` INT DEFAULT NULL COMMENT '新生儿体重(克)', - `apgar_1min` INT DEFAULT NULL COMMENT '新生儿评分1分钟', - `apgar_5min` INT DEFAULT NULL COMMENT '新生儿评分5分钟', - `collector_user_id` VARCHAR(64) DEFAULT NULL COMMENT '采集专员用户ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_party_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-当事人/产妇新生儿信息'; - -CREATE TABLE `biz_protocol_audit` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `auditor_user_id` VARCHAR(64) DEFAULT NULL COMMENT '审核人', - `audit_remark` VARCHAR(1000) DEFAULT NULL COMMENT '审核备注', - `audit_status` VARCHAR(16) NOT NULL COMMENT 'shtg审核通过 shth审核退回', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_audit_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-审核记录'; - -CREATE TABLE `biz_protocol_fullmoon_todo` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `todo_status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '1已办报销 0未办保险', - `handle_date` CHAR(8) DEFAULT NULL COMMENT '办理日期', - `handle_remark` VARCHAR(512) DEFAULT NULL COMMENT '办理备注', - `insurance_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '保险附件逗号分隔', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_fullmoon_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='新生儿满月待办'; - -CREATE TABLE `biz_protocol_storage_cert` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `make_status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '1已制作 0未制作', - `cert_image_urls` VARCHAR(4000) DEFAULT NULL COMMENT '储存证图片逗号分隔', - `make_date` CHAR(8) DEFAULT NULL COMMENT '制作日期', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_cert_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='储存证'; - -CREATE TABLE `biz_protocol_operation_log` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `collection_protocol_id` VARCHAR(64) NOT NULL COMMENT '采集协议单ID', - `operation_code` VARCHAR(64) NOT NULL COMMENT '操作编码', - `operation_name` VARCHAR(200) NOT NULL COMMENT '操作名称', - `operation_detail` VARCHAR(4000) DEFAULT NULL COMMENT '操作详情/变更摘要', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_oplog_cp` (`collection_protocol_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单操作日志'; - --- ========== 2.3 中试服务:细胞产品与价格体系 ========== -DROP TABLE IF EXISTS `biz_cell_product_project_system_item`; -DROP TABLE IF EXISTS `biz_cell_product_project_system`; -DROP TABLE IF EXISTS `biz_cell_product_price`; -DROP TABLE IF EXISTS `biz_cell_product_spec`; -DROP TABLE IF EXISTS `biz_cell_product_type`; - -CREATE TABLE `biz_cell_product_type` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `product_type_code` VARCHAR(64) NOT NULL COMMENT '细胞产品类型编码', - `product_type_name` VARCHAR(200) NOT NULL COMMENT '细胞产品类型名称', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_cpt` (`tenant_no`, `product_type_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品类型'; - -CREATE TABLE `biz_cell_product_spec` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `spec_code` VARCHAR(64) NOT NULL COMMENT '规格编码', - `spec_name` VARCHAR(200) NOT NULL COMMENT '规格名称', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_cps` (`tenant_no`, `spec_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品规格'; - -CREATE TABLE `biz_cell_product_price` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `project_name` VARCHAR(200) NOT NULL COMMENT '项目名称', - `project_code` VARCHAR(64) NOT NULL COMMENT '项目编码', - `cell_type_code` VARCHAR(64) DEFAULT NULL COMMENT '细胞类型编码', - `cell_type_name` VARCHAR(200) DEFAULT NULL COMMENT '细胞类型名称', - `spec_text` VARCHAR(200) DEFAULT NULL COMMENT '规格(展示或关联规格编码)', - `spec_id` VARCHAR(64) DEFAULT NULL COMMENT '规格ID', - `cell_product_type_id` VARCHAR(64) DEFAULT NULL COMMENT '细胞产品类型ID', - `unit_price` DECIMAL(18,2) DEFAULT NULL COMMENT '单价(元)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_cpp_tenant` (`tenant_no`, `del_flag`), - KEY `idx_cpp_code` (`tenant_no`, `project_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品价格体系'; - -CREATE TABLE `biz_cell_product_project_system` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `system_name` VARCHAR(200) NOT NULL COMMENT '体系名称', - `system_code` VARCHAR(64) NOT NULL COMMENT '体系编码', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '1正常 0停用', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_cpps` (`tenant_no`, `system_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品项目价格体系'; - -CREATE TABLE `biz_cell_product_project_system_item` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `project_system_id` VARCHAR(64) NOT NULL COMMENT '项目价格体系ID', - `source_price_id` VARCHAR(64) DEFAULT NULL COMMENT '来源细胞产品价格体系行ID', - `project_name` VARCHAR(200) DEFAULT NULL COMMENT '项目名称', - `project_code` VARCHAR(64) DEFAULT NULL COMMENT '项目编码', - `cell_type_code` VARCHAR(64) DEFAULT NULL COMMENT '细胞类型编码', - `cell_type_name` VARCHAR(200) DEFAULT NULL COMMENT '细胞类型名称', - `spec_text` VARCHAR(200) DEFAULT NULL COMMENT '规格', - `spec_id` VARCHAR(64) DEFAULT NULL COMMENT '规格ID', - `cell_product_type_id` VARCHAR(64) DEFAULT NULL COMMENT '细胞产品类型ID', - `unit_price` DECIMAL(18,2) DEFAULT NULL COMMENT '单价(元)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_cppsi_sys` (`project_system_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目价格体系-细胞价格明细'; - --- ========== 2.3.5 渠道及子表 ========== -DROP TABLE IF EXISTS `biz_channel_agreement_product_price`; -DROP TABLE IF EXISTS `biz_channel_agreement`; -DROP TABLE IF EXISTS `biz_channel_mail_address`; -DROP TABLE IF EXISTS `biz_channel_invoice`; -DROP TABLE IF EXISTS `biz_channel`; - -CREATE TABLE `biz_channel` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `channel_name` VARCHAR(200) NOT NULL COMMENT '渠道名称', - `credit_code` VARCHAR(64) DEFAULT NULL COMMENT '统一社会信用代码', - `channel_type` VARCHAR(16) DEFAULT NULL COMMENT 'yy医院 hzjg合作机构 zs诊所 zfjg政府机构 qt其他', - `contact_name` VARCHAR(100) DEFAULT NULL COMMENT '联系人姓名', - `contact_phone` VARCHAR(32) DEFAULT NULL COMMENT '联系电话', - `contact_email` VARCHAR(128) DEFAULT NULL COMMENT '联系邮箱', - `protocol_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '渠道协议附件逗号分隔', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_channel_tenant` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道信息'; - -CREATE TABLE `biz_channel_invoice` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `channel_id` VARCHAR(64) NOT NULL COMMENT '渠道ID', - `company_name` VARCHAR(200) NOT NULL COMMENT '单位名称', - `tax_no` VARCHAR(64) DEFAULT NULL COMMENT '税号', - `company_address` VARCHAR(512) DEFAULT NULL COMMENT '单位地址', - `company_phone` VARCHAR(64) DEFAULT NULL COMMENT '电话', - `bank_name` VARCHAR(200) DEFAULT NULL COMMENT '开户银行', - `bank_account` VARCHAR(64) DEFAULT NULL COMMENT '银行账号', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_ch_inv_channel` (`channel_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道开票信息'; - -CREATE TABLE `biz_channel_mail_address` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `channel_id` VARCHAR(64) NOT NULL COMMENT '渠道ID', - `mail_address` VARCHAR(512) NOT NULL COMMENT '邮寄地址', - `mail_contact` VARCHAR(100) DEFAULT NULL COMMENT '邮寄联系人', - `mail_phone` VARCHAR(32) DEFAULT NULL COMMENT '邮寄联系电话', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_ch_mail_channel` (`channel_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道邮寄地址'; - -CREATE TABLE `biz_channel_agreement` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `channel_id` VARCHAR(64) NOT NULL COMMENT '渠道ID', - `agreement_type` VARCHAR(16) NOT NULL COMMENT 'kyxy科研 zsxy中试 lcxy临床', - `business_type` VARCHAR(32) DEFAULT NULL COMMENT 'kyyy科研应用/中试应用/临床应用(随协议类型)', - `settle_mode` VARCHAR(16) DEFAULT NULL COMMENT 'yj月结 jj季结 nj年结 dcjs单次结算', - `credit_limit_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '授信额度(元)', - `effective_date` CHAR(8) DEFAULT NULL COMMENT '生效日期YYYYMMDD', - `expire_date` CHAR(8) DEFAULT NULL COMMENT '到期日期YYYYMMDD', - `agreement_status` VARCHAR(16) NOT NULL DEFAULT 'sxz' COMMENT 'sxz生效中 yzf已作废', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_ch_agr_channel` (`channel_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道协议'; - -CREATE TABLE `biz_channel_agreement_product_price` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `channel_agreement_id` VARCHAR(64) NOT NULL COMMENT '渠道协议ID', - `project_name` VARCHAR(200) DEFAULT NULL COMMENT '项目名称', - `project_code` VARCHAR(64) DEFAULT NULL COMMENT '项目编码', - `cell_type_code` VARCHAR(64) DEFAULT NULL COMMENT '细胞类型编码', - `cell_type_name` VARCHAR(200) DEFAULT NULL COMMENT '细胞类型名称', - `spec_text` VARCHAR(200) DEFAULT NULL COMMENT '规格', - `original_price` DECIMAL(18,2) DEFAULT NULL COMMENT '原价(元)', - `agreement_price` DECIMAL(18,2) DEFAULT NULL COMMENT '协议价(元)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_cap_agr` (`channel_agreement_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道协议-细胞产品价格'; - --- ========== 2.3.6 体检机构 ========== -DROP TABLE IF EXISTS `biz_checkup_institution_project`; -DROP TABLE IF EXISTS `biz_checkup_institution_invoice`; -DROP TABLE IF EXISTS `biz_checkup_institution`; - -CREATE TABLE `biz_checkup_institution` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `institution_name` VARCHAR(200) NOT NULL COMMENT '体检机构名称', - `institution_address` VARCHAR(512) DEFAULT NULL COMMENT '体检机构地址', - `contact_name` VARCHAR(100) DEFAULT NULL COMMENT '联系人', - `contact_phone` VARCHAR(32) DEFAULT NULL COMMENT '联系人电话', - `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '1正常 0停用', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_ck_inst` (`tenant_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检机构'; - -CREATE TABLE `biz_checkup_institution_invoice` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `checkup_institution_id` VARCHAR(64) NOT NULL COMMENT '体检机构ID', - `company_name` VARCHAR(200) NOT NULL COMMENT '单位名称', - `tax_no` VARCHAR(64) DEFAULT NULL COMMENT '税号', - `company_address` VARCHAR(512) DEFAULT NULL COMMENT '单位地址', - `company_phone` VARCHAR(64) DEFAULT NULL COMMENT '电话', - `bank_name` VARCHAR(200) DEFAULT NULL COMMENT '开户银行', - `bank_account` VARCHAR(64) DEFAULT NULL COMMENT '银行账号', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_ck_inv` (`checkup_institution_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检机构开票信息'; - -CREATE TABLE `biz_checkup_institution_project` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `checkup_institution_id` VARCHAR(64) NOT NULL COMMENT '体检机构ID', - `project_name` VARCHAR(200) NOT NULL COMMENT '项目名称', - `project_fee` DECIMAL(18,2) DEFAULT NULL COMMENT '项目费用(元)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_ck_proj` (`checkup_institution_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检机构项目及价格'; - --- ========== 2.3.7 / 2.3.8 渠道服务购买订单及财务确认 ========== -DROP TABLE IF EXISTS `biz_channel_service_order_finance_confirm`; -DROP TABLE IF EXISTS `biz_channel_service_order_product`; -DROP TABLE IF EXISTS `biz_channel_service_order`; - -CREATE TABLE `biz_channel_service_order` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `channel_id` VARCHAR(64) NOT NULL COMMENT '渠道ID', - `channel_name` VARCHAR(200) DEFAULT NULL COMMENT '渠道名称冗余', - `dept_name` VARCHAR(200) DEFAULT NULL COMMENT '科室/部门', - `order_type` VARCHAR(16) NOT NULL COMMENT 'yfk预付款 gmcp购买产品', - `prepay_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '预付金额(订单类型yfk)', - `purchase_total_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '购买总金额(订单类型gmcp)', - `order_status` VARCHAR(16) NOT NULL DEFAULT 'sxz' COMMENT 'sxz生效中 yzf已作废', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_cso_channel` (`channel_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道服务购买订单'; - -CREATE TABLE `biz_channel_service_order_product` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `service_order_id` VARCHAR(64) NOT NULL COMMENT '渠道服务购买订单ID', - `project_code` VARCHAR(64) DEFAULT NULL COMMENT '项目编码', - `project_name` VARCHAR(200) DEFAULT NULL COMMENT '项目名称', - `cell_type` VARCHAR(200) DEFAULT NULL COMMENT '细胞类型', - `spec_text` VARCHAR(200) DEFAULT NULL COMMENT '规格', - `agreement_unit_price` DECIMAL(18,2) DEFAULT NULL COMMENT '协议单价', - `buy_qty` DECIMAL(18,4) DEFAULT NULL COMMENT '购买数量', - `buy_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '购买金额', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_csop_order` (`service_order_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道服务购买订单-产品明细'; - -CREATE TABLE `biz_channel_service_order_finance_confirm` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `service_order_id` VARCHAR(64) NOT NULL COMMENT '渠道服务购买订单ID', - `confirm_time` DATETIME DEFAULT NULL COMMENT '确认时间', - `confirm_desc` VARCHAR(2000) DEFAULT NULL COMMENT '确认描述', - `confirm_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '确认附件逗号分隔', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_csofc_order` (`service_order_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道服务购买订单-财务确认'; - --- ========== 2.3.9 中试申请单及嵌套子表 ========== -DROP TABLE IF EXISTS `biz_pilot_checkup_actual_item`; -DROP TABLE IF EXISTS `biz_pilot_checkup_reserve_item`; -DROP TABLE IF EXISTS `biz_pilot_checkup`; -DROP TABLE IF EXISTS `biz_pilot_delivery_method`; -DROP TABLE IF EXISTS `biz_pilot_delivery_material`; -DROP TABLE IF EXISTS `biz_pilot_delivery`; -DROP TABLE IF EXISTS `biz_pilot_infusion_confirm`; -DROP TABLE IF EXISTS `biz_pilot_infusion_schedule`; -DROP TABLE IF EXISTS `biz_pilot_infusion_plan`; -DROP TABLE IF EXISTS `biz_pilot_patient`; -DROP TABLE IF EXISTS `biz_pilot_application`; - -CREATE TABLE `biz_pilot_application` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `channel_service_order_id` VARCHAR(64) NOT NULL COMMENT '渠道服务购买订单ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_pilot_app_order` (`channel_service_order_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道中试服务申请单'; - -CREATE TABLE `biz_pilot_patient` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `pilot_application_id` VARCHAR(64) NOT NULL COMMENT '中试申请单ID', - `customer_id` VARCHAR(64) DEFAULT NULL COMMENT '关联客户ID', - `patient_name` VARCHAR(100) DEFAULT NULL COMMENT '姓名', - `sex` CHAR(1) DEFAULT NULL COMMENT '性别', - `age` INT DEFAULT NULL COMMENT '年龄', - `id_card_no` VARCHAR(32) DEFAULT NULL COMMENT '身份证号', - `phone_no` VARCHAR(32) DEFAULT NULL COMMENT '联系电话', - `emergency_contact` VARCHAR(100) DEFAULT NULL COMMENT '紧急联系人', - `emergency_phone` VARCHAR(32) DEFAULT NULL COMMENT '紧急联系人电话', - `clinical_diagnosis` VARCHAR(2000) DEFAULT NULL COMMENT '临床诊断', - `diagnosis_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '诊断附件逗号分隔', - `need_checkup` TINYINT NOT NULL DEFAULT 1 COMMENT '是否需要体检 1需要 0不需要', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_pilot_patient_app` (`pilot_application_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-患者信息'; - -CREATE TABLE `biz_pilot_infusion_plan` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `pilot_application_id` VARCHAR(64) NOT NULL COMMENT '中试申请单ID', - `cell_type` VARCHAR(200) DEFAULT NULL COMMENT '细胞类型', - `spec_text` VARCHAR(200) DEFAULT NULL COMMENT '规格', - `total_course_count` INT DEFAULT NULL COMMENT '总疗程数', - `course_interval` VARCHAR(200) DEFAULT NULL COMMENT '疗程间隔描述', - `infusion_route` VARCHAR(200) DEFAULT NULL COMMENT '输注途径', - `infusion_start_date` CHAR(8) DEFAULT NULL COMMENT '输注开始日期YYYYMMDD', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_pip_app` (`pilot_application_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-输注方案'; - -CREATE TABLE `biz_pilot_infusion_schedule` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `infusion_plan_id` VARCHAR(64) NOT NULL COMMENT '输注方案ID', - `cell_type` VARCHAR(200) DEFAULT NULL COMMENT '细胞类型', - `spec_text` VARCHAR(200) DEFAULT NULL COMMENT '规格', - `infusion_time` DATETIME DEFAULT NULL COMMENT '输注时间', - `schedule_status` VARCHAR(16) NOT NULL DEFAULT 'dsc' COMMENT 'dsc待生产 ysc已生产 yjf已交付 yqx已取消', - `attachments` VARCHAR(4000) DEFAULT NULL COMMENT '附件逗号分隔', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_pis_plan` (`infusion_plan_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-输注计划'; - -CREATE TABLE `biz_pilot_infusion_confirm` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `infusion_schedule_id` VARCHAR(64) NOT NULL COMMENT '输注计划ID', - `confirm_user_id` VARCHAR(64) DEFAULT NULL COMMENT '确认人', - `confirm_time` DATETIME DEFAULT NULL COMMENT '确认时间', - `confirm_desc` VARCHAR(2000) DEFAULT NULL COMMENT '确认描述', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_pic_sched` (`infusion_schedule_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='输注计划-应用确认'; - -CREATE TABLE `biz_pilot_delivery` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `infusion_schedule_id` VARCHAR(64) NOT NULL COMMENT '输注计划ID', - `delivery_no` VARCHAR(64) DEFAULT NULL COMMENT '交付编号(可程序生成)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_pdel_sched` (`infusion_schedule_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='输注计划-交付信息'; - -CREATE TABLE `biz_pilot_delivery_material` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `delivery_id` VARCHAR(64) NOT NULL COMMENT '交付信息ID', - `material_name` VARCHAR(200) NOT NULL COMMENT '物料名称', - `qty` DECIMAL(18,4) DEFAULT NULL COMMENT '数量', - `unit_id` VARCHAR(64) DEFAULT NULL COMMENT '单位ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_pdm_del` (`delivery_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交付信息-物料清单'; - -CREATE TABLE `biz_pilot_delivery_method` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `delivery_id` VARCHAR(64) NOT NULL COMMENT '交付信息ID', - `delivery_mode` VARCHAR(16) NOT NULL COMMENT 'qdzt渠道自提 kdfh快递发货', - `delivery_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '交付附件', - `ship_time` DATETIME DEFAULT NULL COMMENT '发运时间', - `handover_temp` VARCHAR(64) DEFAULT NULL COMMENT '交接温度', - `pickup_person` VARCHAR(100) DEFAULT NULL COMMENT '自提人员', - `pickup_phone` VARCHAR(32) DEFAULT NULL COMMENT '联系电话', - `sender_name` VARCHAR(100) DEFAULT NULL COMMENT '发货人', - `express_company_id` VARCHAR(64) DEFAULT NULL COMMENT '快递公司ID', - `waybill_no` VARCHAR(128) DEFAULT NULL COMMENT '快递单号', - `express_fee` DECIMAL(18,2) DEFAULT NULL COMMENT '快递费用', - `eta_time` DATETIME DEFAULT NULL COMMENT '预计到达时间', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_pdmeth_del` (`delivery_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交付信息-交付方式'; - -CREATE TABLE `biz_pilot_checkup` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `pilot_application_id` VARCHAR(64) NOT NULL COMMENT '中试申请单ID', - `checkup_institution_id` VARCHAR(64) DEFAULT NULL COMMENT '体检机构ID', - `reserve_time` DATETIME DEFAULT NULL COMMENT '预约时间', - `reserve_fee` DECIMAL(18,2) DEFAULT NULL COMMENT '预约费用', - `actual_checkup_time` DATETIME DEFAULT NULL COMMENT '实际体检时间', - `actual_checkup_fee` DECIMAL(18,2) DEFAULT NULL COMMENT '实际体检费用', - `fee_settled` TINYINT DEFAULT 0 COMMENT '费用是否结算 1是 0否', - `checkup_status` VARCHAR(16) DEFAULT 'dtj' COMMENT 'dtj待体检 ytj已体检', - `fee_status` VARCHAR(16) DEFAULT 'wjs' COMMENT 'wjs未结算 yjs已结算', - `checkup_remark` VARCHAR(2000) DEFAULT NULL COMMENT '体检备注', - `report_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '体检报告附件', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_pchk_app` (`pilot_application_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-体检信息'; - -CREATE TABLE `biz_pilot_checkup_reserve_item` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `pilot_checkup_id` VARCHAR(64) NOT NULL COMMENT '中试申请单体检信息ID', - `project_name` VARCHAR(200) DEFAULT NULL COMMENT '项目名称', - `project_fee` DECIMAL(18,2) DEFAULT NULL COMMENT '项目费用(元)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_pcri_chk` (`pilot_checkup_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检预约项目明细'; - -CREATE TABLE `biz_pilot_checkup_actual_item` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `pilot_checkup_id` VARCHAR(64) NOT NULL COMMENT '中试申请单体检信息ID', - `project_name` VARCHAR(200) DEFAULT NULL COMMENT '项目名称', - `project_fee` DECIMAL(18,2) DEFAULT NULL COMMENT '项目费用(元)', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_pcai_chk` (`pilot_checkup_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检实际项目明细'; - --- ========== 2.4 运营支撑 ========== -DROP TABLE IF EXISTS `biz_clean_room_access_log`; -DROP TABLE IF EXISTS `biz_clean_room`; -CREATE TABLE `biz_clean_room` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `room_code` VARCHAR(64) NOT NULL COMMENT '编号', - `room_name` VARCHAR(200) NOT NULL COMMENT '名称', - `room_grade` VARCHAR(8) DEFAULT NULL COMMENT '等级 A/B/C/D', - `room_status` VARCHAR(16) NOT NULL DEFAULT 'zc' COMMENT 'zc正常 yc异常', - `area_sqm` DECIMAL(12,2) DEFAULT NULL COMMENT '面积m²', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_clean_code` (`tenant_no`, `room_code`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='洁净室信息'; - -CREATE TABLE `biz_clean_room_access_log` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `clean_room_id` VARCHAR(64) NOT NULL COMMENT '洁净室ID', - `user_id` VARCHAR(64) DEFAULT NULL COMMENT '人员用户ID', - `person_name` VARCHAR(100) DEFAULT NULL COMMENT '人员姓名(冗余)', - `dept_name` VARCHAR(200) DEFAULT NULL COMMENT '部门', - `enter_time` DATETIME NOT NULL COMMENT '进入时间', - `purpose` VARCHAR(512) DEFAULT NULL COMMENT '目的', - `leave_time` DATETIME DEFAULT NULL COMMENT '离开时间', - `body_temp` VARCHAR(32) DEFAULT NULL COMMENT '体温', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_crlog_room` (`clean_room_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='洁净室人员进出'; - -DROP TABLE IF EXISTS `biz_storage_facility_slot`; -DROP TABLE IF EXISTS `biz_storage_facility`; -CREATE TABLE `biz_storage_facility` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `parent_id` VARCHAR(64) NOT NULL DEFAULT '0' COMMENT '父节点ID,0根', - `node_name` VARCHAR(200) NOT NULL COMMENT '节点名称(罐/层/盒等)', - `node_type` VARCHAR(32) DEFAULT NULL COMMENT 'facility/layer/box 等扩展', - `is_leaf` TINYINT NOT NULL DEFAULT 0 COMMENT '是否叶子存储位 1是', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_sf_parent` (`parent_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞存储设施树节点'; - -CREATE TABLE `biz_storage_facility_slot` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `facility_node_id` VARCHAR(64) NOT NULL COMMENT '设施树叶子节点ID', - `slot_code` VARCHAR(64) DEFAULT NULL COMMENT '小格编码/坐标', - `slot_status` VARCHAR(16) DEFAULT NULL COMMENT '占用状态等扩展', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - KEY `idx_sfs_node` (`facility_node_id`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞存储设施储存位置(格子)'; - -DROP TABLE IF EXISTS `biz_exhibition_appointment`; -CREATE TABLE `biz_exhibition_appointment` ( - `id` VARCHAR(64) NOT NULL COMMENT '主键ID', - `appointment_no` VARCHAR(64) NOT NULL COMMENT '预约编号', - `visit_org` VARCHAR(200) DEFAULT NULL COMMENT '来访单位', - `contact_name` VARCHAR(100) DEFAULT NULL COMMENT '联系人', - `visit_date` CHAR(8) DEFAULT NULL COMMENT '到访日期YYYYMMDD', - `visitor_count` INT DEFAULT NULL COMMENT '人数', - `reception_user_id` VARCHAR(64) DEFAULT NULL COMMENT '接待人员用户ID', - `tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', - `order_num` INT DEFAULT NULL COMMENT '排序号', - `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', - `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人id', - `create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人id', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `del_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_exh_no` (`tenant_no`, `appointment_no`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='展馆预约'; - -SET FOREIGN_KEY_CHECKS = 1; - --- ----------------------------------------------------------------------------- --- 可选:租户与字典种子(可重复执行:不存在才插入) --- ----------------------------------------------------------------------------- -INSERT INTO `sys_tenant` (`id`, `tenant_no`, `tenant_name`, `contact_name`, `contact_phone`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), 'zkxw', '中科兴皖', NULL, NULL, '', 0 -FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_tenant` `t` WHERE `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); - -INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '组织类别', 'zzlbbm', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_type` `t` WHERE `t`.`dict_type` = 'zzlbbm' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '站点类型', 'zdlx', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_type` `t` WHERE `t`.`dict_type` = 'zdlx' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '站点业务范围', 'zdywfw', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_type` `t` WHERE `t`.`dict_type` = 'zdywfw' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '部门状态', 'bmzt', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_type` `t` WHERE `t`.`dict_type` = 'bmzt' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); - -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '公司', 'gs', 'zzlbbm', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zzlbbm' AND `t`.`dict_value` = 'gs' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '分公司', 'fgs', 'zzlbbm', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zzlbbm' AND `t`.`dict_value` = 'fgs' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '部门', 'bm', 'zzlbbm', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zzlbbm' AND `t`.`dict_value` = 'bm' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '中心基地', 'zxjd', 'zdlx', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zdlx' AND `t`.`dict_value` = 'zxjd' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '样本采集', 'ybcj', 'zdywfw', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zdywfw' AND `t`.`dict_value` = 'ybcj' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '样本制备', 'ybzb', 'zdywfw', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zdywfw' AND `t`.`dict_value` = 'ybzb' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '质量检测', 'zljc', 'zdywfw', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zdywfw' AND `t`.`dict_value` = 'zljc' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '细胞存储', 'xbcc', 'zdywfw', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'zdywfw' AND `t`.`dict_value` = 'xbcc' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '停用', '0', 'bmzt', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'bmzt' AND `t`.`dict_value` = '0' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); -INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `create_dept_id`, `del_flag`) -SELECT REPLACE(UUID(), '-', ''), '正常', '1', 'bmzt', 'zkxw', '', 0 FROM (SELECT 1 AS `x`) `d` -WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` `t` WHERE `t`.`dict_type` = 'bmzt' AND `t`.`dict_value` = '1' AND `t`.`tenant_no` = 'zkxw' AND `t`.`del_flag` = 0 LIMIT 1); diff --git a/docs/sql/zkxw.sql b/docs/sql/zkxw.sql new file mode 100644 index 00000000..fe805ed3 --- /dev/null +++ b/docs/sql/zkxw.sql @@ -0,0 +1,2550 @@ +/* + Navicat Premium Dump SQL + + Source Server : 47.99.164.66 hn02le.34lkdLKD + Source Server Type : MySQL + Source Server Version : 50736 (5.7.36) + Source Host : 47.99.164.66:3306 + Source Schema : zkxw + + Target Server Type : MySQL + Target Server Version : 50736 (5.7.36) + File Encoding : 65001 + + Date: 14/05/2026 10:09:33 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for biz_cell_product_price +-- ---------------------------- +DROP TABLE IF EXISTS `biz_cell_product_price`; +CREATE TABLE `biz_cell_product_price` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `project_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目名称', + `project_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目编码', + `cell_type_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型编码', + `cell_type_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型名称', + `spec_text` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格(展示或关联规格编码)', + `spec_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格ID', + `cell_product_type_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞产品类型ID', + `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价(元)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_cpp_tenant` (`tenant_no`,`del_flag`), + KEY `idx_cpp_code` (`tenant_no`,`project_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品价格体系'; + +-- ---------------------------- +-- Records of biz_cell_product_price +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_cell_product_project_system +-- ---------------------------- +DROP TABLE IF EXISTS `biz_cell_product_project_system`; +CREATE TABLE `biz_cell_product_project_system` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `system_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '体系名称', + `system_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '体系编码', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '1正常 0停用', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_cpps` (`tenant_no`,`system_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品项目价格体系'; + +-- ---------------------------- +-- Records of biz_cell_product_project_system +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_cell_product_project_system_item +-- ---------------------------- +DROP TABLE IF EXISTS `biz_cell_product_project_system_item`; +CREATE TABLE `biz_cell_product_project_system_item` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `project_system_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目价格体系ID', + `source_price_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源细胞产品价格体系行ID', + `project_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目名称', + `project_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目编码', + `cell_type_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型编码', + `cell_type_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型名称', + `spec_text` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格', + `spec_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格ID', + `cell_product_type_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞产品类型ID', + `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价(元)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_cppsi_sys` (`project_system_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目价格体系-细胞价格明细'; + +-- ---------------------------- +-- Records of biz_cell_product_project_system_item +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_cell_product_spec +-- ---------------------------- +DROP TABLE IF EXISTS `biz_cell_product_spec`; +CREATE TABLE `biz_cell_product_spec` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `spec_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规格编码', + `spec_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规格名称', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_cps` (`tenant_no`,`spec_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品规格'; + +-- ---------------------------- +-- Records of biz_cell_product_spec +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_cell_product_type +-- ---------------------------- +DROP TABLE IF EXISTS `biz_cell_product_type`; +CREATE TABLE `biz_cell_product_type` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `product_type_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '细胞产品类型编码', + `product_type_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '细胞产品类型名称', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_cpt` (`tenant_no`,`product_type_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞产品类型'; + +-- ---------------------------- +-- Records of biz_cell_product_type +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_cell_sample_type +-- ---------------------------- +DROP TABLE IF EXISTS `biz_cell_sample_type`; +CREATE TABLE `biz_cell_sample_type` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `type_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '样本类型编码', + `type_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '样本类型名称', + `storage_years` int(11) DEFAULT NULL COMMENT '样本存储年份', + `storage_price` decimal(18,2) DEFAULT NULL COMMENT '样本存储价格(元)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_sample_type` (`tenant_no`,`type_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞样本类型'; + +-- ---------------------------- +-- Records of biz_cell_sample_type +-- ---------------------------- +BEGIN; +INSERT INTO `biz_cell_sample_type` (`id`, `type_code`, `type_name`, `storage_years`, `storage_price`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b3000000000000000000000000000001', 'PBSC', '外周血造血干细胞', 20, 19800.00, 'zkxw', 1, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `biz_cell_sample_type` (`id`, `type_code`, `type_name`, `storage_years`, `storage_price`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b3000000000000000000000000000002', 'UCB', '脐带血', 18, 16800.00, 'zkxw', 2, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `biz_cell_sample_type` (`id`, `type_code`, `type_name`, `storage_years`, `storage_price`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b3000000000000000000000000000003', 'ADSC', '脂肪间充质干细胞', 10, 12800.00, 'zkxw', 3, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel`; +CREATE TABLE `biz_channel` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `channel_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道名称', + `credit_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '统一社会信用代码', + `channel_type` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'yy医院 hzjg合作机构 zs诊所 zfjg政府机构 qt其他', + `contact_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人姓名', + `contact_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', + `contact_email` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系邮箱', + `protocol_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '渠道协议附件逗号分隔', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_channel_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道信息'; + +-- ---------------------------- +-- Records of biz_channel +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel_agreement +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel_agreement`; +CREATE TABLE `biz_channel_agreement` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `channel_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道ID', + `agreement_type` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'kyxy科研 zsxy中试 lcxy临床', + `business_type` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'kyyy科研应用/中试应用/临床应用(随协议类型)', + `settle_mode` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'yj月结 jj季结 nj年结 dcjs单次结算', + `credit_limit_amount` decimal(18,2) DEFAULT NULL COMMENT '授信额度(元)', + `effective_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '生效日期YYYYMMDD', + `expire_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '到期日期YYYYMMDD', + `agreement_status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'sxz' COMMENT 'sxz生效中 yzf已作废', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_ch_agr_channel` (`channel_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道协议'; + +-- ---------------------------- +-- Records of biz_channel_agreement +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel_agreement_product_price +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel_agreement_product_price`; +CREATE TABLE `biz_channel_agreement_product_price` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `channel_agreement_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道协议ID', + `project_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目名称', + `project_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目编码', + `cell_type_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型编码', + `cell_type_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型名称', + `spec_text` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格', + `original_price` decimal(18,2) DEFAULT NULL COMMENT '原价(元)', + `agreement_price` decimal(18,2) DEFAULT NULL COMMENT '协议价(元)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_cap_agr` (`channel_agreement_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道协议-细胞产品价格'; + +-- ---------------------------- +-- Records of biz_channel_agreement_product_price +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel_invoice +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel_invoice`; +CREATE TABLE `biz_channel_invoice` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `channel_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道ID', + `company_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '单位名称', + `tax_no` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '税号', + `company_address` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '单位地址', + `company_phone` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电话', + `bank_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '开户银行', + `bank_account` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '银行账号', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_ch_inv_channel` (`channel_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道开票信息'; + +-- ---------------------------- +-- Records of biz_channel_invoice +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel_mail_address +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel_mail_address`; +CREATE TABLE `biz_channel_mail_address` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `channel_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道ID', + `mail_address` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮寄地址', + `mail_contact` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮寄联系人', + `mail_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮寄联系电话', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_ch_mail_channel` (`channel_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道邮寄地址'; + +-- ---------------------------- +-- Records of biz_channel_mail_address +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel_service_order +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel_service_order`; +CREATE TABLE `biz_channel_service_order` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `channel_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道ID', + `channel_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '渠道名称冗余', + `dept_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '科室/部门', + `order_type` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'yfk预付款 gmcp购买产品', + `prepay_amount` decimal(18,2) DEFAULT NULL COMMENT '预付金额(订单类型yfk)', + `purchase_total_amount` decimal(18,2) DEFAULT NULL COMMENT '购买总金额(订单类型gmcp)', + `order_status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'sxz' COMMENT 'sxz生效中 yzf已作废', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注信息', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_cso_channel` (`channel_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道服务购买订单'; + +-- ---------------------------- +-- Records of biz_channel_service_order +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel_service_order_finance_confirm +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel_service_order_finance_confirm`; +CREATE TABLE `biz_channel_service_order_finance_confirm` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `service_order_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道服务购买订单ID', + `confirm_time` datetime DEFAULT NULL COMMENT '确认时间', + `confirm_desc` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '确认描述', + `confirm_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '确认附件逗号分隔', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_csofc_order` (`service_order_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道服务购买订单-财务确认'; + +-- ---------------------------- +-- Records of biz_channel_service_order_finance_confirm +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_channel_service_order_product +-- ---------------------------- +DROP TABLE IF EXISTS `biz_channel_service_order_product`; +CREATE TABLE `biz_channel_service_order_product` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `service_order_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道服务购买订单ID', + `project_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目编码', + `project_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目名称', + `cell_type` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型', + `spec_text` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格', + `agreement_unit_price` decimal(18,2) DEFAULT NULL COMMENT '协议单价', + `buy_qty` decimal(18,4) DEFAULT NULL COMMENT '购买数量', + `buy_amount` decimal(18,2) DEFAULT NULL COMMENT '购买金额', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_csop_order` (`service_order_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道服务购买订单-产品明细'; + +-- ---------------------------- +-- Records of biz_channel_service_order_product +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_checkup_institution +-- ---------------------------- +DROP TABLE IF EXISTS `biz_checkup_institution`; +CREATE TABLE `biz_checkup_institution` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `institution_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '体检机构名称', + `institution_address` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '体检机构地址', + `contact_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人', + `contact_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人电话', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '1正常 0停用', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_ck_inst` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检机构'; + +-- ---------------------------- +-- Records of biz_checkup_institution +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_checkup_institution_invoice +-- ---------------------------- +DROP TABLE IF EXISTS `biz_checkup_institution_invoice`; +CREATE TABLE `biz_checkup_institution_invoice` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `checkup_institution_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '体检机构ID', + `company_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '单位名称', + `tax_no` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '税号', + `company_address` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '单位地址', + `company_phone` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电话', + `bank_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '开户银行', + `bank_account` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '银行账号', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_ck_inv` (`checkup_institution_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检机构开票信息'; + +-- ---------------------------- +-- Records of biz_checkup_institution_invoice +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_checkup_institution_project +-- ---------------------------- +DROP TABLE IF EXISTS `biz_checkup_institution_project`; +CREATE TABLE `biz_checkup_institution_project` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `checkup_institution_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '体检机构ID', + `project_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目名称', + `project_fee` decimal(18,2) DEFAULT NULL COMMENT '项目费用(元)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_ck_proj` (`checkup_institution_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检机构项目及价格'; + +-- ---------------------------- +-- Records of biz_checkup_institution_project +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_clean_room +-- ---------------------------- +DROP TABLE IF EXISTS `biz_clean_room`; +CREATE TABLE `biz_clean_room` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `room_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '编号', + `room_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称', + `room_grade` varchar(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '等级 A/B/C/D', + `room_status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zc' COMMENT 'zc正常 yc异常', + `area_sqm` decimal(12,2) DEFAULT NULL COMMENT '面积m²', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_clean_code` (`tenant_no`,`room_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='洁净室信息'; + +-- ---------------------------- +-- Records of biz_clean_room +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_clean_room_access_log +-- ---------------------------- +DROP TABLE IF EXISTS `biz_clean_room_access_log`; +CREATE TABLE `biz_clean_room_access_log` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `clean_room_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '洁净室ID', + `user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '人员用户ID', + `person_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '人员姓名(冗余)', + `dept_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门', + `enter_time` datetime NOT NULL COMMENT '进入时间', + `purpose` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的', + `leave_time` datetime DEFAULT NULL COMMENT '离开时间', + `body_temp` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '体温', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_crlog_room` (`clean_room_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='洁净室人员进出'; + +-- ---------------------------- +-- Records of biz_clean_room_access_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_collection_hospital +-- ---------------------------- +DROP TABLE IF EXISTS `biz_collection_hospital`; +CREATE TABLE `biz_collection_hospital` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `region_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集地区ID', + `hospital_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '医院名称', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_hospital_region` (`region_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集地区下属医院'; + +-- ---------------------------- +-- Records of biz_collection_hospital +-- ---------------------------- +BEGIN; +INSERT INTO `biz_collection_hospital` (`id`, `region_id`, `hospital_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('B8C83A5EB28D4B738DAE386FFACEC261', 'e1000000000000000000000000000001', '安医大3', 'zkxw', 3, '333', 'd3000000000000000000000000000001', '', '2026-05-14 09:32:25', 'd3000000000000000000000000000001', '2026-05-14 09:32:34', 1); +INSERT INTO `biz_collection_hospital` (`id`, `region_id`, `hospital_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('e1000000000000000000000000000002', 'e1000000000000000000000000000001', '示例三甲医院(高新区)', 'zkxw', 1, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-14 09:32:38', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_collection_protocol +-- ---------------------------- +DROP TABLE IF EXISTS `biz_collection_protocol`; +CREATE TABLE `biz_collection_protocol` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `protocol_no_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采集协议号表ID(选用号段)', + `protocol_no` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议号展示', + `protocol_status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dsx' COMMENT 'dsx待生效 sxz生效中 ygq已过期 yzf已作废 zky转科研', + `protocol_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '协议附件 minio 地址逗号分隔', + `register_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '登记日期YYYYMMDD', + `effective_time` datetime DEFAULT NULL COMMENT '协议生效时间', + `effective_desc` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '协议生效描述', + `expire_time` datetime DEFAULT NULL COMMENT '协议过期时间', + `expire_desc` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '协议过期描述', + `void_time` datetime DEFAULT NULL COMMENT '协议作废时间', + `void_desc` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '协议作废描述', + `to_research_time` datetime DEFAULT NULL COMMENT '协议转科研时间', + `to_research_desc` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '协议转科研描述', + `collector_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采集专员用户ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注信息', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_cp_status` (`tenant_no`,`protocol_status`,`del_flag`), + KEY `idx_cp_no` (`tenant_no`,`protocol_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单'; + +-- ---------------------------- +-- Records of biz_collection_protocol +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_collection_protocol_no +-- ---------------------------- +DROP TABLE IF EXISTS `biz_collection_protocol_no`; +CREATE TABLE `biz_collection_protocol_no` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `protocol_no` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议号', + `issued` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否下发(1是 0否)', + `issued_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '下发用户ID', + `issued_user_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '下发用户姓名(冗余)', + `issued_time` datetime DEFAULT NULL COMMENT '下发时间', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_protocol_no` (`tenant_no`,`protocol_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议号池'; + +-- ---------------------------- +-- Records of biz_collection_protocol_no +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_collection_region +-- ---------------------------- +DROP TABLE IF EXISTS `biz_collection_region`; +CREATE TABLE `biz_collection_region` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `region_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '地区名称', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_region_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集地区'; + +-- ---------------------------- +-- Records of biz_collection_region +-- ---------------------------- +BEGIN; +INSERT INTO `biz_collection_region` (`id`, `region_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('e1000000000000000000000000000001', '安徽省', 'zkxw', 1, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_customer +-- ---------------------------- +DROP TABLE IF EXISTS `biz_customer`; +CREATE TABLE `biz_customer` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `customer_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户姓名', + `sex` char(1) COLLATE utf8mb4_unicode_ci DEFAULT '2' COMMENT '性别(0男 1女 2未知)', + `id_card_no` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证号', + `age` int(11) DEFAULT NULL COMMENT '年龄', + `phone_no` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', + `customer_tags` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '客户标签,逗号分隔', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_customer_idcard` (`tenant_no`,`id_card_no`,`del_flag`), + KEY `idx_customer_name` (`tenant_no`,`customer_name`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户信息'; + +-- ---------------------------- +-- Records of biz_customer +-- ---------------------------- +BEGIN; +INSERT INTO `biz_customer` (`id`, `customer_name`, `sex`, `id_card_no`, `age`, `phone_no`, `customer_tags`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CCC406C12B2746C492C424D0381B9203', '123', '2', '123', 123, '123', '123', 'zkxw', 0, '123123', 'd3000000000000000000000000000001', '', '2026-05-13 12:07:29', 'd3000000000000000000000000000001', '2026-05-13 12:07:29', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_customer_follow +-- ---------------------------- +DROP TABLE IF EXISTS `biz_customer_follow`; +CREATE TABLE `biz_customer_follow` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `customer_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户ID', + `follow_time` datetime NOT NULL COMMENT '随访时间', + `follow_desc` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '随访描述', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_follow_customer` (`customer_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户随访'; + +-- ---------------------------- +-- Records of biz_customer_follow +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_exhibition_appointment +-- ---------------------------- +DROP TABLE IF EXISTS `biz_exhibition_appointment`; +CREATE TABLE `biz_exhibition_appointment` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `appointment_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '预约编号', + `visit_org` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来访单位', + `contact_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人', + `visit_date` datetime DEFAULT NULL COMMENT '到访日期', + `visitor_count` int(11) DEFAULT NULL COMMENT '人数', + `reception_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '接待人员用户ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_exh_no` (`tenant_no`,`appointment_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='展馆预约'; + +-- ---------------------------- +-- Records of biz_exhibition_appointment +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_express_company +-- ---------------------------- +DROP TABLE IF EXISTS `biz_express_company`; +CREATE TABLE `biz_express_company` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `express_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '快递公司编码', + `express_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '快递公司名称', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_express` (`tenant_no`,`express_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='快递公司'; + +-- ---------------------------- +-- Records of biz_express_company +-- ---------------------------- +BEGIN; +INSERT INTO `biz_express_company` (`id`, `express_code`, `express_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('22655c5a-4e72-11f1-9f47-00163e3bdd96', 'sf', '顺丰速运', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-14 10:00:39', 1); +INSERT INTO `biz_express_company` (`id`, `express_code`, `express_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('22655ee3-4e72-11f1-9f47-00163e3bdd96', 'jd', '京东快递', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-14 10:03:15', 1); +INSERT INTO `biz_express_company` (`id`, `express_code`, `express_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('22655f5a-4e72-11f1-9f47-00163e3bdd96', 'zt', '中通快递', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +INSERT INTO `biz_express_company` (`id`, `express_code`, `express_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('22655fa7-4e72-11f1-9f47-00163e3bdd96', 'yt', '圆通速递', 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_item_unit +-- ---------------------------- +DROP TABLE IF EXISTS `biz_item_unit`; +CREATE TABLE `biz_item_unit` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `unit_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '单位编码', + `unit_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '单位名称', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_item_unit` (`tenant_no`,`unit_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物品单位'; + +-- ---------------------------- +-- Records of biz_item_unit +-- ---------------------------- +BEGIN; +INSERT INTO `biz_item_unit` (`id`, `unit_code`, `unit_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('225da6c3-4e72-11f1-9f47-00163e3bdd96', 'g', '克', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +INSERT INTO `biz_item_unit` (`id`, `unit_code`, `unit_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('225da95c-4e72-11f1-9f47-00163e3bdd96', 'ml', '毫升', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +INSERT INTO `biz_item_unit` (`id`, `unit_code`, `unit_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('225dab01-4e72-11f1-9f47-00163e3bdd96', 'ge', '个', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +INSERT INTO `biz_item_unit` (`id`, `unit_code`, `unit_name`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('225dab5c-4e72-11f1-9f47-00163e3bdd96', 'fen', '份', 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_leave_request +-- ---------------------------- +DROP TABLE IF EXISTS `biz_leave_request`; +CREATE TABLE `biz_leave_request` ( + `id` varchar(64) NOT NULL COMMENT '主键ID', + `leave_type` varchar(32) NOT NULL DEFAULT 'personal' COMMENT '请假类型: personal/sick/annual/other', + `start_date` varchar(20) NOT NULL COMMENT '开始日期', + `end_date` varchar(20) NOT NULL COMMENT '结束日期', + `days` int(11) NOT NULL DEFAULT '1' COMMENT '请假天数', + `reason` varchar(500) DEFAULT NULL COMMENT '请假原因', + `wf_instance_id` varchar(64) DEFAULT NULL COMMENT '流程实例ID', + `status` varchar(32) NOT NULL DEFAULT 'draft' COMMENT '状态: draft/pending/approved/rejected/cancelled', + `tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_tenant` (`tenant_no`), + KEY `idx_status` (`status`), + KEY `idx_create_by` (`create_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='请假申请'; + +-- ---------------------------- +-- Records of biz_leave_request +-- ---------------------------- +BEGIN; +INSERT INTO `biz_leave_request` (`id`, `leave_type`, `start_date`, `end_date`, `days`, `reason`, `wf_instance_id`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('1750B47E85F647B7BA25D8BD0FAF122A', 'personal', '2026-05-05', '2026-05-27', 1, '123123', '9CC8E0387F61441F992DAB624F6B3306', 'pending', '', NULL, '123123', '', '', '2026-05-13 21:24:44', '', '2026-05-13 21:24:48', 0); +INSERT INTO `biz_leave_request` (`id`, `leave_type`, `start_date`, `end_date`, `days`, `reason`, `wf_instance_id`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('3A7A24D0459746C4A2A5728BB374FCE7', 'personal', '2026-05-06', '2026-05-28', 1, '123', '7E522AEC4B3D46329EC3CEBD53F222DD', 'pending', '', NULL, '', '', '', '2026-05-13 20:40:08', '', '2026-05-13 21:05:51', 0); +INSERT INTO `biz_leave_request` (`id`, `leave_type`, `start_date`, `end_date`, `days`, `reason`, `wf_instance_id`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('6C20F75EFAA34530AFEC1751F7833FE2', 'personal', '2026-05-12', '2026-05-29', 1, '123', '8AEC71596A074C7281F913B8000720DF', 'pending', '', NULL, '', 'd3000000000000000000000000000001', '', '2026-05-13 21:25:41', 'd3000000000000000000000000000001', '2026-05-13 21:25:45', 0); +INSERT INTO `biz_leave_request` (`id`, `leave_type`, `start_date`, `end_date`, `days`, `reason`, `wf_instance_id`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('E82F7ED8D4E146A69E8BE132698CCDBF', 'personal', '2026-05-01', '2026-05-26', 1, '123213123', 'E7A16AA9F37A47F29D3BEC93221EFDF4', 'pending', '', NULL, '123123', 'd3000000000000000000000000000001', '', '2026-05-13 22:24:48', 'd3000000000000000000000000000001', '2026-05-13 22:24:52', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_application +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_application`; +CREATE TABLE `biz_pilot_application` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `channel_service_order_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道服务购买订单ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_pilot_app_order` (`channel_service_order_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道中试服务申请单'; + +-- ---------------------------- +-- Records of biz_pilot_application +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_checkup +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_checkup`; +CREATE TABLE `biz_pilot_checkup` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `pilot_application_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '中试申请单ID', + `checkup_institution_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '体检机构ID', + `reserve_time` datetime DEFAULT NULL COMMENT '预约时间', + `reserve_fee` decimal(18,2) DEFAULT NULL COMMENT '预约费用', + `actual_checkup_time` datetime DEFAULT NULL COMMENT '实际体检时间', + `actual_checkup_fee` decimal(18,2) DEFAULT NULL COMMENT '实际体检费用', + `fee_settled` tinyint(4) DEFAULT '0' COMMENT '费用是否结算 1是 0否', + `checkup_status` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT 'dtj' COMMENT 'dtj待体检 ytj已体检', + `fee_status` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT 'wjs' COMMENT 'wjs未结算 yjs已结算', + `checkup_remark` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '体检备注', + `report_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '体检报告附件', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_pchk_app` (`pilot_application_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-体检信息'; + +-- ---------------------------- +-- Records of biz_pilot_checkup +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_checkup_actual_item +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_checkup_actual_item`; +CREATE TABLE `biz_pilot_checkup_actual_item` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `pilot_checkup_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '中试申请单体检信息ID', + `project_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目名称', + `project_fee` decimal(18,2) DEFAULT NULL COMMENT '项目费用(元)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_pcai_chk` (`pilot_checkup_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检实际项目明细'; + +-- ---------------------------- +-- Records of biz_pilot_checkup_actual_item +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_checkup_reserve_item +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_checkup_reserve_item`; +CREATE TABLE `biz_pilot_checkup_reserve_item` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `pilot_checkup_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '中试申请单体检信息ID', + `project_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目名称', + `project_fee` decimal(18,2) DEFAULT NULL COMMENT '项目费用(元)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_pcri_chk` (`pilot_checkup_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='体检预约项目明细'; + +-- ---------------------------- +-- Records of biz_pilot_checkup_reserve_item +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_delivery +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_delivery`; +CREATE TABLE `biz_pilot_delivery` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `infusion_schedule_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '输注计划ID', + `delivery_no` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '交付编号(可程序生成)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_pdel_sched` (`infusion_schedule_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='输注计划-交付信息'; + +-- ---------------------------- +-- Records of biz_pilot_delivery +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_delivery_material +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_delivery_material`; +CREATE TABLE `biz_pilot_delivery_material` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `delivery_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '交付信息ID', + `material_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '物料名称', + `qty` decimal(18,4) DEFAULT NULL COMMENT '数量', + `unit_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '单位ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_pdm_del` (`delivery_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交付信息-物料清单'; + +-- ---------------------------- +-- Records of biz_pilot_delivery_material +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_delivery_method +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_delivery_method`; +CREATE TABLE `biz_pilot_delivery_method` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `delivery_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '交付信息ID', + `delivery_mode` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qdzt渠道自提 kdfh快递发货', + `delivery_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '交付附件', + `ship_time` datetime DEFAULT NULL COMMENT '发运时间', + `handover_temp` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '交接温度', + `pickup_person` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '自提人员', + `pickup_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', + `sender_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发货人', + `express_company_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '快递公司ID', + `waybill_no` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '快递单号', + `express_fee` decimal(18,2) DEFAULT NULL COMMENT '快递费用', + `eta_time` datetime DEFAULT NULL COMMENT '预计到达时间', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_pdmeth_del` (`delivery_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交付信息-交付方式'; + +-- ---------------------------- +-- Records of biz_pilot_delivery_method +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_infusion_confirm +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_infusion_confirm`; +CREATE TABLE `biz_pilot_infusion_confirm` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `infusion_schedule_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '输注计划ID', + `confirm_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '确认人', + `confirm_time` datetime DEFAULT NULL COMMENT '确认时间', + `confirm_desc` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '确认描述', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_pic_sched` (`infusion_schedule_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='输注计划-应用确认'; + +-- ---------------------------- +-- Records of biz_pilot_infusion_confirm +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_infusion_plan +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_infusion_plan`; +CREATE TABLE `biz_pilot_infusion_plan` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `pilot_application_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '中试申请单ID', + `cell_type` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型', + `spec_text` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格', + `total_course_count` int(11) DEFAULT NULL COMMENT '总疗程数', + `course_interval` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '疗程间隔描述', + `infusion_route` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '输注途径', + `infusion_start_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '输注开始日期YYYYMMDD', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_pip_app` (`pilot_application_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-输注方案'; + +-- ---------------------------- +-- Records of biz_pilot_infusion_plan +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_infusion_schedule +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_infusion_schedule`; +CREATE TABLE `biz_pilot_infusion_schedule` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `infusion_plan_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '输注方案ID', + `cell_type` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '细胞类型', + `spec_text` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格', + `infusion_time` datetime DEFAULT NULL COMMENT '输注时间', + `schedule_status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dsc' COMMENT 'dsc待生产 ysc已生产 yjf已交付 yqx已取消', + `attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '附件逗号分隔', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_pis_plan` (`infusion_plan_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-输注计划'; + +-- ---------------------------- +-- Records of biz_pilot_infusion_schedule +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_pilot_patient +-- ---------------------------- +DROP TABLE IF EXISTS `biz_pilot_patient`; +CREATE TABLE `biz_pilot_patient` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `pilot_application_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '中试申请单ID', + `customer_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '关联客户ID', + `patient_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '姓名', + `sex` char(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '性别', + `age` int(11) DEFAULT NULL COMMENT '年龄', + `id_card_no` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证号', + `phone_no` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', + `emergency_contact` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '紧急联系人', + `emergency_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '紧急联系人电话', + `clinical_diagnosis` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '临床诊断', + `diagnosis_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '诊断附件逗号分隔', + `need_checkup` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否需要体检 1需要 0不需要', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_pilot_patient_app` (`pilot_application_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中试申请单-患者信息'; + +-- ---------------------------- +-- Records of biz_pilot_patient +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_audit +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_audit`; +CREATE TABLE `biz_protocol_audit` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `auditor_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核人', + `audit_remark` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核备注', + `audit_status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'shtg审核通过 shth审核退回', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_audit_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-审核记录'; + +-- ---------------------------- +-- Records of biz_protocol_audit +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_collection_info +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_collection_info`; +CREATE TABLE `biz_protocol_collection_info` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `region_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采集地区ID', + `hospital_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采集医院ID', + `medical_staff` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采集医护', + `sales_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '销售人员用户ID', + `sample_collect_time` datetime DEFAULT NULL COMMENT '样本采集时间', + `maternal_blood_collect_time` datetime DEFAULT NULL COMMENT '母血采集时间', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_coll_one` (`collection_protocol_id`,`del_flag`), + KEY `idx_coll_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-采集信息'; + +-- ---------------------------- +-- Records of biz_protocol_collection_info +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_fullmoon_todo +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_fullmoon_todo`; +CREATE TABLE `biz_protocol_fullmoon_todo` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `todo_status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '1已办报销 0未办保险', + `handle_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '办理日期', + `handle_remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '办理备注', + `insurance_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '保险附件逗号分隔', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_fullmoon_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='新生儿满月待办'; + +-- ---------------------------- +-- Records of biz_protocol_fullmoon_todo +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_operation_log +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_operation_log`; +CREATE TABLE `biz_protocol_operation_log` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `operation_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作编码', + `operation_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作名称', + `operation_detail` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '操作详情/变更摘要', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_oplog_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单操作日志'; + +-- ---------------------------- +-- Records of biz_protocol_operation_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_party_customer +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_party_customer`; +CREATE TABLE `biz_protocol_party_customer` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `customer_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '关联客户表 customer', + `mother_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '母亲姓名', + `mother_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '母亲联系电话', + `mother_id_card` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '母亲身份证号', + `mother_birth_ym` char(6) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '母亲出生年月YYYYMM', + `mother_ethnic` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '母亲民族', + `mother_address` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '母亲家庭住址', + `father_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '父亲姓名', + `father_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '父亲电话', + `father_id_card` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '父亲身份证号', + `newborn_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '新生儿姓名', + `newborn_id_card` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '新生儿身份证号', + `newborn_delivery_time` datetime DEFAULT NULL COMMENT '新生儿分娩时间', + `newborn_sex` char(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '1男 0女', + `delivery_mode` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'sc顺产 pgc剖宫产', + `birth_order` int(11) DEFAULT NULL COMMENT '胎次', + `newborn_weight_gram` int(11) DEFAULT NULL COMMENT '新生儿体重(克)', + `apgar_1min` decimal(4,1) DEFAULT NULL COMMENT '新生儿评分1分钟', + `apgar_5min` decimal(4,1) DEFAULT NULL COMMENT '新生儿评分5分钟', + `collector_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采集专员用户ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_party_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-当事人/产妇新生儿信息'; + +-- ---------------------------- +-- Records of biz_protocol_party_customer +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_payment +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_payment`; +CREATE TABLE `biz_protocol_payment` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `pay_mode` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'dj趸交 fq分期', + `contract_amount` decimal(18,2) DEFAULT NULL COMMENT '合同金额', + `installment_count` int(11) DEFAULT NULL COMMENT '分期期数', + `installment_interval_months` int(11) DEFAULT NULL COMMENT '每期间隔(月)', + `lump_sum_deadline` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '趸交截止时间YYYYMMDD', + `installment_start_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '分期开始时间YYYYMMDD', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_payment_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-缴费主表'; + +-- ---------------------------- +-- Records of biz_protocol_payment +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_payment_detail +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_payment_detail`; +CREATE TABLE `biz_protocol_payment_detail` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `payment_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '缴费主表ID', + `transfer_type` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'wxzz微信 zzbzz支付宝 yhkzz银行卡 xj现金', + `due_amount` decimal(18,2) DEFAULT NULL COMMENT '应缴费金额', + `transfer_amount` decimal(18,2) DEFAULT NULL COMMENT '转账金额', + `transfer_time` datetime DEFAULT NULL COMMENT '转账时间', + `transfer_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '转账附件逗号分隔', + `finance_confirm_attachments` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '财务确认附件逗号分隔', + `pay_status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dfj' COMMENT 'dfj待缴费 jfqrz缴费确认中 yjf已缴费 tfqrz退费确认中 ytf已退费', + `installment_no` int(11) DEFAULT NULL COMMENT '分期期序号,趸交为空', + `installment_due_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '分期应缴费时间YYYYMMDD', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_payd_pay` (`payment_id`,`del_flag`), + KEY `idx_payd_status` (`tenant_no`,`pay_status`,`del_flag`), + KEY `idx_payd_due` (`installment_due_date`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-缴费明细'; + +-- ---------------------------- +-- Records of biz_protocol_payment_detail +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_sample +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_sample`; +CREATE TABLE `biz_protocol_sample` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `sample_type_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '样本类型ID', + `sample_code_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '样本编码表ID', + `sample_code` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '样本编码展示', + `collect_amount` decimal(18,4) DEFAULT NULL COMMENT '采集量', + `unit_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '物品单位ID', + `price` decimal(18,2) DEFAULT NULL COMMENT '价格', + `storage_years` int(11) DEFAULT NULL COMMENT '存储年限(年)', + `sample_status` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'ycj已采集 jcz检测中 yrk已入库 yzf已作废', + `storage_in_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '入库日期YYYYMMDD', + `detect_start_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '检测开始日期', + `void_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '作废日期', + `storage_expire_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '存储到期日期=入库+年限', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_sample_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-样本信息'; + +-- ---------------------------- +-- Records of biz_protocol_sample +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_sample_transport +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_sample_transport`; +CREATE TABLE `biz_protocol_sample_transport` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `sample_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '样本ID', + `transport_type` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'kdys快递运输 ryxd人员携带', + `express_company_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '快递公司ID', + `waybill_no` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '快递单号', + `dest_site_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的站点ID', + `express_fee` decimal(18,2) DEFAULT NULL COMMENT '快递费用(元)', + `eta_time` datetime DEFAULT NULL COMMENT '预计到达时间', + `carrier_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '携带人员姓名', + `carrier_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '携带人员联系电话', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_transport_sample` (`sample_id`,`del_flag`), + KEY `idx_transport_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单-样本运输'; + +-- ---------------------------- +-- Records of biz_protocol_sample_transport +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_protocol_storage_cert +-- ---------------------------- +DROP TABLE IF EXISTS `biz_protocol_storage_cert`; +CREATE TABLE `biz_protocol_storage_cert` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', + `make_status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '1已制作 0未制作', + `cert_image_urls` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '储存证图片逗号分隔', + `make_date` char(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '制作日期', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_cert_cp` (`collection_protocol_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='储存证'; + +-- ---------------------------- +-- Records of biz_protocol_storage_cert +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_sample_code +-- ---------------------------- +DROP TABLE IF EXISTS `biz_sample_code`; +CREATE TABLE `biz_sample_code` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `sample_code` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '样本编码', + `sample_code_type` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '样本编码类型 zxbm造血编码 qdbm脐带编码', + `issued` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否下发(1是 0否)', + `issued_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '下发用户ID', + `issued_user_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '下发用户姓名(冗余)', + `issued_time` datetime DEFAULT NULL COMMENT '下发时间', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_sample_code` (`tenant_no`,`sample_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本编码池'; + +-- ---------------------------- +-- Records of biz_sample_code +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_storage_facility +-- ---------------------------- +DROP TABLE IF EXISTS `biz_storage_facility`; +CREATE TABLE `biz_storage_facility` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `parent_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '父节点ID,0根', + `node_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '节点名称(罐/层/盒等)', + `node_type` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'facility/layer/box 等扩展', + `is_leaf` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否叶子存储位 1是', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_sf_parent` (`parent_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞存储设施树节点'; + +-- ---------------------------- +-- Records of biz_storage_facility +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for biz_storage_facility_slot +-- ---------------------------- +DROP TABLE IF EXISTS `biz_storage_facility_slot`; +CREATE TABLE `biz_storage_facility_slot` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `facility_node_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设施树叶子节点ID', + `slot_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '小格编码/坐标', + `slot_status` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '占用状态等扩展', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_sfs_node` (`facility_node_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='细胞存储设施储存位置(格子)'; + +-- ---------------------------- +-- Records of biz_storage_facility_slot +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `parent_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '父组织id,0为根', + `dept_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '组织名称', + `dept_category_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '组织类别编码(字典 zzlbbm)', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '部门状态(字典 bmzt:0停用 1正常)', + `leader` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门负责人', + `leader_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门负责人联系电话', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_dept_parent` (`parent_id`,`del_flag`), + KEY `idx_dept_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='组织/部门'; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b13c2be2-4e71-11f1-9f47-00163e3bdd96', '0', '中科兴皖', 'gs', '1', NULL, NULL, 'zkxw', 0, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b13f9389-4e71-11f1-9f47-00163e3bdd96', 'b13c2be2-4e71-11f1-9f47-00163e3bdd96', '市场营销部门', 'bm', '1', NULL, NULL, 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b1430a9f-4e71-11f1-9f47-00163e3bdd96', 'b13c2be2-4e71-11f1-9f47-00163e3bdd96', '技术研发部', 'bm', '1', NULL, NULL, 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b1430b15-4e71-11f1-9f47-00163e3bdd96', 'b13c2be2-4e71-11f1-9f47-00163e3bdd96', '生产部', 'bm', '1', NULL, NULL, 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b1430b82-4e71-11f1-9f47-00163e3bdd96', 'b13c2be2-4e71-11f1-9f47-00163e3bdd96', '质量安全部', 'bm', '1', NULL, NULL, 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b1430c01-4e71-11f1-9f47-00163e3bdd96', 'b13c2be2-4e71-11f1-9f47-00163e3bdd96', '财务部', 'bm', '1', NULL, NULL, 'zkxw', 5, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b1430f37-4e71-11f1-9f47-00163e3bdd96', 'b13c2be2-4e71-11f1-9f47-00163e3bdd96', '综合部', 'bm', '1', NULL, NULL, 'zkxw', 6, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b146d77f-4e71-11f1-9f47-00163e3bdd96', 'b13f9389-4e71-11f1-9f47-00163e3bdd96', '采集部', 'bm', '1', NULL, NULL, 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b146da5a-4e71-11f1-9f47-00163e3bdd96', 'b13f9389-4e71-11f1-9f47-00163e3bdd96', '临床部', 'bm', '1', NULL, NULL, 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b146dad8-4e71-11f1-9f47-00163e3bdd96', 'b13f9389-4e71-11f1-9f47-00163e3bdd96', '中试服务部', 'bm', '1', NULL, NULL, 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b146db40-4e71-11f1-9f47-00163e3bdd96', 'b13f9389-4e71-11f1-9f47-00163e3bdd96', '支持中心', 'bm', '1', NULL, NULL, 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +INSERT INTO `sys_dept` (`id`, `parent_id`, `dept_name`, `dept_category_code`, `status`, `leader`, `leader_phone`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('b146dba1-4e71-11f1-9f47-00163e3bdd96', 'b13f9389-4e71-11f1-9f47-00163e3bdd96', '客服部', 'bm', '1', NULL, NULL, 'zkxw', 5, NULL, NULL, '', '2026-05-13 10:16:03', NULL, '2026-05-13 10:16:03', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_data`; +CREATE TABLE `sys_dict_data` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `dict_label` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典标签', + `dict_value` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典类型', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_dict_data_type` (`tenant_no`,`dict_type`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='字典数据'; + +-- ---------------------------- +-- Records of sys_dict_data +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a583e-4e71-11f1-9f47-00163e3bdd96', '公司', 'gs', 'zzlbbm', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5bcc-4e71-11f1-9f47-00163e3bdd96', '分公司', 'fgs', 'zzlbbm', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5c61-4e71-11f1-9f47-00163e3bdd96', '部门', 'bm', 'zzlbbm', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5cb2-4e71-11f1-9f47-00163e3bdd96', '中心基地', 'zxjd', 'zdlx', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5cfb-4e71-11f1-9f47-00163e3bdd96', '样本采集', 'ybcj', 'zdywfw', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5d46-4e71-11f1-9f47-00163e3bdd96', '样本制备', 'ybzb', 'zdywfw', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5d91-4e71-11f1-9f47-00163e3bdd96', '质量检测', 'zljc', 'zdywfw', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5dde-4e71-11f1-9f47-00163e3bdd96', '细胞存储', 'xbcc', 'zdywfw', 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5e36-4e71-11f1-9f47-00163e3bdd96', '停用', '0', 'bmzt', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_data` (`id`, `dict_label`, `dict_value`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25a5e7c-4e71-11f1-9f47-00163e3bdd96', '正常', '1', 'bmzt', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; +CREATE TABLE `sys_dict_type` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `dict_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典类型编码', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_dict_type` (`tenant_no`,`dict_type`,`del_flag`), + KEY `idx_dict_type_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='字典类型'; + +-- ---------------------------- +-- Records of sys_dict_type +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25639d6-4e71-11f1-9f47-00163e3bdd96', '组织类别编码', 'zzlbbm', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d2565c69-4e71-11f1-9f47-00163e3bdd96', '站点类型', 'zdlx', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d2565ff8-4e71-11f1-9f47-00163e3bdd96', '站点业务范围', 'zdywfw', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_type`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d25660b0-4e71-11f1-9f47-00163e3bdd96', '部门状态', 'bmzt', 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:16:59', NULL, '2026-05-13 10:16:59', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `menu_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单名称', + `parent_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '父菜单ID,0为根', + `menu_type` char(1) COLLATE utf8mb4_unicode_ci DEFAULT 'M' COMMENT '类型 M目录 C菜单 F按钮(扩展)', + `path` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '路由路径(扩展)', + `component` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '组件路径(扩展)', + `perms` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限标识(扩展)', + `is_frame` int(1) DEFAULT '1' COMMENT '是否外链(1是 0否)', + `visible` char(1) COLLATE utf8mb4_unicode_ci DEFAULT '1' COMMENT '显示状态(1显示 0隐藏)', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '菜单状态(1正常 0停用)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_menu_parent` (`parent_id`,`del_flag`), + KEY `idx_menu_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单'; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +BEGIN; +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('1', 'Web端菜单', '0', 'M', '/system', NULL, NULL, 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('100', '系统管理', '1', 'M', '/system', '/index/index', NULL, 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('10000000000000000000000000000wf01', '流程配置', '0', 'C', 'workflow/process', '/system/workflow/process/index', 'workflow:process', 1, '1', '1', 'zkxw', 15, NULL, NULL, '', '2026-05-13 10:23:04', NULL, '2026-05-13 10:23:04', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('10000000000000000000000000000wf02', '待办中心', '0', 'C', 'workflow/pending', '/system/workflow/task-pending/index', 'workflow:pending', 1, '1', '1', 'zkxw', 16, NULL, NULL, '', '2026-05-13 10:23:04', NULL, '2026-05-13 10:23:04', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('10000000000000000000000000000wf03', '已办中心', '0', 'C', 'workflow/completed', '/system/workflow/task-completed/index', 'workflow:completed', 1, '1', '1', 'zkxw', 17, NULL, NULL, '', '2026-05-13 10:23:04', NULL, '2026-05-13 10:23:04', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('101', '租户管理', '100', 'C', 'tenant', '/system/tenant/index', 'system:tenant:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('102', '字典管理', '100', 'C', 'dict', '/system/dict/index', 'system:dict:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('103', '组织管理', '100', 'C', 'dept', '/system/dept/index', 'system:dept:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('104', '用户管理', '100', 'C', 'user', '/system/user/index', 'system:user:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('105', '角色管理', '100', 'C', 'role', '/system/role/index', 'system:role:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('106', '菜单管理', '100', 'C', 'menu', '/system/menu/index', 'system:menu:list', 1, '1', '1', 'zkxw', 6, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('107', '站点管理', '100', 'C', 'site', '/system/site/index', 'system:site:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('108', '职位管理', '100', 'C', 'post', '/system/post/index', 'system:post:list', 1, '1', '1', 'zkxw', 8, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('109', '物品单位', '100', 'C', 'unit', '/system/unit/index', 'system:unit:list', 1, '1', '1', 'zkxw', 9, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('110', '快递公司', '100', 'C', 'express', '/system/express/index', 'system:express:list', 1, '1', '1', 'zkxw', 10, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('120', '基础信息管理', '1', 'M', '/basic-info', '/index/index', NULL, 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-13 12:05:42', NULL, '2026-05-13 12:05:42', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('121', '客户管理', '120', 'C', 'customer', '/basic-info/customer/index', 'biz:customer:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-13 12:05:42', NULL, '2026-05-13 12:05:42', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('122', '细胞样本类型管理', '120', 'C', 'cell-sample-type', '/basic-info/cell-sample-type/index', 'biz:cellSampleType:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-13 12:05:42', NULL, '2026-05-13 12:05:42', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('123', '采集地区及医院管理', '120', 'C', 'collection-region', '/basic-info/collection-region/index', 'biz:collectionRegion:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-13 12:05:42', NULL, '2026-05-13 12:05:42', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('124', '采集协议号管理', '120', 'C', 'collection-protocol-no', '/basic-info/collection-protocol-no/index', 'biz:collectionProtocolNo:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', '2026-05-13 12:05:42', NULL, '2026-05-13 12:05:42', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('125', '样本编码管理', '120', 'C', 'sample-code', '/basic-info/sample-code/index', 'biz:sampleCode:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-13 12:05:42', NULL, '2026-05-13 12:05:42', 0); +INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('2', '移动端菜单', '0', 'M', '/mini', NULL, NULL, 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:17:45', NULL, '2026-05-13 10:17:45', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_post`; +CREATE TABLE `sys_post` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `post_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '职位名称', + `post_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '职位编码', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '状态(1正常 0停用)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_post_code` (`tenant_no`,`post_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='职位'; + +-- ---------------------------- +-- Records of sys_post +-- ---------------------------- +BEGIN; +INSERT INTO `sys_post` (`id`, `post_name`, `post_code`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('225605c5-4e72-11f1-9f47-00163e3bdd96', '采集专员', 'cjzy', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +INSERT INTO `sys_post` (`id`, `post_name`, `post_code`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('2256083d-4e72-11f1-9f47-00163e3bdd96', '市场支持专员', 'sczczy', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +INSERT INTO `sys_post` (`id`, `post_name`, `post_code`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('22560959-4e72-11f1-9f47-00163e3bdd96', '质检专员', 'zjzy', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `role_name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称', + `data_scope` char(1) COLLATE utf8mb4_unicode_ci DEFAULT '1' COMMENT '数据权限(1全部 2本部门 3本部门及以下 4仅本人 5自定义)', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '角色状态(1正常 0停用)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_role_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色'; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +BEGIN; +INSERT INTO `sys_role` (`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('097bd2c6-4e72-11f1-9f47-00163e3bdd96', '超级管理员', '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:18:31', NULL, '2026-05-13 10:18:31', 0); +INSERT INTO `sys_role` (`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('097bd62b-4e72-11f1-9f47-00163e3bdd96', '运营管理员', '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:18:31', NULL, '2026-05-13 10:18:31', 0); +INSERT INTO `sys_role` (`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('097bd80c-4e72-11f1-9f47-00163e3bdd96', '采集专员', '2', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:18:31', NULL, '2026-05-13 10:18:31', 0); +INSERT INTO `sys_role` (`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('097bd88f-4e72-11f1-9f47-00163e3bdd96', '市场支持专员', '2', '1', 'zkxw', 4, NULL, NULL, '', '2026-05-13 10:18:31', NULL, '2026-05-13 10:18:31', 0); +INSERT INTO `sys_role` (`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('097bd909-4e72-11f1-9f47-00163e3bdd96', '质检专员', '2', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-13 10:18:31', NULL, '2026-05-13 10:18:31', 0); +INSERT INTO `sys_role` (`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d2000000000000000000000000000001', '超级管理员', '1', '1', 'zkxw', 1, '全部数据权限', NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role` (`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d2000000000000000000000000000002', '运营管理员', '2', '1', 'zkxw', 2, '本部门数据', NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_role_data_scope_detail +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_data_scope_detail`; +CREATE TABLE `sys_role_data_scope_detail` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `role_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色ID', + `scope_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '范围类型:dept部门 user用户', + `scope_value` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '范围值:部门ID/用户ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_role_scope_role` (`role_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色自定义数据权限明细'; + +-- ---------------------------- +-- Records of sys_role_data_scope_detail +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `role_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色ID', + `menu_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_role_menu` (`role_id`,`menu_id`,`del_flag`), + KEY `idx_role_menu_menu` (`menu_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色-菜单关联'; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +BEGIN; +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000001', 'd2000000000000000000000000000001', 'd4000000000000000000000000000001', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000002', 'd2000000000000000000000000000001', 'd4000000000000000000000000000002', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000003', 'd2000000000000000000000000000001', 'd4000000000000000000000000000003', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000004', 'd2000000000000000000000000000001', 'd4000000000000000000000000000004', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000005', 'd2000000000000000000000000000001', 'd4000000000000000000000000000005', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000011', 'd2000000000000000000000000000002', 'd4000000000000000000000000000001', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000012', 'd2000000000000000000000000000002', 'd4000000000000000000000000000002', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000013', 'd2000000000000000000000000000002', 'd4000000000000000000000000000003', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d5000000000000000000000000000014', 'd2000000000000000000000000000002', 'd4000000000000000000000000000004', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_site +-- ---------------------------- +DROP TABLE IF EXISTS `sys_site`; +CREATE TABLE `sys_site` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `site_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '站点名称', + `site_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '站点编码', + `site_type` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '站点类型(字典 zdlx)', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '状态(1正常 0停用)', + `address` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '详细地址', + `contact_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人', + `contact_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', + `business_scope` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务范围(字典 zdywfw,多值可逗号分隔)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_site_code` (`tenant_no`,`site_code`,`del_flag`), + KEY `idx_site_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点'; + +-- ---------------------------- +-- Records of sys_site +-- ---------------------------- +BEGIN; +INSERT INTO `sys_site` (`id`, `site_name`, `site_code`, `site_type`, `status`, `address`, `contact_name`, `contact_phone`, `business_scope`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('2237b211-4e72-11f1-9f47-00163e3bdd96', '中科兴皖中心基地', 'zkxw-zxjd', 'zxjd', '1', '', NULL, NULL, NULL, 'zkxw', 1, NULL, NULL, '', '2026-05-13 10:19:13', NULL, '2026-05-13 10:19:13', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_tenant +-- ---------------------------- +DROP TABLE IF EXISTS `sys_tenant`; +CREATE TABLE `sys_tenant` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户编号(业务编码,如 zkxw)', + `tenant_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户名称', + `contact_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人姓名', + `contact_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_sys_tenant_no` (`tenant_no`,`del_flag`), + KEY `idx_sys_tenant_del` (`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户信息'; + +-- ---------------------------- +-- Records of sys_tenant +-- ---------------------------- +BEGIN; +INSERT INTO `sys_tenant` (`id`, `tenant_no`, `tenant_name`, `contact_name`, `contact_phone`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('00D57235D5C8497A8C02D54E0FD3CDCF', '123', '123', '123', '123', 1, '123123', 'd3000000000000000000000000000001', '', '2026-05-12 19:06:42', 'd3000000000000000000000000000001', '2026-05-12 19:06:46', 1); +INSERT INTO `sys_tenant` (`id`, `tenant_no`, `tenant_name`, `contact_name`, `contact_phone`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('034a9f604de111f1938000163e3bdd96', 'zkxw', '中科兴皖', NULL, NULL, NULL, NULL, NULL, '', '2026-05-12 17:00:24', NULL, '2026-05-12 17:00:24', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `dept_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门ID', + `user_no` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', + `email` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '用户邮箱', + `phone_no` varchar(11) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '手机号码', + `sex` char(1) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像(minio完整地址)', + `password` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '密码', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '帐号状态(1正常 0停用)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_no` (`tenant_no`,`user_no`,`del_flag`), + KEY `idx_user_dept` (`dept_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户'; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +BEGIN; +INSERT INTO `sys_user` (`id`, `dept_id`, `user_no`, `nick_name`, `email`, `phone_no`, `sex`, `avatar`, `password`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('2ac3e6204e7211f19f4700163e3bdd96', NULL, 'zhangsan', '张三', 'zhangsan@zkxw.com', '13800000001', '0', NULL, '$2b$12$P.bS1iKm8zS4YEGUpDSi9uswGT/GiZgfJdnqigyQKflsmP2OAn5ai', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-13 10:19:27', NULL, '2026-05-13 22:27:06', 0); +INSERT INTO `sys_user` (`id`, `dept_id`, `user_no`, `nick_name`, `email`, `phone_no`, `sex`, `avatar`, `password`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('2ac489c64e7211f19f4700163e3bdd96', NULL, 'lisi', '李四', 'lisi@zkxw.com', '13800000002', '1', NULL, '$2b$12$P.bS1iKm8zS4YEGUpDSi9uswGT/GiZgfJdnqigyQKflsmP2OAn5ai', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-13 10:19:27', NULL, '2026-05-13 22:27:05', 0); +INSERT INTO `sys_user` (`id`, `dept_id`, `user_no`, `nick_name`, `email`, `phone_no`, `sex`, `avatar`, `password`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d3000000000000000000000000000001', 'd1000000000000000000000000000002', 'admin', '系统管理员', 'admin@zkxw.local', '13800000000', '0', NULL, '$2b$12$P.bS1iKm8zS4YEGUpDSi9uswGT/GiZgfJdnqigyQKflsmP2OAn5ai', '1', 'zkxw', 1, '初始超级管理员', NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_user` (`id`, `dept_id`, `user_no`, `nick_name`, `email`, `phone_no`, `sex`, `avatar`, `password`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d3000000000000000000000000000002', 'd1000000000000000000000000000002', 'demo', '演示运营', 'demo@zkxw.local', '13900000000', '0', NULL, '$2b$12$w0mJ2.WU5yOCM/oHed0Hhu3fjThWEXN4TxXOWrfKX5naOGMObHYNa', '1', 'zkxw', 2, '初始运营账号', NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_user_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_post`; +CREATE TABLE `sys_user_post` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `user_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户ID', + `post_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '职位ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_post` (`user_id`,`post_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户-职位关联'; + +-- ---------------------------- +-- Records of sys_user_post +-- ---------------------------- +BEGIN; +INSERT INTO `sys_user_post` (`id`, `user_id`, `post_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d8000000000000000000000000000001', 'd3000000000000000000000000000001', 'd7000000000000000000000000000002', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `user_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户ID', + `role_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色ID', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_role` (`user_id`,`role_id`,`del_flag`), + KEY `idx_user_role_role` (`role_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户-角色关联'; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +BEGIN; +INSERT INTO `sys_user_role` (`id`, `user_id`, `role_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d6000000000000000000000000000001', 'd3000000000000000000000000000001', 'd2000000000000000000000000000001', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +INSERT INTO `sys_user_role` (`id`, `user_id`, `role_id`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('d6000000000000000000000000000002', 'd3000000000000000000000000000002', 'd2000000000000000000000000000002', 'zkxw', NULL, NULL, NULL, '', '2026-05-12 17:28:38', NULL, '2026-05-12 17:28:38', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for wf_node +-- ---------------------------- +DROP TABLE IF EXISTS `wf_node`; +CREATE TABLE `wf_node` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `process_version_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程版本ID', + `node_key` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '节点唯一键(画布内)', + `node_type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'start/end/approval/condition 等', + `node_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '节点名称', + `config_json` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '审批人/SQL/会签策略等配置', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_wf_node_ver` (`process_version_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程节点'; + +-- ---------------------------- +-- Records of wf_node +-- ---------------------------- +BEGIN; +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('013DF290E0CA48FFA6C1F986CCC75E4E', '1BFEF3BC45FB4B85979203252573251F', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:10', 'd3000000000000000000000000000001', '2026-05-13 21:05:10', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('2A06EA4B65F04C54ADB7FCB3CC83EAF3', '65E51B403AEF4F458E162797470E83AA', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:33', 'd3000000000000000000000000000001', '2026-05-13 20:59:33', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('2BD96A3B1B46407FA220A0106D17A99A', 'E694F38CAC504B51AAF64F102DB88380', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:25', 'd3000000000000000000000000000001', '2026-05-13 21:05:25', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('3EBE44E9A0674140A4014A7AFF29B0B5', 'D31763EFEA9B4082B6104576FDB12718', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:02', 'd3000000000000000000000000000001', '2026-05-13 21:03:02', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('40EC0D320A994063B6CFB02D52717589', 'E694F38CAC504B51AAF64F102DB88380', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:25', 'd3000000000000000000000000000001', '2026-05-13 21:05:25', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('45B29C0FDDEE4BDA9E2A72CAC073E8D7', '1BFEF3BC45FB4B85979203252573251F', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:10', 'd3000000000000000000000000000001', '2026-05-13 21:05:10', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('54C1AA20BA5547B0BBF69206A549CE00', '3DDD7210942F40CB8D117BCC43144B21', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:39', 'd3000000000000000000000000000001', '2026-05-13 20:59:39', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('5B8E273F9066428180CDBA3CAEC9296D', 'A0094B798812442AA8E76C13A0E22460', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:34', 'd3000000000000000000000000000001', '2026-05-13 21:05:34', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('627296C1FC424DD58BD282B4039C2F44', 'D31763EFEA9B4082B6104576FDB12718', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:02', 'd3000000000000000000000000000001', '2026-05-13 21:03:02', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('62B7E5476E914DD9B1CE48FCCDD46CE7', 'D31763EFEA9B4082B6104576FDB12718', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:02', 'd3000000000000000000000000000001', '2026-05-13 21:03:02', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('65FA3C08AF6E4988A6BCD0A635419C57', '3DDD7210942F40CB8D117BCC43144B21', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:39', 'd3000000000000000000000000000001', '2026-05-13 20:59:39', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('6F42034687AD4881A541D3B920BC857D', 'A8762F2E338B406AAF1C7A3A5F25F414', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('7782AA67567F47E3BA8D58426FDCB1B1', '1BFEF3BC45FB4B85979203252573251F', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:10', 'd3000000000000000000000000000001', '2026-05-13 21:05:10', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('7A89CCE6E04C4A1085A1A9FFFAC6AD06', 'E02466CE3C6B4E65A7A0B34C021BE27A', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:00:29', 'd3000000000000000000000000000001', '2026-05-13 21:00:29', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('7B8CC10BFFFD40BB86328DF405735C63', 'D676C619406645849BDBA4CD0DB2D999', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:12', 'd3000000000000000000000000000001', '2026-05-13 21:03:12', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('7C062A2A90ED4E0D96570EA614FB37FC', '65E51B403AEF4F458E162797470E83AA', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:33', 'd3000000000000000000000000000001', '2026-05-13 20:59:33', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('8B0DB5951990413CAE1D76B269826D7E', '8644BC4075D843B29FFB703B8F488E0A', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:04', 'd3000000000000000000000000000001', '2026-05-13 21:03:04', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('99F41F97C9C04A229168DE64A0CB5C11', '3DDD7210942F40CB8D117BCC43144B21', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:39', 'd3000000000000000000000000000001', '2026-05-13 20:59:39', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('9D890BBBAF0B42E2A2DD0F492F97C546', 'A8762F2E338B406AAF1C7A3A5F25F414', 'a66a0f0b-030c-4fcc-970b-eb2a9f33cf9a', 'approval', '张三审批', '{\"strategy\":\"users\",\"userIds\":[\"2ac3e6204e7211f19f4700163e3bdd96\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 3, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('A2378E111B394806ABF0200BE9B60AA2', 'A0094B798812442AA8E76C13A0E22460', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:34', 'd3000000000000000000000000000001', '2026-05-13 21:05:34', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('A577AD86AFD54F5580991DEDCF412F26', 'E02466CE3C6B4E65A7A0B34C021BE27A', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:00:29', 'd3000000000000000000000000000001', '2026-05-13 21:00:29', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('A5864DF129D04033BB8B11B72399E2C3', '51806057C1D243429BE1316A5945D2F2', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:19', 'd3000000000000000000000000000001', '2026-05-13 21:05:19', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('BA731666B37F4FF982A09F5BF43010D1', '51806057C1D243429BE1316A5945D2F2', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:19', 'd3000000000000000000000000000001', '2026-05-13 21:05:19', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('C5D5ABECB5584C92AA860637F1C89DEE', 'D676C619406645849BDBA4CD0DB2D999', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:12', 'd3000000000000000000000000000001', '2026-05-13 21:03:12', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('CB55117070184292AA173FCE222A1E3F', 'A8762F2E338B406AAF1C7A3A5F25F414', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D47501A16C95463AB4798D7392D70A97', 'E694F38CAC504B51AAF64F102DB88380', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:25', 'd3000000000000000000000000000001', '2026-05-13 21:05:25', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('DB23CEEB4E1240D880270F4071AF881C', '3DDAC80316AC4DCCA972060531F92723', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:21', 'd3000000000000000000000000000001', '2026-05-13 20:59:21', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('DBB50E44330D4C8E85CC4393D796A0C6', '8644BC4075D843B29FFB703B8F488E0A', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:04', 'd3000000000000000000000000000001', '2026-05-13 21:03:04', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('DC3895005A1B47988DB4A13FD2553D4E', 'D676C619406645849BDBA4CD0DB2D999', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:12', 'd3000000000000000000000000000001', '2026-05-13 21:03:12', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('DD96A286684C495CA4A9A3BD3329A88C', 'A0094B798812442AA8E76C13A0E22460', 'start', 'start', '开始', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:34', 'd3000000000000000000000000000001', '2026-05-13 21:05:34', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('DF5DF29FB5B1480FBE12A5FE3595F331', '51806057C1D243429BE1316A5945D2F2', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:19', 'd3000000000000000000000000000001', '2026-05-13 21:05:19', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('EDF0EBDB8D79464A80D76AAB927FEA6A', '3DDAC80316AC4DCCA972060531F92723', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:21', 'd3000000000000000000000000000001', '2026-05-13 20:59:21', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('EE7B35D2FD89458F90A884E3CDCCED53', '8644BC4075D843B29FFB703B8F488E0A', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:04', 'd3000000000000000000000000000001', '2026-05-13 21:03:04', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('EF08625814CF4EEDAA85FEC0B575F3A6', '3DDAC80316AC4DCCA972060531F92723', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:21', 'd3000000000000000000000000000001', '2026-05-13 20:59:21', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('F19D557A100549B18469A75B7F5FE885', '65E51B403AEF4F458E162797470E83AA', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:33', 'd3000000000000000000000000000001', '2026-05-13 20:59:33', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('F68E523F6D504E019D4704761AAAA05D', 'E02466CE3C6B4E65A7A0B34C021BE27A', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:00:29', 'd3000000000000000000000000000001', '2026-05-13 21:00:29', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('F94D43C6230849FD9AE6D942EAC67F17', 'A8762F2E338B406AAF1C7A3A5F25F414', 'end', 'end', '结束', '{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}', 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('WF000000000000000000000000000010', 'WF000000000000000000000000000002', 'start', 'start', '开始', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:56:11', 'd3000000000000000000000000000001', '2026-05-13 20:56:11', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('WF000000000000000000000000000011', 'WF000000000000000000000000000002', 'approve1', 'approval', '主管审批', '{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"passType\":\"any\"}', 'zkxw', 2, '指定admin用户审批,任意一人通过即可', 'd3000000000000000000000000000001', '', '2026-05-13 20:56:11', 'd3000000000000000000000000000001', '2026-05-13 20:56:11', 0); +INSERT INTO `wf_node` (`id`, `process_version_id`, `node_key`, `node_type`, `node_name`, `config_json`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('WF000000000000000000000000000012', 'WF000000000000000000000000000002', 'end', 'end', '结束', NULL, 'zkxw', 3, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:56:11', 'd3000000000000000000000000000001', '2026-05-13 20:56:11', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for wf_process_def +-- ---------------------------- +DROP TABLE IF EXISTS `wf_process_def`; +CREATE TABLE `wf_process_def` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `process_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程编码', + `process_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程名称', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '1启用 0停用', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_wf_def` (`tenant_no`,`process_code`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程定义'; + +-- ---------------------------- +-- Records of wf_process_def +-- ---------------------------- +BEGIN; +INSERT INTO `wf_process_def` (`id`, `process_code`, `process_name`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('WF000000000000000000000000000001', 'leave_request', '请假审批', '1', 'zkxw', 1, '请假审批流程种子数据', 'd3000000000000000000000000000001', '', '2026-05-13 20:56:11', 'd3000000000000000000000000000001', '2026-05-13 20:56:11', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for wf_process_instance +-- ---------------------------- +DROP TABLE IF EXISTS `wf_process_instance`; +CREATE TABLE `wf_process_instance` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `process_version_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发起时锁定的流程版本ID', + `business_type` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务类型', + `business_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务主键', + `starter_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发起人', + `variables_json` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '流程变量 JSON', + `status` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'running/completed/rejected 等', + `callback_url` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务回调地址', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_wf_inst_biz` (`business_type`,`business_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程实例'; + +-- ---------------------------- +-- Records of wf_process_instance +-- ---------------------------- +BEGIN; +INSERT INTO `wf_process_instance` (`id`, `process_version_id`, `business_type`, `business_id`, `starter_user_id`, `variables_json`, `status`, `callback_url`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('7E522AEC4B3D46329EC3CEBD53F222DD', 'A0094B798812442AA8E76C13A0E22460', 'leave', '3A7A24D0459746C4A2A5728BB374FCE7', '', '{\"days\": 1, \"leaveType\": \"personal\", \"applicant\": \"\"}', 'running', '/api/admin/leave/callback', 'zkxw', 0, NULL, '', '', '2026-05-13 21:05:50', '', '2026-05-13 21:05:50', 0); +INSERT INTO `wf_process_instance` (`id`, `process_version_id`, `business_type`, `business_id`, `starter_user_id`, `variables_json`, `status`, `callback_url`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('8AEC71596A074C7281F913B8000720DF', 'A0094B798812442AA8E76C13A0E22460', 'leave', '6C20F75EFAA34530AFEC1751F7833FE2', 'd3000000000000000000000000000001', '{\"days\": 1, \"leaveType\": \"personal\", \"applicant\": \"d3000000000000000000000000000001\"}', 'completed', '/api/admin/leave/callback', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:25:45', 'd3000000000000000000000000000001', '2026-05-13 22:25:17', 0); +INSERT INTO `wf_process_instance` (`id`, `process_version_id`, `business_type`, `business_id`, `starter_user_id`, `variables_json`, `status`, `callback_url`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('9CC8E0387F61441F992DAB624F6B3306', 'A0094B798812442AA8E76C13A0E22460', 'leave', '1750B47E85F647B7BA25D8BD0FAF122A', '', '{\"days\": 1, \"leaveType\": \"personal\", \"applicant\": \"\"}', 'running', '/api/admin/leave/callback', 'zkxw', 0, NULL, '', '', '2026-05-13 21:24:47', '', '2026-05-13 21:24:47', 0); +INSERT INTO `wf_process_instance` (`id`, `process_version_id`, `business_type`, `business_id`, `starter_user_id`, `variables_json`, `status`, `callback_url`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('E7A16AA9F37A47F29D3BEC93221EFDF4', 'A8762F2E338B406AAF1C7A3A5F25F414', 'leave', 'E82F7ED8D4E146A69E8BE132698CCDBF', 'd3000000000000000000000000000001', '{\"days\": 1, \"leaveType\": \"personal\", \"applicant\": \"d3000000000000000000000000000001\"}', 'completed', '/api/admin/leave/callback', 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:52', 'd3000000000000000000000000000001', '2026-05-13 22:27:51', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for wf_process_version +-- ---------------------------- +DROP TABLE IF EXISTS `wf_process_version`; +CREATE TABLE `wf_process_version` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `process_def_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程定义ID', + `version_no` int(11) NOT NULL COMMENT '版本号,递增', + `graph_json` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '节点与连线等配置(JSON 字符串)', + `is_latest` char(1) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '是否最新版本(1是 0否)', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_wf_ver_def` (`process_def_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程定义版本'; + +-- ---------------------------- +-- Records of wf_process_version +-- ---------------------------- +BEGIN; +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('1BFEF3BC45FB4B85979203252573251F', 'WF000000000000000000000000000001', 9, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '草稿', 'd3000000000000000000000000000001', '', '2026-05-13 21:05:10', 'd3000000000000000000000000000001', '2026-05-13 21:05:19', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('3DDAC80316AC4DCCA972060531F92723', 'WF000000000000000000000000000001', 2, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"},\"zIndex\":1}]}', '0', 'zkxw', 0, '版本发布 - 2026/5/13 20:59:21', 'd3000000000000000000000000000001', '', '2026-05-13 20:59:21', 'd3000000000000000000000000000001', '2026-05-13 20:59:33', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('3DDD7210942F40CB8D117BCC43144B21', 'WF000000000000000000000000000001', 4, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '版本发布 - 2026/5/13 20:59:38', 'd3000000000000000000000000000001', '', '2026-05-13 20:59:39', 'd3000000000000000000000000000001', '2026-05-13 21:00:28', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('51806057C1D243429BE1316A5945D2F2', 'WF000000000000000000000000000001', 10, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '草稿', 'd3000000000000000000000000000001', '', '2026-05-13 21:05:19', 'd3000000000000000000000000000001', '2026-05-13 21:05:25', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('65E51B403AEF4F458E162797470E83AA', 'WF000000000000000000000000000001', 3, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '版本发布 - 2026/5/13 20:59:32', 'd3000000000000000000000000000001', '', '2026-05-13 20:59:33', 'd3000000000000000000000000000001', '2026-05-13 20:59:39', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('8644BC4075D843B29FFB703B8F488E0A', 'WF000000000000000000000000000001', 7, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '草稿', 'd3000000000000000000000000000001', '', '2026-05-13 21:03:03', 'd3000000000000000000000000000001', '2026-05-13 21:03:11', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('A0094B798812442AA8E76C13A0E22460', 'WF000000000000000000000000000001', 12, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '版本发布 - 2026/5/13 21:05:33', 'd3000000000000000000000000000001', '', '2026-05-13 21:05:34', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('A8762F2E338B406AAF1C7A3A5F25F414', 'WF000000000000000000000000000001', 13, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"position\":{\"x\":288,\"y\":94},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"审批节点\"},\"label\":{\"text\":\"张三审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"a66a0f0b-030c-4fcc-970b-eb2a9f33cf9a\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"张三审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"2ac3e6204e7211f19f4700163e3bdd96\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":2},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"3f64b22f-c8ff-4983-9c53-bd7fbc7410c9\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"a66a0f0b-030c-4fcc-970b-eb2a9f33cf9a\",\"port\":\"port-in\"},\"zIndex\":3},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"38be86a5-627f-4bd0-b7c1-80f05d526eec\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"source\":{\"cell\":\"a66a0f0b-030c-4fcc-970b-eb2a9f33cf9a\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"},\"zIndex\":4,\"labels\":[{\"attrs\":{\"label\":{\"text\":\"其他情况\",\"fill\":\"#9ca3af\",\"fontSize\":11,\"textAnchor\":\"middle\",\"textVerticalAnchor\":\"bottom\",\"fontStyle\":\"italic\"},\"rect\":{\"fill\":\"#f9fafb\",\"stroke\":\"#d1d5db\",\"strokeWidth\":1,\"rx\":4,\"ry\":4,\"strokeDasharray\":\"3,3\"}},\"position\":{\"distance\":0.5}}]}]}', '1', 'zkxw', 0, '版本发布 - 2026/5/13 22:24:31', 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D31763EFEA9B4082B6104576FDB12718', 'WF000000000000000000000000000001', 6, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '版本发布 - 2026/5/13 21:03:01', 'd3000000000000000000000000000001', '', '2026-05-13 21:03:02', 'd3000000000000000000000000000001', '2026-05-13 21:03:03', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D676C619406645849BDBA4CD0DB2D999', 'WF000000000000000000000000000001', 8, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '草稿', 'd3000000000000000000000000000001', '', '2026-05-13 21:03:12', 'd3000000000000000000000000000001', '2026-05-13 21:05:10', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('E02466CE3C6B4E65A7A0B34C021BE27A', 'WF000000000000000000000000000001', 5, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '版本发布 - 2026/5/13 21:00:27', 'd3000000000000000000000000000001', '', '2026-05-13 21:00:28', 'd3000000000000000000000000000001', '2026-05-13 21:03:01', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('E694F38CAC504B51AAF64F102DB88380', 'WF000000000000000000000000000001', 11, '{\"cells\":[{\"position\":{\"x\":180,\"y\":-210},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"开始\"}},\"visible\":true,\"shape\":\"flow-start\",\"ports\":{\"groups\":{\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#10b981\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"start\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":-20},\"size\":{\"width\":180,\"height\":40},\"attrs\":{\"text\":{\"text\":\"主管审批\"},\"label\":{\"text\":\"主管审批\"}},\"visible\":true,\"shape\":\"flow-approval\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"bottom\":{\"position\":\"bottom\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"left\":{\"position\":\"left\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}},\"right\":{\"position\":\"right\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#3b82f6\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"id\":\"approve1\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"position\":{\"x\":200,\"y\":310},\"size\":{\"width\":50,\"height\":50},\"attrs\":{\"text\":{\"text\":\"结束\"}},\"visible\":true,\"shape\":\"flow-end\",\"ports\":{\"groups\":{\"top\":{\"position\":\"top\",\"attrs\":{\"circle\":{\"r\":5,\"magnet\":true,\"stroke\":\"#ef4444\",\"fill\":\"#fff\",\"strokeWidth\":1.5}}}},\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"id\":\"end\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"zIndex\":1},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-start-approve1\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"}},{\"shape\":\"edge\",\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"id\":\"edge-approve1-end\",\"data\":{\"conditionExpr\":\"\"},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}},\"zIndex\":1,\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"}}]}', '0', 'zkxw', 0, '草稿', 'd3000000000000000000000000000001', '', '2026-05-13 21:05:25', 'd3000000000000000000000000000001', '2026-05-13 21:05:34', 0); +INSERT INTO `wf_process_version` (`id`, `process_def_id`, `version_no`, `graph_json`, `is_latest`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('WF000000000000000000000000000002', 'WF000000000000000000000000000001', 1, '{\"cells\":[{\"shape\":\"flow-start\",\"id\":\"start\",\"x\":200,\"y\":50,\"width\":50,\"height\":50,\"label\":\"开始\",\"data\":{\"nodeType\":\"start\",\"nodeName\":\"开始\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"ports\":{\"items\":[{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"attrs\":{\"body\":{\"fill\":\"#10b981\",\"stroke\":\"#059669\",\"strokeWidth\":2},\"label\":{\"fill\":\"#ffffff\",\"fontSize\":13,\"fontWeight\":600}}},{\"shape\":\"flow-approval\",\"id\":\"approve1\",\"x\":140,\"y\":180,\"width\":180,\"height\":40,\"label\":\"主管审批\",\"data\":{\"nodeType\":\"approval\",\"nodeName\":\"主管审批\",\"config\":{\"strategy\":\"users\",\"userIds\":[\"d3000000000000000000000000000001\"],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"ports\":{\"items\":[{\"group\":\"top\",\"id\":\"port-in\"},{\"group\":\"bottom\",\"id\":\"port-out\"}]},\"attrs\":{\"body\":{\"fill\":\"#3b82f6\",\"stroke\":\"#2563eb\",\"strokeWidth\":2,\"rx\":8,\"ry\":8},\"label\":{\"fill\":\"#ffffff\",\"fontSize\":13,\"fontWeight\":500}}},{\"shape\":\"flow-end\",\"id\":\"end\",\"x\":200,\"y\":310,\"width\":50,\"height\":50,\"label\":\"结束\",\"data\":{\"nodeType\":\"end\",\"nodeName\":\"结束\",\"config\":{\"strategy\":\"users\",\"userIds\":[],\"roleIds\":[],\"sqlExpr\":\"\",\"passType\":\"any\"}},\"ports\":{\"items\":[{\"group\":\"top\",\"id\":\"port-in\"}]},\"attrs\":{\"body\":{\"fill\":\"#ef4444\",\"stroke\":\"#dc2626\",\"strokeWidth\":2},\"label\":{\"fill\":\"#ffffff\",\"fontSize\":13,\"fontWeight\":600}}},{\"shape\":\"edge\",\"id\":\"edge-start-approve1\",\"source\":{\"cell\":\"start\",\"port\":\"port-out\"},\"target\":{\"cell\":\"approve1\",\"port\":\"port-in\"},\"data\":{\"conditionExpr\":\"\"},\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}}},{\"shape\":\"edge\",\"id\":\"edge-approve1-end\",\"source\":{\"cell\":\"approve1\",\"port\":\"port-out\"},\"target\":{\"cell\":\"end\",\"port\":\"port-in\"},\"data\":{\"conditionExpr\":\"\"},\"attrs\":{\"line\":{\"stroke\":\"#6b7280\",\"strokeWidth\":1.5,\"targetMarker\":{\"name\":\"block\",\"width\":10,\"height\":6}}},\"router\":{\"name\":\"manhattan\"},\"connector\":{\"name\":\"rounded\",\"args\":{\"radius\":8}}}]}', '0', 'zkxw', 1, '请假审批流程 v1', 'd3000000000000000000000000000001', '', '2026-05-13 20:56:11', 'd3000000000000000000000000000001', '2026-05-13 20:59:21', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for wf_task +-- ---------------------------- +DROP TABLE IF EXISTS `wf_task`; +CREATE TABLE `wf_task` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `instance_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程实例ID', + `node_key` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '当前节点key', + `assignee_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '待办人', + `status` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'pending/approved/rejected 等', + `comment_text` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审批意见', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_wf_task_inst` (`instance_id`,`del_flag`), + KEY `idx_wf_task_user` (`assignee_user_id`,`status`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程任务/待办'; + +-- ---------------------------- +-- Records of wf_task +-- ---------------------------- +BEGIN; +INSERT INTO `wf_task` (`id`, `instance_id`, `node_key`, `assignee_user_id`, `status`, `comment_text`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('4524DB624F574BEEAA26323D9303B07D', '7E522AEC4B3D46329EC3CEBD53F222DD', 'approve1', 'd3000000000000000000000000000001', 'pending', NULL, 'zkxw', 0, NULL, NULL, '', '2026-05-13 21:05:50', NULL, '2026-05-13 21:05:50', 0); +INSERT INTO `wf_task` (`id`, `instance_id`, `node_key`, `assignee_user_id`, `status`, `comment_text`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('B188D2AC0527499987E8FEB9C2E6F804', '8AEC71596A074C7281F913B8000720DF', 'approve1', 'd3000000000000000000000000000001', 'approved', '', 'zkxw', 0, NULL, NULL, '', '2026-05-13 21:25:45', NULL, '2026-05-13 22:25:16', 0); +INSERT INTO `wf_task` (`id`, `instance_id`, `node_key`, `assignee_user_id`, `status`, `comment_text`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D5F711EF896A4056BCFF49EBEE9815A0', 'E7A16AA9F37A47F29D3BEC93221EFDF4', 'approve1', 'd3000000000000000000000000000001', 'approved', '', 'zkxw', 0, NULL, NULL, '', '2026-05-13 22:24:52', NULL, '2026-05-13 22:25:37', 0); +INSERT INTO `wf_task` (`id`, `instance_id`, `node_key`, `assignee_user_id`, `status`, `comment_text`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D6B3E052BAED4E5E80FBA930CC974A9F', '9CC8E0387F61441F992DAB624F6B3306', 'approve1', 'd3000000000000000000000000000001', 'pending', NULL, 'zkxw', 0, NULL, NULL, '', '2026-05-13 21:24:48', NULL, '2026-05-13 21:24:48', 0); +INSERT INTO `wf_task` (`id`, `instance_id`, `node_key`, `assignee_user_id`, `status`, `comment_text`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('E725C70E1BBE48C98C5CB95F90CB807C', 'E7A16AA9F37A47F29D3BEC93221EFDF4', 'a66a0f0b-030c-4fcc-970b-eb2a9f33cf9a', '2ac3e6204e7211f19f4700163e3bdd96', 'approved', '', 'zkxw', 0, NULL, NULL, '', '2026-05-13 22:25:38', NULL, '2026-05-13 22:27:50', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for wf_transition +-- ---------------------------- +DROP TABLE IF EXISTS `wf_transition`; +CREATE TABLE `wf_transition` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `process_version_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程版本ID', + `from_node_key` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '起始节点key', + `to_node_key` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '目标节点key', + `condition_expr` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '条件表达式或分支说明', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_wf_trans_ver` (`process_version_id`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流程连线/转移'; + +-- ---------------------------- +-- Records of wf_transition +-- ---------------------------- +BEGIN; +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('04AD539470D54AB5A4FC28E057B2B680', '3DDAC80316AC4DCCA972060531F92723', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:21', 'd3000000000000000000000000000001', '2026-05-13 20:59:21', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('10399C04A55B4DA38ACE3FEA019FD946', '3DDAC80316AC4DCCA972060531F92723', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:21', 'd3000000000000000000000000000001', '2026-05-13 20:59:21', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('339DF4F6AAE547FDA657CF29C2450459', 'E02466CE3C6B4E65A7A0B34C021BE27A', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:00:29', 'd3000000000000000000000000000001', '2026-05-13 21:00:29', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('3B4EF674E6FE43C89F0FA63F42C6B6CD', 'D31763EFEA9B4082B6104576FDB12718', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:02', 'd3000000000000000000000000000001', '2026-05-13 21:03:02', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('45DA444CB3B24F7187B89DC5891F24FF', 'D676C619406645849BDBA4CD0DB2D999', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:12', 'd3000000000000000000000000000001', '2026-05-13 21:03:12', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('498C033E3BBE42E990E9C885DEA20644', '65E51B403AEF4F458E162797470E83AA', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:33', 'd3000000000000000000000000000001', '2026-05-13 20:59:33', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('503CDA7592004396931DC2AEE9105A46', '1BFEF3BC45FB4B85979203252573251F', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:10', 'd3000000000000000000000000000001', '2026-05-13 21:05:10', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('5C8424B13698421DA74304BDD17891B1', 'E694F38CAC504B51AAF64F102DB88380', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:25', 'd3000000000000000000000000000001', '2026-05-13 21:05:25', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('5CF30C767ADD4EE5BED0DE3086BA09FA', 'E694F38CAC504B51AAF64F102DB88380', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:25', 'd3000000000000000000000000000001', '2026-05-13 21:05:25', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('61D6A14E082444D6BDD21B55F612BE38', 'A0094B798812442AA8E76C13A0E22460', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:34', 'd3000000000000000000000000000001', '2026-05-13 21:05:34', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('6D1874C7C4774B37837075D9C54E7D5C', 'A8762F2E338B406AAF1C7A3A5F25F414', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('751031F1DBE44EADABD976144F4C99C4', 'D31763EFEA9B4082B6104576FDB12718', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:02', 'd3000000000000000000000000000001', '2026-05-13 21:03:02', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('7694FDED26CC44CCB8B56575A75BC743', 'A8762F2E338B406AAF1C7A3A5F25F414', 'a66a0f0b-030c-4fcc-970b-eb2a9f33cf9a', 'end', NULL, 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('7DD6218A60114A83A290ABA6859E575E', '3DDD7210942F40CB8D117BCC43144B21', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:39', 'd3000000000000000000000000000001', '2026-05-13 20:59:39', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('87084B1BCE194175924FA8518E2ECB80', '51806057C1D243429BE1316A5945D2F2', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:19', 'd3000000000000000000000000000001', '2026-05-13 21:05:19', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('99CAADD1B54346AAB8F481A14C734FAE', '8644BC4075D843B29FFB703B8F488E0A', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:04', 'd3000000000000000000000000000001', '2026-05-13 21:03:04', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ABE78DF01CA4402688B7BD1BCEAF4436', '51806057C1D243429BE1316A5945D2F2', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:19', 'd3000000000000000000000000000001', '2026-05-13 21:05:19', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('AF7E94DF904B4D70B05A7BDC90E778BA', 'D676C619406645849BDBA4CD0DB2D999', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:12', 'd3000000000000000000000000000001', '2026-05-13 21:03:12', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D32A87BEA9A74126A4D3402D472F3C2B', 'E02466CE3C6B4E65A7A0B34C021BE27A', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:00:29', 'd3000000000000000000000000000001', '2026-05-13 21:00:29', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D573BEF4FC874EA89206FBE834986DD5', 'A0094B798812442AA8E76C13A0E22460', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:34', 'd3000000000000000000000000000001', '2026-05-13 21:05:34', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('D7D0D2361F1C47CBA37292245EC3B501', '3DDD7210942F40CB8D117BCC43144B21', 'approve1', 'end', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:39', 'd3000000000000000000000000000001', '2026-05-13 20:59:39', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('E3B8350921B84FB0A0E5B658A8D1C726', 'A8762F2E338B406AAF1C7A3A5F25F414', 'approve1', 'a66a0f0b-030c-4fcc-970b-eb2a9f33cf9a', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 22:24:32', 'd3000000000000000000000000000001', '2026-05-13 22:24:32', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('F4F6D978432E410CAEA4483087F6EFE2', '1BFEF3BC45FB4B85979203252573251F', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:05:10', 'd3000000000000000000000000000001', '2026-05-13 21:05:10', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('F84D9088BFFC414481841DE340F3BA32', '65E51B403AEF4F458E162797470E83AA', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:59:33', 'd3000000000000000000000000000001', '2026-05-13 20:59:33', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('FBD25BC7D11D4751B6EAAC45A7CC2E9D', '8644BC4075D843B29FFB703B8F488E0A', 'start', 'approve1', NULL, 'zkxw', 0, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 21:03:04', 'd3000000000000000000000000000001', '2026-05-13 21:03:04', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('WF000000000000000000000000000020', 'WF000000000000000000000000000002', 'start', 'approve1', NULL, 'zkxw', 1, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:56:12', 'd3000000000000000000000000000001', '2026-05-13 20:56:12', 0); +INSERT INTO `wf_transition` (`id`, `process_version_id`, `from_node_key`, `to_node_key`, `condition_expr`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('WF000000000000000000000000000021', 'WF000000000000000000000000000002', 'approve1', 'end', NULL, 'zkxw', 2, NULL, 'd3000000000000000000000000000001', '', '2026-05-13 20:56:12', 'd3000000000000000000000000000001', '2026-05-13 20:56:12', 0); +COMMIT; + +-- ---------------------------- +-- Table structure for biz_attachment +-- ---------------------------- +DROP TABLE IF EXISTS `biz_attachment`; +CREATE TABLE `biz_attachment` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `business_type` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务类型(如 collection_protocol)', + `business_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务主键ID', + `original_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名', + `file_ext` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件后缀名(如 .pdf .jpg)', + `file_url` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'MinIO路径后缀(不含前缀)', + `file_size` bigint(20) DEFAULT NULL COMMENT '文件大小(字节)', + `file_type` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'MIME类型', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_att_biz` (`business_type`,`business_id`,`del_flag`), + KEY `idx_att_tenant` (`tenant_no`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='业务附件'; + +-- ---------------------------- +-- Records of biz_attachment +-- ---------------------------- +BEGIN; +COMMIT; + + + +-- ---------------------------- +-- Table structure for sys_ethnic (民族码表) +-- ---------------------------- +-- 民族码表(国标 GB/T 3304,仅数字编码条目,不含字母编码重复项) +-- 执行前请确认库中无同名表;与 docs/sql/zkxw.sql 全量基线可并存,亦可合并进全量导出 + +DROP TABLE IF EXISTS `sys_ethnic`; +CREATE TABLE `sys_ethnic` ( + `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', + `ethnic_name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '民族名称', + `ethnic_code` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '民族编码', + `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '租户编号(空=全局码表)', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_sys_ethnic_code` (`ethnic_code`,`del_flag`), + KEY `idx_sys_ethnic_name` (`ethnic_name`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='民族码表'; + +BEGIN; +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-01', '汉族', '01', '', 1, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-02', '蒙古族', '02', '', 2, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-03', '回族', '03', '', 3, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-04', '藏族', '04', '', 4, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-05', '维吾尔族', '05', '', 5, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-06', '苗族', '06', '', 6, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-07', '彝族', '07', '', 7, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-08', '壮族', '08', '', 8, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-09', '布依族', '09', '', 9, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-10', '朝鲜族', '10', '', 10, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-11', '满族', '11', '', 11, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-12', '侗族', '12', '', 12, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-13', '瑶族', '13', '', 13, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-14', '白族', '14', '', 14, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-15', '土家族', '15', '', 15, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-16', '哈尼族', '16', '', 16, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-17', '哈萨克族', '17', '', 17, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-18', '傣族', '18', '', 18, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-19', '黎族', '19', '', 19, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-20', '傈僳族', '20', '', 20, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-21', '佤族', '21', '', 21, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-22', '畲族', '22', '', 22, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-23', '高山族', '23', '', 23, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-24', '拉祜族', '24', '', 24, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-25', '水族', '25', '', 25, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-26', '东乡族', '26', '', 26, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-27', '纳西族', '27', '', 27, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-28', '景颇族', '28', '', 28, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-29', '柯尔克孜族', '29', '', 29, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-30', '土族', '30', '', 30, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-31', '达斡尔族', '31', '', 31, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-32', '仫佬族', '32', '', 32, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-33', '羌族', '33', '', 33, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-34', '布朗族', '34', '', 34, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-35', '撒拉族', '35', '', 35, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-36', '毛难族', '36', '', 36, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-37', '仡佬族', '37', '', 37, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-38', '锡伯族', '38', '', 38, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-39', '阿昌族', '39', '', 39, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-40', '普米族', '40', '', 40, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-41', '塔吉克族', '41', '', 41, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-42', '怒族', '42', '', 42, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-43', '乌孜别克族', '43', '', 43, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-44', '俄罗斯族', '44', '', 44, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-45', '鄂温克族', '45', '', 45, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-46', '德昂族', '46', '', 46, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-47', '保安族', '47', '', 47, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-48', '裕固族', '48', '', 48, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-49', '京族', '49', '', 49, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-50', '塔塔尔族', '50', '', 50, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-51', '独龙族', '51', '', 51, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-52', '鄂伦春族', '52', '', 52, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-53', '赫哲族', '53', '', 53, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-54', '门巴族', '54', '', 54, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-55', '珞巴族', '55', '', 55, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-56', '基诺族', '56', '', 56, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-81', '穿青人族', '81', '', 57, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-97', '其他', '97', '', 58, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +INSERT INTO `sys_ethnic` (`id`, `ethnic_name`, `ethnic_code`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('ethnic-98', '外国血统中国籍人士', '98', '', 59, NULL, NULL, '', '2026-05-16 00:00:00', NULL, '2026-05-16 00:00:00', 0); +COMMIT; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/docs/superpowers/plans/2026-05-13-cell-storage-protocol-modules.md b/docs/superpowers/plans/2026-05-13-cell-storage-protocol-modules.md new file mode 100644 index 00000000..b9a257d6 --- /dev/null +++ b/docs/superpowers/plans/2026-05-13-cell-storage-protocol-modules.md @@ -0,0 +1,59 @@ +# 细胞存储管理(2.2.6~2.2.10)Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 交付细胞存储管理一级菜单下 5 个页面及对应后端 API(方案 C),满足已定稿设计:`docs/superpowers/specs/2026-05-13-cell-storage-protocol-modules-design.md` 与 OpenSpec 变更 `openspec/changes/cell-storage-protocol-modules/`。 + +**Architecture:** FastAPI 路由挂在 `/web/admin` 旁路扩展;SQLAlchemy Model 覆盖四张业务表;CRUD 层封装租户过滤与 camelCase 列表 DTO;Vue 页面复制 `system/unit` 列表骨架并按模块替换列与弹窗。 + +**Tech Stack:** Python 3.9、FastAPI、SQLAlchemy、Pydantic v2;Vue 3、Element Plus、pnpm、TypeScript。 + +--- + +## 文件映射(落地前对齐) + +| 区域 | 路径 | +|------|------| +| 设计/契约 | `docs/superpowers/specs/2026-05-13-cell-storage-protocol-modules-design.md`,`openspec/changes/cell-storage-protocol-modules/` | +| Model | `backend/app/models/models.py` | +| CRUD | `backend/app/crud/collection_protocol.py`(或按职责拆分文件) | +| API | `backend/app/api/collection_protocol.py`,注册 `app/api/__init__.py`、`main.py` | +| 菜单 | `backend/app/api/menu.py` | +| 前端路由 | `web-admin/src/router/modules/sqxp.ts` | +| 前端 API | `web-admin/src/api/collection-protocol.ts`(命名以仓库惯例为准) | +| 页面 | `web-admin/src/views/cell-storage/{protocol,protocol-audit,payment-confirm,refund-confirm,installment-warning}/index.vue` | + +--- + +## 执行清单(与 OpenSpec tasks.md 同步) + +按顺序执行 `openspec/changes/cell-storage-protocol-modules/tasks.md` 中章节 **1 → 7**。每完成一节,将对应 `- [ ]` 改为 `- [x]`。 + +--- + +## Self-review(对照 spec) + +| Spec capability | tasks.md 覆盖 | +|-----------------|---------------| +| cell-storage-collection-protocol | 1.x、2.x、5.1、3.x | +| cell-storage-protocol-audit | 1.x、2.x、5.2 | +| cell-storage-receipt-confirm | 1.x、2.x、5.3 | +| cell-storage-refund-confirm | 1.x、2.x、5.4 | +| cell-storage-installment-warning | 1.x、2.x、5.5 | + +无占位:具体接口路径与状态码以实现对照 design.md 为准。 + +--- + +## Execution Handoff + +Plan complete and saved to `docs/superpowers/plans/2026-05-13-cell-storage-protocol-modules.md`. + +**Two execution options:** + +1. **Subagent-Driven(推荐)** — 每个 task 派生子代理并在任务间复核 +2. **Inline Execution** — 本会话按 `openspec apply` 逐项实现(使用 executing-plans) + +**Which approach?** 若未指定,默认本会话按 OpenSpec `tasks.md` 顺序实现。 + +下一步命令:`openspec instructions apply --change cell-storage-protocol-modules --json` 读取上下文文件后开始改代码。 diff --git a/docs/superpowers/plans/2026-05-13-flow-designer-enhancements.md b/docs/superpowers/plans/2026-05-13-flow-designer-enhancements.md new file mode 100644 index 00000000..cbfbf07f --- /dev/null +++ b/docs/superpowers/plans/2026-05-13-flow-designer-enhancements.md @@ -0,0 +1,535 @@ +# FlowDesigner 功能补全实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 补全流程设计器三个核心功能缺口:审批人选择(用户/角色下拉数据加载)、连线条件分支配置(点击连线编辑条件表达式)、SQL查询帮助提示。 + +**Architecture:** 后端新增2个轻量options端点;前端FlowDesigner.vue增加edge:click事件处理、selectedEdge状态、条件表达式编辑面板、以及handleOpen时加载用户/角色数据。 + +**Tech Stack:** Python FastAPI, Vue 3 + Element Plus, AntV X6 + +--- + +## File Structure + +| 文件 | 操作 | 职责 | +|------|------|------| +| `backend/app/api/admin.py` | 修改 | 新增 `GET /web/admin/users/options` 和 `GET /web/admin/roles/options` | +| `web-admin/src/api/admin.ts` | 修改 | 新增 `fetchUserOptions()` 和 `fetchRoleOptions()` | +| `web-admin/src/views/system/workflow/designer/FlowDesigner.vue` | 修改 | 核心改动:edge事件、selectedEdge、条件编辑面板、数据加载 | + +--- + +### Task 1: 后端新增 users/options 和 roles/options 端点 + +**Files:** +- Modify: `backend/app/api/admin.py` — 在 `list_roles_for_select`(line 33)后面新增两个端点 + +- [ ] **Step 1: 新增 `GET /web/admin/users/options` 端点** + +在 `admin.py` 的 `list_roles_for_select` 函数后面(约 line 63 之后),添加: + +```python +@router.get("/users/options") +async def list_users_for_select( + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + """返回用户下拉选项(label=昵称,value=用户ID),供流程设计器审批人选择使用。""" + tenant_no = _user.get("tenant_no", "") + q = db.query(SysUser).filter(SysUser.del_flag == 0, SysUser.status == "1") + if tenant_no: + q = q.filter(SysUser.tenant_no == tenant_no) + rows = q.order_by(SysUser.order_num.asc(), SysUser.create_time.desc()).all() + out = [{"label": (r.nick_name or r.user_no or ""), "value": r.id} for r in rows] + return success_response(out) +``` + +注意:顶部已有 `from app.models.models import SysUser, SysRole, ...` 等导入,无需新增。 + +- [ ] **Step 2: 新增 `GET /web/admin/roles/options` 端点** + +紧接上面的函数后面添加: + +```python +@router.get("/roles/options") +async def list_roles_for_designer( + _user: dict = Depends(require_admin_or_super_admin), + db: Session = Depends(get_db), +): + """返回角色下拉选项(label=角色名,value=角色ID UUID),供流程设计器审批人选择使用。 + 与 /roles 不同,此端点返回角色 UUID 作为 value(引擎解析审批人需要 UUID)。""" + tenant_no = _user.get("tenant_no", "") + q = db.query(SysRole).filter(SysRole.del_flag == 0, SysRole.status == "1") + if tenant_no: + q = q.filter(SysRole.tenant_no == tenant_no) + rows = q.order_by(SysRole.order_num.asc()).all() + out = [{"label": (r.role_name or "").strip(), "value": r.id} for r in rows if (r.role_name or "").strip()] + return success_response(out) +``` + +- [ ] **Step 3: 验证后端启动** + +```bash +cd /Users/wuzilong/work/00code/zkxw/backend && source venv/bin/activate && python -c "from app.api.admin import router; print('OK')" +``` + +Expected: `OK` + +--- + +### Task 2: 前端 API 函数 + 设计器加载用户/角色数据 + +**Files:** +- Modify: `web-admin/src/api/admin.ts` — 新增两个 API 函数 +- Modify: `web-admin/src/views/system/workflow/designer/FlowDesigner.vue` — 导入 API、handleOpen 加载数据 + +- [ ] **Step 1: 在 `admin.ts` 新增两个 API 函数** + +在 `fetchGetRoleList()`(line 18-22)后面添加: + +```typescript +/** 流程设计器:用户下拉选项(label=昵称,value=用户ID) */ +export function fetchUserOptions() { + return request.get>({ + url: '/web/admin/users/options' + }) +} + +/** 流程设计器:角色下拉选项(label=角色名,value=角色ID UUID) */ +export function fetchRoleOptions() { + return request.get>({ + url: '/web/admin/roles/options' + }) +} +``` + +- [ ] **Step 2: 在 FlowDesigner.vue 导入新 API 并加载选项数据** + +修改 line 146 的 import 语句: + +```typescript +import { fetchDesignerData, fetchPublishVersion } from '@/api/workflow' +``` + +改为: + +```typescript +import { fetchDesignerData, fetchPublishVersion } from '@/api/workflow' +import { fetchUserOptions, fetchRoleOptions } from '@/api/admin' +``` + +然后在 `handleOpen()` 函数(line 742-748)中添加数据加载: + +```typescript +function handleOpen() { + selectedNode.value = null + selectedEdge.value = null + registerShapes() + nextTick(() => { + initGraph() + loadDesignerData() + loadOptions() + }) +} + +async function loadOptions() { + try { + const [users, roles] = await Promise.all([fetchUserOptions(), fetchRoleOptions()]) + userOptions.value = users || [] + roleOptions.value = roles || [] + } catch { + // 加载失败则保持空选项,不影响设计器其他功能 + } +} +``` + +注意:`selectedEdge` 在 Task 3 中定义。 + +--- + +### Task 3: 连线点击 + 条件表达式编辑器 + 连线标签 + +**Files:** +- Modify: `web-admin/src/views/system/workflow/designer/FlowDesigner.vue` + +这是最核心的改动。需要在 FlowDesigner.vue 中完成以下修改: + +- [ ] **Step 1: 新增 `selectedEdge` 响应式状态和接口定义** + +在 `selectedNode` 定义(line 201)后面添加: + +```typescript +interface ConditionRule { + field: string + op: string + value: string +} + +interface SelectedEdgeData { + id: string + fromNodeName: string + toNodeName: string + rules: ConditionRule[] + isDefault: boolean +} + +const selectedEdge = ref(null) +``` + +- [ ] **Step 2: 注册 edge:click 事件 + 修改 blank:click 和 node:click 清除 selectedEdge** + +在 `initGraph()` 函数中,`graph.on('blank:click', ...)` (line 560-563)后面添加 edge 事件: + +```typescript +// 连线点击 → 选中(仅条件节点的出线可编辑条件) +graph.on('edge:click', ({ edge }) => { + const sourceId = edge.getSourceCellId() || '' + const sourceNode = graph!.getCellById(sourceId) + const sourceData = sourceNode?.getData() || {} + const targetId = edge.getTargetCellId() || '' + const targetNode = graph!.getCellById(targetId) + const targetData = targetNode?.getData() || {} + + const edgeData = edge.getData() || {} + const rawRules = edgeData.conditionExpr ? (JSON.parse(edgeData.conditionExpr) || {}) : {} + const rules: ConditionRule[] = rawRules.rules || rawRules || [] + // 确保至少有一条规则 + if (!Array.isArray(rules) || rules.length === 0) { + rules.push({ field: '', op: '==', value: '' }) + } + // 规范化为 ConditionRule[](只保留简单的 field/op/value 三元组) + const normalizedRules: ConditionRule[] = rules.map((r: any) => ({ + field: r.field || '', + op: r.op || '==', + value: String(r.value ?? ''), + })) + + selectedEdge.value = { + id: edge.id, + fromNodeName: sourceData.nodeName || sourceId, + toNodeName: targetData.nodeName || targetId, + rules: normalizedRules, + isDefault: !edgeData.conditionExpr, + } + selectedNode.value = null +}) +``` + +同时修改 `blank:click` 和 `node:click` 事件处理,在清除/设置 `selectedNode` 时也清除 `selectedEdge`: + +```typescript +// node:click 处理中添加: +selectedEdge.value = null + +// blank:click 处理中改为: +graph.on('blank:click', () => { + selectedNode.value = null + selectedEdge.value = null +}) +``` + +- [ ] **Step 3: 连线创建时初始化 data** + +在 `createEdge()` 函数(约 line 522-539)中,给新创建的 edge 加上 data 初始化: + +在 `graph!.createEdge({...})` 调用中添加 `data: { conditionExpr: '' }` 属性: + +```typescript +createEdge() { + return graph!.createEdge({ + shape: 'edge', + data: { conditionExpr: '' }, + attrs: { + // ... 保持原有 attrs 不变 + }, + router: { name: 'manhattan' }, + connector: { name: 'rounded', args: { radius: 8 } }, + }) +} +``` + +- [ ] **Step 4: 添加同步连线条件数据到 graph edge 的函数** + +在 `syncNodeLabel` 函数后面添加: + +```typescript +// 同步条件表达式到 edge 并更新标签 +function syncEdgeCondition() { + if (!selectedEdge.value || !graph) return + const edge = graph.getCellById(selectedEdge.value.id) + if (!edge || !edge.isEdge()) return + + if (selectedEdge.value.isDefault) { + edge.setData({ ...edge.getData(), conditionExpr: '' }) + edge.setLabels([]) + } else { + const rules = selectedEdge.value.rules + .filter((r) => r.field.trim()) + .map((r) => ({ field: r.field, op: r.op, value: r.value })) + const expr = JSON.stringify({ rules }) + edge.setData({ ...edge.getData(), conditionExpr: expr }) + // 设置标签显示条件摘要 + const summary = rules.map((r) => `${r.field} ${r.op} ${r.value}`).join(' & ') + edge.setLabels([ + { + attrs: { + label: { + text: summary || '条件', + fill: '#374151', + fontSize: 11, + textAnchor: 'middle', + textVerticalAnchor: 'bottom', + }, + rect: { + fill: '#fff', + stroke: '#d1d5db', + strokeWidth: 1, + rx: 4, + ry: 4, + }, + }, + position: { + distance: 0.5, + }, + }, + ]) + } +} +``` + +- [ ] **Step 5: 修改序列化函数,从 edge data 读取 conditionExpr** + +当前 `serializeGraph()` 已经正确读取 `e.getData()?.conditionExpr`(line 670),无需修改。确认即可。 + +- [ ] **Step 6: 修改模板 — 右侧属性面板增加连线编辑状态** + +在模板的 `