wuqingchuimiexiaoshanhe 1 nedēļu atpakaļ
vecāks
revīzija
bff41e3c9d

+ 338 - 0
app/admin/controller/sys_post.go

@@ -0,0 +1,338 @@
+package controller
+
+import (
+	"cold-delivery/app/admin/model"
+	"cold-delivery/app/admin/service"
+	"cold-delivery/app/admin/service/dto"
+	"cold-delivery/common/actions"
+	"cold-delivery/common/global"
+	"fmt"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+	"github.com/gin-gonic/gin/binding"
+	"gogs.baozhida.cn/zoie/OAuth-core/api"
+	"gogs.baozhida.cn/zoie/OAuth-core/pkg/jwtauth/user"
+)
+
+type SysPostController struct {
+	api.Api
+}
+
+// GetPage 获取岗位列表
+// @Summary 获取岗位列表
+// @Description 分页查询岗位列表
+// @Tags 岗位管理
+// @Param postName query string false "岗位名称"
+// @Param postCode query string false "岗位编码"
+// @Param status query string false "状态"
+// @Param deptId query int false "部门ID"
+// @Param page query int false "页码"
+// @Param pageSize query int false "每页数量"
+// @Success 200 {object} response.Response{data=response.Page{list=[]model.SysPost}} "{"code": 200, "data": [...]}"
+// @Router /api/post [get]
+// @Security Bearer
+func (e SysPostController) GetPage(c *gin.Context) {
+	s := service.SysPost{}
+	req := dto.SysPostGetPageReq{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		Bind(&req, binding.Query).
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	p := actions.GetPermissionFromContext(c)
+	list := make([]model.SysPost, 0)
+	var count int64
+	err = s.GetPage(&req, &list, &count, p)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
+}
+
+// Get 获取单个岗位详情
+// @Summary 获取单个岗位
+// @Description 获取岗位详情(含权限菜单ID列表)
+// @Tags 岗位管理
+// @Param id path int true "岗位ID"
+// @Success 200 {object} response.Response{data=model.SysPost} "{"code": 200, "data": {...}}"
+// @Router /api/post/{id} [get]
+// @Security Bearer
+func (e SysPostController) Get(c *gin.Context) {
+	s := service.SysPost{}
+	req := dto.SysPostGetReq{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		Bind(&req, nil).
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	p := actions.GetPermissionFromContext(c)
+	var postModel model.SysPost
+	err = s.Get(&req, &postModel, p)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(postModel, "查询成功")
+}
+
+// Insert 新增岗位
+// @Summary 新增岗位
+// @Description 新增岗位(含菜单权限分配)
+// @Tags 岗位管理
+// @Param data body dto.SysPostInsertReq true "岗位信息"
+// @Success 200 {object} response.Response "{"code": 200, "msg": "添加成功"}"
+// @Router /api/post [post]
+// @Security Bearer
+func (e SysPostController) Insert(c *gin.Context) {
+	s := service.SysPost{}
+	req := dto.SysPostInsertReq{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		Bind(&req, binding.JSON).
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	req.SetCreateBy(user.GetUserId(c))
+
+	err = s.Insert(&req)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(req.Id, "添加成功")
+}
+
+// Update 修改岗位
+// @Summary 修改岗位
+// @Description 修改岗位(含菜单权限重新分配)
+// @Tags 岗位管理
+// @Param data body dto.SysPostUpdateReq true "岗位信息"
+// @Success 200 {object} response.Response "{"code": 200, "msg": "更新成功"}"
+// @Router /api/post [put]
+// @Security Bearer
+func (e SysPostController) Update(c *gin.Context) {
+	s := service.SysPost{}
+	req := dto.SysPostUpdateReq{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		Bind(&req, binding.JSON).
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	req.SetUpdateBy(user.GetUserId(c))
+	p := actions.GetPermissionFromContext(c)
+
+	cb, err := global.LoadPolicy(c)
+	if err != nil {
+		e.Error(500, err, "Casbin加载失败")
+		return
+	}
+
+	err = s.Update(&req, p, cb)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(req.Id, "更新成功")
+}
+
+// Delete 删除岗位(逻辑删除)
+// @Summary 删除岗位
+// @Description 删除岗位(逻辑删除,检查是否有用户关联)
+// @Tags 岗位管理
+// @Param data body dto.SysPostDeleteReq true "岗位ID"
+// @Success 200 {object} response.Response "{"code": 200, "msg": "删除成功"}"
+// @Router /api/post [delete]
+// @Security Bearer
+func (e SysPostController) Delete(c *gin.Context) {
+	s := service.SysPost{}
+	req := dto.SysPostDeleteReq{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		Bind(&req, binding.JSON).
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	p := actions.GetPermissionFromContext(c)
+
+	cb, err := global.LoadPolicy(c)
+	if err != nil {
+		e.Error(500, err, "Casbin加载失败")
+		return
+	}
+
+	err = s.Remove(&req, p, cb)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(req.Id, "删除成功")
+}
+
+// GetAll 获取所有岗位(下拉选择用)
+// @Summary 获取所有岗位
+// @Description 获取所有岗位(用于下拉选择)
+// @Tags 岗位管理
+// @Param deptId query int false "部门ID"
+// @Success 200 {object} response.Response{data=[]model.SysPost} "{"code": 200, "data": [...]}"
+// @Router /api/post/all [get]
+// @Security Bearer
+func (e SysPostController) GetAll(c *gin.Context) {
+	s := service.SysPost{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	p := actions.GetPermissionFromContext(c)
+	// 从前端请求参数获取 deptId,不用当前登录用户的部门ID
+	deptIdStr := c.Query("deptId")
+	deptId := 0
+	if deptIdStr != "" {
+		fmt.Sscanf(deptIdStr, "%d", &deptId)
+	}
+
+	list := make([]model.SysPost, 0)
+	err = s.GetAll(deptId, &list, p)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(list, "查询成功")
+}
+
+// GetMenuTree 获取菜单权限树
+// @Summary 获取菜单权限树
+// @Description 获取完整菜单树(用于权限分配界面)
+// @Tags 岗位管理
+// @Success 200 {object} response.Response{data=[]dto.MenuLabel} "{"code": 200, "data": [...]}"
+// @Router /api/post/menu-tree [get]
+// @Security Bearer
+func (e SysPostController) GetMenuTree(c *gin.Context) {
+	s := service.SysPost{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	tree, err := s.GetMenuTree()
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(tree, "查询成功")
+}
+
+// GetPostMenuIds 获取岗位已分配的菜单ID
+// @Summary 获取岗位菜单权限
+// @Description 获取岗位已分配的菜单ID列表
+// @Tags 岗位管理
+// @Param id path int true "岗位ID"
+// @Success 200 {object} response.Response{data=dto.SysPostMenuIdsResp} "{"code": 200, "data": {"menuIds": [...]}}"
+// @Router /api/post/{id}/menu-ids [get]
+// @Security Bearer
+func (e SysPostController) GetPostMenuIds(c *gin.Context) {
+	s := service.SysPost{}
+	req := dto.SysPostGetReq{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		Bind(&req, nil).
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	menuIds, err := s.GetPostMenuIds(req.Id)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(dto.SysPostMenuIdsResp{MenuIds: menuIds}, "查询成功")
+}
+
+// GetPostNames 根据ID列表批量获取岗位名称
+// @Summary 根据ID列表获取岗位名称
+// @Description 根据岗位ID列表批量查询岗位名称,返回 id→名称 的映射
+// @Tags 岗位管理
+// @Param ids query string true "岗位ID列表(逗号分隔,如 1,2,3)"
+// @Success 200 {object} response.Response{data=map[int]string} "{"code": 200, "data": {"1":"管理员","2":"配送员"}}"
+// @Router /api/post/names [get]
+// @Security Bearer
+func (e SysPostController) GetPostNames(c *gin.Context) {
+	s := service.SysPost{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		MakeService(&s.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+
+	idsStr := c.Query("ids")
+	if idsStr == "" {
+		e.OK(map[int]string{}, "查询成功")
+		return
+	}
+
+	// 解析逗号分隔的ID列表
+	idStrs := strings.Split(idsStr, ",")
+	ids := make([]int, 0, len(idStrs))
+	for _, s := range idStrs {
+		id := 0
+		fmt.Sscanf(strings.TrimSpace(s), "%d", &id)
+		if id > 0 {
+			ids = append(ids, id)
+		}
+	}
+
+	result, err := s.GetPostNamesByIds(ids)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(result, "查询成功")
+}

+ 15 - 1
app/admin/controller/sys_user.go

@@ -360,8 +360,21 @@ func (e SysUser) GetProfile(c *gin.Context) {
 		e.Error(500, err, "获取用户信息失败")
 		return
 	}
+	// 获取岗位菜单树
+	var menus interface{}
+	var menuIds []int
+	if sysUser.PostId > 0 {
+		postSvc := service.SysPost{}
+		postSvc.Orm = s.Orm
+		postSvc.Log = s.Log
+		menuTree, _ := postSvc.GetMenuTree()
+		menuIds, _ = postSvc.GetPostMenuIds(sysUser.PostId)
+		menus = postSvc.FilterMenuTree(menuTree, menuIds)
+	}
 	e.OK(gin.H{
-		"user": sysUser,
+		"user":        sysUser,
+		"menus":       menus,
+		"postMenuIds": menuIds,
 	}, "查询成功")
 }
 
@@ -413,6 +426,7 @@ func (e SysUser) GetInfo(c *gin.Context) {
 	mp["userId"] = sysUser.Id
 	mp["deptId"] = sysUser.DeptId
 	mp["name"] = sysUser.NickName
+	mp["postId"] = sysUser.PostId
 	mp["code"] = 200
 	e.OK(mp, "查询成功")
 }

+ 75 - 13
app/admin/controller/waybill.go

@@ -82,7 +82,7 @@ func (e WaybillController) GetPage(c *gin.Context) {
 
 	list := make([]model.Waybill, 0)
 	var count int64
-	if userObj.Type == model.SysUserTypeSys {
+	if userObj.HasPost() {
 		err = s.GetPage(&req, &list, &count, p)
 	} else {
 		err = s.GetUserPage(&req, &list, &count, p)
@@ -91,16 +91,29 @@ func (e WaybillController) GetPage(c *gin.Context) {
 		e.Error(500, err, err.Error())
 		return
 	}
+	//for i, _ := range list {
+	//	var ReCheckuser model.SysUser
+	//	err = userSvc.GetUser(list[i].ReCheckId, &ReCheckuser)
+	//	if err != nil {
+	//		e.Error(500, err, err.Error())
+	//		return
+	//	}
+	//	list[i].ReCheck.Id = list[i].ReCheckId
+	//	list[i].ReCheck.NickName = ReCheckuser.NickName
+	//	list[i].ReCheck.Phone = ReCheckuser.Phone
+	//}
 	for i, _ := range list {
-		var ReCheckuser model.SysUser
-		err = userSvc.GetUser(list[i].ReCheckId, &ReCheckuser)
-		if err != nil {
-			e.Error(500, err, err.Error())
-			return
+		if list[i].ReCheckId != 0 {
+			var ReCheckuser model.SysUser
+			err = userSvc.GetUser(list[i].ReCheckId, &ReCheckuser)
+			if err != nil {
+				e.Logger.Error("获取复核人信息失败", "ReCheckId", list[i].ReCheckId, "err", err)
+				continue
+			}
+			list[i].ReCheck.Id = list[i].ReCheckId
+			list[i].ReCheck.NickName = ReCheckuser.NickName
+			list[i].ReCheck.Phone = ReCheckuser.Phone
 		}
-		list[i].ReCheck.Id = list[i].ReCheckId
-		list[i].ReCheck.NickName = ReCheckuser.NickName
-		list[i].ReCheck.Phone = ReCheckuser.Phone
 	}
 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
 }
@@ -235,7 +248,7 @@ func (e WaybillController) Home(c *gin.Context) {
 		return
 	}
 	var res dto.WaybillStatsRes
-	if userObj.Type == model.SysUserTypeSys {
+	if userObj.HasPost() {
 		res = s.GetBasicsStats(&req, p)
 	} else {
 		res = s.GetUserBasicsStats(&req, p, service.GetUserBasicsStatsScopes(p.UserId))
@@ -277,7 +290,7 @@ func (e WaybillController) UserStats(c *gin.Context) {
 	}
 	var res []dto.WaybillUserStats
 	if req.UserType == "delivery" {
-		if userObj.Type == model.SysUserTypeSys {
+		if userObj.HasPost() {
 			res = s.GetDeliveryStats(&req, p)
 		} else {
 			req.UserIds = []int{p.UserId}
@@ -285,7 +298,7 @@ func (e WaybillController) UserStats(c *gin.Context) {
 		}
 	}
 	if req.UserType == "reCheck" {
-		if userObj.Type == model.SysUserTypeSys {
+		if userObj.HasPost() {
 			res = s.GetReCheckStats(&req, p)
 		} else {
 			req.UserIds = []int{p.UserId}
@@ -643,7 +656,7 @@ func (e WaybillController) AdminAudit(c *gin.Context) {
 		e.Error(500, err, "获取用户信息失败")
 		return
 	}
-	if userObj.Type != model.SysUserTypeSys {
+	if !userObj.HasPost() && userObj.Id != 1 {
 		e.Error(500, err, err.Error())
 		return
 	}
@@ -656,6 +669,55 @@ func (e WaybillController) AdminAudit(c *gin.Context) {
 	e.OK(req.GetId(), "审核成功")
 }
 
+// Packing 待取药装箱
+// @Summary 待取药装箱
+// @Description 待取药装箱(分配保温箱、冰排、防伪标签)
+// @Tags 运单
+// @Accept  application/json
+// @Product application/json
+// @Param data body dto.WaybillPackingReq true "body"
+// @Success 200 {string} string	"{"code": 200, "message": "添加成功"}"
+// @Success 200 {string} string	"{"code": -1, "message": "添加失败"}"
+// @Router /api/waybill/packing [post]
+// @Security Bearer
+func (e WaybillController) Packing(c *gin.Context) {
+	s := service.Waybill{}
+	IceRaftSvc := service.IceRaft{}
+	req := dto.WaybillPackingReq{}
+	err := e.MakeContext(c).
+		MakeOrm().
+		Bind(&req).
+		MakeService(&s.Service).
+		MakeService(&IceRaftSvc.Service).
+		Errors
+	if err != nil {
+		e.Logger.Error(err)
+		e.Error(500, err, err.Error())
+		return
+	}
+	p := actions.GetPermissionFromContext(c)
+	if len(req.WaybillIds) == 0 {
+		e.Error(500, err, "运单ID不能为空")
+		return
+	}
+	oldIceRaftCode := make([]string, 0)
+	if req.CoolerBoxId != 0 {
+		err = IceRaftSvc.GetPageByCoolerBoxId(req.CoolerBoxId, &oldIceRaftCode, p)
+		if err != nil {
+			e.Error(500, err, err.Error())
+			return
+		}
+	}
+	req.SetUpdateBy(user.GetUserId(c))
+	req.OldIceRaftCode = oldIceRaftCode
+	err = s.Packing(&req, p)
+	if err != nil {
+		e.Error(500, err, err.Error())
+		return
+	}
+	e.OK(req.GetId(), "装箱成功")
+}
+
 // Delivery 派单
 // @Summary 派单
 // @Description 派单

+ 55 - 0
app/admin/model/sys_post.go

@@ -0,0 +1,55 @@
+package model
+
+import (
+	model2 "cold-delivery/common/model"
+
+	"errors"
+
+	"gorm.io/gorm"
+)
+
+// SysPost 岗位表
+type SysPost struct {
+	model2.Model
+	PostName string `json:"postName" gorm:"size:128;not null;comment:岗位名称"`
+	PostCode string `json:"postCode" gorm:"size:128;not null;comment:岗位编码"`
+	Sort     int    `json:"sort" gorm:"size:4;default:0;comment:排序"`
+	Status   string `json:"status" gorm:"size:4;not null;default:'2';comment:状态 1停用 2正常"`
+	Remark   string `json:"remark" gorm:"size:255;comment:备注"`
+	MenuIds  []int  `json:"menuIds" gorm:"-"`
+	model2.ControlBy
+	model2.ModelTime
+	model2.DeptBy
+}
+
+func (SysPost) TableName() string {
+	return "sys_post"
+}
+
+func (e *SysPost) Generate() model2.ActiveRecord {
+	o := *e
+	return &o
+}
+
+func (e *SysPost) GetId() interface{} {
+	return e.Id
+}
+
+var (
+	ErrForbidUpdateSysPost = errors.New("禁止修改系统岗位")
+	ErrForbidDeleteSysPost = errors.New("禁止删除系统岗位")
+)
+
+func (e *SysPost) BeforeDelete(_ *gorm.DB) (err error) {
+	if e.PostCode == "admin" {
+		return ErrForbidDeleteSysPost
+	}
+	return
+}
+
+func (e *SysPost) BeforeUpdate(_ *gorm.DB) (err error) {
+	if e.PostCode == "admin" {
+		return ErrForbidUpdateSysPost
+	}
+	return
+}

+ 77 - 0
app/admin/model/sys_post_menu.go

@@ -0,0 +1,77 @@
+package model
+
+import (
+	model2 "cold-delivery/common/model"
+	"encoding/json"
+	"errors"
+
+	"gogs.baozhida.cn/zoie/OAuth-core/sdk"
+	"gogs.baozhida.cn/zoie/OAuth-core/sdk/config"
+)
+
+// SysPostMenu 岗位-菜单权限关联表
+type SysPostMenu struct {
+	model2.Model
+	PostId int `json:"postId" gorm:"index;comment:岗位ID"`
+	MenuId int `json:"menuId" gorm:"index;comment:菜单ID"`
+	model2.ControlBy
+	model2.ModelTime
+}
+
+func (SysPostMenu) TableName() string {
+	return "sys_post_menu"
+}
+
+func (e *SysPostMenu) Generate() model2.ActiveRecord {
+	o := *e
+	return &o
+}
+
+func (e *SysPostMenu) GetId() interface{} {
+	return e.Id
+}
+
+// GetPostMenuCacheKey 获取岗位菜单缓存key
+func GetPostMenuCacheKey(postId int) string {
+	return "post_menu-" + string(rune(postId))
+}
+
+// DeleteAllPostMenuCache 菜单变更时删除所有岗位菜单缓存
+func DeleteAllPostMenuCache() error {
+	db := sdk.Runtime.GetDbByKey(config.ApplicationConfig.Host)
+	if db == nil {
+		return errors.New("db not exist")
+	}
+	var list []SysPost
+	err := db.Model(SysPost{}).Find(&list).Error
+	if err != nil {
+		return err
+	}
+	for _, post := range list {
+		_ = sdk.Runtime.GetCacheAdapter().Del(GetPostMenuCacheKey(post.Id))
+	}
+	return nil
+}
+
+// DeletePostMenuCache 删除单个岗位菜单缓存
+func DeletePostMenuCache(postId int) error {
+	return sdk.Runtime.GetCacheAdapter().Del(GetPostMenuCacheKey(postId))
+}
+
+// SetPostMenuCache 设置岗位菜单缓存
+func SetPostMenuCache(postId int, menuList []SysMenu) error {
+	s, err := json.Marshal(menuList)
+	if err != nil {
+		return err
+	}
+	return sdk.Runtime.GetCacheAdapter().Set(GetPostMenuCacheKey(postId), s, 24*60*60)
+}
+
+// GetPostMenuCache 获取岗位菜单缓存
+func GetPostMenuCache(postId int, menuList *[]SysMenu) error {
+	s, err := sdk.Runtime.GetCacheAdapter().Get(GetPostMenuCacheKey(postId))
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal([]byte(s), &menuList)
+}

+ 5 - 0
app/admin/model/sys_user.go

@@ -63,6 +63,11 @@ var (
 	ErrForbidDeleteSysUser = errors.New("禁止删除系统用户")
 )
 
+// HasPost 是否有岗位权限(替代旧 type 判断)
+func (e *SysUser) HasPost() bool {
+	return e.PostId > 0
+}
+
 func (e *SysUser) BeforeCreate(_ *gorm.DB) error {
 	return e.Encrypt()
 }

+ 2 - 0
app/admin/model/waybill.go

@@ -10,6 +10,7 @@ import (
 var (
 	WaybillStatusWaitDelivery = 1 // 未确定订单
 	WaybillStatusAudits       = 2 // 待审核订单
+	WaybillStatusPacking      = 9 // 新增的状态,表示待取药装箱 2——9——3
 	WaybillStatusInDeliverys  = 3 // 待配送
 	WaybillStatusInShippeds   = 4 // 配送中
 	WaybillStatusReceipts     = 5 // 已签收
@@ -19,6 +20,7 @@ var (
 	WaybillStatusMap          = map[int]string{
 		WaybillStatusWaitDelivery: "未确定订单",
 		WaybillStatusAudits:       "待审核订单",
+		WaybillStatusPacking:      "待取药装箱",
 		WaybillStatusInDeliverys:  "待配送",
 		WaybillStatusInShippeds:   "配送中",
 		WaybillStatusReceipts:     "已签收",

+ 31 - 0
app/admin/router/sys_post.go

@@ -0,0 +1,31 @@
+package router
+
+import (
+	"cold-delivery/app/admin/controller"
+	"cold-delivery/common/actions"
+
+	"github.com/gin-gonic/gin"
+	jwt "gogs.baozhida.cn/zoie/OAuth-core/pkg/jwtauth"
+)
+
+func init() {
+	routerCheckRole = append(routerCheckRole, registerSysPostRouter)
+}
+
+// registerSysPostRouter 注册岗位管理路由
+func registerSysPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
+	cont := controller.SysPostController{}
+
+	r := v1.Group("/post").Use(authMiddleware.MiddlewareFunc()).Use(actions.PermissionAction())
+	{
+		r.GET("", cont.GetPage)                     // 分页查询岗位
+		r.GET("/all", cont.GetAll)                  // 获取所有岗位(下拉用)
+		r.GET("/menu-tree", cont.GetMenuTree)       // 获取菜单权限树
+		r.GET("/names", cont.GetPostNames)          // 根据ID列表获取岗位名称映射
+		r.GET("/:id/menu-ids", cont.GetPostMenuIds) // 获取岗位已分配菜单ID
+		r.GET("/:id", cont.Get)                     // 获取岗位详情(放在最后,避免匹配 /menu-tree 等)
+		r.POST("", cont.Insert)                     // 新增岗位
+		r.PUT("", cont.Update)                      // 修改岗位
+		r.DELETE("", cont.Delete)                   // 删除岗位(逻辑删除)
+	}
+}

+ 4 - 6
app/admin/router/sys_user.go

@@ -2,6 +2,7 @@ package router
 
 import (
 	"cold-delivery/common/actions"
+
 	"github.com/gin-gonic/gin"
 
 	"cold-delivery/app/admin/controller"
@@ -37,12 +38,9 @@ func registerSysUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle
 	}
 
 }
-func registerSysUserRouter2(v1 *gin.RouterGroup) {
-	cont := controller.SysUser{}
-
-	r2 := v1.Group("")
+func registerSysUserRouter2(router *gin.RouterGroup) {
+	r := router.Group("")
 	{
-		r2.GET("/verify-code", cont.VerifyCode)
+		r.GET("/verify-code", controller.SysUser{}.VerifyCode)
 	}
-
 }

+ 1 - 0
app/admin/router/waybill.go

@@ -23,6 +23,7 @@ func registerWaybillRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle
 		r.GET("/:id", cont.Get)
 		r.POST("", cont.Insert)
 		r.POST("/delivery", cont.Delivery)
+		r.POST("/packing", cont.Packing) // 待取药装箱
 		r.PUT("", cont.Update)
 		r.DELETE("", cont.Delete)
 		r.PUT("/adminaudit", cont.AdminAudit)             //管理员审核订单

+ 141 - 0
app/admin/service/dto/sys_post.go

@@ -0,0 +1,141 @@
+package dto
+
+import (
+	"cold-delivery/app/admin/model"
+	"cold-delivery/common/dto"
+	common "cold-delivery/common/model"
+)
+
+// SysPostGetPageReq 分页查询岗位请求
+type SysPostGetPageReq struct {
+	dto.Pagination `search:"-"`
+	PostName       string `form:"postName" search:"type:contains;column:post_name;table:sys_post"` // 岗位名称
+	PostCode       string `form:"postCode" search:"type:contains;column:post_code;table:sys_post"` // 岗位编码
+	Status         string `form:"status" search:"type:exact;column:status;table:sys_post"`         // 状态
+	DeptId         int    `form:"deptId" search:"type:exact;column:dept_id;table:sys_post"`        // 部门ID
+	SysPostOrder
+}
+
+type SysPostOrder struct {
+	SortOrder string `form:"sort" search:"type:order;column:sort;table:sys_post" default:"asc"`
+}
+
+func (m *SysPostGetPageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+// SysPostInsertReq 新增岗位请求
+type SysPostInsertReq struct {
+	Id       int    `json:"id" comment:"编码" swaggerignore:"true"`
+	PostName string `json:"postName"` // 岗位名称
+	PostCode string `json:"postCode"` // 岗位编码
+	Sort     int    `json:"sort"`     // 排序
+	Status   string `json:"status"`   // 状态 1停用 2正常
+	Remark   string `json:"remark"`   // 备注
+	MenuIds  []int  `json:"menuIds"`  // 菜单权限ID列表
+	common.DeptBy
+	common.ControlBy `swaggerignore:"true"`
+}
+
+func (s *SysPostInsertReq) Generate(model *model.SysPost) {
+	if s.Id != 0 {
+		model.Id = s.Id
+	}
+	model.PostName = s.PostName
+	model.PostCode = s.PostCode
+	model.Sort = s.Sort
+	model.Status = s.Status
+	model.Remark = s.Remark
+	if model.Status == "" {
+		model.Status = "2"
+	}
+	if s.ControlBy.UpdateBy != 0 {
+		model.UpdateBy = s.UpdateBy
+	}
+	if s.ControlBy.CreateBy != 0 {
+		model.CreateBy = s.CreateBy
+	}
+	model.DeptId = s.DeptId
+}
+
+func (s *SysPostInsertReq) GetId() interface{} {
+	return s.Id
+}
+
+// SysPostUpdateReq 修改岗位请求
+type SysPostUpdateReq struct {
+	Id               int    `json:"id" comment:"编码"` // 编码
+	PostName         string `json:"postName"`        // 岗位名称
+	PostCode         string `json:"postCode"`        // 岗位编码
+	DeptId           int    `json:"deptId"`          // 部门ID(支持修改部门)
+	Sort             int    `json:"sort"`            // 排序
+	Status           string `json:"status"`          // 状态 1停用 2正常
+	Remark           string `json:"remark"`          // 备注
+	MenuIds          []int  `json:"menuIds"`         // 菜单权限ID列表
+	common.ControlBy `swaggerignore:"true"`
+}
+
+func (s *SysPostUpdateReq) Generate(model *model.SysPost) {
+	if s.Id != 0 {
+		model.Id = s.Id
+	}
+	model.PostName = s.PostName
+	model.PostCode = s.PostCode
+	model.Sort = s.Sort
+	model.Status = s.Status
+	model.Remark = s.Remark
+	if model.Status == "" {
+		model.Status = "2"
+	}
+	if s.DeptId != 0 {
+		model.DeptId = s.DeptId
+	}
+	if s.ControlBy.UpdateBy != 0 {
+		model.UpdateBy = s.UpdateBy
+	}
+	if s.ControlBy.CreateBy != 0 {
+		model.CreateBy = s.CreateBy
+	}
+}
+
+func (s *SysPostUpdateReq) GetId() interface{} {
+	return s.Id
+}
+
+// SysPostGetReq 查询单个岗位请求
+type SysPostGetReq struct {
+	Id int `uri:"id"`
+}
+
+func (s *SysPostGetReq) GetId() interface{} {
+	return s.Id
+}
+
+// SysPostDeleteReq 删除岗位请求
+type SysPostDeleteReq struct {
+	Id               int `json:"id"`
+	common.ControlBy `swaggerignore:"true"`
+}
+
+func (s *SysPostDeleteReq) GetId() interface{} {
+	return s.Id
+}
+
+// SysPostMenuTreeResp 菜单树响应
+type SysPostMenuTreeResp struct {
+	List []SysPostMenuLabel `json:"list"`
+}
+
+// SysPostMenuLabel 菜单树节点
+type SysPostMenuLabel struct {
+	Id       int                `json:"id"`
+	Label    string             `json:"label"`
+	ParentId int                `json:"parentId"`
+	MenuType string             `json:"menuType"`
+	Children []SysPostMenuLabel `json:"children,omitempty"`
+}
+
+// SysPostMenuIdsResp 岗位已分配的菜单ID列表
+type SysPostMenuIdsResp struct {
+	MenuIds []int `json:"menuIds"`
+}

+ 8 - 4
app/admin/service/dto/sys_user.go

@@ -91,6 +91,7 @@ type SysUserInsertReq struct {
 	Phone            string `json:"phone" vd:"regexp('^1[3-9]\\d{9}$');msg:'电话号码格式不正确'"`       // 电话号码
 	RoleId           int    `json:"roleId" example:"1" swaggerignore:"true"`                   // 角色id
 	DeptId           int    `json:"deptId" example:"1"`                                        // 机构id
+	PostId           int    `json:"postId" example:"1"`                                        // 岗位ID
 	Status           string `json:"status" example:"2" swaggerignore:"true"`                   // 状态 	// 货车司机绑定《道路运输从业人员从业资格证》信息
 	Type             int    `json:"type" example:"2"`                                          // 管理员1 门店管理员2 配送员3
 	model2.ControlBy `swaggerignore:"true"`
@@ -106,6 +107,7 @@ func (s *SysUserInsertReq) Generate(userModel *model.SysUser) {
 	userModel.Phone = s.Phone
 	userModel.DeptId = s.DeptId
 	userModel.RoleId = s.RoleId
+	userModel.PostId = s.PostId
 	userModel.Status = "2"
 	userModel.Type = s.Type
 	userModel.UserType = model.UserTypeSys
@@ -123,10 +125,11 @@ func (s *SysUserInsertReq) GetId() interface{} {
 }
 
 type SysUserUpdateReq struct {
-	Id    int    `json:"id"  comment:"用户ID"`                // 用户ID
-	Name  string `json:"name" vd:"@:len($)>0;msg:'姓名不能为空'"` // 姓名
-	Phone string `json:"phone"`                             // 电话号码
-	Type  int    `json:"type" example:"2"`                  // 管理员1 仓管2 司机3
+	Id     int    `json:"id"  comment:"用户ID"`                // 用户ID
+	Name   string `json:"name" vd:"@:len($)>0;msg:'姓名不能为空'"` // 姓名
+	Phone  string `json:"phone"`                             // 电话号码
+	PostId int    `json:"postId" example:"1"`                // 岗位ID
+	Type   int    `json:"type" example:"2"`                  // 管理员1 仓管2 司机3
 
 	model2.ControlBy `swaggerignore:"true"`
 }
@@ -137,6 +140,7 @@ func (s *SysUserUpdateReq) Generate(userModel *model.SysUser) {
 	}
 	userModel.NickName = s.Name
 	userModel.Phone = s.Phone
+	userModel.PostId = s.PostId
 	userModel.Type = s.Type
 
 	if s.ControlBy.UpdateBy != 0 {

+ 15 - 0
app/admin/service/dto/waybill.go

@@ -433,3 +433,18 @@ type WaybillTurnarounds struct {
 	WaybillNo string `json:"waybillNo" vd:"len($)>0;msg:'订单编号不能为空'"` // 运单编号-必填
 	DeptId    int    `json:"deptId"`                                 // 门店id
 }
+
+// 待取药装箱请求
+type WaybillPackingReq struct {
+	WaybillIds          []int    `json:"waybillIds"`                            // 运单id
+	CoolerBoxId         int      `json:"coolerBoxId"`                           // 保温箱id
+	IceRaftCode         []string `json:"iceRaftCode"`                           // 冰排code
+	OldIceRaftCode      []string `json:"oldIceRaftCode" swaggerignore:"true"`   // 旧的冰排code
+	TamperProofLabel    string   `json:"tamperProofLabel"  gorm:"size:128"`     // 防拆标签
+	TamperProofLabelImg string   `json:"tamperProofLabelImg"  gorm:"size:text"` // 防拆标签图片
+	model2.ControlBy    `swaggerignore:"true"`
+}
+
+func (s *WaybillPackingReq) GetId() interface{} {
+	return s.WaybillIds
+}

+ 53 - 0
app/admin/service/sms_notify.go

@@ -0,0 +1,53 @@
+package service
+
+import (
+	"cold-delivery/conf"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"gogs.baozhida.cn/zoie/OAuth-core/pkg/sms"
+)
+
+// SendSmsToDeliveryPerson 发送配送通知短信给配送员
+func SendSmsToDeliveryPerson(phone, orderNo string) error {
+	vars := map[string]string{"orderNo": orderNo}
+	varsBytes, _ := json.Marshal(vars)
+
+	payload := url.Values{}
+	payload.Set("appid", conf.ExtConfig.SubMail.Appid)
+	payload.Set("signature", conf.ExtConfig.SubMail.Signature)
+	payload.Set("to", phone)
+	payload.Set("project", "gOK601")
+	payload.Set("vars", string(varsBytes))
+
+	req, err := http.NewRequest("POST", "https://api-v4.mysubmail.com/sms/xsend", strings.NewReader(payload.Encode()))
+	if err != nil {
+		return err
+	}
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	var res sms.XSendRes
+	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
+		return err
+	}
+	if res.Status != sms.SUCCESS {
+		return errors.New(res.Msg)
+	}
+	return nil
+}
+
+// BuildSmsSendLog 构建短信发送日志
+func BuildSmsSendLog(phone, orderNo string) string {
+	return fmt.Sprintf("【贵州宝智达冷链科技有限公司】您有新的配送订单请及时处理,订单号:%s", orderNo)
+}

+ 622 - 0
app/admin/service/sys_post.go

@@ -0,0 +1,622 @@
+package service
+
+import (
+	"cold-delivery/app/admin/model"
+	"cold-delivery/app/admin/service/dto"
+	"cold-delivery/common/actions"
+	cDto "cold-delivery/common/dto"
+	"cold-delivery/common/global"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/casbin/casbin/v2"
+	"gogs.baozhida.cn/zoie/OAuth-core/sdk"
+	"gogs.baozhida.cn/zoie/OAuth-core/sdk/config"
+	"gogs.baozhida.cn/zoie/OAuth-core/service"
+	"gorm.io/gorm"
+)
+
+type SysPost struct {
+	service.Service
+}
+
+// GetPage 分页查询岗位列表
+func (e *SysPost) GetPage(c *dto.SysPostGetPageReq, list *[]model.SysPost, count *int64, p *actions.DataPermission) error {
+	var err error
+	var data model.SysPost
+
+	db := e.Orm.Model(&data).
+		Scopes(
+			cDto.MakeCondition(c.GetNeedSearch()),
+			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+		)
+	// 超级管理员(deptId=0)可以看到所有部门的岗位
+	if p.DeptId != 0 {
+		db = db.Scopes(actions.Permission(data.TableName(), p))
+	}
+
+	err = db.Find(list).Limit(-1).Offset(-1).
+		Count(count).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return global.GetFailedErr
+	}
+	return nil
+}
+
+// Get 获取单个岗位详情(含权限菜单ID列表)
+func (e *SysPost) Get(d *dto.SysPostGetReq, postModel *model.SysPost, p *actions.DataPermission) error {
+	db := e.Orm
+	// 超级管理员可以看到所有部门的岗位
+	if p.DeptId != 0 {
+		db = db.Scopes(actions.Permission(postModel.TableName(), p))
+	}
+	err := db.First(postModel, d.GetId()).Error
+
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return global.GetNotFoundOrNoPermissionErr
+		}
+		return global.GetFailedErr
+	}
+
+	postModel.MenuIds, err = e.GetPostMenuIds(postModel.Id)
+	if err != nil {
+		e.Log.Errorf("get menuIds error, %s", err)
+		return global.GetFailedErr
+	}
+
+	return nil
+}
+
+// Insert 创建岗位(含菜单权限分配)
+func (e *SysPost) Insert(c *dto.SysPostInsertReq) error {
+	var err error
+	var data model.SysPost
+
+	tx := e.Orm.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 检查岗位编码在同一个部门下是否已存在
+	var count int64
+	err = tx.Model(&data).Where("post_code = ? AND dept_id = ?", c.PostCode, c.DeptId).Count(&count).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	if count > 0 {
+		return errors.New("该部门下岗位编码已存在")
+	}
+
+	// 创建岗位
+	c.Generate(&data)
+	err = tx.Create(&data).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	c.Id = data.Id
+
+	// 添加岗位菜单关联
+	if len(c.MenuIds) > 0 {
+		var menuList []model.SysMenu
+		err = tx.Where("id in (?)", c.MenuIds).Find(&menuList).Error
+		if err != nil {
+			return global.GetFailedErr
+		}
+		if len(menuList) == 0 {
+			return errors.New("菜单不存在!")
+		}
+
+		var postMenuList []model.SysPostMenu
+		for _, v := range menuList {
+			postMenuObj := model.SysPostMenu{
+				PostId: data.Id,
+				MenuId: v.Id,
+			}
+			postMenuObj.SetCreateBy(c.CreateBy)
+			postMenuList = append(postMenuList, postMenuObj)
+		}
+		err = tx.Save(&postMenuList).Error
+		if err != nil {
+			e.Log.Errorf("save menuIds error, %s", err)
+			return global.UpdateFailedErr
+		}
+
+		// 同步写入 Casbin 规则
+		err = e.syncCasbinRules(tx, data, menuList)
+		if err == nil {
+			cb := sdk.Runtime.GetCasbinKey(config.ApplicationConfig.Host)
+			if cb != nil {
+				cb.LoadPolicy()
+			}
+		}
+		if err != nil {
+			e.Log.Errorf("sync casbin rules error, %s", err)
+			return global.UpdateFailedErr
+		}
+	}
+
+	return nil
+}
+
+// Update 修改岗位(含菜单权限重新分配)
+func (e *SysPost) Update(c *dto.SysPostUpdateReq, p *actions.DataPermission, cb *casbin.SyncedEnforcer) error {
+	var err error
+
+	tx := e.Orm.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	var postModel = model.SysPost{}
+	// 查询岗位是否存在
+	db := e.Orm
+	if p.DeptId != 0 {
+		db = db.Scopes(actions.Permission(postModel.TableName(), p))
+	}
+	err = db.First(&postModel, c.GetId()).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return global.UpdateNotFoundOrNoPermissionErr
+		}
+		return global.UpdateFailedErr
+	}
+
+	// 更新岗位基本信息
+	c.Generate(&postModel)
+	// deptId 由 Generate 方法从更新请求中设置,允许修改部门
+	err = tx.Save(&postModel).Error
+	if err != nil {
+		if errors.Is(err, model.ErrForbidUpdateSysPost) {
+			return err
+		}
+		e.Log.Errorf("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+
+	// 清除旧的 Casbin 规则(用旧的 postCode)
+	_, _ = cb.RemoveFilteredPolicy(0, "role:"+postModel.PostCode)
+	_, _ = cb.RemoveFilteredPolicy(0, postModel.PostCode) // 清除旧格式规则(无 role: 前缀)
+
+	// 删除旧的菜单关联
+	err = tx.Where("post_id = ?", postModel.Id).Delete(&model.SysPostMenu{}).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+
+	// 重新添加菜单关联
+	if len(c.MenuIds) > 0 {
+		var menuList []model.SysMenu
+		err = e.Orm.Where("id in (?)", c.MenuIds).Find(&menuList).Error
+		if err != nil {
+			return global.GetFailedErr
+		}
+		if len(menuList) == 0 {
+			return errors.New("菜单不存在!")
+		}
+
+		var postMenuList []model.SysPostMenu
+		for _, v := range menuList {
+			postMenuObj := model.SysPostMenu{
+				PostId: postModel.Id,
+				MenuId: v.Id,
+			}
+			postMenuObj.SetCreateBy(c.UpdateBy)
+			postMenuList = append(postMenuList, postMenuObj)
+		}
+		err = tx.Save(&postMenuList).Error
+		if err != nil {
+			e.Log.Errorf("save menuIds error, %s", err)
+			return global.UpdateFailedErr
+		}
+
+		// 同步写入 Casbin 规则
+		err = e.syncCasbinRules(tx, postModel, menuList)
+		if err == nil {
+			cb.LoadPolicy()
+		}
+		if err != nil {
+			e.Log.Errorf("sync casbin rules error, %s", err)
+			return global.UpdateFailedErr
+		}
+	}
+
+	// 清除缓存
+	_ = model.DeletePostMenuCache(postModel.Id)
+
+	c.Id = postModel.Id
+	return nil
+}
+
+// Remove 删除岗位(逻辑删除)
+func (e *SysPost) Remove(c *dto.SysPostDeleteReq, p *actions.DataPermission, cb *casbin.SyncedEnforcer) error {
+	var err error
+
+	tx := e.Orm.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 检查是否有用户关联该岗位
+	var userCount int64
+	err = tx.Table("sys_user").Where("post_id = ? AND deleted_at IS NULL", c.Id).Count(&userCount).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+	if userCount > 0 {
+		return errors.New(fmt.Sprintf("有 %d 个用户关联了该岗位,禁止删除!", userCount))
+	}
+
+	var postModel model.SysPost
+	rdb := e.Orm
+	if p.DeptId != 0 {
+		rdb = rdb.Scopes(actions.Permission(postModel.TableName(), p))
+	}
+	err = rdb.First(&postModel, c.GetId()).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return global.DeleteNotFoundOrNoPermissionErr
+		}
+		return global.DeleteFailedErr
+	}
+
+	// GORM 逻辑删除(自动设置 deleted_at)
+	db := tx.Delete(&postModel)
+	if err = db.Error; err != nil {
+		if errors.Is(err, model.ErrForbidDeleteSysPost) {
+			return err
+		}
+		e.Log.Errorf("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+	if db.RowsAffected == 0 {
+		return global.DeleteNotFoundOrNoPermissionErr
+	}
+
+	// 删除岗位关联的菜单
+	err = tx.Where("post_id = ?", postModel.Id).Delete(&model.SysPostMenu{}).Error
+	if err != nil {
+		return global.UpdateFailedErr
+	}
+
+	// 清除 Casbin 规则
+	_, _ = cb.RemoveFilteredPolicy(0, "role:"+postModel.PostCode)
+	_, _ = cb.RemoveFilteredPolicy(0, postModel.PostCode) // 清除旧格式规则(无 role: 前缀)
+
+	// 清除缓存
+	_ = model.DeletePostMenuCache(postModel.Id)
+
+	return nil
+}
+
+// GetAll 获取所有岗位(下拉列表用)
+func (e *SysPost) GetAll(deptId int, list *[]model.SysPost, p *actions.DataPermission) error {
+	var err error
+	var data model.SysPost
+	db := e.Orm.Model(&data)
+	// 超级管理员可以看到所有部门的岗位
+	if p.DeptId != 0 {
+		db = db.Scopes(actions.Permission(data.TableName(), p))
+	}
+	if deptId > 0 {
+		db = db.Where("dept_id = ?", deptId)
+	}
+	err = db.Order("sort asc").Find(list).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return global.GetFailedErr
+	}
+	return nil
+}
+
+// GetPostMenuIds 获取岗位已分配的菜单ID列表
+func (e *SysPost) GetPostMenuIds(postId int) ([]int, error) {
+	var menuIds []int
+	err := e.Orm.Model(&model.SysPostMenu{}).Where("post_id = ?", postId).Pluck("menu_id", &menuIds).Error
+	if err != nil {
+		return []int{}, global.GetFailedErr
+	}
+	return menuIds, nil
+}
+
+// GetMenuTree 获取完整菜单树(用于权限分配界面)
+func (e *SysPost) GetMenuTree() ([]dto.MenuLabel, error) {
+	// 直接查询所有菜单,不依赖 SysMenu.GetList 的复杂搜索逻辑
+	var menuList []model.SysMenu
+	err := e.Orm.Order("sort asc").Find(&menuList).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return nil, global.GetFailedErr
+	}
+
+	// 构建菜单树
+	tree := make([]dto.MenuLabel, 0)
+	for i := 0; i < len(menuList); i++ {
+		if menuList[i].ParentId != 0 {
+			continue
+		}
+		label := dto.MenuLabel{}
+		label.Id = menuList[i].Id
+		label.Label = menuList[i].Title
+		buildMenuLabelTree(&menuList, &label)
+		tree = append(tree, label)
+	}
+	return tree, nil
+}
+
+// buildMenuLabelTree 递归构建菜单标签树
+func buildMenuLabelTree(menuList *[]model.SysMenu, parent *dto.MenuLabel) {
+	list := *menuList
+	children := make([]dto.MenuLabel, 0)
+	for j := 0; j < len(list); j++ {
+		if parent.Id != list[j].ParentId {
+			continue
+		}
+		child := dto.MenuLabel{}
+		child.Id = list[j].Id
+		child.Label = list[j].Title
+		child.Children = []dto.MenuLabel{}
+		if list[j].MenuType != "F" {
+			buildMenuLabelTree(menuList, &child)
+		}
+		children = append(children, child)
+	}
+	if len(children) > 0 {
+		parent.Children = children
+	} else {
+		parent.Children = nil
+	}
+}
+
+// FilterMenuTree 根据允许的菜单ID过滤菜单树
+func (e *SysPost) FilterMenuTree(fullTree []dto.MenuLabel, allowedIds []int) []dto.MenuLabel {
+	if len(allowedIds) == 0 {
+		return nil
+	}
+	allowedMap := make(map[int]bool)
+	for _, id := range allowedIds {
+		allowedMap[id] = true
+	}
+
+	var filter func(nodes []dto.MenuLabel) []dto.MenuLabel
+	filter = func(nodes []dto.MenuLabel) []dto.MenuLabel {
+		result := make([]dto.MenuLabel, 0)
+		for _, node := range nodes {
+			// Only include if allowed or has allowed children
+			if allowedMap[node.Id] || hasAllowedChild(node.Children, allowedMap) {
+				child := dto.MenuLabel{
+					Id:       node.Id,
+					Label:    node.Label,
+					Children: filter(node.Children),
+				}
+				result = append(result, child)
+			}
+		}
+		return result
+	}
+	return filter(fullTree)
+}
+
+func hasAllowedChild(children []dto.MenuLabel, allowedMap map[int]bool) bool {
+	for _, c := range children {
+		if allowedMap[c.Id] || hasAllowedChild(c.Children, allowedMap) {
+			return true
+		}
+	}
+	return false
+}
+
+// syncCasbinRules 同步岗位权限到 Casbin 规则表
+func (e *SysPost) syncCasbinRules(tx *gorm.DB, post model.SysPost, menuList []model.SysMenu) error {
+	// 获取完整的菜单信息(包括父菜单的路由权限)
+	var allMenus []model.SysMenu
+	menuIds := make([]int, 0)
+	for _, m := range menuList {
+		menuIds = append(menuIds, m.Id)
+	}
+
+	// 递归获取所有父级菜单的权限路径
+	err := e.resolveMenuPaths(menuIds, &allMenus)
+	if err != nil {
+		return err
+	}
+
+	// 为每个菜单路径写入 Casbin 规则
+	// Casbin 规则格式: p = postCode, domain, path, method
+	// 在 Casbin 中: sub = postCode, dom = service::api, obj = path, act = method
+	for _, menu := range allMenus {
+		if menu.Path == "" {
+			continue
+		}
+
+		var methods []string
+		if menu.MenuType == "C" {
+			methods = []string{"GET"}
+		} else if menu.MenuType == "M" {
+			continue
+		} else if menu.MenuType == "F" {
+			methods = mapButtonToMethods(menu.Title)
+			if len(methods) == 0 {
+				continue
+			}
+		}
+		for _, method := range methods {
+			// 使用原始 SQL 插入避免与 Casbin adapter 冲突
+			// 检查是否已存在
+			var count int64
+			sub := "role:" + post.PostCode
+			tx.Table("sys_casbin_rule").
+				Where("p_type = 'p' AND v0 = ? AND v1 = ? AND v2 = ? AND v3 = ?",
+					sub, "service::api", menu.Path, method).
+				Count(&count)
+			if count == 0 {
+				err = tx.Exec(
+					"INSERT INTO sys_casbin_rule (p_type, v0, v1, v2, v3, v4, v5) VALUES ('p', ?, ?, ?, ?, '', '')",
+					sub, "service::api", menu.Path, method,
+				).Error
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	// 写入 g 规则(Casbin RBAC 角色继承)
+	sub := "role:" + post.PostCode
+	var gCount int64
+	tx.Table("sys_casbin_rule").
+		Where("p_type = 'g' AND v0 = ? AND v1 = ? AND v2 = ?", sub, sub, "service::api").
+		Count(&gCount)
+	if gCount == 0 {
+		err = tx.Exec(
+			"INSERT INTO sys_casbin_rule (p_type, v0, v1, v2, v3, v4, v5) VALUES ('g', ?, ?, ?, '', '', '')",
+			sub, sub, "service::api",
+		).Error
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// mapButtonToMethods 将按钮名称映射为 HTTP 方法
+func mapButtonToMethods(title string) []string {
+	title = strings.TrimSpace(title)
+	switch {
+	case strings.Contains(title, "查询"):
+		return []string{"GET"}
+	case strings.Contains(title, "新增") || strings.Contains(title, "导入"):
+		return []string{"POST"}
+	case strings.Contains(title, "编辑") || strings.Contains(title, "修改"):
+		return []string{"PUT"}
+	case strings.Contains(title, "删除"):
+		return []string{"DELETE"}
+	case strings.Contains(title, "重置") || strings.Contains(title, "审核"):
+		return []string{"PUT"}
+	case strings.Contains(title, "分配"):
+		return []string{"POST"}
+	default:
+		return []string{"GET"}
+	}
+}
+
+// resolveMenuPaths 递归获取菜单及其所有父菜单
+func (e *SysPost) resolveMenuPaths(menuIds []int, result *[]model.SysMenu) error {
+	if len(menuIds) == 0 {
+		return nil
+	}
+
+	var menus []model.SysMenu
+	err := e.Orm.Where("id in (?)", menuIds).Find(&menus).Error
+	if err != nil {
+		return err
+	}
+
+	parentIds := make([]int, 0)
+	seen := make(map[int]bool)
+	for _, m := range *result {
+		seen[m.Id] = true
+	}
+
+	for _, m := range menus {
+		if !seen[m.Id] {
+			*result = append(*result, m)
+			seen[m.Id] = true
+		}
+		if m.ParentId != 0 && !seen[m.ParentId] {
+			parentIds = append(parentIds, m.ParentId)
+		}
+	}
+
+	if len(parentIds) > 0 {
+		return e.resolveMenuPaths(parentIds, result)
+	}
+
+	return nil
+}
+
+// GetWithName 根据岗位名称查询
+func (e *SysPost) GetWithName(name string, deptId int, postModel *model.SysPost) *SysPost {
+	db := e.Orm.Where("post_name = ?", name)
+	if deptId > 0 {
+		db = db.Where("dept_id = ?", deptId)
+	}
+	err := db.First(postModel).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			err = global.GetNotFoundOrNoPermissionErr
+		} else {
+			err = global.GetFailedErr
+		}
+		_ = e.AddError(err)
+		return e
+	}
+	return e
+}
+
+// GetPostNamesByIds 根据岗位ID列表获取岗位名称映射
+func (e *SysPost) GetPostNamesByIds(ids []int) (map[int]string, error) {
+	if len(ids) == 0 {
+		return map[int]string{}, nil
+	}
+	var posts []model.SysPost
+	err := e.Orm.Where("id in (?)", ids).Find(&posts).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		return nil, global.GetFailedErr
+	}
+	result := make(map[int]string)
+	for _, p := range posts {
+		result[p.Id] = p.PostName
+	}
+	return result, nil
+}
+
+// GetPostById 通过岗位ID获取菜单权限标识列表
+func (e *SysPost) GetPostById(postId int) ([]string, error) {
+	permissions := make([]string, 0)
+	postModel := model.SysPost{}
+	postModel.Id = postId
+	err := e.Orm.Model(&postModel).First(&postModel).Error
+	if err != nil {
+		e.Log.Errorf("db error: %s", err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, global.GetNotFoundOrNoPermissionErr
+		}
+		return nil, global.GetFailedErr
+	}
+
+	// 查询岗位关联的菜单权限标识
+	var menuPerms []string
+	err = e.Orm.Model(&model.SysMenu{}).
+		Joins("JOIN sys_post_menu ON sys_post_menu.menu_id = sys_menu.id").
+		Where("sys_post_menu.post_id = ? AND sys_menu.permission != ''", postId).
+		Pluck("sys_menu.permission", &menuPerms).Error
+	if err != nil {
+		return nil, global.GetFailedErr
+	}
+	permissions = append(permissions, menuPerms...)
+	return permissions, nil
+}

+ 6 - 6
app/admin/service/sys_user.go

@@ -74,7 +74,7 @@ func (e *SysUser) GetPageByDeptId(c *dto.SysUserGetPageByDeptIdReq, p *actions.D
 		e.Log.Errorf("获取操作用户信息失败: %s", err)
 		return errors.New("获取操作用户信息失败")
 	}
-	if operatingUser.Type != model.SysUserTypeSys {
+	if !operatingUser.HasPost() && operatingUser.Id != 1 {
 		return nil
 	}
 
@@ -173,11 +173,11 @@ func (e *SysUser) Insert(c *dto.SysUserInsertReq, p *actions.DataPermission) err
 		return errors.New("获取操作用户信息失败")
 	}
 
-	//if (operatingUser.Type != model.SysUserTypeSys) || (operatingUser.Id != 1) {
+	//if (!operatingUser.HasPost() && operatingUser.Id != 1) || (operatingUser.Id != 1) {
 	//	err = errors.New("无权添加用户,请联系管理员!")
 	//	return err
 	//}
-	if operatingUser.Type != model.SysUserTypeSys {
+	if !operatingUser.HasPost() && operatingUser.Id != 1 {
 		err = errors.New("无权添加用户,请联系管理员!")
 		return err
 	}
@@ -244,7 +244,7 @@ func (e *SysUser) Update(c *dto.SysUserUpdateReq, p *actions.DataPermission) err
 
 	c.Generate(&userModel)
 	err = tx.Model(&userModel).Where("id = ?", &userModel.Id).
-		Omit("password", "salt").Updates(&userModel).Error
+		Omit("password", "salt", "type").Updates(&userModel).Error
 	if err != nil {
 		if errors.Is(err, model.ErrForbidUpdateSysRole) {
 			return model.ErrForbidUpdateSysRole
@@ -334,12 +334,12 @@ func (e *SysUser) Remove(c *dto.SysUserDeleteReq, p *actions.DataPermission) err
 		return errors.New("获取操作用户信息失败")
 	}
 	if p.UserId != 1 {
-		if data.CreateBy == 1 && data.Type == model.SysUserTypeSys {
+		if data.CreateBy == 1 && data.HasPost() {
 			return errors.New("禁止删除管理员")
 		}
 	}
 	// 管理员1 门店管理员2 配送员3
-	if operatingUser.Type != model.SysUserTypeSys && data.Type == model.SysUserTypeSys {
+	if !operatingUser.HasPost() && operatingUser.Id != 1 && data.HasPost() {
 		return errors.New("禁止删除管理员")
 	}
 

+ 585 - 362
app/admin/service/waybill.go

@@ -582,8 +582,8 @@ func (e *Waybill) UpdateStatus(c *dto.UpdateStatusReq, p *actions.DataPermission
 	return nil
 }
 
-// Delivery 派单
-func (e *Waybill) Delivery(c *dto.WaybillDeliveryReq, p *actions.DataPermission) error {
+// 待取药装箱(分配保温箱、冰排、防伪标签)
+func (e *Waybill) Packing(c *dto.WaybillPackingReq, p *actions.DataPermission) error {
 	var err error
 
 	tx := e.Orm.Begin()
@@ -594,41 +594,9 @@ func (e *Waybill) Delivery(c *dto.WaybillDeliveryReq, p *actions.DataPermission)
 			tx.Commit()
 		}
 	}()
-	var user model.SysUser
-	first := e.Orm.Table(user.TableName()).Where("id = ?", p.UserId).First(&user)
-	if first.Error != nil {
-		return global.GetFailedErr
-	}
 
+	// 处理冰排出库逻辑
 	if !lib.StrListEqual(c.IceRaftCode, c.OldIceRaftCode) {
-		// 获取删除的冰排列表
-		//missingIceRaftCode = lib.FindStrListMissingData(c.OldIceRaftCode, c.IceRaftCode)
-
-		//if len(missingIceRaftCode) > 0 {
-		//	// 删除冰排出库记录
-		//	IceRaftList := []model.IceRaft{}
-		//	err = tx.Model(&model.IceRaft{}).Where("code in (?)", missingIceRaftCode).Find(&IceRaftList).Error
-		//	if err != nil {
-		//		e.Log.Errorf("db error: %s", err)
-		//		return errors.New("获取冰排列表失败")
-		//	}
-		//	for _, iceRaft := range IceRaftList {
-		//		// 获取冰排记录
-		//		err = tx.Model(&model.IceRaftRecord{}).
-		//			Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusUsing).
-		//			Updates(map[string]interface{}{
-		//				"status":           model.IceRaftRecordStatusWaitUse,
-		//				"out_storage_time": nil,
-		//				"cooler_box_id":    0,
-		//			}).Error
-		//		if err != nil {
-		//			e.Log.Errorf("db error: %s", err)
-		//			return errors.New(fmt.Sprintf("冰排【%s】返库失败", iceRaft.Code))
-		//		}
-		//	}
-		//}
-		// 获取新增的冰排列表
-		//判断是否可以随时出库冰排
 		extraData := lib.FindStrListExtraData(c.OldIceRaftCode, c.IceRaftCode)
 		if len(extraData) > 0 {
 			for _, code := range extraData {
@@ -638,74 +606,79 @@ func (e *Waybill) Delivery(c *dto.WaybillDeliveryReq, p *actions.DataPermission)
 					e.Log.Errorf("db error: %s", err)
 					if errors.Is(err, gorm.ErrRecordNotFound) {
 						return errors.New(fmt.Sprintf("冰排【%s】不存在", code))
-					} else {
-						return errors.New(fmt.Sprintf("获取冰排【%s】信息失败", code))
 					}
-
+					return errors.New(fmt.Sprintf("获取冰排【%s】信息失败", code))
 				}
-				if user.Dept.IsOutStorage {
-					if iceRaft.IceRaftRecord.Status != model.IceRaftRecordStatusWaitUse {
-						err = errors.New(fmt.Sprintf("冰排【%s】状态为%s,出库保温箱失败!", code, model.GetIceRaftRecordStatus(iceRaft.IceRaftRecord.Status)))
-						return err
-					}
-					err = tx.Model(&model.IceRaftRecord{}).
-						Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusWaitUse).
-						Updates(map[string]interface{}{
-							"status":           model.IceRaftRecordStatusUsing,
-							"out_storage_time": time.Now(),
-							"cooler_box_id":    c.CoolerBoxId,
-						}).Error
-					if err != nil {
-						e.Log.Errorf("db error: %s", err)
-						return errors.New(fmt.Sprintf("冰排【%s】出库失败", iceRaft.Code))
-					}
-
-				} else {
-					err = tx.Model(&model.IceRaftRecord{}).
-						Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusFreezing).
-						Or("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusWaitUse).
-						Or("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordReleasedInTheCold).
-						Updates(map[string]interface{}{
-							"status":           model.IceRaftRecordStatusUsing,
-							"out_storage_time": time.Now(),
-							"cooler_box_id":    c.CoolerBoxId,
-						}).Error
-					if err != nil {
-						e.Log.Errorf("db error: %s", err)
-						return errors.New(fmt.Sprintf("冰排【%s】出库失败", iceRaft.Code))
-					}
+				// 更新冰排记录为使用中
+				err = tx.Model(&model.IceRaftRecord{}).
+					Where("ice_raft_id = ? and status in (?)", iceRaft.Id, []string{
+						model.IceRaftRecordStatusFreezing,
+						model.IceRaftRecordStatusWaitUse,
+						model.IceRaftRecordReleasedInTheCold,
+					}).
+					Updates(map[string]interface{}{
+						"status":           model.IceRaftRecordStatusUsing,
+						"out_storage_time": time.Now(),
+						"cooler_box_id":    c.CoolerBoxId,
+					}).Error
+				if err != nil {
+					e.Log.Errorf("db error: %s", err)
+					return errors.New(fmt.Sprintf("冰排【%s】出库失败", iceRaft.Code))
 				}
-				// 获取冰排记录
-				//err = tx.Model(&model.IceRaftRecord{}).
-				//	Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusWaitUse).
-				//	Updates(map[string]interface{}{
-				//		"status":           model.IceRaftRecordStatusUsing,
-				//		"out_storage_time": time.Now(),
-				//		"cooler_box_id":    c.CoolerBoxId,
-				//	}).Error
-				//if err != nil {
-				//	e.Log.Errorf("db error: %s", err)
-				//	return errors.New(fmt.Sprintf("冰排【%s】出库失败", iceRaft.Code))
-				//}
 			}
 		}
 	}
 
-	var coolerBox = model.CoolerBox{}
-	// 查询保温箱信息
-	if c.CoolerBoxId != 0 {
-		err = tx.Scopes(actions.Permission(coolerBox.TableName(), p)).
-			First(&coolerBox, c.CoolerBoxId).Error
+	for _, id := range c.WaybillIds {
+		var waybillModel = model.Waybill{}
+		err = tx.Scopes(actions.Permission(waybillModel.TableName(), p)).
+			First(&waybillModel, id).Error
 		if err != nil {
-			e.Log.Errorf("获取保温箱信息失败: %s", err)
-			return errors.New("获取保温箱信息失败")
+			e.Log.Errorf("db error: %s", err)
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				return global.UpdateNotFoundOrNoPermissionErr
+			}
+			return global.UpdateFailedErr
+		}
+
+		// 只有待取药装箱状态(9)才能进行装箱操作
+		if waybillModel.Status != model.WaybillStatusPacking {
+			return errors.New("当前订单状态不允许装箱操作")
+		}
+
+		// 装箱完成,状态变更为待配送(3)
+		waybillModel.Status = model.WaybillStatusInDeliverys
+		waybillModel.CoolerBoxId = c.CoolerBoxId
+		waybillModel.IceRaftCode = c.IceRaftCode
+		waybillModel.TamperProofLabel = c.TamperProofLabel
+		waybillModel.TamperProofLabelImg = c.TamperProofLabelImg
+
+		err = tx.Save(&waybillModel).Error
+		if err != nil {
+			e.Log.Errorf("db error: %s", err)
+			return global.UpdateFailedErr
 		}
 	}
 
-	for _, id := range c.WaybillIds {
+	return nil
+}
 
+func (e *Waybill) Delivery(c *dto.WaybillDeliveryReq, p *actions.DataPermission) error {
+	var err error
+	e.Log.Errorf("DELIVERY_V2_NEW_SMS_ENABLED")
+
+	tx := e.Orm.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 只分配配送员
+	for _, id := range c.WaybillIds {
 		var waybillModel = model.Waybill{}
-		// 查询运单是否存在
 		err = tx.Scopes(actions.Permission(waybillModel.TableName(), p)).
 			First(&waybillModel, id).Error
 		if err != nil {
@@ -715,222 +688,408 @@ func (e *Waybill) Delivery(c *dto.WaybillDeliveryReq, p *actions.DataPermission)
 			}
 			return global.UpdateFailedErr
 		}
-		//派单时添加
-		//waybillModeStatus := waybillModel.Status
-		if waybillModel.Status == model.WaybillStatusInDeliverys || (waybillModel.Status == model.WaybillStatusInDeliverys && waybillModel.CoolerBoxId != c.CoolerBoxId) || (waybillModel.Status == model.WaybillStatusInDeliverys && !lib.StrListEqual(c.IceRaftCode, c.OldIceRaftCode)) {
-			waybillModel.Status = model.WaybillStatusInDeliverys
-			waybillModel.TamperProofLabel = c.TamperProofLabel
-			waybillModel.TamperProofLabelImg = c.TamperProofLabelImg // 防拆标签
-			waybillModel.DeliveryId = c.DeliveryId
-			waybillModel.AssignmentStatus = 2 // 分配状态 2 已派单
-			waybillModel.CoolerBoxId = c.CoolerBoxId
-			waybillModel.DeliveryTime = model2.Time(time.Now())
-			waybillModel.IceRaftCode = c.IceRaftCode
-			waybillModel.SecondaryTime = model2.Time(time.Now()) //分配时间
-			if c.Status == model.WaybillStatusInShippeds {
-				waybillModel.Status = c.Status
-				var data model.IceRaft
-				var list []string
-				err := e.Orm.Model(&data).
-					Select("ice_raft.code").
-					Scopes(
-						actions.Permission(data.TableName(), p),
-						IceRaftNameRecordStatusScopes(model.IceRaftRecordStatusUsing),
-						IceRaftNameRecordCoolerBoxIdScopes(c.CoolerBoxId),
-					).
-					Joins("left join ice_raft_record on ice_raft.ice_raft_record_id = ice_raft_record.id").
-					Find(&list).Error
-				if err != nil {
-					e.Log.Errorf("db error: %s", err)
-					return errors.New("获取冰排编号失败")
-				}
-				//查询保温箱记录信息状态为1使用中
-				var cooler model.CoolerBoxRecord
-				var count int64
-				e.Orm.Scopes(actions.Permission(cooler.TableName(), p)).
-					Where("cooler_box_id = ?", c.CoolerBoxId).Where("status = ?", 1).First(&cooler).Count(&count)
-				if count == 0 {
-					cooler.CoolerBoxId = c.CoolerBoxId
-					cooler.Name = coolerBox.Name
-					cooler.Sn = coolerBox.Sn
-					cooler.CoolerUseUsers = user.NickName
-					cooler.CoolerUserTime = model2.Time(time.Now())
-					cooler.Status = "1" //使用结束
-					cooler.DeptId = user.DeptId
-					if len(list) == 0 {
-						cooler.HistoryCode = append(cooler.HistoryCode, c.IceRaftCode...)
-						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
-					} else {
-						cooler.HistoryCode = append(cooler.HistoryCode, list...)
-						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
-					}
-					err := e.Orm.Save(&cooler).Error
-					if err != nil {
-						e.Log.Errorf("保存保温箱记录失败: %s", err)
-						return errors.New("保存保温箱记录失败")
-					}
+
+		// 只有待配送状态(3)才能进行派单
+		if waybillModel.Status != model.WaybillStatusInDeliverys {
+			return errors.New("当前订单状态不允许派单操作")
+		}
+
+		waybillModel.DeliveryId = c.DeliveryId
+		waybillModel.AssignmentStatus = 2
+		waybillModel.DeliveryTime = model2.Time(time.Now())
+		waybillModel.SecondaryTime = model2.Time(time.Now())
+
+		// 分配配送员后向配送员发送通知短信
+		if c.Status != model.WaybillStatusInShippeds && c.DeliveryId > 0 {
+			var deliveryUser model.SysUser
+			if e.Orm.Where("id = ?", c.DeliveryId).First(&deliveryUser).Error == nil && deliveryUser.Phone != "" {
+				if smsErr := SendSmsToDeliveryPerson(deliveryUser.Phone, waybillModel.OrderNo); smsErr != nil {
+					e.Log.Errorf("配送通知短信发送失败, phone=%s, orderNo=%s, err=%s", deliveryUser.Phone, waybillModel.OrderNo, smsErr)
 				} else {
-					cooler.CoolerUseUsers = user.NickName
-					if len(list) == 0 {
-						cooler.HistoryCode = append(cooler.HistoryCode, c.IceRaftCode...)
-						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
-					} else {
-						cooler.HistoryCode = append(cooler.HistoryCode, list...)
-						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
-					}
-					err = e.Orm.Updates(&cooler).Where("id = ?", cooler.Id).Error
-					if err != nil {
-						e.Log.Errorf("保存保温箱记录失败: %s", err)
-						return errors.New("保存保温箱记录失败")
+					waybillModel.SendLog = model.WaybillSendLog{
+						Phone:   deliveryUser.Phone,
+						Content: BuildSmsSendLog(deliveryUser.Phone, waybillModel.OrderNo),
 					}
 				}
-				// 配送中状态下才发送短信 (改用普通短信 Send,无需模板)
-				// 配送中状态下才发送短信(改用自定义模板发送)
-				addr := conf.ExtConfig.Applet.WaybillUrl + url.QueryEscape(lib.AesEncryptCBC(waybillModel.WaybillNo, lib.AesKey))
-				res, err1 := CustomSmsXSend(conf.ExtConfig.SubMail.Appid, conf.ExtConfig.SubMail.Signature, waybillModel.ConsigneeAddressPhone, addr)
-				if err1 != nil || res.Status != sms.SUCCESS {
-					e.Log.Errorf("派单短信发送失败", zap.Any("res", res), zap.Error(err1))
-					err = errors.New("派单短信发送失败,请检查收件人电话是否正确!")
-					return err
-				}
-				waybillModel.SendLog = model.WaybillSendLog{
-					Phone:   waybillModel.ConsigneeAddressPhone,
-					Content: "【贵州宝智达冷链科技公司】您的订单正在派送中,点击查看详情:" + addr, // 同步改成新模板的文案
-				}
-				//// 查询任务
-				//var logistics model.WaybillLogistics
-				// 添加物流
-				//logisticsObj := model.WaybillLogistics{
-				//	WaybillNo:   waybillModel.WaybillNo,
-				//	Status:      waybillModel.Status,
-				//	CoolerBoxId: coolerBox.Id,
-				//	ControlBy: model2.ControlBy{
-				//		CreateBy: p.UserId,
-				//	},
-				//	DeptBy: model2.DeptBy{
-				//		DeptId: p.DeptId,
-				//	},
-				//}
-				//err = tx.Create(&logisticsObj).Error
-				//if err != nil {
-				//	e.Log.Errorf("db error: %s", err)
-				//	return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
-				//}
-				//logistics.CoolerBoxId = coolerBox.Id
-				//err = tx.Save(&logistics).Error
-				//if err != nil {
-				//	e.Log.Errorf("db error: %s", err)
-				//	return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
-				//}
-			}
-			// 查询任务
-			err = tx.Save(&waybillModel).Error
-			if err != nil {
-				e.Log.Errorf("db error: %s", err)
-				return global.UpdateFailedErr
 			}
-			// 查询任务
-			//var logistics model.WaybillLogistics
-			//err = tx.Model(&logistics).Where("waybill_no = ?", waybillModel.WaybillNo).
-			//	Last(&logistics).Error
-			//if err != nil {
-			//	if errors.Is(err, gorm.ErrRecordNotFound) {
-			//		// 添加物流
-			//		logisticsObj := model.WaybillLogistics{
-			//			WaybillNo:   waybillModel.WaybillNo,
-			//			Status:      waybillModel.Status,
-			//			CoolerBoxId: coolerBox.Id,
-			//			ControlBy: model2.ControlBy{
-			//				CreateBy: p.UserId,
-			//			},
-			//			DeptBy: model2.DeptBy{
-			//				DeptId: p.DeptId,
-			//			},
-			//		}
-			//		err = tx.Create(&logisticsObj).Error
-			//		if err != nil {
-			//			e.Log.Errorf("db error: %s", err)
-			//			return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
-			//		}
-			//	} else {
-			//		e.Log.Errorf("db error: %s", err)
-			//		return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
-			//	}
-			//	logistics.CoolerBoxId = coolerBox.Id
-			//	err = tx.Save(&logistics).Error
-			//	if err != nil {
-			//		e.Log.Errorf("db error: %s", err)
-			//		return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
-			//	}
-			//}
-			// 添加物流
-			logisticsObj := model.WaybillLogistics{
-				WaybillNo:   waybillModel.WaybillNo,
-				Status:      waybillModel.Status,
-				CoolerBoxId: coolerBox.Id,
-				ControlBy: model2.ControlBy{
-					CreateBy: p.UserId,
-				},
-				DeptBy: model2.DeptBy{
-					DeptId: p.DeptId,
-				},
-			}
-			err = tx.Create(&logisticsObj).Error
-			if err != nil {
-				e.Log.Errorf("db error: %s", err)
-				return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
+		}
+
+		// 如果传入状态为配送中(4),则直接变为配送中
+		if c.Status == model.WaybillStatusInShippeds {
+			waybillModel.Status = c.Status
+
+			// 配送中状态下发送短信
+			addr := conf.ExtConfig.Applet.WaybillUrl + url.QueryEscape(lib.AesEncryptCBC(waybillModel.WaybillNo, lib.AesKey))
+			res, err1 := CustomSmsXSend(conf.ExtConfig.SubMail.Appid, conf.ExtConfig.SubMail.Signature, waybillModel.ConsigneeAddressPhone, addr)
+			if err1 != nil || res.Status != sms.SUCCESS {
+				e.Log.Errorf("派单短信发送失败", zap.Any("res", res), zap.Error(err1))
+				err = errors.New("派单短信发送失败,请检查收件人电话是否正确!")
+				return err
 			}
-			// 查询任务
-			var task model.WaybillTask
-			err = tx.Model(&task).Where("waybill_no = ? ", waybillModel.WaybillNo).
-				Last(&task).Error
-			if err != nil {
-				if errors.Is(err, gorm.ErrRecordNotFound) {
-					// 添加任务
-					taskObj := model.WaybillTask{
-						WaybillNo:   waybillModel.WaybillNo,
-						CoolerBoxId: coolerBox.Id,
-						UserId:      p.UserId,
-						Sn:          coolerBox.Sn,
-						StartTime:   model2.Time(time.Now()),
-						ControlBy: model2.ControlBy{
-							CreateBy: p.UserId,
-						},
-						DeptBy: model2.DeptBy{
-							DeptId: p.DeptId,
-						},
-					}
-					err = tx.Create(&taskObj).Error
-					if err != nil {
-						e.Log.Errorf("db error: %s", err)
-						return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
-					}
-				} else {
-					//task.StartTime = model2.Time(time.Now())
-					task.UpdateBy = p.UserId
-					task.CoolerBoxId = coolerBox.Id
-					task.Sn = coolerBox.Sn
-					err = tx.Save(&task).Error
-					if err != nil {
-						e.Log.Errorf("db error: %s", err)
-						return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
-					}
-				}
-			} else {
-				task.UpdateBy = p.UserId
-				task.CoolerBoxId = coolerBox.Id
-				task.Sn = coolerBox.Sn
-				err = tx.Save(&task).Error
-				if err != nil {
-					e.Log.Errorf("db error: %s", err)
-					return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
-				}
+			waybillModel.SendLog = model.WaybillSendLog{
+				Phone:   waybillModel.ConsigneeAddressPhone,
+				Content: "【贵州宝智达冷链科技公司】您的订单正在派送中,点击查看详情:" + addr,
 			}
 		}
+
+		err = tx.Save(&waybillModel).Error
+		if err != nil {
+			e.Log.Errorf("db error: %s", err)
+			return global.UpdateFailedErr
+		}
 	}
 	return nil
 }
 
+// Delivery 派单
+//func (e *Waybill) Delivery(c *dto.WaybillDeliveryReq, p *actions.DataPermission) error {
+//	var err error
+//
+//	tx := e.Orm.Begin()
+//	defer func() {
+//		if err != nil {
+//			tx.Rollback()
+//		} else {
+//			tx.Commit()
+//		}
+//	}()
+//	var user model.SysUser
+//	first := e.Orm.Table(user.TableName()).Where("id = ?", p.UserId).First(&user)
+//	if first.Error != nil {
+//		return global.GetFailedErr
+//	}
+//
+//	if !lib.StrListEqual(c.IceRaftCode, c.OldIceRaftCode) {
+//		// 获取删除的冰排列表
+//		//missingIceRaftCode = lib.FindStrListMissingData(c.OldIceRaftCode, c.IceRaftCode)
+//
+//		//if len(missingIceRaftCode) > 0 {
+//		//	// 删除冰排出库记录
+//		//	IceRaftList := []model.IceRaft{}
+//		//	err = tx.Model(&model.IceRaft{}).Where("code in (?)", missingIceRaftCode).Find(&IceRaftList).Error
+//		//	if err != nil {
+//		//		e.Log.Errorf("db error: %s", err)
+//		//		return errors.New("获取冰排列表失败")
+//		//	}
+//		//	for _, iceRaft := range IceRaftList {
+//		//		// 获取冰排记录
+//		//		err = tx.Model(&model.IceRaftRecord{}).
+//		//			Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusUsing).
+//		//			Updates(map[string]interface{}{
+//		//				"status":           model.IceRaftRecordStatusWaitUse,
+//		//				"out_storage_time": nil,
+//		//				"cooler_box_id":    0,
+//		//			}).Error
+//		//		if err != nil {
+//		//			e.Log.Errorf("db error: %s", err)
+//		//			return errors.New(fmt.Sprintf("冰排【%s】返库失败", iceRaft.Code))
+//		//		}
+//		//	}
+//		//}
+//		// 获取新增的冰排列表
+//		//判断是否可以随时出库冰排
+//		extraData := lib.FindStrListExtraData(c.OldIceRaftCode, c.IceRaftCode)
+//		if len(extraData) > 0 {
+//			for _, code := range extraData {
+//				var iceRaft model.IceRaft
+//				err = tx.Model(&model.IceRaft{}).Where("code = ?", code).Preload("IceRaftRecord").First(&iceRaft).Error
+//				if err != nil {
+//					e.Log.Errorf("db error: %s", err)
+//					if errors.Is(err, gorm.ErrRecordNotFound) {
+//						return errors.New(fmt.Sprintf("冰排【%s】不存在", code))
+//					} else {
+//						return errors.New(fmt.Sprintf("获取冰排【%s】信息失败", code))
+//					}
+//
+//				}
+//				if user.Dept.IsOutStorage {
+//					if iceRaft.IceRaftRecord.Status != model.IceRaftRecordStatusWaitUse {
+//						err = errors.New(fmt.Sprintf("冰排【%s】状态为%s,出库保温箱失败!", code, model.GetIceRaftRecordStatus(iceRaft.IceRaftRecord.Status)))
+//						return err
+//					}
+//					err = tx.Model(&model.IceRaftRecord{}).
+//						Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusWaitUse).
+//						Updates(map[string]interface{}{
+//							"status":           model.IceRaftRecordStatusUsing,
+//							"out_storage_time": time.Now(),
+//							"cooler_box_id":    c.CoolerBoxId,
+//						}).Error
+//					if err != nil {
+//						e.Log.Errorf("db error: %s", err)
+//						return errors.New(fmt.Sprintf("冰排【%s】出库失败", iceRaft.Code))
+//					}
+//
+//				} else {
+//					err = tx.Model(&model.IceRaftRecord{}).
+//						Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusFreezing).
+//						Or("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusWaitUse).
+//						Or("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordReleasedInTheCold).
+//						Updates(map[string]interface{}{
+//							"status":           model.IceRaftRecordStatusUsing,
+//							"out_storage_time": time.Now(),
+//							"cooler_box_id":    c.CoolerBoxId,
+//						}).Error
+//					if err != nil {
+//						e.Log.Errorf("db error: %s", err)
+//						return errors.New(fmt.Sprintf("冰排【%s】出库失败", iceRaft.Code))
+//					}
+//				}
+//				// 获取冰排记录
+//				//err = tx.Model(&model.IceRaftRecord{}).
+//				//	Where("ice_raft_id = ? and status = ?", iceRaft.Id, model.IceRaftRecordStatusWaitUse).
+//				//	Updates(map[string]interface{}{
+//				//		"status":           model.IceRaftRecordStatusUsing,
+//				//		"out_storage_time": time.Now(),
+//				//		"cooler_box_id":    c.CoolerBoxId,
+//				//	}).Error
+//				//if err != nil {
+//				//	e.Log.Errorf("db error: %s", err)
+//				//	return errors.New(fmt.Sprintf("冰排【%s】出库失败", iceRaft.Code))
+//				//}
+//			}
+//		}
+//	}
+//
+//	var coolerBox = model.CoolerBox{}
+//	// 查询保温箱信息
+//	if c.CoolerBoxId != 0 {
+//		err = tx.Scopes(actions.Permission(coolerBox.TableName(), p)).
+//			First(&coolerBox, c.CoolerBoxId).Error
+//		if err != nil {
+//			e.Log.Errorf("获取保温箱信息失败: %s", err)
+//			return errors.New("获取保温箱信息失败")
+//		}
+//	}
+//
+//	for _, id := range c.WaybillIds {
+//
+//		var waybillModel = model.Waybill{}
+//		// 查询运单是否存在
+//		err = tx.Scopes(actions.Permission(waybillModel.TableName(), p)).
+//			First(&waybillModel, id).Error
+//		if err != nil {
+//			e.Log.Errorf("db error: %s", err)
+//			if errors.Is(err, gorm.ErrRecordNotFound) {
+//				return global.UpdateNotFoundOrNoPermissionErr
+//			}
+//			return global.UpdateFailedErr
+//		}
+//		//派单时添加
+//		//waybillModeStatus := waybillModel.Status
+//		if waybillModel.Status == model.WaybillStatusInDeliverys || (waybillModel.Status == model.WaybillStatusInDeliverys && waybillModel.CoolerBoxId != c.CoolerBoxId) || (waybillModel.Status == model.WaybillStatusInDeliverys && !lib.StrListEqual(c.IceRaftCode, c.OldIceRaftCode)) {
+//			waybillModel.Status = model.WaybillStatusInDeliverys
+//			waybillModel.TamperProofLabel = c.TamperProofLabel
+//			waybillModel.TamperProofLabelImg = c.TamperProofLabelImg // 防拆标签
+//			waybillModel.DeliveryId = c.DeliveryId
+//			waybillModel.AssignmentStatus = 2 // 分配状态 2 已派单
+//			waybillModel.CoolerBoxId = c.CoolerBoxId
+//			waybillModel.DeliveryTime = model2.Time(time.Now())
+//			waybillModel.IceRaftCode = c.IceRaftCode
+//			waybillModel.SecondaryTime = model2.Time(time.Now()) //分配时间
+//			if c.Status == model.WaybillStatusInShippeds {
+//				waybillModel.Status = c.Status
+//				var data model.IceRaft
+//				var list []string
+//				err := e.Orm.Model(&data).
+//					Select("ice_raft.code").
+//					Scopes(
+//						actions.Permission(data.TableName(), p),
+//						IceRaftNameRecordStatusScopes(model.IceRaftRecordStatusUsing),
+//						IceRaftNameRecordCoolerBoxIdScopes(c.CoolerBoxId),
+//					).
+//					Joins("left join ice_raft_record on ice_raft.ice_raft_record_id = ice_raft_record.id").
+//					Find(&list).Error
+//				if err != nil {
+//					e.Log.Errorf("db error: %s", err)
+//					return errors.New("获取冰排编号失败")
+//				}
+//				//查询保温箱记录信息状态为1使用中
+//				var cooler model.CoolerBoxRecord
+//				var count int64
+//				e.Orm.Scopes(actions.Permission(cooler.TableName(), p)).
+//					Where("cooler_box_id = ?", c.CoolerBoxId).Where("status = ?", 1).First(&cooler).Count(&count)
+//				if count == 0 {
+//					cooler.CoolerBoxId = c.CoolerBoxId
+//					cooler.Name = coolerBox.Name
+//					cooler.Sn = coolerBox.Sn
+//					cooler.CoolerUseUsers = user.NickName
+//					cooler.CoolerUserTime = model2.Time(time.Now())
+//					cooler.Status = "1" //使用结束
+//					cooler.DeptId = user.DeptId
+//					if len(list) == 0 {
+//						cooler.HistoryCode = append(cooler.HistoryCode, c.IceRaftCode...)
+//						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+//					} else {
+//						cooler.HistoryCode = append(cooler.HistoryCode, list...)
+//						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+//					}
+//					err := e.Orm.Save(&cooler).Error
+//					if err != nil {
+//						e.Log.Errorf("保存保温箱记录失败: %s", err)
+//						return errors.New("保存保温箱记录失败")
+//					}
+//				} else {
+//					cooler.CoolerUseUsers = user.NickName
+//					if len(list) == 0 {
+//						cooler.HistoryCode = append(cooler.HistoryCode, c.IceRaftCode...)
+//						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+//					} else {
+//						cooler.HistoryCode = append(cooler.HistoryCode, list...)
+//						cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+//					}
+//					err = e.Orm.Updates(&cooler).Where("id = ?", cooler.Id).Error
+//					if err != nil {
+//						e.Log.Errorf("保存保温箱记录失败: %s", err)
+//						return errors.New("保存保温箱记录失败")
+//					}
+//				}
+//				// 配送中状态下才发送短信 (改用普通短信 Send,无需模板)
+//				// 配送中状态下才发送短信(改用自定义模板发送)
+//				addr := conf.ExtConfig.Applet.WaybillUrl + url.QueryEscape(lib.AesEncryptCBC(waybillModel.WaybillNo, lib.AesKey))
+//				res, err1 := CustomSmsXSend(conf.ExtConfig.SubMail.Appid, conf.ExtConfig.SubMail.Signature, waybillModel.ConsigneeAddressPhone, addr)
+//				if err1 != nil || res.Status != sms.SUCCESS {
+//					e.Log.Errorf("派单短信发送失败", zap.Any("res", res), zap.Error(err1))
+//					err = errors.New("派单短信发送失败,请检查收件人电话是否正确!")
+//					return err
+//				}
+//				waybillModel.SendLog = model.WaybillSendLog{
+//					Phone:   waybillModel.ConsigneeAddressPhone,
+//					Content: "【贵州宝智达冷链科技公司】您的订单正在派送中,点击查看详情:" + addr, // 同步改成新模板的文案
+//				}
+//				//// 查询任务
+//				//var logistics model.WaybillLogistics
+//				// 添加物流
+//				//logisticsObj := model.WaybillLogistics{
+//				//	WaybillNo:   waybillModel.WaybillNo,
+//				//	Status:      waybillModel.Status,
+//				//	CoolerBoxId: coolerBox.Id,
+//				//	ControlBy: model2.ControlBy{
+//				//		CreateBy: p.UserId,
+//				//	},
+//				//	DeptBy: model2.DeptBy{
+//				//		DeptId: p.DeptId,
+//				//	},
+//				//}
+//				//err = tx.Create(&logisticsObj).Error
+//				//if err != nil {
+//				//	e.Log.Errorf("db error: %s", err)
+//				//	return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
+//				//}
+//				//logistics.CoolerBoxId = coolerBox.Id
+//				//err = tx.Save(&logistics).Error
+//				//if err != nil {
+//				//	e.Log.Errorf("db error: %s", err)
+//				//	return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
+//				//}
+//			}
+//			// 查询任务
+//			err = tx.Save(&waybillModel).Error
+//			if err != nil {
+//				e.Log.Errorf("db error: %s", err)
+//				return global.UpdateFailedErr
+//			}
+//			// 查询任务
+//			//var logistics model.WaybillLogistics
+//			//err = tx.Model(&logistics).Where("waybill_no = ?", waybillModel.WaybillNo).
+//			//	Last(&logistics).Error
+//			//if err != nil {
+//			//	if errors.Is(err, gorm.ErrRecordNotFound) {
+//			//		// 添加物流
+//			//		logisticsObj := model.WaybillLogistics{
+//			//			WaybillNo:   waybillModel.WaybillNo,
+//			//			Status:      waybillModel.Status,
+//			//			CoolerBoxId: coolerBox.Id,
+//			//			ControlBy: model2.ControlBy{
+//			//				CreateBy: p.UserId,
+//			//			},
+//			//			DeptBy: model2.DeptBy{
+//			//				DeptId: p.DeptId,
+//			//			},
+//			//		}
+//			//		err = tx.Create(&logisticsObj).Error
+//			//		if err != nil {
+//			//			e.Log.Errorf("db error: %s", err)
+//			//			return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
+//			//		}
+//			//	} else {
+//			//		e.Log.Errorf("db error: %s", err)
+//			//		return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
+//			//	}
+//			//	logistics.CoolerBoxId = coolerBox.Id
+//			//	err = tx.Save(&logistics).Error
+//			//	if err != nil {
+//			//		e.Log.Errorf("db error: %s", err)
+//			//		return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
+//			//	}
+//			//}
+//			// 添加物流
+//			logisticsObj := model.WaybillLogistics{
+//				WaybillNo:   waybillModel.WaybillNo,
+//				Status:      waybillModel.Status,
+//				CoolerBoxId: coolerBox.Id,
+//				ControlBy: model2.ControlBy{
+//					CreateBy: p.UserId,
+//				},
+//				DeptBy: model2.DeptBy{
+//					DeptId: p.DeptId,
+//				},
+//			}
+//			err = tx.Create(&logisticsObj).Error
+//			if err != nil {
+//				e.Log.Errorf("db error: %s", err)
+//				return errors.New(fmt.Sprintf("保存运单物流信息失败:%s", err))
+//			}
+//			// 查询任务
+//			var task model.WaybillTask
+//			err = tx.Model(&task).Where("waybill_no = ? ", waybillModel.WaybillNo).
+//				Last(&task).Error
+//			if err != nil {
+//				if errors.Is(err, gorm.ErrRecordNotFound) {
+//					// 添加任务
+//					taskObj := model.WaybillTask{
+//						WaybillNo:   waybillModel.WaybillNo,
+//						CoolerBoxId: coolerBox.Id,
+//						UserId:      p.UserId,
+//						Sn:          coolerBox.Sn,
+//						StartTime:   model2.Time(time.Now()),
+//						ControlBy: model2.ControlBy{
+//							CreateBy: p.UserId,
+//						},
+//						DeptBy: model2.DeptBy{
+//							DeptId: p.DeptId,
+//						},
+//					}
+//					err = tx.Create(&taskObj).Error
+//					if err != nil {
+//						e.Log.Errorf("db error: %s", err)
+//						return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
+//					}
+//				} else {
+//					//task.StartTime = model2.Time(time.Now())
+//					task.UpdateBy = p.UserId
+//					task.CoolerBoxId = coolerBox.Id
+//					task.Sn = coolerBox.Sn
+//					err = tx.Save(&task).Error
+//					if err != nil {
+//						e.Log.Errorf("db error: %s", err)
+//						return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
+//					}
+//				}
+//			} else {
+//				task.UpdateBy = p.UserId
+//				task.CoolerBoxId = coolerBox.Id
+//				task.Sn = coolerBox.Sn
+//				err = tx.Save(&task).Error
+//				if err != nil {
+//					e.Log.Errorf("db error: %s", err)
+//					return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
+//				}
+//			}
+//		}
+//	}
+//	return nil
+//}
+
 // Remove 删除Waybill
 func (e *Waybill) Remove(c *dto.WaybillDeleteReq, p *actions.DataPermission) error {
 	var err error
@@ -1001,24 +1160,42 @@ func (e *Waybill) Receipt(c *dto.WaybillReceiptReq, p *actions.DataPermission) e
 	if err != nil {
 		return errors.New("获取用户信息失败")
 	}
-	// 查询保温箱信息
-	var coolerBox = model.CoolerBox{}
-	err = tx.
-		First(&coolerBox, waybillModel.CoolerBoxId).Error
-	if err != nil {
-		e.Log.Errorf("db error: %s", err)
-		return errors.New("获取保温箱信息失败")
+
+	// 查询保温箱信息,无保温箱时跳过
+	var coolerBox model.CoolerBox
+	if waybillModel.CoolerBoxId > 0 {
+		err = tx.First(&coolerBox, waybillModel.CoolerBoxId).Error
+		if err != nil {
+			e.Log.Errorf("db error: %s", err)
+			return errors.New("获取保温箱信息失败")
+		}
 	}
+	// 查询保温箱信息
+	//var coolerBox = model.CoolerBox{}
+	//err = tx.
+	//	First(&coolerBox, waybillModel.CoolerBoxId).Error
+	//if err != nil {
+	//	e.Log.Errorf("db error: %s", err)
+	//	return errors.New("获取保温箱信息失败")
+	//}
 	if waybillModel.Status == model.WaybillStatusWaitDelivery {
 		err = errors.New(fmt.Sprintf("运单状态为%s,禁止操作", model.WaybillStatusMap[waybillModel.Status]))
 		return err
 	}
-	//更新保温箱状态为默认状态  0
-	err = tx.Model(&model.CoolerBox{}).Where("id =?", coolerBox.Id).Update("use_status", 0).Error
-	if err != nil {
-		e.Log.Errorf("更新保温箱状态失败: %s", err)
-		return errors.New("更新保温箱状态失败")
+	//更新保温箱状态为默认状态0,无保温箱时跳过
+	if waybillModel.CoolerBoxId > 0 {
+		err = tx.Model(&model.CoolerBox{}).Where("id =?", coolerBox.Id).Update("use_status", 0).Error
+		if err != nil {
+			e.Log.Errorf("更新保温箱状态失败: %s", err)
+			return errors.New("更新保温箱状态失败")
+		}
 	}
+	//更新保温箱状态为默认状态0
+	//err = tx.Model(&model.CoolerBox{}).Where("id =?", coolerBox.Id).Update("use_status", 0).Error
+	//if err != nil {
+	//	e.Log.Errorf("更新保温箱状态失败: %s", err)
+	//	return errors.New("更新保温箱状态失败")
+	//}
 
 	if waybillModel.Status == model.WaybillStatusReceipts || waybillModel.Status == model.WaybillStatusRejections {
 		return nil
@@ -1040,66 +1217,77 @@ func (e *Waybill) Receipt(c *dto.WaybillReceiptReq, p *actions.DataPermission) e
 		return errors.New(fmt.Sprintf("保存运单信息失败:%s", err))
 	}
 	var lng, lat string
-	lng, lat, err = e.GetSite(waybillModel.DeptId, coolerBox.Sn, waybillModel.ReceiptTime.String())
-	if err != nil {
-		e.Log.Errorf("获取定位信息失败: %s", err)
-		return err
-	}
-	//查询保温箱记录信息状态为1使用中
-	var cooler model.CoolerBoxRecord
-	var count int64
-	tx.Where("cooler_box_id = ?", coolerBox.Id).Where("status = ?", 1).First(&cooler).Count(&count)
-	if count == 0 {
-		cooler.CoolerBoxId = coolerBox.Id
-		cooler.Name = coolerBox.Name
-		cooler.Sn = coolerBox.Sn
-		cooler.CoolerUseUsers = user.NickName
-		cooler.CoolerEndUseTime = model2.Time(time.Now()) //保温箱结束使用时间
-		cooler.Status = "2"                               //使用结束
-		cooler.DeptId = user.DeptId
-		if len(waybillModel.IceRaftCode) == 0 {
-			cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
-		} else {
-			cooler.HistoryCode = append(cooler.HistoryCode, waybillModel.IceRaftCode...)
-			cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
-		}
-		err := tx.Save(&cooler).Error
+	// 获取定位信息,无保温箱时跳过,失败不阻塞签收
+	if waybillModel.CoolerBoxId > 0 {
+		lng, lat, err = e.GetSite(waybillModel.DeptId, coolerBox.Sn, waybillModel.ReceiptTime.String())
 		if err != nil {
-			e.Log.Errorf("保存保温箱记录失败: %s", err)
-			return errors.New("保存保温箱记录失败")
-		}
-	} else {
-		cooler.CoolerUseUsers = user.NickName
-		if len(waybillModel.IceRaftCode) == 0 {
-			cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+			e.Log.Errorf("获取定位信息失败: %s", err)
+		}
+	}
+	// 获取定位信息
+	//var lng, lat string
+	//lng, lat, err = e.GetSite(waybillModel.DeptId, coolerBox.Sn, waybillModel.ReceiptTime.String())
+	//if err != nil {
+	//	e.Log.Errorf("获取定位信息失败: %s", err)
+	//	return err
+	//}
+	// 保温箱记录和任务处理,无保温箱时跳过
+	if waybillModel.CoolerBoxId > 0 {
+		//查询保温箱记录信息状态为1使用中
+		var cooler model.CoolerBoxRecord
+		var count int64
+		tx.Where("cooler_box_id = ?", coolerBox.Id).Where("status = ?", 1).First(&cooler).Count(&count)
+		if count == 0 {
+			cooler.CoolerBoxId = coolerBox.Id
+			cooler.Name = coolerBox.Name
+			cooler.Sn = coolerBox.Sn
+			cooler.CoolerUseUsers = user.NickName
+			cooler.CoolerEndUseTime = model2.Time(time.Now())
+			cooler.Status = "2"
+			cooler.DeptId = user.DeptId
+			if len(waybillModel.IceRaftCode) == 0 {
+				cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+			} else {
+				cooler.HistoryCode = append(cooler.HistoryCode, waybillModel.IceRaftCode...)
+				cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+			}
+			err := tx.Save(&cooler).Error
+			if err != nil {
+				e.Log.Errorf("保存保温箱记录失败: %s", err)
+				return errors.New("保存保温箱记录失败")
+			}
 		} else {
-			cooler.HistoryCode = append(cooler.HistoryCode, waybillModel.IceRaftCode...)
-			cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
-		}
-		err = tx.Updates(&cooler).Where("id = ?", cooler.Id).Error
-		if err != nil {
-			e.Log.Errorf("保存保温箱记录失败: %s", err)
-			return errors.New("保存保温箱记录失败")
+			cooler.CoolerUseUsers = user.NickName
+			if len(waybillModel.IceRaftCode) == 0 {
+				cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+			} else {
+				cooler.HistoryCode = append(cooler.HistoryCode, waybillModel.IceRaftCode...)
+				cooler.HistoryCode = cDto.UniqueSlice(cooler.HistoryCode)
+			}
+			err = tx.Updates(&cooler).Where("id = ?", cooler.Id).Error
+			if err != nil {
+				e.Log.Errorf("保存保温箱记录失败: %s", err)
+				return errors.New("保存保温箱记录失败")
+			}
 		}
-	}
 
-	// 查询任务
-	var task model.WaybillTask
-	err = tx.Model(&task).Where("waybill_no = ? and cooler_box_id = ?", c.WaybillNo, coolerBox.Id).
-		Last(&task).Error
-	if err != nil {
-		e.Log.Errorf("db error: %s", err)
-		return errors.New(fmt.Sprintf("查询运单任务信息失败:%s", err))
-	}
-	// 未下车 直接点签收
-	if time.Time(task.EndTime).IsZero() {
-		task.EndTime = model2.Time(time.Now())
-		task.CoolerBoxId = coolerBox.Id
-		//task.UpdateBy = p.UserIds
-		err = tx.Save(&task).Error
-		if err != nil {
+		// 查询任务
+		var task model.WaybillTask
+		err = tx.Model(&task).Where("waybill_no = ? and cooler_box_id = ?", c.WaybillNo, coolerBox.Id).
+			Last(&task).Error
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
 			e.Log.Errorf("db error: %s", err)
-			return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
+			return errors.New(fmt.Sprintf("查询运单任务信息失败:%s", err))
+		}
+		// 未下车 直接点签收
+		if err == nil && time.Time(task.EndTime).IsZero() {
+			task.EndTime = model2.Time(time.Now())
+			task.CoolerBoxId = coolerBox.Id
+			err = tx.Save(&task).Error
+			if err != nil {
+				e.Log.Errorf("db error: %s", err)
+				return errors.New(fmt.Sprintf("保存运单任务信息失败:%s", err))
+			}
 		}
 	}
 	var userId int
@@ -2003,3 +2191,38 @@ func CustomSmsXSend(appid, signature, to, addr string) (sms.XSendRes, error) {
 	}
 	return res, nil
 }
+
+// 发送配送通知短信给配送员,使用新模板 gOK601
+func SendDeliveryNotifySms(to, orderNo string) error {
+	vars := map[string]string{"orderNo": orderNo}
+	varsBytes, _ := json.Marshal(vars)
+
+	payload := url.Values{}
+	payload.Set("appid", conf.ExtConfig.SubMail.Appid)
+	payload.Set("signature", conf.ExtConfig.SubMail.Signature)
+	payload.Set("to", to)
+	payload.Set("project", "gOK601")
+	payload.Set("vars", string(varsBytes))
+
+	req, err := http.NewRequest("POST", "https://api-v4.mysubmail.com/sms/xsend", strings.NewReader(payload.Encode()))
+	if err != nil {
+		return err
+	}
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	var res sms.XSendRes
+	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
+		return err
+	}
+	if res.Status != sms.SUCCESS {
+		return errors.New(res.Msg)
+	}
+	return nil
+}

+ 1 - 0
cmd/api/server.go

@@ -68,6 +68,7 @@ func setup() {
 		nats.Setup,
 	)
 	database.AutoMigrateDB()
+	database.AutoSeedMenus() // 初始化菜单数据
 	//注册监听函数
 	queue := sdk.Runtime.GetMemoryQueue("")
 	queue.Register(global2.LoginLog, model.SaveLoginLog)

+ 12 - 0
cmd/migrate/initdb.go

@@ -14,6 +14,18 @@ func InitDb(db *gorm.DB) (err error) {
 	if err = ExecSql(db, filePath); err != nil {
 		return err
 	}
+	// 初始化菜单数据(如果表为空)
+	menuPath := "conf/menu_seed.sql"
+	var count int64
+	db.Table("sys_menu").Count(&count)
+	if count == 0 {
+		log.Println("sys_menu 表为空,开始初始化菜单数据...")
+		if err = ExecSql(db, menuPath); err != nil {
+			log.Printf("菜单数据初始化失败: %s", err)
+			return err
+		}
+		log.Println("菜单数据初始化完成")
+	}
 	return err
 }
 

+ 13 - 1
common/middleware/handler/auth.go

@@ -27,6 +27,7 @@ func PayloadFunc(data interface{}) jwt.MapClaims {
 		u, _ := v["user"].(SysUser)
 		r, _ := v["role"].(SysRole)
 		d, _ := v["dept"].(SysDept)
+		p, _ := v["post"].(SysPost)
 		single, _ := v["single"].(bool)
 		return jwt.MapClaims{
 			jwt.UUIDKey:      u.Uuid,
@@ -39,6 +40,8 @@ func PayloadFunc(data interface{}) jwt.MapClaims {
 			jwt.SingleKey:    single,
 			jwt.DeptIdKey:    u.DeptId,
 			jwt.DeptNameKey:  d.DeptName,
+			"postId":         u.PostId,
+			"postCode":       p.PostCode,
 		}
 	}
 	return jwt.MapClaims{}
@@ -58,6 +61,8 @@ func IdentityHandler(c *gin.Context) interface{} {
 		"single":      claims["single"],
 		"DeptId":      claims["deptId"],
 		"DeptName":    claims["deptName"],
+		"PostId":      claims["postId"],
+		"PostCode":    claims["postCode"],
 	}
 }
 
@@ -148,7 +153,12 @@ func Authenticator(c *gin.Context) (interface{}, error) {
 	//	return nil, err
 	//}
 
-	return map[string]interface{}{"user": u, "role": role, "dept": dept, "single": single, "mobile": loginVals.Mobile}, nil
+	// 加载用户岗位
+	var post SysPost
+	if u.PostId > 0 {
+		ormDB.Table("sys_post").Where("id = ? AND status = '2'", u.PostId).First(&post)
+	}
+	return map[string]interface{}{"user": u, "role": role, "dept": dept, "post": post, "single": single, "mobile": loginVals.Mobile}, nil
 }
 
 // LoginLogToDB Write log to database
@@ -223,6 +233,8 @@ func Authorizator(data interface{}, c *gin.Context) bool {
 		c.Set("dataScope", r.DataScope)
 		c.Set("deptId", u.DeptId)
 		c.Set("deptName", d.Name)
+		c.Set("postId", u.PostId)
+		c.Set("postCode", "")
 		return true
 	}
 	return false

+ 6 - 5
common/middleware/handler/login.go

@@ -41,11 +41,12 @@ func (u *Login) GetUser(tx *gorm.DB) (user SysUser, role SysRole, dept SysDept,
 		log.Errorf("user login error, %s", err.Error())
 		return
 	}
-	//err = tx.Table("sys_role").Where("id = ? ", user.RoleId).First(&role).Error
-	//if err != nil {
-	//	log.Errorf("get role error, %s", err.Error())
-	//	return
-	//}
+	if user.RoleId > 0 {
+		err = tx.Table("sys_role").Where("id = ? ", user.RoleId).First(&role).Error
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+			log.Errorf("get role error, %s", err.Error())
+		}
+	}
 	if user.DeptId > 0 {
 		err = tx.Table("sys_dept").Where("id = ? ", user.DeptId).First(&dept).Error
 		if err != nil {

+ 19 - 0
common/middleware/handler/post.go

@@ -0,0 +1,19 @@
+package handler
+
+import (
+	"cold-delivery/common/model"
+)
+
+type SysPost struct {
+	model.Model
+	PostName string `json:"postName" gorm:"size:128"`
+	PostCode string `json:"postCode" gorm:"size:128"`
+	DeptId   int    `json:"deptId"`
+	Status   string `json:"status"`
+	model.ControlBy
+	model.ModelTime
+}
+
+func (SysPost) TableName() string {
+	return "sys_post"
+}

+ 14 - 3
common/middleware/permission.go

@@ -22,8 +22,15 @@ func AuthCheckRole() gin.HandlerFunc {
 		var res, casbinExclude bool
 		var err error
 		// 检查权限
-		rolekey := v["roleKey"].(string)
-		if rolekey == "admin" {
+		rolekey, _ := v["roleKey"].(string)
+		identity, _ := v["identity"]
+		var userId float64
+		if identity != nil {
+			if f, ok := identity.(float64); ok {
+				userId = f
+			}
+		}
+		if rolekey == "admin" || userId == 1 {
 			res = true
 			c.Next()
 			return
@@ -41,7 +48,11 @@ func AuthCheckRole() gin.HandlerFunc {
 			c.Next()
 			return
 		}
-		res, err = mycasbin.EnforceRoute(rolekey, "", c.Request, e)
+		enforceSub := rolekey
+		if postCode, ok := v["postCode"]; ok && postCode != nil && postCode.(string) != "" {
+			enforceSub = postCode.(string)
+		}
+		res, err = mycasbin.EnforceRoute(enforceSub, "", c.Request, e)
 		if err != nil {
 			log.Errorf("AuthCheckRole error: %s method:%s path:%s", err, c.Request.Method, c.Request.URL.Path)
 			response.Error(c, 500, err, err.Error())

+ 660 - 0
conf/cold_delivery.sql

@@ -0,0 +1,660 @@
+/*
+ Navicat MySQL Dump SQL
+
+ Source Server         : yunshu
+ Source Server Type    : MySQL
+ Source Server Version : 80406 (8.4.6)
+ Source Host           : 8.148.211.203:3306
+ Source Schema         : cold_delivery
+
+ Target Server Type    : MySQL
+ Target Server Version : 80406 (8.4.6)
+ File Encoding         : 65001
+
+ Date: 23/07/2026 10:08:26
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for address
+-- ----------------------------
+DROP TABLE IF EXISTS `address`;
+CREATE TABLE `address`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `phone` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `address_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_address_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_address_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_address_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_address_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for address_default
+-- ----------------------------
+DROP TABLE IF EXISTS `address_default`;
+CREATE TABLE `address_default`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `user_id` bigint NULL DEFAULT NULL COMMENT '用户id',
+  `address_id` bigint NULL DEFAULT NULL COMMENT '地址id',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  UNIQUE INDEX `idx_address_default_user_id`(`user_id` ASC) USING BTREE,
+  INDEX `idx_address_default_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_address_default_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_address_default_create_by`(`create_by` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for cooler_box
+-- ----------------------------
+DROP TABLE IF EXISTS `cooler_box`;
+CREATE TABLE `cooler_box`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `sn` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2' COMMENT '状态',
+  `history_sn` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `cold_spots` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `cold_temperatures` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `for_cold_cooler_time` decimal(10, 2) NULL DEFAULT NULL COMMENT '预冷要求',
+  `monitor_status` bigint NULL DEFAULT NULL COMMENT '监控状态',
+  `for_cold_status` bigint NULL DEFAULT 0 COMMENT '预冷状态',
+  `bind_ice_raft_id` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '绑定冰排',
+  `use_status` bigint NULL DEFAULT 0 COMMENT '预冷状态',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_cooler_box_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_cooler_box_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_cooler_box_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_cooler_box_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for cooler_box_record
+-- ----------------------------
+DROP TABLE IF EXISTS `cooler_box_record`;
+CREATE TABLE `cooler_box_record`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `cooler_box_id` bigint NULL DEFAULT NULL,
+  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `sn` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `history_code` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '0' COMMENT '状态',
+  `cooler_box_star_time` datetime(3) NULL DEFAULT NULL COMMENT '保温箱开始预冷时间',
+  `cooler_user_time` datetime(3) NULL DEFAULT NULL COMMENT '保温箱开始使用时间',
+  `cooler_cold_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保温箱预冷地点',
+  `cold_temperatures` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保温箱预冷温度',
+  `cooler_cold_users` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保温箱预冷操作人员',
+  `cooler_use_users` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保温箱使用操作人员',
+  `cooler_end_use_time` datetime(3) NULL DEFAULT NULL COMMENT '保温箱结束使用时间',
+  `for_cold_cooler_time` decimal(10, 2) NULL DEFAULT NULL COMMENT '预冷要求',
+  `for_cold_duration` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '预冷累计时长',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_cooler_box_record_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_cooler_box_record_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_cooler_box_record_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_cooler_box_record_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for drug
+-- ----------------------------
+DROP TABLE IF EXISTS `drug`;
+CREATE TABLE `drug`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  `waybill_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '运单id',
+  `physic_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '药品名称',
+  `physic_type_desc` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '药品类型',
+  `pkg_spec_crit` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '包装规格',
+  `prepn_spec` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '制剂规格',
+  `prepn_type_desc` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '制剂类型',
+  `approval_licence_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '批准文号',
+  `drug_ent_base_info_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '药品id',
+  `ent_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '企业名称',
+  `code` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '药品编码',
+  `count` bigint NULL DEFAULT NULL COMMENT '数量',
+  `is_assignment` tinyint(1) NULL DEFAULT 0 COMMENT '是否分配',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_drug_dept_id`(`dept_id` ASC) USING BTREE,
+  INDEX `idx_drug_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_drug_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_drug_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ice_locker
+-- ----------------------------
+DROP TABLE IF EXISTS `ice_locker`;
+CREATE TABLE `ice_locker`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2' COMMENT '状态',
+  `sn` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `history_sn` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_ice_locker_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_ice_locker_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_ice_locker_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_ice_locker_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ice_raft
+-- ----------------------------
+DROP TABLE IF EXISTS `ice_raft`;
+CREATE TABLE `ice_raft`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `code` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2' COMMENT '状态',
+  `ice_raft_record_id` bigint NULL DEFAULT NULL,
+  `freeze_claim` json NULL COMMENT '冷冻要求',
+  `label` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标签',
+  `ice_cold_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '冰排预冷地点',
+  `suitable_for_cold` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '冰排适冷温度',
+  `sort` tinyint NULL DEFAULT NULL COMMENT '排序',
+  `for_cold_time` decimal(10, 2) NULL DEFAULT NULL COMMENT '适冷时长',
+  `bind_cooler_box` json NULL COMMENT '绑定保温箱',
+  `cooler_box_name` json NULL COMMENT '保温箱名称',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_ice_raft_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_ice_raft_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_ice_raft_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_ice_raft_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ice_raft_record
+-- ----------------------------
+DROP TABLE IF EXISTS `ice_raft_record`;
+CREATE TABLE `ice_raft_record`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `ice_raft_id` bigint NULL DEFAULT NULL COMMENT '冰排id',
+  `label` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标签',
+  `code` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '编号',
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2' COMMENT '状态',
+  `sort` tinyint NULL DEFAULT NULL COMMENT '排序',
+  `for_cold_time` decimal(10, 2) NULL DEFAULT NULL COMMENT '要求适冷时间',
+  `for_cold_time_actual` decimal(10, 0) NULL DEFAULT NULL COMMENT '实际适冷时间',
+  `in_storage_time` datetime(3) NULL DEFAULT NULL COMMENT '入库时间',
+  `out_storage_time` datetime(3) NULL DEFAULT NULL COMMENT '出库时间',
+  `ice_locker_id` bigint NULL DEFAULT NULL COMMENT '冷冻柜id',
+  `cooler_box_id` bigint NULL DEFAULT NULL COMMENT '保温箱id',
+  `freeze_claim` json NULL COMMENT '冷冻要求',
+  `freeze_duration` decimal(10, 2) NULL DEFAULT NULL COMMENT '冷冻时长',
+  `suitable_for_cold` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '冰排适冷温度',
+  `end_for_cold_time` datetime(3) NULL DEFAULT NULL COMMENT '适冷结束时间',
+  `is_suitable_for_cold` tinyint NOT NULL COMMENT '是否结束适冷',
+  `cooler_box_star_time` datetime(3) NULL DEFAULT NULL COMMENT '保温箱开始预冷时间',
+  `cooler_box_end_time` datetime(3) NULL DEFAULT NULL COMMENT '保温箱结束记录时间',
+  `start_ice_cold_time` datetime(3) NULL DEFAULT NULL COMMENT '释冷开始时间',
+  `ice_cold_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '冰排预冷地点',
+  `ice_cold_users` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '冰排预冷操作人员',
+  `ice_use_users` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '冰排使用操作人员',
+  `ice_use_time` datetime(3) NULL DEFAULT NULL COMMENT '冰排使用时间',
+  `cooler_cold_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保温箱预冷地点',
+  `cooler_cold_users` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保温箱预冷操作人员',
+  `cooler_use_users` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保温箱使用操作人员',
+  `return_date` datetime(3) NULL DEFAULT NULL COMMENT '归还日期',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_ice_raft_record_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_ice_raft_record_dept_id`(`dept_id` ASC) USING BTREE,
+  INDEX `idx_ice_raft_record_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_ice_raft_record_update_by`(`update_by` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_casbin_rule
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_casbin_rule`;
+CREATE TABLE `sys_casbin_rule`  (
+  `p_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+  `v0` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+  `v1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+  `v2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+  `v3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+  `v4` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+  `v5` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+  UNIQUE INDEX `idx_sys_casbin_rule`(`p_type` ASC, `v0` ASC, `v1` ASC, `v2` ASC, `v3` ASC, `v4` ASC, `v5` ASC) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_dept
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_dept`;
+CREATE TABLE `sys_dept`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `parent_id` bigint NULL DEFAULT NULL,
+  `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路径',
+  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '部门名称',
+  `sort` tinyint NULL DEFAULT NULL,
+  `status` tinyint NULL DEFAULT NULL,
+  `remark` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `cold_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `cold_pid` bigint NULL DEFAULT NULL,
+  `company_address` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `is_ice_release_cold` tinyint(1) NULL DEFAULT 0,
+  `is_cooler_release_cold` tinyint(1) NULL DEFAULT 0,
+  `is_cooler_show` tinyint(1) NULL DEFAULT 0,
+  `is_out_storage` tinyint(1) NULL DEFAULT 1,
+  `app_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'app名称',
+  `ice_raft_manage` tinyint(1) NULL DEFAULT 0,
+  `company_coordinate` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_dept_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_dept_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_dept_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_job
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_job`;
+CREATE TABLE `sys_job`  (
+  `job_id` bigint NOT NULL AUTO_INCREMENT,
+  `job_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `job_group` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `job_type` tinyint NULL DEFAULT NULL,
+  `cron_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `invoke_target` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `args` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `misfire_policy` bigint NULL DEFAULT NULL,
+  `concurrent` tinyint NULL DEFAULT NULL,
+  `status` tinyint NULL DEFAULT NULL,
+  `entry_id` smallint NULL DEFAULT NULL,
+  `auto` tinyint(1) NULL DEFAULT NULL,
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`job_id`) USING BTREE,
+  INDEX `idx_sys_job_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_job_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_job_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_login_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_login_log`;
+CREATE TABLE `sys_login_log`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `username` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态',
+  `ipaddr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'ip地址',
+  `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '归属地',
+  `browser` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '浏览器',
+  `os` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '系统',
+  `platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '固件',
+  `login_time` datetime(3) NULL DEFAULT NULL COMMENT '登录时间',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
+  `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '信息',
+  `created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` datetime(3) NULL DEFAULT NULL COMMENT '最后更新时间',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_login_log_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_login_log_update_by`(`update_by` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 180 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_menu`;
+CREATE TABLE `sys_menu`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `parent_id` smallint NULL DEFAULT NULL,
+  `sort` tinyint NULL DEFAULT NULL,
+  `visible` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '1',
+  `menu_path` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `redirect` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `menu_type` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `icon` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `is_frame` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0',
+  `path` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `permission` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `is_dept` bigint NULL DEFAULT 1,
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_menu_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_menu_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_menu_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 153 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_opera_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_opera_log`;
+CREATE TABLE `sys_opera_log`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作模块',
+  `business_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作类型',
+  `business_types` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'BusinessTypes',
+  `method` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '函数',
+  `request_method` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求方式 GET POST PUT DELETE',
+  `operator_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作类型',
+  `opera_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作者',
+  `dept_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '机构名称',
+  `opera_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '访问地址',
+  `opera_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '客户端ip',
+  `opera_location` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '访问位置',
+  `opera_param` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求参数',
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作状态 1:正常 2:关闭',
+  `opera_time` datetime(3) NULL DEFAULT NULL COMMENT '操作时间',
+  `json_result` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '返回数据',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
+  `latency_time` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '耗时',
+  `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'ua',
+  `created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` datetime(3) NULL DEFAULT NULL COMMENT '最后更新时间',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_opera_log_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_opera_log_update_by`(`update_by` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 6661 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_post
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_post`;
+CREATE TABLE `sys_post`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `post_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '岗位名称',
+  `post_code` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '岗位编码',
+  `sort` tinyint NULL DEFAULT 0 COMMENT '排序',
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2' COMMENT '状态 1停用 2正常',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_post_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_post_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_post_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_sys_post_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_post_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_post_menu`;
+CREATE TABLE `sys_post_menu`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `post_id` bigint NULL DEFAULT NULL COMMENT '岗位ID',
+  `menu_id` bigint NULL DEFAULT NULL COMMENT '菜单ID',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_post_menu_post_id`(`post_id` ASC) USING BTREE,
+  INDEX `idx_sys_post_menu_menu_id`(`menu_id` ASC) USING BTREE,
+  INDEX `idx_sys_post_menu_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_post_menu_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_post_menu_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2205 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_role
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_role`;
+CREATE TABLE `sys_role`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2',
+  `role_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
+  `sort` bigint NULL DEFAULT NULL,
+  `data_scope` bigint NULL DEFAULT NULL,
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_role_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_role_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_role_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_sys_role_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_role_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_role_menu`;
+CREATE TABLE `sys_role_menu`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `role_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色编码',
+  `menu_id` bigint NULL DEFAULT NULL COMMENT '菜单id',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_role_menu_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_role_menu_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_role_menu_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for sys_user
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_user`;
+CREATE TABLE `sys_user`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'uuid',
+  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
+  `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
+  `nick_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称',
+  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号',
+  `role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
+  `salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '加盐',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门',
+  `post_id` bigint NULL DEFAULT NULL COMMENT '岗位',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
+  `status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2' COMMENT '状态',
+  `type` bigint NULL DEFAULT NULL COMMENT '类型',
+  `user_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户类型',
+  `openid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '微信openid',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_user_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_sys_user_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_sys_user_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 104 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for waybill
+-- ----------------------------
+DROP TABLE IF EXISTS `waybill`;
+CREATE TABLE `waybill`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `waybill_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `order_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `status` bigint NULL DEFAULT NULL,
+  `sender_address_details` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `sender_address_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `sender_address_phone` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `consignee_address_details` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `consignee_address_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `consignee_address_phone` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `delivery_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `delivery_phone` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `delivery_id` bigint NULL DEFAULT NULL,
+  `re_check_id` bigint NULL DEFAULT NULL,
+  `remark` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `order_time` datetime(3) NULL DEFAULT NULL,
+  `delivery_time` datetime(3) NULL DEFAULT NULL,
+  `delivery_duration` bigint NULL DEFAULT NULL COMMENT '冷冻时长',
+  `receipt_time` datetime(3) NULL DEFAULT NULL,
+  `quantity` bigint NULL DEFAULT NULL,
+  `cooler_box_id` bigint NULL DEFAULT NULL,
+  `receipt_img` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `receiptsign` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `tamper_proof_label` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `tamper_proof_label_img` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `rejection_reason` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `send_log` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `child_waybills` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `ice_raft_code` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `assess_star` bigint NULL DEFAULT NULL,
+  `assess_content` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `prescription_is_audit` bigint NULL DEFAULT 1,
+  `invoice_is_audit` bigint NULL DEFAULT 1,
+  `inspection_report_is_audit` bigint NULL DEFAULT 1,
+  `dispensing_medicines_is_audit` bigint NULL DEFAULT 1,
+  `is_cold` bigint NULL DEFAULT 1,
+  `payment_status` bigint NULL DEFAULT 1,
+  `payment_type` bigint NULL DEFAULT 0,
+  `payment_remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `assignment_status` bigint NULL DEFAULT 1,
+  `audit_remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `admin_audit_remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `is_secondary_distribution` tinyint(1) NULL DEFAULT 0,
+  `confirmer` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `confirmer_phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `prescription` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
+  `is_child` tinyint(1) NULL DEFAULT 0,
+  `secondary_time` datetime(3) NULL DEFAULT NULL COMMENT '分配时间',
+  `ask_for_time` datetime(3) NULL DEFAULT NULL COMMENT '客户要求时间',
+  `delivery_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '配送方式',
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_waybill_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_waybill_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_waybill_dept_id`(`dept_id` ASC) USING BTREE,
+  INDEX `idx_waybill_create_by`(`create_by` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for waybill_logistics
+-- ----------------------------
+DROP TABLE IF EXISTS `waybill_logistics`;
+CREATE TABLE `waybill_logistics`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `waybill_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `status` bigint NULL DEFAULT NULL,
+  `cooler_box_id` bigint NULL DEFAULT NULL,
+  `user_id` bigint NULL DEFAULT NULL,
+  `lng` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `lat` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_waybill_logistics_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_waybill_logistics_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_waybill_logistics_deleted_at`(`deleted_at` ASC) USING BTREE,
+  INDEX `idx_waybill_logistics_dept_id`(`dept_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for waybill_task
+-- ----------------------------
+DROP TABLE IF EXISTS `waybill_task`;
+CREATE TABLE `waybill_task`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编码',
+  `waybill_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `cooler_box_id` bigint NULL DEFAULT NULL,
+  `user_id` bigint NULL DEFAULT NULL,
+  `sn` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+  `start_time` datetime(3) NULL DEFAULT NULL,
+  `end_time` datetime(3) NULL DEFAULT NULL,
+  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
+  `update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
+  `deleted_at` datetime(3) NULL DEFAULT NULL COMMENT '删除时间',
+  `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_waybill_task_dept_id`(`dept_id` ASC) USING BTREE,
+  INDEX `idx_waybill_task_create_by`(`create_by` ASC) USING BTREE,
+  INDEX `idx_waybill_task_update_by`(`update_by` ASC) USING BTREE,
+  INDEX `idx_waybill_task_deleted_at`(`deleted_at` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 189 - 0
conf/menu_seed.sql

@@ -0,0 +1,189 @@
+-- ============================================================
+-- 菜单权限种子数据
+-- 使用方式:启动项目后表会自动创建,然后执行此SQL插入菜单数据
+-- ============================================================
+
+-- ==================== 一级菜单 ====================
+
+-- 1. 首页
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (1, 0, 0, '1', '/0/1/', '首页', 'C', 'icon-shouye', 'views/page/homeView', '/api/sys-user', 'home', '', 0, 1, 1, NOW(), NOW());
+
+-- 2. 系统管理(目录)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (2, 0, 1, '1', '/0/2/', '系统管理', 'M', 'icon-system', '', '', 'SystemManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 2-1. 用户管理(超管版)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (3, 2, 0, '1', '/0/2/3/', '用户管理', 'C', 'icon-yonghuguanli', 'views/system/interiorUserManagement', '/api/sys-user', 'interiorUserManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 2-1-1. 查询用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (31, 3, 0, '1', '/0/2/3/31/', '查询用户', 'F', '', '', '/api/sys-user', '', 'sys:user:query', 0, 1, 1, NOW(), NOW());
+-- 2-1-2. 新增用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (32, 3, 1, '1', '/0/2/3/32/', '新增用户', 'F', '', '', '/api/sys-user', '', 'sys:user:add', 0, 1, 1, NOW(), NOW());
+-- 2-1-3. 编辑用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (33, 3, 2, '1', '/0/2/3/33/', '编辑用户', 'F', '', '', '/api/sys-user', '', 'sys:user:edit', 0, 1, 1, NOW(), NOW());
+-- 2-1-4. 删除用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (34, 3, 3, '1', '/0/2/3/34/', '删除用户', 'F', '', '', '/api/sys-user', '', 'sys:user:delete', 0, 1, 1, NOW(), NOW());
+-- 2-1-5. 重置密码
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (35, 3, 4, '1', '/0/2/3/35/', '重置密码', 'F', '', '', '/api/sys-user/pwd/reset', '', 'sys:user:resetPwd', 0, 1, 1, NOW(), NOW());
+
+-- 2-2. 公司管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (4, 2, 1, '1', '/0/2/4/', '公司管理', 'C', 'icon-gongsi', 'views/system/CompanyManagement', '/api/company', 'CompanyManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 2-2-1. 查询公司
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (41, 4, 0, '1', '/0/2/4/41/', '查询公司', 'F', '', '', '/api/company', '', 'sys:company:query', 0, 1, 1, NOW(), NOW());
+-- 2-2-2. 新增公司
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (42, 4, 1, '1', '/0/2/4/42/', '新增公司', 'F', '', '', '/api/company', '', 'sys:company:add', 0, 1, 1, NOW(), NOW());
+-- 2-2-3. 编辑公司
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (43, 4, 2, '1', '/0/2/4/43/', '编辑公司', 'F', '', '', '/api/company', '', 'sys:company:edit', 0, 1, 1, NOW(), NOW());
+-- 2-2-4. 删除公司
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (44, 4, 3, '1', '/0/2/4/44/', '删除公司', 'F', '', '', '/api/company', '', 'sys:company:delete', 0, 1, 1, NOW(), NOW());
+-- 2-2-5. 导入公司
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (45, 4, 4, '1', '/0/2/4/45/', '导入公司', 'F', '', '', '/api/company/import-third-tree', '', 'sys:company:import', 0, 1, 1, NOW(), NOW());
+
+-- 2-3. 岗位管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (5, 2, 2, '1', '/0/2/5/', '岗位管理', 'C', 'icon-gangwei', 'views/system/PostManagement', '/api/post', 'PostManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 2-3-1. 查询岗位
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (51, 5, 0, '1', '/0/2/5/51/', '查询岗位', 'F', '', '', '/api/post', '', 'sys:post:query', 0, 1, 1, NOW(), NOW());
+-- 2-3-2. 新增岗位
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (52, 5, 1, '1', '/0/2/5/52/', '新增岗位', 'F', '', '', '/api/post', '', 'sys:post:add', 0, 1, 1, NOW(), NOW());
+-- 2-3-3. 编辑岗位
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (53, 5, 2, '1', '/0/2/5/53/', '编辑岗位', 'F', '', '', '/api/post', '', 'sys:post:edit', 0, 1, 1, NOW(), NOW());
+-- 2-3-4. 删除岗位
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (54, 5, 3, '1', '/0/2/5/54/', '删除岗位', 'F', '', '', '/api/post', '', 'sys:post:delete', 0, 1, 1, NOW(), NOW());
+-- 2-3-5. 分配权限
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (55, 5, 4, '1', '/0/2/5/55/', '分配权限', 'F', '', '', '/api/post/menu-tree', '', 'sys:post:perm', 0, 1, 1, NOW(), NOW());
+
+-- ==================== 一级菜单(续)====================
+
+-- 3. 用户管理(普通管理员版)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (6, 0, 2, '1', '/0/6/', '用户管理', 'C', 'icon-yonghuguanli', 'views/system/UserManagement', '/api/sys-user', 'UserManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 3-1. 查询用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (61, 6, 0, '1', '/0/6/61/', '查询用户', 'F', '', '', '/api/sys-user', '', 'sys:user:query', 0, 1, 1, NOW(), NOW());
+-- 3-2. 新增用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (62, 6, 1, '1', '/0/6/62/', '新增用户', 'F', '', '', '/api/sys-user', '', 'sys:user:add', 0, 1, 1, NOW(), NOW());
+-- 3-3. 编辑用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (63, 6, 2, '1', '/0/6/63/', '编辑用户', 'F', '', '', '/api/sys-user', '', 'sys:user:edit', 0, 1, 1, NOW(), NOW());
+-- 3-4. 删除用户
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (64, 6, 3, '1', '/0/6/64/', '删除用户', 'F', '', '', '/api/sys-user', '', 'sys:user:delete', 0, 1, 1, NOW(), NOW());
+
+-- 4. 外部客户管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (7, 0, 3, '1', '/0/7/', '外部客户管理', 'C', 'icon-kehu', 'views/system/ClientManagement', '/api/customer', 'ClientManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 5. 车辆管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (8, 0, 4, '1', '/0/8/', '车辆管理', 'C', 'icon-cheliang', 'views/system/VehicleManagement', '/api/car', 'VehicleManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 6. 患者薄管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (9, 0, 5, '1', '/0/9/', '患者薄管理', 'C', 'icon-cangku', 'views/system/addressBook', '/api/address', 'addressBook', '', 0, 1, 1, NOW(), NOW());
+
+-- 7. 门店管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (10, 0, 6, '1', '/0/10/', '门店管理', 'C', 'icon-yaodian', 'views/IceCreamFreezer/storeManagement', '/api/warehouse', 'storeManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 8. 设备定位
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (11, 0, 7, '1', '/0/11/', '设备定位', 'C', 'icon-trace', 'views/page/incubatorView', '/api/device', 'incubatorView', '', 0, 1, 1, NOW(), NOW());
+
+-- 9. 设备管理(目录)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (12, 0, 8, '1', '/0/12/', '设备管理', 'M', 'icon-yundan', '', '', 'IceCreamFreezer', '', 0, 1, 1, NOW(), NOW());
+
+-- 9-1. 冰排管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (121, 12, 0, '1', '/0/12/121/', '冰排管理', 'C', 'icon-bingpaiguanli', 'views/IceCreamFreezer/IceManagement', '/api/ice-raft', 'IceManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 9-2. 冰排追溯管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (122, 12, 1, '1', '/0/12/122/', '冰排追溯管理', 'C', 'icon-iceTracing', 'views/IceCreamFreezer/IceTracingManagement', '/api/ice-raft-record', 'IceTracingManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 9-3. 保温箱管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (123, 12, 2, '1', '/0/12/123/', '保温箱管理', 'C', 'icon-baowenxiangguanli', 'views/incubator/IncubatorManagement', '/api/cooler-box', 'IncubatorManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 9-4. 保温箱追溯
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (124, 12, 3, '1', '/0/12/124/', '保温箱追溯', 'C', 'icon-trace', 'views/incubator/IncubatorTraceability', '/api/cooler-box', 'IncubatorTraceability', '', 0, 1, 1, NOW(), NOW());
+
+-- 9-5. 冷冻柜管理
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (125, 12, 4, '1', '/0/12/125/', '冷冻柜管理', 'C', 'icon-binggui', 'views/IceCreamFreezer/FreezerManagement', '/api/ice-locker', 'FreezerManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 10. 订单管理(目录)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (13, 0, 9, '1', '/0/13/', '订单管理', 'M', 'icon-yundan', '', '', 'OrderManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-1. 未确定订单
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (131, 13, 0, '1', '/0/13/131/', '未确定订单', 'C', 'icon-yueduye-mulu', 'views/OrderManagement/uncertainOrder', '/api/waybill', 'uncertainOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-2. 待审核订单
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (132, 13, 1, '1', '/0/13/132/', '待审核订单', 'C', 'icon-daishenhe', 'views/OrderManagement/reviewedOrder', '/api/waybill', 'reviewedOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-3. 待取药装箱
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (133, 13, 2, '1', '/0/13/133/', '待取药装箱', 'C', 'icon-daipeisong', 'views/OrderManagement/packingOrder', '/api/waybill', 'packingOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-4. 待配送订单(原133→138)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (134, 13, 3, '1', '/0/13/138/', '待配送订单', 'C', 'icon-daipeisong', 'views/OrderManagement/pendingOrder', '/api/waybill', 'pendingOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-5. 配送中订单(原134→139)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (135, 13, 4, '1', '/0/13/139/', '配送中订单', 'C', 'icon-peisongzhong', 'views/OrderManagement/deliveryOrder', '/api/waybill', 'deliveryOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-6. 已完成订单(原135→140)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (136, 13, 5, '1', '/0/13/140/', '已完成订单', 'C', 'icon-yiwanchengdingdan', 'views/OrderManagement/completedOrder', '/api/waybill', 'completedOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-7. 已取消订单(原136→141)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (137, 13, 6, '1', '/0/13/141/', '已取消订单', 'C', 'icon-yiquxiaodingdan', 'views/OrderManagement/cancelledOrder', '/api/waybill', 'cancelledOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 10-8. 已拒收订单(原137→142)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (138, 13, 7, '1', '/0/13/142/', '已拒收订单', 'C', 'icon-yiquxiaodingdan', 'views/OrderManagement/rejectionOrder', '/api/waybill', 'rejectionOrder', '', 0, 1, 1, NOW(), NOW());
+
+-- 11. 订单审核
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (14, 0, 10, '1', '/0/14/', '订单审核', 'C', 'icon-dingdanshenhe', 'views/OrderManagement/OrderReview', '/api/waybill', 'OrderReview', '', 0, 1, 1, NOW(), NOW());
+
+-- 12. 日志管理(目录)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (15, 0, 11, '1', '/0/15/', '日志管理', 'M', 'icon-rizhi', '', '', 'LogManagement', '', 0, 1, 1, NOW(), NOW());
+
+-- 12-1. 登录日志
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (151, 15, 0, '1', '/0/15/151/', '登录日志', 'C', 'icon-denglu', 'views/system/', '/api/sys-login-log', 'SysLoginLog', '', 0, 1, 1, NOW(), NOW());
+
+-- 12-2. 操作日志
+INSERT INTO `sys_menu` (`id`, `parent_id`, `sort`, `visible`, `menu_path`, `title`, `menu_type`, `icon`, `component`, `path`, `name`, `permission`, `is_dept`, `create_by`, `update_by`, `created_at`, `updated_at`) VALUES
+    (152, 15, 1, '1', '/0/15/152/', '操作日志', 'C', 'icon-caozuo', 'views/system/', '/api/sys-opera-log', 'SysOperaLog', '', 0, 1, 1, NOW(), NOW());

+ 73 - 3
db/migration.go

@@ -5,6 +5,12 @@ import (
 
 	"cold-delivery/app/admin/model"
 	jobModel "cold-delivery/app/jobs/model"
+	"fmt"
+	"log"
+	"os"
+	"strings"
+
+	"gorm.io/gorm"
 )
 
 // 执行数据迁移
@@ -14,9 +20,9 @@ func AutoMigrateDB() {
 		AutoMigrate(
 			&jobModel.SysJob{},
 			&model.SysUser{},
-			//&model.SysRole{},
-			//&model.SysRoleMenu{},
-			//&model.SysMenu{},
+			&model.SysRole{},
+			&model.SysRoleMenu{},
+			&model.SysMenu{},
 			&model.SysDept{},
 			&model.SysLoginLog{},
 			&model.SysOperaLog{},
@@ -31,8 +37,72 @@ func AutoMigrateDB() {
 			&model.IceRaftRecord{},
 			&model.CoolerBoxRecord{},
 			&model.Drug{},
+			&model.SysPost{},
+			&model.SysPostMenu{},
 		)
 	if err != nil {
 		zap.L().Panic("migrate db fail", zap.Error(err))
 	}
 }
+
+// AutoSeedMenus 检查并初始化菜单数据
+func AutoSeedMenus() {
+	var count int64
+	DB.Table("sys_menu").Count(&count)
+	if count > 0 {
+		return
+	}
+	log.Println("sys_menu 表为空,开始初始化菜单数据...")
+	err := ExecSqlFile(DB, "conf/menu_seed.sql")
+	if err != nil {
+		log.Printf("菜单数据初始化失败: %s", err.Error())
+	} else {
+		log.Println("菜单数据初始化完成")
+	}
+}
+
+// ExecSqlFile 执行SQL文件
+func ExecSqlFile(db *gorm.DB, filePath string) error {
+	sql, err := ioUtil(filePath)
+	if err != nil {
+		return fmt.Errorf("读取SQL文件失败: %s", err.Error())
+	}
+
+	tx := db.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	sqlList := strings.Split(sql, ";")
+	for i := 0; i < len(sqlList)-1; i++ {
+		s := strings.TrimSpace(sqlList[i])
+		if s == "" || strings.HasPrefix(s, "--") {
+			continue
+		}
+		s = sqlList[i] + ";"
+		s = strings.Replace(s, "\n", "", -1)
+		s = strings.TrimSpace(s)
+		if s == ";" {
+			continue
+		}
+		if err = tx.Exec(s).Error; err != nil {
+			if !strings.Contains(err.Error(), "Query was empty") {
+				log.Printf("error sql: %s", s)
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func ioUtil(filePath string) (string, error) {
+	contents, err := os.ReadFile(filePath)
+	if err != nil {
+		return "", err
+	}
+	return strings.Replace(string(contents), "\n", "", 1), nil
+}