Explorar o código

add:菜单管理

zoie hai 1 mes
pai
achega
2b0d0b3f6c
Modificáronse 9 ficheiros con 1252 adicións e 179 borrados
  1. 226 0
      Nats/Nats.go
  2. 156 60
      controllers/Purchase.go
  3. 550 25
      controllers/Stock.go
  4. 1 1
      go.mod
  5. 2 0
      go.sum
  6. 71 76
      models/Purchase/Purchase.go
  7. 9 2
      models/Stock/StockIn.go
  8. 219 2
      models/Stock/StockOut.go
  9. 18 13
      routers/Stock.go

+ 226 - 0
Nats/Nats.go

@@ -4,7 +4,9 @@ import (
 	"ERP_storage/conf"
 	"ERP_storage/models/Account"
 	"ERP_storage/models/Contract"
+	"encoding/json"
 	"fmt"
+
 	"github.com/astaxie/beego/logs"
 	"github.com/beego/beego/v2/adapter/orm"
 	"github.com/nats-io/nats.go"
@@ -265,4 +267,228 @@ func NatsInit() {
 
 	})
 
+	// ========== 菜单相关NATS服务 ==========
+	// 请求-响应 添加菜单
+	_, _ = Nats.Subscribe(conf.NatsSubj_Prefix+conf.Sys_Name+"_Add_Menu", func(m *nats.Msg) {
+		type T_S struct {
+			Menu menulibs.Menu
+			APIs string // API JSON字符串
+		}
+		type T_R struct {
+			Code int16  `xml:"Code"`
+			Msg  string `xml:"Msg"`
+			Data int    `xml:"Data"`
+		}
+
+		var t_S T_S
+		var t_R T_R
+
+		err := msgpack.Unmarshal(m.Data, &t_S)
+		if err != nil {
+			t_R.Code = 202
+			t_R.Msg = "msgpack unmarshal err!"
+			b, _ := msgpack.Marshal(&t_R)
+			_ = Nats.Publish(m.Reply, b)
+			return
+		}
+
+		o := orm.NewOrm()
+		MenuDao := menulibs.NewMenu(o)
+		id64, err := MenuDao.Add_Menu(t_S.Menu)
+		if err != nil {
+			t_R.Code = 202
+			t_R.Msg = "添加菜单失败!"
+			b, _ := msgpack.Marshal(&t_R)
+			_ = Nats.Publish(m.Reply, b)
+			return
+		}
+		id := int(id64)
+
+		// 处理API JSON字符串
+		if len(t_S.APIs) > 0 {
+			var apiList []struct {
+				T_name   string `json:"T_name"`
+				T_uri    string `json:"T_uri"`
+				T_method string `json:"T_method"`
+			}
+			err = json.Unmarshal([]byte(t_S.APIs), &apiList)
+			if err == nil && len(apiList) > 0 {
+				var apis []menulibs.API
+				for _, api := range apiList {
+					if len(api.T_method) == 0 {
+						api.T_method = "POST"
+					}
+					apis = append(apis, menulibs.API{
+						T_Menu_Id: id,
+						T_name:    api.T_name,
+						T_uri:     api.T_uri,
+						T_method:  api.T_method,
+						T_enable:  1,
+					})
+				}
+				APIDao := menulibs.NewAPI(o, Account.RedisCache_API)
+				_, _ = APIDao.InsertMulti_API(apis)
+			}
+		}
+
+		t_R.Code = 200
+		t_R.Msg = "ok"
+		t_R.Data = id
+		b, _ := msgpack.Marshal(&t_R)
+		_ = Nats.Publish(m.Reply, b)
+	})
+
+	// 请求-响应 更新菜单
+	_, _ = Nats.Subscribe(conf.NatsSubj_Prefix+conf.Sys_Name+"_Update_Menu", func(m *nats.Msg) {
+		type T_S struct {
+			Menu menulibs.Menu
+			Cols []string // 需要更新的字段
+			APIs string   // API JSON字符串
+		}
+		type T_R struct {
+			Code int16  `xml:"Code"`
+			Msg  string `xml:"Msg"`
+			Data int    `xml:"Data"`
+		}
+
+		var t_S T_S
+		var t_R T_R
+
+		err := msgpack.Unmarshal(m.Data, &t_S)
+		if err != nil {
+			t_R.Code = 202
+			t_R.Msg = "msgpack unmarshal err!"
+			b, _ := msgpack.Marshal(&t_R)
+			_ = Nats.Publish(m.Reply, b)
+			return
+		}
+
+		o := orm.NewOrm()
+		MenuDao := menulibs.NewMenu(o)
+		APIDao := menulibs.NewAPI(o, Account.RedisCache_API)
+
+		// 更新菜单
+		if len(t_S.Cols) > 0 {
+			_, err = MenuDao.Update_Menu(t_S.Menu, t_S.Cols...)
+			if err != nil {
+				t_R.Code = 202
+				t_R.Msg = "更新菜单失败!"
+				b, _ := msgpack.Marshal(&t_R)
+				_ = Nats.Publish(m.Reply, b)
+				return
+			}
+		}
+
+		// 处理API JSON字符串
+		if len(t_S.APIs) > 0 {
+			// 先删除旧的API(软删除)
+			_, _ = APIDao.Delete_API_ByMenuId(t_S.Menu.Id)
+
+			var apiList []struct {
+				T_name   string `json:"T_name"`
+				T_uri    string `json:"T_uri"`
+				T_method string `json:"T_method"`
+			}
+			err = json.Unmarshal([]byte(t_S.APIs), &apiList)
+			if err == nil && len(apiList) > 0 {
+				var apis []menulibs.API
+				for _, api := range apiList {
+					if len(api.T_method) == 0 {
+						api.T_method = "POST"
+					}
+					apis = append(apis, menulibs.API{
+						T_Menu_Id: t_S.Menu.Id,
+						T_name:    api.T_name,
+						T_uri:     api.T_uri,
+						T_method:  api.T_method,
+						T_enable:  1,
+					})
+				}
+				_, _ = APIDao.InsertMulti_API(apis)
+			}
+		}
+
+		t_R.Code = 200
+		t_R.Msg = "ok"
+		t_R.Data = t_S.Menu.Id
+		b, _ := msgpack.Marshal(&t_R)
+		_ = Nats.Publish(m.Reply, b)
+	})
+
+	// 请求-响应 删除菜单
+	_, _ = Nats.Subscribe(conf.NatsSubj_Prefix+conf.Sys_Name+"_Delete_Menu", func(m *nats.Msg) {
+		type T_R struct {
+			Code int16  `xml:"Code"`
+			Msg  string `xml:"Msg"`
+			Data int    `xml:"Data"`
+		}
+
+		var menu menulibs.Menu
+		var t_R T_R
+
+		err := msgpack.Unmarshal(m.Data, &menu)
+		if err != nil {
+			t_R.Code = 202
+			t_R.Msg = "msgpack unmarshal err!"
+			b, _ := msgpack.Marshal(&t_R)
+			_ = Nats.Publish(m.Reply, b)
+			return
+		}
+
+		o := orm.NewOrm()
+		MenuDao := menulibs.NewMenu(o)
+		_, err = MenuDao.Delete_Menu(menu)
+		if err != nil {
+			t_R.Code = 202
+			t_R.Msg = "删除菜单失败!"
+			b, _ := msgpack.Marshal(&t_R)
+			_ = Nats.Publish(m.Reply, b)
+			return
+		}
+
+		t_R.Code = 200
+		t_R.Msg = "ok"
+		t_R.Data = menu.Id
+		b, _ := msgpack.Marshal(&t_R)
+		_ = Nats.Publish(m.Reply, b)
+	})
+
+	// 请求-响应 根据ID获取菜单
+	_, _ = Nats.Subscribe(conf.NatsSubj_Prefix+conf.Sys_Name+"_Read_Menu_ById", func(m *nats.Msg) {
+		type T_R struct {
+			Code int16         `xml:"Code"`
+			Msg  string        `xml:"Msg"`
+			Data menulibs.Menu `xml:"Data"`
+		}
+
+		var id int
+		var t_R T_R
+
+		err := msgpack.Unmarshal(m.Data, &id)
+		if err != nil {
+			t_R.Code = 202
+			t_R.Msg = "msgpack unmarshal err!"
+			b, _ := msgpack.Marshal(&t_R)
+			_ = Nats.Publish(m.Reply, b)
+			return
+		}
+
+		o := orm.NewOrm()
+		MenuDao := menulibs.NewMenu(o)
+		menu, err := MenuDao.Read_Menu_ById(id)
+		if err != nil {
+			t_R.Code = 202
+			t_R.Msg = "查询菜单失败!"
+			b, _ := msgpack.Marshal(&t_R)
+			_ = Nats.Publish(m.Reply, b)
+			return
+		}
+
+		t_R.Code = 200
+		t_R.Msg = "ok"
+		t_R.Data = menu
+		b, _ := msgpack.Marshal(&t_R)
+		_ = Nats.Publish(m.Reply, b)
+	})
+
 }

+ 156 - 60
controllers/Purchase.go

