diff --git a/internal/entity/user.go b/internal/entity/user.go index 684fd54..d951ce7 100644 --- a/internal/entity/user.go +++ b/internal/entity/user.go @@ -55,6 +55,13 @@ type Customer struct { OTP string } +type CustomerPoints struct { + ID uint64 + CustomerID uint64 + TotalPoints int + AvailablePoints int +} + type AuthenticateUser struct { ID int64 Token string diff --git a/internal/repository/customer_repo.go b/internal/repository/customer_repo.go index 67a6eb4..bc0937b 100644 --- a/internal/repository/customer_repo.go +++ b/internal/repository/customer_repo.go @@ -18,6 +18,10 @@ type CustomerRepo interface { 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, reference string) error + GetPointsByCustomerID( + ctx mycontext.Context, + customerID int64, + ) (*entity.CustomerPoints, error) FindSequence(ctx mycontext.Context, partnerID int64) (int64, error) GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error) VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error) @@ -172,6 +176,31 @@ func (r *customerRepository) AddPoints(ctx mycontext.Context, customerID int64, return nil } +func (r *customerRepository) GetPointsByCustomerID( + ctx mycontext.Context, + customerID int64, +) (*entity.CustomerPoints, error) { + var cp models.CustomerPointsDB + + if err := r.db.Where("customer_id = ?", customerID).First(&cp).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errors.New("customer not found") + } + return nil, errors.Wrap(err, "failed to find customer point by customer_id") + } + + return r.toDomainCustomerPoint(cp), nil +} + +func (r *customerRepository) toDomainCustomerPoint(dbModel models.CustomerPointsDB) *entity.CustomerPoints { + return &entity.CustomerPoints{ + ID: dbModel.ID, + CustomerID: dbModel.CustomerID, + TotalPoints: dbModel.TotalPoints, + AvailablePoints: dbModel.AvailablePoints, + } +} + func (r *customerRepository) toCustomerDBModel(customer *entity.Customer) models.CustomerDB { return models.CustomerDB{ ID: customer.ID, diff --git a/internal/services/v2/customer/customer.go b/internal/services/v2/customer/customer.go index 5afc24b..fb82925 100644 --- a/internal/services/v2/customer/customer.go +++ b/internal/services/v2/customer/customer.go @@ -21,6 +21,10 @@ type Repository interface { 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, reference string) error + GetPointsByCustomerID( + ctx mycontext.Context, + customerID int64, + ) (*entity.CustomerPoints, error) FindSequence(ctx mycontext.Context, partnerID int64) (int64, error) GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error) VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error) @@ -29,6 +33,7 @@ type Repository interface { type Service interface { ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error + GetCustomerPoints(ctx mycontext.Context, customerID int64) (*entity.CustomerPoints, 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) @@ -188,6 +193,15 @@ func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points return nil } +func (s *customerSvc) GetCustomerPoints(ctx mycontext.Context, customerID int64) (*entity.CustomerPoints, error) { + cp, err := s.repo.GetPointsByCustomerID(ctx, customerID) + if err != nil { + return nil, errors.Wrap(err, "failed to add points to customer") + } + + return cp, nil +} + func (s *customerSvc) GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) { customer, err := s.repo.FindByID(ctx, id) if err != nil { diff --git a/internal/services/v2/order/execute_order.go b/internal/services/v2/order/execute_order.go index 268ea41..53bf5d2 100644 --- a/internal/services/v2/order/execute_order.go +++ b/internal/services/v2/order/execute_order.go @@ -52,7 +52,7 @@ func (s *orderSvc) processPostOrderActions( } if order.CustomerID != nil && *order.CustomerID > 0 { - err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/1000), fmt.Sprintf("TRX #%s", trx.ID)) + err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/50000), fmt.Sprintf("TRX #%s", trx.ID)) if err != nil { logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err)) } @@ -84,10 +84,21 @@ func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, po } func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error { + newPoint := int(order.Total / 50000) + + if newPoint <= 0 { + return nil + } + if order.CustomerID == nil || *order.CustomerID == 0 { return nil } + customerPoint, err := s.customer.GetCustomerPoints(ctx, *order.CustomerID) + if err != nil { + return nil + } + customer, err := s.customer.GetCustomer(ctx, *order.CustomerID) if err != nil { logger.ContextLogger(ctx).Error("error getting customer details", zap.Error(err)) @@ -133,14 +144,15 @@ func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.O } transactionDate := transaction.CreatedAt.Format("02 January 2006 15:04") - viewTransactionLink := fmt.Sprintf("https://enaklo.co.id/transaction/%s", transaction.ID) + viewTransactionLink := "https://web.enaklo.co.id" emailData := map[string]interface{}{ "UserName": customer.Name, - "PointsName": "ELP", - "PointsBalance": int(order.Total / 1000), - "NewPoints": int(order.Total / 1000), - "RedeemLink": "enaklo.co.id", + "PointsName": "Point", + "PointsBalance": newPoint, + "NewPoints": newPoint, + "TotalPoints": customerPoint.TotalPoints, + "RedeemLink": "web.enaklo.co.id", "BranchName": branchName, "TransactionNumber": order.ID, "TransactionDate": transactionDate, @@ -149,6 +161,8 @@ func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.O "TotalPayment": fmt.Sprintf("Rp %s", formatCurrency(order.Total)), "ViewTransactionLink": viewTransactionLink, "ExpiryDate": order.CreatedAt.Format("02 January 2006"), + "UndianDate": "17 Mei 2025", + "WebURL": "https://web.enaklo.co.id/undian", } return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{ diff --git a/internal/services/v2/order/order.go b/internal/services/v2/order/order.go index ac087da..26dea36 100644 --- a/internal/services/v2/order/order.go +++ b/internal/services/v2/order/order.go @@ -44,6 +44,7 @@ type CustomerService interface { ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) + GetCustomerPoints(ctx mycontext.Context, customerID int64) (*entity.CustomerPoints, error) } type TransactionService interface { diff --git a/templates/member_registration_otp.html b/templates/member_registration_otp.html index 8e579a0..a66ff8c 100644 --- a/templates/member_registration_otp.html +++ b/templates/member_registration_otp.html @@ -175,10 +175,9 @@
Cara Menyelesaikan Pendaftaran:
- 1. Tunjukkan email ini kepada staf kasir Enaklo
- 2. Staf akan memverifikasi identitas Anda
- 3. Staf akan memasukkan kode OTP ini ke sistem POS
- 4. Pendaftaran member Anda akan segera aktif! + 1. Masukkan kode OTP yang dikirim ke email Anda pada layar ini
+ 2. Verifikasi identitas Anda dan selesaikan pendaftaran
+ 3. Pendaftaran member Anda akan segera aktif!
diff --git a/templates/monthly_points.html b/templates/monthly_points.html index f286af0..f9bdfbf 100644 --- a/templates/monthly_points.html +++ b/templates/monthly_points.html @@ -10,85 +10,68 @@ body { margin: 0; padding: 0; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; background-color: #f5f5f5; font-family: Arial, sans-serif; } - + .container { + padding: 40px 20px; + background-color: #f5f5f5; + } .content { background-color: #ffffff; - margin: 0px auto; max-width: 600px; - padding: 0; + margin: 0 auto; border-radius: 12px; - box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); overflow: hidden; } - .header { background-color: #d90000; padding: 25px 30px; text-align: center; } - .logo { width: 120px; margin-bottom: 15px; } - .greeting { - color: white; + color: #ffffff; font-size: 22px; font-weight: bold; margin: 0; } - .body-content { padding: 30px; } - - .points-card { - background-color: #fff8f0; + .card { border-radius: 10px; padding: 20px; - margin-bottom: 25px; + margin-bottom: 20px; + } + .new-points-card { + background-color: #fff8f0; border-left: 5px solid #f46f02; } - - .points-heading { - font-size: 16px; - color: #666; - margin: 0 0 5px 0; + .total-points-card { + background-color: #e8f5e9; + border-left: 5px solid #4caf50; } - - .points-value { - font-weight: bold; - font-size: 28px; - color: #f46f02; - margin: 0 0 5px 0; - } - - .expiry { + .card-heading { font-size: 14px; - color: #888; + color: #666666; + margin: 0 0 5px; + } + .card-value { + font-size: 26px; + font-weight: bold; margin: 0; } - - .total-points-card { - background-color: #f5f5f5; - border-radius: 10px; - padding: 20px; - margin-bottom: 25px; - } - - .message { + .subtitle { font-size: 16px; - line-height: 1.5; color: #333333; + line-height: 1.5; margin-bottom: 25px; } - .cta-button { display: block; width: 100%; @@ -100,48 +83,72 @@ text-align: center; text-decoration: none; border-radius: 8px; + margin-bottom: 20px; + } + .info { + font-size: 14px; + color: #555555; + line-height: 1.5; + margin-bottom: 25px; } - .footer { font-size: 12px; - line-height: 1.5; - text-align: center; color: #808080; - margin-top: 30px; - padding-top: 20px; + text-align: center; + padding: 20px; border-top: 1px solid #eeeeee; } + a { + color: #f46f02; + text-decoration: none; + } -
+
+ +
- +

Hai {{ .UserName }}!

+
-
-

Kamu dapat poin

-

{{ .NewPoints }} {{ .PointsName }}

-

Berlaku sampai {{ .ExpiryDate }}

+ + +
+

Poin baru yang kamu dapat

+

{{ .NewPoints }} {{ .PointsName }}

-
- Kamu bisa pakai poinnya untuk memotong jumlah transaksimu atau ditukarkan dengan Deals. Yuk, pakai sekarang! +
+

Total Poin Undian

+

{{ .TotalPoints }} {{ .PointsName }}

- Pakai Sekarang +

+ Poin kamu akan digunakan untuk undian pada tanggal {{ .UndianDate }}. +

+

+ Nantikan undian pada tanggal {{ .UndianDate }} + dan kunjungi {{ .WebURL }} untuk informasi lebih lanjut. +

+ + +
- \ No newline at end of file +