package models import ( "errors" "fmt" "github.com/xuri/excelize/v2" orm "go-admin/common/global" "go-admin/logger" "go-admin/tools/config" "golang.org/x/sync/errgroup" "gorm.io/gorm" "math/rand" "strconv" "strings" "sync" "time" ) const ( NoIMEICommodity = 1 // 非串码商品类型 InStock = 1 // 在库 SoldOut = 2 // 已售 PurchaseReturn = 3 // 采购退货 InAllot = 4 // 调拨中 OnSale = 5 // 销售锁定中 ) // ErpStock 库存列表 type ErpStock struct { Model StoreId uint32 `json:"store_id" gorm:"index"` // 门店编号 StoreName string `json:"store_name"` // 门店名称 ErpCommodityId uint32 `json:"erp_commodity_id" gorm:"index"` // 商品id ErpCommodityName string `json:"erp_commodity_name"` // 商品名称 ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 分类id ErpCategoryName string `json:"erp_category_name"` // 分类名称 CommoditySerialNumber string `json:"commodity_serial_number" gorm:"index"` // 商品编码/串码 IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码(系统生成) 3-串码(手动添加) RetailPrice uint32 `json:"retail_price"` // 指导零售价 MinRetailPrice uint32 `json:"min_retail_price"` // 最低零售价 Count uint32 `json:"count"` // 数量 DispatchCount uint32 `json:"dispatch_count"` // 调拨中数量 Commodities []ErpStockCommodity `json:"commodities" gorm:"-"` } // ErpStockCommodity 库存详情 type ErpStockCommodity struct { Model ErpStockId uint32 `json:"erp_stock_id" gorm:"index"` // 库存id StoreId uint32 `json:"store_id" gorm:"index"` // 门店id StoreName string `json:"store_name"` // 门店名称 ErpCommodityId uint32 `json:"erp_commodity_id" gorm:"index"` // 商品id ErpCommodityName string `json:"erp_commodity_name"` // 商品名称 CommoditySerialNumber string `json:"commodity_serial_number" gorm:"index"` // 商品编号 ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 分类id ErpCategoryName string `json:"erp_category_name"` // 分类名称 IMEIType uint32 `json:"imei_type"` // 是否串码:1-无串码 2-串码(系统生成) 3-串码(手动添加) IMEI string `json:"imei"` // 商品串码 ErpSupplierId uint32 `json:"erp_supplier_id" gorm:"index"` // 供应商id ErpSupplierName string `json:"erp_supplier_name"` // 供应商名称 StockTime time.Time `json:"stock_time"` // 最近入库时间 RetailPrice uint32 `json:"retail_price"` // 指导零售价 MinRetailPrice uint32 `json:"min_retail_price"` // 最低零售价 StaffCostPrice uint32 `json:"staff_cost_price"` // 员工成本价加价 WholesalePrice uint32 `json:"wholesale_price"` // 指导采购价 MemberDiscount float64 `json:"member_discount"` // 会员优惠 State uint32 `json:"state"` // 状态:1-在库 2-已售 3-采购退货 4-调拨中 5-销售锁定中 Count uint32 `json:"count"` // 数量 StorageType uint32 `json:"storage_type"` // 入库方式:1-系统入库 2-采购入库 FirstStockTime time.Time `json:"first_stock_time"` // 首次入库时间 StockSn string `json:"stock_sn"` // 库存订单编号 OriginalSn string `json:"original_sn" gorm:"index"` // 首次入库订单编号 StockStartTime time.Time `json:"stock_start_time" gorm:"-"` // 最近入库开始时间 StockEndTime time.Time `json:"stock_end_time" gorm:"-"` // 最近入库结束时间 Age uint32 `json:"age" gorm:"-"` // 最近库龄 AllAge uint32 `json:"all_age" gorm:"-"` // 总库龄 Remark string `json:"remark"` // 备注 //Commodity ErpCommodity `json:"commodity" gorm:"-"` } // ErpCommodity 商品表 type ErpCommodity struct { Model SerialNumber string `json:"serial_number"` // 商品编号 Number uint32 `json:"number"` // 商品数量 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 ErpSupplierName string `json:"erp_supplier_name"` // 主供应商名称 RetailPrice uint32 `json:"retail_price"` // 指导零售价 MinRetailPrice uint32 `json:"min_retail_price"` // 最低零售价 StaffCostPrice uint32 `json:"staff_cost_price"` // 员工成本价加价 WholesalePrice uint32 `json:"wholesale_price"` // 指导采购价 Brokerage1 float64 `json:"brokerage_1"` // 销售毛利提成 Brokerage2 float64 `json:"brokerage_2"` // 员工毛利提成 MemberDiscount float64 `json:"member_discount"` // 会员优惠 Origin string `json:"origin"` // 产地 Remark string `json:"remark" gorm:"type:varchar(512)"` // 备注 ErpCategory *ErpCategory `json:"erp_category" gorm:"-"` } // ErpCategory 商品分类 type ErpCategory struct { Model Name string `json:"name"` // 名称 Priority string `json:"priority"` // 分类 Number uint32 `json:"number"` FullNum uint32 `json:"full_num"` State uint32 `json:"state"` // 1-未使用 2-使用 3-隐藏 Level uint32 `json:"level"` // 分类层级 Pid uint32 `json:"pid" gorm:"index"` Sort uint32 `json:"sort"` SubCats []ErpCategory `json:"sub_cats" gorm:"-"` // 子列表 // erp_category } // ErpSupplier 供应商 type ErpSupplier struct { Model Number string `json:"number" gorm:"index"` Name string `json:"name"` Contact string `json:"contact"` Tel string `json:"tel"` Address string `json:"address"` OpeningBank string `json:"opening_bank"` BankAccount string `json:"bank_account"` PaymentCycle uint32 `json:"payment_cycle"` TaxNumber string `json:"tax_number"` StoreIds string `json:"store_ids"` Landline string `json:"landline"` Email string `json:"email"` CompanyWebsite string `json:"company_website"` // erp_supplier } type ErpInventoryStock struct { Model StoreId uint32 `json:"store_id" gorm:"index"` StoreName string `json:"store_name"` ErpCommodityId uint32 `json:"erp_commodity_id" gorm:"index"` ErpCommodityName string `json:"erp_commodity_name"` ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` ErpCategoryName string `json:"erp_category_name"` CommoditySerialNumber string `json:"commodity_serial_number" gorm:"index"` IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码(系统生成) 3-串码(手动添加) RetailPrice uint32 `json:"retail_price"` MinRetailPrice uint32 `json:"min_retail_price"` Count uint32 `json:"count"` Sn string `json:"sn" gorm:"index"` //ErpSupplierId uint32 `json:"erp_supplier_id" gorm:"index"` //ErpSupplierName string `json:"erp_supplier_name"` //IMEI string `json:"imei"` //StockTime time.Time `json:"stock_time"` //StaffCostPrice uint32 `json:"staff_cost_price"` //WholesalePrice uint32 `json:"wholesale_price"` // erp_inventory_stock } type ErpInventoryStockCommodity struct { Model ErpInventoryStockId uint32 `json:"erp_inventory_stock_id" gorm:"index"` ErpCommodityId uint32 `json:"erp_commodity_id" gorm:"index"` ErpCommodityName string `json:"erp_commodity_name"` CommoditySerialNumber string `json:"commodity_serial_number" gorm:"index"` IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码(系统生成) 3-串码(手动添加) IMEI string `json:"imei"` ErpSupplierId uint32 `json:"erp_supplier_id" gorm:"index"` ErpSupplierName string `json:"erp_supplier_name"` StockTime time.Time `json:"stock_time"` RetailPrice uint32 `json:"retail_price"` MinRetailPrice uint32 `json:"min_retail_price"` StaffCostPrice uint32 `json:"staff_cost_price"` WholesalePrice uint32 `json:"wholesale_price"` Count uint32 `json:"count"` StorageType uint32 `json:"storage_type"` Sn string `json:"sn" gorm:"index"` //StoreId uint32 `json:"store_id" gorm:"index"` //StoreName string `json:"store_name"` //ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` //ErpCategoryName string `json:"erp_category_name"` // erp_inventory_stock_commodity } // IdInit 新增/编辑商品时获取分类和供应商信息 func (c *ErpCommodity) IdInit() { if c.ErpCategoryId != 0 { if c.ErpCategory == nil { category, err := GetErpCategory(c.ErpCategoryId) if err != nil { //logger.Error("get erp category err:", err) return } c.ErpCategory = category } c.ErpCategoryName = c.ErpCategory.Name } if c.ErpSupplierId != 0 { supplier, err := GetErpSupplier(c.ErpSupplierId) if err != nil { //logger.Error("get erp category err:", err) } else { c.ErpSupplierName = supplier.Name } } } // GetErpCategory 根据id查询分类信息 func GetErpCategory(id uint32) (*ErpCategory, error) { category := new(ErpCategory) err := orm.Eloquent.Table("erp_category").Where("id=?", id).Find(category).Error if err != nil { //logger.Error("category err:", err) return category, err } return category, nil } // GetErpSupplier 根据id查询供应商 func GetErpSupplier(id uint32) (*ErpSupplier, error) { supplier := new(ErpSupplier) err := orm.Eloquent.Table("erp_supplier").Where("id=?", id).Find(supplier).Error if err != nil { //logger.Error("category err:", err) return supplier, err } return supplier, err } // SetErpCategory 新增/编辑商品时设置分类信息 func (c *ErpCommodity) SetErpCategory() error { if c.ErpCategoryId != 0 { category, err := GetErpCategory(c.ErpCategoryId) if err != nil { //logger.Error("get erp category err:", err) return err } c.ErpCategory = category return nil } return errors.New("erp category id null") } type ErpCommodityListReq struct { SerialNumber string `json:"serial_number"` // 商品编号 ErpCommodityName string `json:"erp_commodity_name"` // 商品名称 ErpCategoryId uint32 `json:"erp_category_id"` // 商品分类id IMEI string `json:"imei"` // 串码 ErpSupplierId uint32 `json:"erp_supplier_id"` // 供应商id PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 每页展示数据条数 IsExport uint32 `json:"is_export"` // 1-导出 } type ErpCommodityListResp struct { List []ErpCommodity `json:"list"` Total int `json:"count"` // 数据总条数 PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 每页展示条数 ExportUrl string `json:"export_url"` // 1-导出 } func (m *ErpCommodityListReq) List() (*ErpCommodityListResp, error) { resp := &ErpCommodityListResp{ 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.Debug().Table("erp_commodity") if m.SerialNumber != "" { qs = qs.Where("serial_number=?", m.SerialNumber) } if m.ErpCommodityName != "" { qs = qs.Where("name Like '%" + m.ErpCommodityName + "%'") //qs = qs.Where("name LIKE ?", m.Name) } if m.IMEI != "" { qs = qs.Where("imei=?", m.IMEI) } if m.ErpCategoryId != 0 { qs = qs.Where("erp_category_id=?", m.ErpCategoryId) } if m.ErpSupplierId != 0 { qs = qs.Where("erp_supplier_id=?", m.ErpSupplierId) } var count int64 err := qs.Count(&count).Error if err != nil { //logger.Error("count err:", err) return resp, err } //resp.Total = int(count)/m.PageSize + 1 var commodities []ErpCommodity if m.IsExport == 1 { err = qs.Order("id DESC").Find(&commodities).Error if err != nil && err != RecordNotFound { //logger.Error("dailys err:", err) return resp, err } listExport, err := ErpCommodityListExport(commodities) if err != nil { //logger.Error("list export err:", err) } resp.ExportUrl = listExport } else { err = qs.Order("id DESC").Offset(page * m.PageSize).Limit(m.PageSize).Find(&commodities).Error if err != nil && err != RecordNotFound { //logger.Error("erp commodity list err:", err) return resp, err } resp.List = commodities } for i, v := range resp.List { if v.IMEIType == 1 { //无串码 resp.List[i].IsIMEI = 2 // 非串码 } else { resp.List[i].IsIMEI = 1 // 串码 } } //跟之前保持一致 resp.Total = int(count) resp.PageIndex = page + 1 resp.PageSize = m.PageSize return resp, nil } type ErpCategoryListReq struct { ErpCategoryId uint32 `json:"erp_category_id"` Pid uint32 `json:"pid"` Level uint32 `json:"level"` // 分类层级 State uint32 `json:"state"` // 1-未使用 2-使用 3-隐藏 PageNum int `json:"page_num"` PageSize int `json:"page_size"` IsExport uint32 `json:"is_export"` // 1-导出 } type ErpCategoryListResp struct { List []ErpCategory `json:"list"` Total int `json:"total"` PageNum int `json:"page_num"` PageSize int `json:"page_size"` ExportUrl string `json:"export_url"` } func (m *ErpCategoryListReq) List() (*ErpCategoryListResp, error) { resp := &ErpCategoryListResp{ PageNum: m.PageNum, PageSize: m.PageSize, } //page := m.PageNum - 1 //if page < 0 { // page = 0 //} //if m.PageSize == 0 { // m.PageSize = 10 //} //qs := orm.Eloquent.Table("erp_category") //if m.Level != 0 { // qs = qs.Where("level=?", m.Level) //} ////if m.ErpCategoryId != 0 { //// qs = qs.IDEq(m.ErpCategoryId) ////} //if m.Pid != 0 { // qs = qs.Where("pid", m.Pid) //} //var count int64 //err := qs.Count(&count).Error //if err != nil { // logger.Error("count err:", err) // return resp, err //} //resp.Total = int(count)/m.PageSize + 1 //var categories []ErpCategory //err = qs.Order("id DESC").Offset(page * m.PageSize).Limit(m.PageSize).Find(&categories).Error //if err != nil && err != RecordNotFound { // logger.Error("erp commodity list err:", err) // return resp, err //} var categories []ErpCategory qs := orm.Eloquent.Table("erp_category").Where("level=?", 1) if m.Pid != 0 { qs = qs.Where("id=?", m.Pid) } if m.State != 0 { qs.Where("state=?", m.State) } err := qs.Order("sort DESC").Find(&categories).Error if err != nil { //logger.Error("erp commodity list err:", err) return resp, err } pids := make([]uint32, 0) for i, _ := range categories { pids = append(pids, categories[i].ID) } var subCat []ErpCategory subQs := orm.Eloquent.Table("erp_category").Where("pid in (?)", pids) if m.State != 0 { subQs.Where("state=?", m.State) } err = subQs.Order("sort DESC").Find(&subCat).Error if err != nil { logger.Errorf("pCat err:%#v", err) return resp, err } pCatMap := make(map[uint32][]ErpCategory, 0) for i, _ := range subCat { pCatMap[subCat[i].Pid] = append(pCatMap[subCat[i].Pid], subCat[i]) } for i, _ := range categories { v, ok := pCatMap[categories[i].ID] if ok { categories[i].SubCats = v } } if m.IsExport == 1 { listExport, err := ErpCategoryListExport(categories) if err != nil { //logger.Error("list export err:", err) } resp.ExportUrl = listExport } resp.List = categories return resp, nil } type CommodityNumberCount struct { NumberMap map[uint32]uint32 `json:"number_map"` } func (m *CommodityNumberCount) GetErpCommodityNumberByCategoryId(categoryId uint32) (uint32, error) { v, ok := m.NumberMap[categoryId] if ok { m.NumberMap[categoryId] = v + 1 return v + 1, nil } var commodity ErpCommodity err := orm.Eloquent.Raw(fmt.Sprintf( "SELECT number FROM erp_commodity WHERE erp_category_id=%d ORDER BY id DESC LIMIT 0,1;", categoryId)).Scan(&commodity).Error if err != nil { //logger.Error("all categories map err:", err) return 0, err } m.NumberMap[categoryId] = commodity.Number + 1 return commodity.Number + 1, nil } var EsStockLock sync.Mutex type StockImporter struct { CensusMap map[uint32]map[uint32]uint32 CommodityMap map[uint32]*ErpCommodity StoreMap map[uint32]string Inventories []*ErpInventoryStock } type ErpStockFileExcel struct { StockTimeString string `json:"stock_time_string"` 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"` // 指导采购价 CountString string `json:"count_string"` // 数量 //StoreName string `json:"store_name"` //ErpCommodityName string `json:"erp_commodity_name"` //ErpCategoryName string `json:"erp_category_name"` //SerialNumber string `json:"serial_number"` //IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码 //IMEI string `json:"imei"` //ErpSupplierName string `json:"erp_supplier_name"` ErpStockCommodity } func (e *ErpStockFileExcel) Processing() { e.Count = IntStringToUin32(e.CountString) if e.IsIMEI == "是" { e.IMEIType = 2 e.Count = 1 } else if e.IsIMEI == "否" { e.IMEIType = 1 } //parseTime, err := time.Parse(DateTimeFormat, e.StockTimeString) //fmt.Println("StockTimeString:", e.StockTimeString) //parseTime, err := time.Parse("01-02-06", e.StockTimeString) format := "2006/01/02" parseTime, err := time.Parse(format, e.StockTimeString) if err != nil { //logger.Error("parse err:", err) parseTime = time.Now() } e.StockTime = parseTime e.RetailPrice = PercentFloatStringToUin32(e.RetailPriceString) e.MinRetailPrice = PercentFloatStringToUin32(e.MinRetailPriceString) e.StaffCostPrice = PercentFloatStringToUin32(e.StaffCostPriceString) e.WholesalePrice = PercentFloatStringToUin32(e.WholesalePriceString) } // ImportStockData 库存导入 // 更新库存表和商品表 // 库存表:插入对应的库存数据 // 库存商品表:插入对应库存商品的数据;供应商只需判断是否在供应商列表即可 todo 库存商品表未插入库存id func (m *StockImporter) ImportStockData(colsMap []map[string]interface{}) error { list, err := transStockData(colsMap) if err != nil { return err } 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 { if list[i].Name == "" { //如果商品名称为空,则需要补全 commodityName, err := getCommodityNameBySerialNum(list[i].SerialNum) if err != nil { logger.Errorf("getCommodityNameBySerialNum err:", logger.Field("err", err)) return err } list[i].Name = commodityName } 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 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) { //logger.Error("stores err:", err) return 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() 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") return errors.New("erp supplier name err") } // 注意:表格导入是员工成本价,数据库存储是员工成本价加价=员工成本价-指导采购价 nStaffCostPrice, err := strconv.ParseUint(list[i].StaffCostPrice, 10, 32) if err != nil { return fmt.Errorf("员工成本价转换有误:[%v]", err) } nWholesalePrice, err := strconv.ParseUint(list[i].WholesalePrice, 10, 32) // 指导采购价 if err != nil { return fmt.Errorf("指导采购价转换有误:[%v]", err) } if nStaffCostPrice < nWholesalePrice { return fmt.Errorf("导入价格有误,员工成本价低于指导采购价") } nCount, err := strconv.ParseUint(list[i].Count, 10, 32) if err != nil { return fmt.Errorf("数量转换有误:[%v]", err) } for j := 0; j < int(nCount); j++ { // 商品库存表都是单笔数据,如果非串码商品有多个,需要插入多条数据 stockCommodity := ErpStockCommodity{ StoreId: v1, StoreName: list[i].StoreName, ErpCommodityId: v2.ID, ErpCommodityName: v2.Name, CommoditySerialNumber: v2.SerialNumber, 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: 1, IMEIType: v2.IMEIType, IMEI: "", Remark: "", MemberDiscount: v2.MemberDiscount, MinRetailPrice: v2.MinRetailPrice, RetailPrice: v2.RetailPrice, } // todo 首次入库订单编号、订单编号还未赋值 if list[i].StockTime != "" { //导入时间不为空 local, _ := time.LoadLocation("Local") parsedTime, _ := time.ParseInLocation("2006/1/2", list[i].StockTime, local) //parsedTime, _ := time.Parse("2006/1/2", list[i].StockTime) stockCommodity.FirstStockTime = parsedTime stockCommodity.StockTime = parsedTime } //if list[i].SysGenerate != "" && v2.IMEIType == 3 { //导入串码不为空,则默认为3手动添加 // stockCommodity.IMEIType = 3 // stockCommodity.IMEI = list[i].SysGenerate //} else if v2.IMEIType == 2 { // 如果该商品串码类型是:2-串码(系统生成),则系统自动生成 // stockCommodity.IMEIType = 2 // serialCode, err := generateSerialCode(v2.ErpCategoryId) // if err != nil { // return err // } // stockCommodity.IMEI = serialCode //} if list[i].SysGenerate != "" && v2.IMEIType != 1 { // 商品为串码商品,且导入串码不为空 stockCommodity.IMEIType = v2.IMEIType 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} } } } if err = m.processErpStocks(erpStockCommodity); err != nil { return err } return nil } // 获取商品名称 func getCommodityNameBySerialNum(serialNumber string) (string, error) { var commodity ErpCommodity err := orm.Eloquent.Table("erp_commodity").Where("serial_number=?", serialNumber).Find(&commodity).Error if err != nil { return "", err } return commodity.Name, nil } // generateSerialCode 生成商品串码 func generateSerialCode(categoryID uint32) (string, error) { // 生成年月日6位数 dateStr := time.Now().Format("060102") // 生成四位随机数 rand.Seed(time.Now().UnixNano()) randomNumber := rand.Intn(10000) randomStr := fmt.Sprintf("%04d", randomNumber) // 获取商品分类编号前3位 categoryStr, err := getCategoryCode(categoryID) if err != nil { logger.Errorf("getCategoryCode err:", logger.Field("err", err)) return "", err } // 拼接串码 serialCode := categoryStr + dateStr + randomStr return serialCode, nil } // getCategoryCode 根据商品分类ID查询分类编号前3位 func getCategoryCode(categoryID uint32) (string, error) { // 这里模拟查询数据库,实际中你需要连接数据库进行查询 // 假设这里的数据是从数据库中查询得到的 categoryInfo, err := GetCategoryById(categoryID) if err != nil { logger.Errorf("GetCategoryById err:", logger.Field("err", err)) return "", err } // 取前3位字符串 return categoryInfo.Number[:3], nil } func (m *StockImporter) processErpStocks(erpStocks []ErpStockCommodity) error { begin := orm.Eloquent.Begin() total := len(erpStocks) size := 200 page := (total + size - 1) / size errGroup := errgroup.Group{} for i := 0; i < page; i++ { start := i * size end := (i + 1) * size if end > total { end = total } stockList := erpStocks[start:end] errGroup.Go(func() error { return createStockList(begin, stockList) //插入库存商品详情 }) } err := m.ErpStockCountUpdate(begin) //更新or插入库存表 if err != nil { begin.Rollback() return err } err = errGroup.Wait() if err != nil { begin.Rollback() return err } err = begin.Commit().Error if err != nil { begin.Rollback() return err } return nil } func createStockList(begin *gorm.DB, stockList []ErpStockCommodity) error { err := begin.Debug().Create(&stockList).Error if err != nil { begin.Rollback() return err } return nil } func (m *StockImporter) ErpStockCountUpdate(gdb *gorm.DB) error { for k1, v1 := range m.CensusMap { for k2, v2 := range v1 { exist, err := QueryRecordExist(fmt.Sprintf("SELECT * FROM erp_stock WHERE store_id=%d AND erp_commodity_id=%d", k1, k2)) if err != nil { //logger.Error("exist err:", err) return err } v, ok := m.CommodityMap[k2] fmt.Println("CommodityMap", m.CommodityMap) fmt.Println("ok", ok) fmt.Println("v", v) if exist { err = gdb.Exec(fmt.Sprintf( "UPDATE erp_stock SET count=count+%d WHERE store_id=%d AND erp_commodity_id=%d;", v2, k1, k2)).Error if err != nil { //logger.Error("update stock err:", err) return err } } else { if ok && v != nil { stock := &ErpStock{ StoreId: k1, StoreName: m.StoreMap[k1], ErpCommodityId: v.ID, ErpCommodityName: v.Name, ErpCategoryId: v.ErpCategoryId, ErpCategoryName: v.ErpCategoryName, CommoditySerialNumber: v.SerialNumber, IMEIType: v.IMEIType, RetailPrice: v.RetailPrice, MinRetailPrice: v.MinRetailPrice, Count: v2, DispatchCount: 0, } err = gdb.Create(stock).Error if err != nil { //logger.Error("create stock err:", err) return err } } } if ok && v != nil { inventoryStock := &ErpInventoryStock{ StoreId: k1, StoreName: m.StoreMap[k1], ErpCommodityId: v.ID, ErpCommodityName: v.Name, ErpCategoryId: v.ErpCategoryId, ErpCategoryName: v.ErpCategoryName, CommoditySerialNumber: v.SerialNumber, IMEIType: v.IMEIType, RetailPrice: v.RetailPrice, MinRetailPrice: v.MinRetailPrice, Count: v2, } fmt.Println("inventoryStock", inventoryStock.Count) m.Inventories = append(m.Inventories, inventoryStock) } } } return nil } // 查询库存详情时同步库存id func updateCommodityStock(id uint32) error { var stock ErpStock err := orm.Eloquent.Table("erp_stock").Raw("SELECT * FROM erp_stock WHERE id = ?", id).Scan(&stock).Error if err != nil { return err } if stock.ID != 0 { err = orm.Eloquent.Debug().Exec(fmt.Sprintf( "UPDATE erp_stock_commodity SET erp_stock_id = %d WHERE erp_commodity_name = '%s' AND erp_stock_id = 0;", stock.ID, stock.ErpCommodityName)).Error if err != nil { return err } } return nil } func (m *StockImporter) ErpInventoryStockCreate(gdb *gorm.DB, list []ErpStockCommodity) error { defer func() { if err := recover(); err != nil { //logger.Error("err:", err) return } }() inventoryStockIdMap := make(map[string]uint32, 0) for _, inventory := range m.Inventories { //err := gdb.Create(inventorymanage).Error err := orm.Eloquent.Create(inventory).Error if err != nil { //logger.Error("create erp inventorymanage stock err:", err) return err } inventoryStockIdMap[fmt.Sprintf("%d_%d", inventory.StoreId, inventory.ErpCommodityId)] = inventory.ID } begin := orm.Eloquent.Begin() total := len(list) size := 500 page := total / size if total%size != 0 { page += 1 } errGroup := errgroup.Group{} for i := 0; i < page; i++ { if i == page-1 { stockList := ErpStockCommodityToInventory(inventoryStockIdMap, list[i*size:]) err := begin.Create(&stockList).Error if err != nil { begin.Rollback() //logger.Error("create commodity err:", err) return err } } else { errGroup.Go(func() error { //stockList := list[i*size : (i+1)*size] stockList := ErpStockCommodityToInventory(inventoryStockIdMap, list[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 := errGroup.Wait() if err != nil { //logger.Error("wait err:", err) return err } err = begin.Commit().Error if err != nil { //logger.Error("commit err:", err) return err } return nil } func ErpStockCommodityToInventory(inventoryStockIdMap map[string]uint32, list []ErpStockCommodity) []*ErpInventoryStockCommodity { inventoryList := make([]*ErpInventoryStockCommodity, 0, len(list)) for i, _ := range list { v, ok := inventoryStockIdMap[fmt.Sprintf("%d_%d", list[i].StoreId, list[i].ErpCommodityId)] if ok { inventoryCommodity := &ErpInventoryStockCommodity{ ErpInventoryStockId: v, ErpCommodityId: list[i].ErpCommodityId, ErpCommodityName: list[i].ErpCommodityName, CommoditySerialNumber: list[i].CommoditySerialNumber, IMEIType: list[i].IMEIType, IMEI: list[i].IMEI, ErpSupplierId: list[i].ErpSupplierId, ErpSupplierName: list[i].ErpSupplierName, StockTime: list[i].StockTime, RetailPrice: list[i].RetailPrice, MinRetailPrice: list[i].MinRetailPrice, StaffCostPrice: list[i].StaffCostPrice, WholesalePrice: list[i].WholesalePrice, Count: list[i].Count, } //err := gdb.Create(inventoryCommodity).Error inventoryList = append(inventoryList, inventoryCommodity) //err := orm.Eloquent.Create(inventoryCommodity).Error //if err != nil { // logger.Error("create erp inventorymanage stock commodity err:", err) // return inventoryList //} } } return inventoryList } // ErpCommodityListExport 导出商品列表 func ErpCommodityListExport(list []ErpCommodity) (string, error) { file := excelize.NewFile() streamWriter, err := file.NewStreamWriter("Sheet1") if err != nil { fmt.Println(err) } url := ExportUrl fileName := time.Now().Format(TimeFormat) + "商品" + ".xlsx" //title := []interface{}{"供应商编号", "供应商名称", "联系人", "手机号", "地址", "开户银行", "银行账号", "付款周期/天"} title := []interface{}{"商品编号", "商品名称", "商品分类", "是否串码", "主供应商", "零售价", "最低零售价", "员工成本价", "采购价", "提成等级1", "提成等级2", "产地", "备注", "会员折扣(零售价的百分比)"} cell, _ := excelize.CoordinatesToCellName(1, 1) if err = streamWriter.SetRow(cell, title); err != nil { fmt.Println(err) } var row []interface{} for rowId := 0; rowId < len(list); rowId++ { isIMEI := "否" if list[rowId].IMEIType == 2 { isIMEI = "是" } row = []interface{}{list[rowId].SerialNumber, list[rowId].Name, list[rowId].ErpCategoryName, isIMEI, list[rowId].ErpSupplierName, list[rowId].RetailPrice, list[rowId].MinRetailPrice, list[rowId].StaffCostPrice, list[rowId].WholesalePrice, list[rowId].Brokerage1, list[rowId].Brokerage2, list[rowId].Origin, list[rowId].Remark, list[rowId].MemberDiscount} cell, _ := excelize.CoordinatesToCellName(1, rowId+2) if err := streamWriter.SetRow(cell, row); err != nil { fmt.Println(err) } } if err := streamWriter.Flush(); err != nil { fmt.Println(err) } if err := file.SaveAs("/www/server/images/export/" + fileName); err != nil { //if err := file.SaveAs("./" + fileName); err != nil { fmt.Println(err) } return url + fileName, nil } // InventoryDetailListExport 导出库存商品列表 func InventoryDetailListExport(list []ErpStockCommodity) (string, error) { file := excelize.NewFile() streamWriter, err := file.NewStreamWriter("Sheet1") if err != nil { fmt.Println(err) } url := ExportUrl fileName := time.Now().Format(TimeFormat) + "商品" + ".xlsx" fmt.Println("url fileName:", url+fileName) title := []interface{}{"商品编号", "商品名称", "商品分类", "是否串码", "商品串码", "所属门店", "供应商", "首次入库时间", "首次入库方式", "首次入库订单编号", "最近入库时间", "入库采购价", "入库员工成本价", "最近库龄", "总库龄", "当前状态", "备注"} cell, _ := excelize.CoordinatesToCellName(1, 1) if err = streamWriter.SetRow(cell, title); err != nil { fmt.Println(err) } var row []interface{} for rowId := 0; rowId < len(list); rowId++ { isIMEIType := "是" if list[rowId].IMEIType == 1 { isIMEIType = "否" } storageType := "系统入库" if list[rowId].StorageType == 2 { storageType = "采购入库" } state := "库存中" switch list[rowId].State { case 1: state = "库存中" case 2: state = "已售" case 3: state = "采购退货" case 4: state = "调拨中" } row = []interface{}{ list[rowId].CommoditySerialNumber, list[rowId].ErpCommodityName, list[rowId].ErpCategoryName, isIMEIType, list[rowId].IMEI, list[rowId].StoreName, list[rowId].ErpSupplierName, list[rowId].FirstStockTime, storageType, list[rowId].StockSn, list[rowId].StockTime, list[rowId].WholesalePrice, list[rowId].StaffCostPrice, list[rowId].Age, list[rowId].AllAge, state, list[rowId].Remark} cell, _ := excelize.CoordinatesToCellName(1, rowId+2) if err := streamWriter.SetRow(cell, row); err != nil { fmt.Println(err) } } if err := streamWriter.Flush(); err != nil { fmt.Println(err) } fmt.Println("save fileName:", config.ExportConfig.Path+fileName) if err := file.SaveAs(config.ExportConfig.Path + fileName); err != nil { fmt.Println(err) } return url + fileName, nil } // ErpCategoryListExport 导出商品分类 func ErpCategoryListExport(list []ErpCategory) (string, error) { file := excelize.NewFile() streamWriter, err := file.NewStreamWriter("Sheet1") if err != nil { fmt.Println(err) } url := ExportUrl fileName := time.Now().Format(TimeFormat) + "分类列表数据" + ".xlsx" //title := []interface{}{"门店", "用户ID", "订单编号", "下单时间", "卡带", "说明", "回收价", "闲麦价", "审核时间", "审核人", "操作", "复核时间", "复核状态"} title := []interface{}{"一级分类", "二级分类"} cell, _ := excelize.CoordinatesToCellName(1, 1) if err = streamWriter.SetRow(cell, title); err != nil { fmt.Println(err) } rowIdx := 2 var row []interface{} for rowId := 0; rowId < len(list); rowId++ { cats := list[rowId].SubCats for i := 0; i < len(cats); i++ { rowName := list[rowId].Name if i != 0 { rowName = "" } row = []interface{}{rowName, cats[i].Name} cell, _ := excelize.CoordinatesToCellName(1, rowIdx) if err := streamWriter.SetRow(cell, row); err != nil { fmt.Println(err) } rowIdx++ } } if err := streamWriter.Flush(); err != nil { fmt.Println(err) } if err := file.SaveAs("/www/server/images/export/" + fileName); err != nil { //if err := file.SaveAs("./" + fileName); err != nil { fmt.Println(err) } return url + fileName, nil } type ErpStockListReq struct { SerialNumber string `json:"serial_number"` // 商品编号 CommodityName string `json:"commodity_name"` // 商品名称 ErpCategoryId uint32 `json:"erp_category_id"` // 商品分类 StockType uint32 `json:"stock_type"` // 库存情况:1-全部 2-有库存 3-无库存 StoreId uint32 `json:"store_id"` // 门店编号 PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 页面条数 //IsExport uint32 `json:"is_export"` // 1-导出 } type ErpStockListResp struct { List []ErpStock `json:"list"` Total int `json:"count"` // 数据总条数 PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 每页展示条数 ExportUrl string `json:"export_url"` } func (m *ErpStockListReq) List() (*ErpStockListResp, error) { resp := &ErpStockListResp{ 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_stock"). Joins("JOIN erp_category c ON erp_stock.erp_category_id = c.id") if m.SerialNumber != "" { qs = qs.Where("commodity_serial_number=?", m.SerialNumber) } if m.CommodityName != "" { qs = qs.Where("erp_commodity_name LIKE ?", "%"+m.CommodityName+"%") } if m.ErpCategoryId != 0 { qs = qs.Where("erp_category_id=?", m.ErpCategoryId) } if m.StoreId != 0 { qs = qs.Where("store_id=?", m.StoreId) } switch m.StockType { case 2: qs = qs.Where("count > 0") case 3: qs = qs.Where("count = 0") } // SQL Order By Clause qs = qs.Order("CASE WHEN c.pid = 0 THEN CAST(c.number AS SIGNED) ELSE " + "CAST(SUBSTRING(c.number, 1, 3) AS SIGNED) END"). Order("CAST(c.pid AS SIGNED)"). Order("CAST(SUBSTRING(erp_stock.commodity_serial_number, -4) AS SIGNED)") var count int64 if err := qs.Count(&count).Error; err != nil { //logger.Error("count err:", err) return resp, err } var commodities []ErpStock err := qs.Offset(page * m.PageSize).Limit(m.PageSize).Find(&commodities).Error if err != nil && !errors.Is(err, RecordNotFound) { //logger.Error("erp commodity list err:", err) return resp, err } resp.List = commodities //跟之前保持一致 resp.Total = int(count) resp.PageIndex = page + 1 resp.PageSize = m.PageSize return resp, nil } // StockList // 1、如果筛选条件有:库存情况筛选;则以库存表为准,查库存表即可 // (1)没门店,则查所有库存情况并排序 // (2)有门店,则查门店对应的库存情况并排序 // 2、如果筛选条件没有库存情况, // (1)先查询商品资料,并排序;支持筛选条件:商品编号、商品分类、商品名称 // (2)然后查询每个商品资料的库存情况,没传门店id,则查所有库存;否则查当前门店的库存情况 func (m *ErpStockListReq) StockList() (*ErpStockListResp, error) { switch m.StockType { case 2: // 有库存 return m.stockNoEmptyList() case 3: // 无库存,连表查询商品明细和库存表 return m.stockIsEmptyList() default: // 0和1,以及其他值,表示无库存情况筛选 return m.allCommodityList() } } // stockIsEmptyList 库存列表-无库存查询 // 无库存,要连表查询(商品明细、库存表) func (m *ErpStockListReq) stockIsEmptyList() (*ErpStockListResp, error) { resp := &ErpStockListResp{ PageIndex: m.PageIndex, PageSize: m.PageSize, } page := m.PageIndex - 1 if page < 0 { page = 0 } if m.PageSize == 0 { m.PageSize = 10 } //以下代码组合了2个连表查询 //(1)查询无库存的商品资料 /*** SELECT erp_commodity.*, COALESCE(erp_stock.count, 0) AS count FROM erp_commodity LEFT JOIN ( SELECT erp_commodity_id, SUM(count) AS count FROM erp_stock GROUP BY erp_commodity_id ) erp_stock ON erp_commodity.id = erp_stock.erp_commodity_id WHERE erp_stock.count IS NULL OR erp_stock.count = 0; ***/ //(2)按商品编号排序 /*** SELECT e.* FROM erp_stock e JOIN erp_category c ON e.erp_category_id = c.id ORDER BY CASE WHEN c.pid = 0 THEN CAST(c.number AS SIGNED) ELSE CAST(SUBSTRING(c.number, 1, 3) AS SIGNED) END, CAST(c.pid AS SIGNED), CAST(SUBSTRING(e.commodity_serial_number, -4) AS SIGNED); ***/ qs := orm.Eloquent.Debug().Table("erp_commodity") if m.StoreId != 0 { // 传门店id qs = qs.Select("erp_commodity.*, COALESCE(erp_stock.count, 0) AS count"). Joins("LEFT JOIN erp_stock ON erp_commodity.id = erp_stock.erp_commodity_id"). Joins("JOIN erp_category c ON erp_commodity.erp_category_id = c.id"). Where("erp_stock.count = 0 AND erp_stock.store_id = ?", m.StoreId). Order("CASE WHEN c.pid = 0 THEN CAST(c.number AS SIGNED) " + "ELSE CAST(SUBSTRING(c.number, 1, 3) AS SIGNED) END, " + "CAST(c.pid AS SIGNED), CAST(SUBSTRING(erp_commodity.serial_number, -4) AS SIGNED)") } else { // 没传门店id,则子查询先求库存表中erp_commodity_id相同的count之和 qs = qs.Select("erp_commodity.*, COALESCE(erp_stock.count, 0) AS count"). Joins("LEFT JOIN (SELECT erp_commodity_id, SUM(count) AS count FROM erp_stock GROUP BY erp_commodity_id) " + "erp_stock ON erp_commodity.id = erp_stock.erp_commodity_id"). Joins("JOIN erp_category c ON erp_commodity.erp_category_id = c.id"). Where("erp_stock.count IS NULL OR erp_stock.count = 0"). Order("CASE WHEN c.pid = 0 THEN CAST(c.number AS SIGNED) " + "ELSE CAST(SUBSTRING(c.number, 1, 3) AS SIGNED) END, " + "CAST(c.pid AS SIGNED), CAST(SUBSTRING(erp_commodity.serial_number, -4) AS SIGNED)") } if m.SerialNumber != "" { qs = qs.Where("erp_commodity.serial_number=?", m.SerialNumber) } if m.CommodityName != "" { qs = qs.Where("erp_commodity.name Like '%" + m.CommodityName + "%'") //qs = qs.Where("name LIKE ?", m.Name) } if m.ErpCategoryId != 0 { qs = qs.Where("erp_commodity.erp_category_id=?", m.ErpCategoryId) } var commodities []ErpCommodity err := qs.Offset(page * m.PageSize).Limit(m.PageSize).Find(&commodities).Error if err != nil && err != RecordNotFound { logger.Error("查询无库存列表失败", logger.Field("err", err)) return nil, err } var count int64 err = qs.Count(&count).Error if err != nil { logger.Error("查询无库存列表数量失败", logger.Field("err", err)) return nil, err } //遍历商品资料,转换为库存列表数据 var stockList []ErpStock for _, commodity := range commodities { var stock ErpStock stock.ErpCommodityId = commodity.ID stock.ErpCommodityName = commodity.Name stock.ErpCategoryId = commodity.ErpCategoryId stock.ErpCategoryName = commodity.ErpCategoryName stock.CommoditySerialNumber = commodity.SerialNumber stock.IMEIType = commodity.IsIMEI stock.RetailPrice = commodity.RetailPrice stock.MinRetailPrice = commodity.MinRetailPrice stock.Count = 0 stock.DispatchCount = 0 stockList = append(stockList, stock) } //跟之前保持一致 resp.Total = int(count) resp.PageIndex = page + 1 resp.PageSize = m.PageSize resp.List = stockList return resp, nil } func (m *ErpStockListReq) stockNoEmptyList() (*ErpStockListResp, error) { resp := &ErpStockListResp{ PageIndex: m.PageIndex, PageSize: m.PageSize, } page := m.PageIndex - 1 if page < 0 { page = 0 } if m.PageSize == 0 { m.PageSize = 10 } /*** //组合查询 SELECT ec.*, COALESCE(SUM(es.count), 0) AS total_count FROM erp_commodity ec LEFT JOIN erp_stock es ON ec.id = es.erp_commodity_id GROUP BY ec.id ORDER BY CASE WHEN ec.erp_category_id IN (SELECT id FROM erp_category WHERE pid = 0) THEN CAST((SELECT number FROM erp_category WHERE id = ec.erp_category_id) AS SIGNED) ELSE CAST(SUBSTRING((SELECT number FROM erp_category WHERE id = (SELECT pid FROM erp_category WHERE id = ec.erp_category_id)), 1, 3) AS SIGNED) END, CAST((SELECT pid FROM erp_category WHERE id = ec.erp_category_id) AS SIGNED), CAST(SUBSTRING(ec.serial_number, -4) AS SIGNED); ***/ qs := orm.Eloquent.Debug().Table("erp_commodity") if m.StoreId == 0 { // 没指定门店,连表查询并计算总数量 qs = qs.Select("erp_commodity.*, COALESCE(SUM(erp_stock.count), 0) AS total_count, erp_stock.id AS erp_stock_id"). Joins("LEFT JOIN erp_stock ON erp_commodity.id = erp_stock.erp_commodity_id AND erp_stock.count != 0"). Group("erp_commodity.id"). Order("CASE WHEN erp_commodity.erp_category_id IN (SELECT id FROM erp_category WHERE pid = 0) " + "THEN CAST((SELECT number FROM erp_category WHERE id = erp_commodity.erp_category_id) AS SIGNED) " + "ELSE CAST(SUBSTRING((SELECT number FROM erp_category WHERE id = (SELECT pid FROM erp_category WHERE id = erp_commodity.erp_category_id)), 1, 3) AS SIGNED) " + "END, CAST((SELECT pid FROM erp_category WHERE id = erp_commodity.erp_category_id) AS SIGNED), " + "CAST(SUBSTRING(erp_commodity.serial_number, -4) AS SIGNED)") } else { // 指定了门店,连表查询指定store_id的count qs = qs.Select("erp_commodity.*, COALESCE(SUM(erp_stock.count), 0) AS total_count, erp_stock.id AS erp_stock_id"). Joins("LEFT JOIN erp_stock ON erp_commodity.id = erp_stock.erp_commodity_id "+ "AND erp_stock.count != 0 AND erp_stock.store_id = ?", m.StoreId). Group("erp_commodity.id"). Order("CASE WHEN erp_category.pid = 0 THEN CAST(erp_category.number AS SIGNED) " + "ELSE CAST(SUBSTRING(erp_category.number, 1, 3) AS SIGNED) END, " + "CAST(erp_category.pid AS SIGNED), " + "CAST(SUBSTRING(erp_commodity.serial_number, -4) AS SIGNED)"). Joins("JOIN erp_category ON erp_commodity.erp_category_id = erp_category.id") } if m.SerialNumber != "" { qs = qs.Where("erp_commodity.serial_number=?", m.SerialNumber) } if m.CommodityName != "" { qs = qs.Where("erp_commodity.name Like '%" + m.CommodityName + "%'") } if m.ErpCategoryId != 0 { qs = qs.Where("erp_commodity.erp_category_id=?", m.ErpCategoryId) } var commodities []struct { ErpCommodity TotalCount int ErpStockId int } err := qs.Offset(page * m.PageSize).Limit(m.PageSize).Find(&commodities).Error if err != nil && err != RecordNotFound { logger.Error("commodityList err", logger.Field("err", err)) return resp, err } var count int64 err = qs.Count(&count).Error if err != nil { logger.Error("commodityList count err", logger.Field("err", err)) return resp, err } //遍历商品资料,转换为库存列表数据 var stockList []ErpStock for _, commodity := range commodities { if commodity.TotalCount != 0 { var stock ErpStock stock.ID = uint32(commodity.ErpStockId) stock.ErpCommodityId = commodity.ID stock.ErpCommodityName = commodity.Name stock.ErpCategoryId = commodity.ErpCategoryId stock.ErpCategoryName = commodity.ErpCategoryName stock.CommoditySerialNumber = commodity.SerialNumber stock.IMEIType = commodity.IsIMEI stock.RetailPrice = commodity.RetailPrice stock.MinRetailPrice = commodity.MinRetailPrice stock.Count = uint32(commodity.TotalCount) stock.DispatchCount = 0 stockList = append(stockList, stock) } } //跟之前保持一致 resp.Total = len(stockList) resp.PageIndex = page + 1 resp.PageSize = m.PageSize resp.List = stockList return resp, nil } // 筛选条件无:库存情况筛选 // (1)先查询商品资料,并排序;支持筛选条件:商品编号、商品分类、商品名称 // (2)然后查询每个商品资料的库存情况,没传门店id,则查所有库存;否则查当前门店的库存情况 func (m *ErpStockListReq) allCommodityList() (*ErpStockListResp, error) { resp := &ErpStockListResp{ PageIndex: m.PageIndex, PageSize: m.PageSize, } page := m.PageIndex - 1 if page < 0 { page = 0 } if m.PageSize == 0 { m.PageSize = 10 } /*** //组合查询 SELECT ec.*, COALESCE(SUM(es.count), 0) AS total_count FROM erp_commodity ec LEFT JOIN erp_stock es ON ec.id = es.erp_commodity_id GROUP BY ec.id ORDER BY CASE WHEN ec.erp_category_id IN (SELECT id FROM erp_category WHERE pid = 0) THEN CAST((SELECT number FROM erp_category WHERE id = ec.erp_category_id) AS SIGNED) ELSE CAST(SUBSTRING((SELECT number FROM erp_category WHERE id = (SELECT pid FROM erp_category WHERE id = ec.erp_category_id)), 1, 3) AS SIGNED) END, CAST((SELECT pid FROM erp_category WHERE id = ec.erp_category_id) AS SIGNED), CAST(SUBSTRING(ec.serial_number, -4) AS SIGNED); ***/ qs := orm.Eloquent.Debug().Table("erp_commodity") if m.SerialNumber != "" { qs = qs.Where("erp_commodity.serial_number=?", m.SerialNumber) } if m.CommodityName != "" { qs = qs.Where("erp_commodity.name Like '%" + m.CommodityName + "%'") } if m.ErpCategoryId != 0 { qs = qs.Where("erp_commodity.erp_category_id=?", m.ErpCategoryId) } if m.StoreId == 0 { // 没指定门店,连表查询并计算总数量 qs = qs.Select("erp_commodity.*, COALESCE(SUM(erp_stock.count), 0) AS total_count, erp_stock.id AS erp_stock_id"). Joins("LEFT JOIN erp_stock ON erp_commodity.id = erp_stock.erp_commodity_id"). Group("erp_commodity.id"). Order("CASE WHEN erp_commodity.erp_category_id IN (SELECT id FROM erp_category WHERE pid = 0) " + "THEN CAST((SELECT number FROM erp_category WHERE id = erp_commodity.erp_category_id) AS SIGNED) " + "ELSE CAST(SUBSTRING((SELECT number FROM erp_category WHERE id = (SELECT pid FROM erp_category WHERE id = erp_commodity.erp_category_id)), 1, 3) AS SIGNED) " + "END, CAST((SELECT pid FROM erp_category WHERE id = erp_commodity.erp_category_id) AS SIGNED), " + "CAST(SUBSTRING(erp_commodity.serial_number, -4) AS SIGNED)") } else { // 指定了门店,连表查询指定store_id的count qs = qs.Select("erp_commodity.*, COALESCE(SUM(erp_stock.count), 0) AS total_count, erp_stock.id AS erp_stock_id"). Joins("LEFT JOIN erp_stock ON erp_commodity.id = erp_stock.erp_commodity_id "+ "AND erp_stock.store_id = ?", m.StoreId). Group("erp_commodity.id"). Order("CASE WHEN erp_category.pid = 0 THEN CAST(erp_category.number AS SIGNED) " + "ELSE CAST(SUBSTRING(erp_category.number, 1, 3) AS SIGNED) END, " + "CAST(erp_category.pid AS SIGNED), " + "CAST(SUBSTRING(erp_commodity.serial_number, -4) AS SIGNED)"). Joins("JOIN erp_category ON erp_commodity.erp_category_id = erp_category.id") } var commodities []struct { ErpCommodity TotalCount int ErpStockId int } err := qs.Offset(page * m.PageSize).Limit(m.PageSize).Find(&commodities).Error if err != nil && err != RecordNotFound { logger.Error("commodityList err", logger.Field("err", err)) return resp, err } var count int64 err = qs.Count(&count).Error if err != nil { logger.Error("commodityList count err", logger.Field("err", err)) return resp, err } //遍历商品资料,转换为库存列表数据 var stockList []ErpStock for _, commodity := range commodities { var stock ErpStock stock.ID = uint32(commodity.ErpStockId) stock.ErpCommodityId = commodity.ID stock.ErpCommodityName = commodity.Name stock.ErpCategoryId = commodity.ErpCategoryId stock.ErpCategoryName = commodity.ErpCategoryName stock.CommoditySerialNumber = commodity.SerialNumber stock.IMEIType = commodity.IsIMEI stock.RetailPrice = commodity.RetailPrice stock.MinRetailPrice = commodity.MinRetailPrice stock.Count = uint32(commodity.TotalCount) stock.DispatchCount = 0 stockList = append(stockList, stock) } //跟之前保持一致 resp.Total = int(count) resp.PageIndex = page + 1 resp.PageSize = m.PageSize resp.List = stockList return resp, nil } func ErpStockCommodityListSetAge(commodities []ErpStockCommodity) { nowTime := time.Now() for i, _ := range commodities { commodities[i].Age = uint32(nowTime.Sub(commodities[i].StockTime).Hours()) / 24 commodities[i].AllAge = uint32(nowTime.Sub(commodities[i].FirstStockTime).Hours()) / 24 } } // ErpStockCommodityListReq 库存详情接口请求参数 type ErpStockCommodityListReq struct { ErpStockId uint32 `json:"erp_stock_id"` // 库存id ErpCommodityId uint32 `json:"erp_commodity_id"` // 商品id SerialNumber string `json:"serial_number"` // 商品编号 CommodityName string `json:"commodity_name"` // 商品名称 ErpCategoryId uint32 `json:"erp_category_id"` // 商品分类Id IsIMEI uint32 `json:"is_imei""` // 是否串码:0-查全部 1-查串码类 2-查非串码 IMEI string `json:"imei"` // 串码 StoreId uint32 `json:"store_id"` // 门店编号 SupplierId uint32 `json:"supplier_id"` // 供应商id State uint32 `json:"state"` // 库存状态:1-在库 2-已售 3-采购退货 4-调拨中 5-出库 Sn string `json:"sn"` // 首次入库订单编号 StorageType uint32 `json:"storage_type"` // 首次入库方式:1-系统入库 2-采购入库 StockTimeStart string `json:"stock_time_start"` // 最近入库开始时间 StockTimeEnd string `json:"stock_time_end"` // 最近入库结束时间 Age uint32 `json:"age"` // 最近库龄 AllAge uint32 `json:"all_age"` // 总库龄 PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 每页展示数据条数 IsExport uint32 `json:"is_export"` // 是否导出excel:1-导出 } // ErpStockCommodityListResp 库存详情接口响应参数 type ErpStockCommodityListResp struct { List []ErpStockCommodity `json:"list"` Total int `json:"count"` // 数据总条数 PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 每页展示条数 TotalWholesalePrice int `json:"total_wholesale_price"` // 入库采购价之和 TotalStaffPrice int `json:"total_staff_price"` // 入库员工成本价之和 ExportUrl string `json:"export_url"` } // GetDetailList 查看库存详情 func (m *ErpStockCommodityListReq) GetDetailList() (*ErpStockCommodityListResp, error) { resp := &ErpStockCommodityListResp{ PageIndex: m.PageIndex, PageSize: m.PageSize, } page := m.PageIndex - 1 if page < 0 { page = 0 } if m.PageSize == 0 { m.PageSize = 10 } if m.ErpStockId != 0 { updateCommodityStock(m.ErpStockId) // 同步详情表的库存id } // 出库数据不查询 qs := orm.Eloquent.Table("erp_stock_commodity").Where("state != 5") // 构建查询条件 m.buildQueryConditions(qs) es := qs var count int64 if err := qs.Count(&count).Error; err != nil { logger.Error("count err:", logger.Field("err", err)) return resp, err } nTotalCount := &struct { TotalWholesalePrice int `json:"total_wholesale_price"` TotalStaffCostPrice int `json:"total_staff_cost_price"` }{} err := es.Debug().Select("SUM(wholesale_price) as total_wholesale_price, SUM(staff_cost_price) as total_staff_cost_price"). Scan(&nTotalCount).Error if err != nil { logger.Error("count err:", logger.Field("err", err)) return resp, err } //获取库存商品列表 var commodities []ErpStockCommodity if m.IsExport == 1 { err := qs.Find(&commodities).Order("id DESC").Error if err != nil && !errors.Is(err, RecordNotFound) { //logger.Error("dailys err:", err) return resp, err } listExport, err := InventoryDetailListExport(commodities) if err != nil { //logger.Error("list export err:", err) } resp.ExportUrl = listExport } else { err := qs.Offset(page * m.PageSize).Limit(m.PageSize).Find(&commodities).Order("id DESC").Error if err != nil && !errors.Is(err, RecordNotFound) { //logger.Error("erp commodity list err:", err) return resp, err } ErpStockCommodityListSetAge(commodities) resp.List = commodities } //跟之前保持一致 resp.Total = int(count) resp.PageIndex = page + 1 resp.PageSize = m.PageSize resp.TotalWholesalePrice = int(nTotalCount.TotalWholesalePrice) resp.TotalStaffPrice = int(nTotalCount.TotalStaffCostPrice + nTotalCount.TotalWholesalePrice) return resp, nil } // buildQueryConditions 根据请求参数构建查询条件 func (m *ErpStockCommodityListReq) buildQueryConditions(qs *gorm.DB) { if m.ErpStockId != 0 { //库存id qs = qs.Where("erp_stock_id=?", m.ErpStockId) } if m.ErpCommodityId != 0 { //商品id qs = qs.Where("erp_commodity_id=?", m.ErpCommodityId) } if m.IsIMEI != 0 { // 是否串码 if m.IsIMEI == 1 { // 查串码数据 qs = qs.Where("imei_type != ?", NoIMEICommodity) } else if m.IsIMEI == 2 { // 查非串码数据 qs = qs.Where("imei_type = ?", NoIMEICommodity) } } if m.SerialNumber != "" { //商品编号 qs = qs.Where("commodity_serial_number=?", m.SerialNumber) } if m.CommodityName != "" { //商品名称 qs = qs.Where("erp_commodity_name LIKE ?", "%"+m.CommodityName+"%") } if m.ErpCategoryId != 0 { //商品分类id qs = qs.Where("erp_category_id=?", m.ErpCategoryId) } if m.IMEI != "" { //商品串码 qs = qs.Where("imei=?", m.IMEI) } if m.StoreId != 0 { //门店编号 qs = qs.Where("store_id=?", m.StoreId) } if m.SupplierId != 0 { //供应商id qs = qs.Where("erp_supplier_id=?", m.SupplierId) } if m.State != 0 { //库存状态 qs = qs.Where("state=?", m.State) } if m.Sn != "" { //首次入库订单编号 qs = qs.Where("stock_sn=?", m.Sn) } if m.StorageType != 0 { //首次入库方式 qs = qs.Where("storage_type=?", m.StorageType) } if m.StockTimeStart != "" { //最近入库开始时间 startTime, err := time.Parse(QueryTimeFormat, m.StockTimeStart) if err == nil { qs = qs.Where("stock_time>?", startTime) } else { //logger.Error("stock time start parse err:", err) } } if m.StockTimeEnd != "" { //最近入库结束时间 endTime, err := time.Parse(QueryTimeFormat, m.StockTimeEnd) if err == nil { qs = qs.Where("stock_time 0 { // 扣减商品库存数量 err = begin.Model(&record).Update("count", record.Count-1).Error if err != nil { logger.Error("扣减库存数量失败", logger.Field("err", err)) return err } } err = begin.Commit().Error if err != nil { begin.Rollback() logger.Error("出库事务失败", logger.Field("err", err)) return err } return nil } type BatchPrintInfo struct { ErpCommodityName string `json:"erp_commodity_name" binding:"required"` // 商品名称 RetailPrice uint32 `json:"retail_price" binding:"required"` // 指导零售价 IMEI string `json:"imei" binding:"required"` // 商品串码 } type BatchPrintInfoReq struct { PrintListInfo []*BatchPrintInfo `json:"print_list_info" binding:"required"` } // BatchPrint 批量打印标签 func BatchPrint(req *BatchPrintInfoReq) error { return nil } func StringToFloat(req string) (float64, error) { if req == "" { return 0, nil } return strconv.ParseFloat(req, 64) } 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 { return &StockImporter{ CensusMap: make(map[uint32]map[uint32]uint32, 0), CommodityMap: make(map[uint32]*ErpCommodity), StoreMap: make(map[uint32]string, 0), 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 }