1、新增零售订单批量导入商品接口

This commit is contained in:
chenlin 2024-11-18 14:06:08 +08:00
parent 3e118b8d5d
commit 17d4131b1d
7 changed files with 528 additions and 0 deletions

View File

@ -3,12 +3,14 @@ package erpordermanage
import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
model "go-admin/app/admin/models"
orm "go-admin/common/global"
"go-admin/logger"
"go-admin/tools"
"go-admin/tools/app"
"io"
"net/http"
"time"
)
@ -656,3 +658,54 @@ func ErpOrderSaleDetail(c *gin.Context) {
app.OK(c, resp, "")
return
}
// ErpOrderBatchImport 批量导入零售数据
// @Summary 批量导入零售数据
// @Tags 零售订单
// @Produce json
// @Accept json
// @Param file body string true "上传excel文件"
// @Success 200 {object} models.ErpOrderBatchImportResp
// @Router /api/v1/erp_order/import [post]
func ErpOrderBatchImport(c *gin.Context) {
file, header, err := c.Request.FormFile("file")
if err != nil {
logger.Error("form file err:", logger.Field("err", err))
app.Error(c, http.StatusInternalServerError, err, "预览失败")
return
}
readAll, err := io.ReadAll(file)
if err != nil {
logger.Error("read all err:", logger.Field("err", err))
app.Error(c, http.StatusInternalServerError, err, "预览失败")
return
}
fmt.Println("header:", header.Filename)
_, colsMap, err := model.FileExcelImport(readAll, nil, 4)
if err != nil {
logger.Errorf("file excel reader err:", err)
app.Error(c, http.StatusInternalServerError, err, err.Error())
return
}
fmt.Println("colsMap:", colsMap)
if len(colsMap) != 0 {
colsMap = colsMap[1:]
}
var orderCommodities []model.ErpOrderCommodity
orderCommodities, err = model.ImportOrderData(colsMap)
if err != nil {
app.Error(c, http.StatusInternalServerError, err, err.Error())
return
}
resp := model.ErpOrderBatchImportResp{
Commodities: orderCommodities,
}
app.OK(c, resp, "导入成功")
return
}

View File

