package service import ( "context" "github.com/Caknoooo/go-gin-clean-starter/database/entities" "github.com/Caknoooo/go-gin-clean-starter/modules/product/dto" "github.com/Caknoooo/go-gin-clean-starter/modules/product/query" "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto" "github.com/google/uuid" "gorm.io/gorm" ) type ProductService interface { Create(ctx context.Context, req dto.ProductCreateRequest) (dto.ProductResponse, error) GetById(ctx context.Context, productId string) (dto.ProductResponse, error) GetAll(ctx context.Context, filter query.ProductFilter) ([]dto.ProductResponse, int64, error) Update(ctx context.Context, req dto.ProductUpdateRequest, productId string) (dto.ProductResponse, error) Delete(ctx context.Context, productId string) error AssignCrossReference(ctx context.Context, productId string, vendorIds []string) error RemoveCrossReference(ctx context.Context, productId string, vendorIds []string) error } type productService struct { db *gorm.DB productRepo repository.ProductRepository } // AssignCrossReference implements ProductService. func (s *productService) AssignCrossReference(ctx context.Context, productId string, vendorIds []string) error { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() err := s.productRepo.AssignCrossReference(ctx, tx, productId, vendorIds) if err != nil { tx.Rollback() return err } if err := tx.Commit().Error; err != nil { tx.Rollback() return err } return nil } // RemoveCrossReference implements ProductService. func (s *productService) RemoveCrossReference(ctx context.Context, productId string, vendorIds []string) error { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() err := s.productRepo.RemoveCrossReference(ctx, tx, productId, vendorIds) if err != nil { tx.Rollback() return err } if err := tx.Commit().Error; err != nil { tx.Rollback() return err } return nil } func NewProductService(productRepo repository.ProductRepository, db *gorm.DB) ProductService { return &productService{ productRepo: productRepo, db: db, } } func (s *productService) Create(ctx context.Context, req dto.ProductCreateRequest) (dto.ProductResponse, error) { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() product := entities.MProductEntity{ Name: req.Name, RefNumber: req.RefNumber, SKU: req.SKU, Description: req.Description, Status: req.Status, IsReturnable: req.IsReturnable, // DimLength: req.DimLength, // DimWidth: req.DimWidth, // DimHeight: req.DimHeight, // Weight: req.Weight, // Volume: req.Volume, // MaxStackHeight: req.MaxStackHeight, // Temperature: req.Temperature, // IsHazardous: req.IsHazardous, // MinStock: req.MinStock, // MaxStock: req.MaxStock, // ReplenishType: req.ReplenishType, // CycleCount: req.CycleCount, // LotRules: req.LotRules, // LeadTime: req.LeadTime, // MultiplyRate: req.MultiplyRate, // DivideRate: req.DivideRate, } // UUID fields product.ClientID = parseUUID(req.ClientID) if req.CategoryID != nil { id := parseUUID(*req.CategoryID) product.CategoryID = &id } if req.UomID != nil { id := parseUUID(*req.UomID) product.UomID = &id } // product.DimUomID = parseUUID(req.DimUomID) // product.WeightUomID = parseUUID(req.WeightUomID) // product.VolumeUomID = parseUUID(req.VolumeUomID) // product.MinStockUomID = parseUUID(req.MinStockUomID) // product.MaxStockUomID = parseUUID(req.MaxStockUomID) // product.LeadTimeUomID = parseUUID(req.LeadTimeUomID) // product.UomToUomID = parseUUID(req.UomToUomID) created, err := s.productRepo.Create(ctx, tx, product) if err != nil { tx.Rollback() return dto.ProductResponse{}, err } err = tx.Commit().Error if err != nil { tx.Rollback() return dto.ProductResponse{}, err } return s.GetById(ctx, created.ID.String()) } func (s *productService) GetById(ctx context.Context, productId string) (dto.ProductResponse, error) { product, err := s.productRepo.GetById(ctx, nil, productId) if err != nil { return dto.ProductResponse{}, err } return mapProductToResponse(product), nil } func (s *productService) GetAll(ctx context.Context, filter query.ProductFilter) ([]dto.ProductResponse, int64, error) { products, total, err := s.productRepo.GetAll(ctx, filter) if err != nil { return nil, 0, err } var responses []dto.ProductResponse for _, p := range products { responses = append(responses, mapProductToResponse(p)) } if len(responses) == 0 { responses = []dto.ProductResponse{} // <-- pastikan slice kosong, bukan nil } return responses, total, nil } func (s *productService) Update(ctx context.Context, req dto.ProductUpdateRequest, productId string) (dto.ProductResponse, error) { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() product, err := s.productRepo.GetById(ctx, tx, productId) if err != nil { tx.Rollback() return dto.ProductResponse{}, err } if req.Name != nil { product.Name = *req.Name } if req.RefNumber != nil { product.RefNumber = *req.RefNumber } if req.SKU != nil { product.SKU = *req.SKU } if req.Description != nil { product.Description = *req.Description } if req.Status != nil { product.Status = *req.Status } if req.IsReturnable != nil { product.IsReturnable = *req.IsReturnable } if req.DimLength != nil { product.DimLength = *req.DimLength } if req.DimWidth != nil { product.DimWidth = *req.DimWidth } if req.DimHeight != nil { product.DimHeight = *req.DimHeight } if req.Weight != nil { product.Weight = *req.Weight } if req.Volume != nil { product.Volume = *req.Volume } if req.MaxStackHeight != nil { product.MaxStackHeight = *req.MaxStackHeight } if req.Temperature != nil { product.Temperature = *req.Temperature } if req.IsHazardous != nil { product.IsHazardous = *req.IsHazardous } if req.MinStock != nil { product.MinStock = *req.MinStock } if req.MaxStock != nil { product.MaxStock = *req.MaxStock } if req.ReplenishType != nil { product.ReplenishType = *req.ReplenishType } if req.CycleCount != nil { product.CycleCount = *req.CycleCount } if req.LotRules != nil { product.LotRules = *req.LotRules } if req.LeadTime != nil { product.LeadTime = *req.LeadTime } if req.MultiplyRate != nil { product.MultiplyRate = *req.MultiplyRate } if req.DivideRate != nil { product.DivideRate = *req.DivideRate } if req.ClientID != nil { product.ClientID = parseUUID(*req.ClientID) } if req.CategoryID != nil { id := parseUUID(*req.CategoryID) product.CategoryID = &id } if req.UomID != nil { id := parseUUID(*req.UomID) product.UomID = &id } if req.DimUomID != nil { id := parseUUID(*req.DimUomID) product.DimUomID = &id } if req.WeightUomID != nil { id := parseUUID(*req.WeightUomID) product.WeightUomID = &id } if req.VolumeUomID != nil { id := parseUUID(*req.VolumeUomID) product.VolumeUomID = &id } if req.MinStockUomID != nil { id := parseUUID(*req.MinStockUomID) product.MinStockUomID = &id } if req.MaxStockUomID != nil { id := parseUUID(*req.MaxStockUomID) product.MaxStockUomID = &id } if req.LeadTimeUomID != nil { id := parseUUID(*req.LeadTimeUomID) product.LeadTimeUomID = &id } if req.UomToUomID != nil { id := parseUUID(*req.UomToUomID) product.UomToUomID = &id } updated, err := s.productRepo.Update(ctx, tx, product) if err != nil { tx.Rollback() return dto.ProductResponse{}, err } err = tx.Commit().Error if err != nil { tx.Rollback() return dto.ProductResponse{}, err } return s.GetById(ctx, updated.ID.String()) } func (s *productService) Delete(ctx context.Context, productId string) error { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() err := s.productRepo.Delete(ctx, tx, productId) if err != nil { tx.Rollback() return err } err = tx.Commit().Error if err != nil { tx.Rollback() return err } return nil } // Helper func parseUUID(id string) uuid.UUID { u, err := uuid.Parse(id) if err != nil { return uuid.Nil } return u } func mapProductToResponse(product entities.MProductEntity) dto.ProductResponse { crossRefs := make([]dto.ProductVendorResponse, 0, len(product.CrossReferences)) for _, v := range product.CrossReferences { crossRefs = append(crossRefs, dto.ProductVendorResponse{ ID: v.Vendor.ID.String(), Name: v.Vendor.Name, Address: v.Vendor.Address, ContactPerson: v.Vendor.ContactPerson, SearchKey: v.Vendor.SearchKey, }) } return dto.ProductResponse{ ID: product.ID.String(), Name: product.Name, RefNumber: product.RefNumber, SKU: product.SKU, Description: product.Description, Status: product.Status, IsReturnable: product.IsReturnable, DimLength: product.DimLength, DimWidth: product.DimWidth, DimHeight: product.DimHeight, Weight: product.Weight, Volume: product.Volume, MaxStackHeight: product.MaxStackHeight, Temperature: product.Temperature, IsHazardous: product.IsHazardous, MinStock: product.MinStock, MaxStock: product.MaxStock, ReplenishType: product.ReplenishType, CycleCount: product.CycleCount, LotRules: product.LotRules, LeadTime: product.LeadTime, MultiplyRate: product.MultiplyRate, DivideRate: product.DivideRate, Client: pkgdto.IdNameResponse{ ID: product.Client.ID.String(), Name: product.Client.Name, }, Category: pkgdto.IdNameResponse{ ID: product.Category.ID.String(), Name: product.Category.Name, }, Uom: pkgdto.IdNameResponse{ ID: product.Uom.ID.String(), Name: product.Uom.Name, }, DimUom: pkgdto.IdNameResponse{ ID: product.DimUom.ID.String(), Name: product.DimUom.Name, }, WeightUom: pkgdto.IdNameResponse{ ID: product.WeightUom.ID.String(), Name: product.WeightUom.Name, }, VolumeUom: pkgdto.IdNameResponse{ ID: product.VolumeUom.ID.String(), Name: product.VolumeUom.Name, }, MinStockUom: pkgdto.IdNameResponse{ ID: product.MinStockUom.ID.String(), Name: product.MinStockUom.Name, }, MaxStockUom: pkgdto.IdNameResponse{ ID: product.MaxStockUom.ID.String(), Name: product.MaxStockUom.Name, }, LeadTimeUom: pkgdto.IdNameResponse{ ID: product.LeadTimeUom.ID.String(), Name: product.LeadTimeUom.Name, }, UomToUom: pkgdto.IdNameResponse{ ID: product.UomToUom.ID.String(), Name: product.UomToUom.Name, }, CrossReferences: crossRefs, } }