@@ -7,15 +7,16 @@ import (
 	"ERP_storage/models/Account"
 	"ERP_storage/models/Purchase"
 	"fmt"
+	"math"
+	"net/url"
+	"os"
+	"time"
+
 	"github.com/beego/beego/v2/adapter/orm"
 	beego "github.com/beego/beego/v2/server/web"
 	"github.com/xuri/excelize/v2"
 	userlibs "gogs.baozhida.cn/zoie/ERP_libs/User"
 	"gogs.baozhida.cn/zoie/ERP_libs/lib"
-	"math"
-	"net/url"
-	"os"
-	"time"
 )
 
 type PurchaseController struct {
@@ -158,16 +159,22 @@ func (c *PurchaseController) Purchase_Add() {
 	T_detail := c.GetString("T_detail")
 	T_approver := c.GetString("T_approver")
 	T_dept := c.GetString("T_dept")
+	T_product_name := c.GetString("T_product_name")
+	T_total_price, _ := c.GetFloat("T_total_price")
+	T_product_quantity, _ := c.GetInt("T_product_quantity")
 
 	var_ := Purchase.Purchase{
-		T_dept:     T_dept,
-		T_uuid:     T_uuid,
-		T_submit:   c.User.T_uuid,
-		T_date:     T_date,
-		T_remark:   T_remark,
-		T_detail:   T_detail,
-		T_approver: T_approver,
-		T_State:    Purchase.WaitAudit,
+		T_dept:             T_dept,
+		T_uuid:             T_uuid,
+		T_submit:           c.User.T_uuid,
+		T_date:             T_date,
+		T_remark:           T_remark,
+		T_detail:           T_detail,
+		T_approver:         T_approver,
+		T_State:            Purchase.WaitAudit,
+		T_product_name:     T_product_name,
+		T_total_price:      float32(T_total_price),
+		T_product_quantity: T_product_quantity,
 	}
 
 	o := orm.NewOrm()
@@ -235,6 +242,9 @@ func (c *PurchaseController) Purchase_Edit() {
 	T_detail := c.GetString("T_detail")
 	T_approver := c.GetString("T_approver")
 	T_dept := c.GetString("T_dept")
+	T_product_name := c.GetString("T_product_name")
+	T_total_price, _ := c.GetFloat("T_total_price")
+	T_product_quantity, _ := c.GetInt("T_product_quantity")
 
 	o := orm.NewOrm()
 	PurchaseDao := Purchase.NewPurchase(o)
@@ -281,6 +291,18 @@ func (c *PurchaseController) Purchase_Edit() {
 		purchase.T_State = T_State
 		clos = append(clos, "T_State")
 	}
+	if len(T_product_name) > 0 {
+		purchase.T_product_name = T_product_name
+		clos = append(clos, "T_product_name")
+	}
+	if T_total_price > 0 {
+		purchase.T_total_price = float32(T_total_price)
+		clos = append(clos, "T_total_price")
+	}
+	if T_product_quantity > 0 {
+		purchase.T_product_quantity = T_product_quantity
+		clos = append(clos, "T_product_quantity")
+	}
 
 	_, err = PurchaseDao.Update_Purchase(purchase, clos...)
 	if err != nil {
@@ -394,32 +416,63 @@ func (c *PurchaseController) Purchase_Excel() {
 			},
 		})
 
-	f.MergeCell("Sheet1", "A1", "I1")
+	// 定义列配置(可随时增加、删除或调整字段位置)
+	type ExcelColumn struct {
+		Header   string  // 表头名称
+		Width    float64 // 列宽
+		DataFunc func(detail Purchase.PurchaseDetail_R, index int) interface{}
+	}
+
+	columns := []ExcelColumn{
+		{"序号", 6, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return index + 1 }},
+		{"名称", 18, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_name }},
+		{"位号", 15, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_bit_number }},
+		{"封装", 15, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_packaging }},
+		{"型号", 15, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_model }},
+		{"规格", 20, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_spec }},
+		{"数量", 6, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_quantity }},
+		{"需求", 12, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_demand }},
+
+		{"采购单价", 10, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_unit_price }},
+		{"采购金额", 15, func(detail Purchase.PurchaseDetail_R, index int) interface{} {
+			return fmt.Sprintf("%.2f", detail.T_unit_price*float32(detail.T_quantity))
+		}},
+		{"参考网址", 30, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_reference_site }},
+		{"备注", 20, func(detail Purchase.PurchaseDetail_R, index int) interface{} { return detail.T_remark }},
+	}
+
+	// 计算最后一列的列名
+	lastCol := string(rune('A' + len(columns) - 1))
+	// 计算中间列(用于分隔左右两部分信息)
+	midCol := string(rune('A' + len(columns)/2))
+
+	// 设置头部信息
+	f.MergeCell("Sheet1", "A1", lastCol+"1")
 	f.SetCellValue("Sheet1", "A1", fmt.Sprintf("%d宝智达冷链采购申请", time.Now().Year()))
-	f.SetCellStyle("Sheet1", "A1", "I1", Style1)
+	f.SetCellStyle("Sheet1", "A1", lastCol+"1", Style1)
 
-	f.MergeCell("Sheet1", "A2", "E2")
+	f.MergeCell("Sheet1", "A2", midCol+"2")
 	date, _ := lib.DateStrToTime(detail.T_date)
 	f.SetCellValue("Sheet1", "A2", fmt.Sprintf("申请时间:%s", date.Format("2006年01月02日")))
-	f.MergeCell("Sheet1", "F2", "I2")
-	f.SetCellValue("Sheet1", "F2", fmt.Sprintf("申请人:%-10s负责人审批:", detail.T_uuid_name))
+	f.MergeCell("Sheet1", string(rune('A'+len(columns)/2+1))+"2", lastCol+"2")
+	f.SetCellValue("Sheet1", string(rune('A'+len(columns)/2+1))+"2", fmt.Sprintf("申请人:%-10s负责人审批:", detail.T_uuid_name))
 
-	f.MergeCell("Sheet1", "A3", "E3")
+	f.MergeCell("Sheet1", "A3", midCol+"3")
 	f.SetCellValue("Sheet1", "A3", "执行时间:")
-	f.MergeCell("Sheet1", "F3", "I3")
-	f.SetCellValue("Sheet1", "F3", fmt.Sprintf("执行部门:%-15s执行人:", ""))
+	f.MergeCell("Sheet1", string(rune('A'+len(columns)/2+1))+"3", lastCol+"3")
+	f.SetCellValue("Sheet1", string(rune('A'+len(columns)/2+1))+"3", fmt.Sprintf("执行部门:%-15s执行人:", ""))
 
-	f.MergeCell("Sheet1", "A4", "E4")
+	f.MergeCell("Sheet1", "A4", midCol+"4")
 	f.SetCellValue("Sheet1", "A4", fmt.Sprintf("审批人:%s", detail.T_approver_name))
-	f.MergeCell("Sheet1", "F4", "I4")
+	f.MergeCell("Sheet1", string(rune('A'+len(columns)/2+1))+"4", lastCol+"4")
 	approver_date := ""
 	if len(detail.T_approver_date) > 0 {
 		date2, _ := lib.DateStrToTime(detail.T_approver_date)
 		approver_date = date2.Format("2006年01月02日")
 	}
-	f.SetCellValue("Sheet1", "F4", fmt.Sprintf("审批时间:%s", approver_date))
+	f.SetCellValue("Sheet1", string(rune('A'+len(columns)/2+1))+"4", fmt.Sprintf("审批时间:%s", approver_date))
 
-	f.SetCellStyle("Sheet1", "A2", "I4", Style2)
+	f.SetCellStyle("Sheet1", "A2", lastCol+"4", Style2)
 	f.SetRowHeight("Sheet1", 1, 40)
 	height := 30.0
 	f.SetRowHeight("Sheet1", 2, height)
@@ -427,52 +480,95 @@ func (c *PurchaseController) Purchase_Excel() {
 	f.SetRowHeight("Sheet1", 4, height)
 	f.SetRowHeight("Sheet1", 5, height)
 
-	f.MergeCell("Sheet1", "A5", "I5")
+	// 设置"申请采购明细"标题行
+	f.MergeCell("Sheet1", "A5", lastCol+"5")
 	f.SetCellValue("Sheet1", "A5", "申请采购明细")
-	f.SetCellStyle("Sheet1", "A5", "I5", Style1)
-
-	f.SetCellValue("Sheet1", "A6", "序号")
-	f.SetCellValue("Sheet1", "B6", "名称")
-	f.SetCellValue("Sheet1", "C6", "型号")
-	f.SetCellValue("Sheet1", "D6", "规格")
-	f.SetCellValue("Sheet1", "E6", "数量")
-	f.SetCellValue("Sheet1", "F6", "参考网址")
-	f.SetCellValue("Sheet1", "G6", "需求")
-	f.SetCellValue("Sheet1", "H6", "备注")
-	f.SetCellValue("Sheet1", "I6", "采购金额")
-	// 这里设置表头
-	f.SetCellStyle("Sheet1", "A6", "H6", Style3)
+	f.SetCellStyle("Sheet1", "A5", lastCol+"5", Style1)
+
+	// 设置表头样式
+	f.SetCellStyle("Sheet1", "A6", lastCol+"6", Style3)
 	f.SetRowHeight("Sheet1", 2, 25)
 
-	// 设置列宽
-	f.SetColWidth("Sheet1", "A", "A", 6)
-	f.SetColWidth("Sheet1", "B", "B", 12)
-	f.SetColWidth("Sheet1", "C", "C", 10)
-	f.SetColWidth("Sheet1", "D", "D", 10)
-	f.SetColWidth("Sheet1", "E", "E", 6)
-	f.SetColWidth("Sheet1", "F", "F", 30)
-	f.SetColWidth("Sheet1", "G", "G", 12)
-	f.SetColWidth("Sheet1", "H", "H", 15)
-	f.SetColWidth("Sheet1", "I", "I", 15)
+	// 循环设置表头和列宽
+	for i, col := range columns {
+		colName := string(rune('A' + i))
+		f.SetCellValue("Sheet1", colName+"6", col.Header)
+		f.SetColWidth("Sheet1", colName, colName, col.Width)
+	}
 
+	// 填充数据
 	line := 6
+	// 用于记录需要设置超链接的单元格
+	type hyperlinkCell struct {
+		cellRef     string
+		url         string
+		displayText string
+	}
+	var hyperlinkCells []hyperlinkCell
+
 	for i, v := range detail.T_Detail {
 		line++
-		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), i+1)
-		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v.T_name)
-		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v.T_model)
-		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), v.T_spec)
-		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", line), v.T_quantity)
-		f.SetCellValue("Sheet1", fmt.Sprintf("F%d", line), v.T_reference_site)
-		f.SetCellValue("Sheet1", fmt.Sprintf("G%d", line), v.T_demand)
-		f.SetCellValue("Sheet1", fmt.Sprintf("H%d", line), v.T_remark)
-		f.SetCellValue("Sheet1", fmt.Sprintf("I%d", line), fmt.Sprintf("%.2f", v.T_unit_price*float32(v.T_quantity)))
+		// 循环填充每列数据
+		for j, col := range columns {
+			colName := string(rune('A' + j))
+			cellRef := fmt.Sprintf("%s%d", colName, line)
+
+			// 特殊处理"参考网址"列
+			if col.Header == "参考网址" {
+				// 确定显示的文本
+				displayText := v.T_reference_site_title
+				if len(displayText) == 0 {
+					displayText = "参考网址"
+				}
+
+				// 设置单元格值
+				f.SetCellValue("Sheet1", cellRef, displayText)
+
+				// 如果有网址,记录下来,稍后统一设置超链接
+				if len(v.T_reference_site) > 0 {
+					hyperlinkCells = append(hyperlinkCells, hyperlinkCell{
+						cellRef:     cellRef,
+						url:         v.T_reference_site,
+						displayText: displayText,
+					})
+				}
+			} else {
+				f.SetCellValue("Sheet1", cellRef, col.DataFunc(v, i))
+			}
+		}
 	}
+
+	// 合计行
 	line++
-	f.MergeCell("Sheet1", fmt.Sprintf("A%d", line), fmt.Sprintf("H%d", line))
+	// 合并"序号"到"备注"列(最后一列之前的所有列)
+	f.MergeCell("Sheet1", fmt.Sprintf("A%d", line), fmt.Sprintf("F%d", line))
 	f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), "合计")
-	f.SetCellFormula("Sheet1", fmt.Sprintf("I%d", line), fmt.Sprintf("SUM(I7:I%d)", line-1))
-	f.SetCellStyle("Sheet1", "A6", fmt.Sprintf("I%d", line), Style4)
+	// 在最后一列设置求和公式
+	f.SetCellFormula("Sheet1", fmt.Sprintf("%s%d", "J", line), fmt.Sprintf("SUM(%s7:%s%d)", "J", "J", line-1))
+
+	// 设置数据区域样式
+	f.SetCellStyle("Sheet1", "A6", fmt.Sprintf("%s%d", lastCol, line), Style4)
+
+	// 在统一样式之后,设置超链接和超链接样式
+	linkStyle, _ := f.NewStyle(&excelize.Style{
+		Font: &excelize.Font{
+			Color:     "0563C1",
+			Underline: "single",
+			Size:      13,
+			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},
+		},
+	})
+	for _, cell := range hyperlinkCells {
+		f.SetCellHyperLink("Sheet1", cell.cellRef, cell.url, "External")
+		f.SetCellStyle("Sheet1", cell.cellRef, cell.cellRef, linkStyle)
+	}
 
 	timeStr := time.Now().Format("20060102150405")
 	fileName := fmt.Sprintf("%d宝智达冷链采购申请%v.xlsx", time.Now().Year(), timeStr)

+ 550 - 25
controllers/Stock.go

@@ -14,6 +14,7 @@ import (
 	"fmt"
 	"math"
 	"os"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -218,12 +219,12 @@ func (c *StockController) Device_Check() {
 	T_date := c.GetString("T_date")
 
 	// 检查MQTT授权
-	mqtt := Stock.Read_MqttUser(T_sn)
-	if len(mqtt.Username) == 0 {
-		c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("【%s】未授权,请先授权!", T_sn)}
-		c.ServeJSON()
-		return
-	}
+	//mqtt := Stock.Read_MqttUser(T_sn)
+	//if len(mqtt.Username) == 0 {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("【%s】未授权,请先授权!", T_sn)}
+	//	c.ServeJSON()
+	//	return
+	//}
 
 	DeviceDao := Stock.NewDevice(orm.NewOrm())
 
@@ -233,7 +234,8 @@ func (c *StockController) Device_Check() {
 		c.ServeJSON()
 		return
 	}
-	checkTime := date.Format("2006-01-02 15:04:05")
+
+	checkTime := date.Format("2006-01-02") + time.Now().Format(" 15:04:05")
 	// T_State 1-已出库 2-未出库/入库
 	// 查询checkTime时间前最后一条记录
 	lastBeforeCheckTime, errBefore := DeviceDao.Read_Device_LastBeforeCheckTime(T_sn, checkTime)
@@ -278,7 +280,7 @@ func (c *StockController) Device_Check() {
 		}
 
 		// 2. 时间前已入库,时间后有出库记录
-		if errBefore == nil && lastBeforeCheckTime.T_State == 2 && len(lastBeforeCheckTime.T_in_number) > 0 &&
+		if lastBeforeCheckTime.T_State == 2 && len(lastBeforeCheckTime.T_in_number) > 0 &&
 			errAfter == nil && firstAfterCheckTime.T_State == 1 && len(firstAfterCheckTime.T_out_number) > 0 {
 			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("【%s】已出库,请勿重复提交!", T_sn)}
 			c.ServeJSON()
@@ -286,7 +288,7 @@ func (c *StockController) Device_Check() {
 		}
 
 		// 3. 时间前已出库
-		if errBefore == nil && lastBeforeCheckTime.T_State == 1 && len(lastBeforeCheckTime.T_out_number) > 0 {
+		if lastBeforeCheckTime.T_State == 1 && len(lastBeforeCheckTime.T_out_number) > 0 {
 			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("【%s】已出库,请勿重复提交!", T_sn)}
 			c.ServeJSON()
 			return
@@ -1043,6 +1045,7 @@ func (c *StockController) StockIn_Add() {
 	T_project := c.GetString("T_project")
 	T_return_user := c.GetString("T_return_user")
 	T_batch_number := c.GetString("T_batch_number")
+	T_company_name := c.GetString("T_company_name")
 	date, is := lib.DateStrToTime(T_date)
 	if !is {
 		c.Data["json"] = lib.JSONS{Code: 202, Msg: "日期格式错误!"}
@@ -1070,6 +1073,7 @@ func (c *StockController) StockIn_Add() {
 		T_batch_number: T_batch_number,
 		T_state:        Stock.StockInWarehouse,
 		T_warehouse:    c.User.T_uuid,
+		T_company_name: T_company_name,
 	}
 
 	StockInProductDao := Stock.NewStockInProduct(o)
@@ -1262,6 +1266,7 @@ func (c *StockController) StockIn_Edit() {
 	T_project := c.GetString("T_project")
 	T_return_user := c.GetString("T_return_user")
 	T_batch_number := c.GetString("T_batch_number")
+	T_company_name := c.GetString("T_company_name")
 	date, is := lib.DateStrToTime(T_date)
 	if !is {
 		c.Data["json"] = lib.JSONS{Code: 202, Msg: "日期格式错误!"}
@@ -1554,9 +1559,9 @@ func (c *StockController) StockIn_Edit() {
 			}
 		}
 	}
-	if len(T_remark) > 0 {
-		stockIn.T_remark = T_remark
-	}
+
+	stockIn.T_remark = T_remark
+
 	if len(T_date) > 0 {
 		stockIn.T_date = T_date
 	}
@@ -1569,8 +1574,11 @@ func (c *StockController) StockIn_Edit() {
 	if len(T_batch_number) > 0 {
 		stockIn.T_batch_number = T_batch_number
 	}
+	if len(T_company_name) > 0 {
+		stockIn.T_company_name = T_company_name
+	}
 
-	err = StockInDao.Update_StockIn(stockIn, "T_remark", "T_date", "T_project", "T_return_user", "T_batch_number")
+	err = StockInDao.Update_StockIn(stockIn, "T_remark", "T_date", "T_project", "T_return_user", "T_batch_number", "T_company_name")
 	if err != nil {
 		o.Rollback()
 		c.Data["json"] = lib.JSONS{Code: 203, Msg: "修改入库失败"}
@@ -2151,6 +2159,7 @@ func (c *StockController) StockIn_Apply() {
 	T_product := c.GetString("T_product")
 	T_remark := c.GetString("T_remark")
 	T_project := c.GetString("T_project")
+	T_company_name := c.GetString("T_company_name")
 	T_return_user := c.GetString("T_return_user")
 	if len(T_return_user) == 0 {
 		T_return_user = c.User.T_uuid
@@ -2172,6 +2181,7 @@ func (c *StockController) StockIn_Apply() {
 		T_submit:           c.User.T_uuid,
 		T_project:          T_project,
 		T_return_user:      T_return_user,
+		T_company_name:     T_company_name,
 		T_state:            1,
 		T_application_date: time.Now().Format("2006-01-02"),
 	}
@@ -2232,6 +2242,7 @@ func (c *StockController) StockIn_Apply_Edit() {
 	T_remark := c.GetString("T_remark")
 	T_project := c.GetString("T_project")
 	T_batch_number := c.GetString("T_batch_number")
+	T_company_name := c.GetString("T_company_name")
 
 	NatsServer.AddUserLogs(c.User.T_uuid, "仓库管理", "修改入库", T_product)
 	o := orm.NewOrm()
@@ -2351,17 +2362,20 @@ func (c *StockController) StockIn_Apply_Edit() {
 			}
 		}
 	}
-	if len(T_remark) > 0 {
-		stockIn.T_remark = T_remark
-	}
+
+	stockIn.T_remark = T_remark
+
 	if len(T_project) > 0 {
 		stockIn.T_project = T_project
 	}
 	if len(T_batch_number) > 0 {
 		stockIn.T_batch_number = T_batch_number
 	}
+	if len(T_company_name) > 0 {
+		stockIn.T_company_name = T_company_name
+	}
 
-	err = StockInDao.Update_StockIn(stockIn, "T_remark", "T_project", "T_batch_number")
+	err = StockInDao.Update_StockIn(stockIn, "T_remark", "T_project", "T_batch_number", "T_company_name")
 	if err != nil {
 		o.Rollback()
 		c.Data["json"] = lib.JSONS{Code: 203, Msg: "修改入库失败"}
@@ -2519,11 +2533,11 @@ func (c *StockController) StockIn_Warehouse() {
 
 					device := Stock.Device{
 						T_product_id:   product_id,
-						T_out_number:   T_number,
+						T_in_number:    T_number,
 						T_sn:           sn,
 						T_iccid:        mqtt.Iccid,
 						T_imei:         mqtt.Imei,
-						T_State:        1,
+						T_State:        2,
 						T_project:      stockIn.T_project,
 						T_batch_number: batchNumber,
 						CreateTime:     date,
@@ -3459,9 +3473,9 @@ func (c *StockController) StockOut_Edit() {
 			}
 		}
 	}
-	if len(T_remark) > 0 {
-		StockOut.T_remark = T_remark
-	}
+
+	StockOut.T_remark = T_remark
+
 	if len(T_receive) > 0 {
 		StockOut.T_receive = T_receive
 	}
@@ -4427,9 +4441,9 @@ func (c *StockController) StockOut_Apply_Edit() {
 		}
 
 	}
-	if len(T_remark) > 0 {
-		StockOut.T_remark = T_remark
-	}
+
+	StockOut.T_remark = T_remark
+
 	if len(T_project) > 0 {
 		StockOut.T_project = T_project
 	}
@@ -4925,3 +4939,514 @@ func UpdateAllStockMonthReturn(T_depot_id int, T_start_date, T_end_date string)
 	logs.Info(fmt.Sprintf("批量更新退库数据完成,共更新%d条记录", count))
 	return count, nil
 }
+
+// Company_Device_Statistics 查询公司借出和退回设备统计
+func (c *StockController) Company_Device_Statistics() {
+	// 查询参数(对前端返回扁平化分组列表,不再返回分页包装)
+	T_company_name := c.GetString("T_company_name") // 公司名称
+	T_depot_id, _ := c.GetInt("T_depot_id")         // 仓库ID
+	T_start_date := c.GetString("T_start_date")     // 开始日期
+	T_end_date := c.GetString("T_end_date")         // 结束日期
+
+	StockOutDao := Stock.NewStockOut(orm.NewOrm())
+	R_List, _ := StockOutDao.Read_Company_Device_Statistics(T_company_name, T_start_date, T_end_date, T_depot_id, 1, 9999)
+
+	// 1) 先按公司分组并排序,顺序与导出一致
+	companyMap := make(map[string][]Stock.CompanyDeviceStatistics)
+	for _, stat := range R_List {
+		if len(stat.T_stock_out_list) == 0 {
+			continue
+		}
+		companyMap[stat.T_company_name] = append(companyMap[stat.T_company_name], stat)
+	}
+	companyNames := make([]string, 0, len(companyMap))
+	for name := range companyMap {
+		companyNames = append(companyNames, name)
+	}
+	sort.Strings(companyNames)
+
+	// 2) 构造前端友好的结构:按公司返回 rows 列表 + 汇总
+	type RowItem struct {
+		Seq            int    `json:"seq"`
+		OutNumber      string `json:"out_number"`
+		ProductName    string `json:"product_name"`
+		DeviceSN       string `json:"device_sn"`
+		Num            int    `json:"num"`
+		Unit           string `json:"unit"`
+		Remark         string `json:"remark"`
+		ReturnNumber   string `json:"return_number"`
+		ReturnDeviceSN string `json:"return_device_sn"`
+		ReturnRemark   string `json:"return_remark"`
+	}
+	type CompanyBlock struct {
+		CompanyName        string    `json:"company_name"`
+		StatisticDateLabel string    `json:"statistic_date_label"`
+		OutTotal           int       `json:"out_total"`
+		ReturnTotal        int       `json:"return_total"`
+		Rows               []RowItem `json:"rows"`
+	}
+
+	resp := make([]CompanyBlock, 0, len(companyNames))
+	for _, companyName := range companyNames {
+		statList := companyMap[companyName]
+
+		// 构建退库 SN -> 信息 的映射
+		type ReturnInfo struct {
+			ReturnNumber string
+			SN           string
+			Num          int
+			Unit         string
+			Total        float64
+			Remark       string
+		}
+		returnSNMap := make(map[string]ReturnInfo)
+		for _, stat := range statList {
+			for _, ret := range stat.T_stock_return_list {
+				for _, sn := range ret.T_device_list {
+					if len(sn) > 0 {
+						returnSNMap[sn] = ReturnInfo{
+							ReturnNumber: ret.T_stock_return_number,
+							SN:           sn,
+							Num:          ret.T_num,
+							Unit:         ret.T_unit,
+							Total:        ret.T_total,
+							Remark:       ret.T_remark,
+						}
+					}
+				}
+			}
+		}
+
+		seqNo := 1
+		cumulativeOutTotal := 0
+		cumulativeReturnTotal := 0
+		rows := make([]RowItem, 0)
+
+		for _, stat := range statList {
+			for _, out := range stat.T_stock_out_list {
+				validSNs := make([]string, 0)
+				for _, sn := range out.T_device_list {
+					if len(sn) > 0 {
+						validSNs = append(validSNs, sn)
+					}
+				}
+				if len(validSNs) == 0 {
+					continue
+				}
+
+				cumulativeOutTotal += out.T_num
+
+				currentReturnNum := 0
+				returnRemark := ""
+				for _, sn := range validSNs {
+					if retInfo, exists := returnSNMap[sn]; exists {
+						currentReturnNum++
+						if len(returnRemark) == 0 {
+							returnRemark = retInfo.Remark
+						}
+					}
+				}
+				cumulativeReturnTotal += currentReturnNum
+
+				for idx, sn := range validSNs {
+					row := RowItem{
+						DeviceSN: sn,
+					}
+					if retInfo, ok := returnSNMap[sn]; ok {
+						row.ReturnDeviceSN = sn
+						row.ReturnNumber = retInfo.ReturnNumber
+					}
+					if idx == 0 {
+						row.Seq = seqNo
+						row.ProductName = out.T_product_name
+						row.OutNumber = stat.T_stock_out_number
+						row.Num = out.T_num
+						row.Unit = out.T_unit
+						row.Remark = out.T_remark
+						if len(returnRemark) > 0 {
+							row.ReturnRemark = returnRemark
+						}
+					}
+					rows = append(rows, row)
+				}
+				seqNo++
+			}
+		}
+
+		resp = append(resp, CompanyBlock{
+			OutTotal:    cumulativeOutTotal,
+			ReturnTotal: cumulativeReturnTotal,
+			Rows:        rows,
+		})
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: resp}
+	c.ServeJSON()
+	return
+}
+
+// Company_Device_Statistics_Excel 导出公司借出和退回设备统计Excel
+func (c *StockController) Company_Device_Statistics_Excel() {
+	// 查询参数
+	T_company_name := c.GetString("T_company_name") // 公司名称
+	T_depot_id, _ := c.GetInt("T_depot_id")         // 仓库ID
+	T_start_date := c.GetString("T_start_date")     // 开始日期
+	T_end_date := c.GetString("T_end_date")         // 结束日期
+
+	StockOutDao := Stock.NewStockOut(orm.NewOrm())
+	R_List, _ := StockOutDao.Read_Company_Device_Statistics(T_company_name, T_start_date, T_end_date, T_depot_id, 1, 9999)
+
+	// 生成Excel文件名
+	filename := fmt.Sprintf("公司设备借出退回统计(%s)", lib.GetRandstring(6, "0123456789", 0))
+
+	// 创建Excel文件
+	f := excelize.NewFile()
+
+	// 设置样式
+	StyleTitle, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Bold: true, Size: 14, Family: "宋体"},
+			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+		})
+
+	StyleHeader, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Bold: true, Size: 10, 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},
+			},
+			Fill: excelize.Fill{Type: "pattern", Color: []string{"#E0E0E0"}, Pattern: 1},
+		})
+
+	StyleCell, _ := f.NewStyle(
+		&excelize.Style{
+			Font:      &excelize.Font{Size: 10, 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},
+			},
+		})
+
+	currentRow := 1
+	sheetName := "Sheet1"
+
+	// 按公司名称分组数据
+	companyMap := make(map[string][]Stock.CompanyDeviceStatistics)
+	for _, stat := range R_List {
+		if len(stat.T_stock_out_list) == 0 {
+			continue
+		}
+		companyMap[stat.T_company_name] = append(companyMap[stat.T_company_name], stat)
+	}
+
+	// 提取并排序公司名称,确保导出顺序稳定
+	companyNames := make([]string, 0, len(companyMap))
+	for name := range companyMap {
+		companyNames = append(companyNames, name)
+	}
+	sort.Strings(companyNames)
+
+	// 按排序后的公司名称遍历
+	for _, companyName := range companyNames {
+		statList := companyMap[companyName]
+		// 合并标题行
+		f.MergeCell(sheetName, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("L%d", currentRow))
+		f.SetCellValue(sheetName, fmt.Sprintf("A%d", currentRow), "出库申请单/退库单")
+		f.SetCellStyle(sheetName, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("L%d", currentRow), StyleTitle)
+		f.SetRowHeight(sheetName, currentRow, 25)
+		currentRow++
+
+		// 出库信息行
+		f.MergeCell(sheetName, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("H%d", currentRow))
+		f.SetCellValue(sheetName, fmt.Sprintf("A%d", currentRow), "公司名称:"+companyName)
+		f.MergeCell(sheetName, fmt.Sprintf("I%d", currentRow), fmt.Sprintf("L%d", currentRow))
+		f.SetCellValue(sheetName, fmt.Sprintf("I%d", currentRow), "")
+		f.SetCellStyle(sheetName, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("L%d", currentRow), StyleCell)
+		currentRow++
+
+		// 日期行
+		f.MergeCell(sheetName, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("H%d", currentRow))
+		f.SetCellValue(sheetName, fmt.Sprintf("A%d", currentRow), "统计日期:"+time.Now().Format("2006-01-02"))
+		f.MergeCell(sheetName, fmt.Sprintf("I%d", currentRow), fmt.Sprintf("L%d", currentRow))
+		f.SetCellValue(sheetName, fmt.Sprintf("I%d", currentRow), "")
+		f.SetCellStyle(sheetName, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("L%d", currentRow), StyleCell)
+		currentRow++
+
+		// 表头(增加出库单号和退库单号列)
+		headers := []string{"序号", "出库单号", "品名", "设备编号", "数量", "单位", "累计", "备注", "退库单号", "退库设备编号", "累计", "备注"}
+		for i, header := range headers {
+			cell := fmt.Sprintf("%s%d", string(rune('A'+i)), currentRow)
+			f.SetCellValue(sheetName, cell, header)
+			f.SetCellStyle(sheetName, cell, cell, StyleHeader)
+		}
+		currentRow++
+
+		// 构建出库SN到退库信息的映射(包含退库单号)
+		type ReturnInfo struct {
+			ReturnNumber string // 退库单号
+			SN           string
+			Num          int
+			Unit         string
+			Total        float64
+			Remark       string
+		}
+		returnSNMap := make(map[string]ReturnInfo) // 出库SN -> 退库信息
+
+		// 收集该公司所有的退库信息
+		for _, stat := range statList {
+			for _, ret := range stat.T_stock_return_list {
+				for _, sn := range ret.T_device_list {
+					if len(sn) > 0 {
+						returnSNMap[sn] = ReturnInfo{
+							ReturnNumber: ret.T_stock_return_number, // 关联的退库单号
+							SN:           sn,
+							Num:          ret.T_num,
+							Unit:         ret.T_unit,
+							Total:        ret.T_total,
+							Remark:       ret.T_remark,
+						}
+					}
+				}
+			}
+		}
+
+		// 遍历该公司所有出库单的出库明细,按SN展开
+		seqNo := 1
+		cumulativeOutTotal := 0    // 累计出库总数
+		cumulativeReturnTotal := 0 // 累计退库总数
+		cumulativeStartRow := currentRow
+
+		// 记录每个出库单号的起始行和结束行,用于后续合并
+		type OutNumberRange struct {
+			StartRow int
+			EndRow   int
+			Number   string
+		}
+		outNumberRanges := make([]OutNumberRange, 0)
+
+		// 记录每个退库单号的起始行和结束行
+		type ReturnNumberRange struct {
+			StartRow int
+			EndRow   int
+			Number   string
+		}
+		//returnNumberRanges := make([]ReturnNumberRange, 0)
+
+		for _, stat := range statList {
+			outNumberStartRow := currentRow // 记录当前出库单号的起始行
+			//hasReturnInThisOut := false     // 标记当前出库单是否有退库
+			//returnNumberStartRow := currentRow
+
+			for _, out := range stat.T_stock_out_list {
+				// 过滤掉空的SN
+				validSNs := make([]string, 0)
+				for _, sn := range out.T_device_list {
+					if len(sn) > 0 {
+						validSNs = append(validSNs, sn)
+					}
+				}
+
+				if len(validSNs) == 0 {
+					continue
+				}
+
+				// 累加当前产品的出库数量
+				cumulativeOutTotal += out.T_num
+
+				// 计算当前产品的退库数量和获取退库备注
+				currentReturnNum := 0
+				returnRemark := ""
+				for _, sn := range validSNs {
+					if retInfo, exists := returnSNMap[sn]; exists {
+						currentReturnNum++
+						if len(returnRemark) == 0 {
+							returnRemark = retInfo.Remark // 使用第一个退库记录的备注
+						}
+					}
+				}
+				cumulativeReturnTotal += currentReturnNum
+
+				// 标记该出库单是否有退库
+				//if currentReturnNum > 0 {
+				//	hasReturnInThisOut = true
+				//}
+
+				startRow := currentRow
+				// 为每个SN创建一行
+				for idx, sn := range validSNs {
+					// 出库单号(C列)和设备编号(D列)
+					f.SetCellValue(sheetName, fmt.Sprintf("D%d", currentRow), sn)
+
+					// 退库信息(如果该SN已退库,则显示)
+					if retInfo, exists := returnSNMap[sn]; exists {
+						f.SetCellValue(sheetName, fmt.Sprintf("J%d", currentRow), sn) // 退库设备编号
+						f.SetCellValue(sheetName, fmt.Sprintf("I%d", currentRow), retInfo.ReturnNumber)
+						// 退库单号只在第一行显示
+						// if idx == 0 && currentReturnNum > 0 {
+						// 	f.SetCellValue(sheetName, fmt.Sprintf("I%d", currentRow), retInfo.ReturnNumber)
+						// }
+					}
+
+					// 只在第一行设置序号、品名、出库单号、数量、单位、累计、备注
+					if idx == 0 {
+						f.SetCellValue(sheetName, fmt.Sprintf("A%d", currentRow), seqNo)
+						f.SetCellValue(sheetName, fmt.Sprintf("C%d", currentRow), out.T_product_name)
+						f.SetCellValue(sheetName, fmt.Sprintf("B%d", currentRow), stat.T_stock_out_number) // 出库单号
+						f.SetCellValue(sheetName, fmt.Sprintf("E%d", currentRow), out.T_num)
+						f.SetCellValue(sheetName, fmt.Sprintf("F%d", currentRow), out.T_unit)
+						//f.SetCellValue(sheetName, fmt.Sprintf("G%d", currentRow), cumulativeOutTotal) // 显示累计出库总数
+						f.SetCellValue(sheetName, fmt.Sprintf("H%d", currentRow), out.T_remark)
+
+						// 退库数量、单位、累计合计、备注
+						if currentReturnNum > 0 {
+							//f.SetCellValue(sheetName, fmt.Sprintf("K%d", currentRow), currentReturnNum)
+							//f.SetCellValue(sheetName, fmt.Sprintf("L%d", currentRow), out.T_unit)
+							//f.SetCellValue(sheetName, fmt.Sprintf("M%d", currentRow), cumulativeReturnTotal) // 显示累计退库总数
+							f.SetCellValue(sheetName, fmt.Sprintf("L%d", currentRow), returnRemark) // 显示退库备注
+						}
+					}
+
+					// 设置单元格样式
+					for col := 'A'; col <= 'L'; col++ {
+						cell := fmt.Sprintf("%s%d", string(col), currentRow)
+						f.SetCellStyle(sheetName, cell, cell, StyleCell)
+					}
+					currentRow++
+				}
+
+				// 如果有多个SN,合并序号、品名、数量、单位、累计、备注列(出库单号C列和退库单号I列在外层统一合并)
+				if len(validSNs) > 1 {
+					endRow := currentRow - 1
+					f.MergeCell(sheetName, fmt.Sprintf("A%d", startRow), fmt.Sprintf("A%d", endRow))
+					f.MergeCell(sheetName, fmt.Sprintf("C%d", startRow), fmt.Sprintf("C%d", endRow))
+					f.MergeCell(sheetName, fmt.Sprintf("E%d", startRow), fmt.Sprintf("E%d", endRow))
+					f.MergeCell(sheetName, fmt.Sprintf("F%d", startRow), fmt.Sprintf("F%d", endRow))
+					f.MergeCell(sheetName, fmt.Sprintf("G%d", startRow), fmt.Sprintf("G%d", endRow))
+					f.MergeCell(sheetName, fmt.Sprintf("H%d", startRow), fmt.Sprintf("H%d", endRow))
+
+				}
+
+				seqNo++
+			}
+
+			// 记录当前出库单号的范围
+			if currentRow > outNumberStartRow {
+				outNumberRanges = append(outNumberRanges, OutNumberRange{
+					StartRow: outNumberStartRow,
+					EndRow:   currentRow - 1,
+					Number:   stat.T_stock_out_number,
+				})
+
+			}
+		}
+
+		// 合并相同出库单号的单元格(C列)
+		for _, rng := range outNumberRanges {
+			if rng.EndRow > rng.StartRow {
+				f.MergeCell(sheetName, fmt.Sprintf("B%d", rng.StartRow), fmt.Sprintf("B%d", rng.EndRow))
+			}
+		}
+
+		// 合并相同退库单号的单元格(I列)
+		// for _, rng := range returnNumberRanges {
+		// 	if rng.EndRow > rng.StartRow {
+		// 		f.MergeCell(sheetName, fmt.Sprintf("I%d", rng.StartRow), fmt.Sprintf("I%d", rng.EndRow))
+		// 	}
+		// }
+
+		// 合并出库累计
+		f.MergeCell(sheetName, fmt.Sprintf("G%d", cumulativeStartRow), fmt.Sprintf("G%d", currentRow-1))
+		f.SetCellValue(sheetName, fmt.Sprintf("G%d", cumulativeStartRow), cumulativeOutTotal) // 显示累计出库总数
+
+		// 合并退库累计
+		f.MergeCell(sheetName, fmt.Sprintf("K%d", cumulativeStartRow), fmt.Sprintf("K%d", currentRow-1))
+		f.SetCellValue(sheetName, fmt.Sprintf("K%d", cumulativeStartRow), cumulativeReturnTotal) // 显示累计退库总数
+
+		// 添加空行
+		currentRow++
+	}
+
+	// 设置列宽(14列)
+	f.SetColWidth(sheetName, "A", "A", 8)  // 序号
+	f.SetColWidth(sheetName, "B", "B", 20) // 品名
+	f.SetColWidth(sheetName, "C", "C", 20) // 出库单号
+	f.SetColWidth(sheetName, "D", "D", 30) // 设备编号
+	f.SetColWidth(sheetName, "E", "F", 10) // 数量、单位
+	f.SetColWidth(sheetName, "G", "G", 12) // 累计
+	f.SetColWidth(sheetName, "H", "H", 20) // 备注
+	f.SetColWidth(sheetName, "I", "I", 20) // 退库单号
+	f.SetColWidth(sheetName, "J", "J", 30) // 退库设备编号
+	f.SetColWidth(sheetName, "K", "K", 12) // 累计
+	f.SetColWidth(sheetName, "L", "L", 20) // 备注
+
+	// 保存文件
+	filepath := "./ofile/" + filename + ".xlsx"
+	if err := f.SaveAs(filepath); err != nil {
+		logs.Error(lib.FuncName(), err)
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "生成Excel失败"}
+		c.ServeJSON()
+		return
+	}
+
+	var url string
+	//// 上传 OSS
+	nats := natslibs.NewNats(Nats.Nats, conf.NatsSubj_Prefix)
+	url, is := nats.Qiniu_UploadFile(lib.GetCurrentDirectory()+"/ofile/"+filename+".xlsx", "ofile/"+filename+".xlsx")
+	if !is {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "oss!"}
+		c.ServeJSON()
+		return
+	}
+	//删除目录
+	err := os.Remove("ofile/" + filename + ".xlsx")
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: url}
+	c.ServeJSON()
+	return
+}
+
+// Company_Name_List 获取所有出库的公司名称列表
+func (c *StockController) Stock_Out_Company_Name_List() {
+	T_name := c.GetString("T_name") // 公司名称(模糊查询)
+
+	StockOutDao := Stock.NewStockOut(orm.NewOrm())
+	R_List, err := StockOutDao.Read_Company_Name_List(T_name)
+
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "查询失败"}
+		c.ServeJSON()
+		return
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: R_List}
+	c.ServeJSON()
+	return
+}
+
+// Company_Name_List 获取所有出库的公司名称列表
+func (c *StockController) Stock_Out_Project_List() {
+	T_name := c.GetString("T_name") // 公司名称(模糊查询)
+
+	StockOutDao := Stock.NewStockOut(orm.NewOrm())
+	R_List, err := StockOutDao.Read_Project_List(T_name)
+
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "查询失败"}
+		c.ServeJSON()
+		return
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: R_List}
+	c.ServeJSON()
+	return
+}

+ 1 - 1
go.mod

@@ -14,7 +14,7 @@ require (
 	github.com/smartystreets/goconvey v1.8.1
 	github.com/vmihailenco/msgpack/v5 v5.3.5
 	github.com/xuri/excelize/v2 v2.9.0
-	gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11
+	gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20251106072747-8715c5686ee8
 	gorm.io/driver/mysql v1.5.7
 	gorm.io/gorm v1.25.11
 )

+ 2 - 0
go.sum

@@ -364,6 +364,8 @@ gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250421082929-038047d473e1 h1:Tc4YfrvTkAX
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250421082929-038047d473e1/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11 h1:aMuM32qXdOojinJW9w9rm0tSopDvD5eecI14YXnwMdA=
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20251106072747-8715c5686ee8 h1:UisQz0DrnbqRFLRgeD7iiungJp6qZlyfRUsMQpXUOmE=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20251106072747-8715c5686ee8/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

+ 71 - 76
models/Purchase/Purchase.go

@@ -3,12 +3,13 @@ package Purchase
 import (
 	"ERP_storage/logs"
 	"ERP_storage/models/Account"
+	"encoding/json"
 	"fmt"
+	"time"
+
 	"github.com/beego/beego/v2/adapter/orm"
 	orm2 "github.com/beego/beego/v2/client/orm"
 	"gogs.baozhida.cn/zoie/ERP_libs/lib"
-	"strings"
-	"time"
 )
 
 const (
@@ -22,15 +23,18 @@ const (
 
 // 项目管理
 type Purchase struct {
-	Id              int    `orm:"column(id);size(11);auto;pk"`
-	T_uuid          string `orm:"size(32);null"`   // 申请人
-	T_dept          string `orm:"size(32);null"`   // 申请部门
-	T_submit        string `orm:"size(32);null"`   // 提交人
-	T_date          string `orm:"size(256);null"`  // 申请时间
-	T_detail        string `orm:"type(text);null"` // 明细
-	T_remark        string `orm:"type(text);null"` // 备注
-	T_approver      string `orm:"size(32);null"`   // 审批人
-	T_approver_date string `orm:"size(32);null"`   // 审批时间
+	Id                 int     `orm:"column(id);size(11);auto;pk"`
+	T_uuid             string  `orm:"size(32);null"`          // 申请人
+	T_dept             string  `orm:"size(32);null"`          // 申请部门
+	T_submit           string  `orm:"size(32);null"`          // 提交人
+	T_date             string  `orm:"size(256);null"`         // 申请时间
+	T_detail           string  `orm:"type(text);null"`        // 明细
+	T_remark           string  `orm:"type(text);null"`        // 备注
+	T_approver         string  `orm:"size(32);null"`          // 审批人
+	T_approver_date    string  `orm:"size(32);null"`          // 审批时间
+	T_product_name     string  `orm:"size(500);null"`         // 产品名称
+	T_total_price      float32 `orm:"digits(12);decimals(2)"` // 采购总金额
+	T_product_quantity int     `orm:"size(32);null"`          // 产品数量
 
 	T_State    int       `orm:"size(2);column(t_state);default(1)"`                    // 0 删除  1待采购  2已采购 3待审核 4未通过
 	CreateTime time.Time `orm:"column(create_time);type(timestamp);null;auto_now_add"` //auto_now_add 第一次保存时才设置时间
@@ -55,47 +59,56 @@ func init() {
 }
 
 type Purchase_R struct {
-	Id              int
-	T_dept          string // 申请部门
-	T_uuid          string // 申请人
-	T_uuid_name     string // 申请人名称
-	T_submit        string // 提交人
-	T_submit_name   string // 提交人
-	T_date          string // 申请时间
-	T_remark        string // 备注
-	T_approver      string // 审批人
-	T_approver_name string // 审批人
-	T_approver_date string // 审批时间
-	T_State         int
+	Id                 int
+	T_dept             string // 申请部门
+	T_uuid             string // 申请人
+	T_uuid_name        string // 申请人名称
+	T_submit           string // 提交人
+	T_submit_name      string // 提交人
+	T_date             string // 申请时间
+	T_remark           string // 备注
+	T_approver         string // 审批人
+	T_approver_name    string // 审批人
+	T_approver_date    string // 审批时间
+	T_State            int
+	T_product_name     string
+	T_total_price      float32
+	T_product_quantity int
 }
 
 type PurchaseDetail_R struct {
-	T_name           string  // 名称
-	T_model          string  // 型号
-	T_spec           string  // 规格
-	T_quantity       int     // 数量
-	T_demand         string  // 需求
-	T_remark         string  // 备注
-	T_state          int     // 状态
-	T_date           string  // 采购时间
-	T_unit_price     float32 // 采购单价
-	T_amount         string  // 采购金额
-	T_reference_site string  // 参考网址
+	T_name                 string  // 名称
+	T_model                string  // 型号
+	T_bit_number           string  // 位号
+	T_packaging            string  // 封装
+	T_spec                 string  // 规格
+	T_quantity             int     // 数量
+	T_demand               string  // 需求
+	T_remark               string  // 备注
+	T_state                int     // 状态
+	T_date                 string  // 采购时间
+	T_unit_price           float32 // 采购单价
+	T_amount               float32 // 采购金额
+	T_reference_site       string  // 参考网址
+	T_reference_site_title string  // 参考网址标题
 }
 type Purchase_Detail struct {
-	Id              int
-	T_dept          string // 申请部门
-	T_uuid          string // 申请人
-	T_uuid_name     string // 用户名称
-	T_submit        string // 提交人
-	T_submit_name   string // 提交人
-	T_date          string // 申请时间
-	T_remark        string // 备注
-	T_approver      string // 审批人
-	T_approver_name string // 审批人
-	T_approver_date string // 审批时间
-	T_State         int
-	T_Detail        []PurchaseDetail_R // 项目明细
+	Id                 int
+	T_dept             string // 申请部门
+	T_uuid             string // 申请人
+	T_uuid_name        string // 用户名称
+	T_submit           string // 提交人
+	T_submit_name      string // 提交人
+	T_date             string // 申请时间
+	T_remark           string // 备注
+	T_approver         string // 审批人
+	T_approver_name    string // 审批人
+	T_approver_date    string // 审批时间
+	T_product_name     string
+	T_total_price      float32
+	T_product_quantity int
+	T_State            int
+	T_Detail           []PurchaseDetail_R // 项目明细
 }
 
 // ---------------- 特殊方法 -------------------
@@ -113,6 +126,9 @@ func PurchaseToPurchase_R(t Purchase) (r Purchase_R) {
 	r.T_approver_name = Account.Read_User_T_name_Get(t.T_approver)
 	r.T_approver_date = t.T_approver_date
 	r.T_State = t.T_State
+	r.T_product_name = t.T_product_name
+	r.T_total_price = t.T_total_price
+	r.T_product_quantity = t.T_product_quantity
 
 	return r
 }
@@ -130,6 +146,9 @@ func PurchaseToPurchase_Detail(t Purchase) (r Purchase_Detail) {
 	r.T_approver_name = Account.Read_User_T_name_Get(t.T_approver)
 	r.T_approver_date = t.T_approver_date
 	r.T_State = t.T_State
+	r.T_product_name = t.T_product_name
+	r.T_total_price = t.T_total_price
+	r.T_product_quantity = t.T_product_quantity
 	r.T_Detail = PurchaseToPurchaseDetail(t.T_detail)
 	return r
 }
@@ -139,35 +158,11 @@ func PurchaseToPurchaseDetail(T_detail string) (r []PurchaseDetail_R) {
 	if len(T_detail) == 0 {
 		return
 	}
-
-	detailList := strings.Split(strings.Trim(T_detail, "|"), "|")
-
-	for _, detail := range detailList {
-		T_name := strings.Split(detail, ",")[0]
-		T_model := strings.Split(detail, ",")[1]
-		T_spec := strings.Split(detail, ",")[2]
-		T_quantity := strings.Split(detail, ",")[3]
-		T_demand := strings.Split(detail, ",")[4]
-		T_remark := strings.Split(detail, ",")[5]
-		T_state := strings.Split(detail, ",")[6]
-		T_date := strings.Split(detail, ",")[7]
-		T_unit_price := strings.Split(detail, ",")[8]
-		T_amount := strings.Split(detail, ",")[9]
-		T_reference_site := strings.Split(detail, ",")[10]
-
-		r = append(r, PurchaseDetail_R{
-			T_name:           T_name,
-			T_model:          T_model,
-			T_spec:           T_spec,
-			T_quantity:       lib.To_int(T_quantity),
-			T_demand:         T_demand,
-			T_remark:         T_remark,
-			T_state:          lib.To_int(T_state),
-			T_date:           T_date,
-			T_unit_price:     lib.To_float32(T_unit_price),
-			T_amount:         T_amount,
-			T_reference_site: T_reference_site,
-		})
+	// 将JSON字符串转换为PurchaseDetail_R结构体切片
+	err := json.Unmarshal([]byte(T_detail), &r)
+	if err != nil {
+		logs.Error(lib.FuncName(), "JSON解析错误:", err)
+		return []PurchaseDetail_R{}
 	}
 	return r
 }

+ 9 - 2
models/Stock/StockIn.go

@@ -4,12 +4,14 @@ import (
 	"ERP_storage/models/Account"
 	"ERP_storage/models/Basic"
 	"fmt"
-	orm2 "github.com/beego/beego/v2/client/orm"
-	"gogs.baozhida.cn/zoie/ERP_libs/lib"
 	"strconv"
 	"time"
 
+	orm2 "github.com/beego/beego/v2/client/orm"
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+
 	"ERP_storage/logs"
+
 	_ "github.com/astaxie/beego/cache/redis"
 	"github.com/beego/beego/v2/adapter/orm"
 	_ "github.com/go-sql-driver/mysql"
@@ -35,6 +37,7 @@ type StockIn struct {
 	T_submit           string    `orm:"size(256);null"`                                        // 经办人
 	T_remark           string    `orm:"type(text);null"`                                       // 备注
 	T_project          string    `orm:"type(text);null"`                                       // 退库项目
+	T_company_name     string    `orm:"size(256);null"`                                        // 退库公司名称
 	T_return_user      string    `orm:"type(256);null"`                                        // 退库人
 	T_batch_number     string    `orm:"type(256);null"`                                        // 批次号
 	T_warehouse        string    `orm:"type(text);null"`                                       // 入库人
@@ -76,6 +79,7 @@ type StockIn_R struct {
 	T_return_user      string
 	T_return_user_name string
 	T_batch_number     string
+	T_company_name     string
 }
 
 type StockIn_Detail struct {
@@ -94,6 +98,7 @@ type StockIn_Detail struct {
 	T_return_user      string
 	T_return_user_name string
 	T_batch_number     string
+	T_company_name     string
 	T_Product          []StockInProduct_R
 }
 
@@ -112,6 +117,7 @@ func StockInToStockIn_R(t StockIn) (r StockIn_R) {
 	r.T_batch_number = t.T_batch_number
 	r.T_return_user = t.T_return_user
 	r.T_state = t.T_state
+	r.T_company_name = t.T_company_name
 	r.T_return_user_name = Account.Read_User_T_name_Get(t.T_return_user)
 	return r
 }
@@ -132,6 +138,7 @@ func StockInToStockIn_Detail(t StockIn, productList []StockInProduct_R) (r Stock
 	r.T_project = t.T_project
 	r.T_return_user = t.T_return_user
 	r.T_batch_number = t.T_batch_number
+	r.T_company_name = t.T_company_name
 	r.T_return_user_name = Account.Read_User_T_name_Get(t.T_return_user)
 	return r
 }

+ 219 - 2
models/Stock/StockOut.go

@@ -4,13 +4,15 @@ import (
 	"ERP_storage/models/Account"
 	"ERP_storage/models/Basic"
 	"fmt"
-	orm2 "github.com/beego/beego/v2/client/orm"
-	"gogs.baozhida.cn/zoie/ERP_libs/lib"
 	"strconv"
 	"strings"
 	"time"
 
+	orm2 "github.com/beego/beego/v2/client/orm"
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+
 	"ERP_storage/logs"
+
 	_ "github.com/astaxie/beego/cache/redis"
 	"github.com/beego/beego/v2/adapter/orm"
 	_ "github.com/go-sql-driver/mysql"
@@ -500,3 +502,218 @@ func (dao *StockOutDaoImpl) Read_StockOutProduct_List(T_name, T_start_date, T_en
 	count, _ := strconv.Atoi(maps_z[0][0].(string))
 	return r_, int64(count)
 }
+
+// 公司借出退回设备统计
+type CompanyDeviceStatistics struct {
+	T_company_name      string                    // 公司名称
+	T_stock_out_number  string                    // 出库单号
+	T_stock_out_date    string                    // 出库日期
+	T_payment_method    string                    // 付款方式
+	T_remark            string                    // 备注
+	T_stock_out_list    []CompanyDeviceOutItem    // 出库明细
+	T_stock_return_list []CompanyDeviceReturnItem // 退库明细
+}
+
+type CompanyDeviceOutItem struct {
+	T_product_name  string   // 产品名称
+	T_device_number string   // 设备编号
+	T_num           int      // 数量
+	T_unit          string   // 单位
+	T_total         float64  // 合计
+	T_remark        string   // 备注
+	T_device_list   []string // 设备SN列表
+}
+
+type CompanyDeviceReturnItem struct {
+	T_stock_return_number string   // 出库单号
+	T_product_name        string   // 产品名称
+	T_device_number       string   // 设备编号
+	T_num                 int      // 数量
+	T_unit                string   // 单位
+	T_total               float64  // 合计
+	T_remark              string   // 备注
+	T_return_date         string   // 退库日期
+	T_device_list         []string // 设备SN列表
+}
+
+// 查询公司借出和退回设备统计
+func (dao *StockOutDaoImpl) Read_Company_Device_Statistics(T_company_name, T_start_date, T_end_date string, T_depot_id int, page, page_z int) (r_ []CompanyDeviceStatistics, cnt int64) {
+	var offset int
+	if page <= 1 {
+		offset = 0
+	} else {
+		offset = (page - 1) * page_z
+	}
+
+	sqlWhere := " AND so.t_state = " + strconv.Itoa(StockOutAlreadyOut)          // 只查询已出库的
+	sqlWhere += " AND so.t_company_name != '' AND so.t_company_name IS NOT NULL" // 只查询有公司名称的
+	if len(T_company_name) > 0 {
+		sqlWhere += " AND so.t_company_name like \"%" + T_company_name + "%\""
+	}
+	if T_depot_id > 0 {
+		sqlWhere += fmt.Sprintf(" AND so.t_depot_id = %d", T_depot_id)
+	}
+	if len(T_start_date) > 0 {
+		sqlWhere += " AND so.t_date >= \"" + T_start_date + "\""
+	}
+	if len(T_end_date) > 0 {
+		sqlWhere += " AND so.t_date <= \"" + T_end_date + "\""
+	}
+
+	// 获取总条数
+	var maps_z []orm2.ParamsList
+	sql := "SELECT COUNT(DISTINCT so.ID) FROM stock_out so WHERE 1=1 " + sqlWhere
+	_, err := dao.orm.Raw(sql).ValuesList(&maps_z)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return nil, 0
+	}
+	if len(maps_z) == 0 {
+		return nil, 0
+	}
+	count, _ := strconv.Atoi(maps_z[0][0].(string))
+	cnt = int64(count)
+
+	// 查询出库单列表
+	sql = "SELECT so.* FROM stock_out so WHERE 1=1 " + sqlWhere + " ORDER BY so.t_date DESC"
+	if page_z != 9999 {
+		sql += " LIMIT " + strconv.Itoa(offset) + "," + strconv.Itoa(page_z)
+	}
+
+	var stockOutList []StockOut
+	_, err = dao.orm.Raw(sql).QueryRows(&stockOutList)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return nil, 0
+	}
+
+	// 对每个出库单,查询出库明细和退库明细
+	for _, stockOut := range stockOutList {
+		stat := CompanyDeviceStatistics{
+			T_company_name:     stockOut.T_company_name,
+			T_stock_out_number: stockOut.T_number,
+			T_stock_out_date:   stockOut.T_date,
+			T_payment_method:   stockOut.T_payment_method,
+			T_remark:           stockOut.T_remark,
+		}
+
+		// 查询出库产品明细(只查询需要关联SN的产品)
+		sqlOut := "SELECT sop.*, p.t_name as product_name, p.t_model, p.t_spec FROM stock_out_product sop " +
+			"LEFT JOIN product p ON sop.t_product_id = p.ID " +
+			"WHERE sop.t_number = ? AND p.t_relation_sn = 1"
+
+		type OutProductTemp struct {
+			StockOutProduct
+			Product_name string
+			T_model      string
+			T_spec       string
+		}
+		var outProducts []OutProductTemp
+		_, err = dao.orm.Raw(sqlOut, stockOut.T_number).QueryRows(&outProducts)
+		if err == nil {
+			for _, op := range outProducts {
+				item := CompanyDeviceOutItem{
+					T_product_name:  op.Product_name,
+					T_device_number: op.T_model,
+					T_num:           op.T_num,
+					T_unit:          op.T_spec,
+					T_device_list:   lib.SplitString(op.T_relation_sn, ","),
+				}
+				stat.T_stock_out_list = append(stat.T_stock_out_list, item)
+			}
+		}
+
+		// 查询退库明细(从stock_in中查找t_type=2的记录,只查询需要关联SN的产品)
+		sqlReturn := "SELECT si.t_number, si.t_date, si.t_remark, sip.*, p.t_name as product_name, p.t_model, p.t_spec " +
+			"FROM stock_in si " +
+			"LEFT JOIN stock_in_product sip ON si.t_number = sip.t_number " +
+			"LEFT JOIN product p ON sip.t_product_id = p.ID " +
+			"WHERE si.t_type = 2 AND si.t_company_name like ? AND si.t_state = " + strconv.Itoa(StockInWarehouse) + " AND p.t_relation_sn = 1"
+
+		type ReturnProductTemp struct {
+			T_number string
+			T_date   string
+			T_remark string
+			StockInProduct
+			Product_name string
+			T_model      string
+			T_spec       string
+		}
+		var returnProducts []ReturnProductTemp
+		// 通过备注或其他方式关联(这里假设退库时备注中包含出库单号)
+		_, err = dao.orm.Raw(sqlReturn, "%"+stockOut.T_company_name+"%").QueryRows(&returnProducts)
+		if err == nil {
+			for _, rp := range returnProducts {
+				item := CompanyDeviceReturnItem{
+					T_stock_return_number: rp.T_number,
+					T_product_name:        rp.Product_name,
+					T_device_number:       rp.T_model,
+					T_num:                 rp.T_num,
+					T_unit:                rp.T_spec,
+					T_return_date:         rp.T_date,
+					T_remark:              rp.T_remark,
+					T_device_list:         lib.SplitString(rp.T_relation_sn, ","),
+				}
+				stat.T_stock_return_list = append(stat.T_stock_return_list, item)
+			}
+		}
+
+		r_ = append(r_, stat)
+	}
+
+	return r_, cnt
+}
+
+// 获取所有出库的公司名称列表(去重,不包含空数据)
+func (dao *StockOutDaoImpl) Read_Company_Name_List(T_name string) (r_ []string, err error) {
+	sql := "SELECT DISTINCT t_company_name FROM stock_out " +
+		"WHERE t_company_name != '' AND t_company_name IS NOT NULL"
+
+	if len(T_name) > 0 {
+		sql += " AND t_company_name like '%" + T_name + "%'"
+	}
+
+	sql += " ORDER BY t_company_name ASC"
+
+	var pl_lists orm2.ParamsList
+	_, err = dao.orm.Raw(sql).ValuesFlat(&pl_lists)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return nil, err
+	}
+
+	for _, v := range pl_lists {
+		if v != nil {
+			r_ = append(r_, v.(string))
+		}
+	}
+
+	return r_, nil
+}
+
+// 获取所有出库的项目列表(去重,不包含空数据)
+func (dao *StockOutDaoImpl) Read_Project_List(T_name string) (r_ []string, err error) {
+	sql := "SELECT DISTINCT t_project FROM stock_out " +
+		"WHERE t_project != '' AND t_project IS NOT NULL"
+
+	if len(T_name) > 0 {
+		sql += " AND t_project like '%" + T_name + "%'"
+	}
+
+	sql += " ORDER BY t_project ASC"
+
+	var pl_lists orm2.ParamsList
+	_, err = dao.orm.Raw(sql).ValuesFlat(&pl_lists)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return nil, err
+	}
+
+	for _, v := range pl_lists {
+		if v != nil {
+			r_ = append(r_, v.(string))
+		}
+	}
+
+	return r_, nil
+}

+ 18 - 13
routers/Stock.go

@@ -50,18 +50,23 @@ func init() {
 		beego.NSRouter("/Edit", &controllers.StockController{}, "*:StockOut_Edit"),                             // 修改出库
 		beego.NSRouter("/Del", &controllers.StockController{}, "*:StockOut_Del"),                               // 删除出库
 		beego.NSRouter("/Excel", &controllers.StockController{}, "*:StockOut_Excel"),
-		beego.NSRouter("/Excel_Batch", &controllers.StockController{}, "*:StockOut_Excel_Batch"),         // 批量导出入库
-		beego.NSRouter("/Apply", &controllers.StockController{}, "*:StockOut_Apply"),                     // 出库申请
-		beego.NSRouter("/Apply_Edit", &controllers.StockController{}, "*:StockOut_Apply_Edit"),           // 修改出库申请
-		beego.NSRouter("/Apply_Del", &controllers.StockController{}, "*:StockOut_Apply_Del"),             // 删除出库申请
-		beego.NSRouter("/Apply_List", &controllers.StockController{}, "*:StockOut_Apply_List"),           // 出库申请列表
-		beego.NSRouter("/Finance_List", &controllers.StockController{}, "*:StockOut_Finance_List"),       // 财务审核列表
-		beego.NSRouter("/Manager_List", &controllers.StockController{}, "*:StockOut_Manager_List"),       // 总经理审核列表
-		beego.NSRouter("/Warehouse_List", &controllers.StockController{}, "*:StockOut_Warehouse_List"),   // 总经理审核列表
-		beego.NSRouter("/Audit", &controllers.StockController{}, "*:StockOut_Audit"),                     // 审核
-		beego.NSRouter("/Warehouse", &controllers.StockController{}, "*:StockOut_Warehouse"),             // 确认出库
-		beego.NSRouter("/Generate_Number", &controllers.StockController{}, "*:StockOut_generate_number"), // 批量导出入库
-
+		beego.NSRouter("/Excel_Batch", &controllers.StockController{}, "*:StockOut_Excel_Batch"),              // 批量导出入库
+		beego.NSRouter("/Apply", &controllers.StockController{}, "*:StockOut_Apply"),                          // 出库申请
+		beego.NSRouter("/Apply_Edit", &controllers.StockController{}, "*:StockOut_Apply_Edit"),                // 修改出库申请
+		beego.NSRouter("/Apply_Del", &controllers.StockController{}, "*:StockOut_Apply_Del"),                  // 删除出库申请
+		beego.NSRouter("/Apply_List", &controllers.StockController{}, "*:StockOut_Apply_List"),                // 出库申请列表
+		beego.NSRouter("/Finance_List", &controllers.StockController{}, "*:StockOut_Finance_List"),            // 财务审核列表
+		beego.NSRouter("/Manager_List", &controllers.StockController{}, "*:StockOut_Manager_List"),            // 总经理审核列表
+		beego.NSRouter("/Warehouse_List", &controllers.StockController{}, "*:StockOut_Warehouse_List"),        // 总经理审核列表
+		beego.NSRouter("/Audit", &controllers.StockController{}, "*:StockOut_Audit"),                          // 审核
+		beego.NSRouter("/Warehouse", &controllers.StockController{}, "*:StockOut_Warehouse"),                  // 确认出库
+		beego.NSRouter("/Generate_Number", &controllers.StockController{}, "*:StockOut_generate_number"),      // 批量导出入库
+		beego.NSRouter("/Company_Name_List", &controllers.StockController{}, "*:Stock_Out_Company_Name_List"), // 获取所有公司名称列表
+		beego.NSRouter("/Project_List", &controllers.StockController{}, "*:Stock_Out_Project_List"),           // 获取所有项目列表
+	)
+	company := beego.NewNamespace("/Company",
+		beego.NSRouter("/Device_Statistics", &controllers.StockController{}, "*:Company_Device_Statistics"),             // 公司设备借出退回统计
+		beego.NSRouter("/Device_Statistics_Excel", &controllers.StockController{}, "*:Company_Device_Statistics_Excel"), // 导出公司设备借出退回统计
 	)
-	beego.AddNamespace(device, stock, stockIn, stockOut)
+	beego.AddNamespace(device, stock, stockIn, stockOut, company)
 }