Update template

This commit is contained in:
aditya.siregar 2025-05-06 15:21:12 +07:00
parent 5111fedfa8
commit 4be9c7d73c
7 changed files with 139 additions and 68 deletions

View File

@ -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

View File

@ -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,

View File

@ -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 {

View File

@ -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{

View File

@ -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 {

View File

@ -175,10 +175,9 @@
<div class="info-box">
<strong>Cara Menyelesaikan Pendaftaran:</strong><br>
1. Tunjukkan email ini kepada staf kasir Enaklo<br>
2. Staf akan memverifikasi identitas Anda<br>
3. Staf akan memasukkan kode OTP ini ke sistem POS<br>
4. Pendaftaran member Anda akan segera aktif!
1. Masukkan kode OTP yang dikirim ke email Anda pada layar ini<br>
2. Verifikasi identitas Anda dan selesaikan pendaftaran<br>
3. Pendaftaran member Anda akan segera aktif!
</div>
<div class="text">

View File

@ -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;
}
</style>
</head>
<body>
<div style="padding: 40px 20px; background-color: #f5f5f5;">
<div class="container">
<div class="content">
<!-- HEADER -->
<div class="header">
<img src="https://res.cloudinary.com/dl0wpumax/image/upload/c_thumb,w_200,g_face/v1741363977/61747686_5_vtz0n4.png" alt="Enaklo Logo" class="logo">
<img src="https://res.cloudinary.com/dl0wpumax/image/upload/c_thumb,w_200,g_face/v1741363977/61747686_5_vtz0n4.png"
alt="Enaklo Logo" class="logo">
<h1 class="greeting">Hai {{ .UserName }}!</h1>
</div>
<!-- BODY -->
<div class="body-content">
<div class="points-card">
<p class="points-heading">Kamu dapat poin</p>
<p class="points-value">{{ .NewPoints }} {{ .PointsName }}</p>
<p class="expiry">Berlaku sampai {{ .ExpiryDate }}</p>
<!-- New Points Earned -->
<div class="card new-points-card">
<p class="card-heading">Poin baru yang kamu dapat</p>
<p class="card-value">{{ .NewPoints }} {{ .PointsName }}</p>
</div>
<div class="message">
Kamu bisa pakai poinnya untuk memotong jumlah transaksimu atau ditukarkan dengan Deals. Yuk, pakai sekarang!
<div class="card total-points-card">
<p class="card-heading">Total Poin Undian</p>
<p class="card-value">{{ .TotalPoints }} {{ .PointsName }}</p>
</div>
<a href="{{ .RedeemLink }}" class="cta-button">Pakai Sekarang</a>
<p class="subtitle">
Poin kamu akan digunakan untuk undian pada tanggal <strong>{{ .UndianDate }}</strong>.
</p>
<p class="info">
Nantikan undian pada tanggal <strong>{{ .UndianDate }}</strong>
dan kunjungi <a href="{{ .WebURL }}">{{ .WebURL }}</a> untuk informasi lebih lanjut.
</p>
<!-- FOOTER -->
<div class="footer">
Email ini dikirim secara otomatis. Mohon jangan membalas email ini.<br>
Butuh bantuan? Hubungi tim support kami di <a href="mailto:support@enaklo.com" style="color: #f46f02;">support@enaklo.com</a>.
Email ini dikirim otomatis. Mohon jangan membalas email ini.<br>
Butuh bantuan? Hubungi tim support kami di
<a href="mailto:support@enaklo.com">support@enaklo.com</a>.
</div>
</div>
</div>
</div>
</body>
</html>
</html>