Browse Source

2023-12-14 初版

zoie 1 year ago
parent
commit
d6f9aae941
64 changed files with 9418 additions and 82 deletions
  1. 2 1
      .gitignore
  2. 6 0
      README.md
  3. 1 1
      common/model/model.go
  4. 5 2
      common/response/model.go
  5. 4 4
      common/response/return.go
  6. 89 0
      common/translate/translate.go
  7. 13 0
      common/translate/translate_test.go
  8. 3 3
      conf/app.conf
  9. 1 0
      controllers/base_controller.go
  10. 1 0
      controllers/enterprise.go
  11. 59 0
      controllers/medicine.go
  12. 214 0
      controllers/medicine_template.go
  13. 596 0
      controllers/sales.go
  14. 61 0
      controllers/stock.go
  15. 2377 0
      controllers/stock_template.go
  16. 1 1
      controllers/unit.go
  17. 594 7
      docs/docs.go
  18. 594 7
      docs/swagger.json
  19. 389 5
      docs/swagger.yaml
  20. 1 1
      dto/dosage_form.go
  21. 2 2
      dto/enterprise.go
  22. 8 0
      dto/medicine.go
  23. 102 0
      dto/medicine_template.go
  24. 35 0
      dto/sales.go
  25. 3 3
      dto/spec.go
  26. 87 0
      dto/stock.go
  27. 216 0
      dto/stock_template.go
  28. 1 1
      dto/unit.go
  29. 20 8
      go.mod
  30. 758 19
      go.sum
  31. 4 1
      middleware/auth_middle.go
  32. 1 0
      middleware/cors_middle.go
  33. 1 0
      models/enterprise.go
  34. 127 0
      models/init.go
  35. 19 0
      models/medicine_Info.go
  36. 27 0
      models/medicine_inventory.go
  37. 137 0
      models/medicine_template.go
  38. 1 1
      models/operation_log.go
  39. 21 0
      models/stock_in.go
  40. 19 0
      models/stock_out.go
  41. 1 1
      models/unit.go
  42. 16 0
      routers/medicine.go
  43. 22 0
      routers/medicine_template.go
  44. 20 0
      routers/sales.go
  45. 36 0
      routers/stock.go
  46. 22 2
      services/dosage_form.go
  47. 22 2
      services/enterprise.go
  48. 81 0
      services/medicine.go
  49. 314 0
      services/medicine_template.go
  50. 21 3
      services/product.go
  51. 230 0
      services/sales.go
  52. 22 4
      services/spec.go
  53. 273 0
      services/stock.go
  54. 1487 0
      services/stock_template.go
  55. 21 3
      services/unit.go
  56. BIN
      static/fonts/MiSans-Medium.ttf
  57. BIN
      static/fonts/arialuni.ttf
  58. BIN
      static/fonts/iconfont.eot
  59. 44 0
      static/fonts/iconfont.svg
  60. BIN
      static/fonts/iconfont.ttf
  61. BIN
      static/fonts/iconfont.woff
  62. BIN
      static/fonts/三极行楷简体-粗.ttf
  63. 173 0
      utils/comm.go
  64. 13 0
      utils/comm_test.go

+ 2 - 1
.gitignore

@@ -24,5 +24,6 @@ _testmain.go
 *.test
 *.prof
 
-./bin
+bin
 ./Medical_ERP
+./ofile

+ 6 - 0
README.md

@@ -11,3 +11,9 @@
 swag init --parseDependency --parseDepth=6
 
 
+### 安装 pdflatex
+Ubuntu 或 Debian 等基于 Debian 的 Linux 发行版,可以通过以下命令安装: pdflatex:sudo apt install texlive-latex-base
+Red Hat 或 Centos 等基于 RPM 的 Linux 发行版,可以通过以下命令安装: pdflatex.sudo yum install texlive-latex
+macos,可以使用 Homebrew 安装 pdflatex: brew cask install basictex
+
+

+ 1 - 1
common/model/model.go

@@ -8,7 +8,7 @@ import (
 type ControlBy struct {
 	CreateBy int `json:"createBy" gorm:"index;comment:创建者"` // 创建者
 	UpdateBy int `json:"updateBy" gorm:"index;comment:更新者"` // 更新者
-	DeptId   int `json:"deptId" gorm:"index;comment:更新者"`   // 更新者
+	DeptId   int `json:"deptId" gorm:"index;comment:部门id"`  // 更新者
 }
 
 // SetCreateBy 设置创建人id

+ 5 - 2
common/response/model.go

@@ -18,11 +18,14 @@ type page struct {
 	Page     int `json:"page"`     //页码
 	PageSize int `json:"pageSize"` //页条数
 }
+type Data struct {
+	page
+	List interface{} `json:"list"`
+}
 
 type Page struct {
 	Msg
-	page
-	List interface{} `json:"list"`
+	Data Data `json:"data"`
 }
 
 func (e *Msg) SetTraceID(id string) {

+ 4 - 4
common/response/return.go

@@ -42,9 +42,9 @@ func PageOK(result interface{}, count int, pageIndex int, pageSize int, msg stri
 	}
 	res.SetTraceID(uuid.New().String())
 	res.SetCode(http.StatusOK)
-	res.List = result
-	res.Count = count
-	res.Page = pageIndex
-	res.PageSize = pageSize
+	res.Data.List = result
+	res.Data.Count = count
+	res.Data.Page = pageIndex
+	res.Data.PageSize = pageSize
 	return res
 }

+ 89 - 0
common/translate/translate.go

@@ -0,0 +1,89 @@
+package translate
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/volcengine/volc-sdk-golang/base"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+)
+
+const (
+	kAccessKey      = "AKLTNGYwNTk3NGQzMDdmNDk5Njk0YjE5ZTBlMmZjYjgxNzM" // https://console.volcengine.com/iam/keymanage/
+	kSecretKey      = "Wm1Wa01UbGpOR0U0TW1FNU5ERXpaRGhqT1RRM1pEQm1PREZrTldNeU0yVQ=="
+	kServiceVersion = "2020-06-01"
+)
+
+var (
+	ServiceInfo = &base.ServiceInfo{
+		Timeout: 5 * time.Second,
+		Host:    "translate.volcengineapi.com",
+		Header: http.Header{
+			"Accept": []string{"application/json"},
+		},
+		Credentials: base.Credentials{Region: base.RegionCnNorth1, Service: "translate"},
+	}
+	ApiInfoList = map[string]*base.ApiInfo{
+		"TranslateText": {
+			Method: http.MethodPost,
+			Path:   "/",
+			Query: url.Values{
+				"Action":  []string{"TranslateText"},
+				"Version": []string{kServiceVersion},
+			},
+		},
+	}
+)
+
+type Req struct {
+	SourceLanguage string   `json:"SourceLanguage"`
+	TargetLanguage string   `json:"TargetLanguage"`
+	TextList       []string `json:"TextList"`
+}
+
+type Rsp struct {
+	TranslationList []struct {
+		Translation            string `json:"Translation"`
+		DetectedSourceLanguage string `json:"DetectedSourceLanguage"`
+	} `json:"TranslationList"`
+	ResponseMetadata struct {
+		RequestId string `json:"RequestId"`
+		Action    string `json:"Action"`
+		Version   string `json:"Version"`
+		Service   string `json:"Service"`
+		Region    string `json:"Region"`
+		Error     struct {
+			Code    string `json:"Code"`
+			Message string `json:"Message"`
+		} `json:"Error"`
+	} `json:"ResponseMetadata"`
+}
+
+func Translate(zhStr string) (enStr string, err error) {
+	client := base.NewClient(ServiceInfo, ApiInfoList)
+	client.SetAccessKey(kAccessKey)
+	client.SetSecretKey(kSecretKey)
+
+	req := Req{
+		SourceLanguage: "zh",
+		TargetLanguage: "en",
+		TextList:       []string{zhStr},
+	}
+	body, _ := json.Marshal(req)
+
+	resp, code, err := client.Json("TranslateText", nil, string(body))
+	if err != nil {
+		return enStr, err
+	}
+	fmt.Printf("%d %s\n", code, string(resp))
+
+	var rsp Rsp
+	err = json.Unmarshal(resp, &rsp)
+	if err != nil {
+		return enStr, err
+	}
+
+	return strings.ToLower(rsp.TranslationList[0].Translation), nil
+}

+ 13 - 0
common/translate/translate_test.go

@@ -0,0 +1,13 @@
+package translate
+
+import (
+	"github.com/beego/beego/v2/core/logs"
+	"testing"
+)
+
+// TestGet is a sample to run an endpoint test
+func TestTranslate(t *testing.T) {
+	en, _ := Translate("111")
+	logs.Info(en)
+
+}

+ 3 - 3
conf/app.conf

@@ -33,13 +33,13 @@ Redis_password = ""
 Redis_dbNum = "4"
 
 #不验证登录URL
-FilterExcludeURL =
+FilterExcludeURL = ""
 #只验证登录URL
 FilterOnlyLoginCheckURL = "/api/role-api"
 
 # 统一身份认证
-# OAuth_baseUrl = "http://192.168.11.77:8100"
-OAuth_baseUrl = "http://127.0.0.1:8000"
+OAuth_baseUrl = "http://192.168.11.77:8100"
+# OAuth_baseUrl = "http://127.0.0.1:8000"
 # 服务发现服务信息
 Service_number = "q9YeglML"
 Service_authCode = "3EDJRaagFTVVo2Ilo80nHJfLVN1O1234"

+ 1 - 0
controllers/base_controller.go

@@ -36,6 +36,7 @@ func (c *BaseController) PageOK(result interface{}, count int, pageIndex int, pa
 }
 
 func (c *BaseController) ParseAndValidate(requestBody *[]byte, reqData interface{}) error {
+
 	valueType := reflect.ValueOf(reqData).Elem().Type() // 获取结构体类型
 	newStruct := reflect.New(valueType).Interface()     // 创建新的结构体
 	if err := json.Unmarshal(*requestBody, newStruct); err != nil {

+ 1 - 0
controllers/enterprise.go

@@ -89,6 +89,7 @@ func (c EnterpriseController) Add() {
 		return
 	}
 	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
 	err := s.Insert(&reqData)
 	if err != nil {
 		c.Error(500, err, err.Error())

+ 59 - 0
controllers/medicine.go

@@ -0,0 +1,59 @@
+package controllers
+
+import (
+	"Medical_ERP/common/global"
+	_ "Medical_ERP/common/response"
+	"Medical_ERP/dto"
+	"Medical_ERP/services"
+	"gogs.baozhida.cn/zoie/OAuth-core/pkg/jwtauth/beegouser"
+)
+
+type MedicineController struct {
+	BaseController
+}
+
+// BatchNumber 批号列表
+// @Summary 批号列表
+// @Description 批号列表
+// @Tags 药品
+// @Param data body dto.MedicineDeleteReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /medicine/batch-number [post]
+// @Security Bearer
+func (c MedicineController) BatchNumber() {
+	s := services.Medicine{}
+	reqData := dto.MedicineBatchNumberReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	list := make([]string, 0)
+	err := s.GetBatchNumber(&reqData, &list, beegouser.GetDeptId(c.Ctx))
+	if err != nil {
+		c.Error(global.BadRequest, err, err.Error())
+		return
+	}
+
+	c.OK(list, "查询成功")
+}
+
+// BasicDataStat 基本数据统计
+// @Summary 基本数据统计
+// @Description 基本数据统计
+// @Tags 药品
+// @Param data body dto.MedicineDeleteReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /medicine/basic-data-stat [post]
+// @Security Bearer
+func (c MedicineController) BasicDataStat() {
+	s := services.Medicine{}
+
+	list, err := s.BasicDataStat(beegouser.GetDeptId(c.Ctx))
+	if err != nil {
+		c.Error(global.BadRequest, err, err.Error())
+		return
+	}
+
+	c.OK(list, "查询成功")
+}

+ 214 - 0
controllers/medicine_template.go

@@ -0,0 +1,214 @@
+package controllers
+
+import (
+	"Medical_ERP/common/actions"
+	"Medical_ERP/common/global"
+	_ "Medical_ERP/common/response"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"Medical_ERP/services"
+	"gogs.baozhida.cn/zoie/OAuth-core/pkg/jwtauth/beegouser"
+)
+
+type MedicineTemplateController struct {
+	BaseController
+}
+
+// Initialize 初始化
+// @Summary 初始化
+// @Description 创建系统初始化的药品字段和药品表
+// @Tags 药品信息模板
+// @Param body body dto.MedicineTemplateInitializeReq true "body"
+// @Success 200 {object} response.Page{list=[]models.Product} "{"code": 200, "data": [...]}"
+// @Router /medicine-template/list [post]
+// @Security Bearer
+func (c MedicineTemplateController) Initialize() {
+	s := services.MedicineTemplate{}
+	reqData := dto.MedicineTemplateInitializeReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+	userId := beegouser.GetUserId(c.Ctx)
+	err := s.InitMedicineTemplate(reqData.DeptId, userId)
+	if err != nil {
+		c.Error(global.BadRequest, err, "初始化失败")
+		return
+	}
+
+	c.OK(nil, "初始化成功")
+}
+
+// List 获取品名列表
+// @Summary 获取品名列表
+// @Description 获取品名列表
+// @Tags 药品信息模板
+// @Success 200 {object} response.Page{list=[]models.MedicineTemplate} "{"code": 200, "data": [...]}"
+// @Router /medicine-template/list [post]
+// @Security Bearer
+func (c MedicineTemplateController) List() {
+	s := services.MedicineTemplate{}
+
+	list := make([]models.MedicineTemplate, 0)
+	var count int64
+	//数据权限检查
+	p := actions.GetPermissionFromContext(c.Ctx)
+	err := s.GetPage(&list, &count, p)
+	if err != nil {
+		c.Error(global.BadRequest, err, err.Error())
+		return
+	}
+
+	c.PageOK(list, int(count), 0, 0, "查询成功")
+}
+
+// Add 添加模版字段
+// @Summary 添加模版字段
+// @Description 添加模版字段
+// @Tags 药品信息模板
+// @Param data body dto.MedicineTemplateInsertReq true "data"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /medicine-template/add [post]
+// @Security Bearer
+func (c MedicineTemplateController) Add() {
+	s := services.MedicineTemplate{}
+	reqData := dto.MedicineTemplateInsertReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	//if !utils.IsChineseOnly(reqData.Name) {
+	//	err := errors.New("标签名称不能包含非中文字符")
+	//	c.Error(500, err, err.Error())
+	//	return
+	//}
+	err := s.Insert(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "创建成功")
+}
+
+// Edit 修改模版字段
+// @Summary 修改模版字段
+// @Description 修改模版字段
+// @Tags 药品信息模板
+// @Param data body dto.MedicineTemplateUpdateReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /medicine-template/edit [post]
+// @Security Bearer
+func (c MedicineTemplateController) Edit() {
+	s := services.MedicineTemplate{}
+	reqData := dto.MedicineTemplateUpdateReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+	reqData.SetUpdateBy(beegouser.GetUserId(c.Ctx))
+
+	err := s.Update(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "更新成功")
+}
+
+// Delete 删除模版字段
+// @Summary 删除模版字段
+// @Description 删除模版字段
+// @Tags 药品信息模板
+// @Accept  application/json
+// @Unit application/json
+// @Param data body dto.MedicineTemplateDeleteReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /medicine-template/delete [post]
+// @Security Bearer
+func (c MedicineTemplateController) Delete() {
+	s := services.MedicineTemplate{}
+	reqData := dto.MedicineTemplateDeleteReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+	reqData.SetUpdateBy(beegouser.GetUserId(c.Ctx))
+
+	err := s.Remove(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+
+	c.OK(reqData.GetId(), "删除成功")
+}
+
+// Columns 获取药品列名
+// @Summary 获取品名列表
+// @Description 获取品名列表
+// @Tags 药品信息模板
+// @Success 200 {object} response.Page{list=[]models.MedicineTemplate} "{"code": 200, "data": [...]}"
+// @Router /medicine-template/columns [post]
+// @Security Bearer
+func (c MedicineTemplateController) Columns() {
+	s := services.MedicineTemplate{}
+
+	list := make([]dto.MedicineColumnsRes, 0)
+	//数据权限检查
+	p := actions.GetPermissionFromContext(c.Ctx)
+	err := s.GetColumns(&list, p)
+	if err != nil {
+		c.Error(global.BadRequest, err, err.Error())
+		return
+	}
+
+	c.PageOK(list, 0, 0, 0, "查询成功")
+}
+
+// ListForStockIn 获取入库列表
+// @Summary 获取入库列表
+// @Description 获取入库列表
+// @Tags 药品信息模板
+// @Success 200 {object} response.Page{list=[]models.MedicineTemplate} "{"code": 200, "data": [...]}"
+// @Router /medicine-template/list-for-stock [post]
+// @Security Bearer
+func (c MedicineTemplateController) ListForStock() {
+	s := services.MedicineTemplate{}
+
+	list := make([]dto.MedicineTemplateForStockRes, 0)
+	var count int64
+	//数据权限检查
+	p := actions.GetPermissionFromContext(c.Ctx)
+	err := s.ListForStock(&list, &count, p)
+	if err != nil {
+		c.Error(global.BadRequest, err, err.Error())
+		return
+	}
+
+	c.PageOK(list, int(count), 0, 0, "查询成功")
+}
+
+// ListForStockOut 获取入库列表
+// @Summary 获取入库列表
+// @Description 获取入库列表
+// @Tags 药品信息模板
+// @Success 200 {object} response.Page{list=[]models.MedicineTemplate} "{"code": 200, "data": [...]}"
+// @Router /medicine-template/list-for-stock [post]
+// @Security Bearer
+func (c MedicineTemplateController) ListForStockOut() {
+	s := services.MedicineTemplate{}
+
+	list := make([]dto.MedicineTemplateForStockRes, 0)
+	var count int64
+	//数据权限检查
+	p := actions.GetPermissionFromContext(c.Ctx)
+	err := s.ListForStockOut(&list, &count, p)
+	if err != nil {
+		c.Error(global.BadRequest, err, err.Error())
+		return
+	}
+
+	c.PageOK(list, int(count), 0, 0, "查询成功")
+}

+ 596 - 0
controllers/sales.go

@@ -0,0 +1,596 @@
+package controllers
+
+import (
+	"Medical_ERP/common/global"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"Medical_ERP/services"
+	"Medical_ERP/utils"
+	"fmt"
+	"github.com/jung-kurt/gofpdf"
+	"github.com/xuri/excelize/v2"
+	"gogs.baozhida.cn/zoie/OAuth-core/pkg/jwtauth/beegouser"
+	"os"
+	"time"
+)
+
+type SalesController struct {
+	BaseController
+}
+
+// SalesList 销售管理
+// @Summary 销售管理
+// @Description 销售管理
+// @Tags 销售管理
+// @Param body body dto.SalesPageReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /sales/list [post]
+// @Security Bearer
+func (c SalesController) SalesList() {
+	s := services.Sales{}
+	reqData := dto.SalesPageReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, count, err := s.SalesList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(count), reqData.GetPageIndex(), reqData.GetPageSize(), "查询成功")
+}
+
+// SalesExcel 二类销售清单
+// @Summary 二类销售清单
+// @Description 二类销售清单
+// @Tags 销售管理
+// @Param body body dto.SalesPageReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /sales/excel [post]
+// @Security Bearer
+func (c SalesController) SalesExcel() {
+	s := services.Sales{}
+	reqData := dto.SalesPageReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	deptName := beegouser.GetDeptName(c.Ctx)
+	list, _, err := s.SalesList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	f := excelize.NewFile() // 设置单元格的值
+	StyleTitle, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Bold: true, Size: 20, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+		})
+	f.MergeCell("Sheet1", "A1", "N1")
+	f.SetRowStyle("Sheet1", 1, 1, StyleTitle)
+	f.SetRowHeight("Sheet1", 1, 50)
+	var interval string
+	if len(reqData.StartDate) > 0 && len(reqData.EndDate) > 0 {
+		interval = fmt.Sprintf("%s至%s", reqData.StartDate, reqData.EndDate)
+	}
+	f.SetCellValue("Sheet1", "A1", fmt.Sprintf("%s%s销售清单", deptName, interval))
+	// 这里设置表头
+	f.SetCellValue("Sheet1", "A2", "日期")
+	f.SetCellValue("Sheet1", "B2", "购货单位")
+	f.SetCellValue("Sheet1", "C2", "品种")
+	f.SetCellValue("Sheet1", "D2", "生产企业")
+	f.SetCellValue("Sheet1", "E2", "规格")
+	f.SetCellValue("Sheet1", "F2", "剂型")
+	f.SetCellValue("Sheet1", "G2", "批号")
+	f.SetCellValue("Sheet1", "H2", "数量")
+	f.SetCellValue("Sheet1", "I2", "单位")
+	f.SetCellValue("Sheet1", "J2", "购进单价")
+	f.SetCellValue("Sheet1", "K2", "购进金额")
+	f.SetCellValue("Sheet1", "L2", "销售单价")
+	f.SetCellValue("Sheet1", "M2", "销售金额")
+	f.SetCellValue("Sheet1", "N2", "批准文号")
+	// 设置列宽
+	f.SetColWidth("Sheet1", "A", "A", 12)
+	f.SetColWidth("Sheet1", "B", "C", 15)
+	f.SetColWidth("Sheet1", "C", "E", 15)
+	f.SetColWidth("Sheet1", "E", "F", 15)
+	f.SetColWidth("Sheet1", "F", "G", 12)
+	f.SetColWidth("Sheet1", "H", "I", 10)
+	f.SetColWidth("Sheet1", "J", "N", 12)
+
+	line := 2
+
+	// 循环写入数据
+	for _, v := range list {
+		line++
+
+		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), v["date"])
+		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v["receiving_unit"])
+		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v[models.FieldProductName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), v[models.FieldEnterpriseName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", line), v[models.FieldSpecName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("F%d", line), v[models.FieldDosageFormName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("G%d", line), v[models.FieldBatchNumber])
+		f.SetCellValue("Sheet1", fmt.Sprintf("H%d", line), v["quantity"])
+		f.SetCellValue("Sheet1", fmt.Sprintf("I%d", line), v[models.FieldUnitName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("J%d", line), utils.ToFloat64(v["purchase_unit_price"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("K%d", line), utils.ToFloat64(v["purchase_money"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("L%d", line), utils.ToFloat64(v["sales_unit_price"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("M%d", line), utils.ToFloat64(v["sales_money"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("N%d", line), v[models.FieldApprovalNumber])
+
+	}
+	Style1, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 12, Family: "宋体", Bold: true},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A2", "N2", Style1)
+	Style2, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 12, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A3", fmt.Sprintf("N%d", line), Style2)
+
+	filename := "二类销售清单" + time.Now().Format("20060102150405") + ".xlsx"
+	// 保存文件
+	if err := f.SaveAs("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+}
+
+// SalesReportExcel 销售报表
+// @Summary 销售报表
+// @Description 销售报表
+// @Tags 销售管理
+// @Param body body dto.SalesPageReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /sales/excel [post]
+// @Security Bearer
+func (c SalesController) SalesReportExcel() {
+	s := services.Sales{}
+	reqData := dto.SalesPageReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	deptName := beegouser.GetDeptName(c.Ctx)
+	list, err := s.SalesReportList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	f := excelize.NewFile() // 设置单元格的值
+	StyleTitle, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Bold: true, Size: 20, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+		})
+	f.MergeCell("Sheet1", "A1", "J1")
+	f.SetRowStyle("Sheet1", 1, 1, StyleTitle)
+	f.SetRowHeight("Sheet1", 1, 50)
+	var interval string
+	if len(reqData.StartDate) > 0 && len(reqData.EndDate) > 0 {
+		interval = fmt.Sprintf("%s至%s", reqData.StartDate, reqData.EndDate)
+	}
+	f.SetCellValue("Sheet1", "A1", fmt.Sprintf("%s%s销售报表", deptName, interval))
+	// 这里设置表头
+	f.SetRowHeight("Sheet1", 2, 25)
+
+	f.SetCellValue("Sheet1", "A2", "品种")
+	f.SetCellValue("Sheet1", "B2", "生产企业")
+	f.SetCellValue("Sheet1", "C2", "规格")
+	f.SetCellValue("Sheet1", "D2", "数量")
+	f.SetCellValue("Sheet1", "E2", "单位")
+	f.SetCellValue("Sheet1", "F2", "购进单价")
+	f.SetCellValue("Sheet1", "G2", "购进金额")
+	f.SetCellValue("Sheet1", "H2", "销售单价")
+	f.SetCellValue("Sheet1", "I2", "销售金额")
+	f.SetCellValue("Sheet1", "J2", "利润金额")
+	// 设置列宽
+	f.SetColWidth("Sheet1", "A", "C", 15)
+	f.SetColWidth("Sheet1", "D", "E", 10)
+	f.SetColWidth("Sheet1", "F", "J", 12)
+
+	line := 2
+
+	// 循环写入数据
+	for _, v := range list {
+		line++
+		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), v[models.FieldProductName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v[models.FieldEnterpriseName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v[models.FieldSpecName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), utils.ToInt(v["total"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", line), v[models.FieldUnitName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("F%d", line), utils.ToFloat64(v["purchase_unit_price"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("G%d", line), utils.ToFloat64(v["purchase_money"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("H%d", line), utils.ToFloat64(v["sales_unit_price"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("I%d", line), utils.ToFloat64(v["sales_money"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("J%d", line), utils.ToFloat64(v["profit_money"]))
+
+	}
+	line += 1
+	f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), "合计")
+	f.SetCellFormula("Sheet1", fmt.Sprintf("D%d", line), fmt.Sprintf("SUM(D3:D%d)", line-1))
+	f.SetCellFormula("Sheet1", fmt.Sprintf("G%d", line), fmt.Sprintf("SUM(G3:G%d)", line-1))
+	f.SetCellFormula("Sheet1", fmt.Sprintf("I%d", line), fmt.Sprintf("SUM(I3:I%d)", line-1))
+	f.SetCellFormula("Sheet1", fmt.Sprintf("J%d", line), fmt.Sprintf("SUM(J3:J%d)", line-1))
+	Style1, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 14, Family: "宋体", Bold: true},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A2", "J2", Style1)
+	Style2, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 12, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A3", fmt.Sprintf("J%d", line), Style2)
+
+	filename := "销售报表" + time.Now().Format("20060102150405") + ".xlsx"
+	// 保存文件
+	if err := f.SaveAs("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+}
+
+// SalesOrderList 销售订单
+// @Summary 销售订单
+// @Description 销售订单
+// @Tags 销售管理
+// @Param body body dto.SalesOrderPageReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /sales/order_list [post]
+// @Security Bearer
+func (c SalesController) SalesOrderList() {
+	s := services.Sales{}
+	reqData := dto.SalesOrderPageReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, count, err := s.SalesOrderList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(count), reqData.GetPageIndex(), reqData.GetPageSize(), "查询成功")
+}
+
+// SalesStockOutExcel 出库单
+// @Summary 出库单
+// @Description 出库单
+// @Tags 销售管理
+// @Param body body dto.SalesStockOutExcelReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /sales/order_list [post]
+// @Security Bearer
+func (c SalesController) SalesStockOutExcel(reqData dto.SalesStockOutExcelReq) {
+	s := services.Sales{}
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, err := s.SalesStockOutExcel(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	f := excelize.NewFile() // 设置单元格的值
+	StyleTitle, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Bold: true, Size: 20, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+		})
+	f.MergeCell("Sheet1", "A1", "K1")
+	f.SetRowStyle("Sheet1", 1, 1, StyleTitle)
+	f.SetRowHeight("Sheet1", 1, 40)
+
+	f.SetCellValue("Sheet1", "A1", fmt.Sprintf("%s二类出库单", beegouser.GetDeptName(c.Ctx)))
+	// 这里设置表头
+	f.SetRowHeight("Sheet1", 2, 25)
+	f.MergeCell("Sheet1", "A2", "C2")
+	f.MergeCell("Sheet1", "D2", "F2")
+	f.MergeCell("Sheet1", "G2", "G2")
+	f.MergeCell("Sheet1", "H2", "K2")
+	f.SetRowHeight("Sheet1", 3, 40)
+	f.SetCellValue("Sheet1", "A2", "购货单位:"+reqData.ReceivingUnit)
+	f.SetCellValue("Sheet1", "D2", "下单日期:"+reqData.Date)
+	//f.SetCellValue("Sheet1", "G2", "编号:")
+	//f.SetCellValue("Sheet1", "I2", "第1联,共3联(第1联,库房处留存)")
+
+	f.SetCellValue("Sheet1", "A3", "品种")
+	f.SetCellValue("Sheet1", "B3", "剂型")
+	f.SetCellValue("Sheet1", "C3", "规格")
+	f.SetCellValue("Sheet1", "D3", "生产企业")
+	f.SetCellValue("Sheet1", "E3", "疫苗批号")
+	f.SetCellValue("Sheet1", "F3", "效期")
+	f.SetCellValue("Sheet1", "G3", "批准文号")
+	f.SetCellValue("Sheet1", "H3", "数量")
+	f.SetCellValue("Sheet1", "I3", "单位")
+	f.SetCellValue("Sheet1", "J3", "销售单价")
+	f.SetCellValue("Sheet1", "K3", "销售金额")
+	// 设置列宽
+	f.SetColWidth("Sheet1", "A", "B", 15)
+	f.SetColWidth("Sheet1", "B", "B", 6)
+	f.SetColWidth("Sheet1", "C", "C", 15)
+	f.SetColWidth("Sheet1", "D", "D", 10)
+	f.SetColWidth("Sheet1", "E", "G", 12)
+	f.SetColWidth("Sheet1", "H", "I", 6)
+	f.SetColWidth("Sheet1", "J", "K", 10)
+
+	line := 3
+	var money float64
+	// 循环写入数据
+	for _, v := range list {
+		line++
+		f.SetRowHeight("Sheet1", line, 25)
+		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), v[models.FieldProductName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v[models.FieldDosageFormName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v[models.FieldSpecName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), v[models.FieldEnterpriseName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", line), v[models.FieldBatchNumber])
+		f.SetCellValue("Sheet1", fmt.Sprintf("F%d", line), v[models.FieldExpiryDate])
+		f.SetCellValue("Sheet1", fmt.Sprintf("G%d", line), v[models.FieldApprovalNumber])
+		f.SetCellValue("Sheet1", fmt.Sprintf("H%d", line), utils.ToInt(v["quantity"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("I%d", line), v[models.FieldUnitName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("J%d", line), utils.ToFloat64(v["sales_unit_price"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("K%d", line), utils.ToFloat64(v["sales_money"]))
+		money += utils.ToFloat64(v["sales_money"])
+	}
+	line += 1
+	f.SetRowHeight("Sheet1", line, 25)
+
+	line += 1
+	f.SetRowHeight("Sheet1", line, 25)
+	f.MergeCell("Sheet1", fmt.Sprintf("A%d", line), fmt.Sprintf("D%d", line))
+	f.MergeCell("Sheet1", fmt.Sprintf("E%d", line), fmt.Sprintf("K%d", line))
+	f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), fmt.Sprintf("整单合计金额(小写):¥%.2f元", money))
+	f.SetCellValue("Sheet1", fmt.Sprintf("E%d", line), fmt.Sprintf("整单合计金额(大写):%s", utils.AmountConvert(money, true)))
+
+	line += 1
+	f.SetRowHeight("Sheet1", line, 25)
+	f.MergeCell("Sheet1", fmt.Sprintf("A%d", line), fmt.Sprintf("C%d", line))
+	f.MergeCell("Sheet1", fmt.Sprintf("D%d", line), fmt.Sprintf("E%d", line))
+	f.MergeCell("Sheet1", fmt.Sprintf("F%d", line), fmt.Sprintf("I%d", line))
+	f.MergeCell("Sheet1", fmt.Sprintf("J%d", line), fmt.Sprintf("K%d", line))
+	f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), fmt.Sprintf("开票日期:%s", time.Now().Format("2006年01月02日")))
+	f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), fmt.Sprintf("开票员:%s", reqData.Drawer))
+	f.SetCellValue("Sheet1", fmt.Sprintf("F%d", line), fmt.Sprintf("发货人:%s            财务:%s", reqData.Consigner, reqData.Finance))
+	f.SetCellValue("Sheet1", fmt.Sprintf("J%d", line), fmt.Sprintf("收货人:%s", reqData.Consignee))
+
+	Style0, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 10, Family: "宋体", Bold: true},
+			Alignment: &excelize.Alignment{Horizontal: "left", Vertical: "center", WrapText: true},
+		})
+	f.SetCellStyle("Sheet1", "A2", "K2", Style0)
+	Style1, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 12, Family: "宋体", Bold: true},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A3", "K3", Style1)
+	Style2, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 11, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A4", fmt.Sprintf("k%d", line-1), Style2)
+
+	f.SetCellStyle("Sheet1", fmt.Sprintf("A%d", line), fmt.Sprintf("k%d", line), Style0)
+
+	filename := "二类出库单" + time.Now().Format("20060102150405") + ".xlsx"
+	// 保存文件
+	if err := f.SaveAs("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+}
+func (c SalesController) SalesStockOutPdf(reqData dto.SalesStockOutExcelReq) {
+	s := services.Sales{}
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, err := s.SalesStockOutExcel(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	cols := []float64{22, 10, 22, 18, 20, 20, 20, 10, 10, 18, 18}
+
+	header := []string{"品种", "剂型", "规格", "生产企业", "疫苗批号", "效期", "批准文号", "数量", "单位", "销售单价", "销售金额"}
+	rows := [][]string{}
+	money := 0.0
+	for _, row := range list {
+		temp := []string{}
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldProductName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldDosageFormName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldSpecName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldEnterpriseName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldBatchNumber]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldExpiryDate].(time.Time).Format("2006-01-02")))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldApprovalNumber]))
+		temp = append(temp, fmt.Sprintf("%d", utils.ToInt(row["quantity"])))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldUnitName]))
+		temp = append(temp, fmt.Sprintf("%.2f", utils.ToFloat64(row["sales_unit_price"])))
+		temp = append(temp, fmt.Sprintf("%.2f", utils.ToFloat64(row["sales_money"])))
+		rows = append(rows, temp)
+		money += utils.ToFloat64(row["sales_money"])
+	}
+	rows = append(rows, []string{"", "", "", "", "", "", "", "", "", "", ""})
+
+	pdf := gofpdf.New("P", "mm", "A4", "")
+
+	pdf.AddPage()
+	pdf.AddUTF8Font("simsun", "", "static/fonts/MiSans-Medium.ttf")
+	pdf.SetFont("simsun", "", 20)
+	pdf.SetMargins(10, 10, 10)
+	pdf.SetAutoPageBreak(false, 0)
+	pagew, pageh := pdf.GetPageSize()
+	_, _, _, mbottom := pdf.GetMargins()
+	titleStr := fmt.Sprintf("%s二类出库单", beegouser.GetDeptName(c.Ctx))
+
+	titleWd := pdf.GetStringWidth(titleStr)
+	titleX := (pagew - titleWd) / 2
+	pdf.Text(titleX, 20, titleStr)
+
+	titleX = pagew / 4
+	y := 30.0
+	curx, _ := pdf.GetXY()
+
+	pdf.SetFont("simsun", "", 8)
+	pdf.Text(curx, y, fmt.Sprintf("购货单位:%s", reqData.ReceivingUnit))
+	pdf.Text(titleX+10, y, fmt.Sprintf("下单日期:%s", reqData.Date))
+	//pdf.Text(titleX*2+20, y, fmt.Sprintf("编号:"))
+	//pdf.Text(titleX*3, y, "第1联,共3联(第1联,库房处留存)")
+
+	curx, y = pdf.GetXY()
+	pdf.SetXY(curx, 32)
+	h := 10.0
+	pdf.SetFont("simsun", "", 10)
+	for i := 0; i < len(header); i++ {
+		pdf.CellFormat(cols[i], h, header[i], "1", 0, "CM", false, 0, "")
+	}
+	pdf.Ln(-1)
+	pdf.SetFont("simsun", "", 9)
+	marginCell := 4. // margin of top/bottom of cell
+
+	for _, row := range rows {
+		curx, y = pdf.GetXY()
+		x := curx
+
+		height := 0.
+		_, lineHt := pdf.GetFontSize()
+
+		for i, txt := range row {
+			lines := pdf.SplitLines([]byte(txt), cols[i])
+			h := float64(len(lines))*lineHt + marginCell*float64(len(lines))
+			if h > height {
+				height = h
+			}
+		}
+		// add a new page if the height of the row doesn't fit on the page
+		if pdf.GetY()+height > pageh-mbottom {
+			pdf.AddPage()
+			y = pdf.GetY()
+		}
+		for i, txt := range row {
+			width := cols[i]
+			pdf.Rect(x, y, width, height, "")
+			pdf.MultiCell(width, lineHt+marginCell, txt, "", "CM", false)
+			x += width
+			pdf.SetXY(x, y)
+		}
+		pdf.SetXY(curx, y+height)
+	}
+
+	curx, y = pdf.GetXY()
+	width := cols[0] + cols[1] + cols[2] + cols[3]
+	pdf.CellFormat(width, h, fmt.Sprintf("整单合计金额(小写):¥%.2f元", money), "1", 0, "CM", false, 0, "")
+	width = cols[4] + cols[5] + cols[6] + cols[7] + cols[8] + cols[9] + cols[10]
+	pdf.CellFormat(width, h, fmt.Sprintf("整单合计金额(大写):%s", utils.AmountConvert(money, true)), "1", 0, "CM", false, 0, "")
+	pdf.SetXY(curx, y+10)
+
+	curx, y = pdf.GetXY()
+	y += 4
+	pdf.Text(curx, y, fmt.Sprintf("开票日期:%s", time.Now().Format("2006年01月02日")))
+	width = cols[0] + cols[1] + cols[2]
+	curx += width
+	pdf.Text(curx, y, fmt.Sprintf("开票员:%s", reqData.Drawer))
+	width = cols[3] + cols[4]
+	curx += width
+	pdf.Text(curx, y, fmt.Sprintf("发货人:%s       财务:%s", reqData.Consigner, reqData.Finance))
+	width = cols[5] + cols[6] + cols[7] + cols[8]
+	curx += width
+	pdf.Text(curx, y, fmt.Sprintf("收货人:%s", reqData.Consignee))
+
+	filename := "二类出库单" + time.Now().Format("20060102150405") + ".pdf"
+	// 保存文件
+	if err := pdf.OutputFileAndClose("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+
+}
+func (c SalesController) SalesStockOutExport() {
+	reqData := dto.SalesStockOutExcelReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	if reqData.Type == "excel" {
+		c.SalesStockOutExcel(reqData)
+	}
+
+	if reqData.Type == "pdf" {
+		c.SalesStockOutPdf(reqData)
+	}
+
+}

+ 61 - 0
controllers/stock.go

@@ -0,0 +1,61 @@
+package controllers
+
+import (
+	_ "Medical_ERP/common/response"
+)
+
+type StockController struct {
+	BaseController
+}
+
+// StockIn 入库
+// @Summary 入库
+// @Description 入库
+// @Tags 库存
+// @Param body body dto.StockInInsertReq true "body"
+// @Success 200 {object} response.Page{list=[]models.StockIn} "{"code": 200, "data": [...]}"
+// @Router /stock/in [post]
+// @Security Bearer
+//func (c StockController) StockIn() {
+//	s := services.Stock{}
+//	reqData := dto.StockInInsertReq{}
+//	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+//		c.Error(global.ParseFormErr, err, err.Error())
+//		return
+//	}
+//
+//	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+//	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+//	err := s.StockIn(&reqData)
+//	if err != nil {
+//		c.Error(500, err, err.Error())
+//		return
+//	}
+//	c.OK(reqData.GetId(), "入库成功")
+//}
+
+// StockInList 入库列表
+// @Summary 入库列表
+// @Description 入库列表
+// @Tags 库存
+// @Param body body dto.StockPageReq true "body"
+// @Success 200 {object} response.Page{list=[]models.StockIn} "{"code": 200, "data": [...]}"
+// @Router /stock/in [post]
+// @Security Bearer
+//func (c StockController) StockInList() {
+//	s := services.Stock{}
+//	reqData := dto.StockPageReq{}
+//	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+//		c.Error(global.ParseFormErr, err, err.Error())
+//		return
+//	}
+//
+//	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+//	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+//	err := s.StockIn(&reqData)
+//	if err != nil {
+//		c.Error(500, err, err.Error())
+//		return
+//	}
+//	c.OK(reqData.GetId(), "入库成功")
+//}

+ 2377 - 0
controllers/stock_template.go

