diff --git a/app/admin/apis/inventorymanage/allot.go b/app/admin/apis/inventorymanage/allot.go index 85b2a59..7ce8566 100644 --- a/app/admin/apis/inventorymanage/allot.go +++ b/app/admin/apis/inventorymanage/allot.go @@ -2,8 +2,12 @@ package inventorymanage import ( "errors" + "fmt" "github.com/gin-gonic/gin" "go-admin/app/admin/models" + orm "go-admin/common/global" + "go-admin/logger" + "go-admin/tools" "go-admin/tools/app" "net/http" ) @@ -19,14 +23,33 @@ import ( // @Success 200 {object} models.ErpInventoryAllotOrder // @Router /api/v1/inventory/allot/add [post] func InventoryAllotAdd(c *gin.Context) { - req := &models.ErpStockListReq{} + req := &models.InventoryAllotAddReq{} if err := c.ShouldBindJSON(&req); err != nil { - //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + err := tools.Validate(req) //必填参数校验 + if err != nil { + app.Error(c, http.StatusBadRequest, err, err.Error()) + return + } + + sysUser, err := models.GetSysUserByCtx(c) + if err != nil { + logger.Error("sys user err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "操作失败") + return + } + + inventoryProductOrder, err := models.AddInventoryAllot(req, sysUser) + if err != nil { + logger.Error("InventoryAllotAdd err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "新增失败:"+err.Error()) + return + } + + app.OK(c, inventoryProductOrder, "新增成功") return } @@ -39,14 +62,26 @@ func InventoryAllotAdd(c *gin.Context) { // @Success 200 {object} models.ErpInventoryAllotOrder // @Router /api/v1/inventory/allot/edit [post] func InventoryAllotEdit(c *gin.Context) { - req := &models.ErpStockListReq{} + req := &models.InventoryAllotEditReq{} if err := c.ShouldBindJSON(&req); err != nil { - //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + err := tools.Validate(req) //必填参数校验 + if err != nil { + app.Error(c, http.StatusBadRequest, err, err.Error()) + return + } + + inventoryProductOrder, err := models.EditAllotInventory(req) + if err != nil { + logger.Error("InventoryAllotEdit err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "编辑失败:"+err.Error()) + return + } + + app.OK(c, inventoryProductOrder, "编辑成功") return } @@ -59,14 +94,27 @@ func InventoryAllotEdit(c *gin.Context) { // @Success 200 {object} app.Response // @Router /api/v1/inventory/allot/audit [post] func InventoryAllotAudit(c *gin.Context) { - req := &models.ErpStockListReq{} + req := &models.InventoryAllotAuditReq{} if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + sysUser, err := models.GetSysUserByCtx(c) + if err != nil { + logger.Error("sys user err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "操作失败") + return + } + + err = models.AuditAllotInventory(req, sysUser) + if err != nil { + app.Error(c, http.StatusInternalServerError, err, "审核失败:"+err.Error()) + return + } + + app.OK(c, nil, "操作成功") return } @@ -82,11 +130,77 @@ func InventoryAllotDelete(c *gin.Context) { req := &models.InventoryAllotDeleteReq{} if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + err := tools.Validate(req) //必填参数校验 + if err != nil { + app.Error(c, http.StatusBadRequest, err, err.Error()) + return + } + + // todo 需要校验当前用户是否有权限 + + var inventoryAllotOrder models.ErpInventoryAllotOrder + err = orm.Eloquent.Table("erp_inventory_allot_order").Where("serial_number = ?", req.SerialNumber). + Find(&inventoryAllotOrder).Error + if err != nil { + logger.Error("order err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "删除失败:"+err.Error()) + return + } + + if inventoryAllotOrder.SerialNumber == "" { + logger.Error("order is null") + app.Error(c, http.StatusInternalServerError, err, "删除失败:订单不存在") + return + } + + // 仅待审核订单可删除 + if inventoryAllotOrder.State != models.ErpInventoryAllotOrderUnAudit { + logger.Error("order err, inventoryAllotOrder.State is:", logger.Field("inventoryAllotOrder.State", + inventoryAllotOrder.State)) + app.Error(c, http.StatusInternalServerError, err, "删除失败:仅待审核订单可删除") + return + } + + begin := orm.Eloquent.Begin() + // 1-删除采购订单表 + err = begin.Delete(inventoryAllotOrder).Error + if err != nil { + logger.Error("order delete1 err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "删除失败:"+err.Error()) + return + } + + // 2-删除采购订单商品表 + var commodities []models.ErpInventoryAllotCommodity + err = orm.Eloquent.Table("erp_inventory_allot_commodity").Where("allot_order_id = ?", + inventoryAllotOrder.ID).Find(&commodities).Error + if err != nil { + logger.Error("query erp_inventory_allot_commodity err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "删除失败:"+err.Error()) + return + } + + if len(commodities) != 0 { + err = begin.Delete(&commodities).Error + if err != nil { + logger.Error("更新商品订单信息-删除 error") + app.Error(c, http.StatusInternalServerError, err, "删除失败:"+err.Error()) + return + } + } + + err = begin.Commit().Error + if err != nil { + begin.Rollback() + logger.Error("commit err:", logger.Field("err", err)) + return + } + + app.OK(c, nil, "删除成功") return } @@ -102,11 +216,17 @@ func InventoryAllotList(c *gin.Context) { req := &models.InventoryAllotListReq{} if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + resp, err := req.List() + if err != nil { + app.Error(c, http.StatusInternalServerError, err, "获取失败:"+err.Error()) + return + } + + app.OK(c, resp, "查询成功") return } @@ -121,12 +241,42 @@ func InventoryAllotList(c *gin.Context) { func InventoryAllotDetail(c *gin.Context) { req := &models.InventoryAllotDetailReq{} if err := c.ShouldBindJSON(&req); err != nil { - //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + var allotOrder models.ErpInventoryAllotOrder + err := orm.Eloquent.Table("erp_inventory_allot_order").Where("serial_number=?", req.SerialNumber).Find(&allotOrder).Error + if err != nil { + logger.Error("allot order err:", logger.Field("err", err)) + app.Error(c, http.StatusBadRequest, err, "获取失败") + return + } + if allotOrder.ID == 0 { + logger.Error("allot commodities err:", logger.Field("err", err)) + app.Error(c, http.StatusBadRequest, err, fmt.Sprintf("未查询到订单[%s]", req.SerialNumber)) + return + } + + // 校验时间,如果为01-01-01 08:05,则赋值为空 + if allotOrder.MakerTime != nil && allotOrder.MakerTime.IsZero() { + allotOrder.MakerTime = nil + } + if allotOrder.AuditTime != nil && allotOrder.AuditTime.IsZero() { + allotOrder.AuditTime = nil + } + + var allotCommodities []models.ErpInventoryAllotCommodity + err = orm.Eloquent.Table("erp_inventory_allot_commodity").Where("allot_order_id=?", allotOrder.ID). + Find(&allotCommodities).Error + if err != nil { + logger.Error("allot commodities err:", logger.Field("err", err)) + app.Error(c, http.StatusBadRequest, err, "获取失败") + return + } + + allotOrder.Commodities = allotCommodities + app.OK(c, allotOrder, "查询成功") return } @@ -142,11 +292,17 @@ func InventoryAllotDeliver(c *gin.Context) { req := &models.InventoryAllotDeliverReq{} if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + err := models.DeliverAllotInventory(req) + if err != nil { + app.Error(c, http.StatusInternalServerError, err, "操作失败:"+err.Error()) + return + } + + app.OK(c, nil, "操作成功") return } @@ -162,10 +318,16 @@ func InventoryAllotReceive(c *gin.Context) { req := &models.InventoryAllotReceiveReq{} if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) - app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") + app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } - app.OK(c, "", "OK") + err := models.ReceiveAllotInventory(req) + if err != nil { + app.Error(c, http.StatusInternalServerError, err, "操作失败:"+err.Error()) + return + } + + app.OK(c, nil, "操作成功") return } diff --git a/app/admin/models/commodity.go b/app/admin/models/commodity.go index 82ee0a4..5bd53b1 100644 --- a/app/admin/models/commodity.go +++ b/app/admin/models/commodity.go @@ -25,7 +25,7 @@ const ( InStock = 1 // 在库 SoldOut = 2 // 已售 PurchaseReturn = 3 // 采购退货 - InAllot = 4 // 调拨中 + InAllot = 4 // 调拨中(调入门店) OnSale = 5 // 销售锁定中 ProductReturn = 6 // 产品出库 ) diff --git a/app/admin/models/inventory_allot.go b/app/admin/models/inventory_allot.go index 7581b80..1827f15 100644 --- a/app/admin/models/inventory_allot.go +++ b/app/admin/models/inventory_allot.go @@ -1,6 +1,20 @@ package models -import "time" +import ( + "errors" + "fmt" + orm "go-admin/common/global" + "go-admin/logger" + "gorm.io/gorm" + "time" +) + +const ( + ErpInventoryAllotOrderUnAudit = 1 // 库存调拨-待审核 + ErpInventoryAllotOrderWaitSend = 2 // 库存调拨-待发货 + ErpInventoryAllotOrderWaitReceive = 3 // 库存调拨-待收货 + ErpInventoryAllotOrderFinished = 4 // 库存调拨-已完成 +) // ErpInventoryAllotOrder 库存调拨订单表 type ErpInventoryAllotOrder struct { @@ -104,3 +118,742 @@ type InventoryAllotDeliverReq struct { type InventoryAllotReceiveReq struct { SerialNumber string `json:"serial_number" binding:"required"` // 单据编号 } + +// 检查新增库存调拨信息 +func checkAllotInventoryParam(req *InventoryAllotAddReq, editFlag bool) error { + if len(req.Commodities) == 0 { + return errors.New("商品信息为空") + } + + noIMEICommodityMap := make(map[uint32]bool, 0) + for _, item := range req.Commodities { + // 校验数量 + if item.Count <= 0 { + return fmt.Errorf("商品[%s]数量[%d]错误,需大于0", item.CommodityName, item.Count) + } else { + if item.Count != 1 && item.IMEIType != NoIMEICommodity { // 串码商品数量不为1,则报错 + return fmt.Errorf("串码商品[%s]数量[%d]错误,需为1", item.CommodityName, item.Count) + } + } + + // 校验编辑订单时是否有传商品ID + if editFlag { + if item.ID == 0 { + return fmt.Errorf("商品[%s]ID为空", item.CommodityName) + } + } + + // 校验串码类型 + switch item.IMEIType { + case 1, 2, 3: + // 串码商品校验串码是否为空 + if item.IMEIType != NoIMEICommodity && item.IMEI == "" { + return fmt.Errorf("[%s]是串码商品,其串码不能为空", item.CommodityName) + } + default: + return fmt.Errorf("商品[%s]串码类型[%d]错误", item.CommodityName, item.IMEIType) + } + + // 校验非串码商品是否重复 + if item.IMEIType == NoIMEICommodity { + _, ok := noIMEICommodityMap[item.CommodityId] + if !ok { + noIMEICommodityMap[item.CommodityId] = true + } else { + return fmt.Errorf("商品[%s]重复,相同的非串码商品只能有1条数据", item.CommodityName) + } + } + + // 检查调出门店是否有对应商品,调拨数量是否 < 商品库存数量 + var stockCount int64 + var err error + if item.IMEIType == NoIMEICommodity { // 非串码商品 + err = orm.Eloquent.Table("erp_stock_commodity"). + Where("erp_commodity_id = ? AND store_id = ? AND state = 1", item.CommodityId, req.DeliverStoreId). + Count(&stockCount).Error + } else { // 串码商品 + err = orm.Eloquent.Table("erp_stock_commodity"). + Where("erp_commodity_id = ? AND store_id = ? AND state = 1 AND imei = ?", item.CommodityId, + req.DeliverStoreId, item.IMEI).Count(&stockCount).Error + } + if err != nil { + return errors.New("查询商品库存失败," + err.Error()) + } + if stockCount == 0 { + return fmt.Errorf("商品[%s]库存数量为0", item.CommodityName) + } + if stockCount < int64(item.Count) { + return fmt.Errorf("商品[%s]库存数量[%d]少于调拨数量[%d]", item.CommodityName, stockCount, item.Count) + } + } + + return nil +} + +// AddInventoryAllot 新增库存调拨 +func AddInventoryAllot(req *InventoryAllotAddReq, sysUser *SysUser) (*ErpInventoryAllotOrder, error) { + // 检查库存调拨信息 + if err := checkAllotInventoryParam(req, false); err != nil { + return nil, err + } + + var err error + nowTime := time.Now() + + // 计算库存调拨的总数量 + var nTotalCount uint32 + for i, _ := range req.Commodities { + nTotalCount += req.Commodities[i].Count // 数量 + } + + // 组合库存调拨订单数据 + inventoryAllotOrder := &ErpInventoryAllotOrder{ + SerialNumber: "db" + NewProductInventorySn(), + DeliverStoreId: req.DeliverStoreId, + DeliverStoreName: req.DeliverStoreName, + ReceiveStoreId: req.ReceiveStoreId, + ReceiveStoreName: req.ReceiveStoreName, + HandlerId: req.HandlerId, + HandlerName: req.HandlerName, + MakerTime: &nowTime, + MakerId: uint32(sysUser.UserId), + MakerName: sysUser.NickName, + State: ErpInventoryAllotOrderUnAudit, + TotalCount: nTotalCount, + } + + // 创建库存调拨订单 + begin := orm.Eloquent.Begin() + err = begin.Create(inventoryAllotOrder).Error + if err != nil { + begin.Rollback() + logger.Error("create allot order err:", logger.Field("err", err)) + return nil, err + } + + // 创建库存调拨商品信息,添加库存调拨订单id + for i, _ := range req.Commodities { + req.Commodities[i].AllotOrderId = inventoryAllotOrder.ID + err = begin.Create(&req.Commodities[i]).Error + if err != nil { + begin.Rollback() + logger.Error("create allot commodity err:", logger.Field("err", err)) + return nil, err + } + } + + err = begin.Commit().Error + if err != nil { + begin.Rollback() + logger.Error("commit allot commodity err:", logger.Field("err", err)) + return nil, err + } + + return inventoryAllotOrder, nil +} + +// EditAllotInventory 编辑库存调拨 +func EditAllotInventory(req *InventoryAllotEditReq) (*ErpInventoryAllotOrder, error) { + // 查询订单信息 + var inventoryAllotOrder ErpInventoryAllotOrder + err := orm.Eloquent.Table("erp_inventory_allot_order").Where("serial_number = ?", req.SerialNumber). + Find(&inventoryAllotOrder).Error + if err != nil { + logger.Error("allot order err:", logger.Field("err", err)) + return nil, err + } + + // 未找到订单 + if inventoryAllotOrder.ID == 0 { + return nil, fmt.Errorf("未找到该订单,请检查单据编号[%s]", req.SerialNumber) + } + + if inventoryAllotOrder.State != ErpInventoryAllotOrderUnAudit { // 只有待审核的订单才能编辑 + return nil, errors.New("订单不是待审核状态") + } + + // 检查库存调拨信息 + if err = checkAllotInventoryParam(&req.InventoryAllotAddReq, true); err != nil { + return nil, err + } + + // 计算库存调拨的总数量 + var nTotalCount uint32 + for i, _ := range req.Commodities { + nTotalCount += req.Commodities[i].Count // 数量 + } + + begin := orm.Eloquent.Begin() + // 1-更新库存调拨信息 + inventoryAllotOrder.DeliverStoreId = req.DeliverStoreId + inventoryAllotOrder.DeliverStoreName = req.DeliverStoreName + inventoryAllotOrder.ReceiveStoreId = req.ReceiveStoreId + inventoryAllotOrder.ReceiveStoreName = req.ReceiveStoreName + inventoryAllotOrder.HandlerId = req.HandlerId + inventoryAllotOrder.HandlerName = req.HandlerName + inventoryAllotOrder.TotalCount = nTotalCount + + err = begin.Model(&ErpInventoryAllotOrder{}).Where("id = ?", inventoryAllotOrder.ID). + Updates(inventoryAllotOrder).Error + if err != nil { + begin.Rollback() + logger.Error("update allot order err:", logger.Field("err", err)) + return nil, err + } + + // 2-更新库存调拨商品表 + err = updateAllotCommodityData(begin, inventoryAllotOrder.ID, req) + if err != nil { + begin.Rollback() + logger.Error("update erp_inventory_allot_commodity err:", logger.Field("err", err)) + return nil, err + } + + err = begin.Commit().Error + if err != nil { + begin.Rollback() + logger.Error("commit err:", logger.Field("err", err)) + return nil, err + } + + return &inventoryAllotOrder, nil +} + +// updateAllotCommodityData 更新库存调拨商品信息 +func updateAllotCommodityData(gdb *gorm.DB, orderId uint32, req *InventoryAllotEditReq) error { + // 查询现有的零售订单信息 + var commodities []ErpInventoryAllotCommodity + err := orm.Eloquent.Table("erp_inventory_allot_commodity").Where("allot_order_id = ?", orderId).Find(&commodities).Error + if err != nil { + logger.Error("query erp_inventory_allot_commodity err:", logger.Field("err", err)) + return err + } + + var newCommodities []ErpInventoryAllotCommodity + var deletedCommodities []ErpInventoryAllotCommodity + var matchingCommodities []ErpInventoryAllotCommodity + // 找到新增的商品 + for i, reqCommodity := range req.Commodities { + // 订单商品表信息添加零售订单id + req.Commodities[i].AllotOrderId = orderId + + var found bool + for _, dbCommodity := range commodities { + if reqCommodity.CommodityId == dbCommodity.CommodityId && reqCommodity.IMEI == dbCommodity.IMEI { + found = true + break + } + } + if !found { + newCommodities = append(newCommodities, reqCommodity) + } + } + + // 找到删除的商品 + for _, dbCommodity := range commodities { + var found bool + for _, reqCommodity := range req.Commodities { + if reqCommodity.CommodityId == dbCommodity.CommodityId && reqCommodity.IMEI == dbCommodity.IMEI { + found = true + // 找到匹配的商品,加入匹配列表 + matchingCommodities = append(matchingCommodities, reqCommodity) + break + } + } + if !found { + deletedCommodities = append(deletedCommodities, dbCommodity) + } + } + + // 2-更新商品订单信息-更新 + for _, commodity := range matchingCommodities { + if err := gdb.Model(&ErpInventoryAllotCommodity{}).Where("id = ?", commodity.ID).Updates(commodity).Error; err != nil { + logger.Error("更新商品订单信息-更新 error") + return errors.New("操作失败:" + err.Error()) + } + } + + // 2-更新商品订单信息-新增 + if len(newCommodities) != 0 { + err = gdb.Create(&newCommodities).Error + if err != nil { + logger.Error("更新商品订单信息-新增 error") + return errors.New("操作失败:" + err.Error()) + } + } + + //2-更新商品订单信息-删除 + if len(deletedCommodities) != 0 { + err = gdb.Delete(&deletedCommodities).Error + if err != nil { + logger.Error("更新商品订单信息-删除 error") + return errors.New("操作失败:" + err.Error()) + } + } + + return nil +} + +// List 查询采购订单列表 +func (m *InventoryAllotListReq) List() (*InventoryAllotListResp, error) { + resp := &InventoryAllotListResp{ + PageIndex: m.PageIndex, + PageSize: m.PageSize, + } + page := m.PageIndex - 1 + if page < 0 { + page = 0 + } + if m.PageSize == 0 { + m.PageSize = 10 + } + qs := orm.Eloquent.Table("erp_inventory_allot_order") + if m.SerialNumber != "" { + qs = qs.Where("serial_number=?", m.SerialNumber) + } else { + if m.DeliverStoreId != 0 { + qs = qs.Where("deliver_store_id=?", m.DeliverStoreId) + } + if m.ReceiveStoreId != 0 { + qs = qs.Where("receive_store_id=?", m.ReceiveStoreId) + } + if m.HandlerId != 0 { + qs = qs.Where("handler_id=?", m.HandlerId) + } + if m.State != 0 { + qs = qs.Where("state=?", m.State) + } + if m.AuditTimeStart != "" { + parse, err := time.Parse(QueryTimeFormat, m.AuditTimeStart) + if err != nil { + logger.Errorf("AllotInventoryList err:", err) + return nil, err + } + qs = qs.Where("audit_time > ?", parse) + } + if m.AuditTimeEnd != "" { + parse, err := time.Parse(QueryTimeFormat, m.AuditTimeEnd) + if err != nil { + logger.Errorf("AllotInventoryList err:", err) + return nil, err + } + qs = qs.Where("audit_time < ?", parse) + } + } + + var count int64 + err := qs.Count(&count).Error + if err != nil { + logger.Error("count err:", logger.Field("err", err)) + return resp, err + } + resp.Total = int(count) + var orders []ErpInventoryAllotOrder + err = qs.Order("id DESC").Offset(page * m.PageSize).Limit(m.PageSize).Find(&orders).Error + if err != nil && err != RecordNotFound { + logger.Error("allot list err:", logger.Field("err", err)) + return resp, err + } + + // 校验时间,如果为01-01-01 08:05,则赋值为空 + for i, v := range orders { + if v.MakerTime != nil && v.MakerTime.IsZero() { + orders[i].MakerTime = nil + } + + if v.AuditTime != nil && v.AuditTime.IsZero() { + orders[i].AuditTime = nil + } + } + + resp.List = orders + return resp, nil +} + +// AuditAllotInventory 审核产品入库 state:1-审核,2-取消审核 +func AuditAllotInventory(req *InventoryAllotAuditReq, sysUser *SysUser) error { + // 查询订单信息 + var inventoryAllotOrder ErpInventoryAllotOrder + err := orm.Eloquent.Table("erp_inventory_allot_order").Where("serial_number = ?", req.SerialNumber). + Find(&inventoryAllotOrder).Error + if err != nil { + logger.Error("order err:", logger.Field("err", err)) + return err + } + + if inventoryAllotOrder.ID == 0 { + return errors.New("未查询到订单信息") + } + + begin := orm.Eloquent.Begin() + + // 判断入参state:1-审核,2-取消审核 + orderState := 0 + switch req.State { + case 1: // 1-审核:待审核状态可以审核 + if inventoryAllotOrder.State != ErpInventoryAllotOrderUnAudit { // 订单不是未审核状态 + return errors.New("订单已审核,无需再次审核") + } + orderState = ErpInventoryAllotOrderWaitSend + // 更新商品的库存状态 + err = allotAuditAndUpdateStock(begin, inventoryAllotOrder) + if err != nil { + begin.Rollback() + logger.Error("ProductAuditAndUpdateStock err:", logger.Field("err", err)) + return fmt.Errorf("审核失败,%s", err.Error()) + } + case 2: // 2-取消审核 + orderState = ErpInventoryAllotOrderUnAudit + if inventoryAllotOrder.State == ErpInventoryAllotOrderUnAudit { // 订单未审核 + return errors.New("订单是未审核状态,无需取消审核") + } + // 更新商品的库存状态 + err = cancelAllotAuditAndUpdateStock(begin, inventoryAllotOrder) + if err != nil { + begin.Rollback() + return fmt.Errorf("取消审核失败,%s", err.Error()) + } + default: + logger.Error("order err, req.State is:", logger.Field("req.State", req.State)) + return errors.New("参数有误") + } + + // 更新库存调拨订单表 + err = begin.Table("erp_inventory_allot_order").Where("id = ?", inventoryAllotOrder.ID). + Updates(map[string]interface{}{ + "state": orderState, + "auditor_id": sysUser.UserId, + "audit_time": time.Now(), + "auditor_name": sysUser.NickName, + }).Error + if err != nil { + begin.Rollback() + logger.Error("update erp_inventory_allot_order err:", logger.Field("err", err)) + return err + } + + err = begin.Commit().Error + if err != nil { + begin.Rollback() + logger.Errorf("commit err:", err) + return err + } + + return nil +} + +// allotAuditAndUpdateStock 库存调拨审核后更新库存信息 +func allotAuditAndUpdateStock(gdb *gorm.DB, allotOrder ErpInventoryAllotOrder) error { + // 查询库存调拨商品信息 + var commodities []ErpInventoryAllotCommodity + err := orm.Eloquent.Table("erp_inventory_allot_commodity").Where("allot_order_id = ?", + allotOrder.ID).Find(&commodities).Error + if err != nil { + logger.Error("query erp_inventory_allot_commodity err:", logger.Field("err", err)) + return err + } + + // 遍历库存调拨商品信息 + for _, v := range commodities { + var stockCommodity []ErpStockCommodity + err = orm.Eloquent.Table("erp_stock_commodity").Where("erp_commodity_id = ? AND store_id = ? "+ + "AND state = ? AND imei = ?", v.CommodityId, allotOrder.DeliverStoreId, InStock, v.IMEI). + Find(&stockCommodity).Error + if err != nil { + return fmt.Errorf("审核失败,查询商品库存失败:[%s]", err.Error()) + } + + if len(stockCommodity) == 0 { + return fmt.Errorf("审核失败,未找到商品库存信息") + } + + if v.Count > uint32(len(stockCommodity)) { + return fmt.Errorf("审核失败,商品[%s]库存数量[%d]少于调拨数量[%d]", v.CommodityName, + len(stockCommodity), v.Count) + } + + // 更新库存商品表商品状态为:调拨中 + for i := 0; i < int(v.Count); i++ { + stockCommodity[i].StoreId = allotOrder.ReceiveStoreId + stockCommodity[i].StoreName = allotOrder.ReceiveStoreName + stockCommodity[i].State = InAllot + if err := gdb.Model(&ErpStockCommodity{}).Where("id = ?", stockCommodity[i].ID). + Updates(stockCommodity[i]).Error; err != nil { + return fmt.Errorf("审核失败,更新商品库存失败:%s", err.Error()) + } + } + + // 更新库存商品数量 + err = gdb.Exec(fmt.Sprintf( + "UPDATE erp_stock SET count=count-%d WHERE store_id=%d AND erp_commodity_id=%d", + v.Count, allotOrder.DeliverStoreId, v.CommodityId)).Error + if err != nil { + logger.Errorf("update stock err:", err) + return err + } + } + + return nil +} + +// cancelAllotAuditAndUpdateStock 库存调拨反审核后更新库存信息 +func cancelAllotAuditAndUpdateStock(gdb *gorm.DB, allotOrder ErpInventoryAllotOrder) error { + // 查询库存调拨商品信息 + var commodities []ErpInventoryAllotCommodity + if err := orm.Eloquent.Table("erp_inventory_allot_commodity").Where("allot_order_id = ?", + allotOrder.ID).Find(&commodities).Error; err != nil { + logger.Error("query erp_inventory_allot_commodity err:", logger.Field("err", err)) + return err + } + if len(commodities) == 0 { + return errors.New("取消审核失败,未查询到库存调拨商品信息") + } + + switch allotOrder.State { + // 2 待发货:状态改为待审核,调拨中库存恢复为在库 + // 3 待收货:状态改为待审核,调拨中库存恢复为在库 + case ErpInventoryAllotOrderWaitSend, ErpInventoryAllotOrderWaitReceive: + // 遍历库存调拨商品信息 + for _, v := range commodities { + var stockCommodity []ErpStockCommodity + err := orm.Eloquent.Table("erp_stock_commodity").Where("erp_commodity_id = ? AND store_id = ? "+ + "AND state = ? AND imei = ?", v.CommodityId, allotOrder.ReceiveStoreId, InAllot, v.IMEI). + Find(&stockCommodity).Error + if err != nil { + return fmt.Errorf("查询商品库存失败:[%s]", err.Error()) + } + if len(stockCommodity) == 0 { + return fmt.Errorf("未找到商品库存信息") + } + + // 更新库存商品表商品状态为:在库 + for i := 0; i < int(v.Count); i++ { + stockCommodity[i].StoreId = allotOrder.DeliverStoreId + stockCommodity[i].StoreName = allotOrder.DeliverStoreName + stockCommodity[i].State = InStock + err = gdb.Model(&ErpStockCommodity{}).Where("id = ?", stockCommodity[i].ID). + Updates(stockCommodity[i]).Error + if err != nil { + return fmt.Errorf("更新商品库存失败:%s", err.Error()) + } + } + + // 更新库存商品数量 + err = gdb.Exec(fmt.Sprintf( + "UPDATE erp_stock SET count=count+%d WHERE store_id=%d AND erp_commodity_id=%d", + v.Count, allotOrder.DeliverStoreId, v.CommodityId)).Error + if err != nil { + logger.Errorf("update stock err:", err) + return fmt.Errorf("更新库存商品数量失败:%s", err.Error()) + } + + } + case ErpInventoryAllotOrderFinished: // 已完成:状态改为待审核,调入门店对应商品的库存更新到调出门店,如果库存不足则报错 + // 遍历库存调拨商品信息 + for _, v := range commodities { + var stockCommodity []ErpStockCommodity + err := orm.Eloquent.Table("erp_stock_commodity").Where("erp_commodity_id = ? AND store_id = ? "+ + "AND state = ? AND imei = ?", v.CommodityId, allotOrder.ReceiveStoreId, InStock, v.IMEI). + Find(&stockCommodity).Error + if err != nil { + return fmt.Errorf("查询商品库存失败:[%s]", err.Error()) + } + if len(stockCommodity) == 0 { + return fmt.Errorf("未找到商品库存信息") + } + + if v.Count > uint32(len(stockCommodity)) { + return fmt.Errorf("商品[%s]库存数量[%d]不足", v.CommodityName, len(stockCommodity)) + } + + // 更新库存商品表商品状态为:在库 + for i := 0; i < int(v.Count); i++ { + stockCommodity[i].StoreId = allotOrder.DeliverStoreId + stockCommodity[i].StoreName = allotOrder.DeliverStoreName + stockCommodity[i].State = InStock + err = gdb.Model(&ErpStockCommodity{}).Where("id = ?", stockCommodity[i].ID). + Updates(stockCommodity[i]).Error + if err != nil { + return fmt.Errorf("更新商品库存失败:%s", err.Error()) + } + } + + // 更新库存商品数量 + // 调入门店减去对应调入的数量 + err = gdb.Exec(fmt.Sprintf( + "UPDATE erp_stock SET count=count-%d WHERE store_id=%d AND erp_commodity_id=%d", + v.Count, allotOrder.ReceiveStoreId, v.CommodityId)).Error + if err != nil { + logger.Errorf("update stock err:", err) + return err + } + + // 调出门店加上对应调出的数量 + err = gdb.Exec(fmt.Sprintf( + "UPDATE erp_stock SET count=count+%d WHERE store_id=%d AND erp_commodity_id=%d", + v.Count, allotOrder.DeliverStoreId, v.CommodityId)).Error + if err != nil { + logger.Errorf("update stock err:", err) + return err + } + + } + default: + return fmt.Errorf("订单当前状态未知[%d]", allotOrder.State) + } + + // 更新库存调拨订单信息 + allotOrder.State = ErpInventoryAllotOrderUnAudit + err := gdb.Model(&ErpInventoryAllotOrder{}).Where("id = ?", allotOrder.ID). + Updates(allotOrder).Error + if err != nil { + return fmt.Errorf("更新库存调拨订单失败:%s", err.Error()) + } + + return nil +} + +// DeliverAllotInventory 调拨发货 +func DeliverAllotInventory(req *InventoryAllotDeliverReq) error { + // 查询订单信息 + var inventoryAllotOrder ErpInventoryAllotOrder + err := orm.Eloquent.Table("erp_inventory_allot_order").Where("serial_number = ?", req.SerialNumber). + Find(&inventoryAllotOrder).Error + if err != nil { + logger.Error("order err:", logger.Field("err", err)) + return err + } + + if inventoryAllotOrder.ID == 0 { + return errors.New("未查询到订单信息") + } + + if inventoryAllotOrder.State != ErpInventoryAllotOrderWaitSend { + return errors.New("订单不是待发货状态") + } + + inventoryAllotOrder.LogisticsNumber = req.LogisticsNumber + inventoryAllotOrder.Remark = req.Remark + inventoryAllotOrder.State = ErpInventoryAllotOrderWaitReceive + + err = orm.Eloquent.Model(&ErpInventoryAllotOrder{}).Where("id = ?", inventoryAllotOrder.ID). + Updates(inventoryAllotOrder).Error + if err != nil { + return err + } + + return nil +} + +// ReceiveAllotInventory 调拨收货 +func ReceiveAllotInventory(req *InventoryAllotReceiveReq) error { + // 查询订单信息 + var inventoryAllotOrder ErpInventoryAllotOrder + if err := orm.Eloquent.Table("erp_inventory_allot_order").Where("serial_number = ?", req.SerialNumber). + Find(&inventoryAllotOrder).Error; err != nil { + logger.Error("order err:", logger.Field("err", err)) + return err + } + if inventoryAllotOrder.ID == 0 { + return errors.New("未查询到订单信息") + } + + if inventoryAllotOrder.State != ErpInventoryAllotOrderWaitReceive { + return errors.New("订单不是待收货状态") + } + + // 查询库存调拨商品信息 + var commodities []ErpInventoryAllotCommodity + if err := orm.Eloquent.Table("erp_inventory_allot_commodity").Where("allot_order_id = ?", + inventoryAllotOrder.ID).Find(&commodities).Error; err != nil { + logger.Error("query erp_inventory_allot_commodity err:", logger.Field("err", err)) + return err + } + if len(commodities) == 0 { + return errors.New("未查询到库存调拨商品信息") + } + + begin := orm.Eloquent.Begin() + // 遍历库存调拨商品信息 + for _, v := range commodities { + commodityInfo, err := GetCommodity(v.CommodityId) + if err != nil { + logger.Errorf("GetCommodity err:", err) + return err + } + + var stockCommodity []ErpStockCommodity + err = orm.Eloquent.Table("erp_stock_commodity").Where("erp_commodity_id = ? AND store_id = ? "+ + "AND state = ? AND imei = ?", v.CommodityId, inventoryAllotOrder.ReceiveStoreId, InAllot, v.IMEI). + Find(&stockCommodity).Error + if err != nil { + return fmt.Errorf("查询商品库存失败:[%s]", err.Error()) + } + if len(stockCommodity) == 0 { + return fmt.Errorf("未找到商品库存信息") + } + + // 更新库存商品表商品状态为:在库 + for i := 0; i < int(v.Count); i++ { + stockCommodity[i].State = InStock + err = begin.Model(&ErpStockCommodity{}).Where("id = ?", stockCommodity[i].ID). + Updates(stockCommodity[i]).Error + if err != nil { + return fmt.Errorf("更新商品库存失败:%s", err.Error()) + } + } + + // 更新库存商品数量 + exist, err := QueryRecordExist(fmt.Sprintf("SELECT * FROM erp_stock WHERE store_id=%d AND erp_commodity_id=%d", + inventoryAllotOrder.ReceiveStoreId, v.CommodityId)) + if err != nil { + logger.Errorf("exist err:", err) + return err + } + if exist { + err = begin.Exec(fmt.Sprintf( + "UPDATE erp_stock SET count=count+%d WHERE store_id=%d AND erp_commodity_id=%d", + v.Count, inventoryAllotOrder.ReceiveStoreId, v.CommodityId)).Error + if err != nil { + logger.Errorf("update stock err:", err) + return err + } + } else { + stock := &ErpStock{ + StoreId: inventoryAllotOrder.ReceiveStoreId, + StoreName: inventoryAllotOrder.ReceiveStoreName, + ErpCommodityId: v.CommodityId, + ErpCommodityName: v.CommodityName, + ErpCategoryId: commodityInfo.ErpCategoryId, + ErpCategoryName: commodityInfo.ErpCategoryName, + CommoditySerialNumber: commodityInfo.SerialNumber, + IMEIType: v.IMEIType, + RetailPrice: commodityInfo.RetailPrice, + MinRetailPrice: commodityInfo.MinRetailPrice, + Count: v.Count, + DispatchCount: 0, + } + err = begin.Create(stock).Error + if err != nil { + logger.Errorf("create stock err:", err) + return err + } + } + } + + // 更新调拨订单的状态为:已完成 + inventoryAllotOrder.State = ErpInventoryAllotOrderFinished + err := begin.Model(&ErpInventoryAllotOrder{}).Where("id = ?", inventoryAllotOrder.ID). + Updates(inventoryAllotOrder).Error + if err != nil { + return err + } + + err = begin.Commit().Error + if err != nil { + begin.Rollback() + logger.Errorf("commit err:", err) + return err + } + return nil +} diff --git a/app/admin/models/inventory_product.go b/app/admin/models/inventory_product.go index 9adb828..dadc4b1 100644 --- a/app/admin/models/inventory_product.go +++ b/app/admin/models/inventory_product.go @@ -11,8 +11,8 @@ import ( ) const ( - ErpInventoryProductOrderUnAudit = 1 // 待审核 - ErpInventoryProductOrderFinished = 2 // 已完成 + ErpInventoryProductOrderUnAudit = 1 // 产品入库-待审核 + ErpInventoryProductOrderFinished = 2 // 产品入库-已完成 ) // ErpInventoryProductOrder 产品入库订单表 @@ -134,6 +134,7 @@ func CheckProductInventoryParam(req *ProductInventoryAddReq, editFlag bool) erro } noIMEICommodityMap := make(map[uint32]bool, 0) + IMEICommodityMap := make(map[string]bool, 0) for _, item := range req.Commodities { // 校验商品是否存在 commodityInfo, err := GetCommodity(item.CommodityId) @@ -206,6 +207,13 @@ func CheckProductInventoryParam(req *ProductInventoryAddReq, editFlag bool) erro } else { return fmt.Errorf("商品[%s]重复,相同的非串码商品只能有1条数据", item.CommodityName) } + } else { + _, ok := IMEICommodityMap[item.IMEI] + if !ok { + IMEICommodityMap[item.IMEI] = true + } else { + return fmt.Errorf("串码[%s]有重复项", item.IMEI) + } } } diff --git a/docs/docs.go b/docs/docs.go index 973ed93..efb1e55 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -8028,6 +8028,10 @@ const docTemplate = `{ "description": "收货时间/调入时间", "type": "string" }, + "remark": { + "description": "备注", + "type": "string" + }, "serial_number": { "description": "单据编号", "type": "string" @@ -10648,7 +10652,7 @@ const docTemplate = `{ "type": "string" }, "storage_type": { - "description": "入库方式:1-系统入库 2-采购入库", + "description": "入库方式:1-系统入库 2-采购入库 3-产品入库", "type": "integer" }, "store_id": { @@ -11285,6 +11289,9 @@ const docTemplate = `{ }, "models.InventoryAllotDeliverReq": { "type": "object", + "required": [ + "serial_number" + ], "properties": { "logistics_number": { "description": "物流单号", @@ -11293,6 +11300,10 @@ const docTemplate = `{ "remark": { "description": "备注", "type": "string" + }, + "serial_number": { + "description": "单据编号", + "type": "string" } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 3c00983..071f4ee 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -8017,6 +8017,10 @@ "description": "收货时间/调入时间", "type": "string" }, + "remark": { + "description": "备注", + "type": "string" + }, "serial_number": { "description": "单据编号", "type": "string" @@ -10637,7 +10641,7 @@ "type": "string" }, "storage_type": { - "description": "入库方式:1-系统入库 2-采购入库", + "description": "入库方式:1-系统入库 2-采购入库 3-产品入库", "type": "integer" }, "store_id": { @@ -11274,6 +11278,9 @@ }, "models.InventoryAllotDeliverReq": { "type": "object", + "required": [ + "serial_number" + ], "properties": { "logistics_number": { "description": "物流单号", @@ -11282,6 +11289,10 @@ "remark": { "description": "备注", "type": "string" + }, + "serial_number": { + "description": "单据编号", + "type": "string" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e1b7395..d438bb7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1575,6 +1575,9 @@ definitions: receive_time: description: 收货时间/调入时间 type: string + remark: + description: 备注 + type: string serial_number: description: 单据编号 type: string @@ -3491,7 +3494,7 @@ definitions: description: 最近入库时间 type: string storage_type: - description: 入库方式:1-系统入库 2-采购入库 + description: 入库方式:1-系统入库 2-采购入库 3-产品入库 type: integer store_id: description: 门店id @@ -3958,6 +3961,11 @@ definitions: remark: description: 备注 type: string + serial_number: + description: 单据编号 + type: string + required: + - serial_number type: object models.InventoryAllotDetailReq: properties: