Files
olons_admin/server/internal/logic/sys/product.go
T
2026-10-10 20:56:17 +08:00

195 lines
8.8 KiB
Go

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)
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.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.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
}