zlwu hace 5 meses
padre
commit
832cff3488

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

@@ -1,15 +1,15 @@
 -- 报销菜单:三个业务分组为一级目录(parent_id=0),不再使用「报销业务」父级 id=300。
 -- 报销菜单:三个业务分组为一级目录(parent_id=0),不再使用「报销业务」父级 id=300。
 -- 结构:
 -- 结构:
---   310 我的申请 → 311 备用金申请、321 报销申请
---   320 我的审批 → 312 备用金审批、322 报销审批
+--   310 我的 → 311 备用金申请、321 报销申请
+--   320 待办 → 312 备用金审批、322 报销审批
 --   330 发放/冲抵 → 313 备用金发放管理、323 支付管理(发放/冲抵)
 --   330 发放/冲抵 → 313 备用金发放管理、323 支付管理(发放/冲抵)
 -- 一级排序:基础管理(100)=1,基础配置(200)=2,本脚本 310/320/330=3/4/5,视图中心(400)=6。
 -- 一级排序:基础管理(100)=1,基础配置(200)=2,本脚本 310/320/330=3/4/5,视图中心(400)=6。
 -- 若 id=330 已存在且用途不同,请先调整本脚本中的 id。
 -- 若 id=330 已存在且用途不同,请先调整本脚本中的 id。
 
 
 START TRANSACTION;
 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 (
 INSERT INTO bx_menu (
   id, parent_id, menu_name, menu_code, menu_type, path, icon, sort_order, visible, status,
   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: 'project', title: '费用项目管理', group: '基础配置' },
   { id: 'wf-advance', title: '工作流配置(备用金)', group: '基础配置' },
   { id: 'wf-advance', title: '工作流配置(备用金)', group: '基础配置' },
   { id: 'wf-reimburse', 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: 'advance-issue', title: '备用金发放管理', group: '发放/冲抵' },
   { id: 'payment', 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: [
     children: [
       { id: 311, parentId: 310, menuName: '备用金申请', menuCode: 'advance-apply', menuType: '菜单', path: '/biz/advance-apply', sortOrder: 1, status: '启用' },
       { 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: 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: [
     children: [
       { id: 312, parentId: 320, menuName: '备用金审批', menuCode: 'advance-approve', menuType: '菜单', path: '/biz/advance-approve', sortOrder: 1, status: '启用' },
       { 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: '启用' }
       { 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 {
 .logo {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
   font-size: 16px;
   font-size: 16px;
   font-weight: 700;
   font-weight: 700;
-  color: var(--text);
+  color: var(--text-muted);
   padding: 0 12px 20px;
   padding: 0 12px 20px;
   margin-bottom: 8px;
   margin-bottom: 8px;
   border-bottom: 1px solid var(--border);
   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);
   color: var(--primary);
+  font-weight: 700;
+  letter-spacing: 0.02em;
+  font-size: 18px;
+  line-height: 1.2;
+  text-align: center;
 }
 }
 
 
 .nav-tree {
 .nav-tree {
@@ -161,7 +187,7 @@ body {
   border-radius: var(--radius);
   border-radius: var(--radius);
   font-size: 12px;
   font-size: 12px;
   font-weight: 700;
   font-weight: 700;
-  color: #000000;
+  color: var(--text-muted);
   letter-spacing: 0.02em;
   letter-spacing: 0.02em;
   user-select: none;
   user-select: none;
 }
 }
@@ -174,10 +200,10 @@ body {
   content: "";
   content: "";
   width: 6px;
   width: 6px;
   height: 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);
   transform: rotate(-45deg);
-  transition: transform 0.2s ease;
+  transition: transform 0.2s ease, border-color 0.15s ease;
 }
 }
 
 
 .nav-details[open] > summary::after {
 .nav-details[open] > summary::after {
@@ -187,7 +213,12 @@ body {
 
 
 .nav-details > summary:hover {
 .nav-details > summary:hover {
   background: var(--row-alt);
   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 {
 .nav-summary-main {
@@ -207,11 +238,11 @@ body {
 .nav-summary-ico {
 .nav-summary-ico {
   flex-shrink: 0;
   flex-shrink: 0;
   font-size: 16px;
   font-size: 16px;
-  color: #000000;
+  color: var(--text-muted);
 }
 }
 
 
 .nav-details > summary:hover .nav-summary-ico {
 .nav-details > summary:hover .nav-summary-ico {
-  color: #000000;
+  color: var(--text);
 }
 }
 
 
 .nav-sub {
 .nav-sub {
@@ -240,7 +271,7 @@ body {
   margin: 2px 0;
   margin: 2px 0;
   padding: 8px 12px 8px 14px;
   padding: 8px 12px 8px 14px;
   border-radius: var(--radius);
   border-radius: var(--radius);
-  color: #000000;
+  color: var(--text-muted);
   text-decoration: none;
   text-decoration: none;
   font-size: 13px;
   font-size: 13px;
   font-weight: 500;
   font-weight: 500;
@@ -249,7 +280,7 @@ body {
 .menu-link-ico {
 .menu-link-ico {
   flex-shrink: 0;
   flex-shrink: 0;
   font-size: 16px;
   font-size: 16px;
-  color: #000000;
+  color: var(--text-muted);
 }
 }
 
 
 .menu-link-text {
 .menu-link-text {
@@ -262,21 +293,21 @@ body {
 
 
 .menu-link:hover {
 .menu-link:hover {
   background: var(--row-alt);
   background: var(--row-alt);
-  color: #000000;
+  color: var(--text);
 }
 }
 
 
 .menu-link:hover .menu-link-ico {
 .menu-link:hover .menu-link-ico {
-  color: #000000;
+  color: var(--text);
 }
 }
 
 
 .menu-link.active {
 .menu-link.active {
   background: var(--primary-bg);
   background: var(--primary-bg);
-  color: #000000;
+  color: var(--primary-dark);
   font-weight: 600;
   font-weight: 600;
 }
 }
 
 
 .menu-link.active .menu-link-ico {
 .menu-link.active .menu-link-ico {
-  color: #000000;
+  color: var(--primary-dark);
 }
 }
 
 
 .main {
 .main {
@@ -286,14 +317,44 @@ body {
   flex-direction: column;
   flex-direction: column;
 }
 }
 
 
-.header {
+.main > .header {
   height: 56px;
   height: 56px;
   padding: 0 24px;
   padding: 0 24px;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: space-between;
   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 {
 .header-left {
@@ -398,7 +459,6 @@ body {
   width: 24px;
   width: 24px;
   height: 24px;
   height: 24px;
   border-radius: 50%;
   border-radius: 50%;
-  background: linear-gradient(135deg, #34d399, #10b981);
   color: #fff;
   color: #fff;
   display: inline-flex;
   display: inline-flex;
   align-items: center;
   align-items: center;

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

@@ -29,6 +29,7 @@ import {
   Wallet,
   Wallet,
 } from '@element-plus/icons-vue'
 } from '@element-plus/icons-vue'
 import { useAuthStore, type MenuItem } from '../stores/auth'
 import { useAuthStore, type MenuItem } from '../stores/auth'
+import logoGif from '../assets/logo.gif'
 
 
 const router = useRouter()
 const router = useRouter()
 
 
@@ -111,8 +112,8 @@ function resolveLevel1Icon(menuCode: string | null | undefined, menuName?: strin
   const name = menuName || ''
   const name = menuName || ''
   if (/基础管理/.test(name)) return Management
   if (/基础管理/.test(name)) return Management
   if (/基础配置|系统配置/.test(name)) return Setting
   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 Wallet
   if (/报销/.test(name)) return Operation
   if (/报销/.test(name)) return Operation
   if (/视图|看板|统计/.test(name)) return Histogram
   if (/视图|看板|统计/.test(name)) return Histogram
@@ -255,7 +256,12 @@ onMounted(async () => {
 <template>
 <template>
   <div class="bx-layout" @click="closeContextMenu">
   <div class="bx-layout" @click="closeContextMenu">
     <aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
     <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>
       <el-scrollbar>
         <div class="nav-tree">
         <div class="nav-tree">
           <template v-for="group in auth.menuList" :key="group.id">
           <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):
 -- 菜单 ID 说明(bx-schema-reset.sql 中固定 ID):
 --   100  基础管理(目录)       101  用户管理     102  角色管理     103  菜单管理
 --   100  基础管理(目录)       101  用户管理     102  角色管理     103  菜单管理
 --   200  基础配置(目录)       201  费用项目管理  202  工作流配置(备用金)  203  工作流配置(报销单)
 --   200  基础配置(目录)       201  费用项目管理  202  工作流配置(备用金)  203  工作流配置(报销单)
---   310  我的申请(一级目录)    311  备用金申请     321  报销申请
---   320  我的审批(一级目录)    312  备用金审批     322  报销审批
+--   310  我的(一级目录)        311  备用金申请     321  报销申请
+--   320  待办(一级目录)        312  备用金审批     322  报销审批
 --   330  发放/冲抵(一级目录)   313  备用金发放管理  323  支付管理(发放/冲抵)
 --   330  发放/冲抵(一级目录)   313  备用金发放管理  323  支付管理(发放/冲抵)
 --   400  视图中心(目录)        401  我的申请单     402  待我审批      403  全部申请单
 --   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),
 (100, 1), (101, 1), (102, 1), (103, 1),
 -- 基础配置
 -- 基础配置
 (200, 1), (201, 1), (202, 1), (203, 1),
 (200, 1), (201, 1), (202, 1), (203, 1),
--- 我的申请 / 我的审批 / 发放冲抵
+-- 我的 / 待办 / 发放冲抵
 (310, 1), (311, 1), (321, 1),
 (310, 1), (311, 1), (321, 1),
 (320, 1), (312, 1), (322, 1),
 (320, 1), (312, 1), (322, 1),
 (330, 1), (313, 1), (323, 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),
 (202, 200, '工作流配置(备用金)', 'wf-advance', 2, '/config/wf-advance', 2),
 (203, 200, '工作流配置(报销单)', 'wf-reimburse', 2, '/config/wf-reimburse', 3);
 (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
 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),
 (311, 310, '备用金申请', 'advance-apply', 2, '/biz/advance-apply', 1),
 (321, 310, '报销申请', 'reimburse-apply', 2, '/biz/reimburse-apply', 2),
 (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),
 (312, 320, '备用金审批', 'advance-approve', 2, '/biz/advance-approve', 1),
 (322, 320, '报销审批', 'reimburse-approve', 2, '/biz/reimburse-approve', 2),
 (322, 320, '报销审批', 'reimburse-approve', 2, '/biz/reimburse-approve', 2),
 (330, 0, '发放/冲抵', NULL, 1, NULL, 5),
 (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),
 (202, 200, '工作流配置(备用金)', 'wf-advance', 2, '/config/wf-advance', 2),
 (203, 200, '工作流配置(报销单)', 'wf-reimburse', 2, '/config/wf-reimburse', 3);
 (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
 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),
 (311, 310, '备用金申请', 'advance-apply', 2, '/biz/advance-apply', 1),
 (321, 310, '报销申请', 'reimburse-apply', 2, '/biz/reimburse-apply', 2),
 (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),
 (312, 320, '备用金审批', 'advance-approve', 2, '/biz/advance-approve', 1),
 (322, 320, '报销审批', 'reimburse-approve', 2, '/biz/reimburse-approve', 2),
 (322, 320, '报销审批', 'reimburse-approve', 2, '/biz/reimburse-approve', 2),
 (330, 0, '发放/冲抵', NULL, 1, NULL, 5),
 (330, 0, '发放/冲抵', NULL, 1, NULL, 5),

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 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系统的,方便功能衔接

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio