diff --git a/constants/invoice_html.go b/constants/invoice_html.go index d6181ef1..6e4417a8 100644 --- a/constants/invoice_html.go +++ b/constants/invoice_html.go @@ -1,6 +1,6 @@ package constants -const INVOICE_TEMPLATE = ` +const INVOICE_TEMPLATE_01 = ` @@ -192,29 +192,34 @@ const INVOICE_TEMPLATE = ` Invoice +` + +const INVOICE_TEMPLATE_02 = ` - + - - + + - + - + - +
Bill To:Invoice No. #20220562040Invoice No. #%v
Timmy TurnerDate: 2023/12/04%vDate: %v
North Street%v
London, SE20 3JW%v
United Kingdom%v
+` +const INVOICE_TEMPLATE_03 = ` @@ -223,24 +228,24 @@ const INVOICE_TEMPLATE = ` - - - - - - - - - - - - + %v
Quantity Total
Plastic bowl$01.0020,000 Units$99.00
Paper bag with handlexxxxxxxxxxxxxxx second line$01.0020,000 Units$99.00
+` + +const INVOICE_TEMPLATE_0301 = ` + + %v + %v + %v Units + %v + +` +const INVOICE_TEMPLATE_04 = ` - + @@ -248,21 +253,23 @@ const INVOICE_TEMPLATE = ` - + - + - - + + - - + +
Subtotal$198.00%v
Shipping Fee
Tax$0.00%v
Total$198.00%v
Deposit Requested$99.00%v%v
Deposit Due$99.00%v%v
+` +const INVOICE_TEMPLATE_05 = ` @@ -270,15 +277,18 @@ const INVOICE_TEMPLATE = ` - + - +
Notes:
ICBC%v Thank you for your business !
Account No. :****4589Account No. :%v
+ ` +const INVOICE_TEMPLATE_06 = ` - ` + +` diff --git a/constants/orders.go b/constants/orders.go index 1474574f..5bcfebca 100644 --- a/constants/orders.go +++ b/constants/orders.go @@ -92,7 +92,14 @@ var OrderStatusUserDIRECTMAIL []OrderStatusCode // 订单状态--用户可见--云仓 var OrderStatusUserCLOUDSTORE []OrderStatusCode +// 订单货币 +var OrderCurrencyMessage map[Currency]string + func init() { + // 订单货币 + OrderCurrencyMessage = make(map[Currency]string, 1) + OrderCurrencyMessage[CURRENCYUSD] = "$" + // 订单状态名称 OrderPayStatusMessage = make(map[OrderPayStatusCode]string) OrderPayStatusMessage[ORDER_PAY_STATUS_UNPAIDDEPOSIT] = "Deposit Payment Unpaid" diff --git a/model/gmodel/fs_admin_role_gen.go b/model/gmodel/fs_admin_role_gen.go index 498a4a09..79b28060 100644 --- a/model/gmodel/fs_admin_role_gen.go +++ b/model/gmodel/fs_admin_role_gen.go @@ -11,7 +11,7 @@ type FsAdminRole struct { RolePid *int64 `gorm:"default:0;" json:"role_pid"` // 上级角色 RoleName *string `gorm:"unique_key;default:'';" json:"role_name"` // DataAuthType *int64 `gorm:"default:1;" json:"data_auth_type"` // 数据权限类型 - DataAuth *string `gorm:"default:'';" json:"data_auth"` // + DataAuth *[]byte `gorm:"default:'';" json:"data_auth"` // Status *int64 `gorm:"default:2;" json:"status"` // 状态:1=启用,2=停用 Remark *string `gorm:"default:'';" json:"remark"` // Sort *int64 `gorm:"default:0;" json:"sort"` // 排序权重 diff --git a/model/gmodel/fs_department_gen.go b/model/gmodel/fs_department_gen.go index 7ce37951..58c40c2a 100644 --- a/model/gmodel/fs_department_gen.go +++ b/model/gmodel/fs_department_gen.go @@ -11,6 +11,7 @@ type FsDepartment struct { Status *int64 `gorm:"default:0;" json:"status"` // 状态 1正常0停用 Ctime *int64 `gorm:"default:0;" json:"ctime"` // 添加时间 ParentId *int64 `gorm:"default:0;" json:"parent_id"` // 父级id + Manager *int64 `gorm:"default:0;" json:"manager"` // 负责人 } type FsDepartmentModel struct { db *gorm.DB diff --git a/model/gmodel/fs_font_gen.go b/model/gmodel/fs_font_gen.go index 02f5b748..15bb89de 100644 --- a/model/gmodel/fs_font_gen.go +++ b/model/gmodel/fs_font_gen.go @@ -7,9 +7,9 @@ import ( // fs_font 字体配置 type FsFont struct { Id int64 `gorm:"primary_key;default:0;auto_increment;" json:"id"` // id - Title *string `gorm:"default:'';" json:"title"` // 字体名字 + Title *string `gorm:"default:'';" json:"title"` // LinuxFontname *string `gorm:"default:'';" json:"linux_fontname"` // linux对应字体名 - FilePath *string `gorm:"default:'';" json:"file_path"` // 字体文件路径 + FilePath *string `gorm:"default:'';" json:"file_path"` // Sort *int64 `gorm:"default:0;" json:"sort"` // 排序 } type FsFontModel struct { diff --git a/model/gmodel/fs_order_gen.go b/model/gmodel/fs_order_gen.go index 3329dc28..1af42fde 100644 --- a/model/gmodel/fs_order_gen.go +++ b/model/gmodel/fs_order_gen.go @@ -25,6 +25,9 @@ type FsOrder struct { PayStatusLink *[]byte `gorm:"default:'';" json:"pay_status_link"` // ShoppingCartSnapshot *[]byte `gorm:"default:'';" json:"shopping_cart_snapshot"` // ShoppingProductSnapshot *[]byte `gorm:"default:'';" json:"shopping_product_snapshot"` // + SaleGerentId *int64 `gorm:"default:0;" json:"sale_gerent_id"` // 销售负责人 + DesignGerentId *int64 `gorm:"default:0;" json:"design_gerent_id"` // 设计负责人 + Scm *[]byte `gorm:"default:'';" json:"scm"` // } type FsOrderModel struct { db *gorm.DB diff --git a/model/gmodel/fs_order_logic.go b/model/gmodel/fs_order_logic.go index 1a5cb01e..b29b3b81 100644 --- a/model/gmodel/fs_order_logic.go +++ b/model/gmodel/fs_order_logic.go @@ -19,9 +19,14 @@ type OrderDetail struct { // 收货地址 type OrderAddress struct { - Address string `json:"address"` // 详细地址 - Mobile string `json:"mobile"` // 手机 - Name string `json:"name"` // 姓名 + Street string `json:"street"` // 详细地址 + City string `json:"city"` // 城市 + FirstName string `json:"first_name"` // 姓 + LastName string `json:"last_name"` // 名 + Mobile string `json:"mobile"` // 手机 + State string `json:"state"` // 州 + Suite string `json:"suite"` // 房号 + ZipCode string `json:"zip_code"` // 邮编号码 } // 订单金额 diff --git a/model/gmodel/fs_order_trade_gen.go b/model/gmodel/fs_order_trade_gen.go index 0e3abc15..52abfc53 100644 --- a/model/gmodel/fs_order_trade_gen.go +++ b/model/gmodel/fs_order_trade_gen.go @@ -25,6 +25,7 @@ type FsOrderTrade struct { Ctime *time.Time `gorm:"default:'0000-00-00 00:00:00';" json:"ctime"` // Utime *time.Time `gorm:"default:'0000-00-00 00:00:00';" json:"utime"` // PayTitle *string `gorm:"default:'';" json:"pay_title"` // + ReceiptSn *string `gorm:"default:'';" json:"receipt_sn"` // } type FsOrderTradeModel struct { db *gorm.DB diff --git a/model/gmodel/fs_product_gen.go b/model/gmodel/fs_product_gen.go index 6185e7a9..f2e5d931 100644 --- a/model/gmodel/fs_product_gen.go +++ b/model/gmodel/fs_product_gen.go @@ -37,6 +37,7 @@ type FsProduct struct { SceneIds *string `gorm:"default:'';" json:"scene_ids"` // IsCustomization *int64 `gorm:"default:0;" json:"is_customization"` // 是否可定制 Unit *string `gorm:"default:'';" json:"unit"` // + SupplyChainManager *int64 `gorm:"default:0;" json:"supply_chain_manager"` // 供应链负责人 } type FsProductModel struct { db *gorm.DB diff --git a/model/gmodel/fs_product_model3d_logic.go b/model/gmodel/fs_product_model3d_logic.go index 92a54a39..ced105c5 100755 --- a/model/gmodel/fs_product_model3d_logic.go +++ b/model/gmodel/fs_product_model3d_logic.go @@ -173,12 +173,7 @@ func (d *FsProductModel3dModel) GetAllByProductIdsTags(ctx context.Context, prod } // 获取每个产品最低价格 -func (d *FsProductModel3dModel) GetProductMinPrice(ctx context.Context, productIds []int64, mapProductMinPrice map[int64]int64) error { - //获取产品模型价格列表 - modelList, err := d.GetAllByProductIdsTags(ctx, productIds, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,product_id,price,tag,part_id,step_price") - if err != nil { - return errors.New("failed to get model list") - } +func (d *FsProductModel3dModel) GetProductMinPrice(modelList []FsProductModel3d, mapProductMinPrice map[int64]int64) error { mapModelMinPrice := make(map[int64]int64) //每个模型/配件存储最小价格 for _, modelInfo := range modelList { @@ -189,7 +184,7 @@ func (d *FsProductModel3dModel) GetProductMinPrice(ctx context.Context, productI continue } var stepPrice StepPriceJsonStruct - if err = json.Unmarshal(*modelInfo.StepPrice, &stepPrice); err != nil { + if err := json.Unmarshal(*modelInfo.StepPrice, &stepPrice); err != nil { return errors.New(fmt.Sprintf("failed to parse model step price:%d", modelInfo.Id)) } lenRange := len(stepPrice.PriceRange) @@ -226,3 +221,11 @@ func (d *FsProductModel3dModel) GetProductMinPrice(ctx context.Context, productI } return nil } +func (d *FsProductModel3dModel) GetAllByProductIdTags(ctx context.Context, productId int64, tags []int64, fields ...string) (resp []FsProductModel3d, err error) { + db := d.db.WithContext(ctx).Model(&FsProductModel3d{}).Where("`product_id`= ? and `tag` in(?) and `status` = ?", productId, tags, 1) + if len(fields) != 0 { + db = db.Select(fields[0]) + } + err = db.Find(&resp).Error + return resp, err +} diff --git a/model/gmodel/fs_product_template_v2_logic.go b/model/gmodel/fs_product_template_v2_logic.go index 1462d7b9..3bacf94b 100755 --- a/model/gmodel/fs_product_template_v2_logic.go +++ b/model/gmodel/fs_product_template_v2_logic.go @@ -18,15 +18,16 @@ func (t *FsProductTemplateV2Model) FindAllByProductIds(ctx context.Context, prod err = db.Find(&resp).Error return resp, err } -func (t *FsProductTemplateV2Model) FindAllByIds(ctx context.Context, ids []int64) (resp []FsProductTemplateV2, err error) { +func (t *FsProductTemplateV2Model) FindAllByIds(ctx context.Context, ids []int64, fields ...string) (resp []FsProductTemplateV2, err error) { if len(ids) == 0 { return } - err = t.db.WithContext(ctx).Model(&FsProductTemplateV2{}).Where("`id` in (?) and `is_del` = ? and `status` = ?", ids, 0, 1).Find(&resp).Error - if err != nil { - return nil, err + db := t.db.WithContext(ctx).Model(&FsProductTemplateV2{}).Where("`id` in (?) and `is_del` = ? and `status` = ?", ids, 0, 1) + if len(fields) != 0 { + db = db.Select(fields[0]) } - return + err = db.Find(&resp).Error + return resp, err } func (t *FsProductTemplateV2Model) FindAllByIdsWithoutStatus(ctx context.Context, ids []int64, fields ...string) (resp []FsProductTemplateV2, err error) { if len(ids) == 0 { @@ -179,3 +180,11 @@ func (t *FsProductTemplateV2Model) FindAllByProductIdsTemplateTag(ctx context.Co err = db.Find(&resp).Error return resp, err } +func (t *FsProductTemplateV2Model) FindAllByFittingIds(ctx context.Context, fittingIds []int64, fields ...string) (resp []FsProductTemplateV2, err error) { + db := t.db.WithContext(ctx).Model(&FsProductTemplateV2{}).Where("`model_id` in(?) and `status` = ? and `is_del` = ? ", fittingIds, 1, 0) + if len(fields) != 0 { + db = db.Select(fields[0]) + } + err = db.Find(&resp).Error + return resp, err +} diff --git a/model/gmodel/fs_user_info_logic.go b/model/gmodel/fs_user_info_logic.go index 0521b912..ce76d7cb 100644 --- a/model/gmodel/fs_user_info_logic.go +++ b/model/gmodel/fs_user_info_logic.go @@ -79,9 +79,9 @@ func (m *FsUserInfoModel) GetProfile(ctx context.Context, pkey string, userId in } return info, nil } -func (m *FsUserInfoModel) FindOneByUser(ctx context.Context, userId, guestId int64) (resp *FsUserInfo, err error) { +func (m *FsUserInfoModel) FindOneByUser(ctx context.Context, userId, guestId int64, module string) (resp *FsUserInfo, err error) { if userId > 0 { - err = m.db.WithContext(ctx).Model(&FsUserInfo{}).Where("user_id = ?", userId).Take(&resp).Error + err = m.db.WithContext(ctx).Model(&FsUserInfo{}).Where("user_id = ? and module = ?", userId, module).Take(&resp).Error } else { err = m.db.WithContext(ctx).Model(&FsUserInfo{}).Where("user_id = ? and guest_id = ?", userId, guestId).Take(&resp).Error } diff --git a/server/order/internal/config/config.go b/server/order/internal/config/config.go index 8ebf0d57..d6bea003 100644 --- a/server/order/internal/config/config.go +++ b/server/order/internal/config/config.go @@ -19,4 +19,13 @@ type Config struct { SuccessURL string } } + AWS struct { + S3 struct { + Credentials struct { + AccessKeyID string + Secret string + Token string + } + } + } } diff --git a/server/order/internal/handler/orderinvoicehandler.go b/server/order/internal/handler/orderinvoicehandler.go new file mode 100644 index 00000000..3d85e89c --- /dev/null +++ b/server/order/internal/handler/orderinvoicehandler.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + "reflect" + + "fusenapi/utils/basic" + + "fusenapi/server/order/internal/logic" + "fusenapi/server/order/internal/svc" + "fusenapi/server/order/internal/types" +) + +func OrderInvoiceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var req types.OrderInvoiceReq + userinfo, err := basic.RequestParse(w, r, svcCtx, &req) + if err != nil { + return + } + + // 创建一个业务逻辑层实例 + l := logic.NewOrderInvoiceLogic(r.Context(), svcCtx) + + rl := reflect.ValueOf(l) + basic.BeforeLogic(w, r, rl) + + resp := l.OrderInvoice(&req, userinfo) + + if !basic.AfterLogic(w, r, rl, resp) { + basic.NormalAfterLogic(w, r, resp) + } + } +} diff --git a/server/order/internal/handler/routes.go b/server/order/internal/handler/routes.go index f3dc8b2b..0df4a436 100644 --- a/server/order/internal/handler/routes.go +++ b/server/order/internal/handler/routes.go @@ -37,6 +37,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/api/order/close", Handler: CloseOrderHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/api/order/invoice", + Handler: OrderInvoiceHandler(serverCtx), + }, { Method: http.MethodGet, Path: "/api/order/list", diff --git a/server/order/internal/logic/createprepaymentbydepositlogic.go b/server/order/internal/logic/createprepaymentbydepositlogic.go index 584c3511..602f37cc 100644 --- a/server/order/internal/logic/createprepaymentbydepositlogic.go +++ b/server/order/internal/logic/createprepaymentbydepositlogic.go @@ -43,16 +43,28 @@ func (l *CreatePrePaymentByDepositLogic) CreatePrePaymentByDeposit(req *types.Cr if req.DeliveryAddress == nil { return resp.SetStatus(basic.CodeErrOrderCreatePrePaymentParam) } else { - if req.DeliveryAddress.Address == "" || req.DeliveryAddress.Mobile == "" || req.DeliveryAddress.Name == "" { + if req.DeliveryAddress.Street == "" || + req.DeliveryAddress.City == "" || + req.DeliveryAddress.Mobile == "" || + req.DeliveryAddress.State == "" || + req.DeliveryAddress.Suite == "" || + req.DeliveryAddress.ZipCode == "" || + req.DeliveryAddress.LastName == "" || + req.DeliveryAddress.FirstName == "" { return resp.SetStatus(basic.CodeErrOrderCreatePrePaymentParam) } } } var orderAddress repositories.OrderAddress if req.DeliveryAddress != nil { - orderAddress.Address = req.DeliveryAddress.Address + orderAddress.Street = req.DeliveryAddress.Street + orderAddress.City = req.DeliveryAddress.City + orderAddress.FirstName = req.DeliveryAddress.FirstName + orderAddress.LastName = req.DeliveryAddress.LastName orderAddress.Mobile = req.DeliveryAddress.Mobile - orderAddress.Name = req.DeliveryAddress.Name + orderAddress.State = req.DeliveryAddress.State + orderAddress.Suite = req.DeliveryAddress.Suite + orderAddress.ZipCode = req.DeliveryAddress.ZipCode } res, err := l.svcCtx.Repositories.NewOrder.CreatePrePaymentByDeposit(l.ctx, &repositories.CreatePrePaymentByDepositReq{ UserId: userinfo.UserId, diff --git a/server/order/internal/logic/orderinvoicelogic.go b/server/order/internal/logic/orderinvoicelogic.go new file mode 100644 index 00000000..8cbb2d11 --- /dev/null +++ b/server/order/internal/logic/orderinvoicelogic.go @@ -0,0 +1,57 @@ +package logic + +import ( + "fusenapi/service/repositories" + "fusenapi/utils/auth" + "fusenapi/utils/basic" + + "context" + + "fusenapi/server/order/internal/svc" + "fusenapi/server/order/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type OrderInvoiceLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewOrderInvoiceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OrderInvoiceLogic { + return &OrderInvoiceLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 处理进入前逻辑w,r +// func (l *OrderInvoiceLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { +// } + +func (l *OrderInvoiceLogic) OrderInvoice(req *types.OrderInvoiceReq, userinfo *auth.UserInfo) (resp *basic.Response) { + // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) + // userinfo 传入值时, 一定不为null + if !userinfo.IsUser() { + // 如果是,返回未授权的错误码 + return resp.SetStatus(basic.CodeUnAuth) + } + res, err := l.svcCtx.Repositories.NewOrder.Invoice(l.ctx, &repositories.InvoiceReq{ + OrderSn: req.OrderSn, + UserId: userinfo.UserId, + }) + if err != nil { + return resp.SetStatus(&res.ErrorCode) + } + + return resp.SetStatus(basic.CodeOK, map[string]interface{}{ + "invoice_urls": res.InvoiceUrls, + }) +} + +// 处理逻辑后 w,r 如:重定向, resp 必须重新处理 +// func (l *OrderInvoiceLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { +// // httpx.OkJsonCtx(r.Context(), w, resp) +// } diff --git a/server/order/internal/svc/servicecontext.go b/server/order/internal/svc/servicecontext.go index e109170c..a1ca8ca9 100644 --- a/server/order/internal/svc/servicecontext.go +++ b/server/order/internal/svc/servicecontext.go @@ -7,6 +7,9 @@ import ( "fusenapi/initalize" "fusenapi/model/gmodel" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" "gorm.io/gorm" ) @@ -20,10 +23,14 @@ type ServiceContext struct { } func NewServiceContext(c config.Config) *ServiceContext { + configAWS := aws.Config{ + Credentials: credentials.NewStaticCredentials(c.AWS.S3.Credentials.AccessKeyID, c.AWS.S3.Credentials.Secret, c.AWS.S3.Credentials.Token), + } conn := initalize.InitMysql(c.SourceMysql) // delayQueue := initalize.InitDelayMessage() repositories := initalize.NewAllRepositories(&initalize.NewAllRepositorieData{ - GormDB: conn, + GormDB: conn, + AwsSession: session.Must(session.NewSession(&configAWS)), // DelayQueue: delayQueue, }) diff --git a/server/order/internal/types/types.go b/server/order/internal/types/types.go index 02480c1b..f2f488e6 100644 --- a/server/order/internal/types/types.go +++ b/server/order/internal/types/types.go @@ -5,6 +5,10 @@ import ( "fusenapi/utils/basic" ) +type OrderInvoiceReq struct { + OrderSn string `form:"order_sn"` +} + type CloseOrderReq struct { OrderSn string `json:"order_sn"` } @@ -28,9 +32,14 @@ type CreatePrePaymentByDepositReq struct { } type DeliveryAddress struct { - Address string `json:"address,optional"` - Name string `json:"name,optional"` - Mobile string `json:"mobile,optional"` + Street string `json:"street,optional"` // 街道 + City string `json:"city,optional"` // 城市 + FirstName string `json:"first_name,optional"` // 姓 + LastName string `json:"last_name,optional"` // 名 + Mobile string `json:"mobile,optional"` // 手机 + State string `json:"state,optional"` // 州 + Suite string `json:"suite,optional"` // 房号 + ZipCode string `json:"zip_code,optional"` // 邮编 } type CreatePrePaymentByBalanceReq struct { diff --git a/server/order/invoice.html b/server/order/invoice.html new file mode 100644 index 00000000..6867e884 --- /dev/null +++ b/server/order/invoice.html @@ -0,0 +1,281 @@ + + + + + + + + Invoice + + + + + + + + + + +
Invoice
+ + + + + + + + + + + + + + + + + + + + + + +
Bill To:Invoice No. #20220562040
Timmy TurnerDate: 2023/12/04
North Street
London, SE20 3JW
United Kingdom
+ + + + + + + + + + + + + + + + + + + + + + +
Product NamePriceQuantityTotal
Plastic bowl$01.0020,000 Units$99.00
Paper bag with handlexxxxxxxxxxxxxxx second line$01.0020,000 Units$99.00
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Subtotal$198.00
Shipping FeeFree
Tax$0.00
Total$198.00
Deposit Requested$99.00
Deposit Due$99.00
+ + + + + + + + + + + + + +
Payment Method:Notes:
ICBCThank you for your business !
Account No. :20000000001
+ + + \ No newline at end of file diff --git a/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go b/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go index 47803452..31e0576f 100644 --- a/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go +++ b/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go @@ -70,7 +70,7 @@ func (l *GetProductTemplateTagsLogic) GetProductTemplateTags(req *types.GetProdu return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "logo info`s metadata is not set") } //获取userInfo信息 - userProfile, err := l.svcCtx.AllModels.FsUserInfo.FindOneByUser(l.ctx, userinfo.UserId, userinfo.GuestId) + userProfile, err := l.svcCtx.AllModels.FsUserInfo.FindOneByUser(l.ctx, userinfo.UserId, userinfo.GuestId, "profile") if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "user profile info is not found") diff --git a/server/product/internal/handler/getproductdetailhandler.go b/server/product/internal/handler/getproductdetailhandler.go new file mode 100644 index 00000000..ef0a1725 --- /dev/null +++ b/server/product/internal/handler/getproductdetailhandler.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + "reflect" + + "fusenapi/utils/basic" + + "fusenapi/server/product/internal/logic" + "fusenapi/server/product/internal/svc" + "fusenapi/server/product/internal/types" +) + +func GetProductDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var req types.GetProductDetailReq + userinfo, err := basic.RequestParse(w, r, svcCtx, &req) + if err != nil { + return + } + + // 创建一个业务逻辑层实例 + l := logic.NewGetProductDetailLogic(r.Context(), svcCtx) + + rl := reflect.ValueOf(l) + basic.BeforeLogic(w, r, rl) + + resp := l.GetProductDetail(&req, userinfo) + + if !basic.AfterLogic(w, r, rl, resp) { + basic.NormalAfterLogic(w, r, resp) + } + } +} diff --git a/server/product/internal/handler/routes.go b/server/product/internal/handler/routes.go index 2d0b468c..589462eb 100644 --- a/server/product/internal/handler/routes.go +++ b/server/product/internal/handler/routes.go @@ -72,6 +72,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/api/product/home_page_recommend", Handler: HomePageRecommendProductListHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/api/product/get_product_detail", + Handler: GetProductDetailHandler(serverCtx), + }, }, ) } diff --git a/server/product/internal/logic/getproductdetaillogic.go b/server/product/internal/logic/getproductdetaillogic.go new file mode 100644 index 00000000..724e6797 --- /dev/null +++ b/server/product/internal/logic/getproductdetaillogic.go @@ -0,0 +1,405 @@ +package logic + +import ( + "encoding/json" + "errors" + "fusenapi/constants" + "fusenapi/model/gmodel" + "fusenapi/utils/auth" + "fusenapi/utils/basic" + "fusenapi/utils/color_list" + "fusenapi/utils/format" + "fusenapi/utils/s3url_to_s3id" + "fusenapi/utils/template_switch_info" + "gorm.io/gorm" + "strings" + + "context" + + "fusenapi/server/product/internal/svc" + "fusenapi/server/product/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetProductDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetProductDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductDetailLogic { + return &GetProductDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 处理进入前逻辑w,r +// func (l *GetProductDetailLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { +// } + +func (l *GetProductDetailLogic) GetProductDetail(req *types.GetProductDetailReq, userinfo *auth.UserInfo) (resp *basic.Response) { + if req.ProductId <= 0 { + return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:product id is invalid") + } + req.TemplateTag = strings.Trim(req.TemplateTag, " ") + if req.TemplateTag == "" { + return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:template tag is invalid") + } + //获取产品信息 + productInfo, err := l.svcCtx.AllModels.FsProduct.FindOne(l.ctx, req.ProductId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "the product is not exists") + } + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product info") + } + if *productInfo.Status != 1 { + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "the product status is unNormal") + } + if *productInfo.IsShelf != 1 { + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "the product status is off shelf") + } + if *productInfo.IsDel == 1 { + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "the product status is deleted") + } + //获取产品类型 + productTag, err := l.svcCtx.AllModels.FsTags.FindOne(l.ctx, *productInfo.Type) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "the product`s tag is not exists") + } + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product tag info") + } + //获取模板标签颜色选择信息 + templateTagColorInfo, err := l.GetTemplateTagColor(req, userinfo) + if err != nil { + return resp.SetStatusWithMessage(basic.CodeServiceErr, err.Error()) + } + //获取产品尺寸列表 + sizeList, err := l.svcCtx.AllModels.FsProductSize.GetAllByProductIds(l.ctx, []int64{req.ProductId}, "is_hot DESC,sort ASC") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get size list") + } + //获取模型+配件信息 + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdTags(l.ctx, req.ProductId, []int64{constants.TAG_MODEL, constants.TAG_PARTS}) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get model list") + } + mapSizeKeyModel := make(map[int64]int) //模型(不包含配件)sizeId为key + mapFitting := make(map[int64]int) //配件 + publicFittingOptionTemplateIds := make([]int64, 0, len(modelList)) //配件配置了公共模板的模板id + notPublicFittingOptionTemplateFittingIds := make([]int64, 0, len(modelList)) //配件没有配置公共模板的配件id + lightIds := make([]int64, 0, len(modelList)) + for k, v := range modelList { + switch *v.Tag { + case constants.TAG_MODEL: //模型的 + mapSizeKeyModel[*v.SizeId] = k + if *v.Light > 0 { + lightIds = append(lightIds, *v.Light) + } + case constants.TAG_PARTS: //配件的 + mapFitting[v.Id] = k + if *v.OptionTemplate > 0 { + publicFittingOptionTemplateIds = append(publicFittingOptionTemplateIds, *v.OptionTemplate) + } else { + notPublicFittingOptionTemplateFittingIds = append(notPublicFittingOptionTemplateFittingIds, v.Id) + } + } + } + //获取没有绑定公共模板的配件贴图 + notPublicFittingOptionTemplateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByFittingIds(l.ctx, notPublicFittingOptionTemplateFittingIds, "id,model_id,material_img") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get fitting material image list") + } + mapNotPublicFittingOptionTemplateMaterialImage := make(map[int64]string) //model_id为key + for _, v := range notPublicFittingOptionTemplateList { + mapNotPublicFittingOptionTemplateMaterialImage[*v.ModelId] = *v.MaterialImg + } + //获取配件绑定的公共模板列表 + publicFittingOptionTemplateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByIds(l.ctx, publicFittingOptionTemplateIds, "id,model_id,material_img") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get fitting optional template list") + } + mapPublicFittingOptionTemplate := make(map[int64]string) //模板id为key + for _, v := range publicFittingOptionTemplateList { + mapPublicFittingOptionTemplate[v.Id] = *v.MaterialImg + } + //获取灯光列表 + lightList, err := l.svcCtx.AllModels.FsProductModel3dLight.GetAllByIds(l.ctx, lightIds) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get light list") + } + mapLight := make(map[int64]int) + for k, v := range lightList { + mapLight[v.Id] = k + } + //获取产品模板列表 + templateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByProductIdsTemplateTag(l.ctx, []int64{req.ProductId}, req.TemplateTag, "") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get template list") + } + mapModelIdKeyTemplate := make(map[int64]int) + for k, v := range templateList { + mapModelIdKeyTemplate[*v.ModelId] = k + } + //记录产品最低价 + mapProductMinPrice := make(map[int64]int64) + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product min price") + } + //获取默认渲染的尺寸 + defaultSize, err := l.getRenderDefaultSize(req.ProductId, req.TemplateTag) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get default size") + } + //整理返回 + rspSizeList := make([]types.SizeInfo, 0, len(sizeList)) + for _, sizeInfo := range sizeList { + var sizeTitle interface{} + if err = json.Unmarshal([]byte(*sizeInfo.Title), &sizeTitle); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse size title") + } + //尺寸下最低价 + minPrice := "" + if price, ok := mapProductMinPrice[*sizeInfo.ProductId]; ok { + minPrice = format.CentitoDollar(price, 3) + } + var modelInfoRsp types.ModelInfo + var TemplateInfoRsp interface{} + var FittingListRsp []types.FittingInfo + if modelIndex, ok := mapSizeKeyModel[sizeInfo.Id]; ok { + modelInfo := modelList[modelIndex] + //模板信息 + if templateIndex, ok := mapModelIdKeyTemplate[modelInfo.Id]; ok { + templateInfo := templateList[templateIndex] + //获取开关信息 + TemplateInfoRsp = template_switch_info.GetTemplateSwitchInfo(templateInfo.Id, templateInfo.TemplateInfo, *templateInfo.MaterialImg) + } + //赋值id + modelInfoRsp.Id = modelInfo.Id + //模型设计信息 + var modelDesignInfo interface{} + if modelInfo.ModelInfo != nil && *modelInfo.ModelInfo != "" { + if err = json.Unmarshal([]byte(*modelInfo.ModelInfo), &modelDesignInfo); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse model design info") + } + //赋值 + modelInfoRsp.ModelDesignInfo = modelDesignInfo + } + //灯光信息 + if lightIndex, ok := mapLight[*modelInfo.Light]; ok { + lightInfo := lightList[lightIndex] + var lightDesignInfo interface{} + if lightInfo.Info != nil && *lightInfo.Info != "" { + if err = json.Unmarshal([]byte(*lightInfo.Info), &lightDesignInfo); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse light design info") + } + //赋值 + modelInfoRsp.LightInfo = types.LightInfo{ + Id: lightInfo.Id, + LightName: *lightInfo.Name, + LightDesignInfo: lightDesignInfo, + } + } + } + optionalFittingIdsStr := strings.Trim(*modelInfo.PartList, " ") + optionalFittingIdsStr = strings.Trim(optionalFittingIdsStr, ",") + //配件信息 + FittingListRsp, err = l.GetModelOptionalFittings(l.ctx, optionalFittingIdsStr, mapFitting, mapPublicFittingOptionTemplate, mapNotPublicFittingOptionTemplateMaterialImage, modelInfo, modelList) + if err != nil { + return resp.SetStatusWithMessage(basic.CodeServiceErr, err.Error()) + } + } + isDefaultSize := int64(0) + if sizeInfo.Id == defaultSize { + isDefaultSize = 1 + } + rspSizeList = append(rspSizeList, types.SizeInfo{ + Id: sizeInfo.Id, + IsDefault: isDefaultSize, + Title: sizeTitle, + Capacity: *sizeInfo.Capacity, + PartsCanDeleted: *sizeInfo.PartsCanDeleted, + IsHot: *sizeInfo.IsHot, + MinPrice: minPrice, + TemplateInfo: TemplateInfoRsp, + ModelInfo: modelInfoRsp, + FittingList: FittingListRsp, + }) + } + return resp.SetStatusWithMessage(basic.CodeOK, "success", types.GetProductDetailRsp{ + TemplateTagColorInfo: templateTagColorInfo, + ProductInfo: types.ProductInfo{ + Id: productInfo.Id, + ProductType: *productInfo.Type, + ProductTypeName: *productTag.Title, + Title: *productInfo.Title, + IsEnv: *productInfo.IsProtection, + IsMicro: *productInfo.IsMicrowave, + IsCustomization: *productInfo.IsCustomization, + }, + BaseColors: color_list.GetColor(), + SizeList: rspSizeList, + }) +} + +// 获取模型可选配件列表 +func (l *GetProductDetailLogic) GetModelOptionalFittings(ctx context.Context, optionalFittingIdsStr string, mapFitting map[int64]int, mapPublicFittingOptionTemplate, mapNotPublicFittingOptionTemplateMaterialImage map[int64]string, modelInfo gmodel.FsProductModel3d, modelList []gmodel.FsProductModel3d) (resp []types.FittingInfo, err error) { + if optionalFittingIdsStr == "" { + return + } + optionalFittingIds, err := format.StrSlicToInt64Slice(strings.Split(optionalFittingIdsStr, ",")) + if err != nil { + logx.Error(err) + return nil, errors.New("failed to split optional fitting list") + } + resp = make([]types.FittingInfo, 0, len(optionalFittingIds)) + //可选配件 + for _, optionFittingId := range optionalFittingIds { + fittingIndex, ok := mapFitting[optionFittingId] + if !ok { + continue + } + fittingInfo := modelList[fittingIndex] + var fittingDesignInfo interface{} + if fittingInfo.ModelInfo != nil && *fittingInfo.ModelInfo != "" { + if err = json.Unmarshal([]byte(*fittingInfo.ModelInfo), &fittingDesignInfo); err != nil { + logx.Error(err) + return nil, errors.New("failed to parse fitting design info") + } + } + //是否默认显示配件 + isDefault := int64(0) + if optionFittingId == *modelInfo.PartId { + isDefault = 1 + } + //配件贴图 + FittingMaterialImg := "" + //贴图,如果绑定了公共模板,则获取公共模板的贴图数据(待优化) + if *fittingInfo.OptionTemplate > 0 { + if image, ok := mapPublicFittingOptionTemplate[*fittingInfo.OptionTemplate]; ok { + FittingMaterialImg = image + } + } else { //否则取该配件下的模板贴图 + if image, ok := mapNotPublicFittingOptionTemplateMaterialImage[fittingInfo.Id]; ok { + FittingMaterialImg = image + } + } + resp = append(resp, types.FittingInfo{ + Id: fittingInfo.Id, + IsHot: *fittingInfo.IsHot, + MaterialImage: FittingMaterialImg, + DesignInfo: fittingDesignInfo, + Price: format.CentitoDollar(*fittingInfo.Price, 3), + Name: *fittingInfo.Name, + IsDefault: isDefault, + }) + } + return resp, nil +} + +// 获取列表页默认渲染的尺寸(同列表页) +func (l *GetProductDetailLogic) getRenderDefaultSize(productId int64, templateTag string) (sizeId int64, err error) { + //获取模板 + productTemplate, err := l.svcCtx.AllModels.FsProductTemplateV2.FindOneCloudRenderByProductIdTemplateTag(l.ctx, productId, templateTag, "sort ASC", "model_id") + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, errors.New("找不到对应开启云渲染模板") + } + logx.Error(err) + return 0, errors.New("获取对应开启云渲染模板失败") + } + //根据模板找到模型 + model3d, err := l.svcCtx.AllModels.FsProductModel3d.FindOne(l.ctx, *productTemplate.ModelId, "size_id") + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, errors.New("找不到对应模型") + } + logx.Error(err) + return 0, errors.New("获取对应模型失败") + } + return *model3d.SizeId, nil +} + +// 获取对应模板标签颜色信息 +func (l *GetProductDetailLogic) GetTemplateTagColor(req *types.GetProductDetailReq, userinfo *auth.UserInfo) (resp types.TemplateTagColorInfo, err error) { + if req.SelectColorIndex < 0 { + return types.TemplateTagColorInfo{}, errors.New("param selected_color_index is invalid") + } + //根据logo查询素材资源 + resourceId := s3url_to_s3id.GetS3ResourceIdFormUrl(req.Logo) + if resourceId == "" { + return types.TemplateTagColorInfo{}, errors.New("param logo is invalid") + } + var ( + userMaterial *gmodel.FsUserMaterial + templateTagInfo *gmodel.FsProductTemplateTags + ) + //获取模板标签信息 + templateTagInfo, err = l.svcCtx.AllModels.FsProductTemplateTags.FindOneByTagName(l.ctx, req.TemplateTag) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return types.TemplateTagColorInfo{}, errors.New("the template tag is not exists") + } + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to get template tag info") + } + userMaterial, err = l.svcCtx.AllModels.FsUserMaterial.FindOneByLogoResourceId(l.ctx, resourceId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return types.TemplateTagColorInfo{}, errors.New("the logo is not found") + } + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to get user material") + } + if userMaterial.Metadata == nil || len(*userMaterial.Metadata) == 0 { + return types.TemplateTagColorInfo{}, errors.New("the user material is empty") + } + //解析用户素材元数据 + var metaData map[string]interface{} + if err = json.Unmarshal(*userMaterial.Metadata, &metaData); err != nil { + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to parse user metadata") + } + var mapMaterialTemplateTag map[string][][]string + b, _ := json.Marshal(metaData["template_tag"]) + if err = json.Unmarshal(b, &mapMaterialTemplateTag); err != nil { + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("invalid format of metadata`s template_tag") + } + colors, ok := mapMaterialTemplateTag[req.TemplateTag] + if !ok { + return types.TemplateTagColorInfo{}, errors.New("the template tag is not found from this logo material`s metadata") + } + if req.SelectColorIndex >= len(colors) { + return types.TemplateTagColorInfo{}, errors.New("select color index is out of range !!") + } + var templateTagGroups interface{} + if templateTagInfo.Groups != nil && *templateTagInfo.Groups != "" { + if err = json.Unmarshal([]byte(*templateTagInfo.Groups), &templateTagGroups); err != nil { + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to parse template tag`s groups info") + } + } + return types.TemplateTagColorInfo{ + Colors: colors, + SelectedColorIndex: req.SelectColorIndex, + TemplateTagGroups: templateTagGroups, + }, nil +} diff --git a/server/product/internal/logic/getrecommandproductlistlogic.go b/server/product/internal/logic/getrecommandproductlistlogic.go index 2877ef95..0debe6e8 100644 --- a/server/product/internal/logic/getrecommandproductlistlogic.go +++ b/server/product/internal/logic/getrecommandproductlistlogic.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" @@ -93,7 +94,12 @@ func (l *GetRecommandProductListLogic) GetRecommandProductList(req *types.GetRec } //获取产品最低价 mapProductMinPrice := make(map[int64]int64) - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, mapProductMinPrice); err != nil { + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, []int64{productInfo.Id}, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get model list") + } + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product min price") } diff --git a/server/product/internal/logic/getsizebypidlogic.go b/server/product/internal/logic/getsizebypidlogic.go index 46d81e4f..d83e4dd7 100644 --- a/server/product/internal/logic/getsizebypidlogic.go +++ b/server/product/internal/logic/getsizebypidlogic.go @@ -74,25 +74,26 @@ func (l *GetSizeByPidLogic) GetSizeByPid(req *types.GetSizeByPidReq, userinfo *a return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get size list") } sizeIds := make([]int64, 0, len(sizeList)) - productIds := make([]int64, 0, len(sizeList)) for _, v := range sizeList { sizeIds = append(sizeIds, v.Id) - productIds = append(productIds, *v.ProductId) } //获取产品价格列表 mapProductMinPrice := make(map[int64]int64) - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, mapProductMinPrice); err != nil { - logx.Error(err) - return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product min price") - } - //获取对应模型数据 - modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllBySizeIdsTag(l.ctx, sizeIds, constants.TAG_MODEL, "id,size_id") + //获取产品模型 + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, []int64{productInfo.Id}, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get model list") } + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product min price") + } mapSizeModel := make(map[int64]int) //size id为key for k, v := range modelList { + if *v.Tag != constants.TAG_MODEL { + continue + } mapSizeModel[*v.SizeId] = k } //处理 diff --git a/server/product/internal/logic/gettagproductlistlogic.go b/server/product/internal/logic/gettagproductlistlogic.go index eae2abfb..4ffae478 100644 --- a/server/product/internal/logic/gettagproductlistlogic.go +++ b/server/product/internal/logic/gettagproductlistlogic.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" @@ -228,8 +229,13 @@ func (l *GetTagProductListLogic) getProductRelationInfo(req getProductRelationIn CoverMetadata: req.MapResourceMetadata[*v.Cover], }) } + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, productIds, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") + if err != nil { + logx.Error(err) + return nil, errors.New("failed to get model list") + } //获取产品最低价格 - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, req.MapProductMinPrice); err != nil { + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, req.MapProductMinPrice); err != nil { logx.Error(err) return nil, errors.New("failed to get product min price") } diff --git a/server/product/internal/logic/homepagerecommendproductlistlogic.go b/server/product/internal/logic/homepagerecommendproductlistlogic.go index a4447811..f0bf6206 100644 --- a/server/product/internal/logic/homepagerecommendproductlistlogic.go +++ b/server/product/internal/logic/homepagerecommendproductlistlogic.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" @@ -113,7 +114,12 @@ func (l *HomePageRecommendProductListLogic) HomePageRecommendProductList(req *ty } //获取产品最低价格 mapProductMinPrice := make(map[int64]int64) - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, mapProductMinPrice); err != nil { + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, productIds, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "get product model list err") + } + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product min price") } diff --git a/server/product/internal/types/types.go b/server/product/internal/types/types.go index 05455455..cb6e6448 100644 --- a/server/product/internal/types/types.go +++ b/server/product/internal/types/types.go @@ -191,6 +191,71 @@ type HomePageRecommendProductListRsp struct { IsCustomization int64 `json:"is_customization"` } +type GetProductDetailReq struct { + ProductId int64 `form:"product_id"` //产品id + TemplateTag string `form:"template_tag"` //模板标签 + SelectColorIndex int `form:"select_color_index"` //模板标签颜色索引 + Logo string `form:"logo"` //logo地址 +} + +type GetProductDetailRsp struct { + TemplateTagColorInfo TemplateTagColorInfo `json:"template_tag_color_info"` //标签颜色信息 + ProductInfo ProductInfo `json:"product_info"` //产品基本信息 + BaseColors interface{} `json:"base_colors"` //一些返回写死的颜色 + SizeList []SizeInfo `json:"size_list"` //尺寸相关信息 +} + +type SizeInfo struct { + Id int64 `json:"id"` //尺寸id + IsDefault int64 `json:"is_default"` //是否默认显示 + Title interface{} `json:"title"` //尺寸标题信息 + Capacity string `json:"capacity"` //尺寸名称 + PartsCanDeleted int64 `json:"parts_can_deleted"` //配件是否可删除 + IsHot int64 `json:"is_hot"` //是否热门 + MinPrice string `json:"min_price"` //最低价 + TemplateInfo interface{} `json:"template_info"` //模板相关信息 + ModelInfo ModelInfo `json:"model_info"` //模型相关信息 + FittingList []FittingInfo `json:"fitting_list"` //配件相关信息 +} + +type FittingInfo struct { + Id int64 `json:"id"` //配件id + IsHot int64 `json:"is_hot"` //是否热门 + MaterialImage string `json:"material_image"` //配件材质图 + DesignInfo interface{} `json:"design_info"` //配件设计信息 + Price string `json:"price"` //配件价格 + Name string `json:"name"` //配件名 + IsDefault int64 `json:"is_default"` //是否默认的配件 +} + +type ModelInfo struct { + Id int64 `json:"id"` //模型id + ModelDesignInfo interface{} `json:"design_info"` //模型设计信息 + LightInfo LightInfo `json:"light_info"` //灯光信息 +} + +type LightInfo struct { + Id int64 `json:"id"` //灯光id + LightName string `json:"light_name"` //灯光组名称 + LightDesignInfo interface{} `json:"light_design_info"` //灯光设计信息 +} + +type ProductInfo struct { + Id int64 `json:"id"` //产品id + ProductType int64 `json:"product_type"` //产品类型id + ProductTypeName string `json:"product_type_name"` //产品类型名称 + Title string `json:"title"` //产品标题 + IsEnv int64 `json:"is_env"` //是否环保 + IsMicro int64 `json:"is_micro"` //是否可微波炉 + IsCustomization int64 `json:"is_customization"` //是否可定制产品 +} + +type TemplateTagColorInfo struct { + Colors [][]string `json:"colors"` //传入logo对应的算法颜色组 + SelectedColorIndex int `json:"selected_color_index"` //选择的模板标签的颜色索引值 + TemplateTagGroups interface{} `json:"template_tag_groups"` //模板标签分组信息 +} + type Request struct { } diff --git a/server/resource/internal/logic/logocombinelogic.go b/server/resource/internal/logic/logocombinelogic.go index de3367cc..609a4316 100644 --- a/server/resource/internal/logic/logocombinelogic.go +++ b/server/resource/internal/logic/logocombinelogic.go @@ -43,10 +43,10 @@ func (l *LogoCombineLogic) LogoCombine(req *types.LogoCombineReq, userinfo *auth // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) // userinfo 传入值时, 一定不为null - if userinfo.IsOnlooker() { - // 如果是,返回未授权的错误码 - return resp.SetStatus(basic.CodeUnAuth) - } + // if userinfo.IsOnlooker() { + // // 如果是,返回未授权的错误码 + // return resp.SetStatus(basic.CodeUnAuth) + // } if req.TemplateId == 0 || req.TemplateTag == "" { return resp.SetStatus(basic.CodeLogoCombineNoFoundErr, "模版或标签不存在") diff --git a/server/websocket/internal/handler/closewebsockethandler.go b/server/websocket/internal/handler/closewebsockethandler.go new file mode 100644 index 00000000..b6f755e8 --- /dev/null +++ b/server/websocket/internal/handler/closewebsockethandler.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + "reflect" + + "fusenapi/utils/basic" + + "fusenapi/server/websocket/internal/logic" + "fusenapi/server/websocket/internal/svc" + "fusenapi/server/websocket/internal/types" +) + +func CloseWebsocketHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var req types.CloseWebsocketReq + userinfo, err := basic.RequestParse(w, r, svcCtx, &req) + if err != nil { + return + } + + // 创建一个业务逻辑层实例 + l := logic.NewCloseWebsocketLogic(r.Context(), svcCtx) + + rl := reflect.ValueOf(l) + basic.BeforeLogic(w, r, rl) + + resp := l.CloseWebsocket(&req, userinfo) + + if !basic.AfterLogic(w, r, rl, resp) { + basic.NormalAfterLogic(w, r, resp) + } + } +} diff --git a/server/websocket/internal/handler/routes.go b/server/websocket/internal/handler/routes.go index 8a2c5bd1..4eefa507 100644 --- a/server/websocket/internal/handler/routes.go +++ b/server/websocket/internal/handler/routes.go @@ -27,6 +27,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/api/websocket/common_notify", Handler: CommonNotifyHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/api/websocket/close_websocket", + Handler: CloseWebsocketHandler(serverCtx), + }, }, ) } diff --git a/server/websocket/internal/logic/closewebsocketlogic.go b/server/websocket/internal/logic/closewebsocketlogic.go new file mode 100644 index 00000000..72a5ef7c --- /dev/null +++ b/server/websocket/internal/logic/closewebsocketlogic.go @@ -0,0 +1,50 @@ +package logic + +import ( + "fusenapi/utils/auth" + "fusenapi/utils/basic" + + "context" + + "fusenapi/server/websocket/internal/svc" + "fusenapi/server/websocket/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CloseWebsocketLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCloseWebsocketLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CloseWebsocketLogic { + return &CloseWebsocketLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 处理进入前逻辑w,r +// func (l *CloseWebsocketLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { +// } + +func (l *CloseWebsocketLogic) CloseWebsocket(req *types.CloseWebsocketReq, userinfo *auth.UserInfo) (resp *basic.Response) { + //获取连接 + value, ok := mapConnPool.Load(req.Wid) + if !ok { + return resp.SetStatusAddMessage(basic.CodeRequestParamsErr, "wid connection is not exists") + } + ws, ok := value.(wsConnectItem) + if !ok { + return resp.SetStatusWithMessage(basic.CodeServiceErr, "渲染回调断言websocket连接失败") + } + ws.close() + return resp.SetStatus(basic.CodeOK, "断开成功") +} + +// 处理逻辑后 w,r 如:重定向, resp 必须重新处理 +// func (l *CloseWebsocketLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { +// // httpx.OkJsonCtx(r.Context(), w, resp) +// } diff --git a/server/websocket/internal/logic/datatransferlogic.go b/server/websocket/internal/logic/datatransferlogic.go index ce77f9b8..b7dd3978 100644 --- a/server/websocket/internal/logic/datatransferlogic.go +++ b/server/websocket/internal/logic/datatransferlogic.go @@ -472,7 +472,7 @@ func (w *wsConnectItem) allocationProcessing(data []byte) { //获取工厂实例 processor := w.newAllocationProcessor(parseInfo.T) if processor == nil { - logx.Error("未知消息类型:", string(data)) + //logx.Error("未知消息类型:", string(data)) return } //执行工厂方法 diff --git a/server/websocket/internal/logic/ws_render_image.go b/server/websocket/internal/logic/ws_render_image.go index 7b3d2f7d..f9c96331 100644 --- a/server/websocket/internal/logic/ws_render_image.go +++ b/server/websocket/internal/logic/ws_render_image.go @@ -145,10 +145,6 @@ func (w *wsConnectItem) consumeRenderImageData() { // 执行渲染任务 func (w *wsConnectItem) renderImage(renderImageData websocket_data.RenderImageReqMsg) { - /* if renderImageData.RenderData.Logo == "" { - w.renderErrResponse(renderImageData.RenderId, renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "请传入logo", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) - return - }*/ if !strings.Contains(renderImageData.RenderData.Logo, "storage.fusenpack.com") { w.renderErrResponse(renderImageData.RenderId, renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "非法的logo", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) return diff --git a/server/websocket/internal/types/types.go b/server/websocket/internal/types/types.go index 1bcd95d5..316ac30d 100644 --- a/server/websocket/internal/types/types.go +++ b/server/websocket/internal/types/types.go @@ -25,6 +25,10 @@ type CommonNotifyReq struct { Data map[string]interface{} `json:"data"` //后端与前端约定好的数据 } +type CloseWebsocketReq struct { + Wid string `json:"wid"` +} + type Request struct { } diff --git a/server_api/order.api b/server_api/order.api index 4e62ecd3..b0536334 100644 --- a/server_api/order.api +++ b/server_api/order.api @@ -24,7 +24,10 @@ service order { post /api/order/delete(DeleteOrderReq) returns (response); @handler CloseOrderHandler - post /api/order/close(CloseOrderReq) returns (response); + post /api/order/close(CloseOrderReq) returns (response) + + @handler OrderInvoiceHandler + get /api/order/invoice(OrderInvoiceReq) returns (response); @handler OrderListHandler get /api/order/list(OrderListReq) returns (response); @@ -34,6 +37,12 @@ service order { } +type ( + OrderInvoiceReq { + OrderSn string `form:"order_sn"` + } +) + type ( CloseOrderReq { OrderSn string `json:"order_sn"` @@ -60,9 +69,14 @@ type CreatePrePaymentByDepositReq { } type DeliveryAddress { - Address string `json:"address,optional"` - Name string `json:"name,optional"` - Mobile string `json:"mobile,optional"` + Street string `json:"street,optional"` // 街道 + City string `json:"city,optional"` // 城市 + FirstName string `json:"first_name,optional"` // 姓 + LastName string `json:"last_name,optional"` // 名 + Mobile string `json:"mobile,optional"` // 手机 + State string `json:"state,optional"` // 州 + Suite string `json:"suite,optional"` // 房号 + ZipCode string `json:"zip_code,optional"` // 邮编 } type CreatePrePaymentByBalanceReq { diff --git a/server_api/product.api b/server_api/product.api index 5f586731..8180ee6b 100644 --- a/server_api/product.api +++ b/server_api/product.api @@ -227,55 +227,60 @@ type HomePageRecommendProductListRsp { } //获取产品详情(重构版) -type GetProductDetailReq{ - ProductId int64 `form:"product_id"`//产品id - TemplateTag string `form:"template_tag"` //模板标签 - SelectColorIndex int `form:"select_color_index"` //模板标签颜色索引 +type GetProductDetailReq { + ProductId int64 `form:"product_id"` //产品id + TemplateTag string `form:"template_tag"` //模板标签 + SelectColorIndex int `form:"select_color_index"` //模板标签颜色索引 + Logo string `form:"logo"` //logo地址 } -type GetProductDetailRsp{ +type GetProductDetailRsp { TemplateTagColorInfo TemplateTagColorInfo `json:"template_tag_color_info"` //标签颜色信息 - ProductInfo ProductInfo `json:"product_info"` //产品基本信息 - BaseColors interface{} `json:"base_colors"` //一些返回写死的颜色 - SizeInfo SizeInfo `json:"size_info"` //尺寸相关信息 + ProductInfo ProductInfo `json:"product_info"` //产品基本信息 + BaseColors interface{} `json:"base_colors"` //一些返回写死的颜色 + SizeList []SizeInfo `json:"size_list"` //尺寸相关信息 } -type SizeInfo{ - SizeId int64 `json:"size_id"` - Title interface{} `json:"title"` - Capacity string `json:"capacity"` - PartsCanDeleted bool `json:"parts_can_deleted"` - ModelId int64 `json:"model_id"` - IsHot int64 `json:"is_hot"` - MinPrice string `json:"min_price"` - IsDefault bool `json:"is_default"` - TemplateInfo TemplateInfo `json:"template_info"` - ModelInfo ModelInfo `json:"model_info"` +type SizeInfo { + Id int64 `json:"id"` //尺寸id + IsDefault int64 `json:"is_default"` //是否默认显示 + Title interface{} `json:"title"` //尺寸标题信息 + Capacity string `json:"capacity"` //尺寸名称 + PartsCanDeleted int64 `json:"parts_can_deleted"` //配件是否可删除 + IsHot int64 `json:"is_hot"` //是否热门 + MinPrice string `json:"min_price"` //最低价 + TemplateInfo interface{} `json:"template_info"` //模板相关信息 + ModelInfo ModelInfo `json:"model_info"` //模型相关信息 + FittingList []FittingInfo `json:"fitting_list"` //配件相关信息 } -type ModelInfo{ - ModelId int64 `json:"model_id"` //模型id +type FittingInfo { + Id int64 `json:"id"` //配件id + IsHot int64 `json:"is_hot"` //是否热门 + MaterialImage string `json:"material_image"` //配件材质图 + DesignInfo interface{} `json:"design_info"` //配件设计信息 + Price string `json:"price"` //配件价格 + Name string `json:"name"` //配件名 + IsDefault int64 `json:"is_default"` //是否默认的配件 +} +type ModelInfo { + Id int64 `json:"id"` //模型id ModelDesignInfo interface{} `json:"design_info"` //模型设计信息 - LightInfo LightInfo `json:"light_info"` //灯光信息 + LightInfo LightInfo `json:"light_info"` //灯光信息 } -type LightInfo{ - LightId int64 `json:"light_id"` //灯光id - LightName string `json:"light_name"` //灯光组名称 +type LightInfo { + Id int64 `json:"id"` //灯光id + LightName string `json:"light_name"` //灯光组名称 LightDesignInfo interface{} `json:"light_design_info"` //灯光设计信息 } -type TemplateInfo { - TemplateSwitchInfo interface{} `json:"template_switch_info"` //对应模板标签下模板的开关信息,同列表页 - CombineIsVisible bool `json:"combine_is_visible"` //合图开关是否开启 - Material string `json:"material"` //默认素材 -} -type ProductInfo{ - ProductId int64 `json:"product_id"` //产品id - ProductType int64 `json:"product_type"` //产品类型id +type ProductInfo { + Id int64 `json:"id"` //产品id + ProductType int64 `json:"product_type"` //产品类型id ProductTypeName string `json:"product_type_name"` //产品类型名称 - Title string `json:"title"` //产品标题 - IsEnv int64 `json:"is_env"` //是否环保 - IsMicro int64 `json:"is_micro"` //是否可微波炉 - IsCustomization int64 `json:"is_customization"` //是否可定制产品 + Title string `json:"title"` //产品标题 + IsEnv int64 `json:"is_env"` //是否环保 + IsMicro int64 `json:"is_micro"` //是否可微波炉 + IsCustomization int64 `json:"is_customization"` //是否可定制产品 } -type TemplateTagColorInfo{ - Colors []string `json:"colors"` - SelectedColorIndex int `json:"selected_color_index"` - TemplateTagGroups interface{} `json:"template_tag_groups"` +type TemplateTagColorInfo { + Colors [][]string `json:"colors"` //传入logo对应的算法颜色组 + SelectedColorIndex int `json:"selected_color_index"` //选择的模板标签的颜色索引值 + TemplateTagGroups interface{} `json:"template_tag_groups"` //模板标签分组信息 } \ No newline at end of file diff --git a/server_api/websocket.api b/server_api/websocket.api index b3c38975..43a39910 100644 --- a/server_api/websocket.api +++ b/server_api/websocket.api @@ -18,6 +18,9 @@ service websocket { //通用回调接口 @handler CommonNotifyHandler post /api/websocket/common_notify(CommonNotifyReq) returns (response); + //关闭某个连接 + @handler CloseWebsocketHandler + post /api/websocket/close_websocket(CloseWebsocketReq) returns (response); } //websocket数据交互[ @@ -39,4 +42,8 @@ type CommonNotifyReq { UserId int64 `json:"user_id,optional"` //用户id GuestId int64 `json:"guest_id,optional"` //游客id Data map[string]interface{} `json:"data"` //后端与前端约定好的数据 +} +//关闭连接 +type CloseWebsocketReq { + Wid string `json:"wid"` } \ No newline at end of file diff --git a/service/repositories/order.go b/service/repositories/order.go index 325b90b8..bdf5f037 100644 --- a/service/repositories/order.go +++ b/service/repositories/order.go @@ -8,9 +8,12 @@ import ( "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/basic" + "fusenapi/utils/file" "fusenapi/utils/handlers" + "fusenapi/utils/hash" "fusenapi/utils/order" "fusenapi/utils/pay" + "fusenapi/utils/pdf" "fusenapi/utils/queue" "math" "time" @@ -25,6 +28,7 @@ func NewOrder(gormDB *gorm.DB, awsSession *session.Session, delayQueue *queue.De return &defaultOrder{ MysqlConn: gormDB, DelayQueue: delayQueue, + AwsSession: awsSession, } } @@ -32,6 +36,7 @@ type ( defaultOrder struct { MysqlConn *gorm.DB DelayQueue *queue.DelayMessage + AwsSession *session.Session } Order interface { // 下单 @@ -44,6 +49,8 @@ type ( List(ctx context.Context, in *ListReq) (res *ListRes, err error) // 详情 Detail(ctx context.Context, in *DetailReq) (res *DetailRes, err error) + // 发票 + Invoice(ctx context.Context, in *InvoiceReq) (res *InvoiceRes, err error) // 支付成功 PaymentSuccessful(ctx context.Context, in *PaymentSuccessfulReq) (res *PaymentSuccessfulRes, err error) // 关闭 @@ -65,9 +72,14 @@ type ( } OrderAddress struct { - Address string `json:"address"` // 详细地址 - Mobile string `json:"mobile"` // 手机 - Name string `json:"name"` // 姓名 + Street string `json:"street"` // 详细地址 + City string `json:"city"` // 城市 + FirstName string `json:"first_name"` // 姓 + LastName string `json:"last_name"` // 名 + Mobile string `json:"mobile"` // 手机 + State string `json:"state"` // 州 + Suite string `json:"suite"` // 房号 + ZipCode string `json:"zip_code"` // 邮编号码 } OrderPay struct { ClientSecret string `json:"client_secret"` // 支付方--秘钥 @@ -85,6 +97,16 @@ type ( Amount int64 `json:"amount"` // 金额 Label string `json:"label"` // 标签 } + /* 发票 */ + InvoiceReq struct { + UserId int64 `json:"user_id"` + OrderSn string `json:"order_sn"` + } + InvoiceRes struct { + ErrorCode basic.StatusResponse + InvoiceUrls []string `json:"invoice_urls"` + } + /* 发票 */ /* 删除订单 */ DeleteReq struct { @@ -210,6 +232,293 @@ type ( /* 列表 */ ) +// 订单发票 +func (d *defaultOrder) Invoice(ctx context.Context, in *InvoiceReq) (res *InvoiceRes, err error) { + var errorCode basic.StatusResponse + var orderInfo gmodel.FsOrder + var receiptSnsResources []string + var receiptSnsDeposit string + var receiptSnsFinal string + var invoiceUrls []string + + var orderTradeDeposit gmodel.FsOrderTrade + var orderTradeFinal gmodel.FsOrderTrade + model := d.MysqlConn.Where("is_del = ?", 0) + if in.UserId != 0 { + model = model.Where("user_id = ?", in.UserId) + } + if in.OrderSn != "" { + model = model.Where("order_sn = ?", in.OrderSn) + } + result := model.Take(&orderInfo) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + errorCode = *basic.CodeErrOrderCreatePrePaymentInfoNoFound + } else { + errorCode = *basic.CodeServiceErr + } + logc.Errorf(ctx, "order invoice detail failed, err: %v", err) + return &InvoiceRes{ + ErrorCode: errorCode, + }, result.Error + } + + if *orderInfo.PayStatus == int64(constants.ORDER_PAY_STATUS_PAIDDEPOSIT) || *orderInfo.PayStatus == int64(constants.ORDER_PAY_STATUS_PAIDDREMAINING) { + // 查询支付账单 + var orderTradeList []gmodel.FsOrderTrade + + model1 := d.MysqlConn + if in.OrderSn != "" { + model1 = model1.Where("order_sn = ?", in.OrderSn) + } + result1 := model1.Find(&orderTradeList) + if result1.Error != nil { + if errors.Is(result1.Error, gorm.ErrRecordNotFound) { + errorCode = *basic.CodeErrOrderCreatePrePaymentInfoNoFound + } else { + errorCode = *basic.CodeServiceErr + } + logc.Errorf(ctx, "order invoice trade failed, err: %v", err) + return &InvoiceRes{ + ErrorCode: errorCode, + }, result1.Error + } + if len(orderTradeList) > 0 { + for _, orderTrade := range orderTradeList { + receiptSnsResources = append(receiptSnsResources, hash.JsonHashKey(*orderTrade.ReceiptSn)) + if *orderTrade.PayStage == 1 { + receiptSnsDeposit = *orderTrade.ReceiptSn + orderTradeDeposit = orderTrade + } + if *orderTrade.PayStage == 2 { + receiptSnsFinal = *orderTrade.ReceiptSn + orderTradeFinal = orderTrade + } + } + // 查询支付账单 + var resourceList []gmodel.FsResource + result2 := d.MysqlConn.Where("resource_id in ?", receiptSnsResources).Find(&resourceList) + if result2.Error != nil { + errorCode = *basic.CodeServiceErr + logc.Errorf(ctx, "order invoice esource failed, err: %v", err) + return &InvoiceRes{ + ErrorCode: errorCode, + }, result1.Error + } + resourceListLen := len(resourceList) + for _, resource := range resourceList { + invoiceUrls = append(invoiceUrls, *resource.ResourceUrl) + } + if *orderInfo.PayStatus == int64(constants.ORDER_PAY_STATUS_PAIDDEPOSIT) { + if resourceListLen == 1 { + return &InvoiceRes{ + ErrorCode: errorCode, + InvoiceUrls: invoiceUrls, + }, nil + } + if resourceListLen == 0 { + receiptSnsFinal = "" + } + } + if *orderInfo.PayStatus == int64(constants.ORDER_PAY_STATUS_PAIDDREMAINING) { + if resourceListLen == 2 { + return &InvoiceRes{ + ErrorCode: errorCode, + InvoiceUrls: invoiceUrls, + }, nil + } + if resourceListLen == 1 { + receiptSnsDeposit = "" + } + } + } else { + errorCode = *basic.CodeErrOrderCreatePrePaymentInfoNoFound + return &InvoiceRes{ + ErrorCode: errorCode, + }, errors.New("get order invoice failed, not found") + } + } else { + err = errors.New("get order invoice failed, pay status is illegality") + errorCode = *basic.CodeErrOrderInvoiceStatusIllegality + return &InvoiceRes{ + ErrorCode: errorCode, + }, err + } + + ress, err := d.OrderDetailHandler(ctx, &orderInfo, 1) + if err != nil { + logc.Errorf(ctx, "order invoice detail handler failed, err: %v", err) + errorCode = *basic.CodeServiceErr + return &InvoiceRes{ + ErrorCode: errorCode, + }, err + } else { + var model001 = constants.INVOICE_TEMPLATE_01 + var model002 string + var model003 string + var model004 string + var model005 string + var model006 = constants.INVOICE_TEMPLATE_06 + + ctimeDate := orderInfo.Ctime.Format("2006/01/02") + var name string + var city string + var street string + var state string + if ress.OrderDetail.DeliveryAddress != nil { + name = fmt.Sprintf("%s %s", ress.OrderDetail.DeliveryAddress.FirstName, ress.OrderDetail.DeliveryAddress.LastName) + street = ress.OrderDetail.DeliveryAddress.Street + city = ress.OrderDetail.DeliveryAddress.City + state = ress.OrderDetail.DeliveryAddress.State + } + + var products string + for _, orderProduct := range ress.OrderDetail.OrderProduct { + var model00301 = constants.INVOICE_TEMPLATE_0301 + var price = fmt.Sprintf("%s%s", constants.OrderCurrencyMessage[constants.Currency(orderProduct.ItemPrice.Current.CurrentCurrency)], orderProduct.ItemPrice.Current.CurrentAmount.(string)) + var priceTotal = fmt.Sprintf("%s%s", constants.OrderCurrencyMessage[constants.Currency(orderProduct.TotalPrice.Current.CurrentCurrency)], orderProduct.TotalPrice.Current.CurrentAmount.(string)) + var productsInfo = fmt.Sprintf(model00301, orderProduct.ProductName, price, orderProduct.PurchaseQuantity.Current, priceTotal) + products = products + productsInfo + } + model003 = fmt.Sprintf(constants.INVOICE_TEMPLATE_03, "", products) + + var subtotal = fmt.Sprintf("%s%s", constants.OrderCurrencyMessage[constants.Currency(ress.OrderDetail.OrderAmount.Subtotal.Current.CurrentCurrency)], ress.OrderDetail.OrderAmount.Subtotal.Current.CurrentAmount.(string)) + var taxStr = "0.00" + if ress.OrderDetail.OrderAmount.Tax.Current.CurrentAmount != nil { + taxStr = ress.OrderDetail.OrderAmount.Tax.Current.CurrentAmount.(string) + } + var taxCurrency string = constants.OrderCurrencyMessage[constants.Currency(ress.OrderDetail.OrderAmount.Tax.Current.CurrentCurrency)] + if taxCurrency == "" { + taxCurrency = constants.OrderCurrencyMessage[constants.Currency(ress.OrderDetail.OrderAmount.Total.Current.CurrentCurrency)] + } + var tax = fmt.Sprintf("%s%s", taxCurrency, taxStr) + var total = fmt.Sprintf("%s%s", constants.OrderCurrencyMessage[constants.Currency(ress.OrderDetail.OrderAmount.Total.Current.CurrentCurrency)], ress.OrderDetail.OrderAmount.Total.Current.CurrentAmount.(string)) + + // 生成收据发票--首款 + if receiptSnsDeposit != "" { + v1 := receiptSnsDeposit + v2 := name + v3 := ctimeDate + v4 := street + v5 := city + v6 := state + model002 = fmt.Sprintf(constants.INVOICE_TEMPLATE_02, "", v1, v2, v3, v4, v5, v6) + + v7 := "Deposit Requested" + v8 := fmt.Sprintf("%s%s", constants.OrderCurrencyMessage[constants.Currency(ress.OrderDetail.OrderAmount.Deposit.PayAmount.Current.CurrentCurrency)], ress.OrderDetail.OrderAmount.Deposit.PayAmount.Current.CurrentAmount.(string)) + v9 := "Deposit Due" + v10 := v8 + model004 = fmt.Sprintf(constants.INVOICE_TEMPLATE_04, "", subtotal, tax, total, v7, v8, v9, v10) + + cardSn := "****" + *orderTradeDeposit.CardSn + model005 = fmt.Sprintf(constants.INVOICE_TEMPLATE_05, "", *orderTradeDeposit.CardBrand, cardSn) + + var content = model001 + model002 + model003 + model004 + model005 + model006 + + base64, err := pdf.HtmlToPdfBase64(content, "html") + if err != nil { + logc.Errorf(ctx, "order invoice HtmlToPdfBase64 failed, err: %v", err) + errorCode = *basic.CodeServiceErr + return &InvoiceRes{ + ErrorCode: errorCode, + }, err + } + // 根据hash 查询数据资源 + var resourceId string = hash.JsonHashKey(receiptSnsDeposit) + + // 上传文件 + var upload = file.Upload{ + Ctx: ctx, + MysqlConn: d.MysqlConn, + AwsSession: d.AwsSession, + } + uploadRes, err := upload.UploadFileByBase64(&file.UploadBaseReq{ + FileHash: resourceId, + FileData: "data:application/pdf;base64," + base64, + UploadBucket: 1, + ApiType: 2, + UserId: *orderInfo.UserId, + GuestId: 0, + Source: "order_invoice", + Refresh: 1, + }) + if err != nil { + if err != nil { + logc.Errorf(ctx, "order invoice UploadFileByBase64 failed, err: %v", err) + errorCode = *basic.CodeServiceErr + return &InvoiceRes{ + ErrorCode: errorCode, + }, err + } + } + invoiceUrls = append(invoiceUrls, uploadRes.ResourceUrl) + } + + // 生成收据发票--尾款 + if receiptSnsFinal != "" { + v1 := receiptSnsFinal + v2 := name + v3 := ctimeDate + v4 := street + v5 := city + v6 := state + model002 = fmt.Sprintf(constants.INVOICE_TEMPLATE_02, "", v1, v2, v3, v4, v5, v6) + + v7 := "Balance Requested" + v8 := fmt.Sprintf("%s%s", constants.OrderCurrencyMessage[constants.Currency(ress.OrderDetail.OrderAmount.RemainingBalance.PayAmount.Current.CurrentCurrency)], ress.OrderDetail.OrderAmount.RemainingBalance.PayAmount.Current.CurrentAmount.(string)) + v9 := "Balance Due" + v10 := v8 + model004 = fmt.Sprintf(constants.INVOICE_TEMPLATE_04, "", subtotal, tax, total, v7, v8, v9, v10) + + cardSn := "****" + *orderTradeFinal.CardSn + model005 = fmt.Sprintf(constants.INVOICE_TEMPLATE_05, "", *orderTradeDeposit.CardBrand, cardSn) + var content = model001 + model002 + model003 + model004 + model005 + model006 + base64, err := pdf.HtmlToPdfBase64(content, "html") + if err != nil { + logc.Errorf(ctx, "order invoice HtmlToPdfBase64 failed, err: %v", err) + errorCode = *basic.CodeServiceErr + return &InvoiceRes{ + ErrorCode: errorCode, + }, err + } + // 根据hash 查询数据资源 + var resourceId string = hash.JsonHashKey(receiptSnsDeposit) + + // 上传文件 + var upload = file.Upload{ + Ctx: ctx, + MysqlConn: d.MysqlConn, + AwsSession: d.AwsSession, + } + uploadRes, err := upload.UploadFileByBase64(&file.UploadBaseReq{ + FileHash: resourceId, + FileData: "data:application/pdf;base64," + base64, + UploadBucket: 1, + ApiType: 2, + UserId: *orderInfo.UserId, + GuestId: 0, + Source: "order_invoice", + Refresh: 1, + }) + if err != nil { + if err != nil { + logc.Errorf(ctx, "order invoice UploadFileByBase64 failed, err: %v", err) + errorCode = *basic.CodeServiceErr + return &InvoiceRes{ + ErrorCode: errorCode, + }, err + } + } + invoiceUrls = append(invoiceUrls, uploadRes.ResourceUrl) + } + + return &InvoiceRes{ + ErrorCode: errorCode, + InvoiceUrls: invoiceUrls, + }, nil + } +} + // 订单删除 func (d *defaultOrder) Delete(ctx context.Context, in *DeleteReq) (res *DeleteRes, err error) { var errorCode basic.StatusResponse @@ -626,6 +935,7 @@ func (d *defaultOrder) PaymentSuccessful(ctx context.Context, in *PaymentSuccess } // 新增交易信息 + receiptSn := order.GenerateReceiptNumber() tx.Create(&gmodel.FsOrderTrade{ UserId: orderInfo.UserId, OrderSn: &orderSn, @@ -642,6 +952,7 @@ func (d *defaultOrder) PaymentSuccessful(ctx context.Context, in *PaymentSuccess Ctime: &ntime, Utime: &ntime, PayTitle: &payTitle, + ReceiptSn: &receiptSn, }) // 更新数据库 @@ -818,9 +1129,14 @@ func (d *defaultOrder) CreatePrePaymentByDeposit(ctx context.Context, in *Create if in.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL && in.DeliveryAddress != nil { orderAddress = &gmodel.OrderAddress{ - Name: in.DeliveryAddress.Name, - Mobile: in.DeliveryAddress.Mobile, - Address: in.DeliveryAddress.Address, + Street: in.DeliveryAddress.Street, + City: in.DeliveryAddress.City, + FirstName: in.DeliveryAddress.FirstName, + LastName: in.DeliveryAddress.LastName, + Mobile: in.DeliveryAddress.Mobile, + State: in.DeliveryAddress.State, + Suite: in.DeliveryAddress.Suite, + ZipCode: in.DeliveryAddress.ZipCode, } orderAddressByte, err = json.Marshal(orderAddress) if err != nil { @@ -1101,8 +1417,14 @@ func (d *defaultOrder) Create(ctx context.Context, in *CreateReq) (res *CreateRe // 直邮 if in.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL { orderAddress = &gmodel.OrderAddress{ - Mobile: in.DeliveryAddress.Mobile, - Name: in.DeliveryAddress.Name, + Street: in.DeliveryAddress.Street, + City: in.DeliveryAddress.City, + FirstName: in.DeliveryAddress.FirstName, + LastName: in.DeliveryAddress.LastName, + Mobile: in.DeliveryAddress.Mobile, + State: in.DeliveryAddress.State, + Suite: in.DeliveryAddress.Suite, + ZipCode: in.DeliveryAddress.ZipCode, } } // 预计交付时间 diff --git a/utils/basic/basic.go b/utils/basic/basic.go index 15a9c658..b6bc4be2 100644 --- a/utils/basic/basic.go +++ b/utils/basic/basic.go @@ -113,6 +113,7 @@ var ( CodeErrOrderCreatePrePaymentPaid = &StatusResponse{5309, "create payment failed, order is paid"} CodeErrOrderCreatePrePaymentTimeout = &StatusResponse{5310, "create payment failed, timeout"} CodeErrOrderDeleteStatusIllegality = &StatusResponse{5311, "delete order failed, status is illegality"} + CodeErrOrderInvoiceStatusIllegality = &StatusResponse{5312, "get order invoice failed, pay status is illegality"} ) type Response struct { diff --git a/utils/order/order.go b/utils/order/order.go index 7543e92b..1ce86761 100644 --- a/utils/order/order.go +++ b/utils/order/order.go @@ -125,10 +125,15 @@ func GetPurchaseQuantity(req *gmodel.PurchaseQuantity) gmodel.PurchaseQuantity { func GenerateOrderNumber() string { t := time.Now() orderNumber := fmt.Sprintf("%d%02d%02d%08d", t.Year(), t.Month(), t.Day(), t.UnixNano()%100000000) - fmt.Println(orderNumber) return orderNumber } +// 生成收据编号 +func GenerateReceiptNumber() string { + t := time.Now() + return fmt.Sprintf("%02d%02d%02d%08d", t.Year()%100, t.Month(), t.Day(), t.UnixNano()%100000000) +} + // 初始化订单状态--链路 func GenerateOrderStatusLink(deliveryMethod int64, noTime time.Time, expectedTime time.Time) []gmodel.OrderStatus { var list []gmodel.OrderStatus diff --git a/utils/order/order_invoice.go b/utils/order/order_invoice.go new file mode 100644 index 00000000..375dad9c --- /dev/null +++ b/utils/order/order_invoice.go @@ -0,0 +1,5 @@ +package order + +func GetOrderInvoice() string { + return "" +}