@@ -0,0 +1,2377 @@
+package controllers
+
+import (
+	"Medical_ERP/common/global"
+	_ "Medical_ERP/common/response"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"Medical_ERP/services"
+	"Medical_ERP/utils"
+	"baliance.com/gooxml/color"
+	"baliance.com/gooxml/document"
+	"baliance.com/gooxml/measurement"
+	"baliance.com/gooxml/schema/soo/wml"
+	"fmt"
+	"github.com/jung-kurt/gofpdf"
+	"github.com/signintech/gopdf"
+	"github.com/xuri/excelize/v2"
+	"gogs.baozhida.cn/zoie/OAuth-core/pkg/jwtauth/beegouser"
+	"os"
+	"time"
+)
+
+type StockTemplateController struct {
+	BaseController
+}
+
+// StockTemplateIn 入库
+// @Summary 入库
+// @Description 入库
+// @Tags 库存
+// @Param body body dto.StockTemplateInInsertReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/in [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateIn() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateInInsertReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.StockTemplateIn(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "入库成功")
+}
+
+// StockTemplateInEdit 修改入库
+// @Summary 修改入库
+// @Description 修改入库
+// @Tags 库存
+// @Param body body dto.StockTemplateInEditReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/in [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateInEdit() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateInEditReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetUpdateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.StockTemplateInEdit(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "修改入库信息成功")
+}
+
+// StockTemplateInDelete 删除入库
+// @Summary 删除入库
+// @Description 删除入库
+// @Tags 库存
+// @Param body body dto.StockTemplateInDeleteReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/in [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateInDelete() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateInDeleteReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetUpdateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.StockTemplateInDelete(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "删除入库信息成功")
+}
+
+// BatchStockTemplateIn 批量入库
+// @Summary 批量入库
+// @Description 批量入库
+// @Tags 库存
+// @Param body body dto.BatchStockTemplateInInsertReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/in [post]
+// @Security Bearer
+func (c StockTemplateController) BatchStockTemplateIn() {
+	s := services.StockTemplate{}
+	reqData := dto.BatchStockTemplateInInsertReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.BatchStockTemplateIn(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(nil, "入库成功")
+}
+
+// StockTemplateOut 出库
+// @Summary 出库
+// @Description 出库
+// @Tags 库存
+// @Param body body dto.StockTemplateOutInsertReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/out [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateOut() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateOutInsertReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.StockTemplateOut(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "出库成功")
+}
+
+// StockTemplateOutEdit 修改入库
+// @Summary 修改入库
+// @Description 修改入库
+// @Tags 库存
+// @Param body body dto.StockTemplateOutEditReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/in [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateOutEdit() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateOutEditReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetUpdateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.StockTemplateOutEdit(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "修改出库信息成功")
+}
+
+// StockTemplateOutDelete 删除入库
+// @Summary 删除入库
+// @Description 删除入库
+// @Tags 库存
+// @Param body body dto.StockTemplateOutDeleteReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/in [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateOutDelete() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateOutDeleteReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetUpdateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.StockTemplateOutDelete(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(reqData.GetId(), "删除出库信息成功")
+}
+
+// BatchStockTemplateOut 批量出库
+// @Summary 批量出库
+// @Description 批量出库
+// @Tags 库存
+// @Param body body dto.StockTemplateOutInsertReq true "body"
+// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
+// @Router /stock-template/out [post]
+// @Security Bearer
+func (c StockTemplateController) BatchStockTemplateOut() {
+	s := services.StockTemplate{}
+	reqData := dto.BatchStockTemplateOutInsertReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	reqData.SetCreateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
+	err := s.BatchStockTemplateOut(&reqData)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.OK(nil, "出库成功")
+}
+
+// StockTemplateInList 入库列表
+// @Summary 入库列表
+// @Description 入库列表
+// @Tags 库存
+// @Param body body dto.StockTemplateInPageReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock-template/in/list [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateInList() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateInPageReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, count, err := s.StockTemplateInList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(count), reqData.GetPageIndex(), reqData.GetPageSize(), "查询成功")
+}
+
+// StockTemplateOutList 出库列表
+// @Summary 出库列表
+// @Description 出库列表
+// @Tags 库存
+// @Param body body dto.StockTemplateOutPageReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock-template/out/list [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateOutList() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateOutPageReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, count, err := s.StockTemplateOutList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(count), reqData.GetPageIndex(), reqData.GetPageSize(), "查询成功")
+}
+
+// StockTemplateInventoryList 收发记录
+// @Summary 收发记录
+// @Description 收发记录
+// @Tags 库存
+// @Param body body dto.StockTemplateInventoryPageReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock-template/inventory/list [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateInventoryList() {
+	s := services.StockTemplate{}
+	reqData := dto.StockTemplateInventoryPageReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, count, err := s.StockTemplateInventoryList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(count), reqData.GetPageIndex(), reqData.GetPageSize(), "查询成功")
+}
+
+// StockTemplateInventoryExcel 收发登记表
+// @Summary 收发登记表
+// @Description 收发登记表
+// @Tags 库存
+// @Param body body dto.StockTemplateInventoryExcelReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock-template/inventory/list [post]
+// @Security Bearer
+func (c StockTemplateController) StockTemplateInventoryExcel(reqData dto.StockTemplateInventoryExcelReq) {
+	s := services.StockTemplate{}
+	//reqData := dto.StockTemplateInventoryExcelReq{}
+	//if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+	//	c.Error(global.ParseFormErr, err, err.Error())
+	//	return
+	//}
+	deptId := beegouser.GetDeptId(c.Ctx)
+	models.InitBasicData(deptId)
+	medicineInfo, err := s.GetMedicineInfo(deptId, map[string]interface{}{
+		"product_id":    reqData.ProductID,
+		"enterprise_id": reqData.EnterpriseID,
+		"spec_id":       reqData.SpecId,
+		"batch_number":  reqData.BatchNumber,
+	})
+	if err != nil {
+		c.Error(global.BadRequest, err, "药品信息不存在")
+		return
+	}
+	var productName, specName, unitName, dosageFormName string
+
+	if id, ok := medicineInfo[models.FieldProductID]; ok {
+		productName = models.Read_Product_Get(utils.ToInt(id))
+	}
+	if id, ok := medicineInfo[models.FieldSpecID]; ok {
+		specName = models.Read_Spec_Get(utils.ToInt(id))
+	}
+	if id, ok := medicineInfo[models.FieldUnitID]; ok {
+		unitName = models.Read_Unit_Get(utils.ToInt(id))
+	}
+	if id, ok := medicineInfo[models.FieldDosageFormID]; ok {
+		dosageFormName = models.Read_DosageForm_Get(utils.ToInt(id))
+	}
+
+	list, err := s.StockTemplateInventoryExcel(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	f := excelize.NewFile() // 设置单元格的值
+	StyleTitle, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Bold: true, Size: 20, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+		})
+	f.MergeCell("Sheet1", "A1", "O1")
+	f.SetRowStyle("Sheet1", 1, 1, StyleTitle)
+	f.SetRowHeight("Sheet1", 1, 50)
+	var interval string
+	if len(reqData.StartDate) > 0 && len(reqData.EndDate) > 0 {
+		interval = fmt.Sprintf("%s至%s", reqData.StartDate, reqData.EndDate)
+	}
+	f.SetCellValue("Sheet1", "A1", fmt.Sprintf("%s收发登记表", interval))
+
+	StyleLittleTitle, _ := f.NewStyle(
+		&excelize.Style{
+			Font: &excelize.Font{Size: 12, Family: "宋体"},
+		})
+
+	f.SetCellValue("Sheet1", "K2", fmt.Sprintf("制品名称:%s", productName))
+	f.SetCellValue("Sheet1", "K3", fmt.Sprintf("剂型:%s  规格:%s  单位:%s", dosageFormName, specName, unitName))
+	f.SetRowStyle("Sheet1", 2, 3, StyleLittleTitle)
+
+	// 这里设置表头
+	f.MergeCell("Sheet1", "A4", "A5")
+	f.SetCellValue("Sheet1", "A4", "日期")
+
+	f.MergeCell("Sheet1", "B4", "B5")
+	f.SetCellValue("Sheet1", "B4", "购货/发货单位")
+
+	f.MergeCell("Sheet1", "C4", "C5")
+	f.SetCellValue("Sheet1", "C4", "生产企业")
+
+	f.MergeCell("Sheet1", "D4", "D5")
+	f.SetCellValue("Sheet1", "D4", "批准文号")
+
+	f.MergeCell("Sheet1", "E4", "E5")
+	f.SetCellValue("Sheet1", "E4", "批签发合格证编号")
+
+	f.MergeCell("Sheet1", "F4", "H4")
+	f.SetCellValue("Sheet1", "F4", "收入")
+	f.SetCellValue("Sheet1", "F5", "数量")
+	f.SetCellValue("Sheet1", "G5", "批号")
+	f.SetCellValue("Sheet1", "H5", "效期")
+
+	f.MergeCell("Sheet1", "I4", "K4")
+	f.SetCellValue("Sheet1", "I4", "支出")
+	f.SetCellValue("Sheet1", "I5", "数量")
+	f.SetCellValue("Sheet1", "J5", "批号")
+	f.SetCellValue("Sheet1", "K5", "效期")
+
+	f.MergeCell("Sheet1", "L4", "N4")
+	f.SetCellValue("Sheet1", "L4", "结余")
+	f.SetCellValue("Sheet1", "L5", "数量")
+	f.SetCellValue("Sheet1", "M5", "批号")
+	f.SetCellValue("Sheet1", "N5", "效期")
+
+	f.MergeCell("Sheet1", "O4", "O5")
+	f.SetCellValue("Sheet1", "O4", "经办人签字")
+
+	// 设置列宽
+	f.SetColWidth("Sheet1", "A", "B", 10)
+	f.SetColWidth("Sheet1", "B", "F", 12)
+	f.SetColWidth("Sheet1", "F", "G", 8)
+	f.SetColWidth("Sheet1", "G", "I", 12)
+	f.SetColWidth("Sheet1", "I", "J", 8)
+	f.SetColWidth("Sheet1", "J", "L", 12)
+	f.SetColWidth("Sheet1", "L", "M", 8)
+	f.SetColWidth("Sheet1", "M", "O", 12)
+	f.SetColWidth("Sheet1", "O", "P", 12)
+
+	line := 5
+
+	// 循环写入数据
+	for _, v := range list {
+		line++
+		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), v["date"])
+		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v[models.FieldEnterpriseName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), v[models.FieldApprovalNumber])
+		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", line), v[models.FieldQualificationNumber])
+		if utils.ToInt(v["stock_in_id"]) > 0 {
+			f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v["forwarding_unit"])
+			f.SetCellValue("Sheet1", fmt.Sprintf("F%d", line), utils.ToInt(v["total_in"]))
+			f.SetCellValue("Sheet1", fmt.Sprintf("G%d", line), v[models.FieldApprovalNumber])
+			f.SetCellValue("Sheet1", fmt.Sprintf("H%d", line), v[models.FieldExpiryDate].(time.Time).Format("2006-01-02"))
+		}
+		if utils.ToInt(v["stock_out_id"]) > 0 {
+			f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v["receiving_unit"])
+			f.SetCellValue("Sheet1", fmt.Sprintf("I%d", line), utils.ToInt(v["total_out"]))
+			f.SetCellValue("Sheet1", fmt.Sprintf("J%d", line), v[models.FieldApprovalNumber])
+			f.SetCellValue("Sheet1", fmt.Sprintf("K%d", line), v[models.FieldExpiryDate].(time.Time).Format("2006-01-02"))
+		}
+		f.SetCellValue("Sheet1", fmt.Sprintf("L%d", line), utils.ToInt(v["balance"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("M%d", line), v[models.FieldApprovalNumber])
+		f.SetCellValue("Sheet1", fmt.Sprintf("N%d", line), v[models.FieldExpiryDate].(time.Time).Format("2006-01-02"))
+
+		f.SetCellValue("Sheet1", fmt.Sprintf("O%d", line), v["operator"])
+
+	}
+	f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line+1), "保管单位:"+beegouser.GetDeptName(c.Ctx))
+	f.SetRowStyle("Sheet1", line+1, line+1, StyleLittleTitle)
+
+	Style1, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 13, Family: "宋体", Bold: true},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A4", "O5", Style1)
+	Style2, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 12, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A6", fmt.Sprintf("O%d", line), Style2)
+
+	filename := "收发登记表" + time.Now().Format("20060102150405") + ".xlsx"
+	// 保存文件
+	if err := f.SaveAs("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+}
+func (c StockTemplateController) StockTemplateInventoryPdf(reqData dto.StockTemplateInventoryExcelReq) {
+	s := services.StockTemplate{}
+	deptId := beegouser.GetDeptId(c.Ctx)
+	models.InitBasicData(deptId)
+	medicineInfo, err := s.GetMedicineInfo(deptId, map[string]interface{}{
+		"product_id":    reqData.ProductID,
+		"enterprise_id": reqData.EnterpriseID,
+		"spec_id":       reqData.SpecId,
+		"batch_number":  reqData.BatchNumber,
+	})
+	if err != nil {
+		c.Error(global.BadRequest, err, "药品信息不存在")
+		return
+	}
+	var productName, specName, unitName, dosageFormName string
+
+	if id, ok := medicineInfo[models.FieldProductID]; ok {
+		productName = models.Read_Product_Get(utils.ToInt(id))
+	}
+	if id, ok := medicineInfo[models.FieldSpecID]; ok {
+		specName = models.Read_Spec_Get(utils.ToInt(id))
+	}
+	if id, ok := medicineInfo[models.FieldUnitID]; ok {
+		unitName = models.Read_Unit_Get(utils.ToInt(id))
+	}
+	if id, ok := medicineInfo[models.FieldDosageFormID]; ok {
+		dosageFormName = models.Read_DosageForm_Get(utils.ToInt(id))
+	}
+
+	list, err := s.StockTemplateInventoryExcel(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	cols := []float64{20, 24, 20, 18, 20, 12, 22, 20, 12, 22, 20, 12, 22, 20, 15}
+
+	rows := [][]string{}
+	for _, row := range list {
+		temp := []string{}
+		temp = append(temp, fmt.Sprintf("%v", row["date"]))
+
+		if utils.ToInt(row["stock_in_id"]) > 0 {
+			temp = append(temp, fmt.Sprintf("%s", row["forwarding_unit"]))
+		} else if utils.ToInt(row["stock_out_id"]) > 0 {
+			temp = append(temp, fmt.Sprintf("%s", row["receiving_unit"]))
+		}
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldEnterpriseName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldApprovalNumber]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldQualificationNumber]))
+
+		if utils.ToInt(row["stock_in_id"]) > 0 {
+			temp = append(temp, fmt.Sprintf("%d", row["total_in"]))
+			temp = append(temp, fmt.Sprintf("%s", row[models.FieldBatchNumber]))
+			temp = append(temp, fmt.Sprintf("%s", row[models.FieldExpiryDate].(time.Time).Format("2006-01-02")))
+		} else {
+			temp = append(temp, "")
+			temp = append(temp, "")
+			temp = append(temp, "")
+		}
+
+		if utils.ToInt(row["stock_out_id"]) > 0 {
+			temp = append(temp, fmt.Sprintf("%d", row["total_out"]))
+			temp = append(temp, fmt.Sprintf("%s", row[models.FieldBatchNumber]))
+			temp = append(temp, fmt.Sprintf("%s", row[models.FieldExpiryDate].(time.Time).Format("2006-01-02")))
+		} else {
+			temp = append(temp, "")
+			temp = append(temp, "")
+			temp = append(temp, "")
+		}
+
+		temp = append(temp, fmt.Sprintf("%d", row["balance"]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldBatchNumber]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldExpiryDate].(time.Time).Format("2006-01-02")))
+		temp = append(temp, fmt.Sprintf("%s", row["operator"]))
+		rows = append(rows, temp)
+
+	}
+
+	pdf := gofpdf.New("L", "mm", "A4", "")
+
+	pdf.AddPage()
+	pdf.AddUTF8Font("simsun", "", "static/fonts/MiSans-Medium.ttf")
+	pdf.SetFont("simsun", "", 20)
+	pdf.SetMargins(10, 10, 10)
+	pdf.SetAutoPageBreak(false, 0)
+	pagew, pageh := pdf.GetPageSize()
+	_, _, _, mbottom := pdf.GetMargins()
+	titleStr := "收发登记表"
+
+	titleWd := pdf.GetStringWidth(titleStr)
+	titleX := (pagew - titleWd) / 2
+	pdf.Text(titleX, 20, titleStr)
+
+	titleX = pagew / 3 * 2
+	pdf.SetFont("simsun", "", 10)
+	pdf.Text(titleX, 25, fmt.Sprintf("制品名称:%s 编号", productName))
+	pdf.Text(titleX, 30, fmt.Sprintf("剂型:%s 规格:%s 单位:%s", dosageFormName, specName, unitName))
+
+	h := 16.0
+	curx, y := pdf.GetXY()
+	pdf.SetXY(curx, 35)
+	pdf.CellFormat(cols[0], h, "日期", "1", 0, "CM", false, 0, "")
+	pdf.CellFormat(cols[1], h, "购货/发货单位", "1", 0, "CM", false, 0, "")
+	pdf.CellFormat(cols[2], h, "生产企业", "1", 0, "CM", false, 0, "")
+	pdf.CellFormat(cols[3], h, "批准文号", "1", 0, "CM", false, 0, "")
+	curx, y = pdf.GetXY()
+	width := cols[4]
+	pdf.MultiCell(width, h/2, "批签发合格\n证编号", "1", "CM", false)
+
+	// ------收入
+	curx += width
+	pdf.SetXY(curx, y)
+	width = cols[5] + cols[6] + cols[7]
+	pdf.CellFormat(width, h/2, "收入", "1", 0, "CM", false, 0, "")
+
+	width = cols[5]
+	pdf.SetXY(curx, y+h/2)
+	pdf.CellFormat(width, h/2, "数量", "1", 0, "CM", false, 0, "")
+
+	curx += width
+	pdf.SetXY(curx, y+h/2)
+	width = cols[6]
+	pdf.CellFormat(width, h/2, "批号", "1", 0, "CM", false, 0, "")
+
+	curx += width
+	pdf.SetXY(curx, y+h/2)
+	width = cols[7]
+	pdf.CellFormat(width, h/2, "效期", "1", 0, "CM", false, 0, "")
+
+	// ------支出
+	curx += width
+	pdf.SetXY(curx, y)
+	width = cols[8] + cols[9] + cols[10]
+	pdf.CellFormat(width, h/2, "支出", "1", 0, "CM", false, 0, "")
+
+	width = cols[8]
+	pdf.SetXY(curx, y+h/2)
+	pdf.CellFormat(width, h/2, "数量", "1", 0, "CM", false, 0, "")
+
+	curx += width
+	pdf.SetXY(curx, y+h/2)
+	width = cols[9]
+	pdf.CellFormat(width, h/2, "批号", "1", 0, "CM", false, 0, "")
+
+	curx += width
+	pdf.SetXY(curx, y+h/2)
+	width = cols[10]
+	pdf.CellFormat(width, h/2, "效期", "1", 0, "CM", false, 0, "")
+
+	// ------结余
+	curx += width
+	pdf.SetXY(curx, y)
+	width = cols[11] + cols[12] + cols[13]
+	pdf.CellFormat(width, h/2, "结余", "1", 0, "CM", false, 0, "")
+
+	width = cols[11]
+	pdf.SetXY(curx, y+h/2)
+	pdf.CellFormat(width, h/2, "数量", "1", 0, "CM", false, 0, "")
+
+	curx += width
+	pdf.SetXY(curx, y+h/2)
+	width = cols[12]
+	pdf.CellFormat(width, h/2, "批号", "1", 0, "CM", false, 0, "")
+
+	curx += width
+	pdf.SetXY(curx, y+h/2)
+	width = cols[13]
+	pdf.CellFormat(width, h/2, "效期", "1", 0, "CM", false, 0, "")
+
+	curx += width
+	width = cols[14]
+	pdf.SetXY(curx, y)
+	pdf.MultiCell(width, h/2, "经办人\n签字", "1", "CM", false)
+
+	pdf.SetFont("simsun", "", 9)
+	marginCell := 4. // margin of top/bottom of cell
+
+	for _, row := range rows {
+		curx, y = pdf.GetXY()
+		x := curx
+
+		height := 0.
+		_, lineHt := pdf.GetFontSize()
+
+		for i, txt := range row {
+			lines := pdf.SplitLines([]byte(txt), cols[i])
+			h := float64(len(lines))*lineHt + marginCell*float64(len(lines))
+			if h > height {
+				height = h
+			}
+		}
+		// add a new page if the height of the row doesn't fit on the page
+		if pdf.GetY()+height > pageh-mbottom {
+			pdf.AddPage()
+			y = pdf.GetY()
+		}
+		for i, txt := range row {
+			width = cols[i]
+			pdf.Rect(x, y, width, height, "")
+			pdf.MultiCell(width, lineHt+marginCell, txt, "", "CM", false)
+			x += width
+			pdf.SetXY(x, y)
+		}
+		pdf.SetXY(curx, y+height)
+	}
+
+	filename := "收发登记表" + time.Now().Format("20060102150405") + ".pdf"
+	// 保存文件
+	if err := pdf.OutputFileAndClose("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+}
+func (c StockTemplateController) StockTemplateInventoryExport() {
+	reqData := dto.StockTemplateInventoryExcelReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+	if reqData.Type == "excel" {
+		c.StockTemplateInventoryExcel(reqData)
+	}
+	if reqData.Type == "pdf" {
+		c.StockTemplateInventoryPdf(reqData)
+	}
+}
+
+// TransportRecordWord 运输记录表word
+// @Summary 收发登记表word
+// @Description 收发登记表word
+// @Tags 库存
+// @Param body body dto.TransportRecordWordReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock-template/inventory/list [post]
+// @Security Bearer
+func (c StockTemplateController) TransportRecordWord(reqData dto.TransportRecordWordReq) {
+	s := services.StockTemplate{}
+	deptId := beegouser.GetDeptId(c.Ctx)
+
+	list, err := s.StockTemplateTransportRecordWord(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+
+	doc := document.New()
+	// 获取文档的默认页面设置
+	doc.BodySection().SetPageMargins(
+		0.3*measurement.Inch,
+		0.4*measurement.Inch,
+		0.5*measurement.Inch,
+		0.3*measurement.Inch,
+		1*measurement.Inch,
+		0.4*measurement.Inch,
+		0.1*measurement.Inch)
+	ftr := doc.AddFooter()
+	para := ftr.AddParagraph()
+	//para.Properties().AddTabStop(0.1*measurement.Inch, wml.ST_TabJcCenter, wml.ST_TabTlcNone)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run := para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddTab()
+	run.AddText("第 ")
+	run.AddFieldWithFormatting(document.FieldCurrentPage, "", false)
+	run.AddText(" 页 / 共 ")
+	run.AddFieldWithFormatting(document.FieldNumberOfPages, "", false)
+	run.AddText(" 页")
+	//doc.BodySection().SetFooter(ftr, wml.ST_HdrFtrDefault)
+
+	para = doc.AddParagraph()
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetBold(true)
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(14)
+	para.Properties().Spacing().SetLineSpacing(18*measurement.Point, wml.ST_LineSpacingRuleAuto)
+	run.AddText("表3-4 运输记录表")
+
+	para = doc.AddParagraph()
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(12)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	para.Properties().Spacing().SetLineSpacing(30*measurement.Point, wml.ST_LineSpacingRuleAuto)
+
+	run.AddText("(各级通用)")
+
+	para = doc.AddParagraph()
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	para.Properties().Spacing().SetLineSpacing(20*measurement.Point, wml.ST_LineSpacingRuleAuto)
+	run.AddText("运输工具: □冷藏车  □运输车  □普通车辆  □无  □其他")
+	run = para.AddRun()
+	run.AddText("       ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+
+	para = doc.AddParagraph()
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	para.Properties().Spacing().SetLineSpacing(20*measurement.Point, wml.ST_LineSpacingRuleAuto)
+	run.AddText("冷藏工具: □冷藏车  □车载冷藏箱  □冷藏箱或冷藏包  □其他")
+	run = para.AddRun()
+	run.AddText("       ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+
+	para = doc.AddParagraph()
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	para.Properties().Spacing().SetLineSpacing(20*measurement.Point, wml.ST_LineSpacingRuleAuto)
+	run.AddText("配送方式: □本级配送(车牌号:")
+	run = para.AddRun()
+	run.AddText("              ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText(")  □下级自运(车牌号:")
+	run = para.AddRun()
+	run.AddText("              ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText(")")
+
+	// --------------------运输情况
+	para = doc.AddParagraph()
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetBold(true)
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("运输情况:")
+
+	table := doc.AddTable()
+	table.Properties().SetLayout(wml.ST_TblLayoutTypeFixed)
+	table.Properties().SetWidthPercent(100)
+	look := table.Properties().TableLook()
+	look.SetFirstColumn(true)
+	look.SetFirstRow(true)
+	look.SetHorizontalBanding(true)
+
+	// width of the page
+	// with thick borers
+	borders := table.Properties().Borders()
+	//borders.SetBottom(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	//borders.SetLeft(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	//borders.SetRight(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	//borders.SetTop(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	borders.SetAll(wml.ST_BorderSingle, color.Auto, measurement.Zero)
+	//borders.SetInsideVertical(wml.ST_BorderSingle, color.Auto, measurement.Zero)
+
+	row := table.AddRow()
+	row.Properties().SetHeight(0.35*measurement.Inch, wml.ST_HeightRuleExact)
+
+	cell := row.AddCell()
+	cell.Properties().SetWidthPercent(15)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("品名")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(12)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("生产企业")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(10)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("批准文号")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(15)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("批签发合格编号")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(13)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("规格(剂/支或粒)")
+
+	//cell = row.AddCell()
+	//cell.Properties().SetWidth(0.5 * measurement.Inch)
+	//para = cell.AddParagraph()
+	//cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	//para.Properties().SetAlignment(wml.ST_JcCenter)
+	//run = para.AddRun()
+	//run.Properties().SetFontFamily("宋体")
+	//run.Properties().SetSize(8)
+	//run.AddText("生产日期")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(12)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("疫苗批号")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(10)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("疫苗效期")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(6)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("数量")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(6)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(9)
+	run.AddText("单位")
+
+	models.InitBasicData(deptId)
+	for _, medicineInfo := range list {
+		row = table.AddRow()
+		row.Properties().SetHeight(0.3*measurement.Inch, wml.ST_HeightRuleAtLeast)
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(15)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		productName := models.Read_Product_Get(utils.ToInt(medicineInfo[models.FieldProductID]))
+		run.AddText(productName)
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(12)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		enterpriseName := models.Read_Enterprise_Get(utils.ToInt(medicineInfo[models.FieldEnterpriseID]))
+		run.AddText(enterpriseName)
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(10)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		run.AddText(medicineInfo[models.FieldApprovalNumber].(string))
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(15)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		run.AddText(medicineInfo[models.FieldQualificationNumber].(string))
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(13)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		specName := models.Read_Spec_Get(utils.ToInt(medicineInfo[models.FieldSpecID]))
+		run.AddText(specName)
+
+		//cell = row.AddCell()
+		//cell.Properties().SetWidth(0.5 * measurement.Inch)
+		//para = cell.AddParagraph()
+		//cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		//para.Properties().SetAlignment(wml.ST_JcCenter)
+		//run = para.AddRun()
+		//run.Properties().SetFontFamily("宋体")
+		//run.Properties().SetSize(8)
+		//run.AddText("生产日期")
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(12)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		run.AddText(medicineInfo[models.FieldBatchNumber].(string))
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(10)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		run.AddText(medicineInfo[models.FieldExpiryDate].(time.Time).Format("2006-01-02"))
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(6)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		run.AddText(fmt.Sprintf("%d", utils.ToInt(medicineInfo["quantity"])))
+
+		cell = row.AddCell()
+		cell.Properties().SetWidthPercent(6)
+		para = cell.AddParagraph()
+		cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+		para.Properties().SetAlignment(wml.ST_JcCenter)
+		run = para.AddRun()
+		run.Properties().SetFontFamily("宋体")
+		run.Properties().SetSize(8)
+		unitName := models.Read_Enterprise_Get(utils.ToInt(medicineInfo[models.FieldUnitID]))
+		run.AddText(unitName)
+	}
+
+	if len(list) < 11 {
+		for i := 0; i < 11-len(list); i++ {
+			row = table.AddRow()
+			row.Properties().SetHeight(0.3*measurement.Inch, wml.ST_HeightRuleAtLeast)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(15)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(12)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(10)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(15)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(13)
+
+			//cell = row.AddCell()
+			//cell.Properties().SetWidth(0.5 * measurement.Inch)
+			//para = cell.AddParagraph()
+			//cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+			//para.Properties().SetAlignment(wml.ST_JcCenter)
+			//run = para.AddRun()
+			//run.Properties().SetFontFamily("宋体")
+			//run.Properties().SetSize(8)
+			//run.AddText("生产日期")
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(12)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(10)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(6)
+
+			cell = row.AddCell()
+			cell.Properties().SetWidthPercent(6)
+
+		}
+	}
+
+	// --------------------运输温度记录
+	para = doc.AddParagraph()
+	para.Properties().Spacing().SetLineSpacing(25*measurement.Point, wml.ST_LineSpacingRuleAtLeast)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetBold(true)
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("运输温度记录:")
+
+	table = doc.InsertTableAfter(para)
+	table.Properties().SetWidthPercent(100)
+	table.Properties().SetAlignment(wml.ST_JcTableCenter)
+	table.Properties().SetLayout(wml.ST_TblLayoutTypeAutofit)
+	look = table.Properties().TableLook()
+	look.SetFirstColumn(true)
+	look.SetFirstRow(true)
+	look.SetHorizontalBanding(true)
+
+	borders = table.Properties().Borders()
+	borders.SetBottom(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	borders.SetLeft(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	borders.SetRight(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	borders.SetTop(wml.ST_BorderSingle, color.Auto, 1*measurement.Point)
+	borders.SetInsideHorizontal(wml.ST_BorderSingle, color.Auto, measurement.Zero)
+	borders.SetInsideVertical(wml.ST_BorderSingle, color.Auto, measurement.Zero)
+
+	//-----------------项目名称
+	row = table.AddRow()
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(10)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.AddText("")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(38)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("日期/时间")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(19)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(8)
+	run.AddText("疫苗储存温度")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(17)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("冰排状态")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(16)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("环境温度")
+
+	//------------------启运
+	row = table.AddRow()
+	row.Properties().SetHeight(0.4*measurement.Inch, wml.ST_HeightRuleAtLeast)
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(10)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("启运")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(38)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("        ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("年")
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("月")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("日")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("时")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("分")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(19)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(17)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run.AddText("")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(16)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+
+	//------------------途中
+	row = table.AddRow()
+	row.Properties().SetHeight(0.8*measurement.Inch, wml.ST_HeightRuleAtLeast)
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(10)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("途中")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(38)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	para.Properties().Spacing().SetLineSpacing(20*measurement.Point, wml.ST_LineSpacingRuleAuto)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("        ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("年")
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("月")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("日")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("时")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("分")
+
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("        ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("年")
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("月")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("日")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("时")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("分")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(19)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	para.Properties().Spacing().SetLineSpacing(20*measurement.Point, wml.ST_LineSpacingRuleAuto)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(17)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetSize(5)
+	run.AddText("————————")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(16)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	para.Properties().Spacing().SetLineSpacing(20*measurement.Point, wml.ST_LineSpacingRuleAuto)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+
+	//------------------到达
+	row = table.AddRow()
+	row.Properties().SetHeight(0.4*measurement.Inch, wml.ST_HeightRuleAtLeast)
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(10)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("到达")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(38)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("        ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("年")
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("月")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("日")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("时")
+	run = para.AddRun()
+	run.AddText("      ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("分")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(19)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(17)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run.AddText("")
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(16)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcCenter)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("℃")
+
+	para = doc.AddParagraph()
+	para.Properties().Spacing().SetLineSpacing(25*measurement.Point, wml.ST_LineSpacingRuleExact)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("启运至到达行驶里程数:")
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("               ")
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("千米。")
+
+	table3 := doc.AddTable()
+	table3.Properties().SetLayout(wml.ST_TblLayoutTypeFixed)
+	table3.Properties().SetWidthPercent(100)
+	look.SetFirstColumn(true)
+	look.SetFirstRow(true)
+	look.SetHorizontalBanding(true)
+	look = table3.Properties().TableLook()
+
+	borders3 := table3.Properties().Borders()
+	borders3.SetAll(wml.ST_BorderUnset, color.Auto, measurement.Zero)
+
+	row = table3.AddRow()
+	row.Properties().SetHeight(0.36*measurement.Inch, wml.ST_HeightRuleExact)
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(50)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("发货单位:")
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText(beegouser.GetDeptName(c.Ctx))
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(50)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("发货人签名:")
+	run = para.AddRun()
+	run.Properties().SetSize(5)
+	run.AddText("________________________________________________________________")
+
+	row = table3.AddRow()
+	row.Properties().SetHeight(0.36*measurement.Inch, wml.ST_HeightRuleExact)
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(50)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("收货单位:")
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText(reqData.ReceivingUnit)
+	run.Properties().SetUnderline(wml.ST_UnderlineSingle, color.Black)
+
+	cell = row.AddCell()
+	cell.Properties().SetWidthPercent(50)
+	para = cell.AddParagraph()
+	cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("收货人签名:")
+	run = para.AddRun()
+	run.Properties().SetSize(5)
+	run.AddText("________________________________________________________________")
+
+	para = doc.AddParagraph()
+	para.Properties().SetAlignment(wml.ST_JcLeft)
+	run = para.AddRun()
+	run.Properties().SetFontFamily("宋体")
+	run.Properties().SetSize(10)
+	run.AddText("填表说明:本表供疫苗配送企业、疾病预防控制机构、接种单位疫苗运输时填写;出入库单号为单位编码+年月日+2位流水号;运输超过6小时需记录途中温度,间隔不超过6小时;使用无自动温度显示的冰排保冷设备时,只在启运和达到时填写冰排状态(冻结、冰水混合物、完全融化)。")
+	doc.BodySection().SetFooter(ftr, wml.ST_HdrFtrDefault)
+
+	filename := "运输记录表" + time.Now().Format("20060102150405") + ".docx"
+	// 保存文件
+	if err := doc.SaveToFile("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+
+	// 保存为PDF文件
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+}
+
+func (c StockTemplateController) TransportRecordPdf(reqData dto.TransportRecordWordReq) {
+	s := services.StockTemplate{}
+	deptId := beegouser.GetDeptId(c.Ctx)
+
+	list, err := s.StockTemplateTransportRecordWord(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+
+	cols := []float64{30, 22, 20, 27, 31, 22, 21, 11, 11}
+
+	header := []string{"品种", "生产企业", "批准文号", "批签发合格编号", "规格(剂/支或粒)", "批号", "效期", "数量", "单位"}
+	rows := [][]string{}
+	for _, row := range list {
+		temp := []string{}
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldProductName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldEnterpriseName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldApprovalNumber]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldQualificationNumber]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldSpecName]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldBatchNumber]))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldExpiryDate].(time.Time).Format("2006-01-02")))
+		temp = append(temp, fmt.Sprintf("%d", utils.ToInt(row["quantity"])))
+		temp = append(temp, fmt.Sprintf("%s", row[models.FieldUnitName]))
+		rows = append(rows, temp)
+	}
+	if len(list) < 11 {
+		for i := 0; i < 11-len(list); i++ {
+			rows = append(rows, []string{"", "", "", "", "", "", "", "", ""})
+		}
+	}
+
+	pdf := gofpdf.New("P", "mm", "A4", "")
+
+	pdf.AddPage()
+	pdf.AddUTF8Font("simsun", "", "static/fonts/MiSans-Medium.ttf")
+	pdf.SetFont("simsun", "", 22)
+	pdf.SetMargins(2, 5, 5)
+	pdf.SetAutoPageBreak(true, 0)
+	pagew, pageh := pdf.GetPageSize()
+	_, _, _, mbottom := pdf.GetMargins()
+
+	titleStr := "表3-4 运输记录表"
+	titleWd := pdf.GetStringWidth(titleStr)
+	titleX := (pagew - titleWd) / 2
+	pdf.Text(titleX, 15, titleStr)
+
+	pdf.SetFont("simsun", "", 16)
+	titleStr = "(各级通用)"
+	titleWd = pdf.GetStringWidth(titleStr)
+	titleX = (pagew - titleWd) / 2
+	pdf.Text(titleX, 25, titleStr)
+
+	pdf.SetFont("simsun", "", 10)
+	curx, y := pdf.GetXY()
+	y = 25
+	y += 8
+	pdf.Text(curx, y, "运输工具: □冷藏车  □运输车  □普通车辆  □无  □其他")
+	y += 8
+	pdf.Text(curx, y, "冷藏工具: □冷藏车  □车载冷藏箱  □冷藏箱或冷藏包  □其他")
+	y += 8
+	pdf.Text(curx, y, "配送方式: □本级配送(车牌号:_______) □下级自运(车牌号:_______)")
+
+	y += 10
+	pdf.Text(curx, y, "运输情况:")
+	y += 3
+	pdf.SetXY(curx, y)
+	h := 10.0
+	for i := 0; i < len(header); i++ {
+		pdf.CellFormat(cols[i], h, header[i], "1", 0, "CM", false, 0, "")
+	}
+	pdf.Ln(-1)
+	pdf.SetFont("simsun", "", 9)
+	marginCell := 4. // margin of top/bottom of cell
+	y = pdf.GetY()
+	pdf.SetXY(curx, y)
+	for _, row := range rows {
+		curx, y = pdf.GetXY()
+		if y > pageh-mbottom-marginCell {
+			pdf.AddPage()
+			y = pdf.GetY()
+		}
+		x := curx
+
+		height := 0.
+		_, lineHt := pdf.GetFontSize()
+
+		for i, txt := range row {
+			lines := pdf.SplitLines([]byte(txt), cols[i])
+			h = float64(len(lines))*lineHt + marginCell*float64(len(lines))
+			if h > height {
+				height = h
+			}
+		}
+		//// add a new page if the height of the row doesn't fit on the page
+		//if pdf.GetY()+height > pageh-mbottom {
+		//	pdf.AddPage()
+		//	y = pdf.GetY()
+		//}
+		for i, txt := range row {
+			width := cols[i]
+			if len(txt) == 0 {
+				height = lineHt + marginCell
+			}
+			pdf.Rect(x, y, width, height, "")
+			pdf.MultiCell(width, lineHt+marginCell, txt, "", "CM", false)
+			x += width
+			pdf.SetXY(x, y)
+		}
+
+		pdf.SetXY(curx, y+height)
+	}
+
+	y += 20
+	pdf.Text(curx, y, "运输温度记录:")
+	y += 3
+	pdf.SetXY(curx, y)
+	header2 := []string{"", "日期/时间", "储存温度", "冰排状态", "环境温度"}
+	cols2 := []float64{20, 77, 34, 34, 30}
+	h = 10.0
+	for i := 0; i < len(header2); i++ {
+		pdf.CellFormat(cols2[i], h, header2[i], "1", 0, "CM", false, 0, "")
+	}
+	pdf.Ln(-1)
+	y = pdf.GetY()
+	pdf.SetXY(curx, y)
+
+	row2 := [][]string{
+		{"启运", "______年___月___日___时___分", "________℃", "", "________℃"},
+		{"途中", "______年___月___日___时___分", "________℃", "————", "________℃"},
+		{"到达", "______年___月___日___时___分", "________℃", "", "________℃"},
+	}
+
+	for j, row := range row2 {
+		curx, y = pdf.GetXY()
+		x := curx
+		_, lineHt := pdf.GetFontSize()
+		height := lineHt * 3
+		if j == 1 {
+			height = lineHt * 5
+		}
+
+		// add a new page if the height of the row doesn't fit on the page
+		if pdf.GetY()+height > pageh-mbottom {
+			pdf.AddPage()
+			y = pdf.GetY()
+		}
+		for i, txt := range row {
+			width := cols2[i]
+			txtWd := pdf.GetStringWidth(txt)
+
+			txtX := x + (width-txtWd)/2
+
+			if j == 1 {
+				if i == 1 || i == 2 || i == 4 {
+					txtY := y + height/5*2
+					pdf.Text(txtX, txtY, txt)
+					txtY = y + height/5*4
+					pdf.Text(txtX, txtY, txt)
+				} else {
+					txtY := y + height/5*3
+					pdf.Text(txtX, txtY, txt)
+				}
+			} else {
+				txtY := y + height/3*2
+				pdf.Text(txtX, txtY, txt)
+			}
+
+			pdf.Rect(x, y, width, height, "")
+
+			x += width
+			pdf.SetXY(x, y)
+		}
+		pdf.SetXY(curx, y+height)
+	}
+	curx, y = pdf.GetXY()
+	y += 3
+	pdf.SetXY(curx, y)
+	pdf.CellFormat(0, 10, "启运至到达行驶里程数:_________千米", "", 0, "", false, 0, "")
+
+	y = pdf.GetY()
+	y += 8
+	pdf.SetXY(curx, y)
+	txtWd := pdf.GetStringWidth("发货单位:")
+	pdf.CellFormat(txtWd, 10, "发货单位:", "", 0, "", false, 0, "")
+	pdf.SetFont("simsun", "U", 9)
+	pdf.SetUnderlineThickness(2)
+	txtWd = pdf.GetStringWidth(beegouser.GetDeptName(c.Ctx))
+	pdf.CellFormat(txtWd, 10, beegouser.GetDeptName(c.Ctx), "", 0, "", false, 0, "")
+
+	pdf.SetXY(120, y)
+	pdf.SetFont("simsun", "", 9)
+	pdf.CellFormat(0, 10, "发货人签名:___________________________", "", 0, "", false, 0, "")
+
+	y = pdf.GetY()
+	y += 8
+	pdf.SetXY(curx, y)
+	txtWd = pdf.GetStringWidth("收货单位:")
+	pdf.CellFormat(txtWd, 10, "收货单位:", "", 0, "", false, 0, "")
+	pdf.SetFont("simsun", "U", 9)
+	pdf.SetUnderlineThickness(2)
+	txtWd = pdf.GetStringWidth(reqData.ReceivingUnit)
+	pdf.CellFormat(txtWd, 10, reqData.ReceivingUnit, "", 0, "", false, 0, "")
+
+	pdf.SetXY(120, y)
+	pdf.SetFont("simsun", "", 9)
+	pdf.CellFormat(0, 10, "收货人签名:___________________________", "", 0, "", false, 0, "")
+
+	y = pdf.GetY()
+	y += 10
+	pdf.SetXY(curx, y)
+	txt := "填表说明:本表供疫苗配送企业、疾病预防控制机构、接种单位疫苗运输时填写;出入库单号为单位编码+年月日+2位流水号;运输超过6小时需记录途中温度,间隔不超过6小时;使用无自动温度显示的冰排保冷设备时,只在启运和达到时填写冰排状态(冻结、冰水混合物、完全融化)。"
+	pdf.MultiCell(0, 5, txt, "", "", false)
+	pdf.Ln(-1)
+
+	pdf.SetFooterFunc(func() {
+		// Position at 1.5 cm from bottom
+		pdf.SetY(-15)
+		// Arial italic 8
+		pdf.SetFont("simsun", "", 8)
+		// Text color in gray
+		//pdf.SetTextColor(128, 128, 128)
+		// Page number
+		pdf.CellFormat(0, 10, fmt.Sprintf("第 %d 页 / 共 %d 页", pdf.PageNo(), pdf.PageCount()),
+			"", 0, "C", false, 0, "")
+	})
+	filename := "运输记录表" + time.Now().Format("20060102150405") + ".pdf"
+	// 保存文件
+	if err = pdf.OutputFileAndClose("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+
+}
+func (c StockTemplateController) TransportRecordExport() {
+	reqData := dto.TransportRecordWordReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+	deptId := beegouser.GetDeptId(c.Ctx)
+	models.InitBasicData(deptId)
+
+	if reqData.Type == "pdf" {
+		c.TransportRecordPdf(reqData)
+
+	}
+
+	if reqData.Type == "word" {
+		c.TransportRecordWord(reqData)
+	}
+
+}
+
+// StockUnitList 获取收货单位,发货单位,收发单位
+// @Summary 获取收货单位,发货单位,收发单位
+// @Description 获取收货单位,发货单位,收发单位
+// @Tags 库存
+// @Param body body dto.StockUnitListReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock/unit/list [post]
+// @Security Bearer
+func (c StockTemplateController) StockUnitList() {
+	s := services.StockTemplate{}
+	reqData := dto.StockUnitListReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, err := s.StockUnitList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, 0, 0, 0, "查询成功")
+}
+
+// StockOperatorList 获取收货单位,发货单位,收发单位
+// @Summary 获取收货单位,发货单位,收发单位
+// @Description 获取收货单位,发货单位,收发单位
+// @Tags 库存
+// @Param body body dto.StockOperatorListReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock/unit/list [post]
+// @Security Bearer
+func (c StockTemplateController) StockOperatorList() {
+	s := services.StockTemplate{}
+	reqData := dto.StockOperatorListReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, err := s.StockOperatorList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, 0, 0, 0, "查询成功")
+}
+
+// StockStatList 库存查询
+// @Summary 库存查询-每个商品、生产企业、规格、批号数量
+// @Description 库存查询
+// @Tags 库存
+// @Param body body dto.StockStatListReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock/stat [post]
+// @Security Bearer
+func (c StockTemplateController) StockInquiryList() {
+	s := services.StockTemplate{}
+	reqData := dto.StockStatListReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, cnt, err := s.StockInquiryList(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(cnt), reqData.GetPageIndex(), reqData.GetPageSize(), "查询成功")
+}
+
+// StockInquiryExcel 导出库存信息表
+// @Summary 库存信息表-每个商品、生产企业、规格、批号数量
+// @Description 库存信息表
+// @Tags 库存
+// @Param body body dto.StockStatListReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock/stat/excel [post]
+// @Security Bearer
+func (c StockTemplateController) StockInquiryExcel() {
+	s := services.StockTemplate{}
+	reqData := dto.StockStatListReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, err := s.StockInquiryExcel(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	f := excelize.NewFile() // 设置单元格的值
+	StyleTitle, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Bold: true, Size: 20, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+		})
+	f.MergeCell("Sheet1", "A1", "L1")
+	f.SetRowStyle("Sheet1", 1, 1, StyleTitle)
+	f.SetRowHeight("Sheet1", 1, 50)
+	f.SetCellValue("Sheet1", "A1", fmt.Sprintf("疫苗库存信息表(%s)", time.Now().Format("2006-01-02 15:04:05")))
+	// 这里设置表头
+	f.SetCellValue("Sheet1", "A2", "品名")
+	f.SetCellValue("Sheet1", "B2", "生产企业")
+	f.SetCellValue("Sheet1", "C2", "规格")
+	f.SetCellValue("Sheet1", "D2", "剂型")
+	f.SetCellValue("Sheet1", "E2", "单位")
+	f.SetCellValue("Sheet1", "F2", "数量")
+	f.SetCellValue("Sheet1", "G2", "购进单价")
+	f.SetCellValue("Sheet1", "H2", "销售单价")
+	f.SetCellValue("Sheet1", "I2", "批号")
+	f.SetCellValue("Sheet1", "J2", "效期")
+	f.SetCellValue("Sheet1", "K2", "批准文号")
+	f.SetCellValue("Sheet1", "L2", "批签发编号")
+	// 设置列宽
+	f.SetColWidth("Sheet1", "A", "A", 20)
+	f.SetColWidth("Sheet1", "B", "C", 15)
+	f.SetColWidth("Sheet1", "D", "F", 12)
+	f.SetColWidth("Sheet1", "F", "G", 10)
+	f.SetColWidth("Sheet1", "G", "I", 10)
+	f.SetColWidth("Sheet1", "I", "J", 12)
+	f.SetColWidth("Sheet1", "J", "L", 15)
+
+	line := 2
+
+	// 循环写入数据
+	for _, v := range list {
+		line++
+
+		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), v[models.FieldProductName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v[models.FieldEnterpriseName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v[models.FieldSpecName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), v[models.FieldDosageFormName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", line), v[models.FieldUnitName])
+		f.SetCellValue("Sheet1", fmt.Sprintf("F%d", line), v["balance"])
+		f.SetCellValue("Sheet1", fmt.Sprintf("G%d", line), utils.ToFloat64(v["purchase_unit_price"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("H%d", line), utils.ToFloat64(v["sales_unit_price"]))
+		f.SetCellValue("Sheet1", fmt.Sprintf("I%d", line), v["batch_number"])
+		f.SetCellValue("Sheet1", fmt.Sprintf("J%d", line), v[models.FieldExpiryDate])
+		f.SetCellValue("Sheet1", fmt.Sprintf("K%d", line), v["approval_number"])
+		f.SetCellValue("Sheet1", fmt.Sprintf("L%d", line), v["qualification_number"])
+
+	}
+	Style1, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 12, Family: "宋体", Bold: true},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A2", "L2", Style1)
+	Style2, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 12, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+			Border: []excelize.Border{
+				{Type: "left", Color: "000000", Style: 1},
+				{Type: "top", Color: "000000", Style: 1},
+				{Type: "bottom", Color: "000000", Style: 1},
+				{Type: "right", Color: "000000", Style: 1},
+			},
+		})
+	f.SetCellStyle("Sheet1", "A3", fmt.Sprintf("L%d", line), Style2)
+
+	filename := "库存信息表" + time.Now().Format("20060102150405") + ".xlsx"
+	// 保存文件
+	if err := f.SaveAs("ofile/" + filename); err != nil {
+		fmt.Println(err)
+	}
+	defer func() {
+		os.Remove("ofile/" + filename)
+	}()
+
+	// 返回生成的 Excel 文件
+	c.Ctx.Output.Download("ofile/" + filename)
+}
+
+// StockStat 库存统计
+// @Summary 库存查询-每个商品数量(不区别生产企业)
+// @Description 库存查询
+// @Tags 库存
+// @Param body body dto.StockStatListReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock/stat [post]
+// @Security Bearer
+func (c StockTemplateController) StockStat() {
+	s := services.StockTemplate{}
+	reqData := dto.StockStatReq{}
+	if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+		c.Error(global.ParseFormErr, err, err.Error())
+		return
+	}
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, cnt, err := s.StockStat(&reqData, deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(cnt), 0, 0, "查询成功")
+}
+
+// StockHomeStat 首页出入库信息统计
+// @Summary 首页出入库信息统计
+// @Description 首页出入库信息统计
+// @Tags 库存
+// @Param body body dto.StockStatListReq true "body"
+// @Success 200 {object} response.Page "{"code": 200, "data": [...]}"
+// @Router /stock/stat [post]
+// @Security Bearer
+func (c StockTemplateController) StockHomeStat() {
+	s := services.StockTemplate{}
+
+	deptId := beegouser.GetDeptId(c.Ctx)
+	list, cnt, err := s.StockHomeStat(deptId)
+	if err != nil {
+		c.Error(500, err, err.Error())
+		return
+	}
+	c.PageOK(list, int(cnt), 0, 0, "查询成功")
+}
+
+// 列表 - 接口
+func (c *StockTemplateController) InventoryPdf() {
+	//s := services.StockTemplate{}
+	//reqData := dto.StockTemplateInventoryPageReq{}
+	//if err := c.ParseAndValidate(&c.Ctx.Input.RequestBody, &reqData); err != nil {
+	//	c.Error(global.ParseFormErr, err, err.Error())
+	//	return
+	//}
+	//
+	//deptId := beegouser.GetDeptId(c.Ctx)
+	//list, _, err := s.StockTemplateInventoryList(&reqData, deptId)
+	//if err != nil {
+	//	c.Error(500, err, err.Error())
+	//	return
+	//}
+	pdf := gopdf.GoPdf{}
+	pdf.Start(gopdf.Config{PageSize: gopdf.Rect{W: 841.89, H: 595.28}})
+
+	// 添加页面
+	pdf.AddPage()
+
+	// 设置字体
+	pdf.AddTTFFont("simsun", "static/fonts/MiSans-Medium.ttf")
+	pdf.SetFont("simsun", "", 20)
+	textw, _ := pdf.MeasureTextWidth("生物制品收发登记")
+	pdf.SetX((841 / 2) - (textw / 2))
+	pdf.SetY(40)
+	pdf.Text("生物制品收发登记")
+	// 添加文本内容
+	pdf.SetFont("simsun", "", 12)
+	pdf.SetX(500)
+	pdf.SetY(70)
+	pdf.Text("制品名称:脊灰疫苗")
+	pdf.SetX(500)
+	pdf.SetY(90)
+	pdf.Text("剂型: 规格:1g/粒/剂 单位:")
+
+	pdf.AddPage()
+	textw, _ = pdf.MeasureTextWidth("生物制品收发登记2")
+	pdf.SetFont("simsun", "", 20)
+
+	pdf.SetX((841 / 2) - (textw / 2))
+	pdf.SetY(40)
+	pdf.Text("生物制品收发登记2")
+	// 添加文本内容
+	pdf.SetFont("simsun", "", 12)
+	pdf.SetX(500)
+	pdf.SetY(70)
+	pdf.Text("制品名称:脊灰疫苗")
+	pdf.SetX(500)
+	pdf.SetY(90)
+	pdf.Text("剂型: 规格:1g/粒/剂 单位:")
+	// 保存 PDF 文件
+	pdf.WritePdf("生物制品收发登记.pdf")
+
+	// 返回生成的 PDF 文件
+	c.Ctx.Output.Download("生物制品收发登记.pdf")
+
+	//var err error
+	//pdf := &gopdf.GoPdf{}
+	//
+	//pdf.Start(gopdf.Config{PageSize: gopdf.Rect{W: 841.89, H: 595.28}}) //595.28, 841.89 = A4
+	//
+	//err = pdf.AddTTFFont("simsun", "static/fonts/三极行楷简体-粗.ttf")
+	//if err != nil {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+	//	c.ServeJSON()
+	//	return
+	//}
+	//err = pdf.SetFont("simsun", "", 24)
+	//if err != nil {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+	//	c.ServeJSON()
+	//	return
+	//}
+	//
+	//pdf.SetGrayFill(0.5)
+	//
+	//pdf.SetMargins(0, 20, 0, 20)
+	//pdf.AddPage()
+	//
+	//company, _ := Account.Read_Company_ById(c.T_pid)
+	//
+	//textw, _ := pdf.MeasureTextWidth(company.T_name)
+	//pdf.SetX((595 / 2) - (textw / 2))
+	//pdf.SetY(40)
+	//pdf.Text(company.T_name)
+	//
+	//// 线
+	//pdf.SetLineWidth(2)
+	//pdf.SetLineType("dashed")
+	//pdf.Line(10, 60, 585, 60)
+	//
+	//err = pdf.AddTTFFont("wts", "static/fonts/MiSans-Medium.ttf")
+	//if err != nil {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+	//	c.ServeJSON()
+	//	return
+	//}
+	//err = pdf.SetFont("wts", "", 12)
+	//if err != nil {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+	//	c.ServeJSON()
+	//	return
+	//}
+	//
+	//var s_time, e_time string
+	//if len(DeviceSensor_data) > 0 {
+	//	s_time = fmt.Sprintf("%s", DeviceSensor_data[0].T_time)
+	//	e_time = fmt.Sprintf("%s", DeviceSensor_data[len(DeviceSensor_data)-1].T_time)
+	//}
+	////fmt.Sprintf(" %.1f ", v.T_t)
+	//lib.RectFillColor(pdf, "历史数据["+e_time+" / "+s_time+"]", 14, 22, 80, 550, 40, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//
+	//lib.RectFillColor(pdf, "序号", 12, 22, 120, 30, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//lib.RectFillColor(pdf, "传感器名称", 12, 52, 120, 100, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//lib.RectFillColor(pdf, "温度℃", 12, 152, 120, 50, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//
+	//lib.RectFillColor(pdf, "湿度%", 12, 202, 120, 50, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//
+	//lib.RectFillColor(pdf, "温度范围", 12, 252, 120, 80, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//lib.RectFillColor(pdf, "湿度范围", 12, 332, 120, 80, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//lib.RectFillColor(pdf, "记录时间", 12, 412, 120, 120, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//lib.RectFillColor(pdf, "备注", 12, 532, 120, 40, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//
+	//var y float64 = 140
+	//
+	//err = pdf.SetFont("wts", "", 10)
+	//if err != nil {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+	//	c.ServeJSON()
+	//	return
+	//}
+	//for i, v := range DeviceSensor_data {
+	//	text := fmt.Sprintf(" %d ", i+1)
+	//	var textH float64 = 25 // if text height is 25px.
+	//	pdf.SetNewY(y, textH)
+	//	y = pdf.GetY()
+	//	//pdf.SetX(x) // must after pdf.SetNewY() called.
+	//	//err = pdf.Text(text)
+	//	//if err != nil {
+	//	//	log.Fatalln(err)
+	//	//}
+	//
+	//	T_t := fmt.Sprintf(" %.1f ", v.T_t)
+	//	T_rh := fmt.Sprintf(" %.1f ", v.T_rh)
+	//	T_Tlu := fmt.Sprintf(" %.1f ~ %.1f ", v.T_tl, v.T_tu)
+	//	T_Rlu := fmt.Sprintf(" %.1f ~ %.1f ", v.T_rhl, v.T_rhu)
+	//	T_time := fmt.Sprintf("%s", v.T_time)
+	//
+	//	if v.T_ist == 1 {
+	//		lib.RectFillColor(pdf, T_t, 10, 152, y, 50, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//		lib.RectFillColor(pdf, T_Tlu, 10, 252, y, 80, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//	} else {
+	//		lib.RectFillColor(pdf, "", 10, 152, y, 50, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//		lib.RectFillColor(pdf, "", 10, 252, y, 80, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//	}
+	//	if v.T_ish == 1 {
+	//		lib.RectFillColor(pdf, T_rh, 10, 202, y, 50, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//		lib.RectFillColor(pdf, T_Rlu, 10, 332, y, 80, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//	} else {
+	//		lib.RectFillColor(pdf, "", 10, 202, y, 50, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//		lib.RectFillColor(pdf, "", 10, 332, y, 80, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//	}
+	//
+	//	lib.RectFillColor(pdf, text, 10, 22, y, 30, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//	lib.RectFillColor(pdf, v.T_name, 10, 52, y, 100, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//
+	//	lib.RectFillColor(pdf, T_time, 10, 412, y, 120, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//	lib.RectFillColor(pdf, v.T_remark, 10, 532, y, 40, 20, 255, 255, 255, lib.AlignCenter, lib.ValignMiddle)
+	//	y += 20
+	//}
+	//timeStr := "ofile/" + time.Now().Format("20060102150405") + ".pdf"
+	//
+	//err = pdf.WritePdf(timeStr)
+	//if err != nil {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+	//	c.ServeJSON()
+	//	return
+	//}
+	//
+	//// 上传 OSS
+	//url, is := NatsServer.Qiniu_UploadFile(lib.GetCurrentDirectory()+"/"+timeStr, timeStr)
+	//if !is {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: "oss!"}
+	//	c.ServeJSON()
+	//	return
+	//}
+	////删除目录
+	//err = os.Remove(timeStr)
+	//if err != nil {
+	//	fmt.Println(err)
+	//}
+	//
+	//c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: url}
+	//c.ServeJSON()
+	//return
+
+}

+ 1 - 1
controllers/unit.go

@@ -142,8 +142,8 @@ func (c UnitController) Delete() {
 		return
 	}
 
-	// TODO 判断出入库数据
 	reqData.SetUpdateBy(beegouser.GetUserId(c.Ctx))
+	reqData.SetDeptId(beegouser.GetDeptId(c.Ctx))
 	err := s.Remove(&reqData)
 	if err != nil {
 		c.Error(500, err, err.Error())

+ 594 - 7
docs/docs.go

@@ -454,6 +454,188 @@ const docTemplate = `{
                 }
             }
         },
+        "/medicine-template/add": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "添加模版字段",
+                "consumes": [
+                    "application/json"
+                ],
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "添加模版字段",
+                "parameters": [
+                    {
+                        "description": "data",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.MedicineTemplateInsertReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/columns": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "获取品名列表",
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "获取品名列表",
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Page"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "list": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/models.MedicineTemplate"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/delete": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "删除模版字段",
+                "consumes": [
+                    "application/json"
+                ],
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "删除模版字段",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.MedicineTemplateDeleteReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/edit": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "修改模版字段",
+                "consumes": [
+                    "application/json"
+                ],
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "修改模版字段",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.MedicineTemplateUpdateReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/list": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "获取品名列表",
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "获取品名列表",
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Page"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "list": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/models.MedicineTemplate"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/product/add": {
             "post": {
                 "security": [
@@ -856,6 +1038,138 @@ const docTemplate = `{
                 }
             }
         },
+        "/stock-template/in": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "入库",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "入库",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateInInsertReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/stock-template/in/list": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "入库列表",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "入库列表",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateInPageReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Page"
+                        }
+                    }
+                }
+            }
+        },
+        "/stock-template/out": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "出库",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "出库",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateOutInsertReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/stock-template/out/list": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "出库列表",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "出库列表",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateOutPageReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Page"
+                        }
+                    }
+                }
+            }
+        },
         "/unit/add": {
             "post": {
                 "security": [
@@ -1179,6 +1493,80 @@ const docTemplate = `{
                 }
             }
         },
+        "dto.MedicineTemplateDeleteReq": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.MedicineTemplateInitializeReq": {
+            "type": "object",
+            "properties": {
+                "deptId": {
+                    "description": "更新者",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.MedicineTemplateInsertReq": {
+            "type": "object",
+            "properties": {
+                "name": {
+                    "description": "标签名称",
+                    "type": "string"
+                },
+                "show": {
+                    "description": "1-显示 2-隐藏",
+                    "type": "integer"
+                },
+                "sort": {
+                    "description": "排序",
+                    "type": "integer"
+                },
+                "text": {
+                    "description": "描述",
+                    "type": "string"
+                },
+                "type": {
+                    "description": "数据类型",
+                    "type": "integer"
+                },
+                "width": {
+                    "description": "宽度",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.MedicineTemplateUpdateReq": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "description": "标签名称",
+                    "type": "string"
+                },
+                "show": {
+                    "description": "1-显示 2-隐藏",
+                    "type": "integer"
+                },
+                "sort": {
+                    "description": "排序",
+                    "type": "integer"
+                },
+                "text": {
+                    "description": "描述",
+                    "type": "string"
+                },
+                "width": {
+                    "description": "宽度",
+                    "type": "integer"
+                }
+            }
+        },
         "dto.ProductDeleteReq": {
             "type": "object",
             "properties": {
@@ -1316,6 +1704,136 @@ const docTemplate = `{
                 }
             }
         },
+        "dto.StockTemplateInInsertReq": {
+            "type": "object",
+            "properties": {
+                "date": {
+                    "description": "入库日期",
+                    "type": "string"
+                },
+                "forwardingUnit": {
+                    "description": "发货单位",
+                    "type": "string"
+                },
+                "medicineInfo": {
+                    "description": "药品信息",
+                    "type": "object",
+                    "additionalProperties": true
+                },
+                "operator": {
+                    "description": "经办人",
+                    "type": "string"
+                },
+                "quantity": {
+                    "description": "数量",
+                    "type": "integer"
+                },
+                "unitPrice": {
+                    "description": "购入单价",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.StockTemplateInPageReq": {
+            "type": "object",
+            "properties": {
+                "batchNumber": {
+                    "description": "疫苗批号",
+                    "type": "string"
+                },
+                "enterpriseId": {
+                    "description": "生产企业id",
+                    "type": "integer"
+                },
+                "expiryEndDate": {
+                    "description": "疫苗效期结束时间",
+                    "type": "string"
+                },
+                "expiryStartDate": {
+                    "description": "疫苗效期开始时间",
+                    "type": "string"
+                },
+                "page": {
+                    "description": "页数",
+                    "type": "integer",
+                    "example": 1
+                },
+                "pageSize": {
+                    "description": "每页条数",
+                    "type": "integer",
+                    "example": 10
+                },
+                "productId": {
+                    "description": "药品名称id",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.StockTemplateOutInsertReq": {
+            "type": "object",
+            "properties": {
+                "date": {
+                    "description": "入库日期",
+                    "type": "string"
+                },
+                "medicineInfo": {
+                    "description": "药品信息",
+                    "type": "object",
+                    "additionalProperties": true
+                },
+                "operator": {
+                    "description": "经办人",
+                    "type": "string"
+                },
+                "quantity": {
+                    "description": "数量",
+                    "type": "integer"
+                },
+                "receivingUnit": {
+                    "description": "收货单位",
+                    "type": "string"
+                },
+                "unitPrice": {
+                    "description": "购入单价",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.StockTemplateOutPageReq": {
+            "type": "object",
+            "properties": {
+                "batchNumber": {
+                    "description": "疫苗批号",
+                    "type": "string"
+                },
+                "enterpriseId": {
+                    "description": "生产企业id",
+                    "type": "integer"
+                },
+                "expiryEndDate": {
+                    "description": "疫苗效期结束时间",
+                    "type": "string"
+                },
+                "expiryStartDate": {
+                    "description": "疫苗效期开始时间",
+                    "type": "string"
+                },
+                "page": {
+                    "description": "页数",
+                    "type": "integer",
+                    "example": 1
+                },
+                "pageSize": {
+                    "description": "每页条数",
+                    "type": "integer",
+                    "example": 10
+                },
+                "productId": {
+                    "description": "药品名称id",
+                    "type": "integer"
+                }
+            }
+        },
         "dto.UnitDeleteReq": {
             "type": "object",
             "properties": {
@@ -1467,6 +1985,67 @@ const docTemplate = `{
                 }
             }
         },
+        "models.MedicineTemplate": {
+            "type": "object",
+            "properties": {
+                "createBy": {
+                    "description": "创建者",
+                    "type": "integer"
+                },
+                "createdAt": {
+                    "description": "创建时间",
+                    "type": "string"
+                },
+                "deptId": {
+                    "description": "更新者",
+                    "type": "integer"
+                },
+                "field_name": {
+                    "description": "英语名称",
+                    "type": "string"
+                },
+                "id": {
+                    "description": "主键编码",
+                    "type": "integer"
+                },
+                "name": {
+                    "description": "标签名称",
+                    "type": "string"
+                },
+                "show": {
+                    "description": "1-显示 2-隐藏",
+                    "type": "integer"
+                },
+                "sort": {
+                    "description": "排序",
+                    "type": "integer"
+                },
+                "state": {
+                    "description": "1-系统初始化 2-用户添加",
+                    "type": "integer"
+                },
+                "text": {
+                    "description": "描述",
+                    "type": "string"
+                },
+                "type": {
+                    "description": "数据类型",
+                    "type": "integer"
+                },
+                "updateBy": {
+                    "description": "更新者",
+                    "type": "integer"
+                },
+                "updatedAt": {
+                    "description": "最后更新时间",
+                    "type": "string"
+                },
+                "width": {
+                    "description": "宽度",
+                    "type": "integer"
+                }
+            }
+        },
         "models.Product": {
             "type": "object",
             "properties": {
@@ -1566,20 +2145,14 @@ const docTemplate = `{
                 }
             }
         },
-        "response.Page": {
+        "response.Data": {
             "type": "object",
             "properties": {
-                "code": {
-                    "type": "integer"
-                },
                 "count": {
                     "description": "总数",
                     "type": "integer"
                 },
                 "list": {},
-                "msg": {
-                    "type": "string"
-                },
                 "page": {
                     "description": "页码",
                     "type": "integer"
@@ -1587,6 +2160,20 @@ const docTemplate = `{
                 "pageSize": {
                     "description": "页条数",
                     "type": "integer"
+                }
+            }
+        },
+        "response.Page": {
+            "type": "object",
+            "properties": {
+                "code": {
+                    "type": "integer"
+                },
+                "data": {
+                    "$ref": "#/definitions/response.Data"
+                },
+                "msg": {
+                    "type": "string"
                 },
                 "requestId": {
                     "type": "string"

+ 594 - 7
docs/swagger.json

@@ -450,6 +450,188 @@
                 }
             }
         },
+        "/medicine-template/add": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "添加模版字段",
+                "consumes": [
+                    "application/json"
+                ],
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "添加模版字段",
+                "parameters": [
+                    {
+                        "description": "data",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.MedicineTemplateInsertReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/columns": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "获取品名列表",
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "获取品名列表",
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Page"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "list": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/models.MedicineTemplate"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/delete": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "删除模版字段",
+                "consumes": [
+                    "application/json"
+                ],
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "删除模版字段",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.MedicineTemplateDeleteReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/edit": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "修改模版字段",
+                "consumes": [
+                    "application/json"
+                ],
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "修改模版字段",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.MedicineTemplateUpdateReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/medicine-template/list": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "获取品名列表",
+                "tags": [
+                    "药品信息模板"
+                ],
+                "summary": "获取品名列表",
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Page"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "list": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/models.MedicineTemplate"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/product/add": {
             "post": {
                 "security": [
@@ -852,6 +1034,138 @@
                 }
             }
         },
+        "/stock-template/in": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "入库",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "入库",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateInInsertReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/stock-template/in/list": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "入库列表",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "入库列表",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateInPageReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Page"
+                        }
+                    }
+                }
+            }
+        },
+        "/stock-template/out": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "出库",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "出库",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateOutInsertReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/stock-template/out/list": {
+            "post": {
+                "security": [
+                    {
+                        "Bearer": []
+                    }
+                ],
+                "description": "出库列表",
+                "tags": [
+                    "库存"
+                ],
+                "summary": "出库列表",
+                "parameters": [
+                    {
+                        "description": "body",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/dto.StockTemplateOutPageReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"code\": 200, \"data\": [...]}",
+                        "schema": {
+                            "$ref": "#/definitions/response.Page"
+                        }
+                    }
+                }
+            }
+        },
         "/unit/add": {
             "post": {
                 "security": [
@@ -1175,6 +1489,80 @@
                 }
             }
         },
+        "dto.MedicineTemplateDeleteReq": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.MedicineTemplateInitializeReq": {
+            "type": "object",
+            "properties": {
+                "deptId": {
+                    "description": "更新者",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.MedicineTemplateInsertReq": {
+            "type": "object",
+            "properties": {
+                "name": {
+                    "description": "标签名称",
+                    "type": "string"
+                },
+                "show": {
+                    "description": "1-显示 2-隐藏",
+                    "type": "integer"
+                },
+                "sort": {
+                    "description": "排序",
+                    "type": "integer"
+                },
+                "text": {
+                    "description": "描述",
+                    "type": "string"
+                },
+                "type": {
+                    "description": "数据类型",
+                    "type": "integer"
+                },
+                "width": {
+                    "description": "宽度",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.MedicineTemplateUpdateReq": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "description": "标签名称",
+                    "type": "string"
+                },
+                "show": {
+                    "description": "1-显示 2-隐藏",
+                    "type": "integer"
+                },
+                "sort": {
+                    "description": "排序",
+                    "type": "integer"
+                },
+                "text": {
+                    "description": "描述",
+                    "type": "string"
+                },
+                "width": {
+                    "description": "宽度",
+                    "type": "integer"
+                }
+            }
+        },
         "dto.ProductDeleteReq": {
             "type": "object",
             "properties": {
@@ -1312,6 +1700,136 @@
                 }
             }
         },
+        "dto.StockTemplateInInsertReq": {
+            "type": "object",
+            "properties": {
+                "date": {
+                    "description": "入库日期",
+                    "type": "string"
+                },
+                "forwardingUnit": {
+                    "description": "发货单位",
+                    "type": "string"
+                },
+                "medicineInfo": {
+                    "description": "药品信息",
+                    "type": "object",
+                    "additionalProperties": true
+                },
+                "operator": {
+                    "description": "经办人",
+                    "type": "string"
+                },
+                "quantity": {
+                    "description": "数量",
+                    "type": "integer"
+                },
+                "unitPrice": {
+                    "description": "购入单价",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.StockTemplateInPageReq": {
+            "type": "object",
+            "properties": {
+                "batchNumber": {
+                    "description": "疫苗批号",
+                    "type": "string"
+                },
+                "enterpriseId": {
+                    "description": "生产企业id",
+                    "type": "integer"
+                },
+                "expiryEndDate": {
+                    "description": "疫苗效期结束时间",
+                    "type": "string"
+                },
+                "expiryStartDate": {
+                    "description": "疫苗效期开始时间",
+                    "type": "string"
+                },
+                "page": {
+                    "description": "页数",
+                    "type": "integer",
+                    "example": 1
+                },
+                "pageSize": {
+                    "description": "每页条数",
+                    "type": "integer",
+                    "example": 10
+                },
+                "productId": {
+                    "description": "药品名称id",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.StockTemplateOutInsertReq": {
+            "type": "object",
+            "properties": {
+                "date": {
+                    "description": "入库日期",
+                    "type": "string"
+                },
+                "medicineInfo": {
+                    "description": "药品信息",
+                    "type": "object",
+                    "additionalProperties": true
+                },
+                "operator": {
+                    "description": "经办人",
+                    "type": "string"
+                },
+                "quantity": {
+                    "description": "数量",
+                    "type": "integer"
+                },
+                "receivingUnit": {
+                    "description": "收货单位",
+                    "type": "string"
+                },
+                "unitPrice": {
+                    "description": "购入单价",
+                    "type": "integer"
+                }
+            }
+        },
+        "dto.StockTemplateOutPageReq": {
+            "type": "object",
+            "properties": {
+                "batchNumber": {
+                    "description": "疫苗批号",
+                    "type": "string"
+                },
+                "enterpriseId": {
+                    "description": "生产企业id",
+                    "type": "integer"
+                },
+                "expiryEndDate": {
+                    "description": "疫苗效期结束时间",
+                    "type": "string"
+                },
+                "expiryStartDate": {
+                    "description": "疫苗效期开始时间",
+                    "type": "string"
+                },
+                "page": {
+                    "description": "页数",
+                    "type": "integer",
+                    "example": 1
+                },
+                "pageSize": {
+                    "description": "每页条数",
+                    "type": "integer",
+                    "example": 10
+                },
+                "productId": {
+                    "description": "药品名称id",
+                    "type": "integer"
+                }
+            }
+        },
         "dto.UnitDeleteReq": {
             "type": "object",
             "properties": {
@@ -1463,6 +1981,67 @@
                 }
             }
         },
+        "models.MedicineTemplate": {
+            "type": "object",
+            "properties": {
+                "createBy": {
+                    "description": "创建者",
+                    "type": "integer"
+                },
+                "createdAt": {
+                    "description": "创建时间",
+                    "type": "string"
+                },
+                "deptId": {
+                    "description": "更新者",
+                    "type": "integer"
+                },
+                "field_name": {
+                    "description": "英语名称",
+                    "type": "string"
+                },
+                "id": {
+                    "description": "主键编码",
+                    "type": "integer"
+                },
+                "name": {
+                    "description": "标签名称",
+                    "type": "string"
+                },
+                "show": {
+                    "description": "1-显示 2-隐藏",
+                    "type": "integer"
+                },
+                "sort": {
+                    "description": "排序",
+                    "type": "integer"
+                },
+                "state": {
+                    "description": "1-系统初始化 2-用户添加",
+                    "type": "integer"
+                },
+                "text": {
+                    "description": "描述",
+                    "type": "string"
+                },
+                "type": {
+                    "description": "数据类型",
+                    "type": "integer"
+                },
+                "updateBy": {
+                    "description": "更新者",
+                    "type": "integer"
+                },
+                "updatedAt": {
+                    "description": "最后更新时间",
+                    "type": "string"
+                },
+                "width": {
+                    "description": "宽度",
+                    "type": "integer"
+                }
+            }
+        },
         "models.Product": {
             "type": "object",
             "properties": {
@@ -1562,20 +2141,14 @@
                 }
             }
         },
-        "response.Page": {
+        "response.Data": {
             "type": "object",
             "properties": {
-                "code": {
-                    "type": "integer"
-                },
                 "count": {
                     "description": "总数",
                     "type": "integer"
                 },
                 "list": {},
-                "msg": {
-                    "type": "string"
-                },
                 "page": {
                     "description": "页码",
                     "type": "integer"
@@ -1583,6 +2156,20 @@
                 "pageSize": {
                     "description": "页条数",
                     "type": "integer"
+                }
+            }
+        },
+        "response.Page": {
+            "type": "object",
+            "properties": {
+                "code": {
+                    "type": "integer"
+                },
+                "data": {
+                    "$ref": "#/definitions/response.Data"
+                },
+                "msg": {
+                    "type": "string"
                 },
                 "requestId": {
                     "type": "string"

+ 389 - 5
docs/swagger.yaml

@@ -84,6 +84,58 @@ definitions:
         example: 生产企业
         type: string
     type: object
+  dto.MedicineTemplateDeleteReq:
+    properties:
+      id:
+        type: integer
+    type: object
+  dto.MedicineTemplateInitializeReq:
+    properties:
+      deptId:
+        description: 更新者
+        type: integer
+    type: object
+  dto.MedicineTemplateInsertReq:
+    properties:
+      name:
+        description: 标签名称
+        type: string
+      show:
+        description: 1-显示 2-隐藏
+        type: integer
+      sort:
+        description: 排序
+        type: integer
+      text:
+        description: 描述
+        type: string
+      type:
+        description: 数据类型
+        type: integer
+      width:
+        description: 宽度
+        type: integer
+    type: object
+  dto.MedicineTemplateUpdateReq:
+    properties:
+      id:
+        type: integer
+      name:
+        description: 标签名称
+        type: string
+      show:
+        description: 1-显示 2-隐藏
+        type: integer
+      sort:
+        description: 排序
+        type: integer
+      text:
+        description: 描述
+        type: string
+      width:
+        description: 宽度
+        type: integer
+    type: object
   dto.ProductDeleteReq:
     properties:
       id:
@@ -180,6 +232,102 @@ definitions:
         example: 规格
         type: string
     type: object
+  dto.StockTemplateInInsertReq:
+    properties:
+      date:
+        description: 入库日期
+        type: string
+      forwardingUnit:
+        description: 发货单位
+        type: string
+      medicineInfo:
+        additionalProperties: true
+        description: 药品信息
+        type: object
+      operator:
+        description: 经办人
+        type: string
+      quantity:
+        description: 数量
+        type: integer
+      unitPrice:
+        description: 购入单价
+        type: integer
+    type: object
+  dto.StockTemplateInPageReq:
+    properties:
+      batchNumber:
+        description: 疫苗批号
+        type: string
+      enterpriseId:
+        description: 生产企业id
+        type: integer
+      expiryEndDate:
+        description: 疫苗效期结束时间
+        type: string
+      expiryStartDate:
+        description: 疫苗效期开始时间
+        type: string
+      page:
+        description: 页数
+        example: 1
+        type: integer
+      pageSize:
+        description: 每页条数
+        example: 10
+        type: integer
+      productId:
+        description: 药品名称id
+        type: integer
+    type: object
+  dto.StockTemplateOutInsertReq:
+    properties:
+      date:
+        description: 入库日期
+        type: string
+      medicineInfo:
+        additionalProperties: true
+        description: 药品信息
+        type: object
+      operator:
+        description: 经办人
+        type: string
+      quantity:
+        description: 数量
+        type: integer
+      receivingUnit:
+        description: 收货单位
+        type: string
+      unitPrice:
+        description: 购入单价
+        type: integer
+    type: object
+  dto.StockTemplateOutPageReq:
+    properties:
+      batchNumber:
+        description: 疫苗批号
+        type: string
+      enterpriseId:
+        description: 生产企业id
+        type: integer
+      expiryEndDate:
+        description: 疫苗效期结束时间
+        type: string
+      expiryStartDate:
+        description: 疫苗效期开始时间
+        type: string
+      page:
+        description: 页数
+        example: 1
+        type: integer
+      pageSize:
+        description: 每页条数
+        example: 10
+        type: integer
+      productId:
+        description: 药品名称id
+        type: integer
+    type: object
   dto.UnitDeleteReq:
     properties:
       id:
@@ -289,6 +437,51 @@ definitions:
         description: 最后更新时间
         type: string
     type: object
+  models.MedicineTemplate:
+    properties:
+      createBy:
+        description: 创建者
+        type: integer
+      createdAt:
+        description: 创建时间
+        type: string
+      deptId:
+        description: 更新者
+        type: integer
+      field_name:
+        description: 英语名称
+        type: string
+      id:
+        description: 主键编码
+        type: integer
+      name:
+        description: 标签名称
+        type: string
+      show:
+        description: 1-显示 2-隐藏
+        type: integer
+      sort:
+        description: 排序
+        type: integer
+      state:
+        description: 1-系统初始化 2-用户添加
+        type: integer
+      text:
+        description: 描述
+        type: string
+      type:
+        description: 数据类型
+        type: integer
+      updateBy:
+        description: 更新者
+        type: integer
+      updatedAt:
+        description: 最后更新时间
+        type: string
+      width:
+        description: 宽度
+        type: integer
+    type: object
   models.Product:
     properties:
       createBy:
@@ -361,22 +554,27 @@ definitions:
         description: 最后更新时间
         type: string
     type: object
-  response.Page:
+  response.Data:
     properties:
-      code:
-        type: integer
       count:
         description: 总数
         type: integer
       list: {}
-      msg:
-        type: string
       page:
         description: 页码
         type: integer
       pageSize:
         description: 页条数
         type: integer
+    type: object
+  response.Page:
+    properties:
+      code:
+        type: integer
+      data:
+        $ref: '#/definitions/response.Data'
+      msg:
+        type: string
       requestId:
         type: string
       status:
@@ -659,6 +857,112 @@ paths:
       summary: 获取生产企业列表
       tags:
       - 生产企业
+  /medicine-template/add:
+    post:
+      consumes:
+      - application/json
+      description: 添加模版字段
+      parameters:
+      - description: data
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/dto.MedicineTemplateInsertReq'
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            $ref: '#/definitions/response.Response'
+      security:
+      - Bearer: []
+      summary: 添加模版字段
+      tags:
+      - 药品信息模板
+  /medicine-template/columns:
+    post:
+      description: 获取品名列表
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            allOf:
+            - $ref: '#/definitions/response.Page'
+            - properties:
+                list:
+                  items:
+                    $ref: '#/definitions/models.MedicineTemplate'
+                  type: array
+              type: object
+      security:
+      - Bearer: []
+      summary: 获取品名列表
+      tags:
+      - 药品信息模板
+  /medicine-template/delete:
+    post:
+      consumes:
+      - application/json
+      description: 删除模版字段
+      parameters:
+      - description: body
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/dto.MedicineTemplateDeleteReq'
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            $ref: '#/definitions/response.Response'
+      security:
+      - Bearer: []
+      summary: 删除模版字段
+      tags:
+      - 药品信息模板
+  /medicine-template/edit:
+    post:
+      consumes:
+      - application/json
+      description: 修改模版字段
+      parameters:
+      - description: body
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/dto.MedicineTemplateUpdateReq'
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            $ref: '#/definitions/response.Response'
+      security:
+      - Bearer: []
+      summary: 修改模版字段
+      tags:
+      - 药品信息模板
+  /medicine-template/list:
+    post:
+      description: 获取品名列表
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            allOf:
+            - $ref: '#/definitions/response.Page'
+            - properties:
+                list:
+                  items:
+                    $ref: '#/definitions/models.MedicineTemplate'
+                  type: array
+              type: object
+      security:
+      - Bearer: []
+      summary: 获取品名列表
+      tags:
+      - 药品信息模板
   /product/add:
     post:
       consumes:
@@ -895,6 +1199,86 @@ paths:
       summary: 获取规格列表
       tags:
       - 规格
+  /stock-template/in:
+    post:
+      description: 入库
+      parameters:
+      - description: body
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/dto.StockTemplateInInsertReq'
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            $ref: '#/definitions/response.Response'
+      security:
+      - Bearer: []
+      summary: 入库
+      tags:
+      - 库存
+  /stock-template/in/list:
+    post:
+      description: 入库列表
+      parameters:
+      - description: body
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/dto.StockTemplateInPageReq'
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            $ref: '#/definitions/response.Page'
+      security:
+      - Bearer: []
+      summary: 入库列表
+      tags:
+      - 库存
+  /stock-template/out:
+    post:
+      description: 出库
+      parameters:
+      - description: body
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/dto.StockTemplateOutInsertReq'
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            $ref: '#/definitions/response.Response'
+      security:
+      - Bearer: []
+      summary: 出库
+      tags:
+      - 库存
+  /stock-template/out/list:
+    post:
+      description: 出库列表
+      parameters:
+      - description: body
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/dto.StockTemplateOutPageReq'
+      responses:
+        "200":
+          description: '{"code": 200, "data": [...]}'
+          schema:
+            $ref: '#/definitions/response.Page'
+      security:
+      - Bearer: []
+      summary: 出库列表
+      tags:
+      - 库存
   /unit/add:
     post:
       consumes:

+ 1 - 1
dto/dosage_form.go

@@ -9,7 +9,7 @@ import (
 // DosageFormPageReq 列表或者搜索使用结构体
 type DosageFormPageReq struct {
 	dto.Pagination `search:"-"`
-	Name           string `json:"name" search:"type:contains;column:name;table:product" example:""` // 名称
+	Name           string `json:"name" search:"type:contains;column:name;table:dosage_form" example:""` // 名称
 }
 
 func (m *DosageFormPageReq) GetNeedSearch() interface{} {

+ 2 - 2
dto/enterprise.go

@@ -9,7 +9,7 @@ import (
 // EnterprisePageReq 列表或者搜索使用结构体
 type EnterprisePageReq struct {
 	dto.Pagination `search:"-"`
-	Name           string `json:"name" search:"type:contains;column:name;table:product" example:""` // 名称
+	Name           string `json:"name" search:"type:contains;column:name;table:enterprise" example:""` // 名称
 }
 
 func (m *EnterprisePageReq) GetNeedSearch() interface{} {
@@ -35,6 +35,7 @@ func (s *EnterpriseInsertReq) Generate(model *models.Enterprise) {
 	if s.ControlBy.CreateBy != 0 {
 		model.CreateBy = s.CreateBy
 	}
+	model.DeptId = s.DeptId
 }
 
 // GetId 获取数据对应的ID
@@ -62,7 +63,6 @@ func (s *EnterpriseUpdateReq) Generate(model *models.Enterprise) {
 	if s.ControlBy.CreateBy != 0 {
 		model.CreateBy = s.CreateBy
 	}
-	model.DeptId = s.DeptId
 }
 
 func (s *EnterpriseUpdateReq) GetId() interface{} {

+ 8 - 0
dto/medicine.go

@@ -0,0 +1,8 @@
+package dto
+
+// MedicineBatchNumberReq 使用的结构体
+type MedicineBatchNumberReq struct {
+	ProductID    int    `json:"productId"`    // 药品名称id
+	EnterpriseID int    `json:"enterpriseId"` // 生产企业id
+	BatchNumber  string `json:"batchNumber"`  // 批号
+}

+ 102 - 0
dto/medicine_template.go

@@ -0,0 +1,102 @@
+package dto
+
+import (
+	common "Medical_ERP/common/model"
+	"Medical_ERP/models"
+)
+
+// MedicineTemplateInitializeReq 增使用的结构体
+type MedicineTemplateInitializeReq struct {
+	DeptId int `json:"deptId" gorm:"index;comment:部门id"` // 更新者
+}
+
+// MedicineTemplateInsertReq 增使用的结构体
+type MedicineTemplateInsertReq struct {
+	Id               int    `json:"id" comment:"id" swaggerignore:"true"`
+	Type             int    `json:"type"`  // 数据类型
+	Name             string `json:"name"`  // 标签名称
+	Text             string `json:"text"`  // 描述
+	Sort             int    `json:"sort"`  // 排序
+	Width            int    `json:"width"` // 宽度
+	Show             int    `json:"show"`  // 1-显示 2-隐藏
+	common.ControlBy `swaggerignore:"true"`
+}
+
+func (s *MedicineTemplateInsertReq) Generate(model *models.MedicineTemplate) {
+	model.Type = s.Type
+	model.Name = s.Name
+	model.Text = s.Text
+	model.Sort = s.Sort
+	model.Width = s.Width
+	model.Show = s.Show
+	if s.ControlBy.UpdateBy != 0 {
+		model.UpdateBy = s.UpdateBy
+	}
+	if s.ControlBy.CreateBy != 0 {
+		model.CreateBy = s.CreateBy
+	}
+	model.DeptId = s.DeptId
+}
+
+// GetId 获取数据对应的ID
+func (s *MedicineTemplateInsertReq) GetId() interface{} {
+	return s.Id
+}
+
+// MedicineTemplateUpdateReq 增使用的结构体
+type MedicineTemplateUpdateReq struct {
+	Id               int    `json:"id" comment:"id"`
+	Name             string `json:"name"`  // 标签名称
+	Text             string `json:"text"`  // 描述
+	Sort             int    `json:"sort"`  // 排序
+	Width            int    `json:"width"` // 宽度
+	Show             int    `json:"show"`  // 1-显示 2-隐藏
+	common.ControlBy `swaggerignore:"true"`
+}
+
+func (s *MedicineTemplateUpdateReq) Generate(model *models.MedicineTemplate) {
+	model.Name = s.Name
+	model.Text = s.Text
+	model.Sort = s.Sort
+	model.Width = s.Width
+	model.Show = s.Show
+	if s.ControlBy.UpdateBy != 0 {
+		model.UpdateBy = s.UpdateBy
+	}
+	if s.ControlBy.CreateBy != 0 {
+		model.CreateBy = s.CreateBy
+	}
+}
+
+// GetId 获取数据对应的ID
+func (s *MedicineTemplateUpdateReq) GetId() interface{} {
+	return s.Id
+}
+
+// MedicineTemplateDeleteReq 增使用的结构体
+type MedicineTemplateDeleteReq struct {
+	Id               int `json:"id" comment:"id"`
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *MedicineTemplateDeleteReq) GetId() interface{} {
+	return s.Id
+}
+
+type MedicineColumnsRes struct {
+	Label string `json:"label"` // 标签名称
+	Prop  string `json:"prop" ` // 英语名称
+	Width int    `json:"width"` // 宽度
+}
+type MedicineTemplateForStockRes struct {
+	Type      int         `json:"type"`       // 数据类型
+	Name      string      `json:"name"`       // 标签名称
+	FieldName string      `json:"field_name"` // 英语名称
+	Text      string      `json:"text"`       // 描述
+	Sort      int         `json:"sort"`       // 排序
+	Width     int         `json:"width"`      // 宽度
+	Show      int         `json:"show"`       // 1-显示 2-隐藏
+	State     int         `json:"state"`      // 1-系统初始化 2-用户添加
+	List      interface{} `json:"list"`
+}

+ 35 - 0
dto/sales.go

@@ -0,0 +1,35 @@
+package dto
+
+import "Medical_ERP/common/dto"
+
+// SalesPageReq 列表或者搜索使用结构体
+type SalesPageReq struct {
+	dto.Pagination `search:"-"`
+	ProductID      int    `json:"productId"`     // 药品名称id
+	EnterpriseID   int    `json:"enterpriseId"`  // 生产企业id
+	StartDate      string `json:"startDate"`     // 出库开始时间
+	EndDate        string `json:"endDate"`       // 出库结束时间
+	BatchNumber    string `json:"batchNumber"`   // 疫苗批号
+	ReceivingUnit  string `json:"receivingUnit"` // 收货单位
+}
+
+// SalesOrderPageReq 列表或者搜索使用结构体
+type SalesOrderPageReq struct {
+	dto.Pagination `search:"-"`
+	ProductID      int    `json:"productId"`     // 药品名称id
+	EnterpriseID   int    `json:"enterpriseId"`  // 生产企业id
+	Date           string `json:"date"`          // 下单日期
+	BatchNumber    string `json:"batchNumber"`   // 疫苗批号
+	ReceivingUnit  string `json:"receivingUnit"` // 收货单位
+}
+
+// SalesStockOutExcelReq 列表或者搜索使用结构体
+type SalesStockOutExcelReq struct {
+	Date          string `json:"date"  alias:"下单日期" valid:"Required;MinSize(1)"`          // 下单日期
+	ReceivingUnit string `json:"receivingUnit"  alias:"收货单位" valid:"Required;MinSize(1)"` // 收货单位
+	Drawer        string `json:"drawer"`                                                  // 开票员
+	Consigner     string `json:"consigner"`                                               // 发货人
+	Finance       string `json:"finance"`                                                 // 财务
+	Consignee     string `json:"consignee"`                                               // 收货人
+	Type          string `json:"type"`
+}

+ 3 - 3
dto/spec.go

@@ -9,7 +9,7 @@ import (
 // SpecPageReq 列表或者搜索使用结构体
 type SpecPageReq struct {
 	dto.Pagination `search:"-"`
-	Name           string `json:"name" search:"type:contains;column:name;table:product" example:""` // 名称
+	Name           string `json:"name" search:"type:contains;column:name;table:spec" example:""` // 名称
 }
 
 func (m *SpecPageReq) GetNeedSearch() interface{} {
@@ -20,7 +20,7 @@ func (m *SpecPageReq) GetNeedSearch() interface{} {
 type SpecInsertReq struct {
 	Id               int    `json:"id" comment:"id" swaggerignore:"true"`
 	Name             string `json:"name" example:"规格" valid:"MinSize(1)"`     //规格
-	Sort             int    `json:"sort" example:"0" swaggerignore:"true"`      //排序
+	Sort             int    `json:"sort" example:"0" swaggerignore:"true"`    //排序
 	Remark           string `json:"remark" example:"备注" swaggerignore:"true"` //备注
 	common.ControlBy `swaggerignore:"true"`
 }
@@ -47,7 +47,7 @@ func (s *SpecInsertReq) GetId() interface{} {
 type SpecUpdateReq struct {
 	Id               int    `json:"id"  example:"1"`
 	Name             string `json:"name"  example:"规格"`                         // 规格
-	Sort             int    `json:"sort" example:"0" swaggerignore:"true"`        //排序
+	Sort             int    `json:"sort" example:"0" swaggerignore:"true"`      //排序
 	Remark           string `json:"remark"   example:"备注" swaggerignore:"true"` // 备注
 	common.ControlBy `swaggerignore:"true"`
 }

+ 87 - 0
dto/stock.go

@@ -0,0 +1,87 @@
+package dto
+
+import (
+	"Medical_ERP/common/dto"
+	common "Medical_ERP/common/model"
+)
+
+// StockPageReq 列表或者搜索使用结构体
+type StockPageReq struct {
+	dto.Pagination `search:"-"`
+	Name           string `json:"name" search:"type:contains;column:name;table:product" example:""` // 名称
+}
+
+func (m *StockPageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+// StockInInsertReq 增使用的结构体
+type StockInInsertReq struct {
+	Id int `json:"id" comment:"id" swaggerignore:"true"`
+
+	ProductID           int    `json:"productId"`           // 药品名称id
+	EnterpriseID        int    `json:"enterpriseId"`        // 生产企业id
+	SpecID              int    `json:"specId"`              // 规格id
+	UnitID              int    `json:"unitId"`              // 单位id 非必填
+	DosageFormID        int    `json:"dosageFormId"`        // 剂型id 非必填
+	ApprovalNumber      string `json:"approvalNumber"`      // 批准文号
+	QualificationNumber string `json:"qualificationNumber"` // 批签发合格编号
+	ProductionDate      string `json:"productionDate"`      // 生产日期
+	ExpiryDate          string `json:"expiryDate"`          // 疫苗效期
+	BatchNumber         string `json:"batchNumber"`         // 疫苗批号
+
+	Quantity       int     `json:"quantity"`       // 数量
+	UnitPrice      float32 `json:"unitPrice"`      // 购入单价
+	Operator       string  `json:"operator"`       // 经办人
+	ForwardingUnit string  `json:"forwardingUnit"` // 发货单位
+	Date           string  `json:"date"`           // 入库日期
+
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockInInsertReq) GetId() interface{} {
+	return s.Id
+}
+
+// StockInInsertReq 增使用的结构体
+type StockOutInsertReq struct {
+	Id int `json:"id" comment:"id" swaggerignore:"true"`
+
+	ProductID           int    `json:"productId"`           // 药品名称id
+	EnterpriseID        int    `json:"enterpriseId"`        // 生产企业id
+	SpecID              int    `json:"specId"`              // 规格id
+	UnitID              int    `json:"unitId"`              // 单位id 非必填
+	DosageFormID        int    `json:"dosageFormId"`        // 剂型id 非必填
+	ApprovalNumber      string `json:"approvalNumber"`      // 批准文号
+	QualificationNumber string `json:"qualificationNumber"` // 批签发合格编号
+	ProductionDate      string `json:"productionDate"`      // 生产日期
+	ExpiryDate          string `json:"expiryDate"`          // 疫苗效期
+	BatchNumber         string `json:"batchNumber"`         // 疫苗批号
+
+	Quantity      int     `json:"quantity"`      // 数量
+	UnitPrice     float32 `json:"unitPrice"`     // 购入单价
+	Operator      string  `json:"operator"`      // 经办人
+	ReceivingUnit string  `json:"receivingUnit"` // 收货单位
+	Date          string  `json:"date"`          // 入库日期
+
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockOutInsertReq) GetId() interface{} {
+	return s.Id
+}
+
+// StockInPageReq 列表或者搜索使用结构体
+type StockInPageReq struct {
+	dto.Pagination `search:"-"`
+	ProductID      int    `json:"productId"`    // 药品名称id
+	EnterpriseID   int    `json:"enterpriseId"` // 生产企业id
+	ExpiryDate     string `json:"expiryDate"`   // 疫苗效期
+	BatchNumber    string `json:"batchNumber"`  // 疫苗批号
+}
+
+func (m *StockInPageReq) GetNeedSearch() interface{} {
+	return *m
+}

+ 216 - 0
dto/stock_template.go

@@ -0,0 +1,216 @@
+package dto
+
+import (
+	"Medical_ERP/common/dto"
+	common "Medical_ERP/common/model"
+)
+
+// StockTemplateInInsertReq 增使用的结构体
+type StockTemplateInInsertReq struct {
+	Id int `json:"id" comment:"id" swaggerignore:"true"`
+
+	MedicineInfo map[string]interface{} `json:"medicineInfo"` // 药品信息
+
+	Quantity       int     `json:"quantity" `      // 数量
+	UnitPrice      float32 `json:"unitPrice"`      // 购入单价
+	Operator       string  `json:"operator"`       // 经办人
+	ForwardingUnit string  `json:"forwardingUnit"` // 发货单位
+	Date           string  `json:"date"`           // 入库日期
+
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockTemplateInInsertReq) GetId() interface{} {
+	return s.Id
+}
+
+// StockTemplateInEditReq 增使用的结构体
+type StockTemplateInEditReq struct {
+	Id             int                    `json:"id"`              // 入库id
+	MedicineInfo   map[string]interface{} `json:"medicineInfo"`    // 药品信息
+	Quantity       int                    `json:"quantity" `       // 数量
+	UnitPrice      float32                `json:"unit_Price"`      // 购入单价
+	Operator       string                 `json:"operator"`        // 经办人
+	ForwardingUnit string                 `json:"forwarding_unit"` // 发货单位
+	Date           string                 `json:"date"`            // 入库日期
+
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockTemplateInEditReq) GetId() interface{} {
+	return s.Id
+}
+
+// StockTemplateInDeleteReq 增使用的结构体
+type StockTemplateInDeleteReq struct {
+	Id               int `json:"id"` // 入库id
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockTemplateInDeleteReq) GetId() interface{} {
+	return s.Id
+}
+
+// BatchStockTemplateInInsertReq 增使用的结构体
+type BatchStockTemplateInInsertReq struct {
+	StockInList []struct {
+		Id             int                    `json:"id" comment:"id" swaggerignore:"true"`
+		MedicineInfo   map[string]interface{} `json:"medicineInfo"`   // 药品信息
+		Quantity       int                    `json:"quantity"`       // 数量
+		UnitPrice      float32                `json:"unitPrice"`      // 购入单价
+		Operator       string                 `json:"operator"`       // 经办人
+		ForwardingUnit string                 `json:"forwardingUnit"` // 发货单位
+		Date           string                 `json:"date"`           // 入库日期
+	} `json:"stockInList"`
+
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// BatchStockTemplateOutInsertReq 增使用的结构体
+type BatchStockTemplateOutInsertReq struct {
+	StockOutList []struct {
+		Id            int                    `json:"id" comment:"id" swaggerignore:"true"`
+		MedicineInfo  map[string]interface{} `json:"medicineInfo"`  // 药品信息
+		Quantity      int                    `json:"quantity"`      // 数量
+		UnitPrice     float32                `json:"unitPrice"`     // 购入单价
+		Operator      string                 `json:"operator"`      // 经办人
+		ReceivingUnit string                 `json:"receivingUnit"` // 收货单位
+		Date          string                 `json:"date"`          // 出库日期
+	} `json:"StockOutList"`
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// StockTemplateInInsertReq 增使用的结构体
+type StockTemplateOutInsertReq struct {
+	Id int `json:"id" comment:"id" swaggerignore:"true"`
+
+	MedicineInfo map[string]interface{} `json:"medicineInfo"` // 药品信息
+
+	Quantity      int     `json:"quantity" alias:"数量" valid:"Required;Min(1)"` // 数量
+	UnitPrice     float32 `json:"unitPrice"`                                   // 购入单价
+	Operator      string  `json:"operator"`                                    // 经办人
+	ReceivingUnit string  `json:"receivingUnit"`                               // 收货单位
+	Date          string  `json:"date"`                                        // 出库日期
+
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockTemplateOutInsertReq) GetId() interface{} {
+	return s.Id
+}
+
+// StockTemplateOutEditReq 增使用的结构体
+type StockTemplateOutEditReq struct {
+	Id int `json:"id" comment:"id"`
+
+	MedicineInfo map[string]interface{} `json:"medicineInfo"` // 药品信息
+
+	Quantity      int     `json:"quantity" alias:"数量" valid:"Required;Min(1)"` // 数量
+	UnitPrice     float32 `json:"unit_price"`                                  // 购入单价
+	Operator      string  `json:"operator"`                                    // 经办人
+	ReceivingUnit string  `json:"receiving_unit"`                              // 收货单位
+	Date          string  `json:"date"`                                        // 出库日期
+
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockTemplateOutEditReq) GetId() interface{} {
+	return s.Id
+}
+
+// StockTemplateOutDeleteReq 增使用的结构体
+type StockTemplateOutDeleteReq struct {
+	Id               int `json:"id"`
+	common.ControlBy `swaggerignore:"true"`
+}
+
+// GetId 获取数据对应的ID
+func (s *StockTemplateOutDeleteReq) GetId() interface{} {
+	return s.Id
+}
+
+// StockTemplateInPageReq 列表或者搜索使用结构体
+type StockTemplateInPageReq struct {
+	dto.Pagination `search:"-"`
+	ProductID      int    `json:"productId"`      // 药品名称id
+	EnterpriseID   int    `json:"enterpriseId"`   // 生产企业id
+	StartDate      string `json:"startDate"`      // 入库开始时间
+	EndDate        string `json:"endDate"`        // 入库结束时间
+	BatchNumber    string `json:"batchNumber"`    // 疫苗批号
+	ForwardingUnit string `json:"forwardingUnit"` // 发货单位
+}
+
+// StockTemplateOutPageReq 列表或者搜索使用结构体
+type StockTemplateOutPageReq struct {
+	dto.Pagination `search:"-"`
+	ProductID      int    `json:"productId"`     // 药品名称id
+	EnterpriseID   int    `json:"enterpriseId"`  // 生产企业id
+	StartDate      string `json:"startDate"`     // 出库开始时间
+	EndDate        string `json:"endDate"`       // 出库结束时间
+	BatchNumber    string `json:"batchNumber"`   // 疫苗批号
+	ReceivingUnit  string `json:"receivingUnit"` // 收货单位
+}
+
+// StockTemplateInventoryPageReq 列表或者搜索使用结构体
+type StockTemplateInventoryPageReq struct {
+	dto.Pagination  `search:"-"`
+	ProductID       int    `json:"productId"`       // 药品名称id
+	EnterpriseID    int    `json:"enterpriseId"`    // 生产企业id
+	StartDate       string `json:"startDate"`       // 出/入库开始时间
+	EndDate         string `json:"endDate"`         // 出/入库结束时间
+	BatchNumber     string `json:"batchNumber"`     // 疫苗批号
+	SendReceiveUnit string `json:"SendReceiveUnit"` // 收发单位
+}
+
+// StockTemplateInventoryEditReq 列表或者搜索使用结构体
+type StockTemplateInventoryEditReq struct {
+	Id int `json:"id"`
+}
+
+// StockTemplateInventoryExcelReq 列表或者搜索使用结构体
+type StockTemplateInventoryExcelReq struct {
+	ProductID    int    `json:"productId" alias:"品名" valid:"Required;Min(1)"`       // 药品名称id
+	EnterpriseID int    `json:"enterpriseId" alias:"生产企业" valid:"Required;Min(1)"`  // 生产企业id
+	SpecId       int    `json:"specId" alias:"规格" valid:"Required;Min(1)"`          // 规格id
+	BatchNumber  string `json:"batchNumber" alias:"批号" valid:"Required;MinSize(1)"` // 批号
+	StartDate    string `json:"startDate"`                                          // 出/入库开始时间
+	EndDate      string `json:"endDate"`                                            // 出/入库结束时间
+	Type         string `json:"type"`                                               // 类型 excel pdf
+}
+
+// TransportRecordWordReq 列表或者搜索使用结构体
+type TransportRecordWordReq struct {
+	Date          string `json:"date"`          // 出库时间
+	ReceivingUnit string `json:"receivingUnit"` // 收货单位
+	ProductID     int    `json:"productId"`     // 药品名称id
+	Type          string `json:"type"`          // 类型
+}
+
+// StockStatListReq 列表或者搜索使用结构体
+type StockStatListReq struct {
+	dto.Pagination `search:"-"`
+	ProductID      int    `json:"productId"`    // 药品名称id
+	EnterpriseID   int    `json:"enterpriseId"` // 生产企业id
+	StartDate      string `json:"startDate"`    // 效期开始时间
+	EndDate        string `json:"endDate"`      // 效期结束时间
+	BatchNumber    string `json:"batchNumber"`  // 疫苗批号
+}
+
+// StockStatReq 列表或者搜索使用结构体
+type StockStatReq struct {
+	ProductID int `json:"productId"` // 药品名称id
+	SpecID    int `json:"specId"`    // 规格id
+}
+
+type StockUnitListReq struct {
+	Name string
+	Type int //1-入库 2-出库 3-库存
+}
+type StockOperatorListReq struct {
+	Name string
+}

+ 1 - 1
dto/unit.go

@@ -9,7 +9,7 @@ import (
 // UnitPageReq 列表或者搜索使用结构体
 type UnitPageReq struct {
 	dto.Pagination `search:"-"`
-	Name           string `json:"name" search:"type:contains;column:name;table:product" example:""` // 名称
+	Name           string `json:"name" search:"type:contains;column:name;table:unit" example:""` // 名称
 }
 
 func (m *UnitPageReq) GetNeedSearch() interface{} {

+ 20 - 8
go.mod

@@ -3,6 +3,7 @@ module Medical_ERP
 go 1.19
 
 require (
+	baliance.com/gooxml v1.0.1
 	github.com/beego/beego/v2 v2.1.0
 	github.com/bytedance/go-tagexpr/v2 v2.7.12
 	github.com/gin-gonic/gin v1.8.1
@@ -12,10 +13,14 @@ require (
 	github.com/go-sql-driver/mysql v1.7.0
 	github.com/gobwas/glob v0.2.3
 	github.com/google/uuid v1.3.0
+	github.com/jung-kurt/gofpdf v1.16.2
 	github.com/opentracing/opentracing-go v1.2.0
+	github.com/signintech/gopdf v0.20.0
 	github.com/smartystreets/goconvey v1.8.1
 	github.com/swaggo/swag v1.16.2
 	github.com/tidwall/gjson v1.15.0
+	github.com/volcengine/volc-sdk-golang v1.0.127
+	github.com/xuri/excelize/v2 v2.8.0
 	gogs.baozhida.cn/zoie/OAuth-core v0.0.0-00010101000000-000000000000
 	gorm.io/driver/mysql v1.3.5
 	gorm.io/gorm v1.23.8
@@ -29,12 +34,13 @@ require (
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bsm/redislock v0.5.0 // indirect
 	github.com/casbin/casbin/v2 v2.47.2 // indirect
+	github.com/cenkalti/backoff/v4 v4.1.2 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/chanxuehong/rand v0.0.0-20201110082127-2f19a1bdd973 // indirect
 	github.com/chanxuehong/wechat v0.0.0-20201110083048-0180211b69fd // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
-	github.com/fatih/color v1.9.0 // indirect
+	github.com/fatih/color v1.12.0 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
 	github.com/git-chglog/git-chglog v0.0.0-20190611050339-63a4e637021f // indirect
 	github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -46,12 +52,12 @@ require (
 	github.com/go-playground/validator/v10 v10.11.0 // indirect
 	github.com/goccy/go-json v0.9.10 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
-	github.com/golang/snappy v0.0.1 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/henrylee2cn/ameda v1.4.10 // indirect
 	github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect
-	github.com/imdario/mergo v0.3.9 // indirect
+	github.com/imdario/mergo v0.3.11 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
@@ -60,7 +66,7 @@ require (
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/mailru/easyjson v0.7.6 // indirect
-	github.com/mattn/go-colorable v0.1.7 // indirect
+	github.com/mattn/go-colorable v0.1.8 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mattn/goveralls v0.0.2 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
@@ -68,14 +74,18 @@ require (
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/nsqio/go-nsq v1.0.8 // indirect
 	github.com/nyaruka/phonenumbers v1.0.55 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.2 // indirect
+	github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.15.1 // indirect
 	github.com/prometheus/client_model v0.3.0 // indirect
 	github.com/prometheus/common v0.42.0 // indirect
 	github.com/prometheus/procfs v0.9.0 // indirect
+	github.com/richardlehane/mscfb v1.0.4 // indirect
+	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/robfig/cron/v3 v3.0.1 // indirect
 	github.com/robinjoseph08/redisqueue/v2 v2.1.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -86,10 +96,12 @@ require (
 	github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df // indirect
 	github.com/ugorji/go/codec v1.2.7 // indirect
 	github.com/urfave/cli v1.22.1 // indirect
-	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
-	golang.org/x/net v0.8.0 // indirect
-	golang.org/x/sys v0.6.0 // indirect
-	golang.org/x/text v0.8.0 // indirect
+	github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect
+	github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect
+	golang.org/x/crypto v0.12.0 // indirect
+	golang.org/x/net v0.14.0 // indirect
+	golang.org/x/sys v0.11.0 // indirect
+	golang.org/x/text v0.12.0 // indirect
 	golang.org/x/tools v0.7.0 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/AlecAivazis/survey.v1 v1.8.5 // indirect

File diff suppressed because it is too large
+ 758 - 19
go.sum


+ 4 - 1
middleware/auth_middle.go

@@ -86,13 +86,16 @@ func ValidateToken(c *context.Context) (coreModel.UserInfo, error) {
 
 	if res.Code != http.StatusOK {
 		log.Error(errors.New(res.Msg.Msg))
-		return coreModel.UserInfo{}, errors.New(res.Msg.Msg)
+	}
+	if res.Data.DeptId == 0 {
+		return coreModel.UserInfo{}, errors.New("请先进入公司")
 	}
 	c.Input.SetData(jwt.JwtPayloadKey, jwt.MapClaims{
 		"uuid":      res.Data.Uuid,
 		"identity":  float64(res.Data.UserId),
 		"userName":  res.Data.UserName,
 		"roleName":  res.Data.RoleName,
+		"deptName":  res.Data.DeptName,
 		"roleKey":   res.Data.RoleKey,
 		"userId":    float64(res.Data.UserId),
 		"roleId":    float64(res.Data.RoleId),

+ 1 - 0
middleware/cors_middle.go

@@ -13,6 +13,7 @@ var corsFunc = func(ctx *context.Context) {
 	ctx.Output.Header("Access-Control-Allow-Methods", "OPTIONS,DELETE,POST,GET,PUT,PATCH")
 	ctx.Output.Header("Access-Control-Max-Age", "3600")
 	ctx.Output.Header("Access-Control-Allow-Headers", "X-Custom-Header,accept,Content-Type,Access-Token,Authorization")
+	ctx.Output.Header("Access-Control-Expose-Headers", "Content-Disposition")
 	ctx.Output.Header("Access-Control-Allow-Credentials", "true")
 	ctx.Output.Header("Access-Control-Allow-Origin", "*")
 	if ctx.Input.Method() == http.MethodOptions {

+ 1 - 0
models/enterprise.go

@@ -4,6 +4,7 @@ import (
 	model2 "Medical_ERP/common/model"
 )
 
+// 生产企业
 type Enterprise struct {
 	model2.Model
 	Name   string `orm:"size(128)" json:"name"`                   //生产企业名称

+ 127 - 0
models/init.go

@@ -2,8 +2,10 @@ package models
 
 import (
 	db "Medical_ERP/common/initialize"
+	"fmt"
 	_ "github.com/go-sql-driver/mysql"
 	"log"
+	"sync"
 )
 
 func init() {
@@ -20,8 +22,133 @@ func AutoMigrateDB() {
 			&Enterprise{},
 			&Spec{},
 			&DosageForm{},
+			&Unit{},
+			&MedicineTemplate{},
+			&StockIn{},
+			&StockOut{},
+			&MedicineInventory{},
 		)
 	if err != nil {
 		log.Fatalf("migrate db fail: %v", err)
 	}
 }
+
+var Spec_list *sync.Map
+var DosageForm_list *sync.Map
+var Enterprise_list *sync.Map
+var Product_list *sync.Map
+var Unit_list *sync.Map
+
+// 初始化基础数据
+func InitBasicData(deptId int) {
+	Spec_list = new(sync.Map)
+	DosageForm_list = new(sync.Map)
+	Enterprise_list = new(sync.Map)
+	Product_list = new(sync.Map)
+	Unit_list = new(sync.Map)
+	Read_Spec_All_Map(deptId)
+	Read_Product_All_Map(deptId)
+	Read_DosageForm_All_Map(deptId)
+	Read_Enterprise_All_Map(deptId)
+	Read_Unit_All_Map(deptId)
+}
+
+// 获取全部规格
+func Read_Spec_All_Map(deptId int) {
+	var spec []Spec
+	if err := db.DB.Where("dept_id = ?", deptId).Find(&spec).Error; err != nil {
+		fmt.Println("Error loading Specs:", err)
+		return
+	}
+	for _, v := range spec {
+		Spec_list.Store(v.Id, v.Name)
+	}
+}
+func Read_Spec_Get(Id int) string {
+	v, ok := Spec_list.Load(Id)
+	if ok {
+		return v.(string)
+	} else {
+		return ""
+	}
+}
+
+// 获取全部剂型
+func Read_DosageForm_All_Map(deptId int) {
+	var dosageForm []DosageForm
+	if err := db.DB.Where("dept_id = ?", deptId).Find(&dosageForm).Error; err != nil {
+		fmt.Println("Error loading DosageForm:", err)
+		return
+	}
+	for _, v := range dosageForm {
+		DosageForm_list.Store(v.Id, v.Name)
+	}
+}
+func Read_DosageForm_Get(Id int) string {
+	v, ok := DosageForm_list.Load(Id)
+	if ok {
+		return v.(string)
+	} else {
+		return ""
+	}
+}
+
+// 获取全部生产企业
+func Read_Enterprise_All_Map(deptId int) {
+	var enterprise []Enterprise
+	if err := db.DB.Where("dept_id = ?", deptId).Find(&enterprise).Error; err != nil {
+		fmt.Println("Error loading Enterprise:", err)
+		return
+	}
+	for _, v := range enterprise {
+		Enterprise_list.Store(v.Id, v.Name)
+	}
+}
+func Read_Enterprise_Get(Id int) string {
+	v, ok := Enterprise_list.Load(Id)
+	if ok {
+		return v.(string)
+	} else {
+		return ""
+	}
+}
+
+// 获取全部品名
+func Read_Product_All_Map(deptId int) {
+	var product []Product
+	if err := db.DB.Where("dept_id = ?", deptId).Find(&product).Error; err != nil {
+		fmt.Println("Error loading Product:", err)
+		return
+	}
+	for _, v := range product {
+		Product_list.Store(v.Id, v.Name)
+	}
+}
+func Read_Product_Get(Id int) string {
+	v, ok := Product_list.Load(Id)
+	if ok {
+		return v.(string)
+	} else {
+		return ""
+	}
+}
+
+// 获取全部单位
+func Read_Unit_All_Map(deptId int) {
+	var unit []Unit
+	if err := db.DB.Where("dept_id = ?", deptId).Find(&unit).Error; err != nil {
+		fmt.Println("Error loading Unit:", err)
+		return
+	}
+	for _, v := range unit {
+		Unit_list.Store(v.Id, v.Name)
+	}
+}
+func Read_Unit_Get(Id int) string {
+	v, ok := Unit_list.Load(Id)
+	if ok {
+		return v.(string)
+	} else {
+		return ""
+	}
+}

+ 19 - 0
models/medicine_Info.go

@@ -0,0 +1,19 @@
+package models
+
+import model2 "Medical_ERP/common/model"
+
+type MedicineInfo struct {
+	model2.Model
+	ProductID           int    `json:"productId" gorm:"size:4;"`             // 药品名称id
+	EnterpriseID        int    `json:"enterpriseId" gorm:"size:4;"`          // 生产企业id
+	SpecID              int    `json:"specId" gorm:"size:4;"`                // 规格id
+	UnitID              int    `json:"unitId" gorm:"size:4;"`                // 单位id 非必填
+	DosageFormID        int    `json:"dosageFormId" gorm:"size:4;"`          // 剂型id 非必填
+	ApprovalNumber      string `json:"approvalNumber" gorm:"size:128;"`      // 批准文号
+	QualificationNumber string `json:"qualificationNumber" gorm:"size:128;"` // 批签发合格编号
+	ProductionDate      string `json:"productionDate" gorm:"size:128;"`      // 生产日期
+	ExpiryDate          string `json:"expiryDate" gorm:"size:128;"`          // 疫苗效期
+	BatchNumber         string `json:"batchNumber" gorm:"size:128;"`         // 疫苗批号
+	model2.ControlBy
+	model2.ModelTime
+}

+ 27 - 0
models/medicine_inventory.go

@@ -0,0 +1,27 @@
+package models
+
+import (
+	model2 "Medical_ERP/common/model"
+)
+
+// 药品出入库信息
+type MedicineInventory struct {
+	model2.Model
+	MedicineID     int    `json:"medicineId" gorm:"size:4;not null;comment:药品信息ID"` // 药品信息ID
+	TotalIn        int    `json:"totalIn" gorm:"size:11;"`                          // 入库数量
+	StockInID      int    `json:"stockInId" gorm:"size:11;"`                        // 入库ID
+	TotalOut       int    `json:"totalOut" gorm:"size:11;"`                         // 出库数量
+	StockOutID     int    `json:"stockOutId" gorm:"size:11;"`                       // 出库ID
+	Balance        int    `json:"balance" gorm:"size:11;"`                          // 结余数量
+	ForwardingUnit string `json:"forwardingUnit" gorm:"size:128;"`                  // 发货单位
+	ReceivingUnit  string `json:"receivingUnit" gorm:"size:128;"`                   // 收货单位
+	Operator       string `json:"operator" gorm:"size:128;"`                        // 经办人
+	Date           string `json:"date" gorm:"size:128;"`                            // 日期
+
+	model2.ControlBy
+	model2.ModelTime
+}
+
+func (e *MedicineInventory) TableName() string {
+	return "medicine_inventory"
+}

+ 137 - 0
models/medicine_template.go

@@ -0,0 +1,137 @@
+package models
+
+import (
+	model2 "Medical_ERP/common/model"
+	"github.com/beego/beego/v2/core/logs"
+	"strconv"
+)
+
+const (
+	FieldProductID           = "product_id"           // 品种ID
+	FieldEnterpriseID        = "enterprise_id"        // 生产企业ID
+	FieldSpecID              = "spec_id"              // 规格ID
+	FieldUnitID              = "unit_id"              // 单位id
+	FieldDosageFormID        = "dosage_form_id"       //  剂型id
+	FieldBatchNumber         = "batch_number"         // 批号
+	FieldExpiryDate          = "expiry_date"          // 过期时间
+	FieldApprovalNumber      = "approval_number"      // 批准文号
+	FieldQualificationNumber = "qualification_number" // 批签发合格编号
+
+	FieldProductName    = "product_name"
+	FieldEnterpriseName = "enterprise_name"
+	FieldSpecName       = "spec_name"
+	FieldUnitName       = "unit_name"
+	FieldDosageFormName = "dosage_form_name"
+)
+
+func GetMedicineInfoTableName(deptId int) string {
+	return "medicine_info_" + strconv.Itoa(deptId)
+}
+
+type MedicineTemplate struct {
+	model2.Model
+	Type      int    `json:"type" gorm:"size:11;"`        // 数据类型
+	Name      string `json:"name" gorm:"size:32;"`        // 标签名称
+	FieldName string `json:"field_name" gorm:"size:128;"` // 英语名称
+	Text      string `json:"text" gorm:"size:256;"`       // 描述
+	Sort      int    `json:"sort" gorm:"size:11;"`        // 排序
+	Width     int    `json:"width" gorm:"size:11;"`       // 宽度
+	Show      int    `json:"show" gorm:"size:11;"`        // 1-显示 2-隐藏
+	State     int    `json:"state" gorm:"size:11;"`       // 1-系统初始化 2-用户添加
+	model2.ControlBy
+	model2.ModelTime
+}
+
+func (e *MedicineTemplate) TableName() string {
+	return "medicine_template"
+}
+
+func GetInitMedicineTemplateInfo(deptId, CreateBy int) []MedicineTemplate {
+	return []MedicineTemplate{
+		{Type: 1, Name: "品种", FieldName: FieldProductID, Show: 1, State: 1, Sort: -9, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 2, Name: "生产企业", FieldName: FieldEnterpriseID, Show: 1, State: 1, Sort: -8, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 3, Name: "规格", FieldName: FieldSpecID, Show: 1, State: 1, Sort: -7, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 6, Name: "批号", FieldName: FieldBatchNumber, Show: 1, State: 1, Sort: -6, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 9, Name: "效期", FieldName: FieldExpiryDate, Show: 1, State: 1, Sort: -5, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 6, Name: "批准文号", FieldName: FieldApprovalNumber, Show: 1, State: 1, Sort: -4, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 5, Name: "剂型", FieldName: FieldDosageFormID, Show: 1, State: 1, Sort: -3, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 4, Name: "单位", FieldName: FieldUnitID, Show: 1, State: 1, Sort: -2, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+		{Type: 6, Name: "批签发编号", FieldName: FieldQualificationNumber, Show: 1, State: 1, Sort: -1, ControlBy: model2.ControlBy{CreateBy: CreateBy, DeptId: deptId}},
+	}
+}
+
+func GetInitMedicineTemplateSql(deptId int) string {
+	sqlStmt := `
+    CREATE TABLE IF NOT EXISTS ` + GetMedicineInfoTableName(deptId) + ` (
+        id int primary key auto_increment comment '自增ID',
+        product_id INT comment '品种ID',
+        enterprise_id INT comment '生产企业ID',
+        spec_id INT comment '规格ID',
+		unit_id INT comment '单位ID',
+		dosage_form_id INT comment '剂型ID',
+        batch_number VARCHAR(256) comment '批号',
+        expiry_date DATE comment '效期',
+        approval_number VARCHAR(256) comment '批准文号',
+        qualification_number VARCHAR(256) comment '批签发编号',
+        purchase_unit_price DECIMAL(10,2) comment '购进单价',
+        sales_unit_price DECIMAL(10,2) comment '销售单价'
+    );`
+	return sqlStmt
+}
+
+// 获取插入数据sql
+func GetInsertMedicineTemplateSql(deptId, typeId int, columnName string) string {
+	var typeStr string
+	switch typeId {
+	case 1, 2, 3, 4, 5:
+		typeStr = " INT"
+	case 6:
+		typeStr = " VARCHAR(256)"
+	case 7:
+		typeStr = " INT"
+	case 8:
+		typeStr = " DECIMAL(10,2)"
+	case 9:
+		typeStr = " DATE"
+	}
+	sqlStmt := "ALTER TABLE " + GetMedicineInfoTableName(deptId) + " ADD `" + columnName + "`" + typeStr
+	logs.Info("InsertMedicineTemplateSql", sqlStmt)
+	return sqlStmt
+}
+
+// 获取更新列名sql
+func GetUpdateMedicineTemplateSql(deptId, typeId int, oldColumnName, newColumnName string) string {
+	var typeStr string
+	switch typeId {
+	case 1, 2, 3, 4, 5:
+		typeStr = " INT"
+	case 6:
+		typeStr = " VARCHAR(256)"
+	case 7:
+		typeStr = " INT"
+	case 8:
+		typeStr = " DECIMAL(10,4)"
+	case 9:
+		typeStr = " DATETIME"
+	}
+	sqlStmt := "ALTER TABLE " + GetMedicineInfoTableName(deptId) + " CHANGE `" + oldColumnName + "` `" + newColumnName + "`" + typeStr
+	logs.Info("UpdateMedicineTemplateSql", sqlStmt)
+	return sqlStmt
+}
+
+func GetDeleteMedicineTemplateSql(deptId int, columnName string) string {
+	sqlStmt := "ALTER TABLE " + GetMedicineInfoTableName(deptId) + " DROP `" + columnName + "`"
+	logs.Info("DeleteMedicineTemplateSql", sqlStmt)
+	return sqlStmt
+}
+
+//	type	说明			英文名称
+//	1		疫苗名称		ProductID
+//	2		生产企业		EnterpriseID
+//	3		规格			SpecID
+//	4		剂型			DosageFormID
+//	5		单位			UnitID
+//	6		文本
+//	7		数值(整数)
+//	7		数值(小数)
+//	9		日期

+ 1 - 1
models/operation_log.go

@@ -4,7 +4,7 @@ import model2 "Medical_ERP/common/model"
 
 type OperationLog struct {
 	ID       string `json:"id" gorm:"primaryKey,size:36"`
-	UrlKey   string `json:"url_key" gorm:"size:36"`
+	UrlKey   string `json:"url_key" gorm:"size:128"`
 	Describe string `json:"describe" gorm:"type:longtext"`
 	DataID   string `json:"data_id" gorm:"size:36"`
 	Detailed string `json:"detailed" gorm:"type:longtext"`

+ 21 - 0
models/stock_in.go

@@ -0,0 +1,21 @@
+package models
+
+import model2 "Medical_ERP/common/model"
+
+// 入库
+
+type StockIn struct {
+	model2.Model
+	MedicineID     int     `json:"medicineId" gorm:"size:4;not null;comment:药品信息ID"` // 药品信息ID
+	Quantity       int     `json:"quantity" gorm:"size:128;"`                        // 数量
+	UnitPrice      float32 `json:"unitPrice"`                                        // 购入单价
+	Operator       string  `json:"operator" gorm:"size:128;"`                        // 经办人
+	ForwardingUnit string  `json:"forwardingUnit" gorm:"size:128;"`                  // 发货单位
+	Date           string  `json:"date" gorm:"size:128;"`                            // 入库日期
+	model2.ControlBy
+	model2.ModelTime
+}
+
+func (e *StockIn) TableName() string {
+	return "stock_in"
+}

+ 19 - 0
models/stock_out.go

@@ -0,0 +1,19 @@
+package models
+
+import model2 "Medical_ERP/common/model"
+
+type StockOut struct {
+	model2.Model
+	MedicineID    int     `json:"medicineId" gorm:"size:4;not null;comment:药品信息ID"` // 药品信息ID
+	Quantity      int     `json:"quantity" gorm:"size:128;"`                        // 数量
+	UnitPrice     float32 `json:"unitPrice"`                                        // 购入单价
+	Operator      string  `json:"operator" gorm:"size:128;"`                        // 经办人
+	ReceivingUnit string  `json:"receivingUnit" gorm:"size:128;"`                   // 收货单位
+	Date          string  `json:"date" gorm:"size:128;"`                            // 入库日期
+	model2.ControlBy
+	model2.ModelTime
+}
+
+func (e *StockOut) TableName() string {
+	return "stock_out"
+}

+ 1 - 1
models/unit.go

@@ -14,7 +14,7 @@ type Unit struct {
 }
 
 func (e *Unit) TableName() string {
-	return "spec"
+	return "unit"
 }
 
 func (e *Unit) GetId() interface{} {

+ 16 - 0
routers/medicine.go

@@ -0,0 +1,16 @@
+package routers
+
+import (
+	"Medical_ERP/controllers"
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+func init() {
+
+	medicine := beego.NewNamespace("/api/medicine",
+		beego.NSRouter("/batch-number", &controllers.MedicineController{}, "*:BatchNumber"),
+		beego.NSRouter("/basic-data-stat", &controllers.MedicineController{}, "*:BasicDataStat"),
+	)
+	//注册 namespace
+	beego.AddNamespace(medicine)
+}

+ 22 - 0
routers/medicine_template.go

@@ -0,0 +1,22 @@
+package routers
+
+import (
+	"Medical_ERP/controllers"
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+func init() {
+	medicineTemplate := beego.NewNamespace("/api/medicine-template",
+		beego.NSRouter("/init", &controllers.MedicineTemplateController{}, "*:Initialize"),
+		beego.NSRouter("/list", &controllers.MedicineTemplateController{}, "*:List"),
+		beego.NSRouter("/add", &controllers.MedicineTemplateController{}, "*:Add"),
+		beego.NSRouter("/edit", &controllers.MedicineTemplateController{}, "*:Edit"),
+		beego.NSRouter("/delete", &controllers.MedicineTemplateController{}, "*:Delete"),
+		beego.NSRouter("/columns", &controllers.MedicineTemplateController{}, "*:Columns"),
+		beego.NSRouter("/list-for-stock", &controllers.MedicineTemplateController{}, "*:ListForStock"),
+		beego.NSRouter("/list-for-stock-out", &controllers.MedicineTemplateController{}, "*:ListForStockOut"),
+	)
+
+	//注册 namespace
+	beego.AddNamespace(medicineTemplate)
+}

+ 20 - 0
routers/sales.go

@@ -0,0 +1,20 @@
+package routers
+
+import (
+	"Medical_ERP/controllers"
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+func init() {
+	// 销售管理
+	sales := beego.NewNamespace("/api/sales",
+		beego.NSRouter("/list", &controllers.SalesController{}, "*:SalesList"),
+		beego.NSRouter("/excel", &controllers.SalesController{}, "*:SalesExcel"),
+		beego.NSRouter("/report-excel", &controllers.SalesController{}, "*:SalesReportExcel"),
+		beego.NSRouter("/order-list", &controllers.SalesController{}, "*:SalesOrderList"),
+		beego.NSRouter("/stock-out/export", &controllers.SalesController{}, "*:SalesStockOutExport"),
+	)
+
+	//注册 namespace
+	beego.AddNamespace(sales)
+}

+ 36 - 0
routers/stock.go

@@ -0,0 +1,36 @@
+package routers
+
+import (
+	"Medical_ERP/controllers"
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+func init() {
+	// 分组
+	stockTemplate := beego.NewNamespace("/api/stock-template",
+		beego.NSRouter("/in", &controllers.StockTemplateController{}, "*:StockTemplateIn"),                            // 入库
+		beego.NSRouter("/in/edit", &controllers.StockTemplateController{}, "*:StockTemplateInEdit"),                   // 入库 - 编辑
+		beego.NSRouter("/in/delete", &controllers.StockTemplateController{}, "*:StockTemplateInDelete"),               // 入库 - 删除
+		beego.NSRouter("/batch-in", &controllers.StockTemplateController{}, "*:BatchStockTemplateIn"),                 // 批量入库
+		beego.NSRouter("/out", &controllers.StockTemplateController{}, "*:StockTemplateOut"),                          // 出库
+		beego.NSRouter("/out/edit", &controllers.StockTemplateController{}, "*:StockTemplateOutEdit"),                 // 出库 - 编辑
+		beego.NSRouter("/out/delete", &controllers.StockTemplateController{}, "*:StockTemplateOutDelete"),             // 出库 - 删除
+		beego.NSRouter("/batch-out", &controllers.StockTemplateController{}, "*:BatchStockTemplateOut"),               // 批量出库
+		beego.NSRouter("/in/list", &controllers.StockTemplateController{}, "*:StockTemplateInList"),                   // 入库列表
+		beego.NSRouter("/out/list", &controllers.StockTemplateController{}, "*:StockTemplateOutList"),                 // 出库列表
+		beego.NSRouter("/inventory/list", &controllers.StockTemplateController{}, "*:StockTemplateInventoryList"),     // 收发记录
+		beego.NSRouter("/inventory/export", &controllers.StockTemplateController{}, "*:StockTemplateInventoryExport"), // 库存 - excel
+		beego.NSRouter("/inventory/pdf", &controllers.StockTemplateController{}, "*:InventoryPdf"),
+		beego.NSRouter("/transport-record/export", &controllers.StockTemplateController{}, "*:TransportRecordExport"),
+	)
+	stock := beego.NewNamespace("/api/stock",
+		beego.NSRouter("/unit/list", &controllers.StockTemplateController{}, "*:StockUnitList"),         // 收/发货单位列表
+		beego.NSRouter("/operator/list", &controllers.StockTemplateController{}, "*:StockOperatorList"), // 经办人列表
+		beego.NSRouter("/inquiry/list", &controllers.StockTemplateController{}, "*:StockInquiryList"),   // 库存查询
+		beego.NSRouter("/inquiry/excel", &controllers.StockTemplateController{}, "*:StockInquiryExcel"), // 库存查询 - excel
+		beego.NSRouter("/stat", &controllers.StockTemplateController{}, "*:StockStat"),
+		beego.NSRouter("/home-stat", &controllers.StockTemplateController{}, "*:StockHomeStat"),
+	)
+	//注册 namespace
+	beego.AddNamespace(stockTemplate, stock)
+}

+ 22 - 2
services/dosage_form.go

@@ -57,9 +57,19 @@ func (e *DosageForm) Get(d *dto.DosageFormGetReq, productModel *models.DosageFor
 func (e *DosageForm) Insert(c *dto.DosageFormInsertReq) error {
 	var err error
 	var data models.DosageForm
-	c.Generate(&data)
-	err = db.DB.Create(&data).Error
+	err = db.DB.Where("name = ? AND dept_id = ?", c.Name, c.DeptId).First(&data).Error
 	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+			c.Id = data.Id
+			return nil
+		}
 		logs.Error("db error: %s", err)
 		return global.CreateFailedErr
 	}
@@ -94,6 +104,16 @@ func (e *DosageForm) Remove(d *dto.DosageFormDeleteReq) error {
 	var err error
 	var data models.DosageForm
 
+	var Count int64
+	err = db.DB.Table(models.GetMedicineInfoTableName(d.DeptId)).Where("unit_id = ?", d.GetId()).Count(&Count).Error
+	if err != nil {
+		logs.Error("Delete error: %s", err)
+		return global.DeleteFailedErr
+	}
+	if Count > 0 {
+		return errors.New("该剂型已被关联,禁止删除")
+	}
+
 	result := db.DB.Model(&data).Delete(&data, d.GetId())
 	if err = result.Error; err != nil {
 		logs.Error("Delete error: %s", err)

+ 22 - 2
services/enterprise.go

@@ -57,9 +57,19 @@ func (e *Enterprise) Get(d *dto.EnterpriseGetReq, productModel *models.Enterpris
 func (e *Enterprise) Insert(c *dto.EnterpriseInsertReq) error {
 	var err error
 	var data models.Enterprise
-	c.Generate(&data)
-	err = db.DB.Create(&data).Error
+	err = db.DB.Where("name = ? AND dept_id = ?", c.Name, c.DeptId).First(&data).Error
 	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+			c.Id = data.Id
+			return nil
+		}
 		logs.Error("db error: %s", err)
 		return global.CreateFailedErr
 	}
@@ -94,6 +104,16 @@ func (e *Enterprise) Remove(d *dto.EnterpriseDeleteReq) error {
 	var err error
 	var data models.Enterprise
 
+	var Count int64
+	err = db.DB.Table(models.GetMedicineInfoTableName(d.DeptId)).Where("unit_id = ?", d.GetId()).Count(&Count).Error
+	if err != nil {
+		logs.Error("Delete error: %s", err)
+		return global.DeleteFailedErr
+	}
+	if Count > 0 {
+		return errors.New("该生产企业已被关联,禁止删除")
+	}
+
 	result := db.DB.Model(&data).Delete(&data, d.GetId())
 	if err = result.Error; err != nil {
 		logs.Error("Delete error: %s", err)

+ 81 - 0
services/medicine.go

@@ -0,0 +1,81 @@
+package services
+
+import (
+	"Medical_ERP/common/global"
+	db "Medical_ERP/common/initialize"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"github.com/beego/beego/v2/core/logs"
+	"strconv"
+)
+
+type Medicine struct {
+}
+
+func (e *Medicine) GetBatchNumber(c *dto.MedicineBatchNumberReq, list *[]string, deptId int) error {
+	var err error
+	whereSql := "1=1"
+	if c.ProductID > 0 {
+		whereSql += " AND product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND batch_number like '%" + c.BatchNumber + "%'"
+	}
+
+	err = db.DB.Table(models.GetMedicineInfoTableName(deptId)).Distinct("batch_number").Where(whereSql).Find(list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return global.GetFailedErr
+	}
+	return nil
+}
+func (e *Medicine) BasicDataStat(deptId int) (list map[string]int64, err error) {
+	var unitCount int64
+	var specCount int64
+	var enterpriseCount int64
+	var dosageFormCount int64
+	var productCount int64
+	var medicineTemplateCount int64
+	list = make(map[string]int64)
+
+	err = db.DB.Model(&models.Unit{}).Where("dept_id = ?", deptId).Count(&unitCount).Error
+	if err != nil {
+		logs.Error("get unitCount error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	err = db.DB.Model(&models.Spec{}).Where("dept_id = ?", deptId).Count(&specCount).Error
+	if err != nil {
+		logs.Error("get specCount error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	err = db.DB.Model(&models.Enterprise{}).Where("dept_id = ?", deptId).Count(&enterpriseCount).Error
+	if err != nil {
+		logs.Error("get enterpriseCount error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	err = db.DB.Model(&models.Product{}).Where("dept_id = ?", deptId).Count(&productCount).Error
+	if err != nil {
+		logs.Error("get productCount error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	err = db.DB.Model(&models.DosageForm{}).Where("dept_id = ?", deptId).Count(&dosageFormCount).Error
+	if err != nil {
+		logs.Error("get dosageFormCount error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	err = db.DB.Model(&models.MedicineTemplate{}).Where("dept_id = ?", deptId).Count(&medicineTemplateCount).Error
+	if err != nil {
+		logs.Error("get dosageFormCount error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	list["unitCount"] = unitCount
+	list["specCount"] = specCount
+	list["enterpriseCount"] = enterpriseCount
+	list["productCount"] = productCount
+	list["dosageFormCount"] = dosageFormCount
+	list["medicineTemplateCount"] = medicineTemplateCount
+	return list, nil
+}

+ 314 - 0
services/medicine_template.go

@@ -0,0 +1,314 @@
+package services
+
+import (
+	"Medical_ERP/common/actions"
+	"Medical_ERP/common/global"
+	db "Medical_ERP/common/initialize"
+	"Medical_ERP/common/translate"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"Medical_ERP/utils"
+	"errors"
+	"github.com/beego/beego/v2/core/logs"
+	"gorm.io/gorm"
+)
+
+type MedicineTemplate struct {
+}
+
+// 初始化药品信息
+func (e *MedicineTemplate) InitMedicineTemplate(deptId, userId int) error {
+	tx := db.DB.Begin()
+	// 1、创建模板
+	initMedicineTemplateInfoList := models.GetInitMedicineTemplateInfo(deptId, userId)
+	err := tx.Create(&initMedicineTemplateInfoList).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	err = tx.Exec(models.GetInitMedicineTemplateSql(deptId)).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	tx.Commit()
+
+	return nil
+}
+
+// GetPage 获取MedicineTemplate列表
+func (e *MedicineTemplate) GetPage(list *[]models.MedicineTemplate, count *int64, p *actions.DataPermission) error {
+	var err error
+	var data models.MedicineTemplate
+
+	err = db.DB.Model(&data).
+		Scopes(
+			actions.Permission(data.TableName(), p),
+		).
+		Order("sort").
+		Find(list).Limit(-1).Offset(-1).
+		Count(count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return global.GetFailedErr
+	}
+	return nil
+}
+func (e *MedicineTemplate) ListForStock(list *[]dto.MedicineTemplateForStockRes, count *int64, p *actions.DataPermission) error {
+	var err error
+	var data models.MedicineTemplate
+
+	err = db.DB.Model(&data).
+		Scopes(
+			actions.Permission(data.TableName(), p),
+		).
+		Order("sort").
+		Find(list).Limit(-1).Offset(-1).
+		Count(count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return global.GetFailedErr
+	}
+	for i := 0; i < len(*list); i++ {
+		switch (*list)[i].Type {
+		case 1:
+			var product []models.Product
+			err = db.DB.Model(&models.Product{}).Where("dept_id = ?", p.DeptId).Find(&product).Error
+			(*list)[i].List = product
+		case 2:
+			var enterprise []models.Enterprise
+			err = db.DB.Model(&models.Enterprise{}).Where("dept_id = ?", p.DeptId).Find(&enterprise).Error
+			(*list)[i].List = enterprise
+		case 3:
+			var spec []models.Spec
+			err = db.DB.Model(&models.Spec{}).Where("dept_id = ?", p.DeptId).Find(&spec).Error
+			(*list)[i].List = spec
+		case 4:
+			var unit []models.Unit
+			err = db.DB.Model(&models.Unit{}).Where("dept_id = ?", p.DeptId).Find(&unit).Error
+			(*list)[i].List = unit
+		case 5:
+			var dosageForm []models.DosageForm
+			err = db.DB.Model(&models.DosageForm{}).Where("dept_id = ?", p.DeptId).Find(&dosageForm).Error
+			(*list)[i].List = dosageForm
+		default:
+			(*list)[i].List = nil
+		}
+
+	}
+
+	return nil
+}
+func (e *MedicineTemplate) ListForStockOut(res *[]dto.MedicineTemplateForStockRes, count *int64, p *actions.DataPermission) error {
+	var err error
+	var data models.MedicineTemplate
+	var list []dto.MedicineTemplateForStockRes
+	err = db.DB.Model(&data).
+		Scopes(actions.Permission(data.TableName(), p)).
+		Order("sort").
+		Find(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return global.GetFailedErr
+	}
+	for i := 0; i < len(list); i++ {
+		switch list[i].Type {
+		case 1:
+			var product []models.Product
+			err = db.DB.Model(&models.Product{}).Where("dept_id = ?", p.DeptId).Find(&product).Error
+			list[i].List = product
+		case 2:
+			var enterprise []models.Enterprise
+			err = db.DB.Model(&models.Enterprise{}).Where("dept_id = ?", p.DeptId).Find(&enterprise).Error
+			list[i].List = enterprise
+		case 3:
+			var spec []models.Spec
+			err = db.DB.Model(&models.Spec{}).Where("dept_id = ?", p.DeptId).Find(&spec).Error
+			list[i].List = spec
+		//case 4:
+		//	var dosageForm []models.DosageForm
+		//	err = db.DB.Model(&models.DosageForm{}).Where("dept_id = ?", p.DeptId).Find(&dosageForm).Error
+		//	list[i].List = dosageForm
+		//case 5:
+		//	var unit []models.Unit
+		//	err = db.DB.Model(&models.Unit{}).Where("dept_id = ?", p.DeptId).Find(&unit).Error
+		//	list[i].List = unit
+		default:
+			list[i].List = nil
+		}
+
+	}
+
+	for i, v := range list {
+		if list[i].FieldName != models.FieldProductID &&
+			list[i].FieldName != models.FieldEnterpriseID &&
+			list[i].FieldName != models.FieldSpecID &&
+			list[i].FieldName != models.FieldBatchNumber &&
+			list[i].FieldName != models.FieldExpiryDate {
+			continue
+		}
+		*res = append(*res, v)
+	}
+	*count = int64(len(*res))
+	return nil
+}
+
+// Insert 创建MedicineTemplate对象
+func (e *MedicineTemplate) Insert(c *dto.MedicineTemplateInsertReq) error {
+	var err error
+	var data models.MedicineTemplate
+	err = db.DB.Where("name = ? AND dept_id = ?", c.Name, c.DeptId).Find(&data).Error
+	if data.Id > 0 {
+		return errors.New("标签名称已存在")
+	}
+	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	c.Generate(&data)
+	data.State = 2 // 用户添加
+	en, err := translate.Translate(c.Name)
+	if err != nil {
+		logs.Error("火山引擎翻译出错: %s", err)
+		return errors.New("火山引擎出错,请联系管理员")
+	}
+	data.FieldName = utils.WordsToSnake(en)
+	tx := db.DB.Begin()
+	err = tx.Create(&data).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	// 向表内新增字段
+	err = tx.Exec(models.GetInsertMedicineTemplateSql(c.DeptId, c.Type, data.FieldName)).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	tx.Commit()
+	c.Id = data.Id
+	return nil
+}
+
+// Update 修改MedicineTemplate对象
+func (e *MedicineTemplate) Update(c *dto.MedicineTemplateUpdateReq) error {
+	var mtModel = models.MedicineTemplate{}
+	err := db.DB.First(&mtModel, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return global.UpdateNotFoundOrNoPermissionErr
+		}
+		return global.UpdateFailedErr
+	}
+
+	tx := db.DB.Begin()
+	// 修改列名 系统初始化的不能修改字段名
+	if mtModel.Name != c.Name && mtModel.State != 1 {
+		en, err := translate.Translate(c.Name)
+		if err != nil {
+			tx.Rollback()
+			logs.Error("火山引擎翻译出错: %s", err)
+			return errors.New("火山引擎出错,请联系管理员")
+		}
+		newFieldName := utils.WordsToSnake(en)
+		err = tx.Exec(models.GetUpdateMedicineTemplateSql(mtModel.DeptId, mtModel.Type, mtModel.FieldName, newFieldName)).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		mtModel.FieldName = newFieldName
+	}
+
+	c.Generate(&mtModel)
+
+	err = tx.Save(&mtModel).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+	tx.Commit()
+	return nil
+}
+
+// Remove 删除MedicineTemplate
+func (e *MedicineTemplate) Remove(d *dto.MedicineTemplateDeleteReq) error {
+	var err error
+	var mt models.MedicineTemplate
+	var data models.MedicineTemplate
+	err = db.DB.First(&mt, d.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return global.DeleteNotFoundOrNoPermissionErr
+		}
+		return global.DeleteFailedErr
+	}
+	tx := db.DB.Begin()
+	result := tx.Model(&data).Delete(&data, d.GetId())
+	if err = result.Error; err != nil {
+		tx.Rollback()
+		logs.Error("Delete error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 删除表内字段
+	err = tx.Exec(models.GetDeleteMedicineTemplateSql(mt.DeptId, mt.FieldName)).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	tx.Commit()
+
+	return nil
+}
+
+// GetColumns 获取前端渲染表格-表头
+func (e *MedicineTemplate) GetColumns(columnsList *[]dto.MedicineColumnsRes, p *actions.DataPermission) error {
+	var err error
+	var data models.MedicineTemplate
+	var list []models.MedicineTemplate
+
+	err = db.DB.Model(&data).
+		Scopes(
+			actions.Permission(data.TableName(), p),
+		).
+		Where("`show` = 1").
+		Order("sort").
+		Find(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return global.GetFailedErr
+	}
+
+	for _, v := range list {
+
+		fieldName := v.FieldName
+		switch v.FieldName {
+		case models.FieldProductID:
+			fieldName = models.FieldProductName
+		case models.FieldEnterpriseID:
+			fieldName = models.FieldEnterpriseName
+		case models.FieldSpecID:
+			fieldName = models.FieldSpecName
+		case models.FieldUnitID:
+			fieldName = models.FieldUnitName
+		case models.FieldDosageFormID:
+			fieldName = models.FieldDosageFormName
+		}
+		*columnsList = append(*columnsList, dto.MedicineColumnsRes{
+			Label: v.Name,
+			Prop:  fieldName,
+			Width: v.Width,
+		})
+	}
+
+	return nil
+}

+ 21 - 3
services/product.go

@@ -57,9 +57,19 @@ func (e *Product) Get(d *dto.ProductGetReq, productModel *models.Product, p *act
 func (e *Product) Insert(c *dto.ProductInsertReq) error {
 	var err error
 	var data models.Product
-	c.Generate(&data)
-	err = db.DB.Create(&data).Error
+	err = db.DB.Where("name = ? AND dept_id = ?", c.Name, c.DeptId).First(&data).Error
 	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+			c.Id = data.Id
+			return nil
+		}
 		logs.Error("db error: %s", err)
 		return global.CreateFailedErr
 	}
@@ -93,7 +103,15 @@ func (e *Product) Update(c *dto.ProductUpdateReq) error {
 func (e *Product) Remove(d *dto.ProductDeleteReq) error {
 	var err error
 	var data models.Product
-
+	var Count int64
+	err = db.DB.Table(models.GetMedicineInfoTableName(d.DeptId)).Where("unit_id = ?", d.GetId()).Count(&Count).Error
+	if err != nil {
+		logs.Error("Delete error: %s", err)
+		return global.DeleteFailedErr
+	}
+	if Count > 0 {
+		return errors.New("该产品已被关联,禁止删除")
+	}
 	result := db.DB.Model(&data).Delete(&data, d.GetId())
 	if err = result.Error; err != nil {
 		logs.Error("Delete error: %s", err)

+ 230 - 0
services/sales.go

@@ -0,0 +1,230 @@
+package services
+
+import (
+	cDto "Medical_ERP/common/dto"
+	"Medical_ERP/common/global"
+	db "Medical_ERP/common/initialize"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"Medical_ERP/utils"
+	"github.com/beego/beego/v2/core/logs"
+	"strconv"
+)
+
+type Sales struct {
+}
+
+// SalesList 销售管理列表 包含销售数量、购进单价、销售单价、销售单价、销售金额
+func (e *Sales) SalesList(c *dto.SalesPageReq, deptId int) (list []map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "stock_out.dept_id = " + strconv.Itoa(deptId) + " AND unit_price > 0 "
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND " + mtable + ".enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND " + mtable + ".batch_number = " + c.BatchNumber
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND stock_out.date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND stock_out.date <= '" + c.EndDate + "'"
+	}
+	if len(c.ReceivingUnit) > 0 {
+		whereSql += " AND stock_out.receiving_unit like '%" + c.ReceivingUnit + "%'"
+	}
+	err = db.DB.Table("stock_out").
+		Scopes(
+			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+		).
+		Joins("left join " + mtable + " on stock_out.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Limit(-1).Offset(-1).
+		Count(&count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		list[i]["purchase_unit_price"] = utils.ToFloat64(list[i]["purchase_unit_price"])
+		list[i]["sales_unit_price"] = utils.ToFloat64(list[i]["sales_unit_price"])
+		// 购进金额
+		list[i]["purchase_money"] = utils.ToFloat64(list[i]["purchase_unit_price"]) * utils.ToFloat64(list[i]["quantity"])
+		// 销售金额
+		list[i]["sales_money"] = utils.ToFloat64(list[i]["sales_unit_price"]) * utils.ToFloat64(list[i]["quantity"])
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+	}
+	return list, count, nil
+}
+
+// 销售报表
+func (e *Sales) SalesReportList(c *dto.SalesPageReq, deptId int) (list []map[string]interface{}, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "stock_out.dept_id = " + strconv.Itoa(deptId) + " AND unit_price > 0 AND stock_out.deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND stock_out.date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND stock_out.date <= '" + c.EndDate + "'"
+	}
+	if len(c.ReceivingUnit) > 0 {
+		whereSql += " AND stock_out.receiving_unit like '%" + c.ReceivingUnit + "%'"
+	}
+	err = db.DB.Table("stock_out").
+		Select(mtable + ".*,SUM(quantity) AS total").
+		Scopes(
+			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+		).
+		Joins("left join " + mtable + " on stock_out.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Group("stock_out.medicine_id").
+		Scan(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		list[i]["purchase_unit_price"] = utils.ToFloat64(list[i]["purchase_unit_price"])
+		list[i]["sales_unit_price"] = utils.ToFloat64(list[i]["sales_unit_price"])
+		// 购进金额
+		list[i]["purchase_money"] = utils.ToFloat64(list[i]["purchase_unit_price"]) * utils.ToFloat64(list[i]["total"])
+		// 销售金额
+		list[i]["sales_money"] = utils.ToFloat64(list[i]["sales_unit_price"]) * utils.ToFloat64(list[i]["total"])
+		list[i]["profit_money"] = utils.ToFloat64(list[i]["sales_money"]) - utils.ToFloat64(list[i]["purchase_money"])
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+	}
+	return list, nil
+}
+
+// SalesOrderList 销售订单列表 包含销售数量、销售单价、销售金额
+func (e *Sales) SalesOrderList(c *dto.SalesOrderPageReq, deptId int) (list []map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "stock_out.dept_id = " + strconv.Itoa(deptId) + " AND unit_price > 0 AND stock_out.deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND " + mtable + ".enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND " + mtable + ".batch_number = " + c.BatchNumber
+	}
+	if len(c.Date) > 0 {
+		whereSql += " AND stock_out.date = '" + c.Date + "'"
+	}
+	if len(c.ReceivingUnit) > 0 {
+		whereSql += " AND stock_out.receiving_unit like '%" + c.ReceivingUnit + "%'"
+	}
+	err = db.DB.Table("stock_out").
+		Scopes(
+			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+		).
+		Joins("left join " + mtable + " on stock_out.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Limit(-1).Offset(-1).
+		Count(&count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		list[i]["sales_money"] = utils.ToFloat64(list[i]["unit_price"]) * utils.ToFloat64(list[i]["quantity"])
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+	}
+	return list, count, nil
+}
+
+// SalesStockOutExcel 销售订单列表 包含销售数量、销售单价、销售金额
+func (e *Sales) SalesStockOutExcel(c *dto.SalesStockOutExcelReq, deptId int) (list []map[string]interface{}, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "stock_out.dept_id = " + strconv.Itoa(deptId) + " AND unit_price > 0 AND stock_out.deleted_at is null"
+	if len(c.Date) > 0 {
+		whereSql += " AND stock_out.date = '" + c.Date + "'"
+	}
+	if len(c.ReceivingUnit) > 0 {
+		whereSql += " AND stock_out.receiving_unit like '%" + c.ReceivingUnit + "%'"
+	}
+	err = db.DB.Table("stock_out").
+		Joins("left join " + mtable + " on stock_out.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		list[i]["sales_money"] = utils.ToFloat64(list[i]["unit_price"]) * utils.ToFloat64(list[i]["quantity"])
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+	}
+	return list, nil
+}

+ 22 - 4
services/spec.go

@@ -57,9 +57,19 @@ func (e *Spec) Get(d *dto.SpecGetReq, productModel *models.Spec, p *actions.Data
 func (e *Spec) Insert(c *dto.SpecInsertReq) error {
 	var err error
 	var data models.Spec
-	c.Generate(&data)
-	err = db.DB.Create(&data).Error
+	err = db.DB.Where("name = ? AND dept_id = ?", c.Name, c.DeptId).First(&data).Error
 	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+			c.Id = data.Id
+			return nil
+		}
 		logs.Error("db error: %s", err)
 		return global.CreateFailedErr
 	}
@@ -78,7 +88,7 @@ func (e *Spec) Update(c *dto.SpecUpdateReq) error {
 		}
 		return global.UpdateFailedErr
 	}
-	
+
 	if c.DeptId != productModel.DeptId {
 		return global.UpdateNotFoundOrNoPermissionErr
 	}
@@ -97,7 +107,15 @@ func (e *Spec) Update(c *dto.SpecUpdateReq) error {
 func (e *Spec) Remove(d *dto.SpecDeleteReq) error {
 	var err error
 	var data models.Spec
-
+	var Count int64
+	err = db.DB.Table(models.GetMedicineInfoTableName(d.DeptId)).Where("unit_id = ?", d.GetId()).Count(&Count).Error
+	if err != nil {
+		logs.Error("Delete error: %s", err)
+		return global.DeleteFailedErr
+	}
+	if Count > 0 {
+		return errors.New("该规格已被关联,禁止删除")
+	}
 	result := db.DB.Model(&data).Delete(&data, d.GetId())
 	if err = result.Error; err != nil {
 		logs.Error("Delete error: %s", err)

+ 273 - 0
services/stock.go

@@ -0,0 +1,273 @@
+package services
+
+import (
+	"Medical_ERP/common/global"
+	db "Medical_ERP/common/initialize"
+	model2 "Medical_ERP/common/model"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"errors"
+	"github.com/beego/beego/v2/core/logs"
+	"gorm.io/gorm"
+)
+
+type Stock struct {
+}
+
+// StockIn 入库
+func (e *Stock) StockIn(c *dto.StockInInsertReq) error {
+	var err error
+
+	// 检查药品信息是否已存在
+	medicineInfo, err := e.GetMedicineInfo(c.ProductID, c.EnterpriseID, c.SpecID, c.ApprovalNumber, c.ExpiryDate)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	tx := db.DB.Begin()
+	// 如果药品信息不存在,则创建新的药品信息
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		medicineInfo = models.MedicineInfo{
+			ProductID:           c.ProductID,
+			EnterpriseID:        c.EnterpriseID,
+			SpecID:              c.SpecID,
+			UnitID:              c.UnitID,
+			DosageFormID:        c.DosageFormID,
+			ApprovalNumber:      c.ApprovalNumber,
+			QualificationNumber: c.QualificationNumber,
+			ProductionDate:      c.ProductionDate,
+			ExpiryDate:          c.ExpiryDate,
+			BatchNumber:         c.BatchNumber,
+		}
+		medicineInfo.SetDeptId(c.DeptId)
+		medicineInfo.SetCreateBy(c.CreateBy)
+		err = tx.Create(&medicineInfo).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+	}
+
+	// 进行入库操作
+	stockInRecord := models.StockIn{
+		MedicineID:     medicineInfo.Id,
+		Quantity:       c.Quantity,
+		UnitPrice:      c.UnitPrice,
+		Operator:       c.Operator,
+		ForwardingUnit: c.ForwardingUnit,
+		Date:           c.Date,
+	}
+	err = tx.Create(&stockInRecord).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	c.Id = stockInRecord.Id
+
+	var inventory models.MedicineInventory
+	err = tx.First(&inventory, "medicine_id = ?", medicineInfo.Id).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+
+	// 添加库存记录
+	inventory = models.MedicineInventory{
+		MedicineID:     medicineInfo.Id,
+		TotalIn:        c.Quantity,
+		StockInID:      stockInRecord.Id,
+		TotalOut:       0,
+		StockOutID:     0,
+		Balance:        c.Quantity,
+		ForwardingUnit: c.ForwardingUnit,
+	}
+	inventory.Balance += c.Quantity
+	err = tx.Create(&inventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	return nil
+}
+
+// 出库
+func (e *Stock) StockInStockInList(c *dto.StockInInsertReq) error {
+	var err error
+
+	// 检查药品信息是否已存在
+	medicineInfo, err := e.GetMedicineInfo(c.ProductID, c.EnterpriseID, c.SpecID, c.ApprovalNumber, c.ExpiryDate)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	tx := db.DB.Begin()
+	// 如果药品信息不存在,则创建新的药品信息
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		medicineInfo = models.MedicineInfo{
+			ProductID:           c.ProductID,
+			EnterpriseID:        c.EnterpriseID,
+			SpecID:              c.SpecID,
+			UnitID:              c.UnitID,
+			DosageFormID:        c.DosageFormID,
+			ApprovalNumber:      c.ApprovalNumber,
+			QualificationNumber: c.QualificationNumber,
+			ProductionDate:      c.ProductionDate,
+			ExpiryDate:          c.ExpiryDate,
+			BatchNumber:         c.BatchNumber,
+		}
+		medicineInfo.SetDeptId(c.DeptId)
+		medicineInfo.SetCreateBy(c.CreateBy)
+		err = tx.Create(&medicineInfo).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+	}
+
+	// 进行入库操作
+	stockInRecord := models.StockIn{
+		MedicineID:     medicineInfo.Id,
+		Quantity:       c.Quantity,
+		UnitPrice:      c.UnitPrice,
+		Operator:       c.Operator,
+		ForwardingUnit: c.ForwardingUnit,
+		Date:           c.Date,
+	}
+	err = tx.Create(&stockInRecord).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	c.Id = stockInRecord.Id
+
+	var inventory models.MedicineInventory
+	err = tx.First(&inventory, "medicine_id = ?", medicineInfo.Id).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+
+	// 添加库存记录
+	inventory = models.MedicineInventory{
+		MedicineID: medicineInfo.Id,
+		TotalIn:    c.Quantity,
+		StockInID:  stockInRecord.Id,
+		TotalOut:   0,
+		StockOutID: 0,
+		Balance:    c.Quantity,
+	}
+	inventory.Balance += c.Quantity
+	err = tx.Create(&inventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	return nil
+}
+
+func (e *Stock) StockOut(c *dto.StockOutInsertReq) error {
+	var err error
+
+	// 检查药品信息是否已存在
+	medicineInfo, err := e.GetMedicineInfo(c.ProductID, c.EnterpriseID, c.SpecID, c.ApprovalNumber, c.ExpiryDate)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	// 如果药品信息不存在,提示错误,不能出库
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return errors.New("药品信息不存在")
+
+	}
+	tx := db.DB.Begin()
+	// 进行入库操作
+	stockOutRecord := models.StockOut{
+		MedicineID:    medicineInfo.Id,
+		Quantity:      c.Quantity,
+		UnitPrice:     c.UnitPrice,
+		Operator:      c.Operator,
+		ReceivingUnit: c.ReceivingUnit,
+		Date:          c.Date,
+	}
+	err = tx.Create(&stockOutRecord).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	c.Id = stockOutRecord.Id
+
+	var inventory models.MedicineInventory
+	err = tx.First(&inventory, "medicine_id = ?", medicineInfo.Id).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+
+	// 添加库存记录
+	inventory = models.MedicineInventory{
+		MedicineID:    medicineInfo.Id,
+		TotalIn:       0,
+		StockInID:     0,
+		TotalOut:      c.Quantity,
+		StockOutID:    stockOutRecord.Id,
+		Balance:       c.Quantity,
+		ReceivingUnit: c.ReceivingUnit,
+		Operator:      c.Operator,
+		Date:          c.Date,
+		ControlBy: model2.ControlBy{
+			DeptId:   c.DeptId,
+			CreateBy: c.CreateBy,
+		},
+	}
+	inventory.Balance -= c.Quantity
+	err = tx.Create(&inventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	return nil
+}
+
+func (e *Stock) GetMedicineInfo(productId, enterpriseId, specId int, approvalNumber, expiryDate string) (medicineInfo models.MedicineInfo, err error) {
+	err = db.DB.Where("product_id = ? AND enterprise_id = ? AND spec_id = ?"+
+		"AND approval_number = ? AND expiry_date", productId, enterpriseId, specId, approvalNumber, expiryDate).
+		First(&medicineInfo).Error
+	return
+}
+
+func (e *Stock) InsertMedicineInfo(c *dto.StockInInsertReq) (medicineInfo models.MedicineInfo, err error) {
+
+	data := models.MedicineInfo{
+		ProductID:           c.ProductID,
+		EnterpriseID:        c.EnterpriseID,
+		SpecID:              c.SpecID,
+		UnitID:              c.UnitID,
+		DosageFormID:        c.DosageFormID,
+		ApprovalNumber:      c.ApprovalNumber,
+		QualificationNumber: c.QualificationNumber,
+		ProductionDate:      c.ProductionDate,
+		ExpiryDate:          c.ExpiryDate,
+		BatchNumber:         c.BatchNumber,
+	}
+	data.SetDeptId(c.DeptId)
+	data.SetCreateBy(c.CreateBy)
+	err = db.DB.Create(&data).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return data, global.CreateFailedErr
+	}
+	return data, nil
+
+}

+ 1487 - 0
services/stock_template.go

@@ -0,0 +1,1487 @@
+package services
+
+import (
+	cDto "Medical_ERP/common/dto"
+	"Medical_ERP/common/global"
+	db "Medical_ERP/common/initialize"
+	model2 "Medical_ERP/common/model"
+	"Medical_ERP/dto"
+	"Medical_ERP/models"
+	"Medical_ERP/utils"
+	"errors"
+	"fmt"
+	"github.com/beego/beego/v2/core/logs"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type StockTemplate struct {
+}
+
+// GetMedicineInfo 通过名称、生产企业、规格、批号查询药品信息
+func (e *StockTemplate) GetMedicineInfo(deptId int, medicine map[string]interface{}) (medicineInfo map[string]interface{}, err error) {
+
+	productID := utils.ToInt(medicine[models.FieldProductID])
+	enterpriseID := utils.ToInt(medicine[models.FieldEnterpriseID])
+	specID := utils.ToInt(medicine[models.FieldSpecID])
+	batchNumber := medicine[models.FieldBatchNumber]
+	sql := `SELECT * FROM ` + models.GetMedicineInfoTableName(deptId) + ` WHERE product_id = ? AND enterprise_id = ? AND spec_id = ? AND batch_number = ? `
+	err = db.DB.Raw(sql, productID, enterpriseID, specID, batchNumber).Scan(&medicineInfo).Error
+	return
+}
+
+// BatchStockTemplateIn 批量入库
+func (e *StockTemplate) BatchStockTemplateIn(req *dto.BatchStockTemplateInInsertReq) error {
+	//var err error
+	tx := db.DB.Begin()
+
+	for _, c := range req.StockInList {
+		// 检查药品信息是否已存在
+		medicineInfo, err := e.GetMedicineInfo(req.DeptId, c.MedicineInfo)
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		var medicineInfoId int
+
+		// 如果药品信息不存在,则创建新的药品信息
+		if medicineInfo == nil {
+			sql := "INSERT INTO " + models.GetMedicineInfoTableName(req.DeptId) + " SET "
+			for k, v := range c.MedicineInfo {
+				sql += fmt.Sprintf("`%s`='%v',", k, v)
+			}
+			sql += fmt.Sprintf("`%s`='%v',", "purchase_unit_price", c.UnitPrice)
+
+			sql = sql[:len(sql)-1]
+			err = tx.Exec(sql).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+			err = tx.Raw("SELECT LAST_INSERT_ID()").Scan(&medicineInfoId).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+		} else {
+			medicineInfoId = utils.ToInt(medicineInfo["id"])
+			err = tx.Table(models.GetMedicineInfoTableName(req.DeptId)).Where("id = ?", medicineInfoId).Update("purchase_unit_price", c.UnitPrice).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+		}
+
+		// 进行入库操作
+		stockInRecord := models.StockIn{
+			MedicineID:     medicineInfoId,
+			Quantity:       c.Quantity,
+			UnitPrice:      c.UnitPrice,
+			Operator:       c.Operator,
+			ForwardingUnit: c.ForwardingUnit,
+			Date:           c.Date,
+			ControlBy: model2.ControlBy{
+				DeptId:   req.DeptId,
+				CreateBy: req.CreateBy,
+			},
+		}
+		err = tx.Create(&stockInRecord).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		c.Id = stockInRecord.Id
+
+		var inventory models.MedicineInventory
+		err = tx.Last(&inventory, "medicine_id = ?", medicineInfoId).Error
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+
+		// 添加库存记录
+		data := models.MedicineInventory{
+			MedicineID:     medicineInfoId,
+			TotalIn:        c.Quantity,
+			StockInID:      stockInRecord.Id,
+			TotalOut:       0,
+			StockOutID:     0,
+			Balance:        inventory.Balance + c.Quantity,
+			ForwardingUnit: c.ForwardingUnit,
+			Operator:       c.Operator,
+			Date:           c.Date,
+			ControlBy: model2.ControlBy{
+				DeptId:   req.DeptId,
+				CreateBy: req.CreateBy,
+			},
+		}
+		err = tx.Create(&data).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+	}
+
+	tx.Commit()
+	return nil
+}
+
+// StockTemplateIn 入库
+func (e *StockTemplate) StockTemplateIn(c *dto.StockTemplateInInsertReq) error {
+	var err error
+	// 检查药品信息是否已存在
+	medicineInfo, err := e.GetMedicineInfo(c.DeptId, c.MedicineInfo)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	var medicineInfoId int
+
+	tx := db.DB.Begin()
+	// 如果药品信息不存在,则创建新的药品信息
+	if medicineInfo == nil {
+		sql := "INSERT INTO " + models.GetMedicineInfoTableName(c.DeptId) + " SET "
+		for k, v := range c.MedicineInfo {
+			sql += fmt.Sprintf("`%s`='%v',", k, v)
+		}
+		sql += fmt.Sprintf("`%s`='%v',", "purchase_unit_price", c.UnitPrice)
+
+		sql = sql[:len(sql)-1]
+		err = tx.Exec(sql).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		err = tx.Raw("SELECT LAST_INSERT_ID()").Scan(&medicineInfoId).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+	} else {
+		medicineInfoId = utils.ToInt(medicineInfo["id"])
+		err = tx.Table(models.GetMedicineInfoTableName(c.DeptId)).Where("id = ?", medicineInfoId).Update("purchase_unit_price", c.UnitPrice).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+	}
+
+	// 进行入库操作
+	stockInRecord := models.StockIn{
+		MedicineID:     medicineInfoId,
+		Quantity:       c.Quantity,
+		UnitPrice:      c.UnitPrice,
+		Operator:       c.Operator,
+		ForwardingUnit: c.ForwardingUnit,
+		Date:           c.Date,
+		ControlBy: model2.ControlBy{
+			DeptId:   c.DeptId,
+			CreateBy: c.CreateBy,
+		},
+	}
+	err = tx.Create(&stockInRecord).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	c.Id = stockInRecord.Id
+
+	var inventory models.MedicineInventory
+	err = tx.Last(&inventory, "medicine_id = ?", medicineInfoId).Error
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+
+	// 添加库存记录
+	data := models.MedicineInventory{
+		MedicineID:     medicineInfoId,
+		TotalIn:        c.Quantity,
+		StockInID:      stockInRecord.Id,
+		TotalOut:       0,
+		StockOutID:     0,
+		Balance:        inventory.Balance + c.Quantity,
+		ForwardingUnit: c.ForwardingUnit,
+		Operator:       c.Operator,
+		Date:           c.Date,
+		ControlBy: model2.ControlBy{
+			DeptId:   c.DeptId,
+			CreateBy: c.CreateBy,
+		},
+	}
+	err = tx.Create(&data).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	tx.Commit()
+	return nil
+} // StockTemplateIn 入库
+func (e *StockTemplate) StockTemplateInEdit(c *dto.StockTemplateInEditReq) error {
+	var err error
+	// 检查药品信息是否已存在
+	medicineInfo, err := e.GetMedicineInfo(c.DeptId, c.MedicineInfo)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+	var medicineInfoId int
+
+	tx := db.DB.Begin()
+	// 如果药品信息不存在,则创建新的药品信息
+	if medicineInfo == nil {
+		sql := "INSERT INTO " + models.GetMedicineInfoTableName(c.DeptId) + " SET "
+		for k, v := range c.MedicineInfo {
+			sql += fmt.Sprintf("`%s`='%v',", k, v)
+		}
+		sql += fmt.Sprintf("`%s`='%v',", "purchase_unit_price", c.UnitPrice)
+
+		sql = sql[:len(sql)-1]
+		err = tx.Exec(sql).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+		err = tx.Raw("SELECT LAST_INSERT_ID()").Scan(&medicineInfoId).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+	} else {
+		medicineInfoId = utils.ToInt(medicineInfo["id"])
+		c.MedicineInfo["purchase_unit_price"] = c.UnitPrice
+		err = tx.Table(models.GetMedicineInfoTableName(c.DeptId)).Where("id = ?", medicineInfoId).Updates(c.MedicineInfo).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+	}
+	var StockInInfo models.StockIn
+	// 查询入库信息
+	err = tx.Model(StockInInfo).Where("id = ? AND dept_id = ?", c.Id, c.DeptId).Find(&StockInInfo).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+
+	// 修改入库信息
+	stockInRecord := models.StockIn{
+		MedicineID:     medicineInfoId,
+		Quantity:       c.Quantity,
+		UnitPrice:      c.UnitPrice,
+		Operator:       c.Operator,
+		ForwardingUnit: c.ForwardingUnit,
+		Date:           c.Date,
+		ControlBy: model2.ControlBy{
+			UpdateBy: c.UpdateBy,
+		},
+	}
+	err = tx.Where("id = ?", c.Id).Updates(&stockInRecord).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+	// 1 查询收发记录
+	medicineInventory := models.MedicineInventory{}
+	err = tx.Model(models.MedicineInventory{}).Where("stock_in_id = ?", c.Id).First(&medicineInventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+	// 修改为同一药品id
+	if StockInInfo.MedicineID == medicineInfoId {
+		// 数量相等 只更改入库信息 (购入单价、经办人、发货单位、入库日期)
+		if StockInInfo.Quantity != c.Quantity {
+			// 数量不相等 修改收发记录
+
+			// 2 修改数量
+			err = tx.Model(models.MedicineInventory{}).Where("id = ?", medicineInventory.Id).Updates(
+				map[string]interface{}{
+					"total_in":  c.Quantity,
+					"update_by": c.UpdateBy,
+				}).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.UpdateFailedErr
+			}
+
+			// 3、修改的数量差
+			err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id >= ?", medicineInfoId, medicineInventory.Id).
+				UpdateColumn("balance", gorm.Expr("balance + ?", c.Quantity-medicineInventory.TotalIn)).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.UpdateFailedErr
+			}
+
+		}
+	} else {
+
+		// 2 查询收发记录
+		newMedicineInventory := models.MedicineInventory{}
+		err = tx.Model(models.MedicineInventory{}).Where("id < ? AND medicine_id = ?", medicineInventory.Id, medicineInfoId).Last(&newMedicineInventory).Error
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+		// 2 修改入库数量、药品id、结余数量
+		err = tx.Model(models.MedicineInventory{}).Where("id = ?", medicineInventory.Id).Updates(
+			map[string]interface{}{
+				"medicine_id": medicineInfoId,
+				"total_in":    c.Quantity,
+				"balance":     newMedicineInventory.Balance + c.Quantity,
+				"update_by":   c.UpdateBy,
+			}).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+
+		// 查询原药品id当前收发记录结余最小值
+		var balance *int
+		err = tx.Model(&models.MedicineInventory{}).Select("MIN(balance)").
+			Where("id >= ? AND medicine_id = ?", medicineInventory.Id, StockInInfo.MedicineID).Scan(&balance).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+
+		if balance != nil && *balance-c.Quantity < 0 {
+			tx.Rollback()
+			return errors.New("修改后将导致库存记录数据为负数,禁止修改")
+		}
+
+		// 3、修改原药品id的数量差
+		err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id > ?", medicineInventory.MedicineID, medicineInventory.Id).
+			UpdateColumn("balance", gorm.Expr("balance - ?", c.Quantity)).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+		// 4、修改新药品id的数量差
+		err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id > ?", medicineInfoId, medicineInventory.Id).
+			UpdateColumn("balance", gorm.Expr("balance + ?", c.Quantity)).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+	}
+
+	tx.Commit()
+	return nil
+}
+func (e *StockTemplate) StockTemplateInDelete(c *dto.StockTemplateInDeleteReq) error {
+	var err error
+
+	var StockInInfo models.StockIn
+	tx := db.DB.Begin()
+	// 1 查询入库信息
+	err = tx.Model(StockInInfo).Where("id = ? AND dept_id = ?", c.Id, c.DeptId).Find(&StockInInfo).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 2 查询收发记录
+	medicineInventory := models.MedicineInventory{}
+	err = tx.Model(models.MedicineInventory{}).Where("stock_in_id = ?", c.Id).First(&medicineInventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 3 查询当前收发记录结余最小值
+	var balance *int
+	err = tx.Model(&models.MedicineInventory{}).Select("MIN(balance)").
+		Where("id > ? AND medicine_id = ?", medicineInventory.Id, StockInInfo.MedicineID).Scan(&balance).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+
+	if balance != nil && *balance-medicineInventory.Balance < 0 {
+		tx.Rollback()
+		return errors.New("删除后将导致库存为负数,禁止删除")
+	}
+
+	// 4 删除收发记录
+	err = tx.Model(models.MedicineInventory{}).Where("stock_in_id = ?", c.Id).Delete(&medicineInventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 5 删除入库记录
+	err = tx.Where("id = ?", c.Id).Delete(&StockInInfo).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 6 修改原药品id的数量差
+	err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id > ?", medicineInventory.MedicineID, medicineInventory.Id).
+		UpdateColumn("balance", gorm.Expr("balance - ?", medicineInventory.TotalIn)).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	tx.Commit()
+	return nil
+}
+
+// StockTemplateOut 出库
+func (e *StockTemplate) StockTemplateOut(c *dto.StockTemplateOutInsertReq) error {
+	var err error
+
+	// 检查药品信息是否已存在
+	medicineInfo, err := e.GetMedicineInfo(c.DeptId, c.MedicineInfo)
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	// 如果药品信息不存在,则不能出库
+	if medicineInfo == nil {
+		logs.Error("db error: %s", err)
+		return errors.New("药品信息不存在,禁止出库")
+	}
+	medicineInfoId := utils.ToInt(medicineInfo["id"])
+
+	// 查询库查询信息
+	var mi models.MedicineInventory
+	err = db.DB.Model(mi).Where("medicine_id = ? AND dept_id = ?", medicineInfoId, c.DeptId).Last(&mi).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	if mi.Balance < c.Quantity {
+		return errors.New(fmt.Sprintf("库存量【%d】小于出库库存量【%d】,出库失败", mi.Balance, c.Quantity))
+	}
+
+	tx := db.DB.Begin()
+	err = tx.Table(models.GetMedicineInfoTableName(c.DeptId)).Where("id = ?", medicineInfoId).Update("sales_unit_price", c.UnitPrice).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	// 进行出库操作
+	stockOutRecord := models.StockOut{
+		MedicineID:    medicineInfoId,
+		Quantity:      c.Quantity,
+		UnitPrice:     c.UnitPrice,
+		Operator:      c.Operator,
+		ReceivingUnit: c.ReceivingUnit,
+		Date:          c.Date,
+		ControlBy: model2.ControlBy{
+			DeptId:   c.DeptId,
+			CreateBy: c.CreateBy,
+		},
+	}
+	err = tx.Create(&stockOutRecord).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	c.Id = stockOutRecord.Id
+
+	var inventory models.MedicineInventory
+	err = tx.Last(&inventory, "medicine_id = ?", medicineInfoId).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+
+	// 添加库存记录
+	data := models.MedicineInventory{
+		MedicineID:    medicineInfoId,
+		TotalIn:       0,
+		StockInID:     0,
+		TotalOut:      c.Quantity,
+		StockOutID:    stockOutRecord.Id,
+		Balance:       inventory.Balance - c.Quantity,
+		ReceivingUnit: c.ReceivingUnit,
+		Operator:      c.Operator,
+		Date:          c.Date,
+		ControlBy: model2.ControlBy{
+			DeptId:   c.DeptId,
+			CreateBy: c.CreateBy,
+		},
+	}
+	err = tx.Create(&data).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.CreateFailedErr
+	}
+	tx.Commit()
+	return nil
+}
+
+// StockTemplateOutEdit 修改出库
+func (e *StockTemplate) StockTemplateOutEdit(c *dto.StockTemplateOutEditReq) error {
+	var err error
+	// 检查药品信息是否已存在
+	medicineInfo, err := e.GetMedicineInfo(c.DeptId, c.MedicineInfo)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+	var medicineInfoId int
+
+	tx := db.DB.Begin()
+	// 如果药品信息不存在,则创建新的药品信息
+	if medicineInfo == nil {
+		sql := "INSERT INTO " + models.GetMedicineInfoTableName(c.DeptId) + " SET "
+		for k, v := range c.MedicineInfo {
+			sql += fmt.Sprintf("`%s`='%v',", k, v)
+		}
+		sql += fmt.Sprintf("`%s`='%v',", "purchase_unit_price", c.UnitPrice)
+
+		sql = sql[:len(sql)-1]
+		err = tx.Exec(sql).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+		err = tx.Raw("SELECT LAST_INSERT_ID()").Scan(&medicineInfoId).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+	} else {
+		medicineInfoId = utils.ToInt(medicineInfo["id"])
+		c.MedicineInfo["purchase_unit_price"] = c.UnitPrice
+		err = tx.Table(models.GetMedicineInfoTableName(c.DeptId)).Where("id = ?", medicineInfoId).Updates(c.MedicineInfo).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+	}
+	var StockOutInfo models.StockOut
+	// 查询出库信息
+	err = tx.Model(StockOutInfo).Where("id = ? AND dept_id = ?", c.Id, c.DeptId).Find(&StockOutInfo).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+
+	// 修改入库信息
+	stockOutRecord := models.StockOut{
+		MedicineID:    medicineInfoId,
+		Quantity:      c.Quantity,
+		UnitPrice:     c.UnitPrice,
+		Operator:      c.Operator,
+		ReceivingUnit: c.ReceivingUnit,
+		Date:          c.Date,
+		ControlBy: model2.ControlBy{
+			UpdateBy: c.UpdateBy,
+		},
+	}
+	err = tx.Where("id = ?", c.Id).Updates(&stockOutRecord).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+	// 1 查询收发记录
+	medicineInventory := models.MedicineInventory{}
+	err = tx.Model(models.MedicineInventory{}).Where("stock_out_id = ?", c.Id).First(&medicineInventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.UpdateFailedErr
+	}
+	// 修改为同一药品id
+	if medicineInventory.MedicineID == medicineInfoId {
+		// 数量相等 只更改入库信息 (购入单价、经办人、发货单位、入库日期)
+		if StockOutInfo.Quantity != c.Quantity {
+
+			// 查询收发记录
+			lastMedicineInventory := models.MedicineInventory{}
+			err = tx.Model(models.MedicineInventory{}).Where("id < ? AND medicine_id = ?", medicineInventory.Id, medicineInfoId).Last(&lastMedicineInventory).Error
+			if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.UpdateFailedErr
+			}
+
+			if lastMedicineInventory.Balance < c.Quantity {
+				tx.Rollback()
+				return errors.New("修改后将导致库存记录数据为负数,禁止修改")
+			}
+			// 查询新药品id当前收发记录结余最小值
+			var balance *int
+			err = tx.Model(&models.MedicineInventory{}).Select("MIN(balance)").
+				Where("id >= ? AND medicine_id = ?", medicineInventory.Id, medicineInfoId).Scan(&balance).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.UpdateFailedErr
+			}
+
+			if balance != nil && *balance-c.Quantity+medicineInventory.TotalOut < 0 {
+				tx.Rollback()
+				return errors.New("修改后将导致库存记录数据为负数,禁止修改")
+			}
+
+			// 2 修改数量
+			err = tx.Model(models.MedicineInventory{}).Where("id = ?", medicineInventory.Id).Updates(
+				map[string]interface{}{
+					"total_out": c.Quantity,
+					"update_by": c.UpdateBy,
+				}).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.UpdateFailedErr
+			}
+
+			// 3、修改的数量差
+			err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id >= ?", medicineInfoId, medicineInventory.Id).
+				UpdateColumn("balance", gorm.Expr("balance - ?", c.Quantity-medicineInventory.TotalOut)).Error
+			if err != nil {
+				tx.Rollback()
+				logs.Error("db error: %s", err)
+				return global.UpdateFailedErr
+			}
+
+		}
+	} else {
+
+		// 2 查询收发记录
+		newMedicineInventory := models.MedicineInventory{}
+		err = tx.Model(models.MedicineInventory{}).Where("id < ? AND medicine_id = ?", medicineInventory.Id, medicineInfoId).Last(&newMedicineInventory).Error
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+
+		if newMedicineInventory.Balance < c.Quantity {
+			tx.Rollback()
+			return errors.New("修改后将导致库存记录数据为负数,禁止修改")
+		}
+
+		// 3 查询新药品id当前收发记录结余最小值
+		var balance *int
+		err = tx.Model(&models.MedicineInventory{}).Select("MIN(balance)").
+			Where("id >= ? AND medicine_id = ?", medicineInventory.Id, medicineInfoId).Scan(&balance).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+
+		if balance != nil && *balance-c.Quantity < 0 {
+			tx.Rollback()
+			return errors.New("修改后将导致库存记录数据为负数,禁止修改")
+		}
+		// 2 修改出库数量、药品id、结余数量
+		err = tx.Model(models.MedicineInventory{}).Where("id = ?", medicineInventory.Id).Updates(
+			map[string]interface{}{
+				"medicine_id": medicineInfoId,
+				"total_out":   c.Quantity,
+				"balance":     newMedicineInventory.Balance - c.Quantity,
+				"update_by":   c.UpdateBy,
+			}).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+
+		// 3、修改原药品id的数量差
+		err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id > ?", medicineInventory.MedicineID, medicineInventory.Id).
+			UpdateColumn("balance", gorm.Expr("balance + ?", c.Quantity)).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+		// 4、修改新药品id的数量差
+		err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id > ?", medicineInfoId, medicineInventory.Id).
+			UpdateColumn("balance", gorm.Expr("balance - ?", c.Quantity)).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.UpdateFailedErr
+		}
+	}
+
+	tx.Commit()
+	return nil
+}
+
+// 删除出库
+func (e *StockTemplate) StockTemplateOutDelete(c *dto.StockTemplateOutDeleteReq) error {
+	var err error
+
+	var StockOutInfo models.StockOut
+	tx := db.DB.Begin()
+	// 1 查询入库信息
+	err = tx.Model(StockOutInfo).Where("id = ? AND dept_id = ?", c.Id, c.DeptId).Find(&StockOutInfo).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 2 查询收发记录
+	medicineInventory := models.MedicineInventory{}
+	err = tx.Model(models.MedicineInventory{}).Where("stock_out_id = ?", c.Id).First(&medicineInventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 4 删除收发记录
+	err = tx.Model(models.MedicineInventory{}).Where("stock_out_id = ?", c.Id).Delete(&medicineInventory).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 5 删除入库记录
+	err = tx.Where("id = ?", c.Id).Delete(&StockOutInfo).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	// 6 修改原药品id的数量差
+	err = tx.Model(models.MedicineInventory{}).Where("medicine_id = ? AND id > ?", medicineInventory.MedicineID, medicineInventory.Id).
+		UpdateColumn("balance", gorm.Expr("balance + ?", medicineInventory.TotalOut)).Error
+	if err != nil {
+		tx.Rollback()
+		logs.Error("db error: %s", err)
+		return global.DeleteFailedErr
+	}
+
+	tx.Commit()
+	return nil
+}
+
+// BatchStockTemplateOut 批量出库
+func (e *StockTemplate) BatchStockTemplateOut(req *dto.BatchStockTemplateOutInsertReq) error {
+	tx := db.DB.Begin()
+	for _, c := range req.StockOutList {
+		// 检查药品信息是否已存在
+		medicineInfo, err := e.GetMedicineInfo(req.DeptId, c.MedicineInfo)
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		// 如果药品信息不存在,则不能出库
+		if medicineInfo == nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return errors.New("药品信息不存在,禁止出库")
+		}
+		medicineInfoId := utils.ToInt(medicineInfo["id"])
+
+		// 查询库查询信息
+		var mi models.MedicineInventory
+		err = tx.Model(mi).Where("medicine_id = ? AND dept_id = ?", medicineInfoId, req.DeptId).Last(&mi).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		if mi.Balance < c.Quantity {
+			return errors.New(fmt.Sprintf("库存量【%d】小于出库库存量【%d】,出库失败", mi.Balance, c.Quantity))
+		}
+
+		err = tx.Table(models.GetMedicineInfoTableName(req.DeptId)).Where("id = ?", medicineInfoId).Update("sales_unit_price", c.UnitPrice).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		// 进行出库操作
+		stockOutRecord := models.StockOut{
+			MedicineID:    medicineInfoId,
+			Quantity:      c.Quantity,
+			UnitPrice:     c.UnitPrice,
+			Operator:      c.Operator,
+			ReceivingUnit: c.ReceivingUnit,
+			Date:          c.Date,
+			ControlBy: model2.ControlBy{
+				DeptId:   req.DeptId,
+				CreateBy: req.CreateBy,
+			},
+		}
+		err = tx.Create(&stockOutRecord).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+		c.Id = stockOutRecord.Id
+
+		var inventory models.MedicineInventory
+		err = tx.Last(&inventory, "medicine_id = ?", medicineInfoId).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+
+		// 添加库存记录
+		data := models.MedicineInventory{
+			MedicineID:    medicineInfoId,
+			TotalIn:       0,
+			StockInID:     0,
+			TotalOut:      c.Quantity,
+			StockOutID:    stockOutRecord.Id,
+			Balance:       inventory.Balance - c.Quantity,
+			ReceivingUnit: c.ReceivingUnit,
+			Operator:      c.Operator,
+			Date:          c.Date,
+			ControlBy: model2.ControlBy{
+				DeptId:   req.DeptId,
+				CreateBy: req.CreateBy,
+			},
+		}
+		err = tx.Create(&data).Error
+		if err != nil {
+			tx.Rollback()
+			logs.Error("db error: %s", err)
+			return global.CreateFailedErr
+		}
+	}
+
+	tx.Commit()
+	return nil
+}
+
+// StockTemplateInList 出库列表
+func (e *StockTemplate) StockTemplateInList(c *dto.StockTemplateInPageReq, deptId int) (list []map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "stock_in.dept_id = " + strconv.Itoa(deptId) + " AND stock_in.deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND " + mtable + ".enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND " + mtable + ".batch_number = " + c.BatchNumber
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND stock_in.date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND stock_in.date <= '" + c.EndDate + "'"
+	}
+	if len(c.ForwardingUnit) > 0 {
+		whereSql += " AND stock_in.forwarding_unit like '%" + c.ForwardingUnit + "%'"
+	}
+	err = db.DB.Table("stock_in").
+		Select(mtable + ".*,stock_in.*,stock_in.id AS id").
+		Scopes(
+			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+		).
+		Joins("left join " + mtable + " on stock_in.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Limit(-1).Offset(-1).
+		Count(&count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+		if date, ok := list[i][models.FieldExpiryDate]; ok && date != nil {
+			list[i][models.FieldExpiryDate] = date.(time.Time).Format("2006-01-02")
+		}
+	}
+	return list, count, nil
+}
+
+// StockTemplateInList 出库列表
+func (e *StockTemplate) StockTemplateOutList(c *dto.StockTemplateOutPageReq, deptId int) (list []map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "stock_out.dept_id = " + strconv.Itoa(deptId) + " AND stock_out.deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND " + mtable + ".enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND " + mtable + ".batch_number = " + c.BatchNumber
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND stock_out.date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND stock_out.date <= '" + c.EndDate + "'"
+	}
+	if len(c.ReceivingUnit) > 0 {
+		whereSql += " AND stock_out.receiving_unit like '%" + c.ReceivingUnit + "%'"
+	}
+	err = db.DB.Table("stock_out").
+		Select(mtable + ".*,stock_out.*,stock_out.id AS id").
+		Scopes(
+			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+		).
+		Joins("left join " + mtable + " on stock_out.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Limit(-1).Offset(-1).
+		Count(&count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+		if date, ok := list[i][models.FieldExpiryDate]; ok && date != nil {
+			list[i][models.FieldExpiryDate] = date.(time.Time).Format("2006-01-02")
+		}
+	}
+	return list, count, nil
+}
+
+// StockTemplateInventoryList 库存统计列表
+func (e *StockTemplate) StockTemplateInventoryList(c *dto.StockTemplateInventoryPageReq, deptId int) (list []map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "medicine_inventory.dept_id = " + strconv.Itoa(deptId) + " AND medicine_inventory.deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND " + mtable + ".enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND " + mtable + ".batch_number = " + c.BatchNumber
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND medicine_inventory.date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND medicine_inventory.date <= '" + c.EndDate + "'"
+	}
+	if len(c.SendReceiveUnit) > 0 {
+		whereSql += " AND (medicine_inventory.forwarding_unit like '%" + c.SendReceiveUnit + "%' OR medicine_inventory.receiving_unit like '%" + c.SendReceiveUnit + "%')"
+	}
+	err = db.DB.Table("medicine_inventory").
+		Select(mtable + ".*,medicine_inventory.*,medicine_inventory.id AS id").
+		Scopes(
+			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+		).
+		Joins("left join " + mtable + " on medicine_inventory.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Limit(-1).Offset(-1).
+		Count(&count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+		if date, ok := list[i][models.FieldExpiryDate]; ok && date != nil {
+			list[i][models.FieldExpiryDate] = date.(time.Time).Format("2006-01-02")
+		}
+	}
+	return list, count, nil
+}
+
+// StockTemplateInventoryList 库存统计列表
+func (e *StockTemplate) StockTemplateInventoryExcel(c *dto.StockTemplateInventoryExcelReq, deptId int) (list []map[string]interface{}, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "medicine_inventory.dept_id = " + strconv.Itoa(deptId) + " AND medicine_inventory.deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND " + mtable + ".enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND " + mtable + ".batch_number = " + c.BatchNumber
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND medicine_inventory.date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND medicine_inventory.date <= '" + c.EndDate + "'"
+	}
+	err = db.DB.Table("medicine_inventory").
+		Joins("left join " + mtable + " on medicine_inventory.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+	}
+	return list, nil
+}
+
+// StockUnitList 发货单位,收货单位 去重列表
+func (e *StockTemplate) StockUnitList(c *dto.StockUnitListReq, deptId int) (list []string, err error) {
+	whereSql := "dept_id = " + strconv.Itoa(deptId) + " AND deleted_at is null"
+
+	switch c.Type {
+	case 1:
+		if len(c.Name) > 0 {
+			whereSql += " AND forwarding_unit like '%" + c.Name + "%'"
+		}
+		whereSql += " AND forwarding_unit is not null"
+		err = db.DB.Table("stock_in").
+			Distinct("forwarding_unit").
+			Where(whereSql).
+			Scan(&list).Error
+		if err != nil {
+			logs.Error("db error: %s ", err)
+			return list, global.GetFailedErr
+		}
+		return
+	case 2:
+		if len(c.Name) > 0 {
+			whereSql += " AND receiving_unit like '%" + c.Name + "%'"
+		}
+		whereSql += " AND receiving_unit is not null"
+
+		err = db.DB.Table("stock_out").
+			Distinct("receiving_unit").
+			Where(whereSql).
+			Scan(&list).Error
+		if err != nil {
+			logs.Error("db error: %s ", err)
+			return list, global.GetFailedErr
+		}
+		return
+	default:
+		if len(c.Name) > 0 {
+			whereSql += " AND (forwarding_unit like '%" + c.Name + "%' OR receiving_unit like '%" + c.Name + "%')"
+		}
+
+		forwarding_unit, receiving_unit := []string{}, []string{}
+		err = db.DB.Table("medicine_inventory").
+			Distinct("forwarding_unit").
+			Where(whereSql + " AND forwarding_unit is not null").
+			Scan(&forwarding_unit).Error
+		if err != nil {
+			logs.Error("db error: %s ", err)
+			return list, global.GetFailedErr
+		}
+		err = db.DB.Table("medicine_inventory").
+			Distinct("receiving_unit").
+			Where(whereSql + " AND receiving_unit is not null").
+			Scan(&receiving_unit).Error
+		if err != nil {
+			logs.Error("db error: %s ", err)
+			return list, global.GetFailedErr
+		}
+		list = append(forwarding_unit, receiving_unit...)
+		return
+	}
+
+}
+func (e *StockTemplate) StockOperatorList(c *dto.StockOperatorListReq, deptId int) (list []string, err error) {
+	whereSql := "dept_id = " + strconv.Itoa(deptId) + " AND deleted_at is null"
+
+	if len(c.Name) > 0 {
+		whereSql += " AND operator like '%" + c.Name + "%'"
+	}
+
+	err = db.DB.Table("medicine_inventory").
+		Distinct("operator").
+		Where(whereSql).
+		Scan(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	return list, nil
+
+}
+
+// 库存查询
+func (e *StockTemplate) StockInquiryList(c *dto.StockStatListReq, deptId int) (list []map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := " WHERE mi.dept_id = " + strconv.Itoa(deptId) + " AND deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND m_info.product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND m_info.enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND m_info.batch_number = " + c.BatchNumber
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND m_info.expiry_date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND m_info.expiry_date <= '" + c.EndDate + "'"
+	}
+
+	offset := (c.Page - 1) * c.PageSize
+	//sql := "SELECT * FROM (SELECT a.* FROM (SELECT * FROM medicine_inventory ORDER BY id DESC LIMIT 100) a GROUP BY a.medicine_id) b " +
+	//	"LEFT JOIN " + mtable + " ON b.medicine_id=" + mtable + ".id WHERE " + whereSql
+
+	sql := "SELECT mi.*,m_info.* FROM (SELECT medicine_id,MAX(id) AS latest_id FROM medicine_inventory GROUP BY medicine_id) AS mi_latest " +
+		"JOIN medicine_inventory AS mi ON mi.medicine_id=mi_latest.medicine_id AND mi.id=mi_latest.latest_id " +
+		"LEFT JOIN " + mtable + " AS m_info ON mi.medicine_id=m_info.id" + whereSql + " order by m_info.expiry_date limit ? offset ?;"
+
+	sqlWhereCount := "SELECT COUNT(1) FROM (SELECT medicine_id,MAX(id) AS latest_id FROM medicine_inventory GROUP BY medicine_id) AS mi_latest " +
+		"JOIN medicine_inventory AS mi ON mi.medicine_id=mi_latest.medicine_id AND mi.id=mi_latest.latest_id " +
+		"LEFT JOIN " + mtable + " AS m_info ON mi.medicine_id=m_info.id" + whereSql
+
+	err = db.DB.Raw(sqlWhereCount).Scan(&count).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+
+	err = db.DB.Raw(sql, c.PageSize, offset).Scan(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+		if date, ok := list[i][models.FieldExpiryDate]; ok && date != nil {
+			list[i][models.FieldExpiryDate] = date.(time.Time).Format("2006-01-02")
+		}
+	}
+	return list, count, nil
+
+}
+
+// 库存查询excel
+func (e *StockTemplate) StockInquiryExcel(c *dto.StockStatListReq, deptId int) (list []map[string]interface{}, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := " WHERE mi.dept_id = " + strconv.Itoa(deptId) + " AND deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND m_info.product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.EnterpriseID > 0 {
+		whereSql += " AND m_info.enterprise_id = " + strconv.Itoa(c.EnterpriseID)
+	}
+	if len(c.BatchNumber) > 0 {
+		whereSql += " AND m_info.batch_number = " + c.BatchNumber
+	}
+	if len(c.StartDate) > 0 {
+		whereSql += " AND m_info.expiry_date >= '" + c.StartDate + "'"
+	}
+	if len(c.EndDate) > 0 {
+		whereSql += " AND m_info.expiry_date <= '" + c.EndDate + "'"
+	}
+
+	sql := "SELECT mi.*,m_info.* FROM (SELECT medicine_id,MAX(id) AS latest_id FROM medicine_inventory GROUP BY medicine_id) AS mi_latest " +
+		"JOIN medicine_inventory AS mi ON mi.medicine_id=mi_latest.medicine_id AND mi.id=mi_latest.latest_id " +
+		"LEFT JOIN " + mtable + " AS m_info ON mi.medicine_id=m_info.id" + whereSql + " order by m_info.expiry_date"
+
+	err = db.DB.Raw(sql).Scan(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, err
+	}
+
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldDosageFormID]; ok {
+			list[i][models.FieldDosageFormName] = models.Read_DosageForm_Get(utils.ToInt(id))
+		}
+		if date, ok := list[i][models.FieldExpiryDate]; ok && date != nil {
+			list[i][models.FieldExpiryDate] = date.(time.Time).Format("2006-01-02")
+		}
+	}
+	return list, nil
+
+}
+func (e *StockTemplate) StockStat(c *dto.StockStatReq, deptId int) (list []map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := " WHERE mi.dept_id = " + strconv.Itoa(deptId) + " AND deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND m_info.product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if c.SpecID > 0 {
+		whereSql += " AND m_info.spec_id = " + strconv.Itoa(c.SpecID)
+	}
+	var result []map[string]interface{}
+	sql := "SELECT mi.balance,m_info.product_id,m_info.spec_id,m_info.unit_id FROM (SELECT medicine_id,MAX(id) AS latest_id FROM medicine_inventory GROUP BY medicine_id) AS mi_latest " +
+		"JOIN medicine_inventory AS mi ON mi.medicine_id=mi_latest.medicine_id AND mi.id=mi_latest.latest_id " +
+		"LEFT JOIN " + mtable + " AS m_info ON mi.medicine_id=m_info.id" + whereSql
+	//sqlWhereCount := "SELECT COUNT(1) FROM (SELECT medicine_id,MAX(id) AS latest_id FROM medicine_inventory GROUP BY medicine_id) AS mi_latest " +
+	//	"JOIN medicine_inventory AS mi ON mi.medicine_id=mi_latest.medicine_id AND mi.id=mi_latest.latest_id " +
+	//	"LEFT JOIN " + mtable + " AS m_info ON mi.medicine_id=m_info.id" + whereSql
+
+	//err = db.DB.Raw(sqlWhereCount).Scan(&count).Error
+	//if err != nil {
+	//	logs.Error("db error: %s ", err)
+	//	return list, count, global.GetFailedErr
+	//}
+
+	err = db.DB.Raw(sql).Scan(&result).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+
+	models.InitBasicData(deptId)
+	statMap := map[string]int{}
+	for i := 0; i < len(result); i++ {
+
+		key := fmt.Sprintf("%v-%v", result[i][models.FieldProductID], result[i][models.FieldSpecID])
+
+		if num, ok := statMap[key]; ok {
+			statMap[key] = num + utils.ToInt(result[i]["balance"])
+		} else {
+			statMap[key] = utils.ToInt(result[i]["balance"])
+		}
+	}
+	models.InitBasicData(deptId)
+	for key, num := range statMap {
+		product_id := utils.ToInt(strings.Split(key, "-")[0])
+		spec_id := utils.ToInt(strings.Split(key, "-")[1])
+		list = append(list, map[string]interface{}{
+			"product_id":   product_id,
+			"product_name": models.Read_Product_Get(product_id),
+			"spec_id":      spec_id,
+			"spec_name":    models.Read_Spec_Get(spec_id),
+			"balance":      num,
+		})
+	}
+
+	return list, count, nil
+
+}
+func (e *StockTemplate) StockHomeStat(deptId int) (list map[string]interface{}, count int64, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := " WHERE mi.dept_id = " + strconv.Itoa(deptId) + " AND deleted_at is null"
+	var result []map[string]interface{}
+	sql := "SELECT mi.balance,m_info.purchase_unit_price FROM (SELECT medicine_id,MAX(id) AS latest_id FROM medicine_inventory GROUP BY medicine_id) AS mi_latest " +
+		"JOIN medicine_inventory AS mi ON mi.medicine_id=mi_latest.medicine_id AND mi.id=mi_latest.latest_id " +
+		"LEFT JOIN " + mtable + " AS m_info ON mi.medicine_id=m_info.id" + whereSql
+
+	err = db.DB.Raw(sql).Scan(&result).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+
+	inventoryQuantity := 0
+	inventoryAmount := 0.0
+	for i := 0; i < len(result); i++ {
+		purchase_unit_price := utils.ToFloat64(result[i]["purchase_unit_price"])
+		inventoryQuantity += utils.ToInt(result[i]["balance"])
+		inventoryAmount += utils.ToFloat64(inventoryQuantity) * purchase_unit_price
+
+	}
+	var totalOut, totalOutSale, totalIn, totalInPurchase int
+
+	err = db.DB.Model(models.StockIn{}).Select("COALESCE(SUM(quantity),0)").Where("dept_id = ? AND unit_price = 0", deptId).Scan(&totalIn).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+	err = db.DB.Model(models.StockIn{}).Select("COALESCE(SUM(quantity),0)").Where("dept_id = ? AND unit_price > 0", deptId).Scan(&totalInPurchase).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+	err = db.DB.Model(models.StockOut{}).Select("COALESCE(SUM(quantity),0)").Where("dept_id = ? AND unit_price = 0", deptId).Scan(&totalOut).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+	err = db.DB.Model(models.StockOut{}).Select("COALESCE(SUM(quantity),0)").Where("dept_id = ? AND unit_price > 0", deptId).Scan(&totalOutSale).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, count, err
+	}
+	list = make(map[string]interface{})
+	list["inventoryQuantity"] = inventoryQuantity // 库存总量
+	list["inventoryAmount"] = inventoryAmount     // 库存总额(成本金额)
+	list["totalOut"] = totalOut                   // 出库
+	list["totalOutSale"] = totalOutSale           // 销售出库
+	list["totalIn"] = totalIn                     // 入库
+	list["totalInPurchase"] = totalInPurchase     // 采购入库
+
+	return list, count, nil
+
+}
+func (e *StockTemplate) StockTemplateTransportRecordWord(c *dto.TransportRecordWordReq, deptId int) (list []map[string]interface{}, err error) {
+
+	mtable := models.GetMedicineInfoTableName(deptId)
+	whereSql := "stock_out.dept_id = " + strconv.Itoa(deptId) + " AND stock_out.deleted_at is null"
+	if c.ProductID > 0 {
+		whereSql += " AND " + mtable + ".product_id = " + strconv.Itoa(c.ProductID)
+	}
+	if len(c.Date) > 0 {
+		whereSql += " AND stock_out.date = '" + c.Date + "'"
+	}
+	if len(c.ReceivingUnit) > 0 {
+		whereSql += " AND stock_out.receiving_unit like '%" + c.ReceivingUnit + "%'"
+	}
+	err = db.DB.Table("stock_out").
+		Joins("left join " + mtable + " on stock_out.medicine_id = " + mtable + ".id").
+		Where(whereSql).
+		Scan(&list).Error
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return list, global.GetFailedErr
+	}
+	models.InitBasicData(deptId)
+	for i := 0; i < len(list); i++ {
+		if id, ok := list[i][models.FieldProductID]; ok {
+			list[i][models.FieldProductName] = models.Read_Product_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldEnterpriseID]; ok {
+			list[i][models.FieldEnterpriseName] = models.Read_Enterprise_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldSpecID]; ok {
+			list[i][models.FieldSpecName] = models.Read_Spec_Get(utils.ToInt(id))
+		}
+		if id, ok := list[i][models.FieldUnitID]; ok {
+			list[i][models.FieldUnitName] = models.Read_Unit_Get(utils.ToInt(id))
+		}
+	}
+	return list, nil
+
+}

+ 21 - 3
services/unit.go

@@ -57,9 +57,19 @@ func (e *Unit) Get(d *dto.UnitGetReq, productModel *models.Unit, p *actions.Data
 func (e *Unit) Insert(c *dto.UnitInsertReq) error {
 	var err error
 	var data models.Unit
-	c.Generate(&data)
-	err = db.DB.Create(&data).Error
+	err = db.DB.Where("name = ? AND dept_id = ?", c.Name, c.DeptId).First(&data).Error
 	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return global.CreateFailedErr
+			}
+			c.Id = data.Id
+			return nil
+		}
 		logs.Error("db error: %s", err)
 		return global.CreateFailedErr
 	}
@@ -93,7 +103,15 @@ func (e *Unit) Update(c *dto.UnitUpdateReq) error {
 func (e *Unit) Remove(d *dto.UnitDeleteReq) error {
 	var err error
 	var data models.Unit
-
+	var Count int64
+	err = db.DB.Table(models.GetMedicineInfoTableName(d.DeptId)).Where("unit_id = ?", d.GetId()).Count(&Count).Error
+	if err != nil {
+		logs.Error("Delete error: %s", err)
+		return global.DeleteFailedErr
+	}
+	if Count > 0 {
+		return errors.New("该单位已被关联,禁止删除")
+	}
 	result := db.DB.Model(&data).Delete(&data, d.GetId())
 	if err = result.Error; err != nil {
 		logs.Error("Delete error: %s", err)

BIN
static/fonts/MiSans-Medium.ttf


BIN
static/fonts/arialuni.ttf


BIN
static/fonts/iconfont.eot


File diff suppressed because it is too large
+ 44 - 0
static/fonts/iconfont.svg


BIN
static/fonts/iconfont.ttf


BIN
static/fonts/iconfont.woff


BIN
static/fonts/三极行楷简体-粗.ttf


+ 173 - 0
utils/comm.go

@@ -5,12 +5,18 @@ import (
 	"crypto/rand"
 	"crypto/sha256"
 	"encoding/hex"
+	"encoding/json"
 	"errors"
+	"fmt"
 	"github.com/beego/beego/v2/core/logs"
 	"io"
+	"math"
 	"net/url"
 	"os"
+	"regexp"
+	"strconv"
 	"strings"
+	"unicode"
 )
 
 // 用户输入组合路径安全校验
@@ -125,3 +131,170 @@ func GenerateAppKey(length int) string {
 	}
 	return hex.EncodeToString(b)
 }
+
+// 驼峰转_
+func CamelToSnake(s string) string {
+	var result []rune
+	for i, r := range s {
+		if i > 0 && unicode.IsUpper(r) {
+			result = append(result, '_')
+		}
+		result = append(result, unicode.ToLower(r))
+	}
+	return string(result)
+}
+
+func WordsToSnake(s string) string {
+	return strings.Replace(s, " ", "_", -1)
+}
+
+func ToInt(value interface{}) int {
+	var key int
+	if value == nil {
+		return key
+	}
+	switch value.(type) {
+	case float64:
+		key = int(value.(float64))
+	case float32:
+		key = int(value.(float32))
+	case int:
+		key = int(value.(int))
+	case uint:
+		key = int(value.(uint))
+	case int8:
+		key = int(value.(int8))
+	case uint8:
+		key = int(value.(uint8))
+	case int16:
+		key = int(value.(int16))
+	case uint16:
+		key = int(value.(uint16))
+	case int32:
+		key = int(value.(int32))
+	case uint32:
+		key = int(value.(uint32))
+	case int64:
+		key = int(value.(int64))
+	case uint64:
+		key = int(value.(uint64))
+	case string:
+		key, _ = strconv.Atoi(value.(string))
+	case []byte:
+		key, _ = strconv.Atoi(string(value.([]byte)))
+	default:
+		newValue, _ := json.Marshal(value)
+		key, _ = strconv.Atoi(string(newValue))
+	}
+	return key
+}
+
+func ToFloat64(value interface{}) float64 {
+	var key float64
+	if value == nil {
+		return key
+	}
+
+	switch value.(type) {
+	case float64:
+		key = value.(float64)
+	case float32:
+		key = float64(value.(float32))
+	case int:
+		key = float64(value.(int))
+	case uint:
+		key = float64(value.(uint))
+	case int8:
+		key = float64(value.(int8))
+	case uint8:
+		key = float64(value.(uint8))
+	case int16:
+		key = float64(value.(int16))
+	case uint16:
+		key = float64(value.(uint16))
+	case int32:
+		key = float64(value.(int32))
+	case uint32:
+		key = float64(value.(uint32))
+	case int64:
+		key = float64(value.(int64))
+	case uint64:
+		key = float64(value.(uint64))
+	case string:
+		key_float64, _ := strconv.ParseFloat(value.(string), 32/64)
+		key = key_float64
+	case []byte:
+		key_float64, _ := strconv.ParseFloat(string(value.([]byte)), 32/64)
+		key = key_float64
+	default:
+		newValue, _ := json.Marshal(value)
+		key_float64, _ := strconv.ParseFloat(string(newValue), 32/64)
+		key = key_float64
+	}
+
+	key_float64, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", key), 32/64)
+
+	return key_float64
+}
+
+// IsChineseOnly 判断字符串是否只包含中文字符
+func IsChineseOnly(s string) bool {
+	chineseRegexp := regexp.MustCompile("^[\\p{Han}]+$")
+	return chineseRegexp.MatchString(s)
+}
+
+// 将数字转换为中文大写形式
+
+func AmountConvert(p_money float64, p_round bool) string {
+	var NumberUpper = []string{"壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", "零"}
+	var Unit = []string{"分", "角", "圆", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟"}
+	var regex = [][]string{
+		{"零拾", "零"}, {"零佰", "零"}, {"零仟", "零"}, {"零零零", "零"}, {"零零", "零"},
+		{"零角零分", "整"}, {"零分", "整"}, {"零角", "零"}, {"零亿零万零元", "亿元"},
+		{"亿零万零元", "亿元"}, {"零亿零万", "亿"}, {"零万零元", "万元"}, {"万零元", "万元"},
+		{"零亿", "亿"}, {"零万", "万"}, {"拾零圆", "拾元"}, {"零圆", "元"}, {"零零", "零"}}
+	str, DigitUpper, Unit_Len, round := "", "", 0, 0
+	if p_money == 0 {
+		return "零"
+	}
+	if p_money < 0 {
+		str = "负"
+		p_money = math.Abs(p_money)
+	}
+	if p_round {
+		round = 2
+	} else {
+		round = 1
+	}
+	Digit_byte := []byte(strconv.FormatFloat(p_money, 'f', round+1, 64)) //注意币种四舍五入
+	Unit_Len = len(Digit_byte) - round
+
+	for _, v := range Digit_byte {
+		if Unit_Len >= 1 && v != 46 {
+			s, _ := strconv.ParseInt(string(v), 10, 0)
+			if s != 0 {
+				DigitUpper = NumberUpper[s-1]
+
+			} else {
+				DigitUpper = "零"
+			}
+			str = str + DigitUpper + Unit[Unit_Len-1]
+			Unit_Len = Unit_Len - 1
+		}
+	}
+	for i, _ := range regex {
+		reg := regexp.MustCompile(regex[i][0])
+		str = reg.ReplaceAllString(str, regex[i][1])
+	}
+	if string(str[0:3]) == "元" {
+		str = str[3:len(str)]
+	}
+	if string(str[0:3]) == "零" {
+		str = str[3:len(str)]
+	}
+
+	if strings.Contains(str, "角") {
+		str = strings.Replace(str, "整", "", 1)
+	}
+	return str
+}

+ 13 - 0
utils/comm_test.go

@@ -0,0 +1,13 @@
+package utils
+
+import (
+	"github.com/beego/beego/v2/core/logs"
+	"testing"
+)
+
+// TestGet is a sample to run an endpoint test
+func TestAmountConvert(t *testing.T) {
+	en := AmountConvert(521.71, true)
+	logs.Info(en)
+
+}

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