package customer import ( "enaklo-pos-be/internal/common/logger" "enaklo-pos-be/internal/common/mycontext" "enaklo-pos-be/internal/constants" "enaklo-pos-be/internal/entity" "enaklo-pos-be/internal/utils" "github.com/pkg/errors" "go.uber.org/zap" "strings" ) type Repository interface { Create(ctx mycontext.Context, customer *entity.Customer) (*entity.Customer, error) FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error) FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error) FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error) AddPoints(ctx mycontext.Context, id int64, points int) error FindSequence(ctx mycontext.Context, partnerID int64) (int64, error) GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error) } type Service interface { ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error) AddPoints(ctx mycontext.Context, customerID int64, points int) error GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, error) GetAllCustomers(ctx mycontext.Context, req *entity.MemberSearch) (*entity.MemberList, int, error) } type customerSvc struct { repo Repository } func New(repo Repository) Service { return &customerSvc{ repo: repo, } } func (s *customerSvc) ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error) { if req.Email == "" && req.PhoneNumber == "" { return 0, nil } if req.ID != nil && *req.ID > 0 { customer, err := s.repo.FindByID(ctx, *req.ID) if err != nil { if !strings.Contains(err.Error(), "not found") { return 0, errors.Wrap(err, "failed to find customer by ID") } } else { return customer.ID, nil } } if req.PhoneNumber != "" { customer, err := s.repo.FindByPhone(ctx, req.PhoneNumber) if err != nil { if !strings.Contains(err.Error(), "not found") { return 0, errors.Wrap(err, "failed to find customer by phone") } } else { return customer.ID, nil } } if req.Email != "" { customer, err := s.repo.FindByEmail(ctx, req.Email) if err != nil { if !strings.Contains(err.Error(), "not found") { return 0, errors.Wrap(err, "failed to find customer by email") } } else { return customer.ID, nil } } if req.Name == "" { return 0, errors.New("customer name is required to create a new customer") } lastSeq, err := s.repo.FindSequence(ctx, *ctx.GetPartnerID()) if err != nil { return 0, errors.New("failed to resolve customer sequence") } newCustomer := &entity.Customer{ Name: req.Name, Email: req.Email, Phone: req.PhoneNumber, Points: 0, CreatedAt: constants.TimeNow(), UpdatedAt: constants.TimeNow(), CustomerID: utils.GenerateMemberID(ctx, *ctx.GetPartnerID(), lastSeq), BirthDate: req.BirthDate, } customer, err := s.repo.Create(ctx, newCustomer) if err != nil { logger.ContextLogger(ctx).Error("failed to create customer", zap.Error(err)) return 0, errors.Wrap(err, "failed to create customer") } return customer.ID, nil } func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int) error { if points <= 0 { return nil } err := s.repo.AddPoints(ctx, customerID, points) if err != nil { return errors.Wrap(err, "failed to add points to customer") } return nil } func (s *customerSvc) GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) { customer, err := s.repo.FindByID(ctx, id) if err != nil { return nil, errors.Wrap(err, "failed to get customer") } return customer, nil } func (s *customerSvc) CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, error) { logger.ContextLogger(ctx).Info("checking customer existence before registration", zap.String("email", req.Email), zap.String("phone", req.PhoneNumber)) if req.Email == "" && req.PhoneNumber == "" { return nil, errors.New("email dan phone number is mandatory") } response := &entity.CustomerCheckResponse{ Exists: false, Customer: nil, } if req.PhoneNumber != "" { customer, err := s.repo.FindByPhone(ctx, req.PhoneNumber) if err != nil { if !strings.Contains(err.Error(), "not found") { logger.ContextLogger(ctx).Error("error checking customer by phone", zap.Error(err)) return nil, errors.Wrap(err, "failed to find customer by phone") } } else { logger.ContextLogger(ctx).Info("found existing customer by phone", zap.Int64("customerId", customer.ID)) return &entity.CustomerCheckResponse{ Exists: true, Customer: customer, Message: "Customer already exists with this phone number", }, nil } } if req.Email != "" { customer, err := s.repo.FindByEmail(ctx, req.Email) if err != nil { if !strings.Contains(err.Error(), "not found") { logger.ContextLogger(ctx).Error("error checking customer by email", zap.Error(err)) return nil, errors.Wrap(err, "failed to find customer by email") } } else { logger.ContextLogger(ctx).Info("found existing customer by email", zap.Int64("customerId", customer.ID)) return &entity.CustomerCheckResponse{ Exists: true, Customer: customer, Message: "Customer already exists with this email", }, nil } } return response, nil } func (s *customerSvc) GetAllCustomers(ctx mycontext.Context, req *entity.MemberSearch) (*entity.MemberList, int, error) { if req.Limit <= 0 { req.Limit = 10 } if req.Offset < 0 { req.Offset = 0 } customers, totalCount, err := s.repo.GetAllCustomers(ctx, *req) if err != nil { logger.ContextLogger(ctx).Error("failed to retrieve customers", zap.Error(err), zap.String("search", req.Search), ) return nil, 0, errors.Wrap(err, "failed to get customers") } return &customers, totalCount, nil }