package sys import ( "context" "fmt" "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/contexts" "hotgo/internal/library/dict" "hotgo/internal/library/hgorm/handler" "hotgo/internal/library/hgorm/hook" "hotgo/internal/model" "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/grand" ) type sSysProduct struct{} func NewSysProduct() *sSysProduct { return &sSysProduct{} } func init() { service.RegisterSysProduct(NewSysProduct()) dict.RegisterFunc("productCategoryOption", "商品分类选项", service.SysProduct().CategoryOption, true) } func (s *sSysProduct) Model(ctx context.Context, option ...*handler.Option) *gdb.Model { if len(option) == 0 { option = append(option, &handler.Option{FilterTenant: true}) } return handler.Model(dao.Product.Ctx(ctx), option...) } func (s *sSysProduct) categoryModel(ctx context.Context) *gdb.Model { return handler.Model(dao.ProductCategory.Ctx(ctx), &handler.Option{FilterTenant: true}) } func (s *sSysProduct) listModel(ctx context.Context) *gdb.Model { pc, cc := dao.Product.Columns(), dao.ProductCategory.Columns() return s.Model(ctx).As("p").LeftJoin(dao.ProductCategory.Table()+" c", col("c", cc.Id)+"="+col("p", pc.CategoryId)).Fields("p.*", fmt.Sprintf("COALESCE(%s,'') category_name", col("c", cc.Name))) } func (s *sSysProduct) List(ctx context.Context, in *sysin.ProductListInp) (list []*sysin.ProductListModel, total int, err error) { pc := dao.Product.Columns() mod := s.listModel(ctx).Where(col("p", pc.ProductType), consts.ProductTypeStandard) if in.Name != "" { mod = mod.WhereLike(col("p", pc.Name), "%"+in.Name+"%") } if in.PaymentType != "" { mod = mod.Where(col("p", pc.PaymentType), in.PaymentType) } if in.CategoryId > 0 { mod = mod.Where(col("p", pc.CategoryId), in.CategoryId) } if in.PublishStatus > 0 { mod = mod.Where(col("p", pc.PublishStatus), in.PublishStatus) } if err = mod.Page(in.Page, in.PerPage).OrderAsc(col("p", pc.Sort)).OrderDesc(col("p", pc.Id)).ScanAndCount(&list, &total, false); err != nil { return nil, 0, gerror.Wrap(err, "获取商品列表失败") } for _, row := range list { row.Price = formatCents(row.PriceAmount) count, e := handler.Model(dao.TradeOrder.Ctx(ctx), &handler.Option{FilterTenant: true}).Where(dao.TradeOrder.Columns().ProductId, row.Id).Count() if e != nil { return nil, 0, e } row.Referenced = count > 0 } return } func (s *sSysProduct) View(ctx context.Context, in *sysin.ProductViewInp) (res *sysin.ProductListModel, err error) { if err = s.listModel(ctx).Where(col("p", dao.Product.Columns().Id), in.Id).Scan(&res); err != nil { return nil, gerror.Wrap(err, "获取商品详情失败") } if res == nil { return nil, gerror.New("商品不存在") } res.Price = formatCents(res.PriceAmount) count, err := handler.Model(dao.TradeOrder.Ctx(ctx), &handler.Option{FilterTenant: true}).Where(dao.TradeOrder.Columns().ProductId, res.Id).Count() res.Referenced = count > 0 return } func (s *sSysProduct) Edit(ctx context.Context, in *sysin.ProductEditInp) (id uint64, err error) { amount, err := parseCents(in.Price) if err != nil { return 0, err } in.PriceAmount = amount if in.PaymentType == "" { in.PaymentType = consts.PayTypeBalance } if in.Currency == "" { in.Currency = "CNY" } cc := dao.ProductCategory.Columns() count, err := s.categoryModel(ctx).WherePri(in.CategoryId).Where(cc.Status, consts.StatusEnabled).Count() if err != nil { return 0, err } if count == 0 { return 0, gerror.New("商品分类不存在或已停用") } c := dao.Product.Columns() uid := contexts.GetUserId(ctx) var before *entity.Product if in.Id > 0 { if err = s.Model(ctx).WherePri(in.Id).Scan(&before); err != nil { return } if before == nil { return 0, gerror.New("商品不存在") } result, e := s.Model(ctx).WherePri(in.Id).Where(c.Version, in.Version).Fields(sysin.ProductUpdateFields{}).Data(g.Map{c.CategoryId: in.CategoryId, c.ProductType: in.ProductType, c.PaymentType: in.PaymentType, c.Name: in.Name, c.ShopName: in.ShopName, c.PriceAmount: in.PriceAmount, c.Currency: in.Currency, c.CoverAttachmentId: in.CoverAttachmentId, c.CoverUrl: in.CoverUrl, c.DetailHtml: in.DetailHtml, c.IsRecommended: in.IsRecommended, c.StartAt: in.StartAt, c.EndAt: in.EndAt, c.Sort: in.Sort, c.Remark: in.Remark, c.UpdatedBy: uid, c.Version: gdb.Raw(c.Version + "+1")}).Update() if e != nil { return 0, gerror.Wrap(e, "更新商品失败") } rows, _ := result.RowsAffected() if rows != 1 { return 0, gerror.New("商品数据已更新,请刷新后重试") } id = in.Id } else { in.ProductNo = "PD" + gtime.Now().Format("YmdHis") + grand.S(5) result, e := s.Model(ctx).Fields(c.ProductNo, c.CategoryId, c.ProductType, c.PaymentType, c.Name, c.ShopName, c.PriceAmount, c.Currency, c.CoverAttachmentId, c.CoverUrl, c.DetailHtml, c.IsRecommended, c.ClaimedCount, c.PublishStatus, c.StartAt, c.EndAt, c.Sort, c.Remark, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(g.Map{c.ProductNo: in.ProductNo, c.CategoryId: in.CategoryId, c.ProductType: in.ProductType, c.PaymentType: in.PaymentType, c.Name: in.Name, c.ShopName: in.ShopName, c.PriceAmount: in.PriceAmount, c.Currency: in.Currency, c.CoverAttachmentId: in.CoverAttachmentId, c.CoverUrl: in.CoverUrl, c.DetailHtml: in.DetailHtml, c.IsRecommended: in.IsRecommended, c.ClaimedCount: grand.N(100, 500), c.PublishStatus: consts.BizPublishDraft, c.StartAt: in.StartAt, c.EndAt: in.EndAt, c.Sort: in.Sort, c.Remark: in.Remark, c.CreatedBy: uid, c.UpdatedBy: uid}).Insert() if e != nil { return 0, gerror.Wrap(e, "创建商品失败") } last, _ := result.LastInsertId() id = uint64(last) } err = service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "product", Action: "product.edit", TargetType: "product", TargetId: id, Before: before, After: g.Map{"name": in.Name, "priceAmount": in.PriceAmount, "categoryId": in.CategoryId}}) return } func (s *sSysProduct) Status(ctx context.Context, in *sysin.ProductStatusInp) error { c := dao.Product.Columns() result, err := s.Model(ctx).WherePri(in.Id).Where(c.Version, in.Version).Data(g.Map{c.PublishStatus: in.PublishStatus, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update() if err != nil { return gerror.Wrap(err, "更新商品状态失败") } rows, _ := result.RowsAffected() if rows != 1 { return gerror.New("商品数据已更新,请刷新后重试") } return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "product", Action: "product.status", TargetType: "product", TargetId: in.Id, After: g.Map{"publishStatus": in.PublishStatus}}) } func (s *sSysProduct) Recommend(ctx context.Context, in *sysin.ProductRecommendInp) error { c := dao.Product.Columns() result, err := s.Model(ctx).WherePri(in.Id).Where(c.Version, in.Version).Data(g.Map{c.IsRecommended: in.IsRecommended, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update() if err != nil { return gerror.Wrap(err, "更新商品推荐状态失败") } rows, _ := result.RowsAffected() if rows != 1 { return gerror.New("商品数据已更新,请刷新后重试") } return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "product", Action: "product.recommend", TargetType: "product", TargetId: in.Id, After: g.Map{"isRecommended": in.IsRecommended}}) } func (s *sSysProduct) Delete(ctx context.Context, in *sysin.ProductDeleteInp) error { c := dao.Product.Columns() var row *entity.Product if err := s.Model(ctx).WherePri(in.Id).Scan(&row); err != nil { return err } if row == nil { return gerror.New("商品不存在") } count, err := handler.Model(dao.TradeOrder.Ctx(ctx), &handler.Option{FilterTenant: true}).Where(dao.TradeOrder.Columns().ProductId, in.Id).Count() if err != nil { return err } if count > 0 { return gerror.New("商品已被订单引用,只允许下架,不能删除") } if row.PublishStatus == consts.BizPublishPublished { _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{c.PublishStatus: consts.BizPublishOffline, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update() return err } _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx), c.UpdatedBy: contexts.GetUserId(ctx)}).Update() return gerror.Wrap(err, "删除商品失败") } func (s *sSysProduct) CategoryOption(ctx context.Context) (opts []*model.Option, err error) { var rows []*entity.ProductCategory c := dao.ProductCategory.Columns() if err = s.categoryModel(ctx).Fields(c.Id, c.Name).Where(c.Status, consts.StatusEnabled).OrderAsc(c.Sort).Scan(&rows); err != nil { return nil, gerror.Wrap(err, "获取商品分类选项失败") } for _, row := range rows { opts = append(opts, dict.GenHashOption(row.Id, gconv.String(row.Name))) } return }