daShangDao_psiServer/service/location_import.go

142 lines
3.5 KiB
Go
Raw 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 (
"fmt"
"strings"
"time"
"github.com/xuri/excelize/v2"
"gorm.io/gorm"
"psi/database"
"psi/models"
"psi/models/request"
)
// LocationImportService 库位导入服务
type LocationImportService struct{}
// LOCATION_IMPORT_COLUMNS 库位导入表格列仅1列
var LOCATION_IMPORT_COLUMNS = []string{"库位编码"}
// LocationImportResult 导入结果
type LocationImportResult struct {
SuccessCount int
FailCount int
FailList []string
Message string
}
// AddFail 添加失败记录
func (r *LocationImportResult) AddFail(reason string) {
r.FailCount++
r.FailList = append(r.FailList, reason)
}
// ParseLocationImportExcel 解析Excel文件为库位行数据
func (s *LocationImportService) ParseLocationImportExcel(fileBytes []byte) ([]request.LocationImportRow, error) {
f, err := excelize.OpenReader(strings.NewReader(string(fileBytes)))
if err != nil {
return nil, fmt.Errorf("无法打开Excel文件: %v", err)
}
defer f.Close()
rows, err := f.GetRows(f.GetSheetName(0))
if err != nil {
return nil, fmt.Errorf("读取Sheet失败: %v", err)
}
if len(rows) < 2 {
return nil, fmt.Errorf("Excel至少需要表头+1行数据")
}
// 校验表头
header := rows[0]
if len(header) < len(LOCATION_IMPORT_COLUMNS) {
return nil, fmt.Errorf("表头列数不足,期望%d列(库位编码),实际%d列", len(LOCATION_IMPORT_COLUMNS), len(header))
}
var result []request.LocationImportRow
for i := 1; i < len(rows); i++ {
row := rows[i]
code := strings.TrimSpace(getCell(row, 0))
if code == "" {
continue
}
result = append(result, request.LocationImportRow{Code: code})
}
return result, nil
}
// getCell 安全获取单元格值
func getCell(row []string, idx int) string {
if idx < len(row) {
return row[idx]
}
return ""
}
// ImportLocationsFromExcel 从Excel导入库位覆盖模式事务内先删后插保证原子性
func (s *LocationImportService) ImportLocationsFromExcel(userID, warehouseID int64, rows []request.LocationImportRow) (*LocationImportResult, error) {
databaseConn, err := database.GetTenantDB(userID)
if err != nil {
return nil, fmt.Errorf("获取数据库连接失败: %v", err)
}
result := &LocationImportResult{}
err = databaseConn.Transaction(func(tx *gorm.DB) error {
now := time.Now().Unix()
// 覆盖模式:物理删除当前仓库的所有库位,释放唯一索引 uk_warehouse_code
if err := tx.Where("warehouse_id = ?", warehouseID).Delete(&models.Location{}).Error; err != nil {
return fmt.Errorf("清空旧库位失败: %v", err)
}
rowIndex := 0
for _, row := range rows {
code := strings.TrimSpace(row.Code)
if code == "" {
result.AddFail("库位编码为空")
continue
}
rowIndex++
loc := models.Location{
WarehouseID: warehouseID,
Code: code,
Type: 1, // 存储库位
Capacity: 255,
Sort: rowIndex,
Status: 1, // 启用
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
}
if err := tx.Create(&loc).Error; err != nil {
result.AddFail(fmt.Sprintf("%s: 创建失败: %v", code, err))
continue
}
result.SuccessCount++
}
return nil
})
if err != nil {
return nil, fmt.Errorf("导入库位事务失败: %v", err)
}
if result.FailCount > 0 {
result.Message = fmt.Sprintf("成功 %d 条,失败 %d 条。失败明细: %s",
result.SuccessCount, result.FailCount, strings.Join(result.FailList, "; "))
} else {
result.Message = fmt.Sprintf("全部成功,共 %d 条", result.SuccessCount)
}
return result, nil
}