daShangDao_psiServer/service/warehouse.go
2026-06-15 13:47:39 +08:00

712 lines
18 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"errors"
"fmt"
"github.com/xuri/excelize/v2"
"psi/config"
systemRes "psi/models/response"
"psi/utils"
"strconv"
"strings"
"time"
"gorm.io/gorm"
"psi/database"
"psi/models"
systemReq "psi/models/request"
)
type WarehouseService struct{}
// GetWarehouseList 获取仓库列表
func (s *WarehouseService) GetWarehouseList(req systemReq.QueryWarehouseRequest, db ...*gorm.DB) ([]systemRes.WarehouseResponse, int64, error) {
databaseConn := database.OptionalDB(db...)
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 || req.PageSize > 100 {
req.PageSize = 20
}
query := databaseConn.Model(&models.Warehouse{}).Where("is_del = ?", 0)
if len(req.IDs) > 0 {
query = query.Where("id IN ?", req.IDs)
}
if req.Keyword != "" {
query = query.Where("code like ? OR name like ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
if req.Type != nil {
query = query.Where("type = ?", *req.Type)
}
if req.Status != nil {
query = query.Where("status = ?", *req.Status)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, errors.New("查询仓库总数失败")
}
var warehouses []models.Warehouse
offset := (req.Page - 1) * req.PageSize
if err := query.Order("id DESC").Offset(offset).Limit(req.PageSize).Find(&warehouses).Error; err != nil {
return nil, 0, errors.New("查询仓库列表失败")
}
responses := make([]systemRes.WarehouseResponse, 0, len(warehouses))
for _, wh := range warehouses {
resp := systemRes.ConvertWarehouseToResponse(wh)
responses = append(responses, resp)
}
return responses, total, nil
}
// GetWarehouseByID 获取仓库信息
func (s *WarehouseService) GetWarehouseByID(id int64, db ...*gorm.DB) (*systemRes.WarehouseResponse, error) {
databaseConn := database.OptionalDB(db...)
var warehouse models.Warehouse
if err := databaseConn.Where("id = ? AND is_del = ?", id, 0).First(&warehouse).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("仓库不存在")
}
return nil, errors.New("查询仓库失败")
}
resp := systemRes.ConvertWarehouseToResponse(warehouse)
return &resp, nil
}
// CreateWarehouse 创建仓库
func (s *WarehouseService) CreateWarehouse(req systemReq.CreateWarehouseRequest, aboutId int64, userName string, db ...*gorm.DB) (int64, error) {
databaseConn := database.OptionalDB(db...)
var count int64
databaseConn.Model(&models.Warehouse{}).
Where("code = ? AND is_del = ?", req.Code, 0).
Count(&count)
if count > 0 {
return 0, errors.New("仓库编码已存在")
}
warehouse := models.Warehouse{
LogisticsID: req.LogisticsID,
Code: req.Code,
Name: req.Name,
Type: req.Type,
ContactPerson: req.ContactPerson,
ContactPhone: req.ContactPhone,
Province: req.Province,
City: req.City,
District: req.District,
Address: req.Address,
Status: req.Status,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
IsDel: 0,
}
if warehouse.Type == 0 {
warehouse.Type = 1
}
if warehouse.Status == 0 {
warehouse.Status = 1
}
if err := databaseConn.Create(&warehouse).Error; err != nil {
return 0, errors.New("创建仓库失败")
}
if err := s.SyncUserWarehouseMapping(aboutId, warehouse.ID, userName, req.Code, req.Name, "create"); err != nil {
return 0, errors.New("创建仓库映射关系失败")
}
return warehouse.ID, nil
}
// UpdateWarehouse 更新仓库
func (s *WarehouseService) UpdateWarehouse(req systemReq.UpdateWarehouseRequest, aboutId int64, userName string, db ...*gorm.DB) error {
databaseConn := database.OptionalDB(db...)
var warehouse models.Warehouse
if err := databaseConn.Where("id = ? AND is_del = ?", req.ID, 0).First(&warehouse).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("仓库不存在")
}
return errors.New("查询仓库失败")
}
updates := make(map[string]interface{})
updates["updated_at"] = time.Now().Unix()
if req.Name != "" {
updates["name"] = req.Name
}
if req.Type >= 1 && req.Type <= 3 {
updates["type"] = req.Type
}
if req.ContactPerson != "" {
updates["contact_person"] = req.ContactPerson
}
if req.ContactPhone != "" {
updates["contact_phone"] = req.ContactPhone
}
if req.Province != "" {
updates["province"] = req.Province
}
if req.City != "" {
updates["city"] = req.City
}
if req.District != "" {
updates["district"] = req.District
}
if req.Address != "" {
updates["address"] = req.Address
}
if req.Status == 0 || req.Status == 1 {
updates["status"] = req.Status
}
if req.LogisticsID > 0 {
updates["logistics_id"] = req.LogisticsID
}
if err := databaseConn.Model(&warehouse).Updates(updates).Error; err != nil {
return errors.New("更新仓库失败")
}
if err := s.SyncUserWarehouseMapping(aboutId, warehouse.ID, userName, warehouse.Code, utils.Ternary(req.Name == "", warehouse.Name, req.Name), "update"); err != nil {
return errors.New("更新仓库映射关系失败")
}
return nil
}
// DeleteWarehouse 删除仓库
// aboutId: 用户ID
// userName: 用户名称
// db: 数据库连接
func (s *WarehouseService) DeleteWarehouse(id, aboutId int64, userName string, db ...*gorm.DB) error {
databaseConn := database.OptionalDB(db...)
if id == 0 {
return errors.New("仓库ID不能为空")
}
var warehouse models.Warehouse
if err := databaseConn.Where("id = ? AND is_del = ?", id, 0).First(&warehouse).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New("仓库不存在")
}
return errors.New("查询仓库失败")
}
var locationCount int64
databaseConn.Model(&models.Location{}).
Where("warehouse_id = ? AND is_del = ?", id, 0).
Count(&locationCount)
if locationCount > 0 {
return errors.New("该仓库下存在库位,无法删除")
}
// 执行真正的物理删除
if err := databaseConn.Delete(&warehouse).Error; err != nil {
return errors.New("删除仓库失败")
}
if err := s.SyncUserWarehouseMapping(aboutId, warehouse.ID, userName, warehouse.Code, warehouse.Name, "delete"); err != nil {
return errors.New("删除仓库映射关系失败")
}
return nil
}
// SyncUserWarehouseMapping 同步用户仓库映射关系
// aboutID: 用户关联ID
// code: 仓库编码
// name: 仓库名称
// operation: 操作类型 create/update/delete
func (s *WarehouseService) SyncUserWarehouseMapping(aboutID, id int64, userName, code, name, operation string) error {
db := database.DB
switch operation {
case "create", "update":
return s.upsertUserMapping(db, aboutID, id, userName, code, name)
case "delete":
return s.deleteUserMapping(db, aboutID, id)
default:
return nil
}
}
// GetUserWarehouseMappings 获取用户的仓库映射列表
func (s *WarehouseService) GetUserWarehouseMappings() ([]models.UserMapping, error) {
db := database.DB
var mappings []models.UserMapping
err := db.Where("is_del = ?", 0).
Order("id DESC").
Find(&mappings).Error
return mappings, err
}
// ExportLocations 导出库位数据
func (s *LocationService) ExportLocations(req systemReq.ExportLocationRequest, db ...*gorm.DB) (*systemRes.ExportLocationResponse, error) {
databaseConn := database.OptionalDB(db...)
query := databaseConn.Model(&models.Location{}).
Where("is_del = ?", 0)
if req.WarehouseID > 0 {
query = query.Where("warehouse_id = ?", req.WarehouseID)
}
if req.Type > 0 {
query = query.Where("type = ?", req.Type)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, utils.NewError("查询库位总数失败")
}
if total == 0 {
return nil, fmt.Errorf("没有符合条件的库位数据")
}
var locations []models.Location
if err := query.Order("warehouse_id ASC, code ASC").Find(&locations).Error; err != nil {
return nil, utils.NewError("查询库位数据失败")
}
var warehouses []models.Warehouse
warehouseIDs := make([]int64, 0)
for _, loc := range locations {
warehouseIDs = append(warehouseIDs, loc.WarehouseID)
}
if len(warehouseIDs) > 0 {
databaseConn.Where("id IN ? AND is_del = ?", warehouseIDs, 0).Find(&warehouses)
}
warehouseMap := make(map[int64]string)
for _, wh := range warehouses {
warehouseMap[wh.ID] = wh.Code
}
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("关闭Excel文件失败: %v\n", err)
}
}()
sheetName := "库位列表"
index, _ := f.NewSheet(sheetName)
f.SetActiveSheet(index)
err := f.DeleteSheet("Sheet1")
if err != nil {
return nil, err
}
headers := []string{
"库位ID",
"仓库ID",
"仓库编码",
"库位编码",
"库位类型",
"容量",
"排序",
"状态",
"创建时间",
"更新时间",
}
for i, header := range headers {
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
err := f.SetCellValue(sheetName, cell, header)
if err != nil {
return nil, err
}
}
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{
Bold: true,
Size: 12,
},
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#E0E0E0"},
Pattern: 1,
},
})
err = f.SetCellStyle(sheetName, "A1", "J1", headerStyle)
if err != nil {
return nil, err
}
typeMap := map[int8]string{
1: "存储库位",
2: "拣货库位",
3: "收货库位",
4: "发货库位",
5: "退货库位",
}
statusMap := map[int8]string{
0: "禁用",
1: "启用",
}
for idx, location := range locations {
row := idx + 2
err := f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), location.ID)
if err != nil {
return nil, err
}
err = f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), location.WarehouseID)
if err != nil {
return nil, err
}
warehouseCode := ""
if code, exists := warehouseMap[location.WarehouseID]; exists {
warehouseCode = code
}
err = f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), warehouseCode)
if err != nil {
return nil, err
}
err = f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), location.Code)
if err != nil {
return nil, err
}
typeStr := "未知"
if typeVal, exists := typeMap[location.Type]; exists {
typeStr = typeVal
}
err = f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), typeStr)
if err != nil {
return nil, err
}
err = f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), location.Capacity)
if err != nil {
return nil, err
}
err = f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), location.Sort)
if err != nil {
return nil, err
}
statusStr := "未知"
if statusVal, exists := statusMap[location.Status]; exists {
statusStr = statusVal
}
err = f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), statusStr)
if err != nil {
return nil, err
}
createdAtStr := time.Unix(location.CreatedAt, 0).Format("2006-01-02 15:04:05")
err = f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), createdAtStr)
if err != nil {
return nil, err
}
updatedAtStr := time.Unix(location.UpdatedAt, 0).Format("2006-01-02 15:04:05")
err = f.SetCellValue(sheetName, fmt.Sprintf("J%d", row), updatedAtStr)
if err != nil {
return nil, err
}
}
colWidths := map[string]float64{
"A": 12,
"B": 12,
"C": 15,
"D": 20,
"E": 12,
"F": 12,
"G": 8,
"H": 10,
"I": 20,
"J": 20,
}
for col, width := range colWidths {
err := f.SetColWidth(sheetName, col, col, width)
if err != nil {
return nil, err
}
}
now := time.Now()
fileName := fmt.Sprintf("location_export_%s.xlsx", now.Format("20060102150405"))
filePath := fmt.Sprintf("excel/%s", fileName)
if err := f.SaveAs(filePath); err != nil {
return nil, fmt.Errorf("保存Excel文件失败: %v", err)
}
return &systemRes.ExportLocationResponse{
Total: total,
FileName: fileName,
FilePath: config.AppConfig.Server.Host + filePath,
}, nil
}
// ImportLocationsFromCSV 导入库位数据
func (s *LocationService) ImportLocationsFromCSV(req systemReq.ImportLocationRequest, filePath string, db ...*gorm.DB) (*systemRes.ImportLocationResult, error) {
databaseConn := database.OptionalDB(db...)
result := &systemRes.ImportLocationResult{
SuccessCodes: make([]string, 0),
FailCodes: make([]string, 0),
}
var warehouse models.Warehouse
if err := databaseConn.Where("id = ? AND is_del = ?", req.WarehouseID, 0).First(&warehouse).Error; err != nil {
return result, errors.New("仓库不存在")
}
f, err := excelize.OpenFile(filePath)
if err != nil {
return result, fmt.Errorf("打开Excel文件失败: %v", err)
}
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("关闭Excel文件失败: %v\n", err)
}
}()
sheetName := f.GetSheetName(0)
rows, err := f.GetRows(sheetName)
if err != nil {
return result, fmt.Errorf("读取表格数据失败: %v", err)
}
if len(rows) < 2 {
return result, errors.New("文件中没有数据")
}
now := time.Now().Unix()
successCount := 0
failCount := 0
for i, row := range rows {
if i == 0 {
continue
}
if len(row) < 6 {
failCount++
result.FailCodes = append(result.FailCodes, fmt.Sprintf("第%d行: 列数不足", i+1))
continue
}
code := strings.TrimSpace(row[3])
if code == "" {
failCount++
result.FailCodes = append(result.FailCodes, fmt.Sprintf("第%d行: 库位编码为空", i+1))
continue
}
typeStr := strings.TrimSpace(row[4])
capacityStr := strings.TrimSpace(row[5])
sortStr := ""
if len(row) > 6 {
sortStr = strings.TrimSpace(row[6])
}
statusStr := ""
if len(row) > 7 {
statusStr = strings.TrimSpace(row[7])
}
var locType int8 = 1
if typeStr != "" {
typeMap := map[string]int8{
"存储库位": 1,
"拣货库位": 2,
"收货库位": 3,
"发货库位": 4,
"退货库位": 5,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
}
if val, exists := typeMap[typeStr]; exists {
locType = val
} else {
failCount++
result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 库位类型无效", code))
continue
}
}
var capacity int64 = 0
if capacityStr != "" {
if capVal, err := strconv.ParseInt(capacityStr, 10, 64); err == nil {
capacity = capVal
}
}
var sort int = 0
if sortStr != "" {
if sortVal, err := strconv.Atoi(sortStr); err == nil {
sort = sortVal
}
}
var status int8 = 1
if statusStr != "" {
statusMap := map[string]int8{
"启用": 1,
"禁用": 0,
"1": 1,
"0": 0,
}
if val, exists := statusMap[statusStr]; exists {
status = val
}
}
var existingLocation models.Location
err := databaseConn.Where("warehouse_id = ? AND code = ?", req.WarehouseID, code).First(&existingLocation).Error
if err == nil {
if existingLocation.IsDel == 1 {
existingLocation.IsDel = 0
existingLocation.Type = locType
existingLocation.Capacity = capacity
existingLocation.Sort = sort
existingLocation.Status = status
existingLocation.UpdatedAt = now
if updateErr := databaseConn.Save(&existingLocation).Error; updateErr != nil {
failCount++
result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 恢复库位失败", code))
continue
}
successCount++
result.SuccessCodes = append(result.SuccessCodes, code)
} else {
failCount++
result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 已存在", code))
continue
}
} else if err == gorm.ErrRecordNotFound {
location := models.Location{
WarehouseID: req.WarehouseID,
Code: code,
Type: locType,
Capacity: capacity,
Sort: sort,
Status: status,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
}
if createErr := databaseConn.Create(&location).Error; createErr != nil {
failCount++
result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 创建失败", code))
continue
}
successCount++
result.SuccessCodes = append(result.SuccessCodes, code)
} else {
failCount++
result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 查询失败", code))
continue
}
}
result.SuccessCount = successCount
result.FailCount = failCount
if failCount > 0 {
result.Message = fmt.Sprintf("导入完成:成功%d个失败%d个", successCount, failCount)
} else {
result.Message = fmt.Sprintf("导入完成:全部成功,共%d个", successCount)
}
return result, nil
}
// =============== 私有方法 ===============
// upsertUserMapping 插入或更新用户映射(保证 about_id + code 唯一)
// db: 数据库连接
// aboutID: 用户关联ID
// id: 仓库ID
// userName: 用户名称
func (s *WarehouseService) upsertUserMapping(db *gorm.DB, aboutID, id int64, userName, code, name string) error {
now := time.Now().Unix()
var existing models.UserMapping
err := db.Where("about_id = ? AND warehouse_id = ? AND is_del = ?", aboutID, id, 0).First(&existing).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if errors.Is(err, gorm.ErrRecordNotFound) {
// 不存在则创建
mapping := models.UserMapping{
AboutID: aboutID,
Username: userName,
WarehouseId: id,
WarehouseCode: code,
WarehouseName: name,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
}
return db.Create(&mapping).Error
}
// 存在则更新
return db.Model(&existing).Updates(map[string]interface{}{
"warehouse_name": name,
"warehouse_code": code,
"updated_at": now,
}).Error
}
// deleteUserMapping 删除用户映射(软删除)
func (s *WarehouseService) deleteUserMapping(db *gorm.DB, aboutID, id int64) error {
now := time.Now().Unix()
return db.Model(&models.UserMapping{}).
Where("about_id = ? AND warehouse_id = ? AND is_del = ?", aboutID, id, 0).
Updates(map[string]interface{}{
"is_del": 1,
"updated_at": now,
}).Error
}