387 lines
13 KiB
Go
387 lines
13 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"psi/config"
|
|
"psi/database"
|
|
"psi/models"
|
|
"psi/utils"
|
|
"strconv"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type WangdianPurchaseInfo struct {
|
|
ProviderNo string `json:"provider_no"`
|
|
WarehouseNo string `json:"warehouse_no"`
|
|
OuterNo string `json:"outer_no"`
|
|
IsUseOuterNo int8 `json:"is_use_outer_no,omitempty"`
|
|
IsCheck int8 `json:"is_check,omitempty"`
|
|
Contact string `json:"contact,omitempty"`
|
|
PurchaseName string `json:"purchase_name,omitempty"`
|
|
Telno string `json:"telno,omitempty"`
|
|
ReceiveAddress string `json:"receive_address,omitempty"`
|
|
LogisticsType int8 `json:"logistics_type,omitempty"` // 暂未使用 运货方式(物流公司)
|
|
ExpectArriveTime string `json:"expect_arrive_time,omitempty"`
|
|
Remark string `json:"remark,omitempty"`
|
|
OtherFee float64 `json:"other_fee,omitempty"` // 暂未使用 其他费用
|
|
PostFee float64 `json:"post_fee,omitempty"` // 暂未使用 邮资
|
|
Prop1 string `json:"prop1,omitempty"` // 暂未使用 自定义属性1
|
|
Prop2 string `json:"prop2,omitempty"` // 暂未使用 自定义属性2
|
|
DetailsList []WangdianPurchaseDetailItem `json:"details_list"`
|
|
}
|
|
|
|
type WangdianPurchaseDetailItem struct {
|
|
SpecNo string `json:"spec_no"`
|
|
Num float64 `json:"num"`
|
|
Price float64 `json:"price"`
|
|
Discount float64 `json:"discount,omitempty"`
|
|
Tax float64 `json:"tax,omitempty"` //暂未使用 税率
|
|
TaxPrice float64 `json:"tax_price,omitempty"` //暂未使用 税后单价
|
|
TaxAmount float64 `json:"tax_amount,omitempty"` //暂未使用 税后金额
|
|
Remark string `json:"remark,omitempty"`
|
|
Prop1 string `json:"prop1,omitempty"` // 暂未使用 自定义属性1
|
|
Prop2 string `json:"prop2,omitempty"` // 暂未使用 自定义属性2
|
|
|
|
}
|
|
|
|
type WangdianPurchasePushResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type WangdianProviderQueryResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
TotalCount int `json:"total_count"`
|
|
ProviderList []map[string]interface{} `json:"provider_list"`
|
|
}
|
|
|
|
type WangdianWarehouseQueryResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
TotalCount int `json:"total_count"`
|
|
Warehouses []map[string]interface{} `json:"warehouses"`
|
|
}
|
|
|
|
func wangdianURL(sandboxURL, productionURL string) string {
|
|
cfg := config.AppConfig.Wangdian
|
|
if cfg.Sandbox && sandboxURL != "" {
|
|
return sandboxURL
|
|
}
|
|
if productionURL != "" {
|
|
return productionURL
|
|
}
|
|
return productionURL
|
|
}
|
|
|
|
// QueryProvider 查询旺店通供应商
|
|
func QueryProvider(column, providerNo, providerName string, pageSize, pageNo int) (*WangdianProviderQueryResponse, error) {
|
|
cfg := config.AppConfig.Wangdian
|
|
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
|
|
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
|
|
}
|
|
|
|
params := map[string]string{
|
|
"sid": cfg.Sid,
|
|
"appkey": cfg.AppKey,
|
|
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
|
|
}
|
|
if column != "" {
|
|
params["column"] = column
|
|
}
|
|
if providerNo != "" {
|
|
params["provider_no"] = providerNo
|
|
}
|
|
if providerName != "" {
|
|
params["provider_name"] = providerName
|
|
}
|
|
if pageSize > 0 {
|
|
params["page_size"] = strconv.Itoa(pageSize)
|
|
}
|
|
if pageNo > 0 {
|
|
params["page_no"] = strconv.Itoa(pageNo)
|
|
}
|
|
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
|
|
|
|
url := wangdianURL(cfg.ProviderQuerySandbox, cfg.ProviderQueryURL)
|
|
if url == "" {
|
|
url = "https://api.wangdian.cn/openapi2/purchase_provider_query.php"
|
|
}
|
|
|
|
timeout := cfg.Timeout
|
|
if timeout <= 0 {
|
|
timeout = 30
|
|
}
|
|
|
|
log.Printf("[旺店通供应商查询] 请求URL: %s (sandbox=%v), 参数: sid=%s appkey=%s timestamp=%s sign=%s", url, cfg.Sandbox, cfg.Sid, cfg.AppKey, params["timestamp"], params["sign"])
|
|
|
|
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询请求失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[旺店通供应商查询] 返回信息: %s", string(respBody))
|
|
|
|
var resp WangdianProviderQueryResponse
|
|
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
|
|
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// QueryWarehouse 查询旺店通仓库
|
|
func QueryWarehouse(warehouseNo string, warehouseType int, subType int, pageSize, pageNo int, isDisabled string) (*WangdianWarehouseQueryResponse, error) {
|
|
cfg := config.AppConfig.Wangdian
|
|
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
|
|
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
|
|
}
|
|
|
|
params := map[string]string{
|
|
"sid": cfg.Sid,
|
|
"appkey": cfg.AppKey,
|
|
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
|
|
}
|
|
if warehouseNo != "" {
|
|
params["warehouse_no"] = warehouseNo
|
|
}
|
|
if pageSize > 0 {
|
|
params["page_size"] = strconv.Itoa(pageSize)
|
|
}
|
|
if pageNo > 0 {
|
|
params["page_no"] = strconv.Itoa(pageNo)
|
|
}
|
|
if isDisabled != "" {
|
|
params["is_disabled"] = isDisabled
|
|
}
|
|
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
|
|
|
|
url := wangdianURL(cfg.WarehouseQuerySandbox, cfg.WarehouseQueryURL)
|
|
if url == "" {
|
|
url = "https://api.wangdian.cn/openapi2/warehouse_query.php"
|
|
}
|
|
|
|
timeout := cfg.Timeout
|
|
if timeout <= 0 {
|
|
timeout = 30
|
|
}
|
|
|
|
log.Printf("[旺店通仓库查询] 请求URL: %s (sandbox=%v), 参数: sid=%s appkey=%s timestamp=%s sign=%s", url, cfg.Sandbox, cfg.Sid, cfg.AppKey, params["timestamp"], params["sign"])
|
|
|
|
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询请求失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[旺店通仓库查询] 返回信息: %s", string(respBody))
|
|
|
|
var resp WangdianWarehouseQueryResponse
|
|
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
|
|
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
type WangdianGoodsQueryResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
TotalCount int `json:"total_count"`
|
|
GoodsList []map[string]interface{} `json:"goods_list"`
|
|
}
|
|
|
|
// QueryGoods 查询旺店通商品
|
|
func QueryGoods(specNo, goodsNo, brandNo, className, barcode, startTime, endTime string, deleted int, pageSize, pageNo int) (*WangdianGoodsQueryResponse, error) {
|
|
cfg := config.AppConfig.Wangdian
|
|
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
|
|
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
|
|
}
|
|
|
|
params := map[string]string{
|
|
"sid": cfg.Sid,
|
|
"appkey": cfg.AppKey,
|
|
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
|
|
}
|
|
if specNo != "" {
|
|
params["spec_no"] = specNo
|
|
}
|
|
if goodsNo != "" {
|
|
params["goods_no"] = goodsNo
|
|
}
|
|
if brandNo != "" {
|
|
params["brand_no"] = brandNo
|
|
}
|
|
if className != "" {
|
|
params["class_name"] = className
|
|
}
|
|
if barcode != "" {
|
|
params["barcode"] = barcode
|
|
}
|
|
if startTime != "" {
|
|
params["start_time"] = startTime
|
|
}
|
|
if endTime != "" {
|
|
params["end_time"] = endTime
|
|
}
|
|
params["page_size"] = strconv.Itoa(pageSize)
|
|
params["page_no"] = strconv.Itoa(pageNo)
|
|
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
|
|
|
|
url := wangdianURL(cfg.GoodsQuerySandbox, cfg.GoodsQueryURL)
|
|
if url == "" {
|
|
url = "https://api.wangdian.cn/openapi2/goods_query.php"
|
|
}
|
|
|
|
timeout := cfg.Timeout
|
|
if timeout <= 0 {
|
|
timeout = 30
|
|
}
|
|
|
|
log.Printf("[旺店通商品查询] 请求URL: %s (sandbox=%v), 参数: sid=%s appkey=%s timestamp=%s sign=%s", url, cfg.Sandbox, cfg.Sid, cfg.AppKey, params["timestamp"], params["sign"])
|
|
|
|
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询请求失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[旺店通商品查询] 返回信息: %s", string(respBody))
|
|
|
|
var resp WangdianGoodsQueryResponse
|
|
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
|
|
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
func PushPurchaseOrder(purchaseOrderID int64, db ...*gorm.DB) (*WangdianPurchasePushResponse, error) {
|
|
databaseConn := database.OptionalDB(db...)
|
|
|
|
dbName := databaseConn.Migrator().CurrentDatabase()
|
|
log.Printf("[旺店通推送] 使用数据库: %s", dbName)
|
|
|
|
// 1. 查询采购单
|
|
var po models.PurchaseOrder
|
|
if err := databaseConn.Where("id = ? AND is_del = 0", purchaseOrderID).First(&po).Error; err != nil {
|
|
return nil, fmt.Errorf("采购单不存在: %v", err)
|
|
}
|
|
|
|
// 2. 查询供应商编号(必须)
|
|
var supplier models.Supplier
|
|
if err := databaseConn.Select("code, contact_person, contact_phone, address").
|
|
Where("id = ? AND is_del = 0", po.SupplierID).First(&supplier).Error; err != nil {
|
|
return nil, fmt.Errorf("获取供应商信息失败: %v", err)
|
|
}
|
|
if supplier.Code == "" {
|
|
return nil, fmt.Errorf("供应商编码为空,请先维护供应商档案")
|
|
}
|
|
|
|
// 3. 查询仓库编号(必须)
|
|
var warehouse models.Warehouse
|
|
if err := databaseConn.Select("code, contact_person, contact_phone, address").
|
|
Where("id = ? AND is_del = 0", po.WarehouseID).First(&warehouse).Error; err != nil {
|
|
return nil, fmt.Errorf("获取仓库信息失败: %v", err)
|
|
}
|
|
if warehouse.Code == "" {
|
|
return nil, fmt.Errorf("仓库编码为空,请先维护仓库档案")
|
|
}
|
|
|
|
// 4. 查询采购明细,关联商品条码
|
|
type itemRow struct {
|
|
models.PurchaseOrderItem
|
|
Barcode string `gorm:"column:barcode"`
|
|
}
|
|
var items []itemRow
|
|
if err := databaseConn.Table("purchase_order_item").
|
|
Select("purchase_order_item.*, product.barcode").
|
|
Joins("LEFT JOIN product ON purchase_order_item.product_id = product.id AND product.is_del = 0").
|
|
Where("purchase_order_item.purchase_order_id = ? AND purchase_order_item.is_del = 0", purchaseOrderID).
|
|
Debug().Scan(&items).Error; err != nil {
|
|
return nil, fmt.Errorf("获取采购明细失败: %v", err)
|
|
}
|
|
if len(items) == 0 {
|
|
return nil, fmt.Errorf("采购明细为空,无法推送")
|
|
}
|
|
|
|
// 5. 组装 details_list
|
|
detailList := make([]WangdianPurchaseDetailItem, 0, len(items))
|
|
for _, it := range items {
|
|
specNo := it.Barcode
|
|
if specNo == "" {
|
|
specNo = strconv.FormatInt(it.ProductID, 10)
|
|
}
|
|
detailList = append(detailList, WangdianPurchaseDetailItem{
|
|
SpecNo: specNo,
|
|
Num: float64(it.Quantity),
|
|
Price: float64(it.UnitPrice) / 100.0,
|
|
Discount: 1.0,
|
|
})
|
|
}
|
|
|
|
// 6. 组装 purchase_info
|
|
info := WangdianPurchaseInfo{
|
|
ProviderNo: supplier.Code,
|
|
WarehouseNo: warehouse.Code,
|
|
OuterNo: po.PoNo,
|
|
IsUseOuterNo: 0,
|
|
IsCheck: 0,
|
|
Contact: warehouse.ContactPerson,
|
|
PurchaseName: warehouse.ContactPerson,
|
|
Telno: warehouse.ContactPhone,
|
|
ReceiveAddress: warehouse.Address,
|
|
Remark: po.Remark,
|
|
DetailsList: detailList,
|
|
}
|
|
if po.ExpectedArrivalDate > 0 {
|
|
info.ExpectArriveTime = time.Unix(po.ExpectedArrivalDate, 0).Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
infoBytes, err := json.Marshal(info)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("序列化采购信息失败: %v", err)
|
|
}
|
|
|
|
// 7. 校验配置
|
|
cfg := config.AppConfig.Wangdian
|
|
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
|
|
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
|
|
}
|
|
|
|
// 8. 组装公共请求参数 + 计算签名
|
|
timestamp := time.Now().Unix()
|
|
params := map[string]string{
|
|
"sid": cfg.Sid,
|
|
"appkey": cfg.AppKey,
|
|
"timestamp": strconv.FormatInt(timestamp, 10),
|
|
"purchase_info": string(infoBytes),
|
|
}
|
|
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
|
|
|
|
// 9. 提交请求
|
|
url := wangdianURL(cfg.SandboxURL, cfg.URL)
|
|
if url == "" {
|
|
url = "https://api.wangdian.cn/openapi2/purchase_order_push.php"
|
|
}
|
|
timeout := cfg.Timeout
|
|
if timeout <= 0 {
|
|
timeout = 30
|
|
}
|
|
|
|
log.Printf("[旺店通推送] 请求URL: %s (sandbox=%v, form-urlencoded), 参数: sid=%s appkey=%s timestamp=%s sign=%s", url, cfg.Sandbox, cfg.Sid, cfg.AppKey, params["timestamp"], params["sign"])
|
|
log.Printf("[旺店通推送] purchase_info: %s", string(infoBytes))
|
|
|
|
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("推送请求失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[旺店通推送] 返回信息: %s", string(respBody))
|
|
|
|
var resp WangdianPurchasePushResponse
|
|
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
|
|
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
|
|
}
|
|
return &resp, nil
|
|
}
|