412 lines
11 KiB
Go
412 lines
11 KiB
Go
package controllers
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/sirupsen/logrus"
|
||
|
||
"psi/constant"
|
||
"psi/database"
|
||
"psi/models"
|
||
systemReq "psi/models/request"
|
||
systemRes "psi/models/response"
|
||
"psi/service"
|
||
"psi/utils"
|
||
)
|
||
|
||
type LocationApi struct{}
|
||
|
||
var locationService = service.LocationService{}
|
||
|
||
// LocationToCsv 导出库位到Excel
|
||
// POST /api/location/to-csv
|
||
func (r *LocationApi) LocationToCsv(c *gin.Context) {
|
||
var req systemReq.ExportLocationRequest
|
||
|
||
if err := c.ShouldBind(&req); err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelRequest, "导出库位请求参数异常", fmt.Errorf("参数错误: %v", err), c, nil)
|
||
return
|
||
}
|
||
|
||
fileBytes, fileName, total, err := locationService.ExportLocationToCSV(req, database.GetDB(c))
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "导出库位异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
if req.Type == 0 {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"code": 200,
|
||
"data": gin.H{"total": total},
|
||
})
|
||
return
|
||
}
|
||
|
||
c.Header("Content-Description", "File Transfer")
|
||
c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
|
||
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileBytes)
|
||
}
|
||
|
||
// CsvToLocation 从CSV/Excel导入库位
|
||
// POST /api/location/csv-to
|
||
func (r *LocationApi) CsvToLocation(c *gin.Context) {
|
||
var req systemReq.ImportLocationRequest
|
||
|
||
if err := c.ShouldBind(&req); err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelRequest, "导入库位请求参数异常", fmt.Errorf("参数错误: %v", err), c, nil)
|
||
return
|
||
}
|
||
|
||
file, _, err := c.Request.FormFile("file")
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelRequest, "导入库位未上传文件", fmt.Errorf("未上传文件: %v", err), c, nil)
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
fileBytes, err := io.ReadAll(file)
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelRequest, "导入库位读取文件失败", fmt.Errorf("读取文件失败: %v", err), c, nil)
|
||
return
|
||
}
|
||
|
||
// 从JWT中获取当前用户信息
|
||
employeeObj, exists := c.Get("employee")
|
||
if !exists {
|
||
utils.FailWithRequestLog(constant.LoggerChannelRequest, "导入库位未登录", fmt.Errorf("未获取到用户信息"), c, nil)
|
||
return
|
||
}
|
||
currentEmployee := employeeObj.(models.Employee)
|
||
|
||
result, err := locationService.ImportLocationFromCSV(currentEmployee.ID, req.WarehouseID, fileBytes)
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "导入库位失败", err, c, req)
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"code": 200,
|
||
"msg": "导入完成",
|
||
"data": result,
|
||
})
|
||
}
|
||
|
||
func (r *LocationApi) GetLocationList(c *gin.Context) {
|
||
var req systemReq.QueryLocationRequest
|
||
if err := c.ShouldBindQuery(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "查询库位列表请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
list, total, err := locationService.GetLocationList(req, database.GetDB(c))
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询库位列表异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"code": 0,
|
||
"data": gin.H{
|
||
"list": list,
|
||
"total": total,
|
||
"page": req.Page,
|
||
"page_size": req.PageSize,
|
||
},
|
||
"msg": "查询成功",
|
||
})
|
||
}
|
||
|
||
// GetAllLocationList 查询所有库位列表(仓库ID可选)
|
||
func (r *LocationApi) GetAllLocationList(c *gin.Context) {
|
||
var req systemReq.QueryAllLocationRequest
|
||
if err := c.ShouldBindQuery(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "查询所有库位列表请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
list, total, err := locationService.GetAllLocationList(req, database.GetDB(c))
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询所有库位列表异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"code": 0,
|
||
"data": gin.H{
|
||
"list": list,
|
||
"total": total,
|
||
"page": req.Page,
|
||
"page_size": req.PageSize,
|
||
},
|
||
"msg": "查询成功",
|
||
})
|
||
}
|
||
|
||
// GetLocationDetail 获取库位详情
|
||
func (r *LocationApi) GetLocationDetail(c *gin.Context) {
|
||
idStr := c.Param("id")
|
||
|
||
if idStr == "" {
|
||
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
|
||
"source": "获取库位详情请求参数异常",
|
||
"err_msg": "ID参数不能为空",
|
||
})
|
||
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
|
||
return
|
||
}
|
||
|
||
var id int64
|
||
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
|
||
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
|
||
"source": "获取库位详情请求参数异常",
|
||
"err_msg": "ID格式错误: " + idStr,
|
||
})
|
||
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
|
||
return
|
||
}
|
||
|
||
location, err := locationService.GetLocationDetail(id, database.GetDB(c))
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询库位详情异常", err, c, gin.H{"id": id})
|
||
return
|
||
}
|
||
|
||
systemRes.OkWithDetailed(location, "查询成功", c)
|
||
}
|
||
|
||
// GetLocationInfo 获取库位ID
|
||
func (r *LocationApi) GetLocationInfo(c *gin.Context) {
|
||
var req systemReq.GetLocationIdRequest
|
||
if err := c.ShouldBindQuery(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "获取库位ID请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
location, err := locationService.GetLocationInfo(req.Code, req.WarehouseCode, database.GetDB(c))
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取库位ID异常", err, c, gin.H{"code": req.Code})
|
||
return
|
||
}
|
||
|
||
systemRes.OkWithDetailed(location, "查询成功", c)
|
||
}
|
||
|
||
func (r *LocationApi) CreateLocation(c *gin.Context) {
|
||
var req systemReq.CreateLocationRequest
|
||
if err := c.ShouldBind(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "创建库位请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
id, err := locationService.CreateLocation(req, database.GetDB(c))
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建库位异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
|
||
}
|
||
|
||
func (r *LocationApi) BatchGenerateLocations(c *gin.Context) {
|
||
var req systemReq.BatchGenerateLocationRequest
|
||
if err := c.ShouldBind(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "批量生成库位请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
if len(req.Groups) == 0 {
|
||
groups, err := parseGroupsFromForm(c)
|
||
if err != nil {
|
||
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
|
||
return
|
||
}
|
||
req.Groups = groups
|
||
}
|
||
result, err := locationService.BatchGenerateLocations(req, database.GetDB(c))
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "批量生成库位异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
systemRes.OkWithDetailed(result, result.Message, c)
|
||
}
|
||
|
||
func (r *LocationApi) UpdateLocation(c *gin.Context) {
|
||
var req systemReq.UpdateLocationRequest
|
||
if err := c.ShouldBind(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "更新库位请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
if len(req.IDs) == 0 {
|
||
ids, err := parseIdsFromForm(c)
|
||
if err != nil {
|
||
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
|
||
return
|
||
}
|
||
req.IDs = ids
|
||
}
|
||
if err := locationService.UpdateLocation(req, database.GetDB(c)); err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新库位异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
systemRes.OkWithMessage("更新成功", c)
|
||
}
|
||
|
||
func (r *LocationApi) DeleteLocation(c *gin.Context) {
|
||
var req systemReq.DeleteLocationRequest
|
||
if err := c.ShouldBind(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "删除库位请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
if len(req.IDs) == 0 {
|
||
ids, err := parseIdsFromForm(c)
|
||
if err != nil {
|
||
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
|
||
return
|
||
}
|
||
req.IDs = ids
|
||
}
|
||
if err := locationService.DeleteLocation(req.IDs, database.GetDB(c)); err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除库位异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
systemRes.OkWithMessage("删除成功", c)
|
||
}
|
||
|
||
func (r *LocationApi) SyncLocations(c *gin.Context) {
|
||
var req systemReq.SyncLocationRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "同步库位数据请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
result, err := locationService.SyncLocations(req)
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "同步库位数据异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"code": 200,
|
||
"data": result,
|
||
"msg": "同步完成",
|
||
})
|
||
}
|
||
|
||
// SyncGoods 同步商品数据
|
||
func (r *LocationApi) SyncGoods(c *gin.Context) {
|
||
var req systemReq.SyncGoodsRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
ValidAndFail(constant.LoggerChannelRequest, "同步商品数据请求参数异常", "参数错误: "+err.Error(), c, err)
|
||
return
|
||
}
|
||
|
||
result, err := locationService.SyncGoods(req)
|
||
if err != nil {
|
||
utils.FailWithRequestLog(constant.LoggerChannelWork, "同步商品数据异常", err, c, req)
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"code": 200,
|
||
"msg": "同步完成",
|
||
"data": gin.H{
|
||
"success_count": result.SuccessCount,
|
||
"fail_count": result.FailCount,
|
||
"message": result.Message,
|
||
},
|
||
})
|
||
}
|
||
|
||
func parseIdsFromForm(c *gin.Context) ([]int64, error) {
|
||
var ids []int64
|
||
|
||
for i := 0; i < 100; i++ {
|
||
idStr := c.PostForm(fmt.Sprintf("ids[%d]", i))
|
||
if idStr == "" {
|
||
break
|
||
}
|
||
|
||
var id int64
|
||
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
|
||
return nil, fmt.Errorf("第%d个ID格式错误", i)
|
||
}
|
||
if id <= 0 {
|
||
return nil, fmt.Errorf("第%d个ID必须大于0", i)
|
||
}
|
||
ids = append(ids, id)
|
||
}
|
||
|
||
if len(ids) == 0 {
|
||
return nil, fmt.Errorf("至少需要一个ID")
|
||
}
|
||
|
||
return ids, nil
|
||
}
|
||
|
||
func parseGroupsFromForm(c *gin.Context) ([]systemReq.GroupConfig, error) {
|
||
var groups []systemReq.GroupConfig
|
||
|
||
for i := 0; i < 5; i++ {
|
||
formatTypeStr := c.PostForm(fmt.Sprintf("groups[%d][format_type]", i))
|
||
if formatTypeStr == "" {
|
||
break
|
||
}
|
||
|
||
startValue := c.PostForm(fmt.Sprintf("groups[%d][start_value]", i))
|
||
endValue := c.PostForm(fmt.Sprintf("groups[%d][end_value]", i))
|
||
separator := c.PostForm(fmt.Sprintf("groups[%d][separator]", i))
|
||
paddingLenStr := c.PostForm(fmt.Sprintf("groups[%d][padding_len]", i))
|
||
|
||
if startValue == "" || endValue == "" {
|
||
return nil, fmt.Errorf("第%d组配置缺少必填字段", i)
|
||
}
|
||
|
||
var formatType int
|
||
if _, err := fmt.Sscanf(formatTypeStr, "%d", &formatType); err != nil {
|
||
return nil, fmt.Errorf("第%d组format_type格式错误", i)
|
||
}
|
||
|
||
if formatType < 1 || formatType > 4 {
|
||
return nil, fmt.Errorf("第%d组format_type必须在1-4之间", i)
|
||
}
|
||
|
||
paddingLen := 0
|
||
if paddingLenStr != "" {
|
||
if _, err := fmt.Sscanf(paddingLenStr, "%d", &paddingLen); err != nil {
|
||
return nil, fmt.Errorf("第%d组padding_len格式错误", i)
|
||
}
|
||
}
|
||
|
||
if separator == "" {
|
||
separator = ""
|
||
}
|
||
|
||
group := systemReq.GroupConfig{
|
||
FormatType: formatType,
|
||
StartValue: startValue,
|
||
EndValue: endValue,
|
||
PaddingLen: paddingLen,
|
||
Separator: separator,
|
||
}
|
||
|
||
groups = append(groups, group)
|
||
}
|
||
|
||
if len(groups) == 0 {
|
||
return nil, fmt.Errorf("至少需要一个分组配置")
|
||
}
|
||
|
||
if len(groups) > 5 {
|
||
return nil, fmt.Errorf("最多支持5组")
|
||
}
|
||
|
||
return groups, nil
|
||
}
|