Update template
This commit is contained in:
parent
5111fedfa8
commit
4be9c7d73c
@ -55,6 +55,13 @@ type Customer struct {
|
|||||||
OTP string
|
OTP string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomerPoints struct {
|
||||||
|
ID uint64
|
||||||
|
CustomerID uint64
|
||||||
|
TotalPoints int
|
||||||
|
AvailablePoints int
|
||||||
|
}
|
||||||
|
|
||||||
type AuthenticateUser struct {
|
type AuthenticateUser struct {
|
||||||
ID int64
|
ID int64
|
||||||
Token string
|
Token string
|
||||||
|
|||||||
@ -18,6 +18,10 @@ type CustomerRepo interface {
|
|||||||
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
||||||
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
||||||
AddPoints(ctx mycontext.Context, id int64, points int, reference string) 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)
|
FindSequence(ctx mycontext.Context, partnerID int64) (int64, error)
|
||||||
GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error)
|
GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error)
|
||||||
VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, 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
|
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 {
|
func (r *customerRepository) toCustomerDBModel(customer *entity.Customer) models.CustomerDB {
|
||||||
return models.CustomerDB{
|
return models.CustomerDB{
|
||||||
ID: customer.ID,
|
ID: customer.ID,
|
||||||
|
|||||||
@ -21,6 +21,10 @@ type Repository interface {
|
|||||||
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
||||||
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
||||||
AddPoints(ctx mycontext.Context, id int64, points int, reference string) 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)
|
FindSequence(ctx mycontext.Context, partnerID int64) (int64, error)
|
||||||
GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error)
|
GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error)
|
||||||
VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error)
|
VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error)
|
||||||
@ -29,6 +33,7 @@ type Repository interface {
|
|||||||
type Service interface {
|
type Service interface {
|
||||||
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
||||||
AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) 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)
|
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||||
CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, error)
|
CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, error)
|
||||||
GetAllCustomers(ctx mycontext.Context, req *entity.MemberSearch) (*entity.MemberList, int, 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
|
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) {
|
func (s *customerSvc) GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) {
|
||||||
customer, err := s.repo.FindByID(ctx, id)
|
customer, err := s.repo.FindByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -52,7 +52,7 @@ func (s *orderSvc) processPostOrderActions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if order.CustomerID != nil && *order.CustomerID > 0 {
|
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 {
|
if err != nil {
|
||||||
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
|
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 {
|
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 {
|
if order.CustomerID == nil || *order.CustomerID == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customerPoint, err := s.customer.GetCustomerPoints(ctx, *order.CustomerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
customer, err := s.customer.GetCustomer(ctx, *order.CustomerID)
|
customer, err := s.customer.GetCustomer(ctx, *order.CustomerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ContextLogger(ctx).Error("error getting customer details", zap.Error(err))
|
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")
|
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{}{
|
emailData := map[string]interface{}{
|
||||||
"UserName": customer.Name,
|
"UserName": customer.Name,
|
||||||
"PointsName": "ELP",
|
"PointsName": "Point",
|
||||||
"PointsBalance": int(order.Total / 1000),
|
"PointsBalance": newPoint,
|
||||||
"NewPoints": int(order.Total / 1000),
|
"NewPoints": newPoint,
|
||||||
"RedeemLink": "enaklo.co.id",
|
"TotalPoints": customerPoint.TotalPoints,
|
||||||
|
"RedeemLink": "web.enaklo.co.id",
|
||||||
"BranchName": branchName,
|
"BranchName": branchName,
|
||||||
"TransactionNumber": order.ID,
|
"TransactionNumber": order.ID,
|
||||||
"TransactionDate": transactionDate,
|
"TransactionDate": transactionDate,
|
||||||
@ -149,6 +161,8 @@ func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.O
|
|||||||
"TotalPayment": fmt.Sprintf("Rp %s", formatCurrency(order.Total)),
|
"TotalPayment": fmt.Sprintf("Rp %s", formatCurrency(order.Total)),
|
||||||
"ViewTransactionLink": viewTransactionLink,
|
"ViewTransactionLink": viewTransactionLink,
|
||||||
"ExpiryDate": order.CreatedAt.Format("02 January 2006"),
|
"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{
|
return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
|
||||||
|
|||||||
@ -44,6 +44,7 @@ type CustomerService interface {
|
|||||||
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
||||||
AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error
|
AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error
|
||||||
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||||
|
GetCustomerPoints(ctx mycontext.Context, customerID int64) (*entity.CustomerPoints, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransactionService interface {
|
type TransactionService interface {
|
||||||
|
|||||||
@ -175,10 +175,9 @@
|
|||||||
|
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<strong>Cara Menyelesaikan Pendaftaran:</strong><br>
|
<strong>Cara Menyelesaikan Pendaftaran:</strong><br>
|
||||||
1. Tunjukkan email ini kepada staf kasir Enaklo<br>
|
1. Masukkan kode OTP yang dikirim ke email Anda pada layar ini<br>
|
||||||
2. Staf akan memverifikasi identitas Anda<br>
|
2. Verifikasi identitas Anda dan selesaikan pendaftaran<br>
|
||||||
3. Staf akan memasukkan kode OTP ini ke sistem POS<br>
|
3. Pendaftaran member Anda akan segera aktif!
|
||||||
4. Pendaftaran member Anda akan segera aktif!
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text">
|
<div class="text">
|
||||||
|
|||||||
@ -10,85 +10,68 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-ms-text-size-adjust: 100%;
|
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
.container {
|
||||||
|
padding: 40px 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
.content {
|
.content {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
margin: 0px auto;
|
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
padding: 0;
|
margin: 0 auto;
|
||||||
border-radius: 12px;
|
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;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background-color: #d90000;
|
background-color: #d90000;
|
||||||
padding: 25px 30px;
|
padding: 25px 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.greeting {
|
.greeting {
|
||||||
color: white;
|
color: #ffffff;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body-content {
|
.body-content {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
}
|
}
|
||||||
|
.card {
|
||||||
.points-card {
|
|
||||||
background-color: #fff8f0;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.new-points-card {
|
||||||
|
background-color: #fff8f0;
|
||||||
border-left: 5px solid #f46f02;
|
border-left: 5px solid #f46f02;
|
||||||
}
|
}
|
||||||
|
.total-points-card {
|
||||||
.points-heading {
|
background-color: #e8f5e9;
|
||||||
font-size: 16px;
|
border-left: 5px solid #4caf50;
|
||||||
color: #666;
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
}
|
}
|
||||||
|
.card-heading {
|
||||||
.points-value {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 28px;
|
|
||||||
color: #f46f02;
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expiry {
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #888;
|
color: #666666;
|
||||||
|
margin: 0 0 5px;
|
||||||
|
}
|
||||||
|
.card-value {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: bold;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.subtitle {
|
||||||
.total-points-card {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.5;
|
|
||||||
color: #333333;
|
color: #333333;
|
||||||
|
line-height: 1.5;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button {
|
.cta-button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -100,45 +83,69 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555555;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
|
||||||
text-align: center;
|
|
||||||
color: #808080;
|
color: #808080;
|
||||||
margin-top: 30px;
|
text-align: center;
|
||||||
padding-top: 20px;
|
padding: 20px;
|
||||||
border-top: 1px solid #eeeeee;
|
border-top: 1px solid #eeeeee;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
color: #f46f02;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div style="padding: 40px 20px; background-color: #f5f5f5;">
|
<div class="container">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
||||||
|
<!-- HEADER -->
|
||||||
<div class="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>
|
<h1 class="greeting">Hai {{ .UserName }}!</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- BODY -->
|
||||||
<div class="body-content">
|
<div class="body-content">
|
||||||
<div class="points-card">
|
|
||||||
<p class="points-heading">Kamu dapat poin</p>
|
<!-- New Points Earned -->
|
||||||
<p class="points-value">{{ .NewPoints }} {{ .PointsName }}</p>
|
<div class="card new-points-card">
|
||||||
<p class="expiry">Berlaku sampai {{ .ExpiryDate }}</p>
|
<p class="card-heading">Poin baru yang kamu dapat</p>
|
||||||
|
<p class="card-value">{{ .NewPoints }} {{ .PointsName }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="message">
|
<div class="card total-points-card">
|
||||||
Kamu bisa pakai poinnya untuk memotong jumlah transaksimu atau ditukarkan dengan Deals. Yuk, pakai sekarang!
|
<p class="card-heading">Total Poin Undian</p>
|
||||||
|
<p class="card-value">{{ .TotalPoints }} {{ .PointsName }}</p>
|
||||||
</div>
|
</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">
|
<div class="footer">
|
||||||
Email ini dikirim secara otomatis. Mohon jangan membalas email ini.<br>
|
Email ini dikirim 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>.
|
Butuh bantuan? Hubungi tim support kami di
|
||||||
|
<a href="mailto:support@enaklo.com">support@enaklo.com</a>.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user