From c137b46939d9cd5505bc9a31ca74b1d89c628467 Mon Sep 17 00:00:00 2001
From: zlwu <704541015@qq.com>
Date: Wed, 27 May 2026 16:52:46 +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
---
backend/app.pid | 2 +-
backend/app/api/__init__.py | 2 +
backend/app/api/clinical_payment.py | 337 +
backend/app/api/menu.py | 768 +-
backend/app/api/pilot_application.py | 10 +-
backend/app/crud/clinical_payment.py | 383 +
backend/app/crud/pilot_application.py | 71 +-
backend/app/crud/production_work_order.py | 59 +-
backend/app/crud/qc_work_order.py | 56 +-
backend/app/main.py | 3 +-
backend/app/models/clinical_payment.py | 41 +
backend/app/models/models.py | 3 +
backend/scripts/reorder_web_menus.py | 245 +
docs/demos/consumable-merge-plan-b-demo.html | 953 ++
docs/sql/sys_menu_web_init.sql | 112 +
.../plans/2026-05-27-clinical-application.md | 1252 ++
.../specs/ui-conventions/spec.md | 19 +-
.../graphify-out/.graphify_analysis.json | 2742 +++--
web-admin/graphify-out/.graphify_extract.json | 7862 ++++++------
web-admin/graphify-out/GRAPH_REPORT.md | 251 +-
web-admin/graphify-out/graph.html | 8 +-
web-admin/graphify-out/graph.json | 10011 +++++++++-------
web-admin/graphify-out/manifest.json | 189 +-
web-admin/src/api/business.ts | 84 +
web-admin/src/api/outbound-release-review.ts | 19 +
...cccfd24290389562744d773125b03cf3593a1.json | 1 +
...67290bdb86ea5d7664b6859373a6e3cb8aed9.json | 1 +
...3938de8e52617e48966a795482c4c813c16f8.json | 1 +
...c54789bd278ba8653477ff599e84808f4d0f4.json | 1 +
...7bc85ddc7527c051297ad67d7fe003255c672.json | 1 +
...b78e31aebab7832895d9d152f2bed74bcada7.json | 1 +
...4eda232cecad34720bfbd4a35af3cfca38d5b.json | 1 +
...3a1802d3359a4bec06f53c4e4dab3c39aff68.json | 1 +
...7524d2c0c0731c84e17cd9f5661ddaf3bafad.json | 1 +
...9d11eeec58425ac8dcd9bd0f8ec7598c27a12.json | 1 +
...c3a303624dfbf8a8707f519880ffb1e93b7db.json | 1 +
...6e2dbd97e981abd5c7ad573245b3d22019bbe.json | 1 +
...ecb583162d48a20369024675aff369cffdb6a.json | 1 +
...a54f26fd5ea58218af729eb1fe1b128479329.json | 1 +
...772b0d22afe6e60499c0516fc53f955e8337d.json | 1 +
...636134baa95042bc59760b49e95cf921a1b10.json | 1 +
...cecdde31236d22d796e02ccbb02abb9e0ec47.json | 1 +
...6a9b478c80b94f9af6100c53594e8b7c02049.json | 1 +
...3bd9391249061ce2bffe4e807707920105455.json | 1 +
...9e037124dd06a6bab7a8a36557fd019aeea30.json | 1 +
...065c03515c4cfd562cbdaa0fdb3ebc21239ba.json | 1 +
...1ed2b5e5741a30812fce487f4427e08e4ded3.json | 1 +
...159e452f911ddc94a67abefa57433c1eca57c.json | 1 +
...3bf91d61c31233932c3cac4e509604a3ee35d.json | 1 +
...764cae0f70bba6f4b5c2b65a6543ca9b4f503.json | 1 +
...ba0571c2c4e84e5feafe57b99261a95caf2d5.json | 1 +
...b9affb60ba5d85b30fde585fbc2ded3f0e326.json | 1 +
...69f4874ce46c503a4625f0912c6ca5e8e907a.json | 1 +
...52887cda3a8d7a56fcaada327beca8f0bf5bd.json | 1 +
...f8132c67edcb6baeeafdcaa096936e2dfe56a.json | 1 +
...32df3ccd044d79fd539514aa4942410112778.json | 1 +
...7f23671c961a77d9a996cf881d4f66c91d5e5.json | 1 +
...39b426946cb9b174deb7d90020b35cd5bc87d.json | 1 +
...35b18c1b94fed66b34eed1cbbaa8c3a5e626f.json | 1 +
...02b83bcc89d95af19ceaf3b5d08377b3e1b05.json | 1 +
...a417f35a52d1e7aa99c8686ad8304d49f0d94.json | 1 +
...68f6414ef97a7be1d884f459e4053887ccdd4.json | 1 +
...b2207816dadc316baa3acaf0dfac922b2a807.json | 1 +
...0325aedfca897683b13bba9bcc3fd9a086120.json | 1 +
...8a33922ce1fbb85443f7561ebe46d91a5cef1.json | 1 +
...ad46df6d73b18844735a99705fa7f4415d678.json | 1 +
...a115a3e38661eb5fad5585428392cb4b2fe80.json | 1 +
...23fc5d018d76e1e0d109efe68fe19c7a540df.json | 1 +
...59bfbf820894a44021aea5cea637ba8e31f8e.json | 1 +
...abe63d3ac44c47504f2b245e919ff2ab1d96c.json | 1 +
...d657269756d7c4f8431f7d78575b399986832.json | 1 +
...41ea09239e066f9c9fa03571d524eb3be0ab0.json | 1 +
...f0c217cc3ad8090cd1f24122ebb022651ee89.json | 1 +
...9599f6c0b1e862527b697926dc2ce4023f004.json | 1 +
...8463c3bf87695ad62dcb82f77ad999771b53b.json | 1 +
...9913262aed8d672b9980b3c5c98b7cee4d5d5.json | 1 +
...0e0b7672e756477623e22a4cf5b8073e11dbd.json | 1 +
...c7c8748d0b6a578522d7c10cba86f45f333a1.json | 1 +
...d27af2369833b7abf788bf8d4d2ae63a21888.json | 1 +
...e30c713077b35d938b625f73657ddd6115122.json | 1 +
...bb13416ea29dabb89f9650553531b1809974b.json | 1 +
...93f6cb6fb08c2a5e70600ba9749f56be20a84.json | 1 +
...04b93c2ceb7db0ba7b72fb78dbe5eb8806bfe.json | 1 +
...14d420388f121b188928c79a396569097396b.json | 1 +
...7fc04917303c90b5a77e5b4b1d73a71378c8b.json | 1 +
...f31fb7087ef19730f01222706910a89873780.json | 1 +
...4016bea9e9fd4e9e3ee6ad11f776acfe213fd.json | 1 +
...33ac2d98d7e994d4959ba7241221916fec478.json | 1 +
...bd0d105b6d22ae2daa0334808781881f7c412.json | 1 +
...1795824ce47f7bcc7e7b39a3077966051a5aa.json | 1 +
...621f5891edeef2c4c70dbf9f1191071999b39.json | 1 +
...a1f325f0d51b91c841674b62fd25ae79ca6af.json | 1 +
...55c62a0984107dd30bcb72a6b93dbe121ffbe.json | 1 +
...011ee5630eaf838dfff4cf07884f889c30c33.json | 1 +
...2203d59e2c90ec7b124b417d1f8a8290f70a4.json | 1 +
...946090bf2bd5540317cb1208d90b42fb06d51.json | 1 +
...4b2d6ed28b61fe8f24854c8bc5750e3e3e27c.json | 1 +
...459142f3648b909383c51d39e404d663c7fdc.json | 1 +
...9c52e1ac5fd3e894f15522172e7f7a82d2eaf.json | 1 +
...88ba709de1d807677741f5615fc2a696f4d7a.json | 1 +
...023e068eb21da3c566bdbd941a8676b3b5d3b.json | 1 +
...c32e3e7a06911272a925b72c32242ed35ba43.json | 1 +
...db4706faf2ec79aac9ea50ff093aabe7b71c1.json | 1 +
...ba5627769750daece40478a09d34bc675f676.json | 1 +
...4a3723304a65cc4a5c5e7feab22a396c742d8.json | 1 +
...e50cce592e820b86995c8700631be62e7a944.json | 1 +
...80bd17d277477309ca62c2bc20391fee1eecb.json | 1 +
...16ba4f7b04a55e1eb63fe7e744c8b7469fde1.json | 1 +
...05b1a07681dd4c869b71333915462ee42fef0.json | 1 +
...41974edcfc426cf62590f4c58a5f94c38b2d1.json | 1 +
...80475709d4c73286f8c4eecea73d74d75a455.json | 1 +
...f837890fcc28ec3718a662bdcb0206ebecdf2.json | 1 +
...3a3860db0686558f8957366556e90548e7a4e.json | 1 +
...3723ac30672c57dbaa6d27ae5ff60eba1a49a.json | 1 +
...beec1525fa070e10242aa8133d95c5c591e22.json | 1 +
...b6917fde07c30d4b5ece6cf3edaa220af30fb.json | 1 +
...23a519423a9b4ceae4124ff8cbaa4f5d94976.json | 1 +
...0735e787df0da4db29307008f644b44c7fdc2.json | 1 +
...0934ce76ad8b7a2966d3c96461138d2347505.json | 1 +
...dc5e573a2ab0d489412bd8701efcfe9eaba3f.json | 1 +
...e30120e72974a4a6996ec2bd42ebe5907d461.json | 1 +
...e4802450ee4dee360bea5bf6200dbf1fc514c.json | 1 +
...ecd98c68a577c62c40f2148511262f2556093.json | 1 +
...53f2bb446f51ea5df60f6ee9da3a550f4f922.json | 1 +
...0502d17bd3e05374961c3ce83cb5749542f08.json | 1 +
...bf66eae61a3b58e398fa1a41c2f77b3111819.json | 1 +
...f630cdb4c2ff594284fc85f38184e75277eb7.json | 1 +
...5bdd12706f24ddcdc6b263db0fc01a7aba922.json | 1 +
...8b5b22b9ad505388c4e1d392b74de1c37622a.json | 1 +
...97d41bb8e5857803fa516bd1e20032609a0ca.json | 1 +
...ed40b552f20003daac6f8d90cd8eae225dd29.json | 1 +
...6a9b44eaed4990e2745eb765fce97a60362ce.json | 1 +
...e2ff4298e55641ce04528b03fe6e91d72548b.json | 1 +
...6db6345a7db674a4dd89be27d2849ed8b7bb6.json | 1 +
...8f067fc4ec5e2a849c21f995579618bd88185.json | 1 +
...57617e39ea3bff74e2050da0d97147158de90.json | 1 +
...7a9a695c0cfb2ccb1b388e0d1d772f694a94f.json | 1 +
...b354cf952d311d17483dd3a115f3935dcb1ae.json | 1 +
...a249fcd3d9f31566615eb0eab3d98a90fc2e4.json | 1 +
...51d6a44c49625647debcdab64cfea515175a0.json | 1 +
...c8c0bc2bd3dadb1bad0a9f444f29e5724d789.json | 1 +
...988734f15a5441e26d5c7d2c364d66dbbb84e.json | 1 +
...20735e2fecae488ea48cb7c595cdb9395079f.json | 1 +
...0a1480fcbaf19e3d27f469e614bf2c0d1e415.json | 1 +
...a592b86148052a64dd3f7382db8afd250ff8d.json | 1 +
...131e4f9644941c5f63471068edee90b0f9d44.json | 1 +
...c4a51053993e50a58cb4c4527d1d7fb0316b0.json | 1 +
...782307c5fe147c18dc3ab8962c34ac9381cea.json | 1 +
...bba45632645072fffa2d0ab51f66228f389ef.json | 1 +
...1d4d6e0c092e943e7e8b8d894ee0257651b2d.json | 1 +
...0d465d95fc57e889d4cd0bcf28945c6c96624.json | 1 +
...f971d18b157c388e138802a237ccba0c83d8a.json | 1 +
...3785b42ef0571483c095a48c64cf6e27894f1.json | 1 +
...8e7281de3107bfe84d7f53be5dcb3eb03a7c1.json | 1 +
...bdb2381b352eda17c5cb989e72cc408301a74.json | 1 +
...f43b02cad88d2d2531f5741535405450f0c87.json | 1 +
...6f588fbfcec36b7490b49566769e8b199d8a1.json | 1 +
...531139586277a6f4894230138108409033488.json | 1 +
...cf305f59bbbb908ef1464d4cbe2b224c5865c.json | 1 +
...c9942f9313488763687fda47015dfcdf53d7b.json | 1 +
...916f7707d7e882e543fc5da46c1102bba5868.json | 1 +
...19c609bf4e56c706a81077a5006a9bb0fbef1.json | 1 +
...bf809905a39c37160d8fde0b246c911425f50.json | 1 +
...6ddb0d6f94fa6f606b00657a06339d735d0d0.json | 1 +
...6ccd76c4cf03c3860f3824b32bc6da2f4ad93.json | 1 +
...89be1258eb7ee0227fae3748dfa03f67d44f9.json | 1 +
...d380fd47fdf7cd005022f8b75615ab5b738aa.json | 1 +
...0d657292c0d3b3070b50d740f309b8a221c92.json | 1 +
...a0c09532b545c2439e9ce52873a3635ba5962.json | 1 +
...6894e81735ed52044a54ea08114ca8b9d3cf9.json | 1 +
...91c4f395936b12aa9d7682cdd755f62bf54e2.json | 1 +
...8f483a74c691032f4b06259cddcc621cf52c2.json | 1 +
...d477b899527e0e511ba9d24186fd3d50c4171.json | 1 +
...99538fec0aff7a6d8a1367a192cde33ee9884.json | 1 +
...998cbbad79177d040d71a850d7a6fbc189321.json | 1 +
...64db607408c6a65dca2e2350f96c1b68cc323.json | 1 +
...545f9d6044aea7c421448a90d14b95b8e0c4e.json | 1 +
...ddac0d0eaeb3ec087941120cf6bf78ed9a223.json | 1 +
...4f6faec9b79fe83fdb5fb275102890ccba215.json | 1 +
...cdb83828810e4b37d82c7f7c8d10e5b00c34f.json | 1 +
...d3eb980de35c47a091dc693a292e0a8d5dcea.json | 1 +
...d6ce0345fc8cdd8ca6407fde21c23be995876.json | 1 +
...fe43b2865a7b45c71bcff41503ac683fce42f.json | 1 +
...6cf31e0290d24dd57a391769fe3f03ea9d7b5.json | 1 +
...816972c48315825a76c6bd36ec976daa18720.json | 1 +
...232393f89c0e3aa611b752026a7cce34376bd.json | 1 +
...25bbb36c660bf27de64a01894acda3abdc6a3.json | 1 +
...5d268e4bebc8682e4379e652c5638820c2978.json | 1 +
...e2234cd1f96b0f5b442ca633f27f1e3d061a6.json | 1 +
...88314e912d798c685adeea61f90400649efd0.json | 1 +
...a4e30f8de18b2a7a5259f23a3b27312a0909d.json | 1 +
...23b1261f59c62d1cc4608b20d55776001b984.json | 1 +
...4d9a4fa9a1c313c57d15057e9fa7c5795b491.json | 1 +
...ee5e7b00537036263744f32e06f95de913bf7.json | 1 +
...d0fbec7e4615610dfbedd865adbefc636b18b.json | 1 +
...c4faed0a175818b2680eeb45cad1c1a4b2958.json | 1 +
...2edb42f896dea77d3d329a72929018e8af384.json | 1 +
...f8e4e2919d86114bf82a85b7ccd07817c1040.json | 1 +
...8a5f11c312ff53459bd3ed28b835d9eabc991.json | 1 +
...b58d24bb416e3d62088c46fb5ac9cf722bea8.json | 1 +
...71266066d87653b720a0cddaa1bfd0a45dd2f.json | 1 +
...c75ca132878f001d030a9c4ced42d2c4d8922.json | 1 +
...cd2df3b0bb24ebde8cabfc90d8eb36170e6df.json | 1 +
...7a1d101555a0deb7f95a9db9b7589865fa531.json | 1 +
...87f0b00e182ebe4a2142da14ea5cdfb58d939.json | 1 +
...2a9a960ec574c41d018c53b2150140945397b.json | 1 +
...f2bf3050df5d3ed11e002f9aa9b4d68d9c0dd.json | 1 +
web-admin/src/router/modules/sqxp.ts | 1320 +-
.../cell-sample-type/SampleCodeTab.vue | 836 ++
.../basic-info/cell-sample-type/index.vue | 61 +-
.../collection-protocol-no/index.vue | 781 +-
.../CollectionProtocolNoTab.vue | 795 ++
.../basic-info/collection-region/index.vue | 65 +-
.../src/views/basic-info/customer/index.vue | 41 +-
.../basic-info/insurance-company/index.vue | 51 +-
.../views/basic-info/sample-code/index.vue | 822 +-
.../cell-storage/fullmoon-todo/index.vue | 1287 +-
.../cell-storage/material-stock-log/index.vue | 303 +-
.../material/MaterialStockLogTab.vue | 292 +
.../src/views/cell-storage/material/index.vue | 76 +-
.../payment-confirm/RefundConfirmTab.vue | 632 +
.../cell-storage/payment-confirm/index.vue | 184 +-
.../views/cell-storage/payment-info/index.vue | 900 +-
.../protocol-audit/FullmoonTodoTab.vue | 1276 ++
.../protocol-audit/PaymentInfoTab.vue | 852 ++
.../protocol-audit/ProtocolAuditTab.vue | 582 +
.../protocol-audit/SampleReceptionInfoTab.vue | 352 +
.../protocol-audit/StorageCertTab.vue | 879 ++
.../protocol-audit/StorageEntryInfoTab.vue | 345 +
.../cell-storage/protocol-audit/index.vue | 756 +-
.../src/views/cell-storage/protocol/index.vue | 43 +-
.../cell-storage/refund-confirm/index.vue | 680 +-
.../sample-reception-info/index.vue | 361 +-
.../views/cell-storage/storage-cert/index.vue | 888 +-
.../cell-storage/storage-entry-info/index.vue | 354 +-
.../src/views/dashboard/console/index.vue | 5 -
.../src/views/operations/clean-room/index.vue | 47 +-
web-admin/src/views/operations/demo/index.vue | 5 -
.../exhibition-appointment/index.vue | 41 +-
.../operations/storage-facility/index.vue | 5 -
.../cell-product-price/index.vue | 536 +-
.../pilot-service/cell-product-spec/index.vue | 387 +-
.../cell-product-type/CellProductPriceTab.vue | 547 +
.../cell-product-type/CellProductSpecTab.vue | 395 +
.../ProjectPriceSystemTab.vue | 807 ++
.../pilot-service/cell-product-type/index.vue | 72 +-
.../channel-service-order-finance/index.vue | 5 -
.../channel-service-order/index.vue | 2344 +---
.../channel/ChannelServiceOrderTab.vue | 2352 ++++
.../src/views/pilot-service/channel/index.vue | 82 +-
.../checkup-institution/index.vue | 64 +-
.../checkup-management/index.vue | 429 +-
.../clinical-payment-confirm/index.vue | 726 ++
.../clinical-payment-info/index.vue | 707 ++
.../clinical-refund-confirm/index.vue | 804 ++
.../infusion-schedule-management/index.vue | 547 +-
.../outbound-release-review/index.vue | 5 -
.../ApplicationDetailDialog.vue | 4 +-
.../ApplicationFormDialog.vue | 60 +-
.../CheckupManagementTab.vue | 475 +
.../pilot-application/InfusionScheduleTab.vue | 643 +
.../pilot-application/PilotApplicationTab.vue | 647 +
.../pilot-service/pilot-application/index.vue | 726 +-
.../project-price-system/index.vue | 799 +-
.../views/production/cell-product/index.vue | 45 +-
.../production/consumable-pack/index.vue | 877 +-
.../consumable/ConsumablePackTab.vue | 888 ++
.../src/views/production/consumable/index.vue | 88 +-
.../views/production/output-type/index.vue | 374 +-
.../process-category/OutputTypeTab.vue | 388 +
.../production/process-category/index.vue | 59 +-
.../views/production/process-route/index.vue | 2556 +---
.../production/process/ProcessRouteTab.vue | 2571 ++++
.../src/views/production/process/index.vue | 23 +-
.../production/production-task/index.vue | 505 +-
.../ProductionTaskTab.vue | 557 +
.../production-work-order/index.vue | 199 +-
.../src/views/production/reagent/index.vue | 76 +-
web-admin/src/views/production/sop/index.vue | 39 +-
.../lab-test-item/ThirdPartyCompanyTab.vue | 1003 ++
.../lab-test-item/index.vue | 68 +-
.../qc-material-usage/index.vue | 468 +-
.../qc-material/QcMaterialUsageTab.vue | 456 +
.../quality-inspection/qc-material/index.vue | 78 +-
.../quality-inspection/qc-task/index.vue | 1477 +--
.../qc-work-order/QcTaskTab.vue | 1525 +++
.../qc-work-order/index.vue | 301 +-
.../sample-reception-check-item/index.vue | 5 -
.../sample-reception/index.vue | 7 -
.../storage-release-review/index.vue | 5 -
.../third-party-company/index.vue | 988 +-
.../views/research-donation/audit/index.vue | 53 +-
.../views/research-donation/list/index.vue | 44 +-
web-admin/src/views/system/category/index.vue | 64 +-
web-admin/src/views/system/dept/index.vue | 55 +-
web-admin/src/views/system/dict/index.vue | 55 +-
web-admin/src/views/system/express/index.vue | 45 +-
web-admin/src/views/system/leave/index.vue | 5 -
web-admin/src/views/system/menu/index.vue | 55 +-
web-admin/src/views/system/order/index.vue | 6 -
web-admin/src/views/system/post/index.vue | 57 +-
web-admin/src/views/system/product/index.vue | 49 +-
web-admin/src/views/system/role/index.vue | 63 +-
web-admin/src/views/system/site/index.vue | 45 +-
web-admin/src/views/system/tenant/index.vue | 56 +-
web-admin/src/views/system/unit/index.vue | 46 +-
web-admin/src/views/system/user/index.vue | 5 -
.../system/workflow/my-applications/index.vue | 5 -
.../views/system/workflow/process/index.vue | 46 +-
.../system/workflow/task-completed/index.vue | 5 -
.../system/workflow/task-pending/index.vue | 5 -
311 files changed, 41432 insertions(+), 30702 deletions(-)
create mode 100644 backend/app/api/clinical_payment.py
create mode 100644 backend/app/crud/clinical_payment.py
create mode 100644 backend/app/models/clinical_payment.py
create mode 100644 backend/scripts/reorder_web_menus.py
create mode 100644 docs/demos/consumable-merge-plan-b-demo.html
create mode 100644 docs/sql/sys_menu_web_init.sql
create mode 100644 docs/superpowers/plans/2026-05-27-clinical-application.md
create mode 100644 web-admin/src/graphify-out/cache/ast/0048628a09134908329309cbf1bcccfd24290389562744d773125b03cf3593a1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/00556972ac4b1b4beb9091c8f1567290bdb86ea5d7664b6859373a6e3cb8aed9.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0162e61ecb4b27170c406f25b873938de8e52617e48966a795482c4c813c16f8.json
create mode 100644 web-admin/src/graphify-out/cache/ast/024fa75e46c5aaf9447efaf5a8ac54789bd278ba8653477ff599e84808f4d0f4.json
create mode 100644 web-admin/src/graphify-out/cache/ast/02f12487a23b6d0710ff461dbee7bc85ddc7527c051297ad67d7fe003255c672.json
create mode 100644 web-admin/src/graphify-out/cache/ast/035f86b5daf6ba5964ceaefb989b78e31aebab7832895d9d152f2bed74bcada7.json
create mode 100644 web-admin/src/graphify-out/cache/ast/041e8a325e35ddc0c6b3a07521b4eda232cecad34720bfbd4a35af3cfca38d5b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0459386165cecb7d1a7789d994b3a1802d3359a4bec06f53c4e4dab3c39aff68.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0554b3ed92b76ba0b627be580647524d2c0c0731c84e17cd9f5661ddaf3bafad.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0572f5844e061e3122e45bffcbe9d11eeec58425ac8dcd9bd0f8ec7598c27a12.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0773fed5888cdd758ebebc324efc3a303624dfbf8a8707f519880ffb1e93b7db.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0b46369cd097d6992a8410e85196e2dbd97e981abd5c7ad573245b3d22019bbe.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0d592d27b0cce70e8c844da524becb583162d48a20369024675aff369cffdb6a.json
create mode 100644 web-admin/src/graphify-out/cache/ast/0f3b8de64354deb2743df7cc701a54f26fd5ea58218af729eb1fe1b128479329.json
create mode 100644 web-admin/src/graphify-out/cache/ast/13842c158398164882222a6609c772b0d22afe6e60499c0516fc53f955e8337d.json
create mode 100644 web-admin/src/graphify-out/cache/ast/14c5baef0a4ffdb39e5a765ec00636134baa95042bc59760b49e95cf921a1b10.json
create mode 100644 web-admin/src/graphify-out/cache/ast/153e3475faf9420d1c7994ff360cecdde31236d22d796e02ccbb02abb9e0ec47.json
create mode 100644 web-admin/src/graphify-out/cache/ast/1589cec8c3d30d32888103539646a9b478c80b94f9af6100c53594e8b7c02049.json
create mode 100644 web-admin/src/graphify-out/cache/ast/1632aa4983130140a987295183a3bd9391249061ce2bffe4e807707920105455.json
create mode 100644 web-admin/src/graphify-out/cache/ast/16dcd094b946382dcee77771caa9e037124dd06a6bab7a8a36557fd019aeea30.json
create mode 100644 web-admin/src/graphify-out/cache/ast/19f83f40e12350cde4b83a3b022065c03515c4cfd562cbdaa0fdb3ebc21239ba.json
create mode 100644 web-admin/src/graphify-out/cache/ast/1a486447f8d05948582451299621ed2b5e5741a30812fce487f4427e08e4ded3.json
create mode 100644 web-admin/src/graphify-out/cache/ast/1f7d641dc2fce3c3f47c060a5e3159e452f911ddc94a67abefa57433c1eca57c.json
create mode 100644 web-admin/src/graphify-out/cache/ast/20a9e97f879bc3d772bbbe558863bf91d61c31233932c3cac4e509604a3ee35d.json
create mode 100644 web-admin/src/graphify-out/cache/ast/20d88bef264f39c5b0ee6a88ab9764cae0f70bba6f4b5c2b65a6543ca9b4f503.json
create mode 100644 web-admin/src/graphify-out/cache/ast/21cd62d1a9d27c4e818d92f8cc9ba0571c2c4e84e5feafe57b99261a95caf2d5.json
create mode 100644 web-admin/src/graphify-out/cache/ast/21d9745513c740f3079ebe499e2b9affb60ba5d85b30fde585fbc2ded3f0e326.json
create mode 100644 web-admin/src/graphify-out/cache/ast/230a33677e5c12676afbc39615f69f4874ce46c503a4625f0912c6ca5e8e907a.json
create mode 100644 web-admin/src/graphify-out/cache/ast/2597c0acf60162633aa3436bcf452887cda3a8d7a56fcaada327beca8f0bf5bd.json
create mode 100644 web-admin/src/graphify-out/cache/ast/2604168a3b8d813ed6c82e56feff8132c67edcb6baeeafdcaa096936e2dfe56a.json
create mode 100644 web-admin/src/graphify-out/cache/ast/27cd76c7c73cec3158edfa6a4af32df3ccd044d79fd539514aa4942410112778.json
create mode 100644 web-admin/src/graphify-out/cache/ast/28eae1012481d891d513d98328f7f23671c961a77d9a996cf881d4f66c91d5e5.json
create mode 100644 web-admin/src/graphify-out/cache/ast/295c338e7c24c1afae35a1b126039b426946cb9b174deb7d90020b35cd5bc87d.json
create mode 100644 web-admin/src/graphify-out/cache/ast/2a203d1f3b36abf2d6b3abaab9135b18c1b94fed66b34eed1cbbaa8c3a5e626f.json
create mode 100644 web-admin/src/graphify-out/cache/ast/2b594e41ee74f51e0b8a7d24c7602b83bcc89d95af19ceaf3b5d08377b3e1b05.json
create mode 100644 web-admin/src/graphify-out/cache/ast/2dffa50a07a802d35b120581162a417f35a52d1e7aa99c8686ad8304d49f0d94.json
create mode 100644 web-admin/src/graphify-out/cache/ast/2eb61881be5441a7148e9b822cb68f6414ef97a7be1d884f459e4053887ccdd4.json
create mode 100644 web-admin/src/graphify-out/cache/ast/2fcec7512c1a1cbc8211a2a9ea2b2207816dadc316baa3acaf0dfac922b2a807.json
create mode 100644 web-admin/src/graphify-out/cache/ast/31852eb9f6a7157f21095d73fc20325aedfca897683b13bba9bcc3fd9a086120.json
create mode 100644 web-admin/src/graphify-out/cache/ast/31ba9619b42210f39e91ccb4a518a33922ce1fbb85443f7561ebe46d91a5cef1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/32705428d755743adc946bc420cad46df6d73b18844735a99705fa7f4415d678.json
create mode 100644 web-admin/src/graphify-out/cache/ast/34a69905fa3e1e33dae770d87f6a115a3e38661eb5fad5585428392cb4b2fe80.json
create mode 100644 web-admin/src/graphify-out/cache/ast/3761501383f7e90ccdd191fb4e323fc5d018d76e1e0d109efe68fe19c7a540df.json
create mode 100644 web-admin/src/graphify-out/cache/ast/39f68f59b0924a2d1efcd5db12959bfbf820894a44021aea5cea637ba8e31f8e.json
create mode 100644 web-admin/src/graphify-out/cache/ast/3a7b373a16ea30e90c734f06d53abe63d3ac44c47504f2b245e919ff2ab1d96c.json
create mode 100644 web-admin/src/graphify-out/cache/ast/3e4c0849e5f53f6ae66ce38d5b4d657269756d7c4f8431f7d78575b399986832.json
create mode 100644 web-admin/src/graphify-out/cache/ast/3edbfaea07da4c331b419bf890d41ea09239e066f9c9fa03571d524eb3be0ab0.json
create mode 100644 web-admin/src/graphify-out/cache/ast/3f172d5327144550933096313fcf0c217cc3ad8090cd1f24122ebb022651ee89.json
create mode 100644 web-admin/src/graphify-out/cache/ast/411e90d53742100819a74bf77c79599f6c0b1e862527b697926dc2ce4023f004.json
create mode 100644 web-admin/src/graphify-out/cache/ast/419125279e953fd55b1b22782298463c3bf87695ad62dcb82f77ad999771b53b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/459fd615e1ffefc104f5acca64f9913262aed8d672b9980b3c5c98b7cee4d5d5.json
create mode 100644 web-admin/src/graphify-out/cache/ast/49e157600c1d5455956547ab9860e0b7672e756477623e22a4cf5b8073e11dbd.json
create mode 100644 web-admin/src/graphify-out/cache/ast/4c1bf9bbe672c8c554f4b3c92dfc7c8748d0b6a578522d7c10cba86f45f333a1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/4e3e6b7cbed5535434212eae822d27af2369833b7abf788bf8d4d2ae63a21888.json
create mode 100644 web-admin/src/graphify-out/cache/ast/4eed24d3a7e127dbbd68bd6642be30c713077b35d938b625f73657ddd6115122.json
create mode 100644 web-admin/src/graphify-out/cache/ast/50398e1efa94951d53c53ef8a7fbb13416ea29dabb89f9650553531b1809974b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/529e4eb020c2b433c948f85812593f6cb6fb08c2a5e70600ba9749f56be20a84.json
create mode 100644 web-admin/src/graphify-out/cache/ast/533abb444a93efc580923b2ae2e04b93c2ceb7db0ba7b72fb78dbe5eb8806bfe.json
create mode 100644 web-admin/src/graphify-out/cache/ast/5495bd55c1d757512740a58413f14d420388f121b188928c79a396569097396b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/54de01c5e177c98894b3fc5220e7fc04917303c90b5a77e5b4b1d73a71378c8b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/573f1dd79b17bda42870d3b1d27f31fb7087ef19730f01222706910a89873780.json
create mode 100644 web-admin/src/graphify-out/cache/ast/57a04c8ed39c94d1401e4b6a8564016bea9e9fd4e9e3ee6ad11f776acfe213fd.json
create mode 100644 web-admin/src/graphify-out/cache/ast/5bed965c02144b6a51cbb40382633ac2d98d7e994d4959ba7241221916fec478.json
create mode 100644 web-admin/src/graphify-out/cache/ast/5c99c7122cfea118ed3d57df1cdbd0d105b6d22ae2daa0334808781881f7c412.json
create mode 100644 web-admin/src/graphify-out/cache/ast/5ef210e222ab76b9524a880840c1795824ce47f7bcc7e7b39a3077966051a5aa.json
create mode 100644 web-admin/src/graphify-out/cache/ast/6005f59e69b6168e3a31064fa3f621f5891edeef2c4c70dbf9f1191071999b39.json
create mode 100644 web-admin/src/graphify-out/cache/ast/60426c3693c78ac2d3732b1292ca1f325f0d51b91c841674b62fd25ae79ca6af.json
create mode 100644 web-admin/src/graphify-out/cache/ast/62385f6fbc5e68cd7f9b4d0cccb55c62a0984107dd30bcb72a6b93dbe121ffbe.json
create mode 100644 web-admin/src/graphify-out/cache/ast/6294853ab5d831d74f0c0ee4a95011ee5630eaf838dfff4cf07884f889c30c33.json
create mode 100644 web-admin/src/graphify-out/cache/ast/62d9357053a99b3207d790d4d412203d59e2c90ec7b124b417d1f8a8290f70a4.json
create mode 100644 web-admin/src/graphify-out/cache/ast/6323743b3b22456f7dfd757392b946090bf2bd5540317cb1208d90b42fb06d51.json
create mode 100644 web-admin/src/graphify-out/cache/ast/6377d823ea111573ccbbcac60f64b2d6ed28b61fe8f24854c8bc5750e3e3e27c.json
create mode 100644 web-admin/src/graphify-out/cache/ast/64478b1a4c72753e2c2ce6d5864459142f3648b909383c51d39e404d663c7fdc.json
create mode 100644 web-admin/src/graphify-out/cache/ast/655739a73356b2d158bcaeabc6d9c52e1ac5fd3e894f15522172e7f7a82d2eaf.json
create mode 100644 web-admin/src/graphify-out/cache/ast/6771fc4942afa191531223679e988ba709de1d807677741f5615fc2a696f4d7a.json
create mode 100644 web-admin/src/graphify-out/cache/ast/68c6b8f9e9566d6b20ce509688f023e068eb21da3c566bdbd941a8676b3b5d3b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/68f9afe961a6e5294cf03bfb82fc32e3e7a06911272a925b72c32242ed35ba43.json
create mode 100644 web-admin/src/graphify-out/cache/ast/694808c626b12862f28cb9f2fa0db4706faf2ec79aac9ea50ff093aabe7b71c1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/6a7e3a11c3391001a62119fb386ba5627769750daece40478a09d34bc675f676.json
create mode 100644 web-admin/src/graphify-out/cache/ast/6ef5c0817cd7511d746ec945a984a3723304a65cc4a5c5e7feab22a396c742d8.json
create mode 100644 web-admin/src/graphify-out/cache/ast/71370226022c58d1a8f2d6bf9b7e50cce592e820b86995c8700631be62e7a944.json
create mode 100644 web-admin/src/graphify-out/cache/ast/75b7cbdbec97868307c71dc901180bd17d277477309ca62c2bc20391fee1eecb.json
create mode 100644 web-admin/src/graphify-out/cache/ast/75cfcc05386bc7f8e9d2f6d7c1e16ba4f7b04a55e1eb63fe7e744c8b7469fde1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/769e63fc9b199fb101e4a80cbd205b1a07681dd4c869b71333915462ee42fef0.json
create mode 100644 web-admin/src/graphify-out/cache/ast/76a32f84f2139c7e8dff9ca512f41974edcfc426cf62590f4c58a5f94c38b2d1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/76e858c53ec9f33b92f21f4bb4a80475709d4c73286f8c4eecea73d74d75a455.json
create mode 100644 web-admin/src/graphify-out/cache/ast/791796312ed7eee029aebd08f9ef837890fcc28ec3718a662bdcb0206ebecdf2.json
create mode 100644 web-admin/src/graphify-out/cache/ast/795d4022b14569be97432535e913a3860db0686558f8957366556e90548e7a4e.json
create mode 100644 web-admin/src/graphify-out/cache/ast/7aed9a863ce12deb1a5bb560d273723ac30672c57dbaa6d27ae5ff60eba1a49a.json
create mode 100644 web-admin/src/graphify-out/cache/ast/7bea123ce39186d0a973980a415beec1525fa070e10242aa8133d95c5c591e22.json
create mode 100644 web-admin/src/graphify-out/cache/ast/7c49f69910bfa7f089f959e9274b6917fde07c30d4b5ece6cf3edaa220af30fb.json
create mode 100644 web-admin/src/graphify-out/cache/ast/7d00ee299566222ab5fc7ab2f7023a519423a9b4ceae4124ff8cbaa4f5d94976.json
create mode 100644 web-admin/src/graphify-out/cache/ast/7f1f4a50991c67b9570c0c45d700735e787df0da4db29307008f644b44c7fdc2.json
create mode 100644 web-admin/src/graphify-out/cache/ast/828854919e6fbe9a1b48f7cd30c0934ce76ad8b7a2966d3c96461138d2347505.json
create mode 100644 web-admin/src/graphify-out/cache/ast/842049adb6597ea40ebd75df9e3dc5e573a2ab0d489412bd8701efcfe9eaba3f.json
create mode 100644 web-admin/src/graphify-out/cache/ast/8438cc73800888b1dd3b6032b76e30120e72974a4a6996ec2bd42ebe5907d461.json
create mode 100644 web-admin/src/graphify-out/cache/ast/872b52af3ec5fbdb15071d21319e4802450ee4dee360bea5bf6200dbf1fc514c.json
create mode 100644 web-admin/src/graphify-out/cache/ast/88c4adcff24c38c6372fa7d6a74ecd98c68a577c62c40f2148511262f2556093.json
create mode 100644 web-admin/src/graphify-out/cache/ast/895d13b5992075056506e46919353f2bb446f51ea5df60f6ee9da3a550f4f922.json
create mode 100644 web-admin/src/graphify-out/cache/ast/89f4683d0ceff58680b06f387e70502d17bd3e05374961c3ce83cb5749542f08.json
create mode 100644 web-admin/src/graphify-out/cache/ast/8a4e3e53b3e856db2de755e0520bf66eae61a3b58e398fa1a41c2f77b3111819.json
create mode 100644 web-admin/src/graphify-out/cache/ast/8c4bb7916d3becf065bfa77f252f630cdb4c2ff594284fc85f38184e75277eb7.json
create mode 100644 web-admin/src/graphify-out/cache/ast/8d222360ed81c76ed5a9acda2e35bdd12706f24ddcdc6b263db0fc01a7aba922.json
create mode 100644 web-admin/src/graphify-out/cache/ast/8ee7f63a90878014de33b3057c48b5b22b9ad505388c4e1d392b74de1c37622a.json
create mode 100644 web-admin/src/graphify-out/cache/ast/8efd5e29ce8e3696043c776a2fe97d41bb8e5857803fa516bd1e20032609a0ca.json
create mode 100644 web-admin/src/graphify-out/cache/ast/90243694bc78d9feeb2e34bd4eaed40b552f20003daac6f8d90cd8eae225dd29.json
create mode 100644 web-admin/src/graphify-out/cache/ast/90b554f6115493a99d68b215d366a9b44eaed4990e2745eb765fce97a60362ce.json
create mode 100644 web-admin/src/graphify-out/cache/ast/90e8f3ca5f52bd7668a7e26dab2e2ff4298e55641ce04528b03fe6e91d72548b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/92140c960452249b67ad3f78ce26db6345a7db674a4dd89be27d2849ed8b7bb6.json
create mode 100644 web-admin/src/graphify-out/cache/ast/935ce0f89afe687d782a3f741738f067fc4ec5e2a849c21f995579618bd88185.json
create mode 100644 web-admin/src/graphify-out/cache/ast/94afc2195885d22f7303bd2f63e57617e39ea3bff74e2050da0d97147158de90.json
create mode 100644 web-admin/src/graphify-out/cache/ast/97f1f6806cc2f6a47bedc1c9bd37a9a695c0cfb2ccb1b388e0d1d772f694a94f.json
create mode 100644 web-admin/src/graphify-out/cache/ast/980bdf645e82b0d56cafd9219afb354cf952d311d17483dd3a115f3935dcb1ae.json
create mode 100644 web-admin/src/graphify-out/cache/ast/980cc17a4ee5c6cec3d5cc6abb4a249fcd3d9f31566615eb0eab3d98a90fc2e4.json
create mode 100644 web-admin/src/graphify-out/cache/ast/983ef5a21114ace34d3614aabe151d6a44c49625647debcdab64cfea515175a0.json
create mode 100644 web-admin/src/graphify-out/cache/ast/9ad1838306d2fde0342c1029bb2c8c0bc2bd3dadb1bad0a9f444f29e5724d789.json
create mode 100644 web-admin/src/graphify-out/cache/ast/9cd448e3405ab9611c3d443107e988734f15a5441e26d5c7d2c364d66dbbb84e.json
create mode 100644 web-admin/src/graphify-out/cache/ast/9d75e10016ab5453aa5310c67bd20735e2fecae488ea48cb7c595cdb9395079f.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a01edf1b73f4bb738f61652bbad0a1480fcbaf19e3d27f469e614bf2c0d1e415.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a046dcaa2f11f86c407dbb50d1ea592b86148052a64dd3f7382db8afd250ff8d.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a052b80e2102bda39a53c28bf5b131e4f9644941c5f63471068edee90b0f9d44.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a2f18c783782634d6b9c25ba9f3c4a51053993e50a58cb4c4527d1d7fb0316b0.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a30f9f58e09f760ed3d82dee5c2782307c5fe147c18dc3ab8962c34ac9381cea.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a3783d35578f1288d9bb52b4e1bbba45632645072fffa2d0ab51f66228f389ef.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a51f04d3d59abc9ea0585ef808c1d4d6e0c092e943e7e8b8d894ee0257651b2d.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a5a1195b93b2b29ea8a0da3a2360d465d95fc57e889d4cd0bcf28945c6c96624.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a65424fc13e92c5f1f051103f25f971d18b157c388e138802a237ccba0c83d8a.json
create mode 100644 web-admin/src/graphify-out/cache/ast/a9987a4bce0bea7a02a4e54b0ba3785b42ef0571483c095a48c64cf6e27894f1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/aa6eb0b1190d38f87cba116654f8e7281de3107bfe84d7f53be5dcb3eb03a7c1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/aeb464b430292d5d3ff5d6c489bbdb2381b352eda17c5cb989e72cc408301a74.json
create mode 100644 web-admin/src/graphify-out/cache/ast/aec7f2965058ffe762f60d44abff43b02cad88d2d2531f5741535405450f0c87.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b26fe846090e31c3f74aa0eda116f588fbfcec36b7490b49566769e8b199d8a1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b28fd455a05f8d3bdbfdf5c5da7531139586277a6f4894230138108409033488.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b600863a8752a11102e7522c46ccf305f59bbbb908ef1464d4cbe2b224c5865c.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b6867f22e801dbacb370ea9a285c9942f9313488763687fda47015dfcdf53d7b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b6e6fc456079af32778b61e4137916f7707d7e882e543fc5da46c1102bba5868.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b723165bab1d07fc4660618ee0d19c609bf4e56c706a81077a5006a9bb0fbef1.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b886225a0a24228007715715250bf809905a39c37160d8fde0b246c911425f50.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b8c85a75d016051a32dc794d7626ddb0d6f94fa6f606b00657a06339d735d0d0.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b9994f8103b40cb69db22b5a7496ccd76c4cf03c3860f3824b32bc6da2f4ad93.json
create mode 100644 web-admin/src/graphify-out/cache/ast/b9ba4614f34cc9e01b3598643be89be1258eb7ee0227fae3748dfa03f67d44f9.json
create mode 100644 web-admin/src/graphify-out/cache/ast/bbe5b736aab67d50803e8f9afc3d380fd47fdf7cd005022f8b75615ab5b738aa.json
create mode 100644 web-admin/src/graphify-out/cache/ast/be0b91f62562dfbf51d6a7313bf0d657292c0d3b3070b50d740f309b8a221c92.json
create mode 100644 web-admin/src/graphify-out/cache/ast/be757f92f36002effe307ce8af1a0c09532b545c2439e9ce52873a3635ba5962.json
create mode 100644 web-admin/src/graphify-out/cache/ast/be964f8bb4a07d42178f00f48e96894e81735ed52044a54ea08114ca8b9d3cf9.json
create mode 100644 web-admin/src/graphify-out/cache/ast/bfe0dd3f36f46d708b94f98a11091c4f395936b12aa9d7682cdd755f62bf54e2.json
create mode 100644 web-admin/src/graphify-out/cache/ast/c10fab4327768b7f6c183da7cb28f483a74c691032f4b06259cddcc621cf52c2.json
create mode 100644 web-admin/src/graphify-out/cache/ast/c3763c3f24c288c8c3d02dd4c94d477b899527e0e511ba9d24186fd3d50c4171.json
create mode 100644 web-admin/src/graphify-out/cache/ast/c61133fe4ba17f04769d58d5b5599538fec0aff7a6d8a1367a192cde33ee9884.json
create mode 100644 web-admin/src/graphify-out/cache/ast/c82af594e3d99bffaf460a829bf998cbbad79177d040d71a850d7a6fbc189321.json
create mode 100644 web-admin/src/graphify-out/cache/ast/c960a75c29d0f724665724278cf64db607408c6a65dca2e2350f96c1b68cc323.json
create mode 100644 web-admin/src/graphify-out/cache/ast/ce79e1a7d10f0aca1efafac71e4545f9d6044aea7c421448a90d14b95b8e0c4e.json
create mode 100644 web-admin/src/graphify-out/cache/ast/ced2a0e6a1e6f16d335759218abddac0d0eaeb3ec087941120cf6bf78ed9a223.json
create mode 100644 web-admin/src/graphify-out/cache/ast/cf0d5e6d09c6a7cc5edb9ab91884f6faec9b79fe83fdb5fb275102890ccba215.json
create mode 100644 web-admin/src/graphify-out/cache/ast/cfd6fcafec7aba525bd19315a85cdb83828810e4b37d82c7f7c8d10e5b00c34f.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d14797103f032cf1f4e3e3e7ee4d3eb980de35c47a091dc693a292e0a8d5dcea.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d14bf30b2dfab7d79a39918d416d6ce0345fc8cdd8ca6407fde21c23be995876.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d1e76f60b3e30aafc7a099d97e0fe43b2865a7b45c71bcff41503ac683fce42f.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d3a3f9d14b426842e0db04f7ed16cf31e0290d24dd57a391769fe3f03ea9d7b5.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d3ba752b706a0ba4b7df4151f53816972c48315825a76c6bd36ec976daa18720.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d5ba4421e4a0c9b06b6215e7bce232393f89c0e3aa611b752026a7cce34376bd.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d8c26cdd37868ea58d5794bdafd25bbb36c660bf27de64a01894acda3abdc6a3.json
create mode 100644 web-admin/src/graphify-out/cache/ast/d96bec6bdd740711f156545d7c95d268e4bebc8682e4379e652c5638820c2978.json
create mode 100644 web-admin/src/graphify-out/cache/ast/dcfcf3addb92b64fba102cccbe6e2234cd1f96b0f5b442ca633f27f1e3d061a6.json
create mode 100644 web-admin/src/graphify-out/cache/ast/ddcbb66e82082959694ca6ca9d788314e912d798c685adeea61f90400649efd0.json
create mode 100644 web-admin/src/graphify-out/cache/ast/e49bf171f204f2b1a0b4257d085a4e30f8de18b2a7a5259f23a3b27312a0909d.json
create mode 100644 web-admin/src/graphify-out/cache/ast/e4bde1d6fdebaabf383280a44a623b1261f59c62d1cc4608b20d55776001b984.json
create mode 100644 web-admin/src/graphify-out/cache/ast/e4e73aa82bdbb3d704d56c5733f4d9a4fa9a1c313c57d15057e9fa7c5795b491.json
create mode 100644 web-admin/src/graphify-out/cache/ast/e642acd45094341d21fefaa4e88ee5e7b00537036263744f32e06f95de913bf7.json
create mode 100644 web-admin/src/graphify-out/cache/ast/eaa8575e9cabcd1a3f835634846d0fbec7e4615610dfbedd865adbefc636b18b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/eb26ea9cd3bdb6893b9f9d4fb2fc4faed0a175818b2680eeb45cad1c1a4b2958.json
create mode 100644 web-admin/src/graphify-out/cache/ast/ecb37338497ed3cace36e6361de2edb42f896dea77d3d329a72929018e8af384.json
create mode 100644 web-admin/src/graphify-out/cache/ast/ed5bec448a4f386bf23857c51b9f8e4e2919d86114bf82a85b7ccd07817c1040.json
create mode 100644 web-admin/src/graphify-out/cache/ast/eda4bef5f519e6fa2b6481823a78a5f11c312ff53459bd3ed28b835d9eabc991.json
create mode 100644 web-admin/src/graphify-out/cache/ast/ef3ca7f45cd0df26f4202d7e8b7b58d24bb416e3d62088c46fb5ac9cf722bea8.json
create mode 100644 web-admin/src/graphify-out/cache/ast/effc448da99af9ae1794817f03d71266066d87653b720a0cddaa1bfd0a45dd2f.json
create mode 100644 web-admin/src/graphify-out/cache/ast/f065a0b90b4565369b96c4cec0bc75ca132878f001d030a9c4ced42d2c4d8922.json
create mode 100644 web-admin/src/graphify-out/cache/ast/f225060d168bde414d4203d945bcd2df3b0bb24ebde8cabfc90d8eb36170e6df.json
create mode 100644 web-admin/src/graphify-out/cache/ast/f279f871d98833782095b0f87387a1d101555a0deb7f95a9db9b7589865fa531.json
create mode 100644 web-admin/src/graphify-out/cache/ast/f32aa20e05de5af311817006fd187f0b00e182ebe4a2142da14ea5cdfb58d939.json
create mode 100644 web-admin/src/graphify-out/cache/ast/f6ae24c29d477d62cd27c4d26b72a9a960ec574c41d018c53b2150140945397b.json
create mode 100644 web-admin/src/graphify-out/cache/ast/fe2eb51b3521c2e3b6f4935fb2bf2bf3050df5d3ed11e002f9aa9b4d68d9c0dd.json
create mode 100644 web-admin/src/views/basic-info/cell-sample-type/SampleCodeTab.vue
create mode 100644 web-admin/src/views/basic-info/collection-region/CollectionProtocolNoTab.vue
create mode 100644 web-admin/src/views/cell-storage/material/MaterialStockLogTab.vue
create mode 100644 web-admin/src/views/cell-storage/payment-confirm/RefundConfirmTab.vue
create mode 100644 web-admin/src/views/cell-storage/protocol-audit/FullmoonTodoTab.vue
create mode 100644 web-admin/src/views/cell-storage/protocol-audit/PaymentInfoTab.vue
create mode 100644 web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue
create mode 100644 web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue
create mode 100644 web-admin/src/views/cell-storage/protocol-audit/StorageCertTab.vue
create mode 100644 web-admin/src/views/cell-storage/protocol-audit/StorageEntryInfoTab.vue
create mode 100644 web-admin/src/views/pilot-service/cell-product-type/CellProductPriceTab.vue
create mode 100644 web-admin/src/views/pilot-service/cell-product-type/CellProductSpecTab.vue
create mode 100644 web-admin/src/views/pilot-service/cell-product-type/ProjectPriceSystemTab.vue
create mode 100644 web-admin/src/views/pilot-service/channel/ChannelServiceOrderTab.vue
create mode 100644 web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue
create mode 100644 web-admin/src/views/pilot-service/clinical-payment-info/index.vue
create mode 100644 web-admin/src/views/pilot-service/clinical-refund-confirm/index.vue
create mode 100644 web-admin/src/views/pilot-service/pilot-application/CheckupManagementTab.vue
create mode 100644 web-admin/src/views/pilot-service/pilot-application/InfusionScheduleTab.vue
create mode 100644 web-admin/src/views/pilot-service/pilot-application/PilotApplicationTab.vue
create mode 100644 web-admin/src/views/production/consumable/ConsumablePackTab.vue
create mode 100644 web-admin/src/views/production/process-category/OutputTypeTab.vue
create mode 100644 web-admin/src/views/production/process/ProcessRouteTab.vue
create mode 100644 web-admin/src/views/production/production-work-order/ProductionTaskTab.vue
create mode 100644 web-admin/src/views/quality-inspection/lab-test-item/ThirdPartyCompanyTab.vue
create mode 100644 web-admin/src/views/quality-inspection/qc-material/QcMaterialUsageTab.vue
create mode 100644 web-admin/src/views/quality-inspection/qc-work-order/QcTaskTab.vue
diff --git a/backend/app.pid b/backend/app.pid
index 2c710dc58..901b3e35f 100644
--- a/backend/app.pid
+++ b/backend/app.pid
@@ -1 +1 @@
-60905
+73984
diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py
index 1687079ea..2a4b17a89 100644
--- a/backend/app/api/__init__.py
+++ b/backend/app/api/__init__.py
@@ -24,6 +24,7 @@ from app.api.pilot_delivery import router as pilot_delivery_router
from app.api.pilot_infusion_confirm import router as pilot_infusion_confirm_router
from app.api.pilot_infusion_schedule_mgmt import router as pilot_infusion_schedule_mgmt_router
from app.api.outbound_release_review import router as outbound_release_review_router
+from app.api.clinical_payment import router as clinical_payment_router
__all__ = [
"public_router",
@@ -51,4 +52,5 @@ __all__ = [
"pilot_infusion_confirm_router",
"pilot_infusion_schedule_mgmt_router",
"outbound_release_review_router",
+ "clinical_payment_router",
]
diff --git a/backend/app/api/clinical_payment.py b/backend/app/api/clinical_payment.py
new file mode 100644
index 000000000..935c4adc3
--- /dev/null
+++ b/backend/app/api/clinical_payment.py
@@ -0,0 +1,337 @@
+from typing import 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 clinical_payment
+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 PaymentIdBody(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ id: str
+
+
+class BatchPaymentIdsBody(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ payment_ids: List[str] = Field(..., alias="paymentIds")
+
+
+class ConfirmReceiptBody(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ id: str
+ finance_confirm_attachments: Optional[str] = Field(None, alias="financeConfirmAttachments")
+ finance_remark: Optional[str] = Field(None, alias="financeRemark")
+
+
+class SubmitRefundBody(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ id: str
+ refund_amount: float = Field(..., alias="refundAmount")
+ refund_remark: Optional[str] = Field(None, alias="refundRemark")
+ refund_attachments: Optional[str] = Field(None, alias="refundAttachments")
+
+
+class BatchSubmitRefundBody(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ payment_ids: List[str] = Field(..., alias="paymentIds")
+ refund_amount: float = Field(..., alias="refundAmount")
+ refund_remark: Optional[str] = Field(None, alias="refundRemark")
+ refund_attachments: Optional[str] = Field(None, alias="refundAttachments")
+
+
+class ConfirmRefundBody(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ id: str
+ refund_finance_confirm_attachments: Optional[str] = Field(
+ None, alias="refundFinanceConfirmAttachments"
+ )
+ refund_finance_remark: Optional[str] = Field(None, alias="refundFinanceRemark")
+
+
+@router.get("/clinical-payments")
+async def api_list_payments(
+ page: int = Query(1, ge=1),
+ pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
+ payStatus: Optional[str] = Query(None, alias="payStatus"),
+ keyword: Optional[str] = Query(None, alias="keyword"),
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ rows, total = clinical_payment.list_payments(
+ db,
+ page=page,
+ page_size=pageSize,
+ pay_status=payStatus,
+ keyword=keyword,
+ tenant_no=tenant_no,
+ user_info=user,
+ )
+ return success_response({"rows": rows, "total": total})
+ except Exception as e:
+ return error_response(500, str(e))
+
+
+@router.get("/clinical-payments/{payment_id}")
+async def api_get_payment_detail(
+ payment_id: str,
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ result = clinical_payment.get_payment_detail(
+ db, payment_id=payment_id, tenant_no=tenant_no
+ )
+ if result is None:
+ return error_response(404, "缴费记录不存在")
+ return success_response(result)
+ except ValueError as e:
+ return error_response(404, str(e))
+
+
+@router.post("/clinical-payments/{payment_id}/submit-receipt")
+async def api_submit_receipt(
+ payment_id: str,
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ clinical_payment.submit_receipt(
+ db,
+ payment_id=payment_id,
+ tenant_no=tenant_no,
+ user_id=user.get("user_id"),
+ )
+ return success_response(None, message="提交缴费确认成功")
+ except ValueError as e:
+ return error_response(400, str(e))
+
+
+@router.post("/clinical-payments/submit-receipt-batch")
+async def api_batch_submit_receipt(
+ body: BatchPaymentIdsBody,
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ count = clinical_payment.batch_submit_receipt(
+ db,
+ payment_ids=body.payment_ids,
+ tenant_no=tenant_no,
+ user_id=user.get("user_id"),
+ )
+ return success_response({"count": count}, message=f"批量提交成功,共{count}条")
+ except ValueError as e:
+ return error_response(400, str(e))
+
+
+@router.post("/clinical-payments/{payment_id}/confirm-receipt")
+async def api_confirm_receipt(
+ payment_id: str,
+ body: ConfirmReceiptBody,
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ clinical_payment.confirm_receipt(
+ db,
+ payment_id=payment_id,
+ tenant_no=tenant_no,
+ user_id=user.get("user_id"),
+ finance_confirm_attachments=body.finance_confirm_attachments,
+ finance_remark=body.finance_remark,
+ )
+ return success_response(None, message="确认收款成功")
+ except ValueError as e:
+ return error_response(400, str(e))
+
+
+@router.post("/clinical-payments/{payment_id}/submit-refund")
+async def api_submit_refund(
+ payment_id: str,
+ body: SubmitRefundBody,
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ clinical_payment.submit_refund(
+ db,
+ payment_id=payment_id,
+ tenant_no=tenant_no,
+ user_id=user.get("user_id"),
+ refund_amount=body.refund_amount,
+ refund_remark=body.refund_remark,
+ refund_attachments=body.refund_attachments,
+ )
+ return success_response(None, message="提交退费申请成功")
+ except ValueError as e:
+ return error_response(400, str(e))
+
+
+@router.post("/clinical-payments/submit-refund-batch")
+async def api_batch_submit_refund(
+ body: BatchSubmitRefundBody,
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ count = clinical_payment.batch_submit_refund(
+ db,
+ payment_ids=body.payment_ids,
+ tenant_no=tenant_no,
+ user_id=user.get("user_id"),
+ refund_amount=body.refund_amount,
+ refund_remark=body.refund_remark,
+ refund_attachments=body.refund_attachments,
+ )
+ return success_response({"count": count}, message=f"批量提交退费成功,共{count}条")
+ except ValueError as e:
+ return error_response(400, str(e))
+
+
+@router.post("/clinical-payments/{payment_id}/confirm-refund")
+async def api_confirm_refund(
+ payment_id: str,
+ body: ConfirmRefundBody,
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ clinical_payment.confirm_refund(
+ db,
+ payment_id=payment_id,
+ tenant_no=tenant_no,
+ user_id=user.get("user_id"),
+ refund_finance_confirm_attachments=body.refund_finance_confirm_attachments,
+ refund_finance_remark=body.refund_finance_remark,
+ )
+ return success_response(None, message="确认退费成功")
+ except ValueError as e:
+ return error_response(400, str(e))
+
+
+@router.get("/clinical-payments/receipt-pending")
+async def api_receipt_pending(
+ page: int = Query(1, ge=1),
+ pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
+ keyword: Optional[str] = Query(None, alias="keyword"),
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ rows, total = clinical_payment.list_payments(
+ db,
+ page=page,
+ page_size=pageSize,
+ pay_status="jfqrz",
+ keyword=keyword,
+ tenant_no=tenant_no,
+ user_info=user,
+ )
+ return success_response({"rows": rows, "total": total})
+ except Exception as e:
+ return error_response(500, str(e))
+
+
+@router.get("/clinical-payments/receipt-confirmed")
+async def api_receipt_confirmed(
+ page: int = Query(1, ge=1),
+ pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
+ keyword: Optional[str] = Query(None, alias="keyword"),
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ rows, total = clinical_payment.list_payments(
+ db,
+ page=page,
+ page_size=pageSize,
+ pay_status="yjf",
+ keyword=keyword,
+ tenant_no=tenant_no,
+ user_info=user,
+ )
+ return success_response({"rows": rows, "total": total})
+ except Exception as e:
+ return error_response(500, str(e))
+
+
+@router.get("/clinical-payments/refund-pending")
+async def api_refund_pending(
+ page: int = Query(1, ge=1),
+ pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
+ keyword: Optional[str] = Query(None, alias="keyword"),
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ rows, total = clinical_payment.list_payments(
+ db,
+ page=page,
+ page_size=pageSize,
+ pay_status="tfqrz",
+ keyword=keyword,
+ tenant_no=tenant_no,
+ user_info=user,
+ )
+ return success_response({"rows": rows, "total": total})
+ except Exception as e:
+ return error_response(500, str(e))
+
+
+@router.get("/clinical-payments/refund-confirmed")
+async def api_refund_confirmed(
+ page: int = Query(1, ge=1),
+ pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
+ keyword: Optional[str] = Query(None, alias="keyword"),
+ user: dict = Depends(require_admin_or_super_admin),
+ db: Session = Depends(get_db),
+):
+ tenant_no = _get_tenant_no(db, user)
+ try:
+ rows, total = clinical_payment.list_payments(
+ db,
+ page=page,
+ page_size=pageSize,
+ pay_status="ytf",
+ keyword=keyword,
+ tenant_no=tenant_no,
+ user_info=user,
+ )
+ return success_response({"rows": rows, "total": total})
+ except Exception as e:
+ return error_response(500, str(e))
diff --git a/backend/app/api/menu.py b/backend/app/api/menu.py
index 27b88c242..6d6dc5e57 100644
--- a/backend/app/api/menu.py
+++ b/backend/app/api/menu.py
@@ -1,4 +1,4 @@
-"""运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)"""
+"""运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,13 个一级菜单)"""
from fastapi import APIRouter, Depends
from app.core.menu_guard import require_admin_or_super_admin
@@ -6,11 +6,559 @@ from app.core.response import success_response
router = APIRouter(prefix="/web/menu", tags=["菜单管理"])
-# 与 web-admin/src/router/modules/sqxp.ts 一致:系统管理、基础信息管理 等
+# 与 web-admin/src/router/modules/sqxp.ts 一致:13 个一级菜单
STATIC_MENU_TREE = [
+ # ────────────────────────────────────────────
+ # 1. 细胞存储
+ # ────────────────────────────────────────────
{
- "id": "100",
- "name": "System",
+ "id": "CEL00000000000000000000000000001",
+ "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, "menuCode": "biz:cellStorageProtocol:list"},
+ },
+ {
+ "id": "50000000000000000000000000000003",
+ "name": "CellStorageProtocolAudit",
+ "path": "protocol-audit",
+ "component": "/cell-storage/protocol-audit/index",
+ "meta": {"title": "采集协议单管理", "keepAlive": True, "menuCode": "biz:cellStorageProtocolAudit:list"},
+ },
+ {
+ "id": "50000000000000000000000000000004",
+ "name": "CellStoragePaymentConfirm",
+ "path": "payment-confirm",
+ "component": "/cell-storage/payment-confirm/index",
+ "meta": {"title": "缴退费财务确认", "keepAlive": True, "menuCode": "biz:cellStoragePaymentConfirm:list"},
+ },
+ {
+ "id": "70000000000000000000000000000011",
+ "name": "CellStorageStorageReleaseReview",
+ "path": "storage-release-review",
+ "component": "/quality-inspection/storage-release-review/index",
+ "meta": {"title": "入库放行审核", "keepAlive": True, "menuCode": "biz:storageReleaseReview:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 2. 科研捐赠
+ # ────────────────────────────────────────────
+ {
+ "id": "RSC00000000000000000000000000001",
+ "name": "ResearchDonation",
+ "path": "/research-donation",
+ "component": "/index/index",
+ "meta": {
+ "title": "科研捐赠",
+ "icon": "ri:hand-heart-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "60000000000000000000000000000002",
+ "name": "ResearchDonationList",
+ "path": "list",
+ "component": "/research-donation/list/index",
+ "meta": {"title": "捐赠协议单录入", "keepAlive": True, "menuCode": "biz:researchDonation:list"},
+ },
+ {
+ "id": "60000000000000000000000000000003",
+ "name": "ResearchDonationAudit",
+ "path": "audit",
+ "component": "/research-donation/audit/index",
+ "meta": {"title": "捐赠协议单管理", "keepAlive": True, "menuCode": "biz:researchDonationAudit:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 3. 中试服务
+ # ────────────────────────────────────────────
+ {
+ "id": "PIL00000000000000000000000000001",
+ "name": "PilotService",
+ "path": "/pilot-service",
+ "component": "/index/index",
+ "meta": {
+ "title": "中试服务",
+ "icon": "ri:flask-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "80000000000000000000000000000010",
+ "name": "PilotApplication",
+ "path": "pilot-application",
+ "component": "/pilot-service/pilot-application/index",
+ "meta": {"title": "中试服务申请单", "keepAlive": True, "menuCode": "pilot-service:pilot-application:list"},
+ },
+ {
+ "id": "PIL00000000000000000000000000004",
+ "name": "PilotOutboundRelease",
+ "path": "outbound-release-review",
+ "component": "/pilot-service/outbound-release-review/index",
+ "meta": {"title": "出库放行审核", "keepAlive": True, "menuCode": "pilot-service:outbound-release-review:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 4. 临床应用
+ # ────────────────────────────────────────────
+ {
+ "id": "CLI00000000000000000000000000001",
+ "name": "ClinicalApplication",
+ "path": "/clinical-application",
+ "component": "/index/index",
+ "meta": {
+ "title": "临床应用",
+ "icon": "ri:heart-pulse-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "CLI00000000000000000000000000002",
+ "name": "ClinicalApplicationList",
+ "path": "clinical-application",
+ "component": "/pilot-service/pilot-application/index",
+ "meta": {"title": "临床申请单", "keepAlive": True, "menuCode": "clinical-application:application:list"},
+ },
+ {
+ "id": "CLI00000000000000000000000000003",
+ "name": "ClinicalCheckupManagement",
+ "path": "clinical-checkup-management",
+ "component": "/pilot-service/checkup-management/index",
+ "meta": {"title": "体检管理", "keepAlive": True, "menuCode": "clinical-application:checkup-management:list"},
+ },
+ {
+ "id": "CLI00000000000000000000000000004",
+ "name": "ClinicalInfusionSchedule",
+ "path": "clinical-infusion-schedule-management",
+ "component": "/pilot-service/infusion-schedule-management/index",
+ "meta": {"title": "输注计划管理", "keepAlive": True, "menuCode": "clinical-application:infusion-schedule-management:list"},
+ },
+ {
+ "id": "CLI00000000000000000000000000005",
+ "name": "ClinicalPaymentInfo",
+ "path": "clinical-payment-info",
+ "component": "/pilot-service/clinical-payment-info/index",
+ "meta": {"title": "缴费信息", "keepAlive": True, "menuCode": "clinical-application:payment-info:list"},
+ },
+ {
+ "id": "CLI00000000000000000000000000006",
+ "name": "ClinicalPaymentConfirm",
+ "path": "clinical-payment-confirm",
+ "component": "/pilot-service/clinical-payment-confirm/index",
+ "meta": {"title": "缴费确认(财务)", "keepAlive": True, "menuCode": "clinical-application:payment-confirm:list"},
+ },
+ {
+ "id": "CLI00000000000000000000000000007",
+ "name": "ClinicalRefundConfirm",
+ "path": "clinical-refund-confirm",
+ "component": "/pilot-service/clinical-refund-confirm/index",
+ "meta": {"title": "退费确认(财务)", "keepAlive": True, "menuCode": "clinical-application:refund-confirm:list"},
+ },
+ {
+ "id": "CLI00000000000000000000000000008",
+ "name": "ClinicalOutboundRelease",
+ "path": "clinical-outbound-release-review",
+ "component": "/pilot-service/outbound-release-review/index",
+ "meta": {"title": "出库放行审核", "keepAlive": True, "menuCode": "clinical-application:outbound-release-review:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 5. 质检管理
+ # ────────────────────────────────────────────
+ {
+ "id": "QCM00000000000000000000000000001",
+ "name": "QualityManage",
+ "path": "/quality-manage",
+ "component": "/index/index",
+ "meta": {
+ "title": "质检管理",
+ "icon": "ri:shield-check-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "70000000000000000000000000000002",
+ "name": "QualityInspectionSampleReception",
+ "path": "sample-reception",
+ "component": "/quality-inspection/sample-reception/index",
+ "meta": {"title": "样本接收", "keepAlive": True, "menuCode": "biz:sampleReception:list"},
+ },
+ {
+ "id": "70000000000000000000000000000007",
+ "name": "QualityInspectionQcWorkOrder",
+ "path": "qc-work-order",
+ "component": "/quality-inspection/qc-work-order/index",
+ "meta": {"title": "质检管理", "keepAlive": True, "menuCode": "biz:qcWorkOrder:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 6. 生产管理
+ # ────────────────────────────────────────────
+ {
+ "id": "PRM00000000000000000000000000001",
+ "name": "ProductionManage",
+ "path": "/production",
+ "component": "/index/index",
+ "meta": {
+ "title": "生产管理",
+ "icon": "grid",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "61000000000000000000000000000003",
+ "name": "ProductionStorageFacility",
+ "path": "storage-facility",
+ "component": "/operations/storage-facility/index",
+ "meta": {"title": "库位管理", "keepAlive": True, "menuCode": "operations:storage-facility:list"},
+ },
+ {
+ "id": "90000000000000000000000000000013",
+ "name": "CellProduct",
+ "path": "cell-product",
+ "component": "/production/cell-product/index",
+ "meta": {"title": "细胞产品库", "keepAlive": True, "menuCode": "production:cell-product:list"},
+ },
+ {
+ "id": "90000000000000000000000000000011",
+ "name": "ProductionWorkOrder",
+ "path": "production-work-order",
+ "component": "/production/production-work-order/index",
+ "meta": {"title": "生产管理", "keepAlive": True, "menuCode": "production:work-order:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 7. 运营支撑
+ # ────────────────────────────────────────────
+ {
+ "id": "OPS00000000000000000000000000001",
+ "name": "Operations",
+ "path": "/operations",
+ "component": "/index/index",
+ "meta": {
+ "title": "运营支撑",
+ "icon": "operation",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "61000000000000000000000000000002",
+ "name": "CleanRoom",
+ "path": "clean-room",
+ "component": "/operations/clean-room/index",
+ "meta": {"title": "洁净室管理", "keepAlive": True, "menuCode": "operations:clean-room:list"},
+ },
+ {
+ "id": "61000000000000000000000000000004",
+ "name": "ExhibitionAppointment",
+ "path": "exhibition-appointment",
+ "component": "/operations/exhibition-appointment/index",
+ "meta": {"title": "展馆预约", "keepAlive": True, "menuCode": "operations:exhibition-appointment:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 8. 市场基础信息
+ # ────────────────────────────────────────────
+ {
+ "id": "MKT00000000000000000000000000001",
+ "name": "MarketInfo",
+ "path": "/market-info",
+ "component": "/index/index",
+ "meta": {
+ "title": "市场基础信息",
+ "icon": "ri:price-tag-3-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "122",
+ "name": "BizCellSampleType",
+ "path": "cell-sample-type",
+ "component": "/basic-info/cell-sample-type/index",
+ "meta": {"title": "细胞样本基础信息维护", "keepAlive": True, "menuCode": "biz:cellSampleType:list"},
+ },
+ {
+ "id": "123",
+ "name": "BizCollectionRegion",
+ "path": "collection-region",
+ "component": "/basic-info/collection-region/index",
+ "meta": {"title": "细胞采集基础信息维护", "keepAlive": True, "menuCode": "biz:collectionRegion:list"},
+ },
+ {
+ "id": "80000000000000000000000000000002",
+ "name": "CellProductType",
+ "path": "cell-product-type",
+ "component": "/pilot-service/cell-product-type/index",
+ "meta": {"title": "细胞产品基础信息维护", "keepAlive": True, "menuCode": "pilot-service:cell-product-type:list"},
+ },
+ {
+ "id": "40000000000000000000000000000007",
+ "name": "BasicInfoInsuranceCompany",
+ "path": "insurance-company",
+ "component": "/basic-info/insurance-company/index",
+ "meta": {"title": "保险公司及险种价格", "keepAlive": True, "menuCode": "basic-info:insurance-company:list"},
+ },
+ {
+ "id": "80000000000000000000000000000007",
+ "name": "CheckupInstitution",
+ "path": "checkup-institution",
+ "component": "/pilot-service/checkup-institution/index",
+ "meta": {"title": "体检机构及项目价格", "keepAlive": True, "menuCode": "pilot-service:checkup-institution:list"},
+ },
+ {
+ "id": "50000000000000000000000000000009",
+ "name": "MarketMaterial",
+ "path": "material",
+ "component": "/cell-storage/material/index",
+ "meta": {"title": "市场物料库", "keepAlive": True, "menuCode": "biz:material:list"},
+ },
+ {
+ "id": "121",
+ "name": "BizCustomer",
+ "path": "customer",
+ "component": "/basic-info/customer/index",
+ "meta": {"title": "客户管理", "keepAlive": True, "menuCode": "biz:customer:list"},
+ },
+ {
+ "id": "80000000000000000000000000000006",
+ "name": "Channel",
+ "path": "channel",
+ "component": "/pilot-service/channel/index",
+ "meta": {"title": "渠道信息管理", "keepAlive": True, "menuCode": "pilot-service:channel:list"},
+ },
+ {
+ "id": "80000000000000000000000000000009",
+ "name": "ChannelServiceOrderFinance",
+ "path": "channel-service-order-finance",
+ "component": "/pilot-service/channel-service-order-finance/index",
+ "meta": {"title": "渠道服务订单(财务确认)", "keepAlive": True, "menuCode": "pilot-service:channel-service-order-finance:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 9. 质检基础信息
+ # ────────────────────────────────────────────
+ {
+ "id": "QCB00000000000000000000000000001",
+ "name": "QualityBase",
+ "path": "/quality-base",
+ "component": "/index/index",
+ "meta": {
+ "title": "质检基础信息",
+ "icon": "ri:clipboard-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "70000000000000000000000000000003",
+ "name": "QualityInspectionSampleReceptionCheckItem",
+ "path": "sample-reception-check-item",
+ "component": "/quality-inspection/sample-reception-check-item/index",
+ "meta": {"title": "接收检查项配置", "keepAlive": True, "menuCode": "biz:sampleReceptionCheckItem:list"},
+ },
+ {
+ "id": "70000000000000000000000000000006",
+ "name": "QualityInspectionLabTestItem",
+ "path": "lab-test-item",
+ "component": "/quality-inspection/lab-test-item/index",
+ "meta": {"title": "质检项目管理", "keepAlive": True, "menuCode": "biz:qcLabTestItem:list"},
+ },
+ {
+ "id": "70000000000000000000000000000009",
+ "name": "QualityInspectionQcMaterial",
+ "path": "qc-material",
+ "component": "/quality-inspection/qc-material/index",
+ "meta": {"title": "质检物料库", "keepAlive": True, "menuCode": "biz:qcMaterial:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 10. 生产基础信息
+ # ────────────────────────────────────────────
+ {
+ "id": "PRB00000000000000000000000000001",
+ "name": "ProductionBase",
+ "path": "/production-base",
+ "component": "/index/index",
+ "meta": {
+ "title": "生产基础信息",
+ "icon": "ri:tools-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "90000000000000000000000000000002",
+ "name": "ProductionConsumable",
+ "path": "consumable",
+ "component": "/production/consumable/index",
+ "meta": {"title": "耗材管理", "keepAlive": True, "menuCode": "production:consumable:list"},
+ },
+ {
+ "id": "90000000000000000000000000000003",
+ "name": "ProductionReagent",
+ "path": "reagent",
+ "component": "/production/reagent/index",
+ "meta": {"title": "试剂管理", "keepAlive": True, "menuCode": "production:reagent:list"},
+ },
+ {
+ "id": "90000000000000000000000000000004",
+ "name": "ProductionSop",
+ "path": "sop",
+ "component": "/production/sop/index",
+ "meta": {"title": "SOP文档", "keepAlive": True, "menuCode": "production:sop-document:list"},
+ },
+ {
+ "id": "90000000000000000000000000000005",
+ "name": "ProductionProcessCategory",
+ "path": "process-category",
+ "component": "/production/process-category/index",
+ "meta": {"title": "工艺基础信息维护", "keepAlive": True, "menuCode": "production:process-category:list"},
+ },
+ {
+ "id": "90000000000000000000000000000008",
+ "name": "ProductionProcess",
+ "path": "process",
+ "component": "/production/process/index",
+ "meta": {"title": "工艺库管理", "keepAlive": True, "menuCode": "production:process:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 11. 系统基础信息
+ # ────────────────────────────────────────────
+ {
+ "id": "SYSB000000000000000000000000000001",
+ "name": "SystemBase",
+ "path": "/system-base",
+ "component": "/index/index",
+ "meta": {
+ "title": "系统基础信息",
+ "icon": "ri:archive-line",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "107",
+ "name": "Site",
+ "path": "site",
+ "component": "/system/site/index",
+ "meta": {"title": "站点管理", "keepAlive": True, "menuCode": "system:site:list"},
+ },
+ {
+ "id": "109",
+ "name": "Unit",
+ "path": "unit",
+ "component": "/system/unit/index",
+ "meta": {"title": "物品单位管理", "keepAlive": True, "menuCode": "system:unit:list"},
+ },
+ {
+ "id": "110",
+ "name": "Express",
+ "path": "express",
+ "component": "/system/express/index",
+ "meta": {"title": "快递公司", "keepAlive": True, "menuCode": "system:express:list"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 12. 工作流管理
+ # ────────────────────────────────────────────
+ {
+ "id": "WKF00000000000000000000000000001",
+ "name": "Workflow",
+ "path": "/workflow",
+ "component": "/index/index",
+ "meta": {
+ "title": "工作流管理",
+ "icon": "workflow",
+ "keepAlive": False,
+ "roles": ["admin"],
+ },
+ "children": [
+ {
+ "id": "10000000000000000000000000000wf01",
+ "name": "WorkflowProcess",
+ "path": "process",
+ "component": "/system/workflow/process/index",
+ "meta": {"title": "流程配置", "keepAlive": True, "menuCode": "workflow:process"},
+ },
+ {
+ "id": "10000000000000000000000000000wf02",
+ "name": "WorkflowPending",
+ "path": "pending",
+ "component": "/system/workflow/task-pending/index",
+ "meta": {"title": "待办中心", "keepAlive": True, "menuCode": "workflow:pending"},
+ },
+ {
+ "id": "10000000000000000000000000000wf03",
+ "name": "WorkflowCompleted",
+ "path": "completed",
+ "component": "/system/workflow/task-completed/index",
+ "meta": {"title": "已办中心", "keepAlive": True, "menuCode": "workflow:completed"},
+ },
+ {
+ "id": "10000000000000000000000000000wf05",
+ "name": "WorkflowMyApplications",
+ "path": "my-applications",
+ "component": "/system/workflow/my-applications/index",
+ "meta": {"title": "我的申请", "keepAlive": True, "menuCode": "workflow:myApplications"},
+ },
+ {
+ "id": "10000000000000000000000000000wf04",
+ "name": "LeaveRequest",
+ "path": "leave",
+ "component": "/system/leave/index",
+ "meta": {"title": "请假申请", "keepAlive": True, "menuCode": "workflow:leave"},
+ },
+ ],
+ },
+
+ # ────────────────────────────────────────────
+ # 13. 系统管理
+ # ────────────────────────────────────────────
+ {
+ "id": "SYSM000000000000000000000000000001",
+ "name": "SystemManage",
"path": "/system",
"component": "/index/index",
"meta": {
@@ -34,6 +582,13 @@ STATIC_MENU_TREE = [
"component": "/system/dict/index",
"meta": {"title": "字典管理", "keepAlive": True, "menuCode": "system:dict:list"},
},
+ {
+ "id": "106",
+ "name": "Menu",
+ "path": "menu",
+ "component": "/system/menu/index",
+ "meta": {"title": "菜单管理", "keepAlive": True, "menuCode": "system:menu:list"},
+ },
{
"id": "103",
"name": "Dept",
@@ -41,20 +596,6 @@ STATIC_MENU_TREE = [
"component": "/system/dept/index",
"meta": {"title": "组织管理", "keepAlive": True, "menuCode": "system:dept:list"},
},
- {
- "id": "104",
- "name": "User",
- "path": "user",
- "component": "/system/user/index",
- "meta": {"title": "用户管理", "keepAlive": True, "menuCode": "system:user:list"},
- },
- {
- "id": "10000000000000000000000000000008",
- "name": "UserDetail",
- "path": "user/detail",
- "component": "/system/user/detail",
- "meta": {"title": "用户详情", "isHide": True, "keepAlive": True},
- },
{
"id": "105",
"name": "Role",
@@ -63,18 +604,11 @@ STATIC_MENU_TREE = [
"meta": {"title": "角色管理", "keepAlive": True, "menuCode": "system:role:list"},
},
{
- "id": "106",
- "name": "Menu",
- "path": "menu",
- "component": "/system/menu/index",
- "meta": {"title": "菜单管理", "keepAlive": True, "menuCode": "system:menu:list"},
- },
- {
- "id": "107",
- "name": "Site",
- "path": "site",
- "component": "/system/site/index",
- "meta": {"title": "站点管理", "keepAlive": True, "menuCode": "system:site:list"},
+ "id": "104",
+ "name": "User",
+ "path": "user",
+ "component": "/system/user/index",
+ "meta": {"title": "用户管理", "keepAlive": True, "menuCode": "system:user:list"},
},
{
"id": "108",
@@ -84,175 +618,15 @@ STATIC_MENU_TREE = [
"meta": {"title": "职位管理", "keepAlive": True, "menuCode": "system:post:list"},
},
{
- "id": "109",
- "name": "Unit",
- "path": "unit",
- "component": "/system/unit/index",
- "meta": {"title": "物品单位管理", "keepAlive": True, "menuCode": "system:unit:list"},
- },
- {
- "id": "110",
- "name": "Express",
- "path": "express",
- "component": "/system/express/index",
- "meta": {"title": "快递公司", "keepAlive": True, "menuCode": "system:express:list"},
+ "id": "10000000000000000000000000000008",
+ "name": "UserDetail",
+ "path": "user/detail",
+ "component": "/system/user/detail",
+ "meta": {"title": "用户详情", "isHide": True, "keepAlive": True},
},
],
- },
- {
- "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, "menuCode": "biz:customer:list"},
- },
- {
- "id": "40000000000000000000000000000003",
- "name": "BizCellSampleType",
- "path": "cell-sample-type",
- "component": "/basic-info/cell-sample-type/index",
- "meta": {"title": "细胞样本类型管理", "keepAlive": True, "menuCode": "biz:cellSampleType:list"},
- },
- {
- "id": "40000000000000000000000000000004",
- "name": "BizCollectionRegion",
- "path": "collection-region",
- "component": "/basic-info/collection-region/index",
- "meta": {"title": "采集地区及医院管理", "keepAlive": True, "menuCode": "biz:collectionRegion:list"},
- },
- {
- "id": "40000000000000000000000000000005",
- "name": "BizCollectionProtocolNo",
- "path": "collection-protocol-no",
- "component": "/basic-info/collection-protocol-no/index",
- "meta": {"title": "采集协议号管理", "keepAlive": True, "menuCode": "biz:collectionProtocolNo:list"},
- },
- {
- "id": "40000000000000000000000000000006",
- "name": "BizSampleCode",
- "path": "sample-code",
- "component": "/basic-info/sample-code/index",
- "meta": {"title": "样本编码管理", "keepAlive": True, "menuCode": "biz:sampleCode:list"},
- },
- ],
- },
- {
- "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": "50000000000000000000000000000007",
- "name": "CellStoragePaymentInfo",
- "path": "payment-info",
- "component": "/cell-storage/payment-info/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": "70000000000000000000000000000001",
- "name": "QualityInspection",
- "path": "/quality-inspection",
- "component": "/index/index",
- "meta": {
- "title": "质量检测",
- "icon": "ri:shield-check-line",
- "keepAlive": False,
- "roles": ["admin"],
- },
- "children": [
- {
- "id": "70000000000000000000000000000002",
- "name": "QualityInspectionSampleReception",
- "path": "sample-reception",
- "component": "/quality-inspection/sample-reception/index",
- "meta": {"title": "样本接收", "keepAlive": True},
- },
- {
- "id": "70000000000000000000000000000003",
- "name": "QualityInspectionSampleReceptionCheckItem",
- "path": "sample-reception-check-item",
- "component": "/quality-inspection/sample-reception-check-item/index",
- "meta": {"title": "接收检查项配置", "keepAlive": True},
- },
- {
- "id": "70000000000000000000000000000004",
- "name": "QualityInspectionThirdPartyCompany",
- "path": "third-party-company",
- "component": "/quality-inspection/third-party-company/index",
- "meta": {"title": "第三方检测公司及项目", "keepAlive": True, "menuCode": "biz:qcThirdPartyCompany:list"},
- },
- {
- "id": "70000000000000000000000000000006",
- "name": "QualityInspectionLabTestItem",
- "path": "lab-test-item",
- "component": "/quality-inspection/lab-test-item/index",
- "meta": {"title": "实验室自检项目", "keepAlive": True, "menuCode": "biz:qcLabTestItem:list"},
- },
- {
- "id": "70000000000000000000000000000007",
- "name": "QualityInspectionQcWorkOrder",
- "path": "qc-work-order",
- "component": "/quality-inspection/qc-work-order/index",
- "meta": {"title": "入库质检工单", "keepAlive": True, "menuCode": "biz:qcWorkOrder:list"},
- },
- {
- "id": "70000000000000000000000000000008",
- "name": "QualityInspectionQcTask",
- "path": "qc-task",
- "component": "/quality-inspection/qc-task/index",
- "meta": {"title": "质检任务", "keepAlive": True, "menuCode": "biz:qcTask:list"},
- },
- ],
- },
+ }
+
]
diff --git a/backend/app/api/pilot_application.py b/backend/app/api/pilot_application.py
index c42a4269d..fa5cc1530 100644
--- a/backend/app/api/pilot_application.py
+++ b/backend/app/api/pilot_application.py
@@ -44,8 +44,9 @@ class PlanIdBody(BaseModel):
class ApplicationCreateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
- channel_service_order_id: str = Field(..., alias="channelServiceOrderId")
+ channel_service_order_id: Optional[str] = Field(None, alias="channelServiceOrderId")
remark: Optional[str] = None
+ biz_type: Optional[str] = Field(None, alias="bizType")
class ApplicationUpdateBody(BaseModel):
@@ -111,6 +112,7 @@ class ScheduleCreateBody(BaseModel):
plan_id: str = Field(..., alias="infusionPlanId")
infusion_time: Optional[str] = Field(None, alias="infusionTime")
remark: Optional[str] = None
+ amount: Optional[float] = None
# --- 复合保存 Schema(参考采集协议单一次性保存模式)---
@@ -202,6 +204,7 @@ async def api_list_applications(
channelId: Optional[str] = Query(None, alias="channelId"),
applicationStatus: Optional[str] = Query(None, alias="applicationStatus"),
channelServiceOrderId: Optional[str] = Query(None, alias="channelServiceOrderId"),
+ bizType: Optional[str] = Query(None, alias="bizType"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
@@ -215,6 +218,7 @@ async def api_list_applications(
application_status=applicationStatus,
channel_service_order_id=channelServiceOrderId,
keyword=keyword,
+ biz_type=bizType,
)
return success_response(result)
@@ -241,8 +245,9 @@ async def api_create_application(
try:
tenant_no = _get_tenant_no(db, user)
data = {
- "channelServiceOrderId": body.channel_service_order_id,
+ "channelServiceOrderId": body.channel_service_order_id or "",
"remark": body.remark,
+ "bizType": body.biz_type or "pilot_service",
}
result = pilot_application.create_application(
db,
@@ -638,6 +643,7 @@ async def api_create_schedule(
data = {
"infusionTime": body.infusion_time,
"remark": body.remark,
+ "amount": body.amount,
}
result = pilot_application.create_schedule(
db,
diff --git a/backend/app/crud/clinical_payment.py b/backend/app/crud/clinical_payment.py
new file mode 100644
index 000000000..ed1bc6fdf
--- /dev/null
+++ b/backend/app/crud/clinical_payment.py
@@ -0,0 +1,383 @@
+"""临床应用缴费 CRUD"""
+import uuid
+from datetime import datetime
+from decimal import Decimal
+from typing import Any, Dict, List, Optional, Tuple
+
+from sqlalchemy import and_, func
+from sqlalchemy.orm import Session
+
+from app.models.clinical_payment import BizPilotPayment
+from app.models.models import (
+ BizPilotApplication,
+ BizPilotPatient,
+ BizPilotInfusionPlan,
+ BizPilotInfusionSchedule,
+)
+
+
+# ─── helpers ──────────────────────────────────────────────
+
+
+def _row_to_dict(row: BizPilotPayment, *, extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
+ d: Dict[str, Any] = {
+ "id": row.id,
+ "pilotApplicationId": row.pilot_application_id or "",
+ "infusionScheduleId": row.infusion_schedule_id or "",
+ "bizType": row.biz_type or "clinical_application",
+ "amount": float(row.amount) if row.amount else 0,
+ "transferType": row.transfer_type or "",
+ "transferAmount": float(row.transfer_amount) if row.transfer_amount else None,
+ "transferTime": row.transfer_time.strftime("%Y-%m-%d %H:%M:%S") if row.transfer_time else "",
+ "transferAttachments": row.transfer_attachments or "",
+ "payStatus": row.pay_status or "dfj",
+ "financeConfirmAttachments": row.finance_confirm_attachments or "",
+ "financeRemark": row.finance_remark or "",
+ "financeConfirmTime": row.finance_confirm_time.strftime("%Y-%m-%d %H:%M:%S") if row.finance_confirm_time else "",
+ "refundAmount": float(row.refund_amount) if row.refund_amount else None,
+ "refundRemark": row.refund_remark or "",
+ "refundAttachments": row.refund_attachments or "",
+ "refundApplyBy": row.refund_apply_by or "",
+ "refundApplyTime": row.refund_apply_time.strftime("%Y-%m-%d %H:%M:%S") if row.refund_apply_time else "",
+ "refundFinanceConfirmAttachments": row.refund_finance_confirm_attachments or "",
+ "refundFinanceRemark": row.refund_finance_remark or "",
+ "refundFinanceConfirmTime": row.refund_finance_confirm_time.strftime("%Y-%m-%d %H:%M:%S") if row.refund_finance_confirm_time else "",
+ "tenantNo": row.tenant_no or "",
+ "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 "",
+ "updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "",
+ }
+ if extra:
+ d.update(extra)
+ return d
+
+
+def _get_or_raise(db: Session, *, payment_id: str, tenant_no: str) -> BizPilotPayment:
+ row = (
+ db.query(BizPilotPayment)
+ .filter(
+ and_(
+ BizPilotPayment.id == payment_id,
+ BizPilotPayment.tenant_no == tenant_no,
+ BizPilotPayment.del_flag == 0,
+ )
+ )
+ .first()
+ )
+ if not row:
+ raise ValueError("缴费记录不存在")
+ return row
+
+
+def _enrich_payment(db: Session, row: BizPilotPayment) -> Dict[str, Any]:
+ """关联查询申请单、患者、输注计划信息,附加到 dict"""
+ app_row = (
+ db.query(BizPilotApplication)
+ .filter(BizPilotApplication.id == row.pilot_application_id)
+ .first()
+ )
+ patient_row = None
+ schedule_row = (
+ db.query(BizPilotInfusionSchedule)
+ .filter(BizPilotInfusionSchedule.id == row.infusion_schedule_id)
+ .first()
+ )
+ if app_row:
+ patient_row = (
+ db.query(BizPilotPatient)
+ .filter(
+ BizPilotPatient.pilot_application_id == app_row.id,
+ BizPilotPatient.del_flag == 0,
+ )
+ .first()
+ )
+ extra = {
+ "applicationNo": app_row.application_no if app_row else "",
+ "applicationName": app_row.application_name if app_row else "",
+ "channelName": app_row.channel_name if app_row else "",
+ "patientName": patient_row.patient_name if patient_row else "",
+ "cellType": schedule_row.cell_type if schedule_row else "",
+ "specText": schedule_row.spec_text if schedule_row else "",
+ "infusionTime": schedule_row.infusion_time.strftime("%Y-%m-%d %H:%M:%S") if schedule_row and schedule_row.infusion_time else "",
+ }
+ return _row_to_dict(row, extra=extra)
+
+
+# ─── create (auto) ────────────────────────────────────────
+
+
+def auto_create_payment(
+ db: Session,
+ *,
+ application_id: str,
+ schedule_id: str,
+ amount: Any,
+ tenant_no: str,
+ user_id: str,
+ dept_id: str,
+) -> Optional[BizPilotPayment]:
+ """输注计划创建时自动调用,生成 dfj 缴费记录"""
+ if amount is None or Decimal(str(amount)) <= 0:
+ return None
+ now = datetime.now()
+ row = BizPilotPayment(
+ id=str(uuid.uuid4()),
+ pilot_application_id=application_id,
+ infusion_schedule_id=schedule_id,
+ biz_type="clinical_application",
+ amount=Decimal(str(amount)),
+ pay_status="dfj",
+ tenant_no=tenant_no,
+ order_num=0,
+ create_by=user_id,
+ create_dept_id=dept_id or "",
+ create_time=now,
+ update_by=user_id,
+ update_time=now,
+ del_flag=0,
+ )
+ db.add(row)
+ return row
+
+
+# ─── list ─────────────────────────────────────────────────
+
+
+def list_payments(
+ db: Session,
+ *,
+ page: int = 1,
+ page_size: int = 10,
+ pay_status: Optional[str] = None,
+ keyword: Optional[str] = None,
+ tenant_no: str = "",
+ user_info=None,
+) -> Tuple[List[Dict[str, Any]], int]:
+ q = (
+ db.query(BizPilotPayment)
+ .filter(
+ BizPilotPayment.del_flag == 0,
+ BizPilotPayment.tenant_no == tenant_no,
+ )
+ )
+ if pay_status and pay_status.strip():
+ q = q.filter(BizPilotPayment.pay_status == pay_status.strip())
+ if keyword and keyword.strip():
+ kw = f"%{keyword.strip()}%"
+ sub = (
+ db.query(BizPilotApplication.id)
+ .filter(
+ BizPilotApplication.del_flag == 0,
+ BizPilotApplication.application_no.like(kw),
+ )
+ .subquery()
+ )
+ q = q.filter(BizPilotPayment.pilot_application_id.in_(sub))
+ if user_info:
+ from app.core.data_scope import apply_data_scope
+ q = apply_data_scope(q, BizPilotPayment, user_info, db, tenant_no)
+ total = q.order_by(None).with_entities(func.count(BizPilotPayment.id)).scalar() or 0
+ page = max(1, page)
+ page_size = min(max(1, page_size), 100)
+ rows = (
+ q.order_by(BizPilotPayment.create_time.desc())
+ .offset((page - 1) * page_size)
+ .limit(page_size)
+ .all()
+ )
+ out = [_enrich_payment(db, r) for r in rows]
+ return out, int(total)
+
+
+# ─── submit receipt (dfj → jfqrz) ────────────────────────
+
+
+def submit_receipt(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "dfj":
+ raise ValueError("仅待缴费状态可提交缴费确认")
+ row.pay_status = "jfqrz"
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+def batch_submit_receipt(
+ db: Session,
+ *,
+ payment_ids: List[str],
+ tenant_no: str,
+ user_id: Optional[str],
+) -> int:
+ ids = [str(i).strip() for i in (payment_ids or []) if str(i).strip()]
+ if not ids:
+ raise ValueError("请选择缴费记录")
+ for pid in ids:
+ row = _get_or_raise(db, payment_id=pid, tenant_no=tenant_no)
+ if row.pay_status != "dfj":
+ continue
+ row.pay_status = "jfqrz"
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ return len(ids)
+
+
+# ─── confirm receipt (jfqrz → yjf) ──────────────────────
+
+
+def confirm_receipt(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+ finance_confirm_attachments: Optional[str] = None,
+ finance_remark: Optional[str] = None,
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "jfqrz":
+ if row.pay_status == "yjf":
+ raise ValueError("该记录已确认收款")
+ raise ValueError("当前状态不可确认收款")
+ row.pay_status = "yjf"
+ if finance_confirm_attachments is not None:
+ row.finance_confirm_attachments = finance_confirm_attachments.strip() or None
+ if finance_remark is not None:
+ row.finance_remark = finance_remark.strip() or None
+ row.finance_confirm_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+# ─── submit refund (yjf → tfqrz) ─────────────────────────
+
+
+def submit_refund(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+ refund_amount: Any,
+ refund_remark: Optional[str] = None,
+ refund_attachments: Optional[str] = None,
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "yjf":
+ raise ValueError("仅已缴费状态可发起退费")
+ if refund_amount is None or Decimal(str(refund_amount)) <= 0:
+ raise ValueError("请填写有效的退费金额")
+ row.pay_status = "tfqrz"
+ row.refund_amount = Decimal(str(refund_amount))
+ row.refund_remark = (refund_remark or "").strip() or None
+ row.refund_attachments = (refund_attachments or "").strip() or None
+ row.refund_apply_by = user_id
+ row.refund_apply_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+def batch_submit_refund(
+ db: Session,
+ *,
+ payment_ids: List[str],
+ tenant_no: str,
+ user_id: Optional[str],
+ refund_amount: Any,
+ refund_remark: Optional[str] = None,
+ refund_attachments: Optional[str] = None,
+) -> int:
+ ids = [str(i).strip() for i in (payment_ids or []) if str(i).strip()]
+ if not ids:
+ raise ValueError("请选择缴费记录")
+ for pid in ids:
+ row = _get_or_raise(db, payment_id=pid, tenant_no=tenant_no)
+ if row.pay_status != "yjf":
+ continue
+ if refund_amount is None or Decimal(str(refund_amount)) <= 0:
+ continue
+ row.pay_status = "tfqrz"
+ row.refund_amount = Decimal(str(refund_amount))
+ row.refund_remark = (refund_remark or "").strip() or None
+ row.refund_attachments = (refund_attachments or "").strip() or None
+ row.refund_apply_by = user_id
+ row.refund_apply_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ return len(ids)
+
+
+# ─── confirm refund (tfqrz → ytf) ───────────────────────
+
+
+def confirm_refund(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+ refund_finance_confirm_attachments: Optional[str] = None,
+ refund_finance_remark: Optional[str] = None,
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "tfqrz":
+ if row.pay_status == "ytf":
+ raise ValueError("该记录已确认退费")
+ raise ValueError("当前状态不可确认退费")
+ row.pay_status = "ytf"
+ if refund_finance_confirm_attachments is not None:
+ row.refund_finance_confirm_attachments = refund_finance_confirm_attachments.strip() or None
+ if refund_finance_remark is not None:
+ row.refund_finance_remark = refund_finance_remark.strip() or None
+ row.refund_finance_confirm_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+# ─── detail ───────────────────────────────────────────────
+
+
+def get_payment_detail(db: Session, *, payment_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ return _enrich_payment(db, row)
+
+
+def get_payment_by_schedule(db: Session, *, schedule_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
+ row = (
+ db.query(BizPilotPayment)
+ .filter(
+ BizPilotPayment.infusion_schedule_id == schedule_id,
+ BizPilotPayment.tenant_no == tenant_no,
+ BizPilotPayment.del_flag == 0,
+ )
+ .first()
+ )
+ if not row:
+ return None
+ return _enrich_payment(db, row)
diff --git a/backend/app/crud/pilot_application.py b/backend/app/crud/pilot_application.py
index d821268f1..661b3f42f 100644
--- a/backend/app/crud/pilot_application.py
+++ b/backend/app/crud/pilot_application.py
@@ -71,9 +71,9 @@ def _release_application_no_for_soft_delete(application_no: str, row_id: str) ->
return f"{base[:max_base_len]}{suffix}"
-def _generate_application_no(db: Session, tenant_no: str) -> str:
+def _generate_application_no(db: Session, tenant_no: str, biz_type: str = "pilot_service") -> str:
today_str = datetime.now().strftime("%Y%m%d")
- prefix = f"ZSSQ{today_str}"
+ prefix = f"LCSQ{today_str}" if biz_type == "clinical_application" else f"ZSSQ{today_str}"
existing = (
db.query(BizPilotApplication.application_no)
.filter(
@@ -99,6 +99,7 @@ def _row_to_dict_application(row, db=None, tenant_no=None) -> Dict[str, Any]:
"applicationNo": row.application_no or "",
"applicationName": row.application_name or "",
"applicationStatus": row.application_status or "",
+ "bizType": row.biz_type or "pilot_service",
"channelServiceOrderId": row.channel_service_order_id or "",
"channelId": row.channel_id or "",
"channelName": row.channel_name or "",
@@ -268,6 +269,7 @@ def _row_to_dict_infusion_schedule(row, cell_products: Optional[List[Dict[str, A
"qcStatus": row.qc_status or "dzc",
"cellProducts": cell_products if cell_products is not None else [],
"attachments": row.attachments or "",
+ "amount": float(row.amount) if row.amount else None,
"tenantNo": row.tenant_no or "",
"remark": row.remark or "",
"createBy": row.create_by or "",
@@ -290,6 +292,7 @@ def list_applications(
application_status: Optional[str] = None,
channel_service_order_id: Optional[str] = None,
keyword: Optional[str] = None,
+ biz_type: Optional[str] = None,
):
q = db.query(BizPilotApplication).filter(
BizPilotApplication.tenant_no == tenant_no,
@@ -303,6 +306,8 @@ def list_applications(
q = q.filter(BizPilotApplication.application_status.in_(statuses))
if channel_service_order_id:
q = q.filter(BizPilotApplication.channel_service_order_id == channel_service_order_id)
+ if biz_type:
+ q = q.filter(BizPilotApplication.biz_type == biz_type)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
@@ -390,25 +395,28 @@ def create_application(
dept_id: str,
) -> Dict[str, Any]:
channel_service_order_id = data.get("channelServiceOrderId") or ""
- if not channel_service_order_id:
+ biz_type = data.get("bizType") or "pilot_service"
+ if not channel_service_order_id and biz_type != "clinical_application":
raise ValueError("请选择渠道服务订单")
# Validate service order exists and is active
- svc_order = (
- db.query(BizChannelServiceOrder)
- .filter(
- BizChannelServiceOrder.id == channel_service_order_id,
- BizChannelServiceOrder.del_flag == 0,
+ svc_order = None
+ if channel_service_order_id:
+ svc_order = (
+ db.query(BizChannelServiceOrder)
+ .filter(
+ BizChannelServiceOrder.id == channel_service_order_id,
+ BizChannelServiceOrder.del_flag == 0,
+ )
+ .first()
)
- .first()
- )
- if not svc_order:
- raise ValueError("渠道服务订单不存在")
- if svc_order.order_status != "sxz":
- raise ValueError("渠道服务订单未生效,无法创建中试申请")
+ if not svc_order:
+ raise ValueError("渠道服务订单不存在")
+ if svc_order.order_status != "sxz":
+ raise ValueError("渠道服务订单未生效,无法创建中试申请")
now = datetime.now()
- application_no = _generate_application_no(db, tenant_no)
+ application_no = _generate_application_no(db, tenant_no, biz_type=biz_type)
row = BizPilotApplication(
id=str(uuid.uuid4()),
@@ -416,8 +424,8 @@ def create_application(
application_name=data.get("applicationName") or "",
application_status="dks",
channel_service_order_id=channel_service_order_id,
- channel_id=svc_order.channel_id or "",
- channel_name=svc_order.channel_name or "",
+ channel_id=svc_order.channel_id or "" if svc_order else "",
+ channel_name=svc_order.channel_name or "" if svc_order else "",
tenant_no=tenant_no,
order_num=0,
remark=data.get("remark") or "",
@@ -427,6 +435,7 @@ def create_application(
update_by=user_id,
update_time=now,
del_flag=0,
+ biz_type=biz_type,
)
db.add(row)
db.commit()
@@ -1129,6 +1138,7 @@ def create_schedule(
infusion_status=data.get("infusionStatus") or "dtz",
qc_status=data.get("qcStatus") or "dzc",
attachments=data.get("attachments") or "",
+ amount=data.get("amount") if data.get("amount") else None,
tenant_no=tenant_no,
order_num=data.get("sortNo") or data.get("orderNum") or 0,
remark=data.get("remark") or "",
@@ -1164,6 +1174,33 @@ def create_schedule(
user_id=user_id,
create_dept_id=dept_id,
)
+ # 临床应用自动创建缴费记录
+ plan_row = (
+ db.query(BizPilotInfusionPlan)
+ .filter(BizPilotInfusionPlan.id == plan_id, BizPilotInfusionPlan.del_flag == 0)
+ .first()
+ )
+ if plan_row:
+ application = (
+ db.query(BizPilotApplication)
+ .filter(
+ BizPilotApplication.id == plan_row.pilot_application_id,
+ BizPilotApplication.del_flag == 0,
+ )
+ .first()
+ )
+ if application and application.biz_type == "clinical_application" and data.get("amount"):
+ from app.crud.clinical_payment import auto_create_payment
+
+ auto_create_payment(
+ db,
+ application_id=application.id,
+ schedule_id=schedule_id,
+ amount=data["amount"],
+ tenant_no=tenant_no,
+ user_id=user_id,
+ dept_id=dept_id,
+ )
if commit:
db.commit()
db.refresh(row)
diff --git a/backend/app/crud/production_work_order.py b/backend/app/crud/production_work_order.py
index 77cbb7c14..2f3734110 100644
--- a/backend/app/crud/production_work_order.py
+++ b/backend/app/crud/production_work_order.py
@@ -179,6 +179,49 @@ def _enrich_outbound_items(db: Session, items: List[Dict[str, Any]]) -> List[Dic
return items
+def _outbound_work_order_ids_by_keyword(db: Session, tenant_no: str, keyword: str) -> List[str]:
+ """出库工单关键词:按关联申请单号、申请单名称匹配。"""
+ from app.models.models import (
+ BizPilotApplication,
+ BizPilotInfusionPlan,
+ BizPilotInfusionSchedule,
+ )
+
+ kw = (keyword or "").strip()
+ if not kw:
+ return []
+ like = f"%{kw}%"
+ rows = (
+ db.query(BizProductionWorkOrder.id)
+ .join(
+ BizPilotInfusionSchedule,
+ BizProductionWorkOrder.infusion_schedule_id == BizPilotInfusionSchedule.id,
+ )
+ .join(
+ BizPilotInfusionPlan,
+ BizPilotInfusionSchedule.infusion_plan_id == BizPilotInfusionPlan.id,
+ )
+ .join(
+ BizPilotApplication,
+ BizPilotInfusionPlan.pilot_application_id == BizPilotApplication.id,
+ )
+ .filter(
+ BizProductionWorkOrder.del_flag == 0,
+ BizProductionWorkOrder.tenant_no == tenant_no,
+ BizProductionWorkOrder.work_order_type == "outbound",
+ BizPilotInfusionSchedule.del_flag == 0,
+ BizPilotInfusionPlan.del_flag == 0,
+ BizPilotApplication.del_flag == 0,
+ or_(
+ BizPilotApplication.application_no.like(like),
+ BizPilotApplication.application_name.like(like),
+ ),
+ )
+ .all()
+ )
+ return [r[0] for r in rows]
+
+
def _reception_source_label(source: Optional[str]) -> str:
key = (source or "").strip() or "cell_storage"
return _RECEPTION_SOURCE_LABELS.get(key, key)
@@ -410,13 +453,15 @@ def list_production_work_orders(
kw = (query.keyword or "").strip()
if kw:
like = f"%{kw}%"
- q0 = q0.filter(
- or_(
- BizProductionWorkOrder.work_order_no.like(like),
- BizProductionWorkOrder.protocol_no.like(like),
- BizProductionWorkOrder.sample_code.like(like),
- )
- )
+ keyword_conditions = [
+ BizProductionWorkOrder.work_order_no.like(like),
+ BizProductionWorkOrder.protocol_no.like(like),
+ BizProductionWorkOrder.sample_code.like(like),
+ ]
+ outbound_ids = _outbound_work_order_ids_by_keyword(db, tenant_no, kw)
+ if outbound_ids:
+ keyword_conditions.append(BizProductionWorkOrder.id.in_(outbound_ids))
+ q0 = q0.filter(or_(*keyword_conditions))
elif query.work_order_no and query.work_order_no.strip():
like = f"%{query.work_order_no.strip()}%"
q0 = q0.filter(BizProductionWorkOrder.work_order_no.like(like))
diff --git a/backend/app/crud/qc_work_order.py b/backend/app/crud/qc_work_order.py
index 8af173133..ec4209821 100644
--- a/backend/app/crud/qc_work_order.py
+++ b/backend/app/crud/qc_work_order.py
@@ -195,6 +195,45 @@ def _enrich_outbound_work_orders(
return items
+def _outbound_qc_work_order_ids_by_keyword(
+ db: Session, tenant_no: str, keyword: str
+) -> List[str]:
+ """出库质检工单关键词:按关联申请单号、申请单名称匹配。"""
+ kw = (keyword or "").strip()
+ if not kw:
+ return []
+ like = f"%{kw}%"
+ rows = (
+ db.query(BizQcWorkOrder.id)
+ .join(
+ BizPilotInfusionSchedule,
+ BizQcWorkOrder.infusion_schedule_id == BizPilotInfusionSchedule.id,
+ )
+ .join(
+ BizPilotInfusionPlan,
+ BizPilotInfusionSchedule.infusion_plan_id == BizPilotInfusionPlan.id,
+ )
+ .join(
+ BizPilotApplication,
+ BizPilotInfusionPlan.pilot_application_id == BizPilotApplication.id,
+ )
+ .filter(
+ BizQcWorkOrder.del_flag == 0,
+ BizQcWorkOrder.tenant_no == tenant_no,
+ BizQcWorkOrder.work_order_type == "outbound",
+ BizPilotInfusionSchedule.del_flag == 0,
+ BizPilotInfusionPlan.del_flag == 0,
+ BizPilotApplication.del_flag == 0,
+ or_(
+ BizPilotApplication.application_no.like(like),
+ BizPilotApplication.application_name.like(like),
+ ),
+ )
+ .all()
+ )
+ return [r[0] for r in rows]
+
+
def _accept_conclusion_label(v: Optional[str]) -> str:
key = (v or "").strip()
return _ACCEPT_CONCLUSION_LABELS.get(key, key)
@@ -565,13 +604,16 @@ def list_work_orders(
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
- q0 = q0.filter(
- or_(
- BizQcWorkOrder.work_order_no.like(like),
- BizQcWorkOrder.protocol_no.like(like),
- BizQcWorkOrder.sample_code.like(like),
- )
- )
+ keyword_conditions = [
+ BizQcWorkOrder.work_order_no.like(like),
+ BizQcWorkOrder.protocol_no.like(like),
+ BizQcWorkOrder.sample_code.like(like),
+ ]
+ if tenant_no:
+ outbound_ids = _outbound_qc_work_order_ids_by_keyword(db, tenant_no, kw)
+ if outbound_ids:
+ keyword_conditions.append(BizQcWorkOrder.id.in_(outbound_ids))
+ q0 = q0.filter(or_(*keyword_conditions))
st = (work_order_status or "").strip()
if st:
q0 = q0.filter(BizQcWorkOrder.work_order_status == st)
diff --git a/backend/app/main.py b/backend/app/main.py
index 1569a72ea..97b1d734e 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -13,7 +13,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, workflow_router, basic_info_router, collection_protocol_router, leave_router, operations_router, ethnic_router, sample_reception_router, qc_router, research_donation_router, production_router, pilot_service_router, insurance_company_router, material_router, storage_release_review_router, sample_sms_notification_router, storage_entry_sms_notification_router, pilot_application_router, pilot_checkup_router, pilot_delivery_router, pilot_infusion_confirm_router, pilot_infusion_schedule_mgmt_router, outbound_release_review_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, sample_reception_router, qc_router, research_donation_router, production_router, pilot_service_router, insurance_company_router, material_router, storage_release_review_router, sample_sms_notification_router, storage_entry_sms_notification_router, pilot_application_router, pilot_checkup_router, pilot_delivery_router, pilot_infusion_confirm_router, pilot_infusion_schedule_mgmt_router, outbound_release_review_router, clinical_payment_router
@asynccontextmanager
@@ -123,6 +123,7 @@ app.include_router(pilot_delivery_router, prefix=settings.API_V1_PREFIX)
app.include_router(pilot_infusion_confirm_router, prefix=settings.API_V1_PREFIX)
app.include_router(pilot_infusion_schedule_mgmt_router, prefix=settings.API_V1_PREFIX)
app.include_router(outbound_release_review_router, prefix=settings.API_V1_PREFIX)
+app.include_router(clinical_payment_router, prefix=settings.API_V1_PREFIX)
def _mask_secret(value: str) -> str:
diff --git a/backend/app/models/clinical_payment.py b/backend/app/models/clinical_payment.py
new file mode 100644
index 000000000..7c8dc78ef
--- /dev/null
+++ b/backend/app/models/clinical_payment.py
@@ -0,0 +1,41 @@
+"""临床应用缴费记录模型"""
+from sqlalchemy import Column, String, Text, SmallInteger, Numeric, DateTime
+from sqlalchemy.sql import func
+from app.models.base import Base
+
+
+class BizPilotPayment(Base):
+ """临床应用缴费记录 biz_pilot_payment"""
+
+ __tablename__ = "biz_pilot_payment"
+
+ id = Column(String(64), primary_key=True, comment="主键ID")
+ pilot_application_id = Column(String(64), nullable=False, comment="临床申请单ID")
+ infusion_schedule_id = Column(String(64), nullable=False, comment="输注计划ID")
+ biz_type = Column(String(20), nullable=False, default="clinical_application", comment="业务类型")
+ amount = Column(Numeric(18, 2), nullable=False, comment="缴费金额")
+ transfer_type = Column(String(16), nullable=True, comment="转账方式: wxzz/zzfbzz/yhkzz/xj")
+ transfer_amount = Column(Numeric(18, 2), nullable=True, comment="实际转账金额")
+ transfer_time = Column(DateTime, nullable=True, comment="转账时间")
+ transfer_attachments = Column(Text, nullable=True, comment="转账凭证附件")
+ pay_status = Column(String(32), nullable=False, default="dfj", comment="缴费状态: dfj/jfqrz/yjf/tfqrz/ytf")
+ finance_confirm_attachments = Column(Text, nullable=True, comment="财务确认收款附件")
+ finance_remark = Column(String(512), nullable=True, comment="财务确认收款备注")
+ finance_confirm_time = Column(DateTime, nullable=True, comment="财务确认收款时间")
+ refund_amount = Column(Numeric(18, 2), nullable=True, comment="退费金额")
+ refund_remark = Column(String(512), nullable=True, comment="退费备注")
+ refund_attachments = Column(Text, nullable=True, comment="退费附件")
+ refund_apply_by = Column(String(64), nullable=True, comment="退费申请人ID")
+ refund_apply_time = Column(DateTime, nullable=True, comment="退费申请时间")
+ refund_finance_confirm_attachments = Column(Text, nullable=True, comment="退费财务确认附件")
+ refund_finance_remark = Column(String(512), nullable=True, comment="退费财务确认备注")
+ refund_finance_confirm_time = Column(DateTime, nullable=True, comment="退费财务确认时间")
+ tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
+ order_num = Column(SmallInteger, 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, server_default=func.now(), comment="创建时间")
+ update_by = Column(String(64), nullable=True, comment="更新人id")
+ update_time = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now(), comment="更新时间")
+ del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
diff --git a/backend/app/models/models.py b/backend/app/models/models.py
index 4b6963e06..4b6511268 100644
--- a/backend/app/models/models.py
+++ b/backend/app/models/models.py
@@ -2826,6 +2826,7 @@ class BizPilotApplication(Base):
channel_service_order_id = Column(String(64), nullable=False, comment="渠道服务购买订单ID")
channel_id = Column(String(64), nullable=True, comment="渠道ID(缓存自服务订单)")
channel_name = Column(String(200), nullable=True, comment="渠道名称(缓存)")
+ biz_type = Column(String(20), nullable=False, default="pilot_service", comment="业务类型: pilot_service=中试服务, clinical_application=临床应用")
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
order_num = Column(Integer, nullable=True, comment="排序号")
remark = Column(String(512), nullable=True, comment="备注")
@@ -2907,6 +2908,7 @@ class BizPilotInfusionSchedule(Base):
infusion_status = Column(String(16), nullable=False, default="dtz", comment="dtz待输注 yrqr已用户确认 yqx已取消 ywc已完成")
cancel_reason = Column(String(512), nullable=True, default=None, comment="取消原因")
qc_status = Column(String(16), nullable=False, default="dzc", comment="dzc待质检 yzj已质检 yqx已取消")
+ amount = Column(Numeric(18, 2), nullable=True, comment="临床应用-手动填写的输注金额(中试为NULL)")
attachments = Column(String(4000), nullable=True, comment="附件逗号分隔")
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
order_num = Column(Integer, nullable=True, comment="排序号")
@@ -3251,3 +3253,4 @@ class BizOutboundReleaseReview(Base):
from app.models.qc_param import BizQcItemParam, BizQcTaskResult # noqa: E402,F401
from app.models.qc_material import BizQcMaterial, BizQcMaterialUsage # noqa: E402,F401
from app.models.material import BizMaterial, BizMaterialStockLog # noqa: E402,F401
+from app.models.clinical_payment import BizPilotPayment # noqa: E402,F401
diff --git a/backend/scripts/reorder_web_menus.py b/backend/scripts/reorder_web_menus.py
new file mode 100644
index 000000000..ccd5be5f7
--- /dev/null
+++ b/backend/scripts/reorder_web_menus.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python3
+"""重排 Web 端一级菜单顺序,并生成 sys_menu 初始化 SQL。"""
+from __future__ import annotations
+
+import ast
+import re
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[2]
+MENU_PY = ROOT / "backend/app/api/menu.py"
+SQXP_TS = ROOT / "web-admin/src/router/modules/sqxp.ts"
+SQL_OUT = ROOT / "docs/sql/sys_menu_web_init.sql"
+
+BLOCK_ORDER = [2, 3, 4, 5, 7, 9, 10, 1, 6, 8, 11, 12, 13]
+
+BLOCK_TITLES = {
+ 1: "市场基础信息",
+ 2: "细胞存储",
+ 3: "科研捐赠",
+ 4: "中试服务",
+ 5: "临床应用",
+ 6: "质检基础信息",
+ 7: "质检管理",
+ 8: "生产基础信息",
+ 9: "生产管理",
+ 10: "运营支撑",
+ 11: "系统基础信息",
+ 12: "工作流管理",
+ 13: "系统管理",
+}
+
+SQXP_HEADER = """import type { AppRouteRecord } from '@/types/router'
+
+/**
+ * 前端路由(13 个一级菜单:细胞存储、科研捐赠、中试服务、临床应用、
+ * 质检管理、生产管理、运营支撑、市场基础信息、质检基础信息、生产基础信息、
+ * 系统基础信息、工作流管理、系统管理)
+ * 与后端菜单对齐时请同步 `backend/app/api/menu.py`。
+ */
+export const sqxpAdminRoutes: AppRouteRecord[] = [
+"""
+
+MENU_TAIL = (
+ "\n]\n\n\n"
+ '@router.get("/list")\n'
+ "async def get_menu_list(_user: dict = Depends(require_admin_or_super_admin)):\n"
+ ' """获取菜单列表(与当前前端路由一致)"""\n'
+ ' return success_response({"menuList": STATIC_MENU_TREE})\n'
+)
+
+BLOCK_RE_TS = re.compile(
+ r" // ──+\n // (\d+)\. ([^\n]+)\n // ──+\n",
+ re.MULTILINE,
+)
+BLOCK_RE_PY = re.compile(
+ r" # ──+\n # (\d+)\. ([^\n]+)\n # ──+\n",
+ re.MULTILINE,
+)
+
+
+def split_menu_blocks(
+ content: str, array_start_marker: str, block_re: re.Pattern
+) -> tuple[str, dict[int, str]]:
+ start = content.index(array_start_marker) + len(array_start_marker)
+ depth = 1
+ i = start
+ while i < len(content) and depth > 0:
+ if content[i] == "[":
+ depth += 1
+ elif content[i] == "]":
+ depth -= 1
+ i += 1
+ array_body = content[start : i - 1]
+ prefix = content[:start]
+
+ parts = block_re.split(array_body)
+ blocks: dict[int, str] = {}
+ idx = 1
+ while idx < len(parts):
+ num = int(parts[idx])
+ block = parts[idx + 2].rstrip()
+ if block.endswith(","):
+ block = block[:-1]
+ blocks[num] = block.rstrip()
+ idx += 3
+ return prefix, blocks
+
+
+def reorder_blocks(blocks: dict[int, str]) -> list[tuple[int, str, str]]:
+ ordered = []
+ for new_idx, old_num in enumerate(BLOCK_ORDER, start=1):
+ ordered.append((new_idx, BLOCK_TITLES[old_num], blocks[old_num]))
+ return ordered
+
+
+def assemble_blocks(ordered: list[tuple[int, str, str]], *, ts: bool) -> str:
+ chunks = []
+ for num, title, body in ordered:
+ if ts:
+ sep = " // " + "─" * 46
+ chunks.append(f"{sep}\n // {num}. {title}\n{sep}\n{body}")
+ else:
+ sep = " # " + "─" * 44
+ chunks.append(f"{sep}\n # {num}. {title}\n{sep}\n{body}")
+ return ",\n\n".join(chunks) + "\n"
+
+
+def load_static_menu_tree() -> list:
+ content = MENU_PY.read_text(encoding="utf-8")
+ marker = "STATIC_MENU_TREE = "
+ start = content.index(marker) + len(marker)
+ depth = 0
+ i = start
+ while i < len(content):
+ ch = content[i]
+ if ch == "[":
+ depth += 1
+ elif ch == "]":
+ depth -= 1
+ if depth == 0:
+ i += 1
+ break
+ i += 1
+ literal = content[start:i]
+ return ast.literal_eval(literal)
+
+
+def sql_escape(val: str | None) -> str:
+ if val is None:
+ return "NULL"
+ return "'" + str(val).replace("'", "''") + "'"
+
+
+def walk_routes(nodes: list, parent_id: str, order_counter: dict, rows: list) -> None:
+ for node in nodes:
+ node_id = node["id"]
+ meta = node.get("meta") or {}
+ title = meta.get("title") or node.get("name") or ""
+ path = node.get("path") or ""
+ component = node.get("component") or ""
+ children = node.get("children") or []
+ is_hide = meta.get("isHide") is True
+ menu_code = meta.get("menuCode")
+ perms = menu_code if menu_code else None
+
+ if parent_id == "2":
+ order_counter["web"] += 1
+ order_num = order_counter["web"]
+ else:
+ order_counter.setdefault(parent_id, 0)
+ order_counter[parent_id] += 1
+ order_num = order_counter[parent_id]
+
+ menu_type = "M" if children else "C"
+ visible = "0" if is_hide else "1"
+
+ rows.append(
+ (
+ node_id,
+ title,
+ parent_id,
+ menu_type,
+ path,
+ component or None,
+ perms,
+ visible,
+ order_num,
+ )
+ )
+
+ if children:
+ walk_routes(children, node_id, order_counter, rows)
+
+
+def generate_sql(tree: list) -> str:
+ rows: list = []
+ order_counter: dict = {"web": 0}
+ walk_routes(tree, "2", order_counter, rows)
+
+ lines = [
+ "-- Web 端 sys_menu 初始化(根节点 + 13 个一级业务菜单及子菜单)",
+ "-- 生成:backend/scripts/reorder_web_menus.py",
+ "-- 一级顺序:细胞存储 → 科研捐赠 → 中试服务 → 临床应用 → 质检管理 → 生产管理",
+ "-- → 运营支撑 → 市场基础信息 → 质检基础信息 → 生产基础信息",
+ "-- → 系统基础信息 → 工作流管理 → 系统管理",
+ "",
+ "SET NAMES utf8mb4;",
+ "",
+ "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', '移动端菜单', '0', 'M', '/mini', NULL, NULL, 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),",
+ "('2', 'Web端菜单', '0', 'M', '/web', NULL, NULL, 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 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",
+ ]
+
+ value_lines = []
+ for row in rows:
+ (
+ node_id,
+ title,
+ parent_id,
+ menu_type,
+ path,
+ component,
+ perms,
+ visible,
+ order_num,
+ ) = row
+ value_lines.append(
+ f"({sql_escape(node_id)}, {sql_escape(title)}, {sql_escape(parent_id)}, "
+ f"{sql_escape(menu_type)}, {sql_escape(path)}, {sql_escape(component)}, "
+ f"{sql_escape(perms)}, 1, {sql_escape(visible)}, '1', 'zkxw', {order_num}, "
+ f"NULL, NULL, '', NOW(), NULL, NOW(), 0)"
+ )
+
+ lines.append(",\n".join(value_lines) + ";")
+ lines.append("")
+ return "\n".join(lines)
+
+
+def main() -> None:
+ menu_content = MENU_PY.read_text(encoding="utf-8")
+ pre, blocks = split_menu_blocks(menu_content, "STATIC_MENU_TREE = [\n", BLOCK_RE_PY)
+ body = assemble_blocks(reorder_blocks(blocks), ts=False)
+ MENU_PY.write_text(pre + body + MENU_TAIL, encoding="utf-8")
+ print(f"updated {MENU_PY.relative_to(ROOT)}")
+
+ ts_content = SQXP_TS.read_text(encoding="utf-8")
+ _, blocks_ts = split_menu_blocks(
+ ts_content, "export const sqxpAdminRoutes: AppRouteRecord[] = [\n", BLOCK_RE_TS
+ )
+ body_ts = assemble_blocks(reorder_blocks(blocks_ts), ts=True)
+ SQXP_TS.write_text(SQXP_HEADER + body_ts + "]\n", encoding="utf-8")
+ print(f"updated {SQXP_TS.relative_to(ROOT)}")
+
+ tree = load_static_menu_tree()
+ sql = generate_sql(tree)
+ SQL_OUT.write_text(sql, encoding="utf-8")
+ row_count = sql.count("('") - 2 # minus root inserts
+ print(f"wrote {SQL_OUT.relative_to(ROOT)} (~{row_count} menu rows, {len(tree)} top-level menus)")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs/demos/consumable-merge-plan-b-demo.html b/docs/demos/consumable-merge-plan-b-demo.html
new file mode 100644
index 000000000..d0eeeac6b
--- /dev/null
+++ b/docs/demos/consumable-merge-plan-b-demo.html
@@ -0,0 +1,953 @@
+
+
+
+
+
+ 方案 B · 耗材管理融合页 UI Demo
+
+
+
+
+
+
+
+
+
+
+
+ 生产管理 / 耗材管理
+ 方案 B 静态 Demo · 非真实数据
+
+
+
+
+ 方案 B:两级 Tab — 一级区分「耗材 / 耗材包」;仅在「耗材」下出现二级 Tab「耗材列表 /
+ 变动记录」。侧栏合并后仅保留一个菜单「耗材管理」(灰线划掉的为将移除的菜单项示意)。
+ 用浏览器直接打开本文件即可预览,路径:docs/demos/consumable-merge-plan-b-demo.html
+
+
+
+
+
耗材管理
+ 融合页 · 方案 B
+
+
+
+
+
+
+
+
+
+
+
耗材视图
+
+
+
+
+
+
+
+ 当前结构:一级「耗材」 → 二级「耗材列表」(筛选 + 工具栏 + 表格 + 分页)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
按编码、名称模糊查询,可按状态筛选。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
按耗材编码/名称、变动类型和时间范围查询变动记录。
+
+
+
+
+
+
+
+
+
+
+
+ | 序号 |
+ 耗材编码 |
+ 耗材名称 |
+ 变动类型 |
+ 变动数量 |
+ 变动后库存 |
+ 操作人 |
+ 变动时间 |
+
+
+
+
+ | 1 |
+ HC-2024-001 |
+ 细胞培养瓶 25cm² |
+ 入库 |
+ +50 |
+ 128 |
+ 张三 |
+ 2026-05-26 14:32 |
+
+
+ | 2 |
+ HC-2024-018 |
+ 无菌离心管 15mL |
+ 出库 |
+ -20 |
+ 12 |
+ 李四 |
+ 2026-05-25 09:15 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
按编号、名称模糊查询。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/sql/sys_menu_web_init.sql b/docs/sql/sys_menu_web_init.sql
new file mode 100644
index 000000000..07aa9813b
--- /dev/null
+++ b/docs/sql/sys_menu_web_init.sql
@@ -0,0 +1,112 @@
+-- Web 端 sys_menu 初始化(根节点 + 13 个一级业务菜单及子菜单)
+-- 生成:backend/scripts/reorder_web_menus.py
+-- 一级顺序:细胞存储 → 科研捐赠 → 中试服务 → 临床应用 → 质检管理 → 生产管理
+-- → 运营支撑 → 市场基础信息 → 质检基础信息 → 生产基础信息
+-- → 系统基础信息 → 工作流管理 → 系统管理
+
+SET NAMES utf8mb4;
+
+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', '移动端菜单', '0', 'M', '/mini', NULL, NULL, 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('2', 'Web端菜单', '0', 'M', '/web', NULL, NULL, 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 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
+('CEL00000000000000000000000000001', '细胞存储', '2', 'M', '/cell-storage', '/index/index', NULL, 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('50000000000000000000000000000002', '采集协议单录入', 'CEL00000000000000000000000000001', 'C', 'protocol', '/cell-storage/protocol/index', 'biz:cellStorageProtocol:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('50000000000000000000000000000003', '采集协议单管理', 'CEL00000000000000000000000000001', 'C', 'protocol-audit', '/cell-storage/protocol-audit/index', 'biz:cellStorageProtocolAudit:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 缴费信息/样本接收/样本入库/储存证/满月待办已并入采集协议单管理 Tab;已有库可执行:
+-- UPDATE sys_menu SET del_flag=1 WHERE id IN ('904b5173519411f1944300163e3bdd96','50000000000000000000000000000011','50000000000000000000000000000012','50000000000000000000000000000006','50000000000000000000000000000008');
+('50000000000000000000000000000004', '缴退费财务确认', 'CEL00000000000000000000000000001', 'C', 'payment-confirm', '/cell-storage/payment-confirm/index', 'biz:cellStoragePaymentConfirm:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('70000000000000000000000000000011', '入库放行审核', 'CEL00000000000000000000000000001', 'C', 'storage-release-review', '/quality-inspection/storage-release-review/index', 'biz:storageReleaseReview:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 退费确认已并入缴退费财务确认 Tab;已有库可执行:UPDATE sys_menu SET menu_name='缴退费财务确认' WHERE id='50000000000000000000000000000004'; UPDATE sys_menu SET del_flag=1 WHERE id='50000000000000000000000000000005';
+-- 入库放行审核已移至细胞存储;已有库可执行:UPDATE sys_menu SET parent_id='CEL00000000000000000000000000001', order_num=5 WHERE id='70000000000000000000000000000011';
+('RSC00000000000000000000000000001', '科研捐赠', '2', 'M', '/research-donation', '/index/index', NULL, 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('60000000000000000000000000000002', '捐赠协议单录入', 'RSC00000000000000000000000000001', 'C', 'list', '/research-donation/list/index', 'biz:researchDonation:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('60000000000000000000000000000003', '捐赠协议单管理', 'RSC00000000000000000000000000001', 'C', 'audit', '/research-donation/audit/index', 'biz:researchDonationAudit:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('PIL00000000000000000000000000001', '中试服务', '2', 'M', '/pilot-service', '/index/index', NULL, 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('80000000000000000000000000000010', '中试服务申请单', 'PIL00000000000000000000000000001', 'C', 'pilot-application', '/pilot-service/pilot-application/index', 'pilot-service:pilot-application:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 体检管理/输注计划已并入中试服务申请单 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id IN ('PIL00000000000000000000000000002','PIL00000000000000000000000000003');
+('PIL00000000000000000000000000004', '出库放行审核', 'PIL00000000000000000000000000001', 'C', 'outbound-release-review', '/pilot-service/outbound-release-review/index', 'pilot-service:outbound-release-review:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000001', '临床应用', '2', 'M', '/clinical-application', '/index/index', NULL, 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000002', '临床申请单', 'CLI00000000000000000000000000001', 'C', 'clinical-application', '/pilot-service/pilot-application/index', 'clinical-application:application:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000003', '体检管理', 'CLI00000000000000000000000000001', 'C', 'clinical-checkup-management', '/pilot-service/checkup-management/index', 'clinical-application:checkup-management:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000004', '输注计划管理', 'CLI00000000000000000000000000001', 'C', 'clinical-infusion-schedule-management', '/pilot-service/infusion-schedule-management/index', 'clinical-application:infusion-schedule-management:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000005', '缴费信息', 'CLI00000000000000000000000000001', 'C', 'clinical-payment-info', '/pilot-service/clinical-payment-info/index', 'clinical-application:payment-info:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000006', '缴费确认(财务)', 'CLI00000000000000000000000000001', 'C', 'clinical-payment-confirm', '/pilot-service/clinical-payment-confirm/index', 'clinical-application:payment-confirm:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000007', '退费确认(财务)', 'CLI00000000000000000000000000001', 'C', 'clinical-refund-confirm', '/pilot-service/clinical-refund-confirm/index', 'clinical-application:refund-confirm:list', 1, '1', '1', 'zkxw', 6, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('CLI00000000000000000000000000008', '出库放行审核', 'CLI00000000000000000000000000001', 'C', 'clinical-outbound-release-review', '/pilot-service/outbound-release-review/index', 'clinical-application:outbound-release-review:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('QCM00000000000000000000000000001', '质检管理', '2', 'M', '/quality-manage', '/index/index', NULL, 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('70000000000000000000000000000002', '样本接收', 'QCM00000000000000000000000000001', 'C', 'sample-reception', '/quality-inspection/sample-reception/index', 'biz:sampleReception:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('70000000000000000000000000000007', '质检管理', 'QCM00000000000000000000000000001', 'C', 'qc-work-order', '/quality-inspection/qc-work-order/index', 'biz:qcWorkOrder:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 质检任务已并入质检管理 Tab;已有库可执行:
+-- UPDATE sys_menu SET menu_name='质检管理' WHERE id='70000000000000000000000000000007';
+-- UPDATE sys_menu SET del_flag=1 WHERE id='70000000000000000000000000000008';
+('PRM00000000000000000000000000001', '生产管理', '2', 'M', '/production', '/index/index', NULL, 1, '1', '1', 'zkxw', 6, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('61000000000000000000000000000003', '库位管理', 'PRM00000000000000000000000000001', 'C', 'storage-facility', '/operations/storage-facility/index', 'operations:storage-facility:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('90000000000000000000000000000013', '细胞产品库', 'PRM00000000000000000000000000001', 'C', 'cell-product', '/production/cell-product/index', 'production:cell-product:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('90000000000000000000000000000011', '生产管理', 'PRM00000000000000000000000000001', 'C', 'production-work-order', '/production/production-work-order/index', 'production:work-order:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 生产任务已并入生产管理 Tab;已有库可执行:
+-- UPDATE sys_menu SET menu_name='生产管理' WHERE id='90000000000000000000000000000011';
+-- UPDATE sys_menu SET del_flag=1 WHERE id='90000000000000000000000000000012';
+('OPS00000000000000000000000000001', '运营支撑', '2', 'M', '/operations', '/index/index', NULL, 1, '1', '1', 'zkxw', 7, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('61000000000000000000000000000002', '洁净室管理', 'OPS00000000000000000000000000001', 'C', 'clean-room', '/operations/clean-room/index', 'operations:clean-room:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('61000000000000000000000000000004', '展馆预约', 'OPS00000000000000000000000000001', 'C', 'exhibition-appointment', '/operations/exhibition-appointment/index', 'operations:exhibition-appointment:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('MKT00000000000000000000000000001', '市场基础信息', '2', 'M', '/market-info', '/index/index', NULL, 1, '1', '1', 'zkxw', 8, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('122', '细胞样本基础信息维护', 'MKT00000000000000000000000000001', 'C', 'cell-sample-type', '/basic-info/cell-sample-type/index', 'biz:cellSampleType:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('123', '细胞采集基础信息维护', 'MKT00000000000000000000000000001', 'C', 'collection-region', '/basic-info/collection-region/index', 'biz:collectionRegion:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('80000000000000000000000000000002', '细胞产品基础信息维护', 'MKT00000000000000000000000000001', 'C', 'cell-product-type', '/pilot-service/cell-product-type/index', 'pilot-service:cell-product-type:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('40000000000000000000000000000007', '保险公司及险种价格', 'MKT00000000000000000000000000001', 'C', 'insurance-company', '/basic-info/insurance-company/index', 'basic-info:insurance-company:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('80000000000000000000000000000007', '体检机构及项目价格', 'MKT00000000000000000000000000001', 'C', 'checkup-institution', '/pilot-service/checkup-institution/index', 'pilot-service:checkup-institution:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('50000000000000000000000000000009', '市场物料库', 'MKT00000000000000000000000000001', 'C', 'material', '/cell-storage/material/index', 'biz:material:list', 1, '1', '1', 'zkxw', 6, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('121', '客户管理', 'MKT00000000000000000000000000001', 'C', 'customer', '/basic-info/customer/index', 'biz:customer:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('80000000000000000000000000000006', '渠道信息管理', 'MKT00000000000000000000000000001', 'C', 'channel', '/pilot-service/channel/index', 'pilot-service:channel:list', 1, '1', '1', 'zkxw', 8, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('80000000000000000000000000000009', '渠道服务订单(财务确认)', 'MKT00000000000000000000000000001', 'C', 'channel-service-order-finance', '/pilot-service/channel-service-order-finance/index', 'pilot-service:channel-service-order-finance:list', 1, '1', '1', 'zkxw', 9, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 采集协议号已并入细胞采集基础信息维护 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='124';
+-- 样本编码已并入细胞样本基础信息维护 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='125';
+-- 变动记录已并入市场物料库 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='50000000000000000000000000000010';
+-- 规格/价格体系/项目价格已并入细胞产品基础信息维护 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id IN ('80000000000000000000000000000003','80000000000000000000000000000004','80000000000000000000000000000005');
+-- 服务购买订单已并入渠道信息管理 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='80000000000000000000000000000008';
+-- 市场基础信息二级菜单排序与更名(已有库可执行):
+-- UPDATE sys_menu SET order_num=1 WHERE id='122';
+-- UPDATE sys_menu SET order_num=2 WHERE id='123';
+-- UPDATE sys_menu SET order_num=3 WHERE id='80000000000000000000000000000002';
+-- UPDATE sys_menu SET order_num=4 WHERE id='40000000000000000000000000000007';
+-- UPDATE sys_menu SET order_num=5 WHERE id='80000000000000000000000000000007';
+-- UPDATE sys_menu SET order_num=6 WHERE id='50000000000000000000000000000009';
+-- UPDATE sys_menu SET order_num=7 WHERE id='121';
+-- UPDATE sys_menu SET order_num=8 WHERE id='80000000000000000000000000000006';
+-- UPDATE sys_menu SET order_num=9, menu_name='渠道服务订单(财务确认)' WHERE id='80000000000000000000000000000009';
+('QCB00000000000000000000000000001', '质检基础信息', '2', 'M', '/quality-base', '/index/index', NULL, 1, '1', '1', 'zkxw', 9, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('70000000000000000000000000000003', '接收检查项配置', 'QCB00000000000000000000000000001', 'C', 'sample-reception-check-item', '/quality-inspection/sample-reception-check-item/index', 'biz:sampleReceptionCheckItem:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('70000000000000000000000000000006', '质检项目管理', 'QCB00000000000000000000000000001', 'C', 'lab-test-item', '/quality-inspection/lab-test-item/index', 'biz:qcLabTestItem:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 第三方检测已并入质检项目管理 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='70000000000000000000000000000004'; UPDATE sys_menu SET menu_name='质检项目管理' WHERE id='70000000000000000000000000000006';
+('70000000000000000000000000000009', '质检物料库', 'QCB00000000000000000000000000001', 'C', 'qc-material', '/quality-inspection/qc-material/index', 'biz:qcMaterial:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 变动记录已并入质检物料库 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='70000000000000000000000000000010';
+('PRB00000000000000000000000000001', '生产基础信息', '2', 'M', '/production-base', '/index/index', NULL, 1, '1', '1', 'zkxw', 10, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('90000000000000000000000000000002', '耗材管理', 'PRB00000000000000000000000000001', 'C', 'consumable', '/production/consumable/index', 'production:consumable:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 耗材包已并入耗材管理 Tab,不再单独挂菜单;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='90000000000000000000000000000007';
+('90000000000000000000000000000003', '试剂管理', 'PRB00000000000000000000000000001', 'C', 'reagent', '/production/reagent/index', 'production:reagent:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('90000000000000000000000000000004', 'SOP文档', 'PRB00000000000000000000000000001', 'C', 'sop', '/production/sop/index', 'production:sop-document:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('90000000000000000000000000000005', '工艺基础信息维护', 'PRB00000000000000000000000000001', 'C', 'process-category', '/production/process-category/index', 'production:process-category:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 产出物类型已并入工艺基础信息维护 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='90000000000000000000000000000006';
+('90000000000000000000000000000008', '工艺库管理', 'PRB00000000000000000000000000001', 'C', 'process', '/production/process/index', 'production:process:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+-- 工艺路线已并入工艺库管理 Tab;已有库可执行:UPDATE sys_menu SET del_flag=1 WHERE id='90000000000000000000000000000010';
+('SYSB000000000000000000000000000001', '系统基础信息', '2', 'M', '/system-base', '/index/index', NULL, 1, '1', '1', 'zkxw', 11, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('107', '站点管理', 'SYSB000000000000000000000000000001', 'C', 'site', '/system/site/index', 'system:site:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('109', '物品单位管理', 'SYSB000000000000000000000000000001', 'C', 'unit', '/system/unit/index', 'system:unit:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('110', '快递公司', 'SYSB000000000000000000000000000001', 'C', 'express', '/system/express/index', 'system:express:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('WKF00000000000000000000000000001', '工作流管理', '2', 'M', '/workflow', '/index/index', NULL, 1, '1', '1', 'zkxw', 12, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('10000000000000000000000000000wf01', '流程配置', 'WKF00000000000000000000000000001', 'C', 'process', '/system/workflow/process/index', 'workflow:process', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('10000000000000000000000000000wf02', '待办中心', 'WKF00000000000000000000000000001', 'C', 'pending', '/system/workflow/task-pending/index', 'workflow:pending', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('10000000000000000000000000000wf03', '已办中心', 'WKF00000000000000000000000000001', 'C', 'completed', '/system/workflow/task-completed/index', 'workflow:completed', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('10000000000000000000000000000wf05', '我的申请', 'WKF00000000000000000000000000001', 'C', 'my-applications', '/system/workflow/my-applications/index', 'workflow:myApplications', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('10000000000000000000000000000wf04', '请假申请', 'WKF00000000000000000000000000001', 'C', 'leave', '/system/leave/index', 'workflow:leave', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('SYSM000000000000000000000000000001', '系统管理', '2', 'M', '/system', '/index/index', NULL, 1, '1', '1', 'zkxw', 13, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('101', '租户管理', 'SYSM000000000000000000000000000001', 'C', 'tenant', '/system/tenant/index', 'system:tenant:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('102', '字典管理', 'SYSM000000000000000000000000000001', 'C', 'dict', '/system/dict/index', 'system:dict:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('106', '菜单管理', 'SYSM000000000000000000000000000001', 'C', 'menu', '/system/menu/index', 'system:menu:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('103', '组织管理', 'SYSM000000000000000000000000000001', 'C', 'dept', '/system/dept/index', 'system:dept:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('105', '角色管理', 'SYSM000000000000000000000000000001', 'C', 'role', '/system/role/index', 'system:role:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('104', '用户管理', 'SYSM000000000000000000000000000001', 'C', 'user', '/system/user/index', 'system:user:list', 1, '1', '1', 'zkxw', 6, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('108', '职位管理', 'SYSM000000000000000000000000000001', 'C', 'post', '/system/post/index', 'system:post:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', NOW(), NULL, NOW(), 0),
+('10000000000000000000000000000008', '用户详情', 'SYSM000000000000000000000000000001', 'C', 'user/detail', '/system/user/detail', NULL, 1, '0', '1', 'zkxw', 8, NULL, NULL, '', NOW(), NULL, NOW(), 0);
diff --git a/docs/superpowers/plans/2026-05-27-clinical-application.md b/docs/superpowers/plans/2026-05-27-clinical-application.md
new file mode 100644
index 000000000..9afb308e2
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-27-clinical-application.md
@@ -0,0 +1,1252 @@
+# 临床应用业务 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:** 在现有中试服务基础上,通过 `biz_type` 区分新增临床应用业务线,包含独立缴费流程(缴费信息/缴费确认/退费确认三个菜单)。
+
+**Architecture:** 复用中试服务的 `biz_pilot_application` 表体系,新增 `biz_type` 字段区分两条线。缴费参照细胞存储 `biz_protocol_payment` 模式,但简化为单表趸交(一个输注计划一条缴费记录)。前端复用 pilot-service 目录下的页面组件,通过路由 prop 区分行为。
+
+**Tech Stack:** FastAPI + SQLAlchemy + Pydantic v2(后端),Vue 3 + Element Plus + TypeScript(前端),MySQL 5.7
+
+**Design Spec:** `docs/superpowers/specs/2026-05-27-clinical-application-design.md`
+
+---
+
+## File Structure
+
+### Backend — New Files
+- `backend/app/models/clinical_payment.py` — `BizPilotPayment` 模型
+- `backend/app/crud/clinical_payment.py` — 缴费 CRUD(参照 `collection_protocol.py` 中缴费函数)
+- `backend/app/api/clinical_payment.py` — 缴费 API 路由(10 个端点)
+- `backend/app/schemas/clinical_payment.py` — Pydantic schemas
+
+### Backend — Modified Files
+- `backend/app/models/models.py` — `BizPilotApplication` 加 `biz_type`,`BizPilotInfusionSchedule` 加 `amount`
+- `backend/app/crud/pilot_application.py` — `create_application` / `_row_to_dict_application` / `_generate_application_no` / `list_applications` 加 `biz_type` 支持
+- `backend/app/api/pilot_application.py` — 路由参数加 `biz_type`
+- `backend/app/api/__init__.py` — 注册 `clinical_payment_router`
+- `backend/app/main.py` — 挂载新路由
+
+### Frontend — New Files
+- `web-admin/src/views/pilot-service/clinical-payment-info/index.vue` — 缴费信息页面
+- `web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue` — 缴费确认页面
+- `web-admin/src/views/pilot-service/clinical-refund-confirm/index.vue` — 退费确认页面
+
+### Frontend — Modified Files
+- `web-admin/src/router/modules/sqxp.ts` — 新增临床应用管理一级菜单 + 7 个子路由
+- `web-admin/src/api/business.ts` — 新增 clinical payment API 函数
+- `web-admin/src/views/pilot-service/pilot-application/index.vue` — 接收 `bizType` prop,条件渲染
+- `web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue` — 临床应用模式隐藏渠道订单
+- `web-admin/src/views/pilot-service/infusion-schedule-management/index.vue` — 临床应用显示金额列
+
+### SQL
+- `docs/sql/clinical-application-migration.sql` — ALTER + CREATE TABLE
+
+---
+
+## Task 1: 数据库迁移 SQL
+
+**Files:**
+- Create: `docs/sql/clinical-application-migration.sql`
+
+- [ ] **Step 1: 编写迁移 SQL**
+
+```sql
+-- ============================================================
+-- 临床应用业务 - 数据库迁移
+-- ============================================================
+
+-- 1. biz_pilot_application 新增 biz_type 字段
+ALTER TABLE biz_pilot_application
+ ADD COLUMN biz_type VARCHAR(20) NOT NULL DEFAULT 'pilot_service'
+ COMMENT '业务类型: pilot_service=中试服务, clinical_application=临床应用'
+ AFTER channel_name;
+
+-- 2. biz_pilot_infusion_schedule 新增 amount 字段
+ALTER TABLE biz_pilot_infusion_schedule
+ ADD COLUMN amount DECIMAL(18,2) DEFAULT NULL
+ COMMENT '临床应用-手动填写的输注金额(中试为NULL)'
+ AFTER qc_status;
+
+-- 3. 新建 biz_pilot_payment 缴费记录表
+CREATE TABLE biz_pilot_payment (
+ id VARCHAR(64) NOT NULL COMMENT '主键ID',
+ pilot_application_id VARCHAR(64) NOT NULL COMMENT '临床申请单ID',
+ infusion_schedule_id VARCHAR(64) NOT NULL COMMENT '输注计划ID',
+ biz_type VARCHAR(20) NOT NULL DEFAULT 'clinical_application' COMMENT '业务类型',
+ amount DECIMAL(18,2) NOT NULL COMMENT '缴费金额',
+ transfer_type VARCHAR(16) DEFAULT NULL COMMENT '转账方式: wxzz/zzfbzz/yhkzz/xj',
+ transfer_amount DECIMAL(18,2) DEFAULT NULL COMMENT '实际转账金额',
+ transfer_time DATETIME DEFAULT NULL COMMENT '转账时间',
+ transfer_attachments TEXT DEFAULT NULL COMMENT '转账凭证附件',
+ pay_status VARCHAR(32) NOT NULL DEFAULT 'dfj' COMMENT '缴费状态: dfj/jfqrz/yjf/tfqrz/ytf',
+ finance_confirm_attachments TEXT DEFAULT NULL COMMENT '财务确认收款附件',
+ finance_remark VARCHAR(512) DEFAULT NULL COMMENT '财务确认收款备注',
+ finance_confirm_time DATETIME DEFAULT NULL COMMENT '财务确认收款时间',
+ refund_amount DECIMAL(18,2) DEFAULT NULL COMMENT '退费金额',
+ refund_remark VARCHAR(512) DEFAULT NULL COMMENT '退费备注',
+ refund_attachments TEXT DEFAULT NULL COMMENT '退费附件',
+ refund_apply_by VARCHAR(64) DEFAULT NULL COMMENT '退费申请人ID',
+ refund_apply_time DATETIME DEFAULT NULL COMMENT '退费申请时间',
+ refund_finance_confirm_attachments TEXT DEFAULT NULL COMMENT '退费财务确认附件',
+ refund_finance_remark VARCHAR(512) DEFAULT NULL COMMENT '退费财务确认备注',
+ refund_finance_confirm_time DATETIME DEFAULT NULL COMMENT '退费财务确认时间',
+ 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 TINYINT(4) NOT NULL DEFAULT '0' COMMENT '删除标记: 0-正常 1-已删除',
+ PRIMARY KEY (id),
+ UNIQUE KEY uk_infusion_schedule (infusion_schedule_id, del_flag),
+ KEY idx_pilot_app (pilot_application_id, del_flag),
+ KEY idx_pay_status (pay_status, del_flag),
+ KEY idx_tenant (tenant_no, del_flag)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='临床应用缴费记录';
+```
+
+- [ ] **Step 2: 在数据库执行迁移**
+
+Run: `mysql -u root -p zkxw < docs/sql/clinical-application-migration.sql`
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add docs/sql/clinical-application-migration.sql
+git commit -m "feat: add clinical application DB migration (biz_type + biz_pilot_payment)"
+```
+
+---
+
+## Task 2: 后端模型层
+
+**Files:**
+- Modify: `backend/app/models/models.py` — `BizPilotApplication` 和 `BizPilotInfusionSchedule`
+- Create: `backend/app/models/clinical_payment.py` — `BizPilotPayment`
+
+- [ ] **Step 1: 在 `BizPilotApplication` 类中添加 `biz_type` 字段**
+
+在 `backend/app/models/models.py` 中 `BizPilotApplication` 类的 `channel_name` 字段后面添加:
+
+```python
+biz_type = Column(String(20), nullable=False, default="pilot_service", comment="业务类型: pilot_service=中试服务, clinical_application=临床应用")
+```
+
+- [ ] **Step 2: 在 `BizPilotInfusionSchedule` 类中添加 `amount` 字段**
+
+在 `backend/app/models/models.py` 中 `BizPilotInfusionSchedule` 类的 `qc_status` 字段后面添加:
+
+```python
+amount = Column(Numeric(18, 2), nullable=True, comment="临床应用-手动填写的输注金额(中试为NULL)")
+```
+
+- [ ] **Step 3: 创建 `backend/app/models/clinical_payment.py`**
+
+```python
+"""临床应用缴费记录模型"""
+from sqlalchemy import Column, String, Text, SmallInteger, Numeric, DateTime
+from sqlalchemy.sql import func
+from app.models.base import Base
+from decimal import Decimal
+
+
+class BizPilotPayment(Base):
+ """临床应用缴费记录 biz_pilot_payment"""
+
+ __tablename__ = "biz_pilot_payment"
+
+ id = Column(String(64), primary_key=True, comment="主键ID")
+ pilot_application_id = Column(String(64), nullable=False, comment="临床申请单ID")
+ infusion_schedule_id = Column(String(64), nullable=False, comment="输注计划ID")
+ biz_type = Column(String(20), nullable=False, default="clinical_application", comment="业务类型")
+ amount = Column(Numeric(18, 2), nullable=False, comment="缴费金额")
+ transfer_type = Column(String(16), nullable=True, comment="转账方式: wxzz/zzfbzz/yhkzz/xj")
+ transfer_amount = Column(Numeric(18, 2), nullable=True, comment="实际转账金额")
+ transfer_time = Column(DateTime, nullable=True, comment="转账时间")
+ transfer_attachments = Column(Text, nullable=True, comment="转账凭证附件")
+ pay_status = Column(String(32), nullable=False, default="dfj", comment="缴费状态: dfj/jfqrz/yjf/tfqrz/ytf")
+ finance_confirm_attachments = Column(Text, nullable=True, comment="财务确认收款附件")
+ finance_remark = Column(String(512), nullable=True, comment="财务确认收款备注")
+ finance_confirm_time = Column(DateTime, nullable=True, comment="财务确认收款时间")
+ refund_amount = Column(Numeric(18, 2), nullable=True, comment="退费金额")
+ refund_remark = Column(String(512), nullable=True, comment="退费备注")
+ refund_attachments = Column(Text, nullable=True, comment="退费附件")
+ refund_apply_by = Column(String(64), nullable=True, comment="退费申请人ID")
+ refund_apply_time = Column(DateTime, nullable=True, comment="退费申请时间")
+ refund_finance_confirm_attachments = Column(Text, nullable=True, comment="退费财务确认附件")
+ refund_finance_remark = Column(String(512), nullable=True, comment="退费财务确认备注")
+ refund_finance_confirm_time = Column(DateTime, nullable=True, comment="退费财务确认时间")
+ tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
+ order_num = Column(SmallInteger, 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, server_default=func.now(), comment="创建时间")
+ update_by = Column(String(64), nullable=True, comment="更新人id")
+ update_time = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now(), comment="更新时间")
+ del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
+```
+
+- [ ] **Step 4: 在 `models.py` 末尾导入新模型**
+
+在 `backend/app/models/models.py` 末尾已有的 import 行后面添加:
+
+```python
+from app.models.clinical_payment import BizPilotPayment # noqa: E402,F401
+```
+
+- [ ] **Step 5: 验证模型加载**
+
+Run: `cd backend && source venv/bin/activate && python -c "from app.models.models import BizPilotApplication, BizPilotInfusionSchedule; from app.models.clinical_payment import BizPilotPayment; print('OK')"`
+Expected: `OK`
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add backend/app/models/models.py backend/app/models/clinical_payment.py
+git commit -m "feat: add biz_type to pilot tables + BizPilotPayment model"
+```
+
+---
+
+## Task 3: 后端 CRUD — clinical_payment.py
+
+**Files:**
+- Create: `backend/app/crud/clinical_payment.py`
+
+参照 `backend/app/crud/collection_protocol.py` 中 `confirm_receipt` (line 1390)、`confirm_refund` (line 1522)、`submit_receipt_finance` (line 1595)、`submit_refund_finance` (line 1648) 的模式。
+
+- [ ] **Step 1: 创建 CRUD 文件**
+
+创建 `backend/app/crud/clinical_payment.py`,包含以下函数:
+
+```python
+"""临床应用缴费 CRUD"""
+import uuid
+from datetime import datetime
+from decimal import Decimal
+from typing import Any, Dict, List, Optional, Tuple
+
+from sqlalchemy import and_, func
+from sqlalchemy.orm import Session
+
+from app.models.clinical_payment import BizPilotPayment
+from app.models.models import (
+ BizPilotApplication,
+ BizPilotPatient,
+ BizPilotInfusionPlan,
+ BizPilotInfusionSchedule,
+)
+
+
+# ─── helpers ──────────────────────────────────────────────
+
+
+def _row_to_dict(row: BizPilotPayment, *, extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
+ d: Dict[str, Any] = {
+ "id": row.id,
+ "pilotApplicationId": row.pilot_application_id or "",
+ "infusionScheduleId": row.infusion_schedule_id or "",
+ "bizType": row.biz_type or "clinical_application",
+ "amount": float(row.amount) if row.amount else 0,
+ "transferType": row.transfer_type or "",
+ "transferAmount": float(row.transfer_amount) if row.transfer_amount else None,
+ "transferTime": row.transfer_time.strftime("%Y-%m-%d %H:%M:%S") if row.transfer_time else "",
+ "transferAttachments": row.transfer_attachments or "",
+ "payStatus": row.pay_status or "dfj",
+ "financeConfirmAttachments": row.finance_confirm_attachments or "",
+ "financeRemark": row.finance_remark or "",
+ "financeConfirmTime": row.finance_confirm_time.strftime("%Y-%m-%d %H:%M:%S") if row.finance_confirm_time else "",
+ "refundAmount": float(row.refund_amount) if row.refund_amount else None,
+ "refundRemark": row.refund_remark or "",
+ "refundAttachments": row.refund_attachments or "",
+ "refundApplyBy": row.refund_apply_by or "",
+ "refundApplyTime": row.refund_apply_time.strftime("%Y-%m-%d %H:%M:%S") if row.refund_apply_time else "",
+ "refundFinanceConfirmAttachments": row.refund_finance_confirm_attachments or "",
+ "refundFinanceRemark": row.refund_finance_remark or "",
+ "refundFinanceConfirmTime": row.refund_finance_confirm_time.strftime("%Y-%m-%d %H:%M:%S") if row.refund_finance_confirm_time else "",
+ "tenantNo": row.tenant_no or "",
+ "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 "",
+ "updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "",
+ }
+ if extra:
+ d.update(extra)
+ return d
+
+
+def _get_or_raise(db: Session, *, payment_id: str, tenant_no: str) -> BizPilotPayment:
+ row = (
+ db.query(BizPilotPayment)
+ .filter(
+ and_(
+ BizPilotPayment.id == payment_id,
+ BizPilotPayment.tenant_no == tenant_no,
+ BizPilotPayment.del_flag == 0,
+ )
+ )
+ .first()
+ )
+ if not row:
+ raise ValueError("缴费记录不存在")
+ return row
+
+
+def _enrich_payment(db: Session, row: BizPilotPayment) -> Dict[str, Any]:
+ """关联查询申请单、患者、输注计划信息,附加到 dict"""
+ app_row = (
+ db.query(BizPilotApplication)
+ .filter(BizPilotApplication.id == row.pilot_application_id)
+ .first()
+ )
+ patient_row = None
+ schedule_row = (
+ db.query(BizPilotInfusionSchedule)
+ .filter(BizPilotInfusionSchedule.id == row.infusion_schedule_id)
+ .first()
+ )
+ if app_row:
+ patient_row = (
+ db.query(BizPilotPatient)
+ .filter(
+ BizPilotPatient.pilot_application_id == app_row.id,
+ BizPilotPatient.del_flag == 0,
+ )
+ .first()
+ )
+ extra = {
+ "applicationNo": app_row.application_no if app_row else "",
+ "applicationName": app_row.application_name if app_row else "",
+ "channelName": app_row.channel_name if app_row else "",
+ "patientName": patient_row.patient_name if patient_row else "",
+ "cellType": schedule_row.cell_type if schedule_row else "",
+ "specText": schedule_row.spec_text if schedule_row else "",
+ "infusionTime": schedule_row.infusion_time.strftime("%Y-%m-%d %H:%M:%S") if schedule_row and schedule_row.infusion_time else "",
+ }
+ return _row_to_dict(row, extra=extra)
+
+
+# ─── create (auto) ────────────────────────────────────────
+
+
+def auto_create_payment(
+ db: Session,
+ *,
+ application_id: str,
+ schedule_id: str,
+ amount: Any,
+ tenant_no: str,
+ user_id: str,
+ dept_id: str,
+) -> Optional[BizPilotPayment]:
+ """输注计划创建时自动调用,生成 dfj 缴费记录"""
+ if amount is None or Decimal(str(amount)) <= 0:
+ return None
+ now = datetime.now()
+ row = BizPilotPayment(
+ id=str(uuid.uuid4()),
+ pilot_application_id=application_id,
+ infusion_schedule_id=schedule_id,
+ biz_type="clinical_application",
+ amount=Decimal(str(amount)),
+ pay_status="dfj",
+ tenant_no=tenant_no,
+ order_num=0,
+ create_by=user_id,
+ create_dept_id=dept_id or "",
+ create_time=now,
+ update_by=user_id,
+ update_time=now,
+ del_flag=0,
+ )
+ db.add(row)
+ return row
+
+
+# ─── list ─────────────────────────────────────────────────
+
+
+def list_payments(
+ db: Session,
+ *,
+ page: int = 1,
+ page_size: int = 10,
+ pay_status: Optional[str] = None,
+ keyword: Optional[str] = None,
+ tenant_no: str = "",
+ user_info=None,
+) -> Tuple[List[Dict[str, Any]], int]:
+ q = (
+ db.query(BizPilotPayment)
+ .filter(
+ BizPilotPayment.del_flag == 0,
+ BizPilotPayment.tenant_no == tenant_no,
+ )
+ )
+ if pay_status and pay_status.strip():
+ q = q.filter(BizPilotPayment.pay_status == pay_status.strip())
+ if keyword and keyword.strip():
+ kw = f"%{keyword.strip()}%"
+ sub = (
+ db.query(BizPilotApplication.id)
+ .filter(
+ BizPilotApplication.del_flag == 0,
+ BizPilotApplication.application_no.like(kw),
+ )
+ .subquery()
+ )
+ q = q.filter(BizPilotPayment.pilot_application_id.in_(sub))
+ if user_info:
+ from app.core.data_scope import apply_data_scope
+ q = apply_data_scope(q, BizPilotPayment, user_info, db, tenant_no)
+ total = q.order_by(None).with_entities(func.count(BizPilotPayment.id)).scalar() or 0
+ page = max(1, page)
+ page_size = min(max(1, page_size), 100)
+ rows = (
+ q.order_by(BizPilotPayment.create_time.desc())
+ .offset((page - 1) * page_size)
+ .limit(page_size)
+ .all()
+ )
+ out = [_enrich_payment(db, r) for r in rows]
+ return out, int(total)
+
+
+# ─── submit receipt (dfj → jfqrz) ────────────────────────
+
+
+def submit_receipt(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "dfj":
+ raise ValueError("仅待缴费状态可提交缴费确认")
+ row.pay_status = "jfqrz"
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+def batch_submit_receipt(
+ db: Session,
+ *,
+ payment_ids: List[str],
+ tenant_no: str,
+ user_id: Optional[str],
+) -> int:
+ ids = [str(i).strip() for i in (payment_ids or []) if str(i).strip()]
+ if not ids:
+ raise ValueError("请选择缴费记录")
+ for pid in ids:
+ row = _get_or_raise(db, payment_id=pid, tenant_no=tenant_no)
+ if row.pay_status != "dfj":
+ continue
+ row.pay_status = "jfqrz"
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ return len(ids)
+
+
+# ─── confirm receipt (jfqrz → yjf) ──────────────────────
+
+
+def confirm_receipt(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+ finance_confirm_attachments: Optional[str] = None,
+ finance_remark: Optional[str] = None,
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "jfqrz":
+ if row.pay_status == "yjf":
+ raise ValueError("该记录已确认收款")
+ raise ValueError("当前状态不可确认收款")
+ row.pay_status = "yjf"
+ if finance_confirm_attachments is not None:
+ row.finance_confirm_attachments = finance_confirm_attachments.strip() or None
+ if finance_remark is not None:
+ row.finance_remark = finance_remark.strip() or None
+ row.finance_confirm_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+# ─── submit refund (yjf → tfqrz) ─────────────────────────
+
+
+def submit_refund(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+ refund_amount: Any,
+ refund_remark: Optional[str] = None,
+ refund_attachments: Optional[str] = None,
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "yjf":
+ raise ValueError("仅已缴费状态可发起退费")
+ if refund_amount is None or Decimal(str(refund_amount)) <= 0:
+ raise ValueError("请填写有效的退费金额")
+ row.pay_status = "tfqrz"
+ row.refund_amount = Decimal(str(refund_amount))
+ row.refund_remark = (refund_remark or "").strip() or None
+ row.refund_attachments = (refund_attachments or "").strip() or None
+ row.refund_apply_by = user_id
+ row.refund_apply_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+def batch_submit_refund(
+ db: Session,
+ *,
+ payment_ids: List[str],
+ tenant_no: str,
+ user_id: Optional[str],
+ refund_amount: Any,
+ refund_remark: Optional[str] = None,
+ refund_attachments: Optional[str] = None,
+) -> int:
+ ids = [str(i).strip() for i in (payment_ids or []) if str(i).strip()]
+ if not ids:
+ raise ValueError("请选择缴费记录")
+ for pid in ids:
+ row = _get_or_raise(db, payment_id=pid, tenant_no=tenant_no)
+ if row.pay_status != "yjf":
+ continue
+ if refund_amount is None or Decimal(str(refund_amount)) <= 0:
+ continue
+ row.pay_status = "tfqrz"
+ row.refund_amount = Decimal(str(refund_amount))
+ row.refund_remark = (refund_remark or "").strip() or None
+ row.refund_attachments = (refund_attachments or "").strip() or None
+ row.refund_apply_by = user_id
+ row.refund_apply_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ return len(ids)
+
+
+# ─── confirm refund (tfqrz → ytf) ───────────────────────
+
+
+def confirm_refund(
+ db: Session,
+ *,
+ payment_id: str,
+ tenant_no: str,
+ user_id: Optional[str],
+ refund_finance_confirm_attachments: Optional[str] = None,
+ refund_finance_remark: Optional[str] = None,
+) -> BizPilotPayment:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ if row.pay_status != "tfqrz":
+ if row.pay_status == "ytf":
+ raise ValueError("该记录已确认退费")
+ raise ValueError("当前状态不可确认退费")
+ row.pay_status = "ytf"
+ if refund_finance_confirm_attachments is not None:
+ row.refund_finance_confirm_attachments = refund_finance_confirm_attachments.strip() or None
+ if refund_finance_remark is not None:
+ row.refund_finance_remark = refund_finance_remark.strip() or None
+ row.refund_finance_confirm_time = datetime.now()
+ row.update_by = user_id
+ row.update_time = datetime.now()
+ db.add(row)
+ db.commit()
+ db.refresh(row)
+ return row
+
+
+# ─── detail ───────────────────────────────────────────────
+
+
+def get_payment_detail(db: Session, *, payment_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
+ row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
+ return _enrich_payment(db, row)
+
+
+def get_payment_by_schedule(db: Session, *, schedule_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
+ row = (
+ db.query(BizPilotPayment)
+ .filter(
+ BizPilotPayment.infusion_schedule_id == schedule_id,
+ BizPilotPayment.tenant_no == tenant_no,
+ BizPilotPayment.del_flag == 0,
+ )
+ .first()
+ )
+ if not row:
+ return None
+ return _enrich_payment(db, row)
+```
+
+- [ ] **Step 2: 验证导入**
+
+Run: `cd backend && source venv/bin/activate && python -c "from app.crud.clinical_payment import list_payments; print('OK')"`
+Expected: `OK`
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add backend/app/crud/clinical_payment.py
+git commit -m "feat: add clinical payment CRUD layer"
+```
+
+---
+
+## Task 4: 后端 API 路由 — clinical_payment.py
+
+**Files:**
+- Create: `backend/app/api/clinical_payment.py`
+- Modify: `backend/app/api/__init__.py`
+- Modify: `backend/app/main.py`
+
+- [ ] **Step 1: 创建 API 路由文件**
+
+创建 `backend/app/api/clinical_payment.py`:
+
+```python
+"""临床应用缴费 API"""
+from fastapi import APIRouter, Depends, Query, Request
+from typing import Optional
+
+router = APIRouter()
+
+
+def _common_params(
+ request: Request,
+ page: int = Query(1, ge=1),
+ pageSize: int = Query(10, ge=1, le=100),
+ keyword: Optional[str] = Query(None),
+):
+ return {
+ "page": page,
+ "page_size": pageSize,
+ "keyword": keyword,
+ "tenant_no": request.state.tenant_no,
+ "user_info": getattr(request.state, "user_info", None),
+ }
+
+
+# ─── 缴费信息页面 ──────────────────────────────────────
+
+
+@router.get("/clinical-payments")
+def list_clinical_payments(
+ payStatus: Optional[str] = Query(None),
+ **kwargs,
+):
+ from app.core.database import get_db
+ from app.crud.clinical_payment import list_payments
+
+ params = _common_params(**kwargs) if kwargs else _common_params(
+ kwargs.get("__request"), kwargs.get("page", 1), kwargs.get("pageSize", 10), kwargs.get("keyword")
+ )
+ # 重新从 request 获取参数
+ # 注意:FastAPI 会自动注入 Query 参数,这里用更简洁的方式
+ pass
+
+
+# 更简洁的写法 — 每个端点独立定义
+```
+
+由于上面的草稿过于冗长,下面用完整的每个端点函数替代。每个端点参照 `backend/app/api/pilot_service.py` 中现有端点的模式(获取 db session、tenant_no、user_info)。
+
+实际实现时参照已有模式:
+- `request.state.tenant_no` 获取租户
+- `request.state.user_info` 获取用户信息
+- `get_db()` 获取数据库会话
+- `_row_to_dict` 系列函数序列化返回
+
+**10 个端点**:
+
+| 端点 | CRUD 函数 |
+|------|----------|
+| `GET /clinical-payments` | `list_payments(pay_status=...)` |
+| `GET /clinical-payments/{payment_id}` | `get_payment_detail(payment_id=...)` |
+| `POST /clinical-payments/{payment_id}/submit-receipt` | `submit_receipt(payment_id=...)` |
+| `POST /clinical-payments/submit-receipt-batch` | `batch_submit_receipt(payment_ids=...)` |
+| `POST /clinical-payments/{payment_id}/confirm-receipt` | `confirm_receipt(payment_id=..., finance_confirm_attachments=..., finance_remark=...)` |
+| `POST /clinical-payments/{payment_id}/submit-refund` | `submit_refund(payment_id=..., refund_amount=..., refund_remark=..., refund_attachments=...)` |
+| `POST /clinical-payments/submit-refund-batch` | `batch_submit_refund(payment_ids=..., refund_amount=..., ...)` |
+| `POST /clinical-payments/{payment_id}/confirm-refund` | `confirm_refund(payment_id=..., refund_finance_confirm_attachments=..., refund_finance_remark=...)` |
+| `GET /clinical-payments/receipt-pending` | `list_payments(pay_status='jfqrz')` |
+| `GET /clinical-payments/receipt-confirmed` | `list_payments(pay_status='yjf')` |
+| `GET /clinical-payments/refund-pending` | `list_payments(pay_status='tfqrz')` |
+| `GET /clinical-payments/refund-confirmed` | `list_payments(pay_status='ytf')` |
+
+- [ ] **Step 2: 在 `__init__.py` 注册路由**
+
+在 `backend/app/api/__init__.py` 添加:
+
+```python
+from app.api.clinical_payment import router as clinical_payment_router
+```
+
+在 `routers` 列表中添加 `"clinical_payment_router"`。
+
+- [ ] **Step 3: 在 `main.py` 挂载路由**
+
+在 `backend/app/main.py` 中现有路由挂载位置添加:
+
+```python
+from app.api import clinical_payment_router
+app.include_router(clinical_payment_router, prefix="/web/admin", tags=["clinical-payment"])
+```
+
+- [ ] **Step 4: 验证路由注册**
+
+Run: `cd backend && source venv/bin/activate && python -c "from app.api.clinical_payment import router; print(f'Routes: {len(router.routes)}')"`
+Expected: `Routes: 12`(或对应数量)
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add backend/app/api/clinical_payment.py backend/app/api/__init__.py backend/app/main.py
+git commit -m "feat: add clinical payment API routes (12 endpoints)"
+```
+
+---
+
+## Task 5: 后端 CRUD 改造 — pilot_application.py 支持 biz_type
+
+**Files:**
+- Modify: `backend/app/crud/pilot_application.py`
+
+关键改动点(参照已读取的代码):
+
+- [ ] **Step 1: `_generate_application_no` 支持 biz_type 参数**
+
+在 line 74,将 `prefix` 根据 `biz_type` 动态选择:
+
+```python
+def _generate_application_no(db: Session, tenant_no: str, biz_type: str = "pilot_service") -> str:
+ today_str = datetime.now().strftime("%Y%m%d")
+ prefix = f"LCSQ{today_str}" if biz_type == "clinical_application" else f"ZSSQ{today_str}"
+ existing = (
+ db.query(BizPilotApplication.application_no)
+ .filter(
+ BizPilotApplication.tenant_no == tenant_no,
+ BizPilotApplication.application_no.like(f"{prefix}%"),
+ BizPilotApplication.del_flag == 0,
+ )
+ .order_by(BizPilotApplication.application_no.desc())
+ .first()
+ )
+ seq = 1
+ if existing and existing[0]:
+ try:
+ seq = int(existing[0][-4:]) + 1
+ except ValueError:
+ seq = 1
+ return f"{prefix}{seq:04d}"
+```
+
+- [ ] **Step 2: `_row_to_dict_application` 添加 bizType 字段**
+
+在 line 96-110 的 dict 中添加:
+
+```python
+"bizType": row.biz_type or "pilot_service",
+```
+
+- [ ] **Step 3: `list_applications` 支持 biz_type 过滤**
+
+在 line 284 的 `list_applications` 函数签名中添加 `biz_type` 参数,并在查询中添加过滤:
+
+```python
+def list_applications(
+ db: Session,
+ ...
+ biz_type: Optional[str] = None, # 新增
+) -> Tuple[List[Dict[str, Any]], int]:
+ ...
+ if biz_type:
+ q = q.filter(BizPilotApplication.biz_type == biz_type)
+```
+
+- [ ] **Step 4: `create_application` 支持 biz_type=clinical_application**
+
+在 line 385 的 `create_application` 函数中:
+- 当 `biz_type == 'clinical_application'` 时,`channel_service_order_id` 不必填,直接从 data 中获取 `channelId`
+- 调用 `_generate_application_no` 时传入 `biz_type`
+
+```python
+def create_application(db, data, tenant_no, user_id, dept_id):
+ biz_type = data.get("bizType") or "pilot_service"
+
+ if biz_type == "pilot_service":
+ channel_service_order_id = data.get("channelServiceOrderId") or ""
+ if not channel_service_order_id:
+ raise ValueError("请选择渠道服务订单")
+ # ... 现有逻辑不变
+ else:
+ # clinical_application
+ channel_service_order_id = ""
+ channel_id = data.get("channelId") or ""
+ channel_name = data.get("channelName") or ""
+
+ application_no = _generate_application_no(db, tenant_no, biz_type)
+
+ row = BizPilotApplication(
+ ...
+ biz_type=biz_type,
+ channel_service_order_id=channel_service_order_id,
+ channel_id=channel_id,
+ channel_name=channel_name,
+ )
+```
+
+- [ ] **Step 5: `create_schedule` 支持临床应用 amount + 自动创建缴费记录**
+
+在 line 1106 的 `create_schedule` 函数中:
+- 保存 `amount` 字段到 `BizPilotInfusionSchedule`
+- 当 `biz_type == 'clinical_application'` 时调用 `auto_create_payment`
+
+```python
+# 在 row = BizPilotInfusionSchedule(...) 中添加:
+amount=data.get("amount") if data.get("amount") else None,
+
+# 在创建工单之后添加:
+if application.biz_type == "clinical_application" and data.get("amount"):
+ from app.crud.clinical_payment import auto_create_payment
+ auto_create_payment(
+ db,
+ application_id=application.id,
+ schedule_id=schedule_id,
+ amount=data["amount"],
+ tenant_no=tenant_no,
+ user_id=user_id,
+ dept_id=dept_id,
+ )
+```
+
+- [ ] **Step 6: `_row_to_dict_infusion_schedule` 添加 amount 字段**
+
+在输注计划序列化函数中添加:
+
+```python
+"amount": float(row.amount) if row.amount else None,
+```
+
+- [ ] **Step 7: 验证导入和基本逻辑**
+
+Run: `cd backend && source venv/bin/activate && python -c "from app.crud.pilot_application import create_application; print('OK')"`
+Expected: `OK`
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add backend/app/crud/pilot_application.py
+git commit -m "feat: pilot_application CRUD supports biz_type for clinical application"
+```
+
+---
+
+## Task 6: 后端 API 改造 — pilot_application.py 路由
+
+**Files:**
+- Modify: `backend/app/api/pilot_application.py`
+
+- [ ] **Step 1: list 路由添加 bizType 过滤**
+
+在申请单列表路由中,从 Query 参数获取 `bizType` 并传给 `list_applications`。
+
+- [ ] **Step 2: create 路由传递 bizType**
+
+在创建申请单路由中,从前端传入的 data 中透传 `bizType` 字段。
+
+- [ ] **Step 3: 输注计划创建路由传递 amount**
+
+在创建输注计划路由中,透传 `amount` 字段。
+
+- [ ] **Step 4: 验证 API 可访问**
+
+Run: `cd backend && source venv/bin/activate && python -c "from app.api.pilot_application import router; print('OK')"`
+Expected: `OK`
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add backend/app/api/pilot_application.py
+git commit -m "feat: pilot_application API supports bizType filter and amount field"
+```
+
+---
+
+## Task 7: 前端路由 + 菜单
+
+**Files:**
+- Modify: `web-admin/src/router/modules/sqxp.ts`
+
+- [ ] **Step 1: 在 sqxp.ts 末尾(`]` 之前)添加临床应用管理一级菜单**
+
+```typescript
+ {
+ id: '90000000000000000000000000000001',
+ name: 'ClinicalApplication',
+ path: '/clinical-application',
+ component: '/index/index',
+ meta: {
+ title: '临床应用管理',
+ icon: 'ri:heart-pulse-line',
+ keepAlive: false,
+ roles: ['admin']
+ },
+ children: [
+ {
+ id: '90000000000000000000000000000002',
+ name: 'ClinicalApplication',
+ path: 'clinical-application',
+ component: '/pilot-service/pilot-application/index',
+ meta: {
+ title: '临床申请单',
+ keepAlive: true,
+ menuCode: 'clinical-application:application:list'
+ },
+ props: { bizType: 'clinical_application' }
+ },
+ {
+ id: '90000000000000000000000000000003',
+ name: 'ClinicalCheckupManagement',
+ path: 'clinical-checkup-management',
+ component: '/pilot-service/checkup-management/index',
+ meta: {
+ title: '体检管理',
+ keepAlive: true,
+ menuCode: 'clinical-application:checkup-management:list'
+ },
+ props: { bizType: 'clinical_application' }
+ },
+ {
+ id: '90000000000000000000000000000004',
+ name: 'ClinicalInfusionScheduleManagement',
+ path: 'clinical-infusion-schedule-management',
+ component: '/pilot-service/infusion-schedule-management/index',
+ meta: {
+ title: '输注计划管理',
+ keepAlive: true,
+ menuCode: 'clinical-application:infusion-schedule-management:list'
+ },
+ props: { bizType: 'clinical_application' }
+ },
+ {
+ id: '90000000000000000000000000000005',
+ name: 'ClinicalPaymentInfo',
+ path: 'clinical-payment-info',
+ component: '/pilot-service/clinical-payment-info/index',
+ meta: {
+ title: '缴费信息',
+ keepAlive: true,
+ menuCode: 'clinical-application:payment-info:list'
+ }
+ },
+ {
+ id: '90000000000000000000000000000006',
+ name: 'ClinicalPaymentConfirm',
+ path: 'clinical-payment-confirm',
+ component: '/pilot-service/clinical-payment-confirm/index',
+ meta: {
+ title: '缴费确认(财务)',
+ keepAlive: true,
+ menuCode: 'clinical-application:payment-confirm:list'
+ }
+ },
+ {
+ id: '90000000000000000000000000000007',
+ name: 'ClinicalRefundConfirm',
+ path: 'clinical-refund-confirm',
+ component: '/pilot-service/clinical-refund-confirm/index',
+ meta: {
+ title: '退费确认(财务)',
+ keepAlive: true,
+ menuCode: 'clinical-application:refund-confirm:list'
+ }
+ },
+ {
+ id: '90000000000000000000000000000008',
+ name: 'ClinicalOutboundReleaseReview',
+ path: 'clinical-outbound-release-review',
+ component: '/pilot-service/outbound-release-review/index',
+ meta: {
+ title: '出库放行审核',
+ keepAlive: true,
+ menuCode: 'clinical-application:outbound-release-review:list'
+ },
+ props: { bizType: 'clinical_application' }
+ }
+ ]
+ }
+```
+
+- [ ] **Step 2: 验证路由文件语法**
+
+Run: `cd web-admin && npx tsc --noEmit src/router/modules/sqxp.ts 2>&1 | head -5`
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add web-admin/src/router/modules/sqxp.ts
+git commit -m "feat: add clinical application menu and routes"
+```
+
+---
+
+## Task 8: 前端 API — clinical payment
+
+**Files:**
+- Modify: `web-admin/src/api/business.ts`
+
+- [ ] **Step 1: 在 business.ts 末尾添加 clinical payment API 函数**
+
+```typescript
+// ─── 临床应用缴费 ─────────────────────────────────────
+
+export function fetchClinicalPayments(params: any) {
+ return http.get('/admin/clinical-payments', params)
+}
+
+export function fetchClinicalPaymentDetail(id: string) {
+ return http.get(`/admin/clinical-payments/${id}`)
+}
+
+export function submitClinicalReceipt(id: string) {
+ return http.post(`/admin/clinical-payments/${id}/submit-receipt`)
+}
+
+export function batchSubmitClinicalReceipt(ids: string[]) {
+ return http.post('/admin/clinical-payments/submit-receipt-batch', { ids })
+}
+
+export function confirmClinicalReceipt(id: string, data: any) {
+ return http.post(`/admin/clinical-payments/${id}/confirm-receipt`, data)
+}
+
+export function submitClinicalRefund(id: string, data: any) {
+ return http.post(`/admin/clinical-payments/${id}/submit-refund`, data)
+}
+
+export function batchSubmitClinicalRefund(ids: string[], data: any) {
+ return http.post('/admin/clinical-payments/submit-refund-batch', { ...data, ids })
+}
+
+export function confirmClinicalRefund(id: string, data: any) {
+ return http.post(`/admin/clinical-payments/${id}/confirm-refund`, data)
+}
+
+export function fetchClinicalReceiptPending(params: any) {
+ return http.get('/admin/clinical-payments/receipt-pending', params)
+}
+
+export function fetchClinicalReceiptConfirmed(params: any) {
+ return http.get('/admin/clinical-payments/receipt-confirmed', params)
+}
+
+export function fetchClinicalRefundPending(params: any) {
+ return http.get('/admin/clinical-payments/refund-pending', params)
+}
+
+export function fetchClinicalRefundConfirmed(params: any) {
+ return http.get('/admin/clinical-payments/refund-confirmed', params)
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add web-admin/src/api/business.ts
+git commit -m "feat: add clinical payment API functions"
+```
+
+---
+
+## Task 9: 前端页面 — 缴费信息
+
+**Files:**
+- Create: `web-admin/src/views/pilot-service/clinical-payment-info/index.vue`
+
+参照 `web-admin/src/views/cell-storage/payment-info/index.vue` 的结构。
+
+- [ ] **Step 1: 创建页面**
+
+核心结构:
+- Tab 切换:`dfj` 待缴费 / `yjf` 已缴费
+- 列表表格列:申请单号、患者姓名、渠道名称、细胞类型/规格、输注时间、缴费金额、缴费状态(`payStatusLabel` + `payStatusTagType`)、操作
+- 待缴费 Tab 操作:单条缴费确认、批量缴费确认
+- 已缴费 Tab 操作:单条退费(弹窗填退费金额+备注+附件)、批量退费
+- 复用 `payStatusLabel`、`payStatusTagType`、`formatMoneyDisplay`
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add web-admin/src/views/pilot-service/clinical-payment-info/
+git commit -m "feat: add clinical payment info page"
+```
+
+---
+
+## Task 10: 前端页面 — 缴费确认(财务)
+
+**Files:**
+- Create: `web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue`
+
+参照 `web-admin/src/views/cell-storage/payment-confirm/index.vue`。
+
+- [ ] **Step 1: 创建页面**
+
+核心结构:
+- Tab:待确认(`jfqrz`)/ 已确认(`yjf`)
+- 确认收款弹窗:复用 `ProtocolFinanceConfirmFields` 组件(备注 + 附件上传)
+- 表格列同缴费信息页
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add web-admin/src/views/pilot-service/clinical-payment-confirm/
+git commit -m "feat: add clinical payment confirm page (finance)"
+```
+
+---
+
+## Task 11: 前端页面 — 退费确认(财务)
+
+**Files:**
+- Create: `web-admin/src/views/pilot-service/clinical-refund-confirm/index.vue`
+
+参照 `web-admin/src/views/cell-storage/refund-confirm/index.vue`。
+
+- [ ] **Step 1: 创建页面**
+
+核心结构:
+- Tab:待确认(`tfqrz`)/ 已确认(`ytf`)
+- 确认退费弹窗:展示退费申请信息(只读)+ 财务确认表单(复用 `ProtocolFinanceConfirmFields`)
+- 表格列同缴费信息页
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add web-admin/src/views/pilot-service/clinical-refund-confirm/
+git commit -m "feat: add clinical refund confirm page (finance)"
+```
+
+---
+
+## Task 12: 前端改造 — 申请单页面支持 bizType
+
+**Files:**
+- Modify: `web-admin/src/views/pilot-service/pilot-application/index.vue`
+- Modify: `web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue`
+
+- [ ] **Step 1: index.vue 接收 bizType prop**
+
+在 `
+
+
diff --git a/web-admin/src/views/basic-info/cell-sample-type/index.vue b/web-admin/src/views/basic-info/cell-sample-type/index.vue
index a7ce98a6e..302dfba0e 100644
--- a/web-admin/src/views/basic-info/cell-sample-type/index.vue
+++ b/web-admin/src/views/basic-info/cell-sample-type/index.vue
@@ -2,16 +2,13 @@
-
- 细胞样本类型
- 维护样本类型编码、名称、存储年限与存储价格。
-
-
-
细胞样本类型列表
+ 细胞样本基础信息维护
+
+
@@ -113,17 +110,32 @@
/>
- 查看
- 编辑
- 删除
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -209,6 +221,12 @@
code-label="类型编码"
:sections="viewSections"
/>
+
+
+
+
+
+
@@ -223,7 +241,10 @@
View,
Collection,
RefreshLeft,
- Search
+ Search,
+ More,
+ Edit,
+ Delete
} from '@element-plus/icons-vue'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
import {
@@ -233,9 +254,12 @@
fetchDeleteCellSampleType
} from '@/api/admin'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
+ import SampleCodeTab from './SampleCodeTab.vue'
defineOptions({ name: 'BizCellSampleType' })
+ const route = useRoute()
+ const activeTab = ref('type')
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
const tableData = ref([])
const selectedRows = ref([])
@@ -382,6 +406,13 @@
}
}
+ function handleDropdownCommand(command: string, row: any) {
+ switch (command) {
+ case 'edit': handleEdit(row); break
+ case 'delete': handleDelete(row); break
+ }
+ }
+
async function handleDelete(row: Api.BizCellSampleType.CellSampleTypeItem) {
await ElMessageBox.confirm('确定要删除该类型吗?', '提示', { type: 'warning' })
await fetchDeleteCellSampleType(row.id)
@@ -413,6 +444,10 @@
)
onMounted(() => {
+ const tab = route.query.tab
+ if (tab === 'sample-code') {
+ activeTab.value = 'sample-code'
+ }
loadData()
})
diff --git a/web-admin/src/views/basic-info/collection-protocol-no/index.vue b/web-admin/src/views/basic-info/collection-protocol-no/index.vue
index 81b515cb7..d9499aee7 100644
--- a/web-admin/src/views/basic-info/collection-protocol-no/index.vue
+++ b/web-admin/src/views/basic-info/collection-protocol-no/index.vue
@@ -1,781 +1,18 @@
-
-
+
-
-
- 采集协议号
- 维护采集协议号池及是否下发标记。
-
-
-
-
-
采集协议号列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ row.issued === 1 ? '是' : '否' }}
-
-
-
- {{ resolveIssuedUserName(row) }}
-
-
- {{ row.issuedTime || '-' }}
-
-
- {{ row.remark || '-' }}
-
-
-
-
- 查看
- 编辑
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 序号位数补零后的总位数
-
-
-
- {{ batchGenPreview }}
-
-
-
-
-
-
- 取消
-
-
-
- 生成
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 已选 {{ selectedRows.length }} 条,下发后"是否下发"将变为"是"
-
-
-
-
-
-
- 取消
-
-
-
- 确定下发
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ formData.issuedTime }}
-
-
-
-
-
-
-
-
-
- 取消
-
-
-
- 确定
-
-
-
-
-
-
-
+
-
-
diff --git a/web-admin/src/views/basic-info/collection-region/CollectionProtocolNoTab.vue b/web-admin/src/views/basic-info/collection-region/CollectionProtocolNoTab.vue
new file mode 100644
index 000000000..91907b981
--- /dev/null
+++ b/web-admin/src/views/basic-info/collection-region/CollectionProtocolNoTab.vue
@@ -0,0 +1,795 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.issued === 1 ? '是' : '否' }}
+
+
+
+ {{ resolveIssuedUserName(row) }}
+
+
+ {{ row.issuedTime || '-' }}
+
+
+ {{ row.remark || '-' }}
+
+
+
+
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 序号位数补零后的总位数
+
+
+
+ {{ batchGenPreview }}
+
+
+
+
+
+
+ 取消
+
+
+
+ 生成
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 已选 {{ selectedRows.length }} 条,下发后"是否下发"将变为"是"
+
+
+
+
+
+
+ 取消
+
+
+
+ 确定下发
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formData.issuedTime }}
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确定
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/basic-info/collection-region/index.vue b/web-admin/src/views/basic-info/collection-region/index.vue
index 46b46eaaf..776fd3d35 100644
--- a/web-admin/src/views/basic-info/collection-region/index.vue
+++ b/web-admin/src/views/basic-info/collection-region/index.vue
@@ -2,16 +2,13 @@
-
- 采集地区及医院
- 维护采集地区;医院在子表中维护,可通过「地区医院配置」管理。
-
-
-
采集地区列表
+ 细胞采集基础信息维护
+
+
@@ -117,20 +114,35 @@
/>
- 查看
- 地区医院配置
- 编辑
- 删除
+
+
+ 地区医院配置
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -354,6 +366,12 @@
:meta="viewRow?.createTime ? '创建时间:' + viewRow.createTime : ''"
:sections="viewSections"
/>
+
+
+
+
+
+
@@ -361,7 +379,7 @@
diff --git a/web-admin/src/views/basic-info/customer/index.vue b/web-admin/src/views/basic-info/customer/index.vue
index 3db72e371..562d36ffe 100644
--- a/web-admin/src/views/basic-info/customer/index.vue
+++ b/web-admin/src/views/basic-info/customer/index.vue
@@ -2,11 +2,6 @@
-
- 客户管理
- 维护客户主数据;同一租户下身份证号不可重复。
-
-
客户列表
@@ -139,17 +134,32 @@
/>
- 查看
- 编辑
- 删除
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -249,7 +259,7 @@
-
-
diff --git a/web-admin/src/views/cell-storage/fullmoon-todo/index.vue b/web-admin/src/views/cell-storage/fullmoon-todo/index.vue
index 2b413aa79..a465c1226 100644
--- a/web-admin/src/views/cell-storage/fullmoon-todo/index.vue
+++ b/web-admin/src/views/cell-storage/fullmoon-todo/index.vue
@@ -1,1289 +1,18 @@
-
-
+
-
-
- 满月待办管理
-
- 展示全部已生效采集协议;已满月且未办保险的行以红色背景标识,其余为正常底色。
-
-
-
-
-
-
待办列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持按协议号、姓名模糊检索;待办状态默认未办保险,满月日期默认近 30 天。
-
-
- 重置
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
-
-
-
-
-
-
-
-
- {{ row.todoStatus == 1 ? '已办保险' : '未办保险' }}
-
-
-
-
-
-
- {{ row.insuranceSubscribed == 1 ? '是' : '否' }}
-
-
-
-
-
-
-
- 查看
-
- 办理保险
-
-
-
-
-
-
-
-
-
-
-
-
-
- 将为已选
-
{{ batchEditingRows.length }}
- 条记录统一填写保险信息(适用于团体险等场景)。
-
{{
- batchProtocolSummary
- }}
-
-
-
-
-
-
- 取消
-
-
- 确定
-
-
-
-
-
-
-
-
-
-
+
-
-
diff --git a/web-admin/src/views/cell-storage/material-stock-log/index.vue b/web-admin/src/views/cell-storage/material-stock-log/index.vue
index adaf4bb22..f864f1013 100644
--- a/web-admin/src/views/cell-storage/material-stock-log/index.vue
+++ b/web-admin/src/views/cell-storage/material-stock-log/index.vue
@@ -1,303 +1,18 @@
-
-
+
-
-
- 市场物料变动记录
- 查看市场物料的入库、出库历史变动记录
-
-
-
-
-
变动记录列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
按物料名称/编码查询,可按变动类型和日期范围筛选。
-
- 重置
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ row.changeType === 'rk' ? '入库' : '出库' }}
-
-
-
-
-
-
- {{ formatChangeQty(row).text }}
-
-
-
-
-
-
-
- {{ row.changeType === 'ck' ? row.receiver || '-' : '-' }}
-
-
-
-
- {{ row.changeType === 'ck' ? row.purpose || '-' : '-' }}
-
-
-
-
- {{ row.createByName || row.createBy || '-' }}
-
-
-
-
- {{ row.remark || '-' }}
-
-
-
-
-
-
- 查看
-
-
-
-
-
-
-
-
-
-
-
+
-
-
diff --git a/web-admin/src/views/cell-storage/material/MaterialStockLogTab.vue b/web-admin/src/views/cell-storage/material/MaterialStockLogTab.vue
new file mode 100644
index 000000000..2cc2be925
--- /dev/null
+++ b/web-admin/src/views/cell-storage/material/MaterialStockLogTab.vue
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
按物料名称/编码查询,可按变动类型和日期范围筛选。
+
+ 重置
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.changeType === 'rk' ? '入库' : '出库' }}
+
+
+
+
+
+
+ {{ formatChangeQty(row).text }}
+
+
+
+
+
+
+
+ {{ row.changeType === 'ck' ? row.receiver || '-' : '-' }}
+
+
+
+
+ {{ row.changeType === 'ck' ? row.purpose || '-' : '-' }}
+
+
+
+
+ {{ row.createByName || row.createBy || '-' }}
+
+
+
+
+ {{ row.remark || '-' }}
+
+
+
+
+
+
+ 查看
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/material/index.vue b/web-admin/src/views/cell-storage/material/index.vue
index 01f29a7c6..791e3d8bc 100644
--- a/web-admin/src/views/cell-storage/material/index.vue
+++ b/web-admin/src/views/cell-storage/material/index.vue
@@ -2,16 +2,13 @@
-
- 市场物料库
- 管理市场部门的物料信息,支持入库、出库操作
-
-
-
物料列表
+ 市场物料库
+
+
@@ -160,27 +157,38 @@
/>
- 入库
- 出库
- 查看
- 编辑
- 删除
+
+
+ 入库
+
+
+ 出库
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -807,6 +815,12 @@
:meta="viewRow?.createTime || ''"
:sections="viewSections"
/>
+
+
+
+
+
+
@@ -825,7 +839,8 @@
Upload,
Download,
Edit,
- Delete
+ Delete,
+ More
} from '@element-plus/icons-vue'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
import { fetchDictDataOptions, fetchItemUnitList, fetchUserList } from '@/api/admin'
@@ -840,9 +855,12 @@
fetchBatchStockOutMaterial
} from '@/api/material'
import type { Material } from '@/api/material'
+ import MaterialStockLogTab from './MaterialStockLogTab.vue'
defineOptions({ name: 'CellStorageMaterial' })
+ const route = useRoute()
+ const activeTab = ref('list')
const searchForm = reactive({ keyword: '', category: '', status: '' })
const tableRef = ref()
const tableData = ref([])
@@ -1216,6 +1234,14 @@
viewVisible.value = true
}
+ function handleDropdownCommand(command: string, row: any) {
+ switch (command) {
+ case 'view': handleView(row); break
+ case 'edit': handleEdit(row); break
+ case 'delete': handleDelete(row); break
+ }
+ }
+
async function handleDelete(row: Material) {
await ElMessageBox.confirm('确定要删除该物料吗?', '提示', { type: 'warning' })
await fetchDeleteMaterial(row.id)
@@ -1520,6 +1546,10 @@
)
onMounted(() => {
+ const tab = route.query.tab
+ if (tab === 'stock-log') {
+ activeTab.value = 'stock-log'
+ }
loadDictOptions()
loadData()
})
diff --git a/web-admin/src/views/cell-storage/payment-confirm/RefundConfirmTab.vue b/web-admin/src/views/cell-storage/payment-confirm/RefundConfirmTab.vue
new file mode 100644
index 000000000..be4038b8e
--- /dev/null
+++ b/web-admin/src/views/cell-storage/payment-confirm/RefundConfirmTab.vue
@@ -0,0 +1,632 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持协议号、客户姓名、身份证号、新生儿姓名、新生儿身份证号模糊检索;默认展示近 30
+ 天内应缴费的明细。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.motherName || '-' }}
+
+
+ {{ maskIdCard(row.motherIdCard) }}
+
+
+ {{ row.newbornName || '-' }}
+
+
+ {{ maskIdCard(row.newbornIdCard) }}
+
+
+
+ {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
+
+
+
+ {{ payModeLabel(String(row.payMode || '')) }}
+
+
+ {{ row.installmentNo ?? '-' }}
+
+
+ {{
+ formatPaymentDueDate(row.installmentDueDate)
+ }}
+
+
+ {{ formatPaymentAmount(row.dueAmount) }}
+
+
+ {{ formatPaymentAmount(row.transferAmount) }}
+
+
+ {{
+ transferTypeLabel(String(row.transferType || ''))
+ }}
+
+
+ {{ row.transferTime || '-' }}
+
+
+ {{ formatPaymentAmount(row.refundAmount) }}
+
+
+ {{ row.refundRemark || '-' }}
+
+
+
+
+ {{ activeTab === 'pending' ? '退费确认中' : '已退费' }}
+
+
+
+
+
+ {{ row.refundFinanceRemark || '-' }}
+
+
+
+
+
+ 确认退费
+
+ 查看确认信息
+
+
+
+
+
+
+
+
+
+ 财务确认
+
+
+
+
+
+
+ 取消
+
+
+
+ 确认
+
+
+
+
+
+
+
+ 财务确认信息
+
+
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/payment-confirm/index.vue b/web-admin/src/views/cell-storage/payment-confirm/index.vue
index fca003841..bb096c6f0 100644
--- a/web-admin/src/views/cell-storage/payment-confirm/index.vue
+++ b/web-admin/src/views/cell-storage/payment-confirm/index.vue
@@ -1,35 +1,49 @@
-
+
-
- 缴费确认
-
- 待确认:处理「缴费确认中」明细;已确认:查看财务确认收款记录及当时填写的备注与附件。
-
-
-
-
-
-
-
- {{ tab.label }}
-
-
-
-
-
-
-
缴费确认列表
+
+
+
+
+
+
+
+
+
@@ -369,6 +383,9 @@
+
+
+
@@ -376,7 +393,7 @@
diff --git a/web-admin/src/views/cell-storage/protocol-audit/FullmoonTodoTab.vue b/web-admin/src/views/cell-storage/protocol-audit/FullmoonTodoTab.vue
new file mode 100644
index 000000000..af28ace64
--- /dev/null
+++ b/web-admin/src/views/cell-storage/protocol-audit/FullmoonTodoTab.vue
@@ -0,0 +1,1276 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持按协议号、姓名模糊检索;待办状态默认未办保险,满月日期默认近 30 天。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
+
+
+
+
+
+
+
+
+ {{ row.todoStatus == 1 ? '已办保险' : '未办保险' }}
+
+
+
+
+
+
+ {{ row.insuranceSubscribed == 1 ? '是' : '否' }}
+
+
+
+
+
+
+
+ 查看
+
+ 办理保险
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 将为已选
+
{{ batchEditingRows.length }}
+ 条记录统一填写保险信息(适用于团体险等场景)。
+
{{
+ batchProtocolSummary
+ }}
+
+
+
+
+
+
+ 取消
+
+
+ 确定
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/protocol-audit/PaymentInfoTab.vue b/web-admin/src/views/cell-storage/protocol-audit/PaymentInfoTab.vue
new file mode 100644
index 000000000..512c24a1d
--- /dev/null
+++ b/web-admin/src/views/cell-storage/protocol-audit/PaymentInfoTab.vue
@@ -0,0 +1,852 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持按协议号模糊检索;默认展示近 30 天内应缴费的明细。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.motherName || '-' }}
+
+
+
+
+ {{ maskIdCard(row.motherIdCard) }}
+
+
+
+
+ {{ row.newbornName || '-' }}
+
+
+
+
+ {{ maskIdCard(row.newbornIdCard) }}
+
+
+
+
+ {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
+
+
+
+
+ {{ payModeLabel(String(row.payMode || '')) }}
+
+
+
+
+
+ {{ formatDate(String(row.installmentDueDate || '')) }}
+
+
+
+
+ {{ formatAmount(row.dueAmount) }}
+
+
+
+
+ {{ formatAmount(row.transferAmount) }}
+
+
+
+
+ {{ transferTypeLabel(String(row.transferType || '')) }}
+
+
+
+
+ {{ row.transferTime || '-' }}
+
+
+
+
+
+ {{ payStatusLabel(String(row.payStatus || '')) }}
+
+
+
+
+
+
+
+ 查看
+
+
+ 缴费确认
+
+
+ 退费确认
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确认提交
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue b/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue
new file mode 100644
index 000000000..1fa8c0117
--- /dev/null
+++ b/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue
@@ -0,0 +1,582 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。
+
+ 重置
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
+
+
+
+
+
+
+
+
+ {{
+ newbornSexLabel(String(row.newbornSex || ''))
+ }}
+
+
+ {{
+ deliveryModeLabel(String(row.deliveryMode || ''))
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 审核
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue b/web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue
new file mode 100644
index 000000000..48f7c7488
--- /dev/null
+++ b/web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内接收的样本。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
+
+
+
+
+
+
+
+
+
+
+ {{
+ row.acceptConclusionLabel ||
+ (row.acceptConclusion === 'js'
+ ? '接收'
+ : row.acceptConclusion === 'bj'
+ ? '拒收'
+ : '待接收确认')
+ }}
+
+
+
+
+
+
+ {{ row.smsNotified == 1 ? '已通知' : '未通知' }}
+
+
+
+
+
+
+ 查看
+
+
+ 提取短信
+
+
+ {{ row.smsNotified == 1 ? '标记未通知' : '标记已通知' }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/protocol-audit/StorageCertTab.vue b/web-admin/src/views/cell-storage/protocol-audit/StorageCertTab.vue
new file mode 100644
index 000000000..d826410b5
--- /dev/null
+++ b/web-admin/src/views/cell-storage/protocol-audit/StorageCertTab.vue
@@ -0,0 +1,879 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持按协议号、姓名模糊检索;制作状态默认未制作,入库日期默认近 30 天。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
+
+
+
+
+ {{ row.motherPhone || '—' }}
+
+
+ {{ maskIdCard(row.motherIdCard) }}
+
+
+
+
+ {{ formatStorageInDate(row.storageInDate) }}
+
+
+
+
+ {{ row.makeStatus == '1' ? '已制作' : '未制作' }}
+
+
+
+
+ {{ formatStorageInDate(row.makeDate) }}
+
+
+
+
+ 上传储存证
+
+
+ 查看
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持图片、视频、PDF、Word、Excel等文件
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 确定
+
+
+
+
+
+
+
![预览]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/protocol-audit/StorageEntryInfoTab.vue b/web-admin/src/views/cell-storage/protocol-audit/StorageEntryInfoTab.vue
new file mode 100644
index 000000000..44a6da135
--- /dev/null
+++ b/web-admin/src/views/cell-storage/protocol-audit/StorageEntryInfoTab.vue
@@ -0,0 +1,345 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内入库的样本。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
+
+
+
+
+
+
+
+
+
+
+ {{ row.storageStatusLabel }}
+
+
+
+
+
+
+ {{ row.smsNotified == 1 ? '已通知' : '未通知' }}
+
+
+
+
+
+
+ 查看
+
+
+ 提取短信
+
+
+ {{ row.smsNotified == 1 ? '标记未通知' : '标记已通知' }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/protocol-audit/index.vue b/web-admin/src/views/cell-storage/protocol-audit/index.vue
index d18b73df6..634ca1ed2 100644
--- a/web-admin/src/views/cell-storage/protocol-audit/index.vue
+++ b/web-admin/src/views/cell-storage/protocol-audit/index.vue
@@ -1,573 +1,225 @@
-
-
+
-
- 采集协议单管理
- 查看与管理采集协议单及审核数据。
-
-
-
-
协议单列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。
-
- 重置
- 搜索
-
-
-
-
-
-
-
-
-
+
-
+
+
+ {{ item.title }}
+ {{ item.desc }}
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/cell-storage/protocol/index.vue b/web-admin/src/views/cell-storage/protocol/index.vue
index 54a9f3c0f..ca8ca4e55 100644
--- a/web-admin/src/views/cell-storage/protocol/index.vue
+++ b/web-admin/src/views/cell-storage/protocol/index.vue
@@ -2,11 +2,6 @@
-
- 采集协议单录入
- 录入与管理采集协议单主数据。
-
-
协议单列表
@@ -241,21 +236,32 @@
/>
- 查看
- 编辑
- 删除
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -295,7 +301,7 @@
-
-
diff --git a/web-admin/src/views/cell-storage/sample-reception-info/index.vue b/web-admin/src/views/cell-storage/sample-reception-info/index.vue
index e9ede4904..7e0001efd 100644
--- a/web-admin/src/views/cell-storage/sample-reception-info/index.vue
+++ b/web-admin/src/views/cell-storage/sample-reception-info/index.vue
@@ -1,363 +1,18 @@
-
+
-
-
- 样本接收信息
- 查看样本接收信息,提取短信通知内容并管理通知状态。
-
-
-
-
-
样本接收列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内接收的样本。
-
-
- 重置
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
- {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
-
-
-
-
-
-
-
-
-
-
- {{
- row.acceptConclusionLabel ||
- (row.acceptConclusion === 'js'
- ? '接收'
- : row.acceptConclusion === 'bj'
- ? '拒收'
- : '待接收确认')
- }}
-
-
-
-
-
-
- {{ row.smsNotified == 1 ? '已通知' : '未通知' }}
-
-
-
-
-
-
- 查看
-
-
- 提取短信
-
-
- {{ row.smsNotified == 1 ? '标记未通知' : '标记已通知' }}
-
-
-
-
-
-
-
-
-
-
+
diff --git a/web-admin/src/views/cell-storage/storage-cert/index.vue b/web-admin/src/views/cell-storage/storage-cert/index.vue
index 0ea508298..6a9d9744d 100644
--- a/web-admin/src/views/cell-storage/storage-cert/index.vue
+++ b/web-admin/src/views/cell-storage/storage-cert/index.vue
@@ -1,890 +1,18 @@
-
-
+
-
-
- 储存证管理
- 管理细胞储存协议的储存证制作与查看。
-
-
-
-
-
储存证列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持按协议号、姓名模糊检索;制作状态默认未制作,入库日期默认近 30 天。
-
-
- 重置
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
- {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
-
-
-
-
- {{ row.motherPhone || '—' }}
-
-
- {{ maskIdCard(row.motherIdCard) }}
-
-
-
-
- {{ formatStorageInDate(row.storageInDate) }}
-
-
-
-
- {{ row.makeStatus == '1' ? '已制作' : '未制作' }}
-
-
-
-
- {{ formatStorageInDate(row.makeDate) }}
-
-
-
-
- 上传储存证
-
-
- 查看
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持图片、视频、PDF、Word、Excel等文件
-
-
-
-
-
-
-
-
-
-
-
-
-
- 取消
-
-
- 确定
-
-
-
-
-
-
-
![预览]()
-
-
-
-
-
-
-
-
-
-
+
-
-
diff --git a/web-admin/src/views/cell-storage/storage-entry-info/index.vue b/web-admin/src/views/cell-storage/storage-entry-info/index.vue
index d653171b9..f6b3d5e67 100644
--- a/web-admin/src/views/cell-storage/storage-entry-info/index.vue
+++ b/web-admin/src/views/cell-storage/storage-entry-info/index.vue
@@ -1,356 +1,18 @@
-
+
-
-
- 样本入库信息
- 查看样本入库信息,提取短信通知内容并管理通知状态。
-
-
-
-
-
样本入库列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内入库的样本。
-
-
- 重置
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
- {{ protocolStatusLabel(String(row.protocolStatus || '')) }}
-
-
-
-
-
-
-
-
-
-
- {{ row.storageStatusLabel }}
-
-
-
-
-
-
- {{ row.smsNotified == 1 ? '已通知' : '未通知' }}
-
-
-
-
-
-
- 查看
-
-
- 提取短信
-
-
- {{ row.smsNotified == 1 ? '标记未通知' : '标记已通知' }}
-
-
-
-
-
-
-
-
-
-
+
diff --git a/web-admin/src/views/dashboard/console/index.vue b/web-admin/src/views/dashboard/console/index.vue
index c0575130f..a6bccbc51 100644
--- a/web-admin/src/views/dashboard/console/index.vue
+++ b/web-admin/src/views/dashboard/console/index.vue
@@ -1,11 +1,6 @@
-
-
工作台
-
以下为固定参考页,新增管理端界面请对照布局与类名;视觉以文档为准。
-
-
快速入口
diff --git a/web-admin/src/views/operations/clean-room/index.vue b/web-admin/src/views/operations/clean-room/index.vue
index 7ff4ec7dd..fa8301300 100644
--- a/web-admin/src/views/operations/clean-room/index.vue
+++ b/web-admin/src/views/operations/clean-room/index.vue
@@ -2,11 +2,6 @@
-
- 洁净室管理
- 维护洁净室信息及人员进出记录。
-
-
洁净室列表
@@ -113,22 +108,33 @@
/>
- 查看
-
- 编辑
-
- 人员进出
-
- 删除
-
+
+ 人员进出
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -477,6 +483,7 @@
View,
Edit,
Delete,
+ More,
User,
OfficeBuilding,
RefreshLeft,
@@ -656,6 +663,14 @@
viewVisible.value = true
}
+ function handleDropdownCommand(command: string, row: any) {
+ switch (command) {
+ case 'view': handleView(row); break
+ case 'edit': handleEdit(row); break
+ case 'delete': handleDelete(row); break
+ }
+ }
+
async function handleDelete(row: any) {
await ElMessageBox.confirm('确定要删除该洁净室吗?', '提示', { type: 'warning' })
await fetchDeleteCleanRoom(row.id)
diff --git a/web-admin/src/views/operations/demo/index.vue b/web-admin/src/views/operations/demo/index.vue
index 777a156f4..921e9d828 100644
--- a/web-admin/src/views/operations/demo/index.vue
+++ b/web-admin/src/views/operations/demo/index.vue
@@ -1,11 +1,6 @@
-
-
选择项
diff --git a/web-admin/src/views/operations/exhibition-appointment/index.vue b/web-admin/src/views/operations/exhibition-appointment/index.vue
index bba11239a..4beb71fee 100644
--- a/web-admin/src/views/operations/exhibition-appointment/index.vue
+++ b/web-admin/src/views/operations/exhibition-appointment/index.vue
@@ -2,11 +2,6 @@
-
- 展馆预约
- 管理展馆来访预约信息,包括来访单位、联系人及到访日期等。
-
-
预约列表
@@ -133,17 +128,32 @@
/>
- 查看
- 编辑
- 删除
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -239,7 +249,7 @@
-
-
diff --git a/web-admin/src/views/pilot-service/cell-product-spec/index.vue b/web-admin/src/views/pilot-service/cell-product-spec/index.vue
index e22a29934..c0428847e 100644
--- a/web-admin/src/views/pilot-service/cell-product-spec/index.vue
+++ b/web-admin/src/views/pilot-service/cell-product-spec/index.vue
@@ -1,389 +1,18 @@
-
-
+
-
-
- 细胞产品规格管理
- 维护细胞产品的规格编码与名称。
-
-
-
-
-
规格列表
-
-
-
-
-
-
-
-
-
-
-
按编码、名称模糊查询。
-
- 重置
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 查看
- 编辑
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 取消
-
-
-
- 确定
-
-
-
-
-
-
-
+
-
-
diff --git a/web-admin/src/views/pilot-service/cell-product-type/CellProductPriceTab.vue b/web-admin/src/views/pilot-service/cell-product-type/CellProductPriceTab.vue
new file mode 100644
index 000000000..5b529ab37
--- /dev/null
+++ b/web-admin/src/views/pilot-service/cell-product-type/CellProductPriceTab.vue
@@ -0,0 +1,547 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
按项目名称、编码模糊查询。
+
+ 重置
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.unitPrice != null ? `${formatMoneyDisplay(row.unitPrice)} 元` : '-' }}
+
+
+
+
+
+
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)"
+ >
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确定
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/cell-product-type/CellProductSpecTab.vue b/web-admin/src/views/pilot-service/cell-product-type/CellProductSpecTab.vue
new file mode 100644
index 000000000..b684decf8
--- /dev/null
+++ b/web-admin/src/views/pilot-service/cell-product-type/CellProductSpecTab.vue
@@ -0,0 +1,395 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
按编码、名称模糊查询。
+
+ 重置
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确定
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/cell-product-type/ProjectPriceSystemTab.vue b/web-admin/src/views/pilot-service/cell-product-type/ProjectPriceSystemTab.vue
new file mode 100644
index 000000000..e3ede0035
--- /dev/null
+++ b/web-admin/src/views/pilot-service/cell-product-type/ProjectPriceSystemTab.vue
@@ -0,0 +1,807 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
按体系名称、编码模糊查询。
+
+ 重置
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ String(row.status) === '1' ? '正常' : '停用' }}
+
+
+
+
+
+
+
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)">
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确定
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.unitPrice != null ? `${formatMoneyDisplay(row.unitPrice)} 元` : '-' }}
+
+
+
+
+
+ 取消
+
+ 确认添加({{ priceSelectorSelected.length }})
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/cell-product-type/index.vue b/web-admin/src/views/pilot-service/cell-product-type/index.vue
index e0ff9fdb9..9bd25e0de 100644
--- a/web-admin/src/views/pilot-service/cell-product-type/index.vue
+++ b/web-admin/src/views/pilot-service/cell-product-type/index.vue
@@ -2,16 +2,13 @@
-
- 细胞产品类型管理
- 维护细胞产品的类型编码与名称。
-
-
-
类型列表
+ 细胞产品基础信息维护
+
+
@@ -102,21 +99,35 @@
/>
- 查看
- 编辑
- 删除
+
+
+ 查看
+
+ handleDropdownCommand(cmd, row)"
+ >
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -183,6 +194,20 @@
code-label="类型编码"
:sections="viewSections"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -197,6 +222,7 @@
View,
Edit,
Delete,
+ More,
ScaleToOriginal,
RefreshLeft,
Search
@@ -209,9 +235,14 @@
fetchUpdateCellProductType,
fetchDeleteCellProductType
} from '@/api/admin'
+ import CellProductSpecTab from './CellProductSpecTab.vue'
+ import CellProductPriceTab from './CellProductPriceTab.vue'
+ import ProjectPriceSystemTab from './ProjectPriceSystemTab.vue'
defineOptions({ name: 'PilotCellProductType' })
+ const route = useRoute()
+ const activeTab = ref('type')
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
const tableData = ref([])
const selectedRows = ref([])
@@ -337,6 +368,11 @@
viewVisible.value = true
}
+ function handleDropdownCommand(cmd: string, row: Api.CellProductType.CellProductTypeItem) {
+ if (cmd === 'edit') handleEdit(row)
+ else if (cmd === 'delete') handleDelete(row)
+ }
+
async function handleDelete(row: Api.CellProductType.CellProductTypeItem) {
try {
await ElMessageBox.confirm('确定要删除该类型吗?', '提示', { type: 'warning' })
@@ -376,6 +412,10 @@
)
onMounted(() => {
+ const tab = route.query.tab
+ if (tab === 'spec' || tab === 'price' || tab === 'project-price') {
+ activeTab.value = tab as string
+ }
loadData()
})
diff --git a/web-admin/src/views/pilot-service/channel-service-order-finance/index.vue b/web-admin/src/views/pilot-service/channel-service-order-finance/index.vue
index a65885549..f605a6f1a 100644
--- a/web-admin/src/views/pilot-service/channel-service-order-finance/index.vue
+++ b/web-admin/src/views/pilot-service/channel-service-order-finance/index.vue
@@ -1,11 +1,6 @@
-
- 财务确认
- 集中处理渠道服务订单的操作流水确认。
-
-
财务确认
diff --git a/web-admin/src/views/pilot-service/channel-service-order/index.vue b/web-admin/src/views/pilot-service/channel-service-order/index.vue
index 89b7cc203..fc010eace 100644
--- a/web-admin/src/views/pilot-service/channel-service-order/index.vue
+++ b/web-admin/src/views/pilot-service/channel-service-order/index.vue
@@ -1,2346 +1,18 @@
-
+
-
-
- 渠道服务订单
- 管理渠道预付款充值与产品购买订单,支持发起财务确认、退款、作废等操作。
-
-
-
-
-
订单列表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
默认展示近 30 天内创建的订单;渠道名称输入后自动检索。
-
-
- 重置
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ channelServiceOrderTypeLabel(row.orderType) }}
-
-
-
-
-
-
- ¥{{ formatAmount(row.currentBalance) }}
-
- -
-
-
-
-
-
- ¥{{ formatAmount(row.purchaseTotalAmount) }}
-
- -
-
-
-
-
-
- {{ channelServiceOrderStatusLabel(row.orderStatus) }}
-
-
-
-
-
-
- {{ displayValue(row.createByName || row.createBy) }}
-
-
-
-
-
-
-
- 查看
-
-
-
操作流水
-
-
- 编辑
-
-
-
-
- 删除
-
-
-
-
-
- 发起充值
-
-
-
- 申请退款
-
-
-
- 购买产品
-
-
-
- 退订产品
-
-
-
- 作废
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ¥{{ formatAmount(row.agreementUnitPrice) }}
-
-
-
- {{ formatProductQty(calcProductCumulativeUsedQty(row)) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 取消
-
-
- 确定
-
-
-
-
-
-
- {{
- formatOrderContext(currentOrderForTxn)
- }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持图片、视频、PDF、Word、Excel 等,可上传多个
-
-
-
-
-
- 取消
-
-
- 确认
-
-
-
-
-
-
- {{
- formatOrderContext(currentOrderForTxn)
- }}
-
-
- 退款金额(元)当前余额:¥{{ formatAmount(currentOrderForTxn?.currentBalance) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持图片、视频、PDF、Word、Excel 等,可上传多个
-
-
-
-
-
- 取消
-
-
- 确认
-
-
-
-
-
-
- {{
- formatOrderContext(currentOrderForTxn)
- }}
-
-
-
-
-
-
- handlePurchasePriceChange(v, $index)"
- >
-
-
-
-
-
- ¥{{ formatAmount(row.unitPrice) }}
-
-
-
-
-
-
-
- ¥{{ formatAmount(row.amount) }}
-
-
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
- 取消
-
-
- 确认
-
-
-
-
-
-
- {{
- formatOrderContext(currentOrderForTxn)
- }}
-
-
-
-
-
-
-
- ¥{{ formatAmount(row.agreementUnitPrice) }}
-
-
-
-
-
-
-
-
- ¥{{ formatAmount(row.refundAmount) }}
-
-
-
-
- 退订合计:¥{{ formatAmount(unsubscribeTotalAmount) }}(元)
-
-
-
-
-
-
-
-
- 取消
-
-
- 确认退订
-
-
-
-
-
-
-
-
![预览]()
-
-
-
-
-
-
+
-
-
diff --git a/web-admin/src/views/pilot-service/channel/ChannelServiceOrderTab.vue b/web-admin/src/views/pilot-service/channel/ChannelServiceOrderTab.vue
new file mode 100644
index 000000000..fd5609e26
--- /dev/null
+++ b/web-admin/src/views/pilot-service/channel/ChannelServiceOrderTab.vue
@@ -0,0 +1,2352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
默认展示近 30 天内创建的订单;渠道名称输入后自动检索。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ channelServiceOrderTypeLabel(row.orderType) }}
+
+
+
+
+
+
+ ¥{{ formatAmount(row.currentBalance) }}
+
+ -
+
+
+
+
+
+ ¥{{ formatAmount(row.purchaseTotalAmount) }}
+
+ -
+
+
+
+
+
+ {{ channelServiceOrderStatusLabel(row.orderStatus) }}
+
+
+
+
+
+
+ {{ displayValue(row.createByName || row.createBy) }}
+
+
+
+
+
+
+
+
操作流水
+
+
+ 发起充值
+
+
+ 申请退款
+
+
+ 购买产品
+
+
+ 退订产品
+
+
+ 作废
+
+ handleDropdownCommand(cmd, row)"
+ >
+
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥{{ formatAmount(row.agreementUnitPrice) }}
+
+
+
+ {{ formatProductQty(calcProductCumulativeUsedQty(row)) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 确定
+
+
+
+
+
+
+ {{
+ formatOrderContext(currentOrderForTxn)
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持图片、视频、PDF、Word、Excel 等,可上传多个
+
+
+
+
+
+ 取消
+
+
+ 确认
+
+
+
+
+
+
+ {{
+ formatOrderContext(currentOrderForTxn)
+ }}
+
+
+ 退款金额(元)当前余额:¥{{ formatAmount(currentOrderForTxn?.currentBalance) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持图片、视频、PDF、Word、Excel 等,可上传多个
+
+
+
+
+
+ 取消
+
+
+ 确认
+
+
+
+
+
+
+ {{
+ formatOrderContext(currentOrderForTxn)
+ }}
+
+
+
+
+
+
+ handlePurchasePriceChange(v, $index)"
+ >
+
+
+
+
+
+ ¥{{ formatAmount(row.unitPrice) }}
+
+
+
+
+
+
+
+ ¥{{ formatAmount(row.amount) }}
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 确认
+
+
+
+
+
+
+ {{
+ formatOrderContext(currentOrderForTxn)
+ }}
+
+
+
+
+
+
+
+ ¥{{ formatAmount(row.agreementUnitPrice) }}
+
+
+
+
+
+
+
+
+ ¥{{ formatAmount(row.refundAmount) }}
+
+
+
+
+ 退订合计:¥{{ formatAmount(unsubscribeTotalAmount) }}(元)
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 确认退订
+
+
+
+
+
+
+
+
![预览]()
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/channel/index.vue b/web-admin/src/views/pilot-service/channel/index.vue
index e0f2c1967..97054f854 100644
--- a/web-admin/src/views/pilot-service/channel/index.vue
+++ b/web-admin/src/views/pilot-service/channel/index.vue
@@ -2,16 +2,13 @@
-
- 渠道信息管理
- 维护渠道基础信息、开票信息、邮寄地址及协议管理。
-
-
-
渠道列表
+ 渠道信息管理
+
+
@@ -122,24 +119,42 @@
/>
-
- 查看
-
-
- 编辑
-
-
- 渠道协议
-
-
- 删除
-
+
+
+ 渠道协议
+
+ handleDropdownCommand(cmd, row)"
+ >
+
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -1109,6 +1124,12 @@
/>
+
+
+
+
+
+
@@ -1126,6 +1147,7 @@
Edit,
Delete,
Document,
+ More,
VideoPlay,
Loading,
Download,
@@ -1177,9 +1199,13 @@
fetchProjectSystemList,
fetchProjectSystemItems
} from '@/api/admin'
+ import ChannelServiceOrderTab from './ChannelServiceOrderTab.vue'
defineOptions({ name: 'PilotChannel' })
+ const route = useRoute()
+ const mainTab = ref('channel')
+
// ---------------------------------------------------------------------------
// 常量映射
// ---------------------------------------------------------------------------
@@ -1686,6 +1712,20 @@
// ---------------------------------------------------------------------------
// 查看弹窗
// ---------------------------------------------------------------------------
+ function handleDropdownCommand(command: string, row: any) {
+ switch (command) {
+ case 'view':
+ handleView(row)
+ break
+ case 'edit':
+ handleEdit(row)
+ break
+ case 'delete':
+ handleDelete(row)
+ break
+ }
+ }
+
function handleView(row: any) {
viewChannelId.value = String(row.id)
viewRow.value = row
@@ -2410,6 +2450,10 @@
{ debounce: 300, deep: true }
)
onMounted(() => {
+ const tab = route.query.tab
+ if (tab === 'service-order') {
+ mainTab.value = 'service-order'
+ }
loadData()
})
diff --git a/web-admin/src/views/pilot-service/checkup-institution/index.vue b/web-admin/src/views/pilot-service/checkup-institution/index.vue
index 4634817bb..d6be7bcae 100644
--- a/web-admin/src/views/pilot-service/checkup-institution/index.vue
+++ b/web-admin/src/views/pilot-service/checkup-institution/index.vue
@@ -2,11 +2,6 @@
-
- 体检机构及项目价格
- 维护体检机构基础信息、开票信息及项目价格管理。
-
-
机构列表
@@ -111,24 +106,42 @@
- 查看
- 项目价格配置
- 编辑
- 删除
+
+
+ 项目价格配置
+
+ handleDropdownCommand(cmd, row)"
+ >
+
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
@@ -429,6 +442,7 @@
Setting,
Edit,
Delete,
+ More,
OfficeBuilding
} from '@element-plus/icons-vue'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
@@ -702,6 +716,20 @@
// ---------------------------------------------------------------------------
// 查看弹窗
// ---------------------------------------------------------------------------
+ function handleDropdownCommand(command: string, row: any) {
+ switch (command) {
+ case 'view':
+ handleView(row)
+ break
+ case 'edit':
+ handleEdit(row)
+ break
+ case 'delete':
+ handleDelete(row)
+ break
+ }
+ }
+
async function handleView(row: any) {
viewRow.value = row
const sections: ViewSection[] = [
diff --git a/web-admin/src/views/pilot-service/checkup-management/index.vue b/web-admin/src/views/pilot-service/checkup-management/index.vue
index 5b28c9891..e97cca447 100644
--- a/web-admin/src/views/pilot-service/checkup-management/index.vue
+++ b/web-admin/src/views/pilot-service/checkup-management/index.vue
@@ -1,433 +1,30 @@
-
+
-
-
- 体检管理
- 管理中试服务患者体检信息填写、报告上传与费用记录。
-
-
+
+
体检记录
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持申请单号、患者姓名、渠道名称模糊查询,输入即检索。
-
-
- 重置
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ sexLabel(row.sex) || '—' }}
-
-
- {{ row.age ?? '—' }}
-
-
- {{ row.channelName || '—' }}
-
-
- {{ row.institutionName || '—' }}
-
-
- {{ row.reserveTime || '—' }}
-
-
-
-
- {{ checkupStatusLabel(row.checkupStatus) }}
-
-
-
-
- {{
- row.actualCheckupFee != null ? '¥' + formatMoneyDisplay(row.actualCheckupFee) : '—'
- }}
-
-
-
-
- 填写体检信息
-
-
-
- 查看
-
-
- 编辑
-
-
-
- 标记已体检
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue b/web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue
new file mode 100644
index 000000000..8d6e6127d
--- /dev/null
+++ b/web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue
@@ -0,0 +1,726 @@
+
+
+
+
+
+
+
+
+ {{ tab.label }}
+
+
+
+
+
+
+
+
缴费确认列表
+
+
+
+
+
+
+
+
+
+
+
支持按申请单号、患者姓名模糊检索。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.patientName || '-' }}
+
+
+ {{ row.channelName || '-' }}
+
+
+ {{ row.cellTypeSpec || '-' }}
+
+
+ {{ row.infusionTime || '-' }}
+
+
+
+ {{ formatAmount(row.paymentAmount) }}
+
+
+
+
+
+ {{ activeTab === 'jfqrz' ? '缴费确认中' : '已缴费' }}
+
+
+
+
+ {{ row.financeRemark || '-' }}
+
+
+
+
+
+ 确认收款
+
+
+ 查看确认信息
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确认
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/clinical-payment-info/index.vue b/web-admin/src/views/pilot-service/clinical-payment-info/index.vue
new file mode 100644
index 000000000..379514826
--- /dev/null
+++ b/web-admin/src/views/pilot-service/clinical-payment-info/index.vue
@@ -0,0 +1,707 @@
+
+
+
+
+
+
+
+
+ {{ tab.label }}
+
+
+
+
+
+
+
+
缴费信息列表
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持按申请单号模糊检索。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.patientName || '-' }}
+
+
+ {{ row.channelName || '-' }}
+
+
+ {{ row.cellTypeSpec || '-' }}
+
+
+ {{ row.infusionTime || '-' }}
+
+
+
+ {{ formatAmount(row.paymentAmount) }}
+
+
+
+
+
+ {{ payStatusLabel(String(row.payStatus || '')) }}
+
+
+
+
+
+
+ 缴费确认
+
+
+ 退费确认
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确认提交
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/clinical-refund-confirm/index.vue b/web-admin/src/views/pilot-service/clinical-refund-confirm/index.vue
new file mode 100644
index 000000000..dfb721b7d
--- /dev/null
+++ b/web-admin/src/views/pilot-service/clinical-refund-confirm/index.vue
@@ -0,0 +1,804 @@
+
+
+
+
+
+
+
+
+ {{ tab.label }}
+
+
+
+
+
+
+
+
退费确认列表
+
+
+
+
+
+
+
+
+
+
+
支持按申请单号、患者姓名模糊检索。
+
+
+ 重置
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.patientName || '-' }}
+
+
+ {{ row.channelName || '-' }}
+
+
+ {{ row.cellTypeSpec || '-' }}
+
+
+ {{ row.infusionTime || '-' }}
+
+
+
+ {{ formatAmount(row.paymentAmount) }}
+
+
+
+
+ {{ formatAmount(row.refundAmount) }}
+
+
+
+
+
+ {{ activeTab === 'tfqrz' ? '退费确认中' : '已退费' }}
+
+
+
+
+ {{ row.refundRemark || '-' }}
+
+
+ {{ row.refundFinanceRemark || '-' }}
+
+
+
+
+
+ 确认退费
+
+
+ 查看确认信息
+
+
+
+
+
+
+
+
+
+
+
退费申请信息
+
+
+
+ {{ confirmRow?.applicationNo || '-' }}
+
+
+ {{ confirmRow?.patientName || '-' }}
+
+
+ {{ formatAmount(confirmRow?.paymentAmount) }}
+
+
+ {{ formatAmount(confirmRow?.refundAmount) }}
+
+
+ {{
+ confirmRow?.refundRemark || '—'
+ }}
+
+
+
+
+
+ 财务确认
+
+
+
+
+
+
+ 取消
+
+
+
+ 确认
+
+
+
+
+
+
+
+
退费申请信息
+
+
+
+ {{ viewRow?.applicationNo || '-' }}
+
+
+ {{ viewRow?.patientName || '-' }}
+
+
+ {{ formatAmount(viewRow?.paymentAmount) }}
+
+
+ {{ formatAmount(viewRow?.refundAmount) }}
+
+
+ {{
+ viewRow?.refundRemark || '—'
+ }}
+
+
+
+
+
+ 财务确认信息
+
+
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/infusion-schedule-management/index.vue b/web-admin/src/views/pilot-service/infusion-schedule-management/index.vue
index 522a00895..1923c4040 100644
--- a/web-admin/src/views/pilot-service/infusion-schedule-management/index.vue
+++ b/web-admin/src/views/pilot-service/infusion-schedule-management/index.vue
@@ -1,551 +1,32 @@
-
+
-
-
- 输注计划管理
- 管理中试服务患者输注计划的确认、完成与取消。
-
-
+
+
输注计划
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
支持申请单号、患者姓名、渠道名称模糊查询,输入即检索。
-
-
- 重置
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ sexLabel(row.patientSex ?? row.sex) || '—' }}
-
-
- {{ row.patientAge ?? row.age ?? '—' }}
-
-
- {{ row.channelName || '—' }}
-
-
- {{ formatScheduleSortNo(row) }}
-
-
- {{ formatScheduleTotalCount(row) }}
-
-
- {{ row.infusionTime || '—' }}
-
-
- {{ row.infusionLocation || '—' }}
-
-
-
-
- {{ scheduleStatusLabel(row.scheduleStatus) }}
-
-
-
-
-
-
- {{ infusionStatusLabel(row.infusionStatus) }}
-
-
-
-
-
- {{
- qcStatusLabel(row.qcStatus)
- }}
-
-
-
-
-
- 编辑
-
-
- 用户已确认
-
-
- 完成
-
-
- 取消
-
-
- 出库交接
-
-
- 查看
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web-admin/src/views/pilot-service/outbound-release-review/index.vue b/web-admin/src/views/pilot-service/outbound-release-review/index.vue
index f833a2941..c783750bf 100644
--- a/web-admin/src/views/pilot-service/outbound-release-review/index.vue
+++ b/web-admin/src/views/pilot-service/outbound-release-review/index.vue
@@ -2,11 +2,6 @@
-
- 出库放行审核
- 审核出库放行记录,管理中试服务出库流程。
-
-
审核列表
diff --git a/web-admin/src/views/pilot-service/pilot-application/ApplicationDetailDialog.vue b/web-admin/src/views/pilot-service/pilot-application/ApplicationDetailDialog.vue
index f89242037..7ed51e1b1 100644
--- a/web-admin/src/views/pilot-service/pilot-application/ApplicationDetailDialog.vue
+++ b/web-admin/src/views/pilot-service/pilot-application/ApplicationDetailDialog.vue
@@ -1928,14 +1928,12 @@
savePilotDeliveryMethod,
createOutboundProductionWorkOrder,
createOutboundQcWorkOrder,
- fetchReleaseReviewList,
- updateReleaseReview,
- deleteReleaseReview,
fetchInfusionConfirmList,
confirmInfusion,
updateInfusionConfirm,
deleteInfusionConfirm
} from '@/api/admin'
+ import { fetchReleaseReviewList, updateReleaseReview, deleteReleaseReview } from '@/api/outbound-release-review'
import { fetchUploadAttachment } from '@/api/business'
import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
import {
diff --git a/web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue b/web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue
index 414afdbe5..2a1bc5be7 100644
--- a/web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue
+++ b/web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue
@@ -60,6 +60,7 @@
输注方案
-
+
请先在「申请单信息」中选择渠道与服务订单,系统将按订单类型加载协议与可选细胞产品。
+
+ 请先在「申请单信息」中选择渠道,系统将加载可选细胞产品。
+
-