@ -439,6 +439,10 @@ type TableData struct {
IMEI string `json:"imei"` // 串码
}
type ErpOrderBatchImportResp struct {
Commodities []ErpOrderCommodity `json:"commodities"` // 零售订单商品信息
}
// Contains 判断id是否在list中
func Contains(list []uint32, id uint32) bool {
for _, item := range list {

View File

@ -56,6 +56,17 @@ type StockExcel struct {
Count string `json:"count" binding:"required"` // 数量
}
type OrderExcel struct {
SerialNum string `json:"serial_num"` // 商品编号
Name string `json:"name"` // 商品名称
IMEI string `json:"imei"` // 商品串码
Count string `json:"count" binding:"required"` // 销售数量
StoreName string `json:"store_name" binding:"required"` // 所属门店
SalePrice string `json:"sale_price"` // 零售价
PresentType string `json:"present_type"` // 是否赠送
Remark string `json:"remark"` // 备注
}
// 获取struct的tag标签匹配excel导入数据
func getJSONTagNames(s interface{}) []string {
valueType := reflect.TypeOf(s)
@ -208,6 +219,14 @@ func FileExcelImport(d []byte, cols []string, nType int) ([]byte, []map[string]i
return nil, nil, err
}
cols = getJSONTagNames(StockExcel{})
case 4:
if sheetList[0] != "导零售" {
return nil, nil, errors.New("格式错误不是零售模版excel")
}
if err := checkOrderExcel(sheetCols); err != nil {
return nil, nil, err
}
cols = getJSONTagNames(OrderExcel{})
default:
return nil, nil, errors.New("格式错误不是商品分类或资料模版excel")
}
@ -1335,3 +1354,335 @@ func IsExistingCommodity(commodityId uint32) bool {
return count > 0
}
// 校验导入零售excel格式
// 必填项:商品名称、销售数量
// 商品名称和串码是否对应
// 串码商品数量只能为1
// 是否有库存
func checkOrderExcel(sheetCols [][]string) error {
if len(sheetCols) != 8 {
return errors.New("模版错误,请检查文件")
}
maxLength := findMaxLength(sheetCols)
nLow, nMax := determineLowAndMax(sheetCols)
if nMax < maxLength {
return errors.New("第" + strconv.Itoa(nMax+1) + "行商品名称和商品编号不能同时为空")
}
// 判断是否有重复串码
if duplicateName, nFlag := hasDuplicateIMEI(sheetCols[2]); nFlag {
return fmt.Errorf("商品串码不允许重复,请检查:[%v]", duplicateName)
}
//先遍历第1列
for i := 1; i < nLow; i++ {
if sheetCols[0][i] == "" && sheetCols[1][i] == "" { // 商品名称和编号不能都为空
return errors.New("第" + strconv.Itoa(i+1) + "行商品名称和商品编号不能同时为空")
}
}
for i := 1; i < nMax; i++ {
if i < len(sheetCols[1]) {
if !IsExistingProduct(sheetCols[1][i]) {
return errors.New("第" + strconv.Itoa(i+1) + "行商品不存在")
}
}
// 商品编号必须为纯数字
if i < len(sheetCols[0]) {
if sheetCols[0][i] != "" {
if _, err := strconv.Atoi(sheetCols[0][i]); err != nil {
return errors.New("第" + strconv.Itoa(i+1) + "行商品编号必须为纯数字")
}
}
if !isExistingProductCode(sheetCols[0][i]) {
return errors.New("第" + strconv.Itoa(i+1) + "行商品编号不存在")
}
}
// 所属门店不能为空
if i < len(sheetCols[4]) {
if sheetCols[4][i] == "" {
return errors.New("第" + strconv.Itoa(i+1) + "行所属门店不能为空")
}
if !isExistingStore(sheetCols[4][i]) {
return errors.New("第" + strconv.Itoa(i+1) + "行门店不存在")
}
}
// 数量必须为正整数
if i < len(sheetCols[3]) {
if quantity, err := strconv.Atoi(sheetCols[3][i]); err != nil || quantity < 1 {
return errors.New("第" + strconv.Itoa(i+1) + "行数量必须是大于等于1的整数")
}
}
// 串码商品数量只能为1
var nCount int
if i < len(sheetCols[3]) {
if i < len(sheetCols[3]) {
nCount, _ = strconv.Atoi(sheetCols[3][i])
}
// 串码类商品数量只能为1
if sheetCols[2][i] != "" && nCount != 1 {
return errors.New("第" + strconv.Itoa(i+1) + "行串码类商品数量只能为1")
}
}
// 判断商品是否赠送
if i < len(sheetCols[6]) {
if sheetCols[6][i] != "是" && sheetCols[6][i] != "" {
return errors.New("第" + strconv.Itoa(i+1) + "行商品是否赠送填写有误")
}
}
// 查询商品是否有库存
if sheetCols[2][i] != "" { // 串码商品
var imeiStockCommodity ErpStockCommodity
err := orm.Eloquent.Table("erp_stock_commodity").Where("imei = ? and state = ?",
sheetCols[2][i], InStock).
Find(&imeiStockCommodity).Error
if err != nil {
return err
}
if imeiStockCommodity.ID == 0 {
return errors.New("第" + strconv.Itoa(i+1) + "行商品不存在")
}
// 门店对比
if imeiStockCommodity.StoreName != sheetCols[4][i] {
return errors.New("第" + strconv.Itoa(i+1) + "行商品" +
"[" + imeiStockCommodity.ErpCommodityName + "]非所选门店库存,请检查")
}
// 零售价对比
var floatVal float64
if i < len(sheetCols[5]) {
if sheetCols[5][i] != "" {
floatVal, err = strconv.ParseFloat(sheetCols[5][i], 64)
if err != nil {
return errors.New("第" + strconv.Itoa(i+1) + "行零售价有误")
}
strMin := strconv.FormatFloat(imeiStockCommodity.MinRetailPrice, 'f', 2, 64)
if floatVal < imeiStockCommodity.MinRetailPrice {
return errors.New("第" + strconv.Itoa(i+1) + "行零售价有误,不能低于最低零售价[" + strMin + "]")
}
}
}
} else { // 非串码商品
var count int64
var err error
if sheetCols[0][i] != "" { // 商品编号不为空
err = orm.Eloquent.Table("erp_stock_commodity").
Where("commodity_serial_number = ? and store_name = ? and state = ? and imei_type = ?",
sheetCols[0][i], sheetCols[4][i], InStock, NoIMEICommodity).
Count(&count).Error
} else {
err = orm.Eloquent.Table("erp_stock_commodity").
Where("erp_commodity_name = ? and store_name = ? and state = ? and imei_type = ?",
sheetCols[1][i], sheetCols[4][i], InStock, NoIMEICommodity).
Count(&count).Error
}
if err != nil {
return errors.New("第" + strconv.Itoa(i+1) + "行商品不存在")
}
if count < int64(nCount) {
// 获取商品名称
return errors.New("第" + strconv.Itoa(i+1) + "行商品" + "[" + sheetCols[1][i] + "]库存不足")
}
}
}
return nil
}
// 将读取的excel数据转换成OrderExcel struct
func transOrderData(colsMap []map[string]interface{}) ([]OrderExcel, error) {
var stockInfos []OrderExcel
// 遍历 colsMap 进行类型断言和转换
for _, col := range colsMap {
var stockInfo OrderExcel
// 将 col 转换为 JSON 字符串
jsonData, err := json.Marshal(col)
if err != nil {
return nil, fmt.Errorf("failed to marshal data to JSON: %v", err)
}
// 将 JSON 字符串反序列化为 CommodityExcel
if err := json.Unmarshal(jsonData, &stockInfo); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON to StockExcel: %v", err)
}
// 将处理后的数据追加到 commodities 中
stockInfos = append(stockInfos, stockInfo)
}
return stockInfos, nil
}
func ImportOrderData(colsMap []map[string]interface{}) ([]ErpOrderCommodity, error) {
list, err := transOrderData(colsMap)
if err != nil {
return nil, err
}
var orderCommodities []ErpOrderCommodity
for i, _ := range list {
var orderCommodity ErpOrderCommodity
// 商品数量
var nCount int
nCount, _ = strconv.Atoi(list[i].Count)
// 判断商品是否赠送
var nPresentType uint32
nPresentType = 1
if i < len(list[i].PresentType) {
if list[i].PresentType == "是" {
nPresentType = 2
}
}
// 查询商品是否有库存
if list[i].IMEI != "" { // 串码商品
var imeiStockCommodity ErpStockCommodity
err = orm.Eloquent.Table("erp_stock_commodity").Where("imei = ? and state = ?",
list[i].IMEI, InStock).
Find(&imeiStockCommodity).Error
if err != nil {
return nil, err
}
if imeiStockCommodity.ID == 0 {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在")
}
// 门店对比
if imeiStockCommodity.StoreName != list[i].StoreName {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品" +
"[" + imeiStockCommodity.ErpCommodityName + "]非所选门店库存,请检查")
}
// 零售价对比
var floatVal float64
if list[i].SalePrice != "" {
floatVal, err = strconv.ParseFloat(list[i].SalePrice, 64)
if err != nil {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误")
}
strMin := strconv.FormatFloat(imeiStockCommodity.MinRetailPrice, 'f', 2, 64)
if floatVal < imeiStockCommodity.MinRetailPrice {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误,不能低于最低零售价[" + strMin + "]")
}
} else {
floatVal = imeiStockCommodity.RetailPrice
}
orderCommodity.ErpCategoryId = imeiStockCommodity.ErpCategoryId
orderCommodity.ErpCategoryName = imeiStockCommodity.ErpCategoryName
orderCommodity.ErpCommodityId = imeiStockCommodity.ErpCommodityId
orderCommodity.ErpCommodityName = imeiStockCommodity.ErpCommodityName
orderCommodity.ErpSupplierId = imeiStockCommodity.ErpSupplierId
orderCommodity.ErpSupplierName = imeiStockCommodity.ErpSupplierName
orderCommodity.IMEIType = imeiStockCommodity.IMEIType
orderCommodity.IMEI = imeiStockCommodity.IMEI
orderCommodity.PresentType = nPresentType
orderCommodity.RetailPrice = imeiStockCommodity.RetailPrice
orderCommodity.SalePrice = floatVal
orderCommodity.ReceivedAmount = floatVal
orderCommodity.SaleDiscount = floatVal - imeiStockCommodity.RetailPrice
orderCommodity.WholesalePrice = imeiStockCommodity.WholesalePrice
orderCommodity.StaffCostPrice = imeiStockCommodity.StaffCostPrice
orderCommodity.MemberDiscount = imeiStockCommodity.MemberDiscount
orderCommodity.SalesProfit = floatVal - imeiStockCommodity.WholesalePrice
orderCommodity.StaffProfit = floatVal - imeiStockCommodity.StaffCostPrice
orderCommodity.Count = int32(nCount)
orderCommodity.Remark = list[i].Remark
} else { // 非串码商品
var count int64
var imeiStockCommodities []ErpStockCommodity
if list[i].SerialNum != "" { // 商品编号不为空
err = orm.Eloquent.Table("erp_stock_commodity").
Where("commodity_serial_number = ? and store_name = ? and state = ? and imei_type = ?",
list[i].SerialNum, list[i].StoreName, InStock, NoIMEICommodity).
Count(&count).Error
} else {
err = orm.Eloquent.Table("erp_stock_commodity").
Where("erp_commodity_name = ? and store_name = ? and state = ? and imei_type = ?",
list[i].Name, list[i].StoreName, InStock, NoIMEICommodity).
Count(&count).Error
}
if err != nil {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在")
}
if count < int64(nCount) {
// 获取商品名称
return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品" + "[" + list[i].Name + "]库存不足")
}
if list[i].SerialNum != "" { // 商品编号不为空
err = orm.Eloquent.Table("erp_stock_commodity").
Where("commodity_serial_number = ? and store_name = ? and state = ? and imei_type = ?",
list[i].SerialNum, list[i].StoreName, InStock, NoIMEICommodity).
Find(&imeiStockCommodities).Order("first_stock_time DESC").Error
} else {
err = orm.Eloquent.Table("erp_stock_commodity").
Where("erp_commodity_name = ? and store_name = ? and state = ? and imei_type = ?",
list[i].Name, list[i].StoreName, InStock, NoIMEICommodity).
Find(&imeiStockCommodities).Order("first_stock_time DESC").Error
}
if err != nil {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在")
}
if len(imeiStockCommodities) == 0 {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在")
}
// 零售价对比
var floatVal float64
if list[i].SalePrice != "" {
floatVal, err = strconv.ParseFloat(list[i].SalePrice, 64)
if err != nil {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误")
}
strMin := strconv.FormatFloat(imeiStockCommodities[0].MinRetailPrice, 'f', 2, 64)
if floatVal < imeiStockCommodities[0].MinRetailPrice {
return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误,不能低于最低零售价[" + strMin + "]")
}
} else {
floatVal = imeiStockCommodities[0].RetailPrice
}
orderCommodity.ErpCategoryId = imeiStockCommodities[0].ErpCategoryId
orderCommodity.ErpCategoryName = imeiStockCommodities[0].ErpCategoryName
orderCommodity.ErpCommodityId = imeiStockCommodities[0].ErpCommodityId
orderCommodity.ErpCommodityName = imeiStockCommodities[0].ErpCommodityName
orderCommodity.ErpSupplierId = imeiStockCommodities[0].ErpSupplierId
orderCommodity.ErpSupplierName = imeiStockCommodities[0].ErpSupplierName
orderCommodity.IMEIType = imeiStockCommodities[0].IMEIType
orderCommodity.IMEI = imeiStockCommodities[0].IMEI
orderCommodity.PresentType = nPresentType
orderCommodity.RetailPrice = imeiStockCommodities[0].RetailPrice
orderCommodity.SalePrice = floatVal
orderCommodity.ReceivedAmount = floatVal
orderCommodity.SaleDiscount = floatVal - imeiStockCommodities[0].RetailPrice
orderCommodity.WholesalePrice = imeiStockCommodities[0].WholesalePrice
orderCommodity.StaffCostPrice = imeiStockCommodities[0].StaffCostPrice
orderCommodity.MemberDiscount = imeiStockCommodities[0].MemberDiscount
orderCommodity.SalesProfit = floatVal - imeiStockCommodities[0].WholesalePrice
orderCommodity.StaffProfit = floatVal - imeiStockCommodities[0].StaffCostPrice
orderCommodity.Count = int32(nCount)
orderCommodity.Remark = list[i].Remark
}
orderCommodities = append(orderCommodities, orderCommodity)
}
return orderCommodities, nil
}

View File

@ -25,4 +25,5 @@ func registerErpOrderManageRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJW
r.POST("show_all_data", erpordermanage.ErpOrderShowAllData) // 展示所有订单
r.POST("daily_report", erpordermanage.ErpOrderDailyReport) // 经营日报表
r.POST("sale_detail", erpordermanage.ErpOrderSaleDetail) // 查询销售明细
r.POST("import", erpordermanage.ErpOrderBatchImport) // 批量导入零售数据
}

View File

@ -1943,6 +1943,39 @@ const docTemplate = `{
}
}
},
"/api/v1/erp_order/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/models.ErpOrderBatchImportResp"
}
}
}
}
},
"/api/v1/erp_order/list": {
"post": {
"consumes": [
@ -9455,6 +9488,18 @@ const docTemplate = `{
}
}
},
"models.ErpOrderBatchImportResp": {
"type": "object",
"properties": {
"commodities": {
"description": "零售订单商品信息",
"type": "array",
"items": {
"$ref": "#/definitions/models.ErpOrderCommodity"
}
}
}
},
"models.ErpOrderCashier": {
"type": "object",
"properties": {

View File

@ -1932,6 +1932,39 @@
}
}
},
"/api/v1/erp_order/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/models.ErpOrderBatchImportResp"
}
}
}
}
},
"/api/v1/erp_order/list": {
"post": {
"consumes": [
@ -9444,6 +9477,18 @@
}
}
},
"models.ErpOrderBatchImportResp": {
"type": "object",
"properties": {
"commodities": {
"description": "零售订单商品信息",
"type": "array",
"items": {
"$ref": "#/definitions/models.ErpOrderCommodity"
}
}
}
},
"models.ErpOrderCashier": {
"type": "object",
"properties": {

View File

@ -2268,6 +2268,14 @@ definitions:
- bill_sn
- state
type: object
models.ErpOrderBatchImportResp:
properties:
commodities:
description: 零售订单商品信息
items:
$ref: '#/definitions/models.ErpOrderCommodity'
type: array
type: object
models.ErpOrderCashier:
properties:
amount:
@ -9779,6 +9787,27 @@ paths:
summary: 编辑零售订单
tags:
- 零售订单
/api/v1/erp_order/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/models.ErpOrderBatchImportResp'
summary: 批量导入零售数据
tags:
- 零售订单
/api/v1/erp_order/list:
post:
consumes: