1、优化库存导入接口;2、库存模块增加添加备注接口 3、商品列表和详情接口入参增加是否串码的字段is_imei;4、优化提示语

This commit is contained in:
chenlin 2023-11-24 17:09:24 +08:00
parent 147354b28c
commit c5e0728f01
9 changed files with 797 additions and 442 deletions

View File

@ -15,7 +15,8 @@ import (
type CommodityCreateRequest struct {
Name string `json:"name" binding:"required"` // 商品名称
ErpCategoryId uint32 `json:"erp_category_id" binding:"required"` // 商品分类id
IMEIType uint32 `json:"imei_type" binding:"required"` // 1-无串码 2-串码(系统生成) 3-串码(手动添加)
IsIMEI uint32 `json:"is_imei" binding:"required"` // 是否串码1-串码类 2-非串码
IMEIType uint32 `json:"imei_type" binding:"required"` // 系统生成串码2-是(系统生成) 3-否(手动添加)
ErpSupplierId uint32 `json:"erp_supplier_id" binding:"required"` // 主供应商
RetailPrice uint32 `json:"retail_price" binding:"required"` // 指导零售价
MinRetailPrice uint32 `json:"min_retail_price" binding:"required"` // 最低零售价
@ -68,6 +69,10 @@ func CommodityCreate(c *gin.Context) {
return
}
if req.IsIMEI == 2 { // 是否串码1-串码类 2-非串码
req.IMEIType = 1 // 系统生成串码2-是(系统生成) 3-否(手动添加) 1表示非串码
}
commodity := &models.ErpCommodity{
Number: 1,
Name: req.Name,
@ -175,6 +180,12 @@ func CommodityDetail(c *gin.Context) {
return
}
if commodity.IMEIType == 1 { //无串码
commodity.IsIMEI = 2 // 非串码
} else {
commodity.IsIMEI = 1 // 串码
}
app.OK(c, commodity, "")
return
}

View File

@ -11,6 +11,16 @@ import (
"net/http"
)
type DeliveryCargoReq struct {
Id uint32 `json:"id" binding:"required"` // 商品库存列表id
State uint32 `json:"state" binding:"required"` // 库存状态:4-出库
}
type AddRemarkReq struct {
Id int `json:"id" binding:"required"` // 商品库存列表id
Remark string `json:"remark"` // 备注
}
// GetInventoryList 查询库存列表
// @Summary 查询库存列表
// @Tags 库存管理
@ -65,11 +75,6 @@ func GetInventoryDetail(c *gin.Context) {
return
}
type DeliveryCargoReq struct {
Id uint32 `json:"id" binding:"required"` // 商品库存列表id
State uint32 `json:"state" binding:"required"` // 库存状态:4-出库
}
// DeliveryCargo 出库
// @Summary 出库
// @Tags 库存管理
@ -142,125 +147,6 @@ func BatchPrint(c *gin.Context) {
// @Param file body string true "上传excel文件"
// @Success 200 {object} app.Response
// @Router /api/v1/inventory/import [post]
//func BatchImport(c *gin.Context) {
// models.EsStockLock.Lock()
// defer models.EsStockLock.Unlock()
//
// file, header, err := c.Request.FormFile("file")
// if err != nil {
// //logger.Error("form file err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
// defer file.Close()
//
// readAll, err := io.ReadAll(file)
// if err != nil {
// //logger.Error("read all err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
// fmt.Println("header:", header.Filename)
// //fmt.Println("readAll:", readAll)
//
// fileStart := time.Now().UnixMilli()
// bCol, colsMap, err := models.FileExcelImport(readAll, nil, 3)
// if err != nil {
// //logger.Error("file excel reader err:", err)
// app.Error(c, http.StatusInternalServerError, err, err.Error())
// return
// }
// fmt.Println("FileExcelReader", time.Now().UnixMilli()-fileStart)
// fmt.Println("colsMap:", colsMap)
// if len(colsMap) != 0 {
// colsMap = colsMap[1:]
// }
//
// var stockFiles []models.StockExcel
// err = json.Unmarshal(bCol, &stockFiles)
// if err != nil {
// //logger.Error("erp commodity file excel unmarshal err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
// if len(stockFiles) != 0 {
// stockFiles = stockFiles[1:]
// }
// stockier := models.NewStockImporter()
// fileStart = time.Now().UnixMilli()
// erpStocks := make([]models.ErpStockCommodity, 0, len(stockFiles))
// erpStocks, err = stockier.ImportStockData(stockFiles)
// if err != nil {
// //logger.Error("processing err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
// fmt.Println("ErpStockFileExcelListProcessing", time.Now().UnixMilli()-fileStart)
//
// begin := orm.Eloquent.Begin()
// total := len(erpStocks)
// size := 200
// page := total / size
// if total%size != 0 {
// page += 1
// }
// errGroup := errgroup.Group{}
// for i := 0; i < page; i++ {
// if i == page-1 {
// stockList := erpStocks[i*size:]
// err = begin.Create(&stockList).Error
// if err != nil {
// begin.Rollback()
// //logger.Error("create commodity err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
// } else {
// errGroup.Go(func() error {
// stockList := erpStocks[i*size : (i+1)*size]
// err = begin.Create(&stockList).Error
// if err != nil {
// begin.Rollback()
// //logger.Error("create commodity err:", err)
// return err
// }
// return nil
// })
// }
// }
// err = stockier.ErpStockCountUpdate(begin)
// if err != nil {
// begin.Rollback()
// //logger.Error("erp stock count update err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
//
// err = errGroup.Wait()
// if err != nil {
// begin.Rollback()
// //logger.Error("err group wait err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
// err = begin.Commit().Error
// if err != nil {
// begin.Rollback()
// //logger.Error("commit err:", err)
// app.Error(c, http.StatusInternalServerError, err, "导入失败")
// return
// }
//
// logging := models.Logging{
// Function: "库存导入",
// Event: models.LoggingEventInventoryImport,
// EventName: "库存导入",
// }
// logging.Create(c)
// app.OK(c, nil, "OK")
// return
//}
func BatchImport(c *gin.Context) {
models.EsStockLock.Lock()
defer models.EsStockLock.Unlock()
@ -280,7 +166,6 @@ func BatchImport(c *gin.Context) {
}
fmt.Println("header:", header.Filename)
_, colsMap, err := models.FileExcelImport(readAll, nil, 3)
if err != nil {
//logger.Error("file excel reader err:", err)
@ -294,7 +179,7 @@ func BatchImport(c *gin.Context) {
}
stockier := models.NewStockImporter()
err = stockier.ImportStockData2(colsMap)
err = stockier.ImportStockData(colsMap)
if err != nil {
app.Error(c, http.StatusInternalServerError, err, err.Error())
return
@ -303,3 +188,35 @@ func BatchImport(c *gin.Context) {
app.OK(c, nil, "导入成功")
return
}
// AddRemark 添加备注
// @Summary 添加备注
// @Tags 库存管理
// @Produce json
// @Accept json
// @Param request body AddRemarkReq true "添加备注模型"
// @Success 200 {object} app.Response
// @Router /api/v1/inventory/add_remark [post]
func AddRemark(c *gin.Context) {
req := &AddRemarkReq{}
if err := c.ShouldBindJSON(&req); err != nil {
//logger.Error(err)
app.Error(c, http.StatusBadRequest, errors.New("param err"), "参数错误")
return
}
if err := tools.Validate(req); err != nil {
app.Error(c, http.StatusBadRequest, err, err.Error())
return
}
err := models.UpdateStockCommodityRemark(req.Id, req.Remark)
if err != nil {
//logger.Error("erp stock err:", err)
app.Error(c, http.StatusInternalServerError, err, "获取失败")
return
}
app.OK(c, nil, "OK")
return
}

View File

@ -65,7 +65,7 @@ type ErpStockCommodity struct {
StockEndTime time.Time `json:"stock_end_time" gorm:"-"` // 最近入库结束时间
Age uint32 `json:"age" gorm:"-"` // 最近库龄
AllAge uint32 `json:"all_age" gorm:"-"` // 总库龄
Remark string `json:"remark" gorm:"-"` // 备注
Remark string `json:"remark"` // 备注
//Commodity ErpCommodity `json:"commodity" gorm:"-"`
}
@ -78,6 +78,7 @@ type ErpCommodity struct {
Name string `json:"name"` // 商品名称
ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 商品分类id
ErpCategoryName string `json:"erp_category_name"` // 商品分类名称
IsIMEI uint32 `json:"is_imei" gorm:"-"` // 是否串码1-串码类 2-非串码
IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码(系统生成) 3-串码(手动添加)
IMEI string `json:"imei"` // 串码
ErpSupplierId uint32 `json:"erp_supplier_id" gorm:"index"` // 主供应商id
@ -319,6 +320,14 @@ func (m *ErpCommodityListReq) List() (*ErpCommodityListResp, error) {
resp.List = commodities
}
for i, v := range resp.List {
if v.IMEIType == 1 { //无串码
resp.List[i].IsIMEI = 2 // 非串码
} else {
resp.List[i].IsIMEI = 1 // 串码
}
}
return resp, nil
}
@ -430,94 +439,6 @@ func (m *ErpCategoryListReq) List() (*ErpCategoryListResp, error) {
return resp, nil
}
var ErpCommodityFileExcelCols = []string{"category_1", "category_2", "name", "is_imei", "erp_supplier_name",
"brokerage_1_string", "brokerage_2_string", "retail_price_string", "min_retail_price_string",
"staff_cost_price_string", "wholesale_price_string", "origin", "remark"}
type ErpCommodityFileExcel struct {
Category1 string `json:"category_1"`
Category2 string `json:"category_2"`
IsIMEI string `json:"is_imei"`
RetailPriceString string `json:"retail_price_string"`
MinRetailPriceString string `json:"min_retail_price_string"`
StaffCostPriceString string `json:"staff_cost_price_string"`
WholesalePriceString string `json:"wholesale_price_string"`
Brokerage1String string `json:"brokerage_1_string"`
Brokerage2String string `json:"brokerage_2_string"`
Code string `json:"code"`
ErpCommodity
}
func (e *ErpCommodityFileExcel) Processing() {
if e.IsIMEI == "是" {
e.IMEIType = 2
} else if e.IsIMEI == "否" {
e.IMEIType = 1
}
e.RetailPrice = PercentFloatStringToUin32(e.RetailPriceString)
e.MinRetailPrice = PercentFloatStringToUin32(e.MinRetailPriceString)
e.StaffCostPrice = PercentFloatStringToUin32(e.StaffCostPriceString)
e.WholesalePrice = PercentFloatStringToUin32(e.WholesalePriceString)
e.Brokerage1 = PercentFloatStringToFloat(e.Brokerage1String)
e.Brokerage2 = PercentFloatStringToFloat(e.Brokerage2String)
}
func PercentFloatStringToUin32(s string) uint32 {
u := uint32(0)
if s != "" {
s = strings.ReplaceAll(s, "%", "")
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
//logger.Error("parse float err:", err)
return u
}
u = uint32(f * 100)
return u
}
func PercentFloatStringToFloat(s string) float64 {
//u := uint32(0)
if s != "" {
s = strings.ReplaceAll(s, "%", "")
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
//logger.Error("parse float err:", err)
return f
}
//u = uint32(f * 100)
return f
}
func IntStringToUin32(s string) uint32 {
u := uint32(0)
if s != "" {
s = strings.ReplaceAll(s, "%", "")
}
i, err := strconv.Atoi(s)
if err != nil {
//logger.Error("parse float err:", err)
return u
}
u = uint32(i)
return u
}
type ErpCategorySub struct {
Id uint32 `json:"id"`
Name string `json:"name"`
FullNum uint32 `json:"full_num"`
SubMap map[string]ErpCategory
//UnCodeMap map[string]uint32
}
type CommodityNumberCount struct {
NumberMap map[uint32]uint32 `json:"number_map"`
}
@ -539,15 +460,6 @@ func (m *CommodityNumberCount) GetErpCommodityNumberByCategoryId(categoryId uint
return commodity.Number + 1, nil
}
type ErpSupplierListResp struct {
List []ErpSupplier `json:"list"`
Total int `json:"total"`
PageNum int `json:"page_num"`
PageSize int `json:"page_size"`
ExportUrl string `json:"export_url"`
}
var EsfLock sync.Mutex
var EsStockLock sync.Mutex
type StockImporter struct {
@ -604,7 +516,11 @@ func (e *ErpStockFileExcel) Processing() {
}
func (m *StockImporter) ImportStockData2(colsMap []map[string]interface{}) error {
// ImportStockData 库存导入
// 更新库存表和商品表
// 库存表:插入对应的库存数据
// 库存商品表:插入对应库存商品的数据;供应商只需判断是否在供应商列表即可 todo 库存商品表未插入库存id
func (m *StockImporter) ImportStockData(colsMap []map[string]interface{}) error {
list, err := transStockData(colsMap)
if err != nil {
return err
@ -643,14 +559,12 @@ func (m *StockImporter) ImportStockData2(colsMap []map[string]interface{}) error
//logger.Error("stores err:", err)
return err
}
var erpCommodities []ErpCommodity
err = orm.Eloquent.Table("erp_commodity").Where("name IN (?)", erpCommodityNames).Find(&erpCommodities).Error
if err != nil {
//logger.Error("stores err:", err)
return err
}
var erpSuppliers []ErpSupplier
err = orm.Eloquent.Table("erp_supplier").Debug().Where("name IN (?)", erpSupplierNames).Find(&erpSuppliers).Error
if err != nil && !errors.Is(err, RecordNotFound) {
@ -662,31 +576,26 @@ func (m *StockImporter) ImportStockData2(colsMap []map[string]interface{}) error
storeNameMap[stores[i].Name] = stores[i].ID
m.StoreMap[stores[i].ID] = stores[i].Name
}
for i, _ := range erpCommodities {
erpCommodityMap[erpCommodities[i].Name] = &erpCommodities[i]
m.CommodityMap[erpCommodities[i].ID] = &erpCommodities[i]
}
for i, _ := range erpSuppliers {
erpSupplierNameMap[erpSuppliers[i].Name] = erpSuppliers[i].ID
}
nowTime := time.Now()
//erpStockCommodity := make([]ErpStockCommodity, 0, len(list))
for i, _ := range list {
v1, ok1 := storeNameMap[list[i].StoreName]
if !ok1 {
logger.Error("store name err")
return errors.New("store name err")
}
v2, ok2 := erpCommodityMap[list[i].Name]
if !ok2 || v2 == nil {
logger.Error("erp commodity name err")
return errors.New("erp commodity name err")
}
v3, ok3 := erpSupplierNameMap[list[i].SupplierName]
if !ok3 {
logger.Error("erp supplier name err")
@ -711,6 +620,7 @@ func (m *StockImporter) ImportStockData2(colsMap []map[string]interface{}) error
return fmt.Errorf("数量转换有误:[%v]", err)
}
for j := 0; j < int(nCount); j++ { // 商品库存表都是单笔数据,如果非串码商品有多个,需要插入多条数据
stockCommodity := ErpStockCommodity{
StoreId: v1,
StoreName: list[i].StoreName,
@ -727,16 +637,15 @@ func (m *StockImporter) ImportStockData2(colsMap []map[string]interface{}) error
StorageType: 1,
FirstStockTime: nowTime,
StockTime: nowTime,
Count: uint32(nCount),
Count: 1,
IMEIType: v2.IMEIType,
IMEI: v2.IMEI,
Remark: "",
}
if list[i].SysGenerate != "" { //导入串码不为空则默认为3手动添加
stockCommodity.IMEIType = 3
stockCommodity.IMEI = list[i].SysGenerate
}
erpStockCommodity = append(erpStockCommodity, stockCommodity)
_, ok4 := m.CensusMap[stockCommodity.StoreId]
@ -746,6 +655,7 @@ func (m *StockImporter) ImportStockData2(colsMap []map[string]interface{}) error
m.CensusMap[stockCommodity.StoreId] = map[uint32]uint32{stockCommodity.ErpCommodityId: stockCommodity.Count}
}
}
}
if err = m.processErpStocks(erpStockCommodity); err != nil {
return err
@ -770,7 +680,6 @@ func (m *StockImporter) processErpStocks(erpStocks []ErpStockCommodity) error {
}
stockList := erpStocks[start:end]
errGroup.Go(func() error {
return createStockList(begin, stockList)
})
@ -806,151 +715,6 @@ func createStockList(begin *gorm.DB, stockList []ErpStockCommodity) error {
return nil
}
// ImportStockData 库存导入
// 更新库存表和商品表
// 库存表:插入对应的库存数据()
// 库存商品表:插入对应库存商品的数据;供应商只需判断是否在供应商列表即可
func (m *StockImporter) ImportStockData(list []StockExcel) ([]ErpStockCommodity, error) {
var erpStockCommodity []ErpStockCommodity
storeNameMap := make(map[string]uint32, 0)
erpCommodityMap := make(map[string]*ErpCommodity, 0)
erpSupplierNameMap := make(map[string]uint32, 0)
storeNames := make([]string, 0, len(list))
erpCommodityNames := make([]string, 0, len(list))
erpSupplierNames := make([]string, 0, len(list))
for i, _ := range list {
_, ok1 := storeNameMap[list[i].StoreName]
if !ok1 {
storeNames = append(storeNames, list[i].StoreName)
}
_, ok2 := erpCommodityMap[list[i].Name]
if !ok2 {
erpCommodityNames = append(erpCommodityNames, list[i].Name)
}
_, ok3 := erpSupplierNameMap[list[i].SupplierName]
if !ok3 {
erpSupplierNames = append(erpSupplierNames, list[i].SupplierName)
}
storeNameMap[list[i].StoreName] = uint32(0)
erpCommodityMap[list[i].Name] = nil
erpSupplierNameMap[list[i].SupplierName] = uint32(0)
}
var stores []Store
err := orm.Eloquent.Table("store").Where("name IN (?)", storeNames).Find(&stores).Error
if err != nil {
//logger.Error("stores err:", err)
return nil, err
}
var erpCommodities []ErpCommodity
err = orm.Eloquent.Table("erp_commodity").Where("name IN (?)", erpCommodityNames).Find(&erpCommodities).Error
if err != nil {
//logger.Error("stores err:", err)
return nil, err
}
var erpSuppliers []ErpSupplier
err = orm.Eloquent.Table("erp_supplier").Debug().Where("name IN (?)", erpSupplierNames).Find(&erpSuppliers).Error
if err != nil && !errors.Is(err, RecordNotFound) {
//logger.Error("stores err:", err)
return nil, err
}
for i, _ := range stores {
storeNameMap[stores[i].Name] = stores[i].ID
m.StoreMap[stores[i].ID] = stores[i].Name
}
for i, _ := range erpCommodities {
erpCommodityMap[erpCommodities[i].Name] = &erpCommodities[i]
m.CommodityMap[erpCommodities[i].ID] = &erpCommodities[i]
}
for i, _ := range erpSuppliers {
erpSupplierNameMap[erpSuppliers[i].Name] = erpSuppliers[i].ID
}
nowTime := time.Now()
//erpStockCommodity := make([]ErpStockCommodity, 0, len(list))
for i, _ := range list {
v1, ok1 := storeNameMap[list[i].StoreName]
if !ok1 {
logger.Error("store name err")
return nil, errors.New("store name err")
}
v2, ok2 := erpCommodityMap[list[i].Name]
if !ok2 || v2 == nil {
logger.Error("erp commodity name err")
return nil, errors.New("erp commodity name err")
}
v3, ok3 := erpSupplierNameMap[list[i].SupplierName]
if !ok3 {
logger.Error("erp supplier name err")
return nil, errors.New("erp supplier name err")
}
// 注意:表格导入是员工成本价,数据库存储是员工成本价加价=员工成本价-指导采购价
nStaffCostPrice, err := strconv.ParseUint(list[i].StaffCostPrice, 10, 32)
if err != nil {
return nil, fmt.Errorf("员工成本价转换有误:[%v]", err)
}
nWholesalePrice, err := strconv.ParseUint(list[i].WholesalePrice, 10, 32) // 指导采购价
if err != nil {
return nil, fmt.Errorf("指导采购价转换有误:[%v]", err)
}
if nStaffCostPrice < nWholesalePrice {
return nil, fmt.Errorf("导入价格有误,员工成本价低于指导采购价")
}
nCount, err := strconv.ParseUint(list[i].Count, 10, 32)
if err != nil {
return nil, fmt.Errorf("数量转换有误:[%v]", err)
}
stockCommodity := ErpStockCommodity{
StoreId: v1,
StoreName: list[i].StoreName,
ErpCommodityId: v2.ID,
ErpCommodityName: v2.Name,
CommoditySerialNumber: list[i].SerialNum,
ErpCategoryId: v2.ErpCategoryId,
ErpCategoryName: v2.ErpCategoryName,
ErpSupplierId: v3,
ErpSupplierName: list[i].SupplierName,
StaffCostPrice: uint32(nStaffCostPrice - nWholesalePrice),
WholesalePrice: uint32(nWholesalePrice),
State: 1,
StorageType: 1,
FirstStockTime: nowTime,
StockTime: nowTime,
Count: uint32(nCount),
IMEIType: v2.IMEIType,
IMEI: v2.IMEI,
}
if list[i].SysGenerate != "" { //导入串码不为空则默认为3手动添加
stockCommodity.IMEIType = 3
stockCommodity.IMEI = list[i].SysGenerate
}
erpStockCommodity = append(erpStockCommodity, stockCommodity)
_, ok4 := m.CensusMap[stockCommodity.StoreId]
if ok4 {
m.CensusMap[stockCommodity.StoreId][stockCommodity.ErpCommodityId] += stockCommodity.Count
} else {
m.CensusMap[stockCommodity.StoreId] = map[uint32]uint32{stockCommodity.ErpCommodityId: stockCommodity.Count}
}
}
return erpStockCommodity, nil
}
func (m *StockImporter) ErpStockCountUpdate(gdb *gorm.DB) error {
for k1, v1 := range m.CensusMap {
for k2, v2 := range v1 {
@ -1377,6 +1141,8 @@ type ErpStockCommodityListReq struct {
PageSize int `json:"page_size"` // 页码
IsExport uint32 `json:"is_export"` // 是否导出excel1-导出
}
// ErpStockCommodityListResp 库存详情接口响应参数
type ErpStockCommodityListResp struct {
List []ErpStockCommodity `json:"list"`
Total int `json:"total"`
@ -1385,6 +1151,7 @@ type ErpStockCommodityListResp struct {
ExportUrl string `json:"export_url"`
}
// GetDetailList 查看库存详情
func (m *ErpStockCommodityListReq) GetDetailList() (*ErpStockCommodityListResp, error) {
resp := &ErpStockCommodityListResp{
PageNum: m.PageNum,
@ -1467,6 +1234,10 @@ func (m *ErpStockCommodityListReq) buildQueryConditions(qs *gorm.DB) {
qs = qs.Where("store_id=?", m.StoreId)
}
if m.SupplierId != 0 { //供应商id
qs = qs.Where("supplier_id=?", m.SupplierId)
}
if m.State != 0 { //库存状态
qs = qs.Where("state=?", m.State)
}
@ -1506,6 +1277,7 @@ func (m *ErpStockCommodityListReq) buildQueryConditions(qs *gorm.DB) {
}
}
// SetStockCommodityState 更新库存状态
func SetStockCommodityState(id, state uint32) error {
if state != 4 {
state = 4
@ -1528,6 +1300,7 @@ type BatchPrintInfoReq struct {
PrintListInfo []*BatchPrintInfo `json:"print_list_info" binding:"required"`
}
// BatchPrint 批量打印标签
func BatchPrint(req *BatchPrintInfoReq) error {
return nil
@ -1541,10 +1314,34 @@ func StringToFloat(req string) (float64, error) {
return strconv.ParseFloat(req, 64)
}
// 入库相关
var ErpStockFileExcelCols = []string{"store_name", "erp_commodity_name", "erp_category_name", "serial_number",
"is_imei", "imei", "erp_supplier_name", "stock_time_string", "retail_price_string",
"min_retail_price_string", "staff_cost_price_string", "wholesale_price_string", "count_string",
func PercentFloatStringToUin32(s string) uint32 {
u := uint32(0)
if s != "" {
s = strings.ReplaceAll(s, "%", "")
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
//logger.Error("parse float err:", err)
return u
}
u = uint32(f * 100)
return u
}
func IntStringToUin32(s string) uint32 {
u := uint32(0)
if s != "" {
s = strings.ReplaceAll(s, "%", "")
}
i, err := strconv.Atoi(s)
if err != nil {
//logger.Error("parse float err:", err)
return u
}
u = uint32(i)
return u
}
func NewStockImporter() *StockImporter {
@ -1555,3 +1352,16 @@ func NewStockImporter() *StockImporter {
Inventories: make([]*ErpInventoryStock, 0),
}
}
// UpdateStockCommodityRemark 更新库存商品备注信息
func UpdateStockCommodityRemark(id int, remark string) error {
if remark == "" {
return nil
}
if err := orm.Eloquent.Model(&ErpStockCommodity{}).Where("id=?", id).Updates(map[string]interface{}{
"remark": remark}).Error; err != nil {
return fmt.Errorf("[update err]%v", err)
}
return nil
}

View File

@ -156,6 +156,11 @@ func FileExcelImport(d []byte, cols []string, nType int) ([]byte, []map[string]i
return nil, nil, errors.New("sheet list is empty")
}
rowData, err := reader.GetRows(sheetList[0])
if err != nil {
return nil, nil, fmt.Errorf("reader rows error: %v", err)
}
rows, err := reader.Rows(sheetList[0])
if err != nil {
return nil, nil, fmt.Errorf("reader rows err: %v", err)
@ -170,6 +175,8 @@ func FileExcelImport(d []byte, cols []string, nType int) ([]byte, []map[string]i
return nil, nil, errors.New("get cols is empty")
}
sheetCols = checkExcelFormat(rowData, sheetCols)
var colsMap []map[string]interface{}
if len(cols) == 0 {
switch nType { // 导入类型1 商品分类 2 商品资料 3 商品库存
@ -204,7 +211,12 @@ func FileExcelImport(d []byte, cols []string, nType int) ([]byte, []map[string]i
return nil, nil, errors.New("cols length does not match the number of columns")
}
nCounter := len(rowData) //记录有数据的行数,空行则跳过循环
for rows.Next() {
if nCounter == 0 {
break
}
nCounter--
columns, err := rows.Columns()
if err != nil {
return nil, nil, fmt.Errorf("rows columns err: %v", err)
@ -232,6 +244,23 @@ func FileExcelImport(d []byte, cols []string, nType int) ([]byte, []map[string]i
return mCols, colsMap, nil
}
// checkExcelFormat校验excel表格格式剔除最后1行的空格
func checkExcelFormat(rowData, sheetCols [][]string) [][]string {
if findMaxLength(sheetCols) > len(rowData) { // 最后1行有空格踢除
newSheetCols := make([][]string, len(sheetCols))
for i, col := range sheetCols {
if len(col) > len(rowData) {
newSheetCols[i] = col[:len(rowData)]
} else {
newSheetCols[i] = col
}
}
return newSheetCols
}
return sheetCols
}
// 校验商品分类导入规则
// 导入商品分类校验报错: 1只有3级没有2级或1级 2有23级但没有1级
func checkCategoryExcel(sheetCols [][]string) error {
@ -361,8 +390,9 @@ func checkStockExcel(sheetCols [][]string) error {
}
// 商品串码规则校验 当串码行数跟其他一致时正常遍历,如果小于其他行,则最后一行不遍历 todo
// 如何串码在商品库存表已经存在,则报错提示
if len(sheetCols[3]) <= nLow && i+1 < nLow {
if err := checkSerialCode(sheetCols[0][i], sheetCols[1][i], sheetCols[3][i]); err != nil {
if err := checkSerialCode(sheetCols[0][i], sheetCols[1][i], sheetCols[3][i], i); err != nil {
return err
}
// 串码类商品数量只能为1
@ -476,14 +506,15 @@ func isExistingSupplier(supplierName string) bool {
return count > 0
}
func checkSerialCode(productName, productCode, serialCode string) error {
func checkSerialCode(productName, productCode, serialCode string, row int) error {
var existingCommodity ErpCommodity
var existingStockCommodity ErpStockCommodity
var queryCondition string
if productName != "" {
queryCondition = "name = ?"
} else if productCode != "" {
if productCode != "" {
queryCondition = "serial_number= ?"
} else if productName != "" {
queryCondition = "name = ?"
} else {
return errors.New("商品名称和商品编码不能都为空")
}
@ -507,8 +538,17 @@ func checkSerialCode(productName, productCode, serialCode string) error {
return errors.New("串码类商品,请填写正确的串码")
}
result = orm.Eloquent.Where("imei = ?", serialCode).First(&existingStockCommodity)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { //没找到对应串码
return nil
}
fmt.Println("Error:", result.Error)
return result.Error
}
return errors.New("第" + strconv.Itoa(row+1) + "行串码重复,请勿重复导入")
}
func isNumericOrAlpha(s string) bool {
for _, char := range s {
@ -725,7 +765,7 @@ func convertToErpCommodities(data []CommodityExcel) ([]ErpCommodity, error) {
list, err := GetSupplier(GetSupplierRequest{Name: item.SupplierName})
if len(list) == 0 || errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("有供应商未导入,请检查:[%v]", item.SupplierName)
return nil, fmt.Errorf("[%v]该供应商不存在,请新建供应商", item.SupplierName)
}
serialNumber := fmt.Sprintf("%s%04d", category.Number,
@ -749,7 +789,7 @@ func getCategoryByName(name string) (*Category, error) {
err := orm.Eloquent.Debug().Model(&Category{}).
Where("name = ?", name).First(&category).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("有商品分类未导入,请检查:[%v]", name)
return nil, fmt.Errorf("[%v]该分类不存在,请先新建分类", name)
}
return &category, err
}

View File

@ -86,7 +86,7 @@ const (
)
const DateTimeFormat = "2006-01-02"
const TimeFormat = "2006-01-02 15_04_05"
const TimeFormat = "2006-01-02 15-04-05"
const (
ExportUrl = "https://dev.admin.deovo.com/load/export/"

View File

@ -9,9 +9,10 @@ import (
func registerInventoryManageRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
r := v1.Group("/inventory").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
r.POST("list", inventorymanage.GetInventoryList)
r.POST("detail", inventorymanage.GetInventoryDetail)
r.POST("list", inventorymanage.GetInventoryList) // 库存列表
r.POST("detail", inventorymanage.GetInventoryDetail) // 库存详情
r.POST("delivery", inventorymanage.DeliveryCargo) // 出库
r.POST("print", inventorymanage.BatchPrint) // 批量打印
r.POST("import", inventorymanage.BatchImport)
r.POST("import", inventorymanage.BatchImport) // 库存导入
r.POST("add_remark", inventorymanage.AddRemark) // 添加备注
}

View File

@ -1462,7 +1462,40 @@ const docTemplate = `{
}
}
},
"/api/v1/inventory/detail": {
"/api/v1/inventory/add_remark": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "添加备注",
"parameters": [
{
"description": "添加备注模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/inventorymanage.AddRemarkReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/inventory/delivery": {
"post": {
"consumes": [
"application/json"
@ -1480,6 +1513,39 @@ const docTemplate = `{
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/inventorymanage.DeliveryCargoReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/inventory/detail": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "查询库存详情",
"parameters": [
{
"description": "查询库存详情模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.ErpStockCommodityListReq"
}
@ -1495,6 +1561,39 @@ const docTemplate = `{
}
}
},
"/api/v1/inventory/import": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "库存导入",
"parameters": [
{
"description": "上传excel文件",
"name": "file",
"in": "body",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/inventory/list": {
"post": {
"consumes": [
@ -1528,6 +1627,39 @@ const docTemplate = `{
}
}
},
"/api/v1/inventory/print": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "批量打印",
"parameters": [
{
"description": "批量打印模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.BatchPrintInfoReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/loginlog": {
"put": {
"security": [
@ -3482,10 +3614,10 @@ const docTemplate = `{
"basic.CommodityCreateRequest": {
"type": "object",
"required": [
"brokerage_1",
"erp_category_id",
"erp_supplier_id",
"imei_type",
"is_imei",
"min_retail_price",
"name",
"retail_price",
@ -3510,7 +3642,11 @@ const docTemplate = `{
"type": "integer"
},
"imei_type": {
"description": "1-无串码 2-串码(系统生成) 3-串码(手动添加)",
"description": "系统生成串码2-是(系统生成) 3-否(手动添加)",
"type": "integer"
},
"is_imei": {
"description": "是否串码1-串码类 2-非串码",
"type": "integer"
},
"member_discount": {
@ -3863,6 +3999,75 @@ const docTemplate = `{
}
}
},
"inventorymanage.AddRemarkReq": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"description": "商品库存列表id",
"type": "integer"
},
"remark": {
"description": "备注",
"type": "string"
}
}
},
"inventorymanage.DeliveryCargoReq": {
"type": "object",
"required": [
"id",
"state"
],
"properties": {
"id": {
"description": "商品库存列表id",
"type": "integer"
},
"state": {
"description": "库存状态:4-出库",
"type": "integer"
}
}
},
"models.BatchPrintInfo": {
"type": "object",
"required": [
"erp_commodity_name",
"imei",
"retail_price"
],
"properties": {
"erp_commodity_name": {
"description": "商品名称",
"type": "string"
},
"imei": {
"description": "商品串码",
"type": "string"
},
"retail_price": {
"description": "指导零售价",
"type": "integer"
}
}
},
"models.BatchPrintInfoReq": {
"type": "object",
"required": [
"print_list_info"
],
"properties": {
"print_list_info": {
"type": "array",
"items": {
"$ref": "#/definitions/models.BatchPrintInfo"
}
}
}
},
"models.CashierStore": {
"type": "object",
"properties": {
@ -4287,6 +4492,10 @@ const docTemplate = `{
"description": "1-无串码 2-串码(系统生成) 3-串码(手动添加)",
"type": "integer"
},
"is_imei": {
"description": "是否串码1-串码类 2-非串码",
"type": "integer"
},
"member_discount": {
"description": "会员优惠",
"type": "number"
@ -4535,6 +4744,10 @@ const docTemplate = `{
"description": "首次入库订单编号",
"type": "string"
},
"remark": {
"description": "备注",
"type": "string"
},
"retail_price": {
"description": "指导零售价",
"type": "integer"
@ -5549,6 +5762,7 @@ const docTemplate = `{
"type": "string"
},
"cooperative_business_id": {
"description": "合作商id",
"type": "integer"
},
"cooperative_name": {
@ -5613,6 +5827,7 @@ const docTemplate = `{
"type": "string"
},
"status": {
"description": "状态",
"type": "string"
},
"store_id": {
@ -5620,7 +5835,7 @@ const docTemplate = `{
"type": "integer"
},
"store_name": {
"description": "门店id",
"description": "门店名称",
"type": "string"
},
"updateBy": {
@ -5635,6 +5850,7 @@ const docTemplate = `{
"type": "integer"
},
"username": {
"description": "用户名",
"type": "string"
}
}

View File

@ -1451,7 +1451,40 @@
}
}
},
"/api/v1/inventory/detail": {
"/api/v1/inventory/add_remark": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "添加备注",
"parameters": [
{
"description": "添加备注模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/inventorymanage.AddRemarkReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/inventory/delivery": {
"post": {
"consumes": [
"application/json"
@ -1469,6 +1502,39 @@
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/inventorymanage.DeliveryCargoReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/inventory/detail": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "查询库存详情",
"parameters": [
{
"description": "查询库存详情模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.ErpStockCommodityListReq"
}
@ -1484,6 +1550,39 @@
}
}
},
"/api/v1/inventory/import": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "库存导入",
"parameters": [
{
"description": "上传excel文件",
"name": "file",
"in": "body",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/inventory/list": {
"post": {
"consumes": [
@ -1517,6 +1616,39 @@
}
}
},
"/api/v1/inventory/print": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"库存管理"
],
"summary": "批量打印",
"parameters": [
{
"description": "批量打印模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.BatchPrintInfoReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.Response"
}
}
}
}
},
"/api/v1/loginlog": {
"put": {
"security": [
@ -3471,10 +3603,10 @@
"basic.CommodityCreateRequest": {
"type": "object",
"required": [
"brokerage_1",
"erp_category_id",
"erp_supplier_id",
"imei_type",
"is_imei",
"min_retail_price",
"name",
"retail_price",
@ -3499,7 +3631,11 @@
"type": "integer"
},
"imei_type": {
"description": "1-无串码 2-串码(系统生成) 3-串码(手动添加)",
"description": "系统生成串码2-是(系统生成) 3-否(手动添加)",
"type": "integer"
},
"is_imei": {
"description": "是否串码1-串码类 2-非串码",
"type": "integer"
},
"member_discount": {
@ -3852,6 +3988,75 @@
}
}
},
"inventorymanage.AddRemarkReq": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"description": "商品库存列表id",
"type": "integer"
},
"remark": {
"description": "备注",
"type": "string"
}
}
},
"inventorymanage.DeliveryCargoReq": {
"type": "object",
"required": [
"id",
"state"
],
"properties": {
"id": {
"description": "商品库存列表id",
"type": "integer"
},
"state": {
"description": "库存状态:4-出库",
"type": "integer"
}
}
},
"models.BatchPrintInfo": {
"type": "object",
"required": [
"erp_commodity_name",
"imei",
"retail_price"
],
"properties": {
"erp_commodity_name": {
"description": "商品名称",
"type": "string"
},
"imei": {
"description": "商品串码",
"type": "string"
},
"retail_price": {
"description": "指导零售价",
"type": "integer"
}
}
},
"models.BatchPrintInfoReq": {
"type": "object",
"required": [
"print_list_info"
],
"properties": {
"print_list_info": {
"type": "array",
"items": {
"$ref": "#/definitions/models.BatchPrintInfo"
}
}
}
},
"models.CashierStore": {
"type": "object",
"properties": {
@ -4276,6 +4481,10 @@
"description": "1-无串码 2-串码(系统生成) 3-串码(手动添加)",
"type": "integer"
},
"is_imei": {
"description": "是否串码1-串码类 2-非串码",
"type": "integer"
},
"member_discount": {
"description": "会员优惠",
"type": "number"
@ -4524,6 +4733,10 @@
"description": "首次入库订单编号",
"type": "string"
},
"remark": {
"description": "备注",
"type": "string"
},
"retail_price": {
"description": "指导零售价",
"type": "integer"
@ -5538,6 +5751,7 @@
"type": "string"
},
"cooperative_business_id": {
"description": "合作商id",
"type": "integer"
},
"cooperative_name": {
@ -5602,6 +5816,7 @@
"type": "string"
},
"status": {
"description": "状态",
"type": "string"
},
"store_id": {
@ -5609,7 +5824,7 @@
"type": "integer"
},
"store_name": {
"description": "门店id",
"description": "门店名称",
"type": "string"
},
"updateBy": {
@ -5624,6 +5839,7 @@
"type": "integer"
},
"username": {
"description": "用户名",
"type": "string"
}
}

View File

@ -122,7 +122,10 @@ definitions:
description: 主供应商
type: integer
imei_type:
description: 1-无串码 2-串码(系统生成) 3-串码(手动添加)
description: 系统生成串码2-是(系统生成) 3-否(手动添加)
type: integer
is_imei:
description: 是否串码1-串码类 2-非串码
type: integer
member_discount:
description: 会员优惠
@ -149,10 +152,10 @@ definitions:
description: 指导采购价
type: integer
required:
- brokerage_1
- erp_category_id
- erp_supplier_id
- imei_type
- is_imei
- min_retail_price
- name
- retail_price
@ -394,6 +397,54 @@ definitions:
- id
- name
type: object
inventorymanage.AddRemarkReq:
properties:
id:
description: 商品库存列表id
type: integer
remark:
description: 备注
type: string
required:
- id
type: object
inventorymanage.DeliveryCargoReq:
properties:
id:
description: 商品库存列表id
type: integer
state:
description: 库存状态:4-出库
type: integer
required:
- id
- state
type: object
models.BatchPrintInfo:
properties:
erp_commodity_name:
description: 商品名称
type: string
imei:
description: 商品串码
type: string
retail_price:
description: 指导零售价
type: integer
required:
- erp_commodity_name
- imei
- retail_price
type: object
models.BatchPrintInfoReq:
properties:
print_list_info:
items:
$ref: '#/definitions/models.BatchPrintInfo'
type: array
required:
- print_list_info
type: object
models.CashierStore:
properties:
store_id:
@ -702,6 +753,9 @@ definitions:
imei_type:
description: 1-无串码 2-串码(系统生成) 3-串码(手动添加)
type: integer
is_imei:
description: 是否串码1-串码类 2-非串码
type: integer
member_discount:
description: 会员优惠
type: number
@ -884,6 +938,9 @@ definitions:
original_sn:
description: 首次入库订单编号
type: string
remark:
description: 备注
type: string
retail_price:
description: 指导零售价
type: integer
@ -1613,6 +1670,7 @@ definitions:
description: 头像
type: string
cooperative_business_id:
description: 合作商id
type: integer
cooperative_name:
description: 合作商名称
@ -1660,12 +1718,13 @@ definitions:
description: 性别
type: string
status:
description: 状态
type: string
store_id:
description: 门店id
type: integer
store_name:
description: 门店id
description: 门店名称
type: string
updateBy:
type: string
@ -1676,6 +1735,7 @@ definitions:
description: 编码
type: integer
username:
description: 用户名
type: string
type: object
tools.Params:
@ -2769,12 +2829,54 @@ paths:
summary: 字典类型列表数据
tags:
- system/字典类型
/api/v1/inventory/detail:
/api/v1/inventory/add_remark:
post:
consumes:
- application/json
parameters:
- description: 添加备注模型
in: body
name: request
required: true
schema:
$ref: '#/definitions/inventorymanage.AddRemarkReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/app.Response'
summary: 添加备注
tags:
- 库存管理
/api/v1/inventory/delivery:
post:
consumes:
- application/json
parameters:
- description: 出库模型
in: body
name: request
required: true
schema:
$ref: '#/definitions/inventorymanage.DeliveryCargoReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/app.Response'
summary: 出库
tags:
- 库存管理
/api/v1/inventory/detail:
post:
consumes:
- application/json
parameters:
- description: 查询库存详情模型
in: body
name: request
required: true
@ -2787,7 +2889,28 @@ paths:
description: OK
schema:
$ref: '#/definitions/models.ErpStockCommodityListResp'
summary: 出库
summary: 查询库存详情
tags:
- 库存管理
/api/v1/inventory/import:
post:
consumes:
- application/json
parameters:
- description: 上传excel文件
in: body
name: file
required: true
schema:
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/app.Response'
summary: 库存导入
tags:
- 库存管理
/api/v1/inventory/list:
@ -2811,6 +2934,27 @@ paths:
summary: 查询库存列表
tags:
- 库存管理
/api/v1/inventory/print:
post:
consumes:
- application/json
parameters:
- description: 批量打印模型
in: body
name: request
required: true
schema:
$ref: '#/definitions/models.BatchPrintInfoReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/app.Response'
summary: 批量打印
tags:
- 库存管理
/api/v1/loginlog:
post:
consumes: