zlwu 5 maanden geleden
bovenliggende
commit
832cff3488

+ 4 - 4
bx/bx-backend/sql/restructure_biz_menu_under_300.sql

@@ -1,15 +1,15 @@
 -- 报销菜单:三个业务分组为一级目录(parent_id=0),不再使用「报销业务」父级 id=300。
 -- 结构:
---   310 我的申请 → 311 备用金申请、321 报销申请
---   320 我的审批 → 312 备用金审批、322 报销审批
+--   310 我的 → 311 备用金申请、321 报销申请
+--   320 待办 → 312 备用金审批、322 报销审批
 --   330 发放/冲抵 → 313 备用金发放管理、323 支付管理(发放/冲抵)
 -- 一级排序:基础管理(100)=1,基础配置(200)=2,本脚本 310/320/330=3/4/5,视图中心(400)=6。
 -- 若 id=330 已存在且用途不同,请先调整本脚本中的 id。
 
 START TRANSACTION;
 
-UPDATE bx_menu SET menu_name = '我的申请' WHERE id = 310 AND del_flag = 0;
-UPDATE bx_menu SET menu_name = '我的审批' WHERE id = 320 AND del_flag = 0;
+UPDATE bx_menu SET menu_name = '我的' WHERE id = 310 AND del_flag = 0;
+UPDATE bx_menu SET menu_name = '待办' WHERE id = 320 AND del_flag = 0;
 
 INSERT INTO bx_menu (
   id, parent_id, menu_name, menu_code, menu_type, path, icon, sort_order, visible, status,

BIN
bx/bx-web/src/assets/logo.gif


+ 8 - 8
bx/bx-web/src/mock/data.ts

@@ -19,12 +19,12 @@ export const menuItems: MenuItem[] = [
   { id: 'project', title: '费用项目管理', group: '基础配置' },
   { id: 'wf-advance', title: '工作流配置(备用金)', group: '基础配置' },
   { id: 'wf-reimburse', title: '工作流配置(报销单)', group: '基础配置' },
-  // 我的申请(一级侧栏分组)
-  { id: 'advance-apply', title: '备用金申请', group: '我的申请' },
-  { id: 'reimburse-apply', title: '报销申请', group: '我的申请' },
-  // 我的审批
-  { id: 'advance-approve', title: '备用金审批', group: '我的审批' },
-  { id: 'reimburse-approve', title: '报销审批', group: '我的审批' },
+  // 我的(一级侧栏分组)
+  { id: 'advance-apply', title: '备用金申请', group: '我的' },
+  { id: 'reimburse-apply', title: '报销申请', group: '我的' },
+  // 待办
+  { id: 'advance-approve', title: '备用金审批', group: '待办' },
+  { id: 'reimburse-approve', title: '报销审批', group: '待办' },
   // 发放/冲抵
   { id: 'advance-issue', title: '备用金发放管理', group: '发放/冲抵' },
   { id: 'payment', title: '支付管理(发放/冲抵)', group: '发放/冲抵' },
@@ -115,14 +115,14 @@ export const menuRows: MenuRow[] = [
     ]
   },
   {
-    id: 310, parentId: 0, menuName: '我的申请', menuCode: '', menuType: '目录', path: '', sortOrder: 3, status: '启用',
+    id: 310, parentId: 0, menuName: '我的', menuCode: '', menuType: '目录', path: '', sortOrder: 3, status: '启用',
     children: [
       { id: 311, parentId: 310, menuName: '备用金申请', menuCode: 'advance-apply', menuType: '菜单', path: '/biz/advance-apply', sortOrder: 1, status: '启用' },
       { id: 321, parentId: 310, menuName: '报销申请', menuCode: 'reimburse-apply', menuType: '菜单', path: '/biz/reimburse-apply', sortOrder: 2, status: '启用' }
     ]
   },
   {
-    id: 320, parentId: 0, menuName: '我的审批', menuCode: '', menuType: '目录', path: '', sortOrder: 4, status: '启用',
+    id: 320, parentId: 0, menuName: '待办', menuCode: '', menuType: '目录', path: '', sortOrder: 4, status: '启用',
     children: [
       { id: 312, parentId: 320, menuName: '备用金审批', menuCode: 'advance-approve', menuType: '菜单', path: '/biz/advance-approve', sortOrder: 1, status: '启用' },
       { id: 322, parentId: 320, menuName: '报销审批', menuCode: 'reimburse-approve', menuType: '菜单', path: '/biz/reimburse-approve', sortOrder: 2, status: '启用' }

+ 79 - 19
bx/bx-web/src/styles/index.css

@@ -128,16 +128,42 @@ body {
 }
 
 .logo {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
   font-size: 16px;
   font-weight: 700;
-  color: var(--text);
+  color: var(--text-muted);
   padding: 0 12px 20px;
   margin-bottom: 8px;
   border-bottom: 1px solid var(--border);
 }
 
-.logo span {
+.logo-mark {
+  flex-shrink: 0;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  max-height: 40px;
+}
+
+.logo-img {
+  display: block;
+  height: 36px;
+  width: auto;
+  max-width: min(100%, 180px);
+  object-fit: contain;
+}
+
+.logo-title {
   color: var(--primary);
+  font-weight: 700;
+  letter-spacing: 0.02em;
+  font-size: 18px;
+  line-height: 1.2;
+  text-align: center;
 }
 
 .nav-tree {
@@ -161,7 +187,7 @@ body {
   border-radius: var(--radius);
   font-size: 12px;
   font-weight: 700;
-  color: #000000;
+  color: var(--text-muted);
   letter-spacing: 0.02em;
   user-select: none;
 }
@@ -174,10 +200,10 @@ body {
   content: "";
   width: 6px;
   height: 6px;
-  border-right: 2px solid #000000;
-  border-bottom: 2px solid #000000;
+  border-right: 2px solid var(--text-muted);
+  border-bottom: 2px solid var(--text-muted);
   transform: rotate(-45deg);
-  transition: transform 0.2s ease;
+  transition: transform 0.2s ease, border-color 0.15s ease;
 }
 
 .nav-details[open] > summary::after {
@@ -187,7 +213,12 @@ body {
 
 .nav-details > summary:hover {
   background: var(--row-alt);
-  color: #000000;
+  color: var(--text);
+}
+
+.nav-details > summary:hover::after {
+  border-right-color: var(--text);
+  border-bottom-color: var(--text);
 }
 
 .nav-summary-main {
@@ -207,11 +238,11 @@ body {
 .nav-summary-ico {
   flex-shrink: 0;
   font-size: 16px;
-  color: #000000;
+  color: var(--text-muted);
 }
 
 .nav-details > summary:hover .nav-summary-ico {
-  color: #000000;
+  color: var(--text);
 }
 
 .nav-sub {
@@ -240,7 +271,7 @@ body {
   margin: 2px 0;
   padding: 8px 12px 8px 14px;
   border-radius: var(--radius);
-  color: #000000;
+  color: var(--text-muted);
   text-decoration: none;
   font-size: 13px;
   font-weight: 500;
@@ -249,7 +280,7 @@ body {
 .menu-link-ico {
   flex-shrink: 0;
   font-size: 16px;
-  color: #000000;
+  color: var(--text-muted);
 }
 
 .menu-link-text {
@@ -262,21 +293,21 @@ body {
 
 .menu-link:hover {
   background: var(--row-alt);
-  color: #000000;
+  color: var(--text);
 }
 
 .menu-link:hover .menu-link-ico {
-  color: #000000;
+  color: var(--text);
 }
 
 .menu-link.active {
   background: var(--primary-bg);
-  color: #000000;
+  color: var(--primary-dark);
   font-weight: 600;
 }
 
 .menu-link.active .menu-link-ico {
-  color: #000000;
+  color: var(--primary-dark);
 }
 
 .main {
@@ -286,14 +317,44 @@ body {
   flex-direction: column;
 }
 
-.header {
+.main > .header {
   height: 56px;
   padding: 0 24px;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  background: var(--surface);
-  border-bottom: 1px solid var(--border);
+  background: var(--primary-dark);
+  border-bottom: 1px solid #047857;
+}
+
+.main > .header .crumb {
+  color: rgba(255, 255, 255, 0.82);
+}
+
+.main > .header .crumb b {
+  color: #fff;
+}
+
+.main > .header .icon-btn {
+  color: rgba(255, 255, 255, 0.88);
+}
+
+.main > .header .icon-btn:hover {
+  color: #fff;
+  background: rgba(255, 255, 255, 0.14);
+}
+
+.main > .header .user-trigger {
+  color: #fff;
+  background: transparent;
+}
+
+.main > .header .user-trigger:hover {
+  background: transparent;
+}
+
+.main > .header .user-avatar {
+  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
 }
 
 .header-left {
@@ -398,7 +459,6 @@ body {
   width: 24px;
   height: 24px;
   border-radius: 50%;
-  background: linear-gradient(135deg, #34d399, #10b981);
   color: #fff;
   display: inline-flex;
   align-items: center;

+ 9 - 3
bx/bx-web/src/views/MainLayout.vue

@@ -29,6 +29,7 @@ import {
   Wallet,
 } from '@element-plus/icons-vue'
 import { useAuthStore, type MenuItem } from '../stores/auth'
+import logoGif from '../assets/logo.gif'
 
 const router = useRouter()
 
@@ -111,8 +112,8 @@ function resolveLevel1Icon(menuCode: string | null | undefined, menuName?: strin
   const name = menuName || ''
   if (/基础管理/.test(name)) return Management
   if (/基础配置|系统配置/.test(name)) return Setting
-  if (/我的申请/.test(name)) return DocumentAdd
-  if (/我的审批/.test(name)) return CircleCheck
+  if (name === '我的' || name === '我的申请') return User
+  if (name === '待办' || name === '我的审批') return Bell
   if (/备用金/.test(name)) return Wallet
   if (/报销/.test(name)) return Operation
   if (/视图|看板|统计/.test(name)) return Histogram
@@ -255,7 +256,12 @@ onMounted(async () => {
 <template>
   <div class="bx-layout" @click="closeContextMenu">
     <aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
-      <div class="logo">山峡化工<span>报销系统</span></div>
+      <div class="logo">
+        <span class="logo-mark">
+          <img :src="logoGif" alt="" class="logo-img" />
+        </span>
+        <span class="logo-title">报销系统</span>
+      </div>
       <el-scrollbar>
         <div class="nav-tree">
           <template v-for="group in auth.menuList" :key="group.id">

+ 3 - 3
bx/报销业务/bx-menu-role-init.sql

@@ -17,8 +17,8 @@ SET CHARACTER SET utf8mb4;
 -- 菜单 ID 说明(bx-schema-reset.sql 中固定 ID):
 --   100  基础管理(目录)       101  用户管理     102  角色管理     103  菜单管理
 --   200  基础配置(目录)       201  费用项目管理  202  工作流配置(备用金)  203  工作流配置(报销单)
---   310  我的申请(一级目录)    311  备用金申请     321  报销申请
---   320  我的审批(一级目录)    312  备用金审批     322  报销审批
+--   310  我的(一级目录)        311  备用金申请     321  报销申请
+--   320  待办(一级目录)        312  备用金审批     322  报销审批
 --   330  发放/冲抵(一级目录)   313  备用金发放管理  323  支付管理(发放/冲抵)
 --   400  视图中心(目录)        401  我的申请单     402  待我审批      403  全部申请单
 -- ============================================================
@@ -34,7 +34,7 @@ INSERT INTO bx_menu_role (menu_id, role_id) VALUES
 (100, 1), (101, 1), (102, 1), (103, 1),
 -- 基础配置
 (200, 1), (201, 1), (202, 1), (203, 1),
--- 我的申请 / 我的审批 / 发放冲抵
+-- 我的 / 待办 / 发放冲抵
 (310, 1), (311, 1), (321, 1),
 (320, 1), (312, 1), (322, 1),
 (330, 1), (313, 1), (323, 1),

+ 3 - 3
bx/报销业务/bx-schema-reset.sql

@@ -344,12 +344,12 @@ INSERT INTO bx_menu (id, parent_id, menu_name, menu_code, menu_type, path, sort_
 (202, 200, '工作流配置(备用金)', 'wf-advance', 2, '/config/wf-advance', 2),
 (203, 200, '工作流配置(报销单)', 'wf-reimburse', 2, '/config/wf-reimburse', 3);
 
--- L1:我的申请、我的审批、发放/冲抵(一级目录;不再使用「报销业务」父级 id=300)
+-- L1:我的、待办、发放/冲抵(一级目录;不再使用「报销业务」父级 id=300)
 INSERT INTO bx_menu (id, parent_id, menu_name, menu_code, menu_type, path, sort_order) VALUES
-(310, 0, '我的申请', NULL, 1, NULL, 3),
+(310, 0, '我的', NULL, 1, NULL, 3),
 (311, 310, '备用金申请', 'advance-apply', 2, '/biz/advance-apply', 1),
 (321, 310, '报销申请', 'reimburse-apply', 2, '/biz/reimburse-apply', 2),
-(320, 0, '我的审批', NULL, 1, NULL, 4),
+(320, 0, '待办', NULL, 1, NULL, 4),
 (312, 320, '备用金审批', 'advance-approve', 2, '/biz/advance-approve', 1),
 (322, 320, '报销审批', 'reimburse-approve', 2, '/biz/reimburse-approve', 2),
 (330, 0, '发放/冲抵', NULL, 1, NULL, 5),

+ 3 - 3
bx/报销业务/bx-schema.sql

@@ -319,12 +319,12 @@ INSERT INTO bx_menu (id, parent_id, menu_name, menu_code, menu_type, path, sort_
 (202, 200, '工作流配置(备用金)', 'wf-advance', 2, '/config/wf-advance', 2),
 (203, 200, '工作流配置(报销单)', 'wf-reimburse', 2, '/config/wf-reimburse', 3);
 
--- L1:我的申请、我的审批、发放/冲抵(一级目录;不再使用「报销业务」父级 id=300)
+-- L1:我的、待办、发放/冲抵(一级目录;不再使用「报销业务」父级 id=300)
 INSERT INTO bx_menu (id, parent_id, menu_name, menu_code, menu_type, path, sort_order) VALUES
-(310, 0, '我的申请', NULL, 1, NULL, 3),
+(310, 0, '我的', NULL, 1, NULL, 3),
 (311, 310, '备用金申请', 'advance-apply', 2, '/biz/advance-apply', 1),
 (321, 310, '报销申请', 'reimburse-apply', 2, '/biz/reimburse-apply', 2),
-(320, 0, '我的审批', NULL, 1, NULL, 4),
+(320, 0, '待办', NULL, 1, NULL, 4),
 (312, 320, '备用金审批', 'advance-approve', 2, '/biz/advance-approve', 1),
 (322, 320, '报销审批', 'reimburse-approve', 2, '/biz/reimburse-approve', 2),
 (330, 0, '发放/冲抵', NULL, 1, NULL, 5),

File diff suppressed because it is too large
+ 976 - 0
plan/sx.sql


+ 445 - 0
plan/vehicle_management.sql

@@ -0,0 +1,445 @@
+/*
+ Navicat Premium Dump SQL
+
+ Source Server         : 47.99.164.66  hn02le.34lkdLKD
+ Source Server Type    : MySQL
+ Source Server Version : 50736 (5.7.36)
+ Source Host           : 47.99.164.66:3306
+ Source Schema         : vehicle_management
+
+ Target Server Type    : MySQL
+ Target Server Version : 50736 (5.7.36)
+ File Encoding         : 65001
+
+ Date: 05/05/2026 10:14:43
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for certificates
+-- ----------------------------
+DROP TABLE IF EXISTS `certificates`;
+CREATE TABLE `certificates` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL COMMENT '用户ID',
+  `certificate_type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '证件类型: driving_license(驾驶证), escort_license(押运证)等',
+  `file_url` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件URL',
+  `file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原文件名',
+  `expiry_date` date DEFAULT NULL COMMENT '证件过期日期',
+  `uploaded_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_certificates_expiry` (`expiry_date`),
+  CONSTRAINT `certificates_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='证件表';
+
+-- ----------------------------
+-- Records of certificates
+-- ----------------------------
+BEGIN;
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (5, 11, 'driving_license', 'http://localhost:8000/files/uploads/general/2026/01/15/7cbec2b6-6bef-4b57-90fc-b85460f99fd8.png', 'Snipaste_2025-12-27_16-03-25.png', '2026-01-21', '2026-01-15 16:50:44', 0);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (6, 12, 'driving_license', 'http://121.43.228.102:9000/clgl/ed678c29-239d-46d4-a03b-75d1bbca6d1c.png', 'Snipaste_2025-12-27_16-03-21.png', '2026-02-04', '2026-01-15 17:05:56', 0);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (7, 12, 'id_card', 'http://121.43.228.102:9000/clgl/4dbae2be-ab0b-43ca-b7f1-d9f1cdf9bbc1.png', 'Snipaste_2025-12-27_16-03-11.png', '2025-12-30', '2026-01-15 17:06:14', 1);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (8, 12, 'escort_license', 'http://121.43.228.102:9000/clgl/6f20a27a-7399-47f3-aa23-5e30d74f507b.png', 'Snipaste_2025-12-27_16-03-06.png', '2026-01-14', '2026-01-15 17:10:05', 1);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (9, 12, 'id_card', 'http://121.43.228.102:9000/clgl/736349d7-9443-4677-aacf-8ced1396c14c.png', 'Snipaste_2025-12-27_16-03-33.png', '2026-01-21', '2026-01-15 17:10:13', 0);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (10, 12, 'id_card', 'http://121.43.228.102:9000/clgl/5e3203df-b0c3-45d5-a6b2-0a83c6ea7cda.png', 'Snipaste_2025-12-27_16-03-21.png', '2025-12-30', '2026-01-15 17:10:40', 0);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (11, 12, 'qualification_certificate', 'http://121.43.228.102:9000/clgl/3b3bf250-62ab-42b9-920e-79401538ead2.png', 'Snipaste_2025-12-27_16-03-17.png', '2025-12-30', '2026-01-15 17:10:47', 0);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (12, 12, 'escort_license', 'http://121.43.228.102:9000/clgl/bb416d90-8ce1-43bb-841d-ff86bb192190.png', 'Snipaste_2025-12-27_16-03-21.png', '2026-01-14', '2026-01-15 17:10:56', 0);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (13, 9, 'driving_license', 'http://121.43.228.102:9000/clgl/ac003234-a437-4a18-92cf-5260ac71ec07.png', 'Snipaste_2025-12-27_16-03-33.png', '2026-01-14', '2026-01-15 22:53:09', 0);
+INSERT INTO `certificates` (`id`, `user_id`, `certificate_type`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (14, 10, 'escort_license', 'http://121.43.228.102:9000/clgl/4907c641-d10e-4016-a517-65d1824df918.png', 'Snipaste_2025-12-27_16-03-21.png', '2025-12-31', '2026-01-15 22:53:21', 0);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for manufacturers
+-- ----------------------------
+DROP TABLE IF EXISTS `manufacturers`;
+CREATE TABLE `manufacturers` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '厂商名称',
+  `safety_notice` text COLLATE utf8mb4_unicode_ci COMMENT '厂商安全须知',
+  `address` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '厂商地址',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
+  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  KEY `idx_name` (`name`),
+  KEY `idx_is_active` (`is_active`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='厂商表';
+
+-- ----------------------------
+-- Records of manufacturers
+-- ----------------------------
+BEGIN;
+INSERT INTO `manufacturers` (`id`, `name`, `safety_notice`, `address`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (1, '山峡水泥厂', '注意安全,戴好安全帽,禁止在装卸区吸烟', '山峡市工业区A区', 1, '2026-01-13 07:14:49', '2026-01-13 07:14:49', 0);
+INSERT INTO `manufacturers` (`id`, `name`, `safety_notice`, `address`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (2, '山峡钢材厂', '注意防火,禁止明火,穿戴好防护装备', '山峡市工业区B区', 1, '2026-01-13 07:14:49', '2026-01-13 07:14:49', 0);
+INSERT INTO `manufacturers` (`id`, `name`, `safety_notice`, `address`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (3, '山峡物流中心', '注意交通安全,服从指挥', '山峡市物流园区', 1, '2026-01-13 07:14:49', '2026-01-13 07:14:49', 0);
+INSERT INTO `manufacturers` (`id`, `name`, `safety_notice`, `address`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (4, 'a s d f da s', '<h1><span style=\"color: rgb(225, 60, 57);\">123123123123123123213</span></h1><p><br></p><p>1额啊范德萨范德萨</p>', '啊是否撒点发三顿饭撒的范德萨发撒点饭撒的饭都是法师123123123123', 1, '2026-01-16 00:20:08', '2026-01-16 00:20:24', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for plan_photos
+-- ----------------------------
+DROP TABLE IF EXISTS `plan_photos`;
+CREATE TABLE `plan_photos` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `plan_id` int(11) NOT NULL COMMENT '计划ID',
+  `photo_type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '照片类型: loading_arrival(装货到场), loading_departure(装货离场), unloading_arrival(卸货到场), unloading_departure(卸货离场), waybill(运单)',
+  `file_url` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件URL',
+  `file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原文件名',
+  `uploaded_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  KEY `idx_plan_id` (`plan_id`),
+  CONSTRAINT `plan_photos_ibfk_1` FOREIGN KEY (`plan_id`) REFERENCES `vehicle_plans` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='计划照片表';
+
+-- ----------------------------
+-- Records of plan_photos
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for products
+-- ----------------------------
+DROP TABLE IF EXISTS `products`;
+CREATE TABLE `products` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品名称',
+  `specification` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品规格',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
+  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  KEY `idx_name` (`name`),
+  KEY `idx_is_active` (`is_active`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品表';
+
+-- ----------------------------
+-- Records of products
+-- ----------------------------
+BEGIN;
+INSERT INTO `products` (`id`, `name`, `specification`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (1, '水泥', '425号普通硅酸盐水泥', 1, '2026-01-13 07:14:49', '2026-01-13 07:14:49', 0);
+INSERT INTO `products` (`id`, `name`, `specification`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (2, '钢筋', '直径12mm HRB400钢筋', 1, '2026-01-13 07:14:49', '2026-01-13 07:14:49', 0);
+INSERT INTO `products` (`id`, `name`, `specification`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (3, '砂石', '中砂(细度模数2.3-1.6)', 1, '2026-01-13 07:14:49', '2026-01-13 07:14:49', 0);
+INSERT INTO `products` (`id`, `name`, `specification`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (4, '混凝土', 'C30商品混凝土', 1, '2026-01-13 07:14:49', '2026-01-16 00:33:18', 1);
+INSERT INTO `products` (`id`, `name`, `specification`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (5, '12344', '12344', 0, '2026-01-16 00:36:27', '2026-01-16 00:36:34', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for schema_version
+-- ----------------------------
+DROP TABLE IF EXISTS `schema_version`;
+CREATE TABLE `schema_version` (
+  `version` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据库版本',
+  `description` text COLLATE utf8mb4_unicode_ci COMMENT '版本描述',
+  `applied_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '应用时间',
+  PRIMARY KEY (`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ----------------------------
+-- Records of schema_version
+-- ----------------------------
+BEGIN;
+INSERT INTO `schema_version` (`version`, `description`, `applied_at`) VALUES ('2.0.0', '添加证件过期时间管理功能,完善车辆证件分类系统', '2026-01-13 07:14:49');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for users
+-- ----------------------------
+DROP TABLE IF EXISTS `users`;
+CREATE TABLE `users` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `username` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '登录工号',
+  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
+  `role` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'operator' COMMENT '角色: operator(操作员), admin(管理员), driver(司机), escort(押运员)',
+  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名',
+  `phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号码(司机、押运员必填)',
+  `id_card` varchar(18) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证号(司机、押运员必填)',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
+  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username` (`username`),
+  KEY `idx_username` (`username`),
+  KEY `idx_role` (`role`),
+  KEY `idx_is_active` (`is_active`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
+
+-- ----------------------------
+-- Records of users
+-- ----------------------------
+BEGIN;
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (1, 'admin', 'admin123', 'admin', '系统管理员', NULL, NULL, 1, '2026-01-13 07:14:49', '2026-01-13 07:14:49', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (2, 'testuser1768458386083', '123456', 'operator', '测试用户', NULL, NULL, 1, '2026-01-15 14:26:26', '2026-01-15 14:26:26', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (3, 'testuser1768458429763', '123456', 'operator', '测试用户', NULL, NULL, 1, '2026-01-15 14:27:09', '2026-01-15 14:27:09', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (7, 'test422', '123456', 'operator', '测试422', '13877711221', NULL, 1, '2026-01-15 14:37:18', '2026-01-15 14:37:18', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (8, 'czy03', '123456', 'operator', '操作员03', '13899001122', NULL, 1, '2026-01-15 14:39:18', '2026-01-15 14:39:18', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (9, '1231', '123456', 'escort', '王五', '13899112232', '340822198111221122', 1, '2026-01-15 14:43:18', '2026-01-15 19:01:50', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (10, '3', '123456', 'driver', 'lisan', '13877112211', '340822198811223344', 1, '2026-01-15 14:43:41', '2026-01-15 15:09:56', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (11, '4', '123456', 'operator', '4', '13511223311', NULL, 1, '2026-01-15 14:49:14', '2026-01-15 14:49:14', 0);
+INSERT INTO `users` (`id`, `username`, `password`, `role`, `name`, `phone`, `id_card`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (12, '5', '123456', 'operator', '5', '13611332211', NULL, 0, '2026-01-15 14:49:23', '2026-01-15 17:19:18', 0);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for vehicle_certificates
+-- ----------------------------
+DROP TABLE IF EXISTS `vehicle_certificates`;
+CREATE TABLE `vehicle_certificates` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `vehicle_id` int(11) NOT NULL COMMENT '车辆ID',
+  `certificate_category` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '证件类别: license(车牌), registration(行驶证), transport(运输证), insurance(保险), inspection(罐检)',
+  `vehicle_part` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '车辆部位: head(车头), tail(车尾), tank(罐体)',
+  `file_url` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件URL',
+  `file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原文件名',
+  `expiry_date` date DEFAULT NULL COMMENT '证件过期日期',
+  `uploaded_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  KEY `idx_vehicle_id` (`vehicle_id`),
+  KEY `idx_vehicle_certificates_expiry` (`expiry_date`),
+  KEY `idx_vehicle_certificates_part_category` (`vehicle_part`,`certificate_category`),
+  CONSTRAINT `vehicle_certificates_ibfk_1` FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆证件表';
+
+-- ----------------------------
+-- Records of vehicle_certificates
+-- ----------------------------
+BEGIN;
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (1, 1, 'license', 'head', 'http://121.43.228.102:9000/clgl/77ccdee8-1e0e-47fc-a34f-d9d06574a335.png', 'Snipaste_2025-12-27_16-03-25.png', '2026-01-13', '2026-01-15 20:00:52', 1);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (2, 1, 'registration', 'head', 'http://example.com/test.pdf', 'test.pdf', '2025-12-31', '2026-01-15 20:01:02', 1);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (3, 1, 'registration', 'head', 'http://121.43.228.102:9000/clgl/40194dc8-62f9-49eb-9e13-b5a7fefb9ed0.png', 'Snipaste_2025-12-27_16-03-25.png', '2026-01-21', '2026-01-15 20:28:36', 0);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (4, 1, 'inspection', 'head', 'http://121.43.228.102:9000/clgl/56f3c3a2-355f-4ffb-b59a-63db99d82ad3.png', 'Snipaste_2025-12-27_16-03-21.png', '2026-01-06', '2026-01-15 20:32:47', 1);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (5, 1, 'insurance', 'head', 'http://121.43.228.102:9000/clgl/350bad8b-ce21-42f4-913c-b279a0347f92.png', 'Snipaste_2025-12-27_16-03-33.png', '2025-12-30', '2026-01-15 20:33:01', 1);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (6, 1, 'registration', 'head', 'http://121.43.228.102:9000/clgl/b799d8e6-8b94-439a-8402-1fc11016b90e.png', 'Snipaste_2025-12-27_16-03-25.png', '2025-12-30', '2026-01-15 20:52:21', 1);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (7, 1, 'transport', 'tail', 'http://121.43.228.102:9000/clgl/86b2b5f4-938f-4bac-9621-eebcc5522b08.png', 'Snipaste_2025-12-27_16-03-25.png', '2026-01-28', '2026-01-15 21:28:56', 0);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (8, 1, 'registration', 'tail', 'http://121.43.228.102:9000/clgl/9468380e-9f5d-44a3-ba22-b5805570e8bc.png', 'Snipaste_2025-12-27_16-03-17.png', '2026-01-07', '2026-01-15 21:29:10', 0);
+INSERT INTO `vehicle_certificates` (`id`, `vehicle_id`, `certificate_category`, `vehicle_part`, `file_url`, `file_name`, `expiry_date`, `uploaded_at`, `is_deleted`) VALUES (9, 1, 'license', 'tail', 'http://121.43.228.102:9000/clgl/b17330dc-1c32-4408-8f2d-bbcae00a3046.jpg', 'avatar.jpg', '2026-01-22', '2026-01-15 21:29:21', 0);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for vehicle_heads
+-- ----------------------------
+DROP TABLE IF EXISTS `vehicle_heads`;
+CREATE TABLE `vehicle_heads` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `plate_number` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `is_active` tinyint(1) DEFAULT '1',
+  `is_deleted` tinyint(1) DEFAULT '0',
+  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
+  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  `category` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'owned' COMMENT '车辆分类: owned(自有车辆), other(其他车辆)',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `plate_number` (`plate_number`),
+  KEY `idx_plate_number` (`plate_number`),
+  KEY `idx_is_active` (`is_active`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车头表';
+
+-- ----------------------------
+-- Records of vehicle_heads
+-- ----------------------------
+BEGIN;
+INSERT INTO `vehicle_heads` (`id`, `plate_number`, `is_active`, `is_deleted`, `created_at`, `updated_at`, `category`) VALUES (1, '皖P13231', 1, 0, '2026-01-15 19:18:40', '2026-01-15 19:18:40', 'owned');
+INSERT INTO `vehicle_heads` (`id`, `plate_number`, `is_active`, `is_deleted`, `created_at`, `updated_at`, `category`) VALUES (2, '皖D13223', 1, 1, '2026-01-15 21:16:13', '2026-01-15 21:30:24', 'owned');
+INSERT INTO `vehicle_heads` (`id`, `plate_number`, `is_active`, `is_deleted`, `created_at`, `updated_at`, `category`) VALUES (3, '皖S12222', 1, 0, '2026-01-15 22:28:32', '2026-01-15 22:28:32', 'owned');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for vehicle_plans
+-- ----------------------------
+DROP TABLE IF EXISTS `vehicle_plans`;
+CREATE TABLE `vehicle_plans` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `plan_date` date NOT NULL COMMENT '计划日期',
+  `start_time` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '开始时间 (HH:MM格式)',
+  `end_time` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '结束时间 (HH:MM格式)',
+  `customer_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户名称',
+  `product_id` int(11) NOT NULL COMMENT '商品ID',
+  `quantity` float NOT NULL COMMENT '数量',
+  `unit` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '单位',
+  `loading_manufacturer_id` int(11) NOT NULL COMMENT '装货厂商ID',
+  `unloading_manufacturer_id` int(11) NOT NULL COMMENT '卸货厂商ID',
+  `vehicle_id` int(11) NOT NULL COMMENT '车辆ID',
+  `driver_id` int(11) NOT NULL COMMENT '司机ID',
+  `escort_id` int(11) NOT NULL COMMENT '押运员ID',
+  `loading_arrival_time` datetime DEFAULT NULL COMMENT '装货到场时间',
+  `loading_departure_time` datetime DEFAULT NULL COMMENT '装货离场时间',
+  `unloading_arrival_time` datetime DEFAULT NULL COMMENT '卸货到场时间',
+  `unloading_departure_time` datetime DEFAULT NULL COMMENT '卸货离场时间',
+  `status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending' COMMENT '状态: pending(待开始), loading(装货中), unloading(卸货中), completed(已完成)',
+  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  KEY `idx_plan_date` (`plan_date`),
+  KEY `idx_status` (`status`),
+  KEY `idx_driver_id` (`driver_id`),
+  KEY `idx_escort_id` (`escort_id`),
+  KEY `product_id` (`product_id`),
+  KEY `loading_manufacturer_id` (`loading_manufacturer_id`),
+  KEY `unloading_manufacturer_id` (`unloading_manufacturer_id`),
+  KEY `vehicle_id` (`vehicle_id`),
+  KEY `idx_vehicle_plans_date_status` (`plan_date`,`status`),
+  KEY `idx_vehicle_plans_driver_escort` (`driver_id`,`escort_id`),
+  CONSTRAINT `vehicle_plans_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`),
+  CONSTRAINT `vehicle_plans_ibfk_2` FOREIGN KEY (`loading_manufacturer_id`) REFERENCES `manufacturers` (`id`),
+  CONSTRAINT `vehicle_plans_ibfk_3` FOREIGN KEY (`unloading_manufacturer_id`) REFERENCES `manufacturers` (`id`),
+  CONSTRAINT `vehicle_plans_ibfk_4` FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles` (`id`),
+  CONSTRAINT `vehicle_plans_ibfk_5` FOREIGN KEY (`driver_id`) REFERENCES `users` (`id`),
+  CONSTRAINT `vehicle_plans_ibfk_6` FOREIGN KEY (`escort_id`) REFERENCES `users` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆计划表';
+
+-- ----------------------------
+-- Records of vehicle_plans
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for vehicle_tails
+-- ----------------------------
+DROP TABLE IF EXISTS `vehicle_tails`;
+CREATE TABLE `vehicle_tails` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `plate_number` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `is_active` tinyint(1) DEFAULT '1',
+  `is_deleted` tinyint(1) DEFAULT '0',
+  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
+  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  `category` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'owned' COMMENT '车辆分类: owned(自有车辆), other(其他车辆)',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `plate_number` (`plate_number`),
+  KEY `idx_plate_number` (`plate_number`),
+  KEY `idx_is_active` (`is_active`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车尾表';
+
+-- ----------------------------
+-- Records of vehicle_tails
+-- ----------------------------
+BEGIN;
+INSERT INTO `vehicle_tails` (`id`, `plate_number`, `is_active`, `is_deleted`, `created_at`, `updated_at`, `category`) VALUES (1, '皖A09871', 0, 0, '2026-01-15 19:18:41', '2026-01-15 21:29:50', 'owned');
+INSERT INTO `vehicle_tails` (`id`, `plate_number`, `is_active`, `is_deleted`, `created_at`, `updated_at`, `category`) VALUES (2, '皖A12333', 1, 0, '2026-01-15 22:28:39', '2026-01-15 22:28:39', 'owned');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for vehicles
+-- ----------------------------
+DROP TABLE IF EXISTS `vehicles`;
+CREATE TABLE `vehicles` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `category` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类: owned(自有车辆), other(其他车辆)',
+  `head_id` int(11) DEFAULT NULL,
+  `tail_id` int(11) DEFAULT NULL,
+  `default_driver_id` int(11) DEFAULT NULL COMMENT '默认司机ID',
+  `default_escort_id` int(11) DEFAULT NULL COMMENT '默认押运员ID',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
+  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除: 0-正常, 1-删除',
+  PRIMARY KEY (`id`),
+  KEY `idx_category` (`category`),
+  KEY `idx_is_active` (`is_active`),
+  KEY `default_driver_id` (`default_driver_id`),
+  KEY `default_escort_id` (`default_escort_id`),
+  KEY `fk_vehicle_head` (`head_id`),
+  KEY `fk_vehicle_tail` (`tail_id`),
+  CONSTRAINT `fk_vehicle_head` FOREIGN KEY (`head_id`) REFERENCES `vehicle_heads` (`id`),
+  CONSTRAINT `fk_vehicle_tail` FOREIGN KEY (`tail_id`) REFERENCES `vehicle_tails` (`id`) ON DELETE SET NULL,
+  CONSTRAINT `vehicles_ibfk_1` FOREIGN KEY (`default_driver_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
+  CONSTRAINT `vehicles_ibfk_2` FOREIGN KEY (`default_escort_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆表';
+
+-- ----------------------------
+-- Records of vehicles
+-- ----------------------------
+BEGIN;
+INSERT INTO `vehicles` (`id`, `category`, `head_id`, `tail_id`, `default_driver_id`, `default_escort_id`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (1, 'owned', 1, 1, 10, 9, 1, '2026-01-15 19:02:06', '2026-01-15 22:35:59', 1);
+INSERT INTO `vehicles` (`id`, `category`, `head_id`, `tail_id`, `default_driver_id`, `default_escort_id`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (2, 'other', 1, 1, 10, 9, 1, '2026-01-15 22:28:10', '2026-01-15 22:43:35', 0);
+INSERT INTO `vehicles` (`id`, `category`, `head_id`, `tail_id`, `default_driver_id`, `default_escort_id`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (3, 'owned', 3, 2, 10, 9, 0, '2026-01-15 22:29:18', '2026-01-15 23:45:59', 0);
+INSERT INTO `vehicles` (`id`, `category`, `head_id`, `tail_id`, `default_driver_id`, `default_escort_id`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (4, 'owned', 1, 2, 10, 9, 1, '2026-01-15 22:33:46', '2026-01-15 22:33:46', 0);
+INSERT INTO `vehicles` (`id`, `category`, `head_id`, `tail_id`, `default_driver_id`, `default_escort_id`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (5, 'owned', 1, 2, 10, 9, 1, '2026-01-15 22:33:58', '2026-01-15 22:33:58', 0);
+INSERT INTO `vehicles` (`id`, `category`, `head_id`, `tail_id`, `default_driver_id`, `default_escort_id`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (6, 'owned', 1, 2, 10, 9, 1, '2026-01-15 22:35:36', '2026-01-15 22:35:36', 0);
+INSERT INTO `vehicles` (`id`, `category`, `head_id`, `tail_id`, `default_driver_id`, `default_escort_id`, `is_active`, `created_at`, `updated_at`, `is_deleted`) VALUES (7, 'owned', 1, 2, 10, 9, 1, '2026-01-15 22:35:54', '2026-01-15 22:35:54', 0);
+COMMIT;
+
+-- ----------------------------
+-- Triggers structure for table manufacturers
+-- ----------------------------
+DROP TRIGGER IF EXISTS `update_manufacturers_timestamp`;
+delimiter ;;
+CREATE TRIGGER `vehicle_management`.`update_manufacturers_timestamp` BEFORE UPDATE ON `manufacturers` FOR EACH ROW BEGIN
+    SET NEW.updated_at = CURRENT_TIMESTAMP;
+END
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table products
+-- ----------------------------
+DROP TRIGGER IF EXISTS `update_products_timestamp`;
+delimiter ;;
+CREATE TRIGGER `vehicle_management`.`update_products_timestamp` BEFORE UPDATE ON `products` FOR EACH ROW BEGIN
+    SET NEW.updated_at = CURRENT_TIMESTAMP;
+END
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table users
+-- ----------------------------
+DROP TRIGGER IF EXISTS `update_users_timestamp`;
+delimiter ;;
+CREATE TRIGGER `vehicle_management`.`update_users_timestamp` BEFORE UPDATE ON `users` FOR EACH ROW BEGIN
+    SET NEW.updated_at = CURRENT_TIMESTAMP;
+END
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table vehicle_heads
+-- ----------------------------
+DROP TRIGGER IF EXISTS `update_vehicle_heads_timestamp`;
+delimiter ;;
+CREATE TRIGGER `vehicle_management`.`update_vehicle_heads_timestamp` BEFORE UPDATE ON `vehicle_heads` FOR EACH ROW SET NEW.updated_at = CURRENT_TIMESTAMP
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table vehicle_plans
+-- ----------------------------
+DROP TRIGGER IF EXISTS `update_vehicle_plans_timestamp`;
+delimiter ;;
+CREATE TRIGGER `vehicle_management`.`update_vehicle_plans_timestamp` BEFORE UPDATE ON `vehicle_plans` FOR EACH ROW BEGIN
+    SET NEW.updated_at = CURRENT_TIMESTAMP;
+END
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table vehicle_tails
+-- ----------------------------
+DROP TRIGGER IF EXISTS `update_vehicle_tails_timestamp`;
+delimiter ;;
+CREATE TRIGGER `vehicle_management`.`update_vehicle_tails_timestamp` BEFORE UPDATE ON `vehicle_tails` FOR EACH ROW SET NEW.updated_at = CURRENT_TIMESTAMP
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table vehicles
+-- ----------------------------
+DROP TRIGGER IF EXISTS `update_vehicles_timestamp`;
+delimiter ;;
+CREATE TRIGGER `vehicle_management`.`update_vehicles_timestamp` BEFORE UPDATE ON `vehicles` FOR EACH ROW BEGIN
+    SET NEW.updated_at = CURRENT_TIMESTAMP;
+END
+;;
+delimiter ;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 61 - 53
plan/计划需求.md

@@ -1,53 +1,61 @@
-# 需求
-
-有个计划总表
-每天由程欢每天来创建计划
-由郑丽娜录入到ERP系统中
-对于大公司的计划单独列出来
-产品信息表
-客户信息表
-供应商信息表
-缺一个运输信息表
-
-单据类别:直销单、出库单、入库单、云栋单 。 云栋单怎么处理
-程欢接到方金强的信息就会创建计划单,出库数是一个预估的值,未来根据实际的量来回填
-送货方式:山峡自提、一票制、客户自提
-
-计划单下一步就是通知送货,通知收货。通过跟进状态来安排。默认 待安排 、待入库(待出库)、
-入库计划
-出库计划 需要安排车子的 需要通知车辆
-客户自提 或者 自己安排车子来处理
-自己去提,客户送到
-
-计划出来了,就是通知分为 通知收货 还是 通知送货
-计划生成后 待安排,待入库、待出库、已完成
-对于待入库、待出库的单子要为未来的盘存做准备
-
-计划单分跟进状态和完成状态
-
-给司机一个二维码,来完成数据和订单的上传,线下还要把这个收据收回来
-
-由郑丽娜来完成附件的上传,数据填写完成这些订单
-
-可以关联我们的地磅单系统,如果是第三方的照片无法识别
-
-运费放在最后一步填写
-
-价格分年度框架合同、
-价格可能提前出来,可能延后出来
-
-自有车辆有车牌号
-运输队车牌号不需要强制填写, 运输费用放开自己填写, 分 趟, 分吨位 。 运费要可以勾选进行结算运费,审核后,付完了,已开票, 有个状态,已开票,未开票
-
-怎么跟地磅单结合,直接跟地磅单结合好,自动根据送货方、收货方是山峡,自动就生成了地磅单
-
-地磅单计划单就没用了,每个自动生成大计划,每个月自动可以填写运输信息
-
-还有个调价功能,根据调价函来发起操作
-
-三方厂家可以提交送货、收货的信息,要经过审核,然后地磅单就可以看见
-
-涉及到库存
-
-易制毒的证书 购买证书 运输证书 才可以发货
-产品要分易制毒 易制爆
+# 现状
+
+- `crm/web` 这个是crm系统的前端,包括了线索管理、客户管理、商机管理、报价单管理、合同管理、产品管理及系统管理的功能
+- `crm/backend` 这个是crm系统的后端java代码,生产环境再用的代码
+- `crm/py-backend` 这个是crm系统的后端,pyhton代码重构的,还未进行测试
+
+# 新需求
+
+开发一套计划管理模块,计划管理模块包括计划管理、运输公司管理、库存管理、运输管理
+
+## 计划管理
+
+- 业务过程
+    - 第一步:计划管理从合同模块开始发起计划。一个计划可以包括多个产品。这里要注意框架合同的商品价格会根据调价函进行调整。所以对于这些框架合同支持调整价格,每次价格调整都要有个调价函并记录好各个版本历史(这里要改造合同模块的功能)
+    - 第二步:计划中产品有易制毒、易制爆的属性,则必须上传购买证和运输证,才可以进行第三步
+    - 第三步:安排运输。运输分两种情况。一种是自有车辆、一种是运输公司的车辆。将信息生成一个二维码,由计划专员A复制线下发给司机或者现场的工作人员(待入库和待出库的单子需要同步给现场的工作人员)
+    - 第四步:如果是待入库、待出库的单子,就会自动生成地磅单,地磅单信息也会在计划表中看见。待入库、待出库的单子,就是看看供应商名称或者目的地是不是“安庆市山峡化工有限公司”或者“安徽云栋化工有限公司”。如果目的地是“安庆市山峡化工有限公司”或者“安徽云栋化工有限公司” 就是 待入库、如果供应商是“安庆市山峡化工有限公司”或者“安徽云栋化工有限公司” ,就是待出库。 由现场工作人员确认后,提交地磅单单据、出入库单据附件。
+    - 第五步:司机通过扫描二维码回传运输单据,运输数量等信息
+    - 第六步:整个运输完成后,由计划专业B核对整个订单
+    - 第七步:由计划专业C对运输费用进行确认,然后归档
+- 计划包括这些字段:计划日期、计划订单编号(SXDDH20260101000001,其中SXDDH固定的,20260101是YYYYMMDD的日期,000001是序列号)、单据类别、跟进状态、完成状态、供应商名称、目的地(也是公司名称)、物料编号、物料名称、产品规格、包装规格、单位、入库数、入库预估数、入库价、入库金额、出库预估数、出库数、出库单价、出库金额、磅差、送货方式、运输编号、车牌号、运输公司、运输单位、运输费用、运费合计、单据附件、销售合同、跟进人、备注、创建人、创建时间、修改人、修改时间、通知信息
+- 单据类别包括:直销单、入库单、出库单、云栋单
+- 跟进状态包括:待采购、等客户通知、待安排、计划取消、待发货、已发货、待入库、已入库、待出库、已出库
+- 完成状态包括: 是、否
+- 入库价默认是合同中价格,但是可以后续修改
+- 入库金额等于入库数\*入库价
+- 出库金额等于出库数\*出库价
+- 磅差支持负数
+- 送货方式包括:山峡自提、一票制、客户自提
+- 通知信息是一个标准的信息模版:
+  "计划号:SXDDH20260102000002
+  企业名称:安徽英毅热电有限公司
+  提货单位:安庆市山峡化工有限公司
+  到货时间:2026-01-02
+  药品名称:31% 工业盐酸
+  到货吨位:【4.7吨】
+  备注:"
+- 计划中产品属性如果是易制毒、易制爆的属性,那么就需要购买证、运输证、必须这两个证书具备了,才可以进行运输安排。(目前产品需要增加是否易制毒、是否易制爆的配置)
+- 运输结算
+    - 有个模块可以看见历史所有的运输情况总揽
+    - 多选运输单,生成运输结算单,提交审核后,审核成功后,通知对方开票,票据上传后给对方汇款,上传汇款单据信息
+    - 运费分两种形式,一种按照重量计算,可以通过运输数量\*运输单价进行计算,计算出来的也可以调整。一种是按照趟次的,那这个不计算,按照手填模式。
+- 盘存统计分析功能
+
+## 自有车辆信息维护
+
+自有车辆的信息字段可以参考`/Users/wuzilong/work/00code/shanxia/plan/vehicle_management.sql` 进行设计,这个车辆管理的信息比较全
+
+## 运输公司及运输公司车辆信息维护
+
+运输公司的车辆信息也可以参考`/Users/wuzilong/work/00code/shanxia/plan/vehicle_management.sql` 进行设计,这个车辆管理的信息比较全
+
+## 运输信息表
+
+运输信息表包括这些字段:运输编号(SXYS0000001,其中SXYS是固定的,0000001 是序列号),车牌号、运输公司、所属公司(运输公司所属的人或公司名称)、运输介质、运输起点、运输终点、运输公里数、单位(吨/躺)、运输费用、创建人、是否开票、备注
+
+## 要求
+
+- crm系统和财务报销系统的sql模型`plan/sx.sql`
+- 计划的表名都用plan\_开头
+- 计划系统的用户管理、角色管理这块的功能复用crm系统的,方便功能衔接

Some files were not shown because too many files changed in this diff