This commit is contained in:
aditya.siregar 2025-04-05 11:28:06 +08:00
parent 118ec58521
commit c642c5c61b
35 changed files with 1194 additions and 212 deletions

View File

@ -5,44 +5,48 @@ import "net/http"
type ErrType string type ErrType string
const ( const (
errRequestTimeOut ErrType = "Request Timeout to 3rd Party" errRequestTimeOut ErrType = "Request Timeout to 3rd Party"
errConnectTimeOut ErrType = "Connect Timeout to 3rd Party" errConnectTimeOut ErrType = "Connect Timeout to 3rd Party"
errFailedExternalCall ErrType = "Failed response from 3rd Party call" errFailedExternalCall ErrType = "Failed response from 3rd Party call"
errExternalCall ErrType = "error on 3rd Party call" errExternalCall ErrType = "error on 3rd Party call"
errInvalidRequest ErrType = "Invalid Request" errInvalidRequest ErrType = "Invalid Request"
errBadRequest ErrType = "Bad Request" errBadRequest ErrType = "Bad Request"
errOrderNotFound ErrType = "Astria order is not found" errOrderNotFound ErrType = "Astria order is not found"
errCheckoutIDNotDefined ErrType = "Checkout client id not found" errCheckoutIDNotDefined ErrType = "Checkout client id not found"
errInternalServer ErrType = "Internal Server error" errInternalServer ErrType = "Internal Server error"
errExternalServer ErrType = "External Server error" errExternalServer ErrType = "External Server error"
errUserIsNotFound ErrType = "User is not found" errUserIsNotFound ErrType = "User is not found"
errInvalidLogin ErrType = "User email or password is invalid" errInvalidLogin ErrType = "User email or password is invalid"
errUnauthorized ErrType = "Unauthorized" errUnauthorized ErrType = "Unauthorized"
errInsufficientBalance ErrType = "Insufficient Balance" errInsufficientBalance ErrType = "Insufficient Balance"
errInactivePartner ErrType = "Partner's license is invalid or has expired. Please contact Admin Support." errInactivePartner ErrType = "Partner's license is invalid or has expired. Please contact Admin Support."
errTicketAlreadyUsed ErrType = "Ticket Already Used." errTicketAlreadyUsed ErrType = "Ticket Already Used."
errProductIsRequired ErrType = "Product" errProductIsRequired ErrType = "Product"
errEmailAndPhoneNumberRequired ErrType = "Email or Phone is required" errEmailAndPhoneNumberRequired ErrType = "Email or Phone is required"
errEmailAlreadyRegistered ErrType = "Email is already registered"
errPhoneNumberAlreadyRegistered ErrType = "Phone is already registered"
) )
var ( var (
ErrorBadRequest = NewServiceException(errBadRequest) ErrorBadRequest = NewServiceException(errBadRequest)
ErrorInvalidRequest = NewServiceException(errInvalidRequest) ErrorInvalidRequest = NewServiceException(errInvalidRequest)
ErrorExternalRequest = NewServiceException(errExternalServer) ErrorExternalRequest = NewServiceException(errExternalServer)
ErrorUnauthorized = NewServiceException(errUnauthorized) ErrorUnauthorized = NewServiceException(errUnauthorized)
ErrorOrderNotFound = NewServiceException(errOrderNotFound) ErrorOrderNotFound = NewServiceException(errOrderNotFound)
ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined) ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined)
ErrorRequestTimeout = NewServiceException(errRequestTimeOut) ErrorRequestTimeout = NewServiceException(errRequestTimeOut)
ErrorExternalCall = NewServiceException(errExternalCall) ErrorExternalCall = NewServiceException(errExternalCall)
ErrorFailedExternalCall = NewServiceException(errFailedExternalCall) ErrorFailedExternalCall = NewServiceException(errFailedExternalCall)
ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut) ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut)
ErrorInternalServer = NewServiceException(errInternalServer) ErrorInternalServer = NewServiceException(errInternalServer)
ErrorUserIsNotFound = NewServiceException(errUserIsNotFound) ErrorUserIsNotFound = NewServiceException(errUserIsNotFound)
ErrorUserInvalidLogin = NewServiceException(errInvalidLogin) ErrorUserInvalidLogin = NewServiceException(errInvalidLogin)
ErrorInsufficientBalance = NewServiceException(errInsufficientBalance) ErrorInsufficientBalance = NewServiceException(errInsufficientBalance)
ErrorInvalidLicense = NewServiceException(errInactivePartner) ErrorInvalidLicense = NewServiceException(errInactivePartner)
ErrorTicketInvalidOrAlreadyUsed = NewServiceException(errTicketAlreadyUsed) ErrorTicketInvalidOrAlreadyUsed = NewServiceException(errTicketAlreadyUsed)
ErrorPhoneNumberEmailIsRequired = NewServiceException(errEmailAndPhoneNumberRequired) ErrorPhoneNumberEmailIsRequired = NewServiceException(errEmailAndPhoneNumberRequired)
ErrorPhoneNumberIsAlreadyRegistered = NewServiceException(errPhoneNumberAlreadyRegistered)
ErrorEmailIsAlreadyRegistered = NewServiceException(errEmailAlreadyRegistered)
) )
type Error interface { type Error interface {

View File

@ -0,0 +1,2 @@
package entity

View File

@ -0,0 +1,29 @@
package entity
import "time"
type InProgressOrder struct {
ID string
PartnerID int64
CustomerID *int64
CustomerName string
CreatedBy int64
PaymentType string
PaymentProvider string
OrderItems []InProgressOrderItem
Payment Payment
User User
Source string
OrderType string
TableNumber string
CreatedAt time.Time
UpdatedAt time.Time
}
type InProgressOrderItem struct {
ID int64
InProgressOrderID int64
ItemID int64
Quantity int
Product *Product
}

View File

@ -5,24 +5,29 @@ import (
) )
type Order struct { type Order struct {
ID int64 `gorm:"primaryKey;autoIncrement;column:id"` ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
PartnerID int64 `gorm:"type:int;column:partner_id"` PartnerID int64 `gorm:"type:int;column:partner_id"`
Status string `gorm:"type:varchar;column:status"` Status string `gorm:"type:varchar;column:status"`
Amount float64 `gorm:"type:numeric;not null;column:amount"` Amount float64 `gorm:"type:numeric;not null;column:amount"`
Total float64 `gorm:"type:numeric;not null;column:total"` Total float64 `gorm:"type:numeric;not null;column:total"`
Fee float64 `gorm:"type:numeric;not null;column:fee"` Fee float64 `gorm:"type:numeric;not null;column:fee"`
CustomerID *int64 CustomerID *int64
InquiryID *string CustomerName string
Site *Site `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"` InquiryID *string
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"` Site *Site `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"`
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"` CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
CreatedBy int64 `gorm:"type:int;column:created_by"` UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
PaymentType string `gorm:"type:varchar;column:payment_type"` CreatedBy int64 `gorm:"type:int;column:created_by"`
UpdatedBy int64 `gorm:"type:int;column:updated_by"` PaymentType string `gorm:"type:varchar;column:payment_type"`
OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` PaymentProvider string `gorm:"type:varchar;column:payment_provider"`
Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` UpdatedBy int64 `gorm:"type:int;column:updated_by"`
User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"` OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
Source string `gorm:"type:varchar;column:source"` Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"`
Source string `gorm:"type:varchar;column:source"`
OrderType string `gorm:"type:varchar;column:order_type"`
TableNumber string
InProgressOrderID string
} }
type OrderDB struct { type OrderDB struct {
@ -97,6 +102,9 @@ type OrderRequest struct {
CustomerName string CustomerName string
CustomerEmail string CustomerEmail string
CustomerPhoneNumber string CustomerPhoneNumber string
TableNumber string
PaymentProvider string
OrderType string
} }
type OrderItemRequest struct { type OrderItemRequest struct {
@ -111,11 +119,7 @@ type OrderExecuteRequest struct {
} }
func (o *Order) SetExecutePaymentStatus() { func (o *Order) SetExecutePaymentStatus() {
if o.PaymentType == "CASH" { o.Status = "PAID"
o.Status = "PAID"
return
}
o.Status = "PENDING"
} }
type CallbackRequest struct { type CallbackRequest struct {

View File

@ -23,6 +23,9 @@ type OrderInquiry struct {
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
ExpiresAt time.Time `json:"expires_at"` ExpiresAt time.Time `json:"expires_at"`
OrderItems []OrderItem `json:"order_items"` OrderItems []OrderItem `json:"order_items"`
PaymentProvider string `json:"payment_provider"`
TableNumber string `json:"table_number"`
OrderType string `json:"order_type"`
} }
type OrderCalculation struct { type OrderCalculation struct {
@ -48,6 +51,9 @@ func NewOrderInquiry(
customerName string, customerName string,
customerPhoneNumber string, customerPhoneNumber string,
customerEmail string, customerEmail string,
paymentProvider string,
tableNumber string,
orderType string,
) *OrderInquiry { ) *OrderInquiry {
return &OrderInquiry{ return &OrderInquiry{
ID: constants.GenerateUUID(), ID: constants.GenerateUUID(),
@ -66,6 +72,9 @@ func NewOrderInquiry(
CustomerName: customerName, CustomerName: customerName,
CustomerEmail: customerEmail, CustomerEmail: customerEmail,
CustomerPhoneNumber: customerPhoneNumber, CustomerPhoneNumber: customerPhoneNumber,
PaymentProvider: paymentProvider,
TableNumber: tableNumber,
OrderType: orderType,
} }
} }
@ -80,22 +89,24 @@ func (oi *OrderInquiry) AddOrderItem(item OrderItemRequest, product *Product) {
}) })
} }
func (i *OrderInquiry) ToOrder(paymentMethod string) *Order { func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
now := time.Now() now := time.Now()
order := &Order{ order := &Order{
PartnerID: i.PartnerID, PartnerID: i.PartnerID,
CustomerID: &i.CustomerID, CustomerID: &i.CustomerID,
InquiryID: &i.ID, InquiryID: &i.ID,
Status: constants.StatusPaid, Status: constants.StatusPaid,
Amount: i.Amount, Amount: i.Amount,
Fee: i.Fee, Fee: i.Fee,
Total: i.Total, Total: i.Total,
PaymentType: paymentMethod, PaymentType: paymentMethod,
Source: i.Source, PaymentProvider: paymentProvider,
CreatedBy: i.CreatedBy, Source: i.Source,
CreatedAt: now, CreatedBy: i.CreatedBy,
OrderItems: make([]OrderItem, len(i.OrderItems)), CreatedAt: now,
OrderItems: make([]OrderItem, len(i.OrderItems)),
OrderType: i.OrderType,
} }
for idx, item := range i.OrderItems { for idx, item := range i.OrderItems {

View File

@ -30,27 +30,29 @@ type User struct {
} }
type Customer struct { type Customer struct {
ID int64 ID int64
Name string Name string
Email string Email string
Password string Password string
Phone string Phone string
Points int Points int
Status userstatus.UserStatus Status userstatus.UserStatus
NIK string NIK string
UserType string UserType string
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
RoleID role.Role RoleID role.Role
PhoneNumber string PhoneNumber string
RoleName string RoleName string
PartnerID *int64 PartnerID *int64
SiteID *int64 SiteID *int64
SiteName string SiteName string
PartnerName string PartnerName string
ResetPassword bool ResetPassword bool
CustomerID string CustomerID string
BirthDate time.Time BirthDate time.Time
VerificationID string
OTP string
} }
type AuthenticateUser struct { type AuthenticateUser struct {
@ -117,3 +119,9 @@ func (u User) HashedPassword(password string) (string, error) {
return string(hashedPassword), nil return string(hashedPassword), nil
} }
func (c Customer) HashedPassword() string {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(c.Password), bcrypt.DefaultCost)
return string(hashedPassword)
}

View File

@ -1,6 +1,8 @@
package customerauth package customerauth
import ( import (
auth2 "enaklo-pos-be/internal/handlers/request"
"enaklo-pos-be/internal/services/v2/customer"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -8,7 +10,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"enaklo-pos-be/internal/common/errors" "enaklo-pos-be/internal/common/errors"
auth2 "enaklo-pos-be/internal/handlers/request"
"enaklo-pos-be/internal/handlers/response" "enaklo-pos-be/internal/handlers/response"
"enaklo-pos-be/internal/services" "enaklo-pos-be/internal/services"
) )
@ -16,6 +17,7 @@ import (
type AuthHandler struct { type AuthHandler struct {
service services.Auth service services.Auth
userService services.User userService services.User
customerSvc customer.Service
} }
func (a *AuthHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { func (a *AuthHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
@ -24,12 +26,14 @@ func (a *AuthHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
authRoute.POST("/forgot-password", a.ForgotPassword) authRoute.POST("/forgot-password", a.ForgotPassword)
authRoute.POST("/reset-password", jwt, a.ResetPassword) authRoute.POST("/reset-password", jwt, a.ResetPassword)
authRoute.POST("/register", a.Register) authRoute.POST("/register", a.Register)
authRoute.POST("/verify", a.VerifyRegistration)
} }
func NewAuthHandler(service services.Auth, userService services.User) *AuthHandler { func NewAuthHandler(service services.Auth, userService services.User, customerSvc customer.Service) *AuthHandler {
return &AuthHandler{ return &AuthHandler{
service: service, service: service,
userService: userService, userService: userService,
customerSvc: customerSvc,
} }
} }
@ -147,31 +151,47 @@ func (h *AuthHandler) ResetPassword(c *gin.Context) {
} }
func (h *AuthHandler) Register(c *gin.Context) { func (h *AuthHandler) Register(c *gin.Context) {
var req auth2.UserRegister var req auth2.CustomerRegister
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest) response.ErrorWrapper(c, errors.ErrorBadRequest)
return return
} }
ctx := auth2.GetMyContext(c) ctx := auth2.GetMyContext(c)
res, err := h.userService.Create(ctx, req.ToEntity()) customer, err := h.customerSvc.RegistrationMember(ctx, req.ToEntity())
if err != nil { if err != nil {
response.ErrorWrapper(c, err) response.ErrorWrapper(c, err)
return return
} }
resp := response.UserRegister{ c.JSON(http.StatusOK, response.BaseResponse{
ID: res.ID, Success: true,
Name: res.Name, Status: http.StatusOK,
Email: res.Email, Data: response.CustomerRegistrationResp{
Status: string(res.Status), EmailVerificationRequired: true,
CreatedAt: res.CreatedAt, PhoneVerificationRequired: false,
UpdatedAt: res.UpdatedAt, VerificationID: customer.VerificationID,
},
})
}
func (h *AuthHandler) VerifyRegistration(c *gin.Context) {
var req auth2.VerifyEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
ctx := auth2.GetMyContext(c)
err := h.customerSvc.VerifyOTP(ctx, req.VerificationID, req.OTPCode)
if err != nil {
response.ErrorWrapper(c, err)
return
} }
c.JSON(http.StatusOK, response.BaseResponse{ c.JSON(http.StatusOK, response.BaseResponse{
Success: true, Success: true,
Status: http.StatusOK, Status: http.StatusOK,
Data: resp, Message: "Email verification successful",
}) })
} }

View File

@ -0,0 +1,173 @@
package http
import (
"enaklo-pos-be/internal/common/errors"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/handlers/request"
"enaklo-pos-be/internal/handlers/response"
"enaklo-pos-be/internal/services/v2/inprogress_order"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"net/http"
"strconv"
)
type InProgressOrderHandler struct {
service inprogress_order.InProgressOrderService
}
func NewInProgressOrderHandler(service inprogress_order.InProgressOrderService) *InProgressOrderHandler {
return &InProgressOrderHandler{
service: service,
}
}
func (h *InProgressOrderHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
route := group.Group("/inprogress-order")
route.POST("/save", jwt, h.Save)
route.GET("/list", jwt, h.GetByPartnerID)
}
type CreateInProgressOrderRequest struct {
CustomerID *int64 `json:"customer_id"`
CustomerName string `json:"customer_name" validate:"required_without=CustomerID"`
CustomerEmail string `json:"customer_email"`
CustomerPhoneNumber string `json:"customer_phone_number"`
PaymentMethod string `json:"payment_method"`
OrderItems []InProgressOrderItemRequest `json:"order_items" validate:"required,min=1,dive"`
OrderType string `json:"order_type"`
PaymentProvider string `json:"payment_provider"`
TableNumber string `json:"table_number"`
InProgressOrderID string `json:"in_progress_order_id"`
}
type InProgressOrderItemRequest struct {
ProductID int64 `json:"product_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
}
type UpdateInProgressOrderRequest struct {
Status string `json:"status" validate:"required"`
Amount float64 `json:"amount" validate:"required,min=0"`
Fee float64 `json:"fee" validate:"min=0"`
Total float64 `json:"total" validate:"required,min=0"`
PaymentType string `json:"payment_type" validate:"required"`
OrderItems []InProgressOrderItemRequest `json:"order_items" validate:"required,min=1,dive"`
}
func (h *InProgressOrderHandler) Save(c *gin.Context) {
ctx := request.GetMyContext(c)
userID := ctx.RequestedBy()
partnerID := ctx.GetPartnerID()
var req CreateInProgressOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
validate := validator.New()
if err := validate.Struct(req); err != nil {
response.ErrorWrapper(c, err)
return
}
orderItems := make([]entity.InProgressOrderItem, len(req.OrderItems))
for i, item := range req.OrderItems {
orderItems[i] = entity.InProgressOrderItem{
ItemID: item.ProductID,
Quantity: item.Quantity,
}
}
order := &entity.InProgressOrder{
PartnerID: *partnerID,
CustomerID: req.CustomerID,
CustomerName: req.CustomerName,
CreatedBy: userID,
OrderItems: orderItems,
TableNumber: req.TableNumber,
OrderType: req.OrderType,
ID: req.InProgressOrderID,
}
_, err := h.service.Save(ctx, order)
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusCreated, response.BaseResponse{
Success: true,
Status: http.StatusCreated,
})
}
func mapToInProgressOrderResponse(order *entity.InProgressOrder) map[string]interface{} {
orderItems := make([]map[string]interface{}, len(order.OrderItems))
for i, item := range order.OrderItems {
orderItems[i] = map[string]interface{}{
"id": item.ID,
"item_id": item.ItemID,
"quantity": item.Quantity,
"name": item.Product.Name,
"price": item.Product.Price,
"image": item.Product.Image,
}
}
return map[string]interface{}{
"id": order.ID,
"partner_id": order.PartnerID,
"customer_id": order.CustomerID,
"customer_name": order.CustomerName,
"payment_type": order.PaymentType,
"source": order.Source,
"created_by": order.CreatedBy,
"created_at": order.CreatedAt,
"updated_at": order.UpdatedAt,
"order_items": orderItems,
"table_number": order.TableNumber,
}
}
func (h *InProgressOrderHandler) GetByPartnerID(c *gin.Context) {
ctx := request.GetMyContext(c)
limitStr := c.DefaultQuery("limit", "10")
offsetStr := c.DefaultQuery("offset", "0")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit < 0 {
limit = 10
}
offset, err := strconv.Atoi(offsetStr)
if err != nil || offset < 0 {
offset = 0
}
orders, err := h.service.GetOrdersByPartnerID(ctx, *ctx.GetPartnerID(), limit, offset)
if err != nil {
response.ErrorWrapper(c, err)
return
}
orderResponses := make([]map[string]interface{}, len(orders))
for i, order := range orders {
orderResponses[i] = mapToInProgressOrderResponse(order)
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: map[string]interface{}{
"orders": orderResponses,
"pagination": map[string]interface{}{
"limit": limit,
"offset": offset,
"count": len(orders),
},
},
})
}

View File

@ -35,6 +35,17 @@ type InquiryRequest struct {
CustomerPhoneNumber string `json:"customer_phone_number"` CustomerPhoneNumber string `json:"customer_phone_number"`
PaymentMethod string `json:"payment_method" validate:"required"` PaymentMethod string `json:"payment_method" validate:"required"`
OrderItems []OrderItemRequest `json:"order_items" validate:"required,min=1,dive"` OrderItems []OrderItemRequest `json:"order_items" validate:"required,min=1,dive"`
OrderType string `json:"order_type"`
PaymentProvider string `json:"payment_provider"`
TableNumber string `json:"table_number"`
}
func (o *InquiryRequest) GetPaymentProvider() string {
if o.PaymentMethod == "CASH" {
return "CASH"
}
return o.PaymentProvider
} }
type OrderItemRequest struct { type OrderItemRequest struct {
@ -43,8 +54,10 @@ type OrderItemRequest struct {
} }
type ExecuteRequest struct { type ExecuteRequest struct {
PaymentMethod string `json:"payment_method" validate:"required"` PaymentMethod string `json:"payment_method" validate:"required"`
Token string `json:"token"` PaymentProvider string `json:"payment_provider"`
InProgressOrderID string `json:"in_progress_order_id"`
Token string `json:"token"`
} }
func (h *Handler) Inquiry(c *gin.Context) { func (h *Handler) Inquiry(c *gin.Context) {
@ -82,6 +95,9 @@ func (h *Handler) Inquiry(c *gin.Context) {
CustomerName: req.CustomerName, CustomerName: req.CustomerName,
CustomerEmail: req.CustomerEmail, CustomerEmail: req.CustomerEmail,
CustomerPhoneNumber: req.CustomerPhoneNumber, CustomerPhoneNumber: req.CustomerPhoneNumber,
OrderType: req.OrderType,
PaymentProvider: req.GetPaymentProvider(),
TableNumber: req.TableNumber,
} }
result, err := h.service.CreateOrderInquiry(ctx, orderReq) result, err := h.service.CreateOrderInquiry(ctx, orderReq)
@ -112,7 +128,7 @@ func (h *Handler) Execute(c *gin.Context) {
return return
} }
result, err := h.service.ExecuteOrderInquiry(ctx, req.Token, req.PaymentMethod) result, err := h.service.ExecuteOrderInquiry(ctx, req.Token, req.PaymentMethod, req.PaymentProvider, req.InProgressOrderID)
if err != nil { if err != nil {
response.ErrorWrapper(c, err) response.ErrorWrapper(c, err)
return return

View File

@ -0,0 +1,49 @@
package request
import (
"enaklo-pos-be/internal/entity"
"github.com/go-playground/validator/v10"
"strings"
"time"
)
type CustomerRegister struct {
Name string `json:"name" validate:"required" binding:"required"`
Email string `json:"email" validate:"required" binding:"required"`
PhoneNumber string `json:"phone_number" validate:"required" binding:"required"`
BirthDate string `json:"birth_date" validate:"required" binding:"required"`
Password string `json:"password" validate:"required" binding:"required"`
}
func (c *CustomerRegister) Validate() error {
validate := validator.New()
if err := validate.Struct(c); err != nil {
return err
}
return nil
}
func (c *CustomerRegister) GetBirthdate() (time.Time, error) {
parsedDate, err := time.Parse("02-01-2006", c.BirthDate)
if err != nil {
return time.Time{}, err
}
return parsedDate, nil
}
func (c *CustomerRegister) ToEntity() *entity.Customer {
birthdate, _ := c.GetBirthdate()
return &entity.Customer{
Name: c.Name,
Email: strings.ToLower(c.Email),
PhoneNumber: c.PhoneNumber,
Password: c.Password,
BirthDate: birthdate,
}
}
type VerifyEmailRequest struct {
VerificationID string `json:"verification_id" binding:"required"`
OTPCode string `json:"otp_code" binding:"required"`
}

View File

@ -7,11 +7,13 @@ import (
) )
type Order struct { type Order struct {
CustomerName string `json:"customer_name"` CustomerName string `json:"customer_name"`
CustomerPhone string `json:"customer_phone"` CustomerPhone string `json:"customer_phone"`
CustomerEmail string `json:"customer_email"` CustomerEmail string `json:"customer_email"`
PaymentMethod string `json:"payment_method"` PaymentMethod string `json:"payment_method"`
OrderItems []OrderItem `json:"order_items"` PaymentProvider string `json:"payment_provider"`
TableNumber string `json:"table_number"`
OrderItems []OrderItem `json:"order_items"`
} }
type CustomerOrder struct { type CustomerOrder struct {

View File

@ -174,7 +174,5 @@ func (u *UserRegister) ToEntity() *entity.User {
Email: strings.ToLower(u.Email), Email: strings.ToLower(u.Email),
PhoneNumber: u.PhoneNumber, PhoneNumber: u.PhoneNumber,
Password: u.Password, Password: u.Password,
RoleID: role.Customer,
UserType: "CUSTOMER",
} }
} }

View File

@ -33,3 +33,9 @@ func MapToCustomerListResponse(customers *entity.MemberList) []CustomerResponse
return responseList return responseList
} }
type CustomerRegistrationResp struct {
EmailVerificationRequired bool `json:"email_verification_required"`
PhoneVerificationRequired bool `json:"phone_verification_required"`
VerificationID string `json:"verification_id"`
}

View File

@ -46,10 +46,10 @@ type CustomerList struct {
} }
type UserRegister struct { type UserRegister struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Email string `json:"email"` Email string `json:"email"`
Status string `json:"status"` PhoneNumber string `json:"phone_number"`
CreatedAt time.Time `json:"created_at,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"`
} }

View File

@ -0,0 +1,267 @@
package repository
import (
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/constants"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository/models"
"github.com/pkg/errors"
"gorm.io/gorm"
time2 "time"
)
type InProgressOrderRepository interface {
CreateOrUpdate(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error)
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error)
}
type inprogressOrderRepository struct {
db *gorm.DB
}
func NewInProgressOrderRepository(db *gorm.DB) *inprogressOrderRepository {
return &inprogressOrderRepository{db: db}
}
func (r *inprogressOrderRepository) CreateOrUpdate(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) {
isUpdate := order.ID != ""
tx := r.db.Begin()
if tx.Error != nil {
return nil, errors.Wrap(tx.Error, "failed to begin transaction")
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
orderDB := r.toInProgressOrderDBModel(order)
if isUpdate {
var existingOrder models.InProgressOrderDB
if err := tx.First(&existingOrder, order.ID).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "order not found for update")
}
if err := tx.Model(&orderDB).Updates(orderDB).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to update order")
}
if err := tx.Where("in_progress_order_id = ?", order.ID).Delete(&models.InProgressOrderItemDB{}).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to delete existing order items")
}
} else {
if err := tx.Create(&orderDB).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to insert order")
}
order.ID = orderDB.ID
}
var itemIDs []int64
for i := range order.OrderItems {
itemIDs = append(itemIDs, order.OrderItems[i].ItemID)
}
var products []models.ProductDB
if len(itemIDs) > 0 {
if err := tx.Where("id IN ?", itemIDs).Find(&products).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to fetch products")
}
}
productMap := make(map[int64]models.ProductDB)
for _, product := range products {
productMap[product.ID] = product
}
for i := range order.OrderItems {
item := &order.OrderItems[i]
itemDB := r.toOrderItemDBModel(item, orderDB.ID)
if err := tx.Create(&itemDB).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to insert order item")
}
item.ID = itemDB.ID
if product, exists := productMap[item.ItemID]; exists {
item.Product = r.toDomainProductModel(&product)
}
}
if err := tx.Commit().Error; err != nil {
return nil, errors.Wrap(err, "failed to commit transaction")
}
return order, nil
}
func (r *inprogressOrderRepository) GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) {
var ordersDB []models.InProgressOrderDB
query := r.db.Where("partner_id = ?", partnerID).Order("created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Preload("OrderItems.Product").Find(&ordersDB).Error; err != nil {
return nil, errors.Wrap(err, "failed to find orders by partner ID")
}
orders := make([]*entity.InProgressOrder, 0, len(ordersDB))
for _, orderDB := range ordersDB {
order := r.toDomainOrderModel(&orderDB)
order.OrderItems = make([]entity.InProgressOrderItem, 0, len(orderDB.OrderItems))
for _, itemDB := range orderDB.OrderItems {
item := r.toDomainOrderItemModel(&itemDB)
orderItem := entity.InProgressOrderItem{
ID: item.ID,
ItemID: item.ItemID,
Quantity: item.Quantity,
}
if itemDB.Product.ID > 0 {
productDomain := r.toDomainProductModel(&itemDB.Product)
orderItem.Product = productDomain
}
order.OrderItems = append(order.OrderItems, orderItem)
}
orders = append(orders, order)
}
return orders, nil
}
func (r *inprogressOrderRepository) toInProgressOrderDBModel(order *entity.InProgressOrder) models.InProgressOrderDB {
now := time2.Now()
return models.InProgressOrderDB{
ID: constants.GenerateUUID(),
PartnerID: order.PartnerID,
CustomerID: order.CustomerID,
CustomerName: order.CustomerName,
PaymentType: order.PaymentType,
CreatedBy: order.CreatedBy,
CreatedAt: now,
UpdatedAt: now,
TableNumber: order.TableNumber,
OrderType: order.OrderType,
}
}
func (r *inprogressOrderRepository) toDomainOrderModel(dbModel *models.InProgressOrderDB) *entity.InProgressOrder {
return &entity.InProgressOrder{
ID: dbModel.ID,
PartnerID: dbModel.PartnerID,
CustomerID: dbModel.CustomerID,
CustomerName: dbModel.CustomerName,
PaymentType: dbModel.PaymentType,
CreatedBy: dbModel.CreatedBy,
OrderItems: []entity.InProgressOrderItem{},
TableNumber: dbModel.TableNumber,
OrderType: dbModel.OrderType,
CreatedAt: dbModel.CreatedAt,
UpdatedAt: dbModel.UpdatedAt,
}
}
func (r *inprogressOrderRepository) toOrderItemDBModel(item *entity.InProgressOrderItem, inprogressOrderID string) models.InProgressOrderItemDB {
return models.InProgressOrderItemDB{
ID: item.ID,
InProgressOrderIO: inprogressOrderID,
ItemID: item.ItemID,
Quantity: item.Quantity,
}
}
func (r *inprogressOrderRepository) toDomainOrderItemModel(dbModel *models.InProgressOrderItemDB) *entity.OrderItem {
return &entity.OrderItem{
ID: dbModel.ID,
ItemID: dbModel.ItemID,
Quantity: dbModel.Quantity,
CreatedBy: dbModel.CreatedBy,
CreatedAt: dbModel.CreatedAt,
}
}
func (r *inprogressOrderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) models.OrderInquiryDB {
return models.OrderInquiryDB{
ID: inquiry.ID,
PartnerID: inquiry.PartnerID,
CustomerID: &inquiry.CustomerID,
Status: inquiry.Status,
Amount: inquiry.Amount,
Fee: inquiry.Fee,
Total: inquiry.Total,
PaymentType: inquiry.PaymentType,
Source: inquiry.Source,
CreatedBy: inquiry.CreatedBy,
CreatedAt: inquiry.CreatedAt,
UpdatedAt: inquiry.UpdatedAt,
ExpiresAt: inquiry.ExpiresAt,
CustomerName: inquiry.CustomerName,
CustomerPhoneNumber: inquiry.CustomerPhoneNumber,
CustomerEmail: inquiry.CustomerEmail,
PaymentProvider: inquiry.PaymentProvider,
OrderType: inquiry.OrderType,
TableNumber: inquiry.TableNumber,
}
}
func (r *inprogressOrderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiryDB) *entity.OrderInquiry {
inquiry := &entity.OrderInquiry{
ID: dbModel.ID,
PartnerID: dbModel.PartnerID,
Status: dbModel.Status,
Amount: dbModel.Amount,
Fee: dbModel.Fee,
Total: dbModel.Total,
PaymentType: dbModel.PaymentType,
Source: dbModel.Source,
CreatedBy: dbModel.CreatedBy,
CreatedAt: dbModel.CreatedAt,
ExpiresAt: dbModel.ExpiresAt,
OrderItems: []entity.OrderItem{},
}
if dbModel.CustomerID != nil {
inquiry.CustomerID = *dbModel.CustomerID
}
inquiry.UpdatedAt = dbModel.UpdatedAt
return inquiry
}
func (r *inprogressOrderRepository) toDomainProductModel(productDB *models.ProductDB) *entity.Product {
if productDB == nil {
return nil
}
return &entity.Product{
ID: productDB.ID,
Name: productDB.Name,
Description: productDB.Description,
Price: productDB.Price,
CreatedAt: productDB.CreatedAt,
UpdatedAt: productDB.UpdatedAt,
Type: productDB.Type,
Image: productDB.Image,
}
}

View File

@ -4,8 +4,11 @@ import (
"enaklo-pos-be/internal/common/mycontext" "enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity" "enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository/models" "enaklo-pos-be/internal/repository/models"
"fmt"
"github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
"gorm.io/gorm" "gorm.io/gorm"
"math/rand"
"time" "time"
) )
@ -14,9 +17,10 @@ type CustomerRepo interface {
FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error) FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error)
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) error AddPoints(ctx mycontext.Context, id int64, points int, reference string) 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)
} }
type customerRepository struct { type customerRepository struct {
@ -28,13 +32,53 @@ func NewCustomerRepository(db *gorm.DB) *customerRepository {
} }
func (r *customerRepository) Create(ctx mycontext.Context, customer *entity.Customer) (*entity.Customer, error) { func (r *customerRepository) Create(ctx mycontext.Context, customer *entity.Customer) (*entity.Customer, error) {
customerDB := r.toCustomerDBModel(customer) tx := r.db.Begin()
if tx.Error != nil {
return nil, errors.Wrap(tx.Error, "failed to begin transaction")
}
if err := r.db.Create(&customerDB).Error; err != nil { customerDB := r.toCustomerDBModel(customer)
if err := tx.Omit("CustomerID").Create(&customerDB).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to insert customer") return nil, errors.Wrap(err, "failed to insert customer")
} }
customerPoints := models.CustomerPointsDB{
CustomerID: uint64(customerDB.ID),
TotalPoints: 0,
AvailablePoints: 0,
LastUpdated: time.Now(),
}
if err := tx.Create(&customerPoints).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to create initial customer points")
}
otpCode := r.generateOTPCode()
expiresAt := time.Now().Add(15 * time.Minute)
verificationCode := models.CustomerVerificationCodeDB{
CustomerID: uint64(customerDB.ID),
Code: otpCode,
Type: "EMAIL",
ExpiresAt: expiresAt,
IsUsed: false,
VerificationID: uuid.New(),
}
if err := tx.Create(&verificationCode).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to create verification code")
}
if err := tx.Commit().Error; err != nil {
return nil, errors.Wrap(err, "failed to commit transaction")
}
customer.ID = customerDB.ID customer.ID = customerDB.ID
customer.VerificationID = verificationCode.VerificationID.String()
customer.OTP = otpCode
return customer, nil return customer, nil
} }
@ -84,22 +128,45 @@ func (r *customerRepository) FindByEmail(ctx mycontext.Context, email string) (*
return customer, nil return customer, nil
} }
func (r *customerRepository) AddPoints(ctx mycontext.Context, id int64, points int) error { func (r *customerRepository) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
now := time.Now() tx := r.db.Begin()
if tx.Error != nil {
return errors.Wrap(tx.Error, "failed to begin transaction")
}
result := r.db.Model(&models.CustomerDB{}). result := tx.Model(&models.CustomerPointsDB{}).
Where("id = ?", id). Where("customer_id = ?", customerID).
Updates(map[string]interface{}{ Updates(map[string]interface{}{
"points": gorm.Expr("points + ?", points), "total_points": gorm.Expr("total_points + ?", points),
"updated_at": now, "available_points": gorm.Expr("available_points + ?", points),
"last_updated": time.Now(),
}) })
if result.Error != nil { if result.Error != nil {
return errors.Wrap(result.Error, "failed to add points to customer") tx.Rollback()
return errors.Wrap(result.Error, "failed to update customer points")
} }
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
return errors.New("customer not found") tx.Rollback()
return errors.New("customer points record not found")
}
pointTransaction := models.CustomerPointTransactionDB{
CustomerID: customerID,
Reference: reference,
PointsEarned: points,
TransactionDate: time.Now(),
Status: "SUCCESS",
}
if err := tx.Create(&pointTransaction).Error; err != nil {
tx.Rollback()
return errors.Wrap(err, "failed to create point transaction record")
}
if err := tx.Commit().Error; err != nil {
return errors.Wrap(err, "failed to commit transaction")
} }
return nil return nil
@ -107,15 +174,15 @@ func (r *customerRepository) AddPoints(ctx mycontext.Context, id int64, points i
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,
Name: customer.Name, Name: customer.Name,
Email: customer.Email, Email: customer.Email,
Phone: customer.Phone, Phone: customer.Phone,
Points: customer.Points, Points: customer.Points,
CreatedAt: customer.CreatedAt, CreatedAt: customer.CreatedAt,
UpdatedAt: customer.UpdatedAt, UpdatedAt: customer.UpdatedAt,
CustomerID: customer.CustomerID, BirthDate: customer.BirthDate,
BirthDate: customer.BirthDate, Password: customer.Password,
} }
} }
@ -232,3 +299,59 @@ func (r *customerRepository) GetAllCustomers(ctx mycontext.Context, req entity.M
return customers, int(totalCount), nil return customers, int(totalCount), nil
} }
func (r *customerRepository) generateOTPCode() string {
rand.Seed(time.Now().UnixNano())
otpCode := fmt.Sprintf("%06d", rand.Intn(1000000))
return otpCode
}
func (r *customerRepository) VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error) {
var verificationCode models.CustomerVerificationCodeDB
if err := r.db.Where("verification_id = ? AND is_used = false", verificationHash).First(&verificationCode).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, errors.New("invalid or expired verification code")
}
return 0, errors.Wrap(err, "failed to find verification code")
}
if time.Now().After(verificationCode.ExpiresAt) {
return 0, errors.New("verification code has expired")
}
if verificationCode.Code != otpCode {
return 0, errors.New("invalid verification code")
}
tx := r.db.Begin()
if tx.Error != nil {
return 0, errors.Wrap(tx.Error, "failed to begin transaction")
}
if err := tx.Model(&verificationCode).Updates(map[string]interface{}{
"is_used": true,
}).Error; err != nil {
tx.Rollback()
return 0, errors.Wrap(err, "failed to mark verification code as used")
}
if verificationCode.Type == "EMAIL" {
if err := tx.Model(&models.CustomerDB{}).Where("id = ?", verificationCode.CustomerID).
Update("is_email_verified", true).Error; err != nil {
tx.Rollback()
return 0, errors.Wrap(err, "failed to update customer verification status")
}
} else if verificationCode.Type == "PHONE" {
if err := tx.Model(&models.CustomerDB{}).Where("id = ?", verificationCode.CustomerID).
Update("is_phone_verified", true).Error; err != nil {
tx.Rollback()
return 0, errors.Wrap(err, "failed to update customer verification status")
}
}
if err := tx.Commit().Error; err != nil {
return 0, errors.Wrap(err, "failed to commit transaction")
}
return int64(verificationCode.CustomerID), nil
}

View File

@ -1,19 +1,23 @@
package models package models
import ( import (
"github.com/google/uuid"
"time" "time"
) )
type CustomerDB struct { type CustomerDB struct {
ID int64 `gorm:"primaryKey;column:id"` ID int64 `gorm:"primaryKey;column:id"`
Name string `gorm:"column:name"` Name string `gorm:"column:name"`
Email string `gorm:"column:email"` Email string `gorm:"column:email"`
Phone string `gorm:"column:phone"` Phone string `gorm:"column:phone"`
Points int `gorm:"column:points"` Points int `gorm:"column:points"`
CreatedAt time.Time `gorm:"column:created_at"` CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"` UpdatedAt time.Time `gorm:"column:updated_at"`
CustomerID string `gorm:"column:customer_id"` CustomerID string `gorm:"column:customer_id"`
BirthDate time.Time `gorm:"column:birth_date"` BirthDate time.Time `gorm:"column:birth_date"`
Password string `gorm:"column:password"`
IsEmailVerified bool `gorm:"column:is_email_verified"`
IsPhoneVerified bool `gorm:"column:is_phone_verified"`
} }
func (CustomerDB) TableName() string { func (CustomerDB) TableName() string {
@ -30,3 +34,45 @@ type PartnerMemberSequence struct {
func (PartnerMemberSequence) TableName() string { func (PartnerMemberSequence) TableName() string {
return "partner_member_sequences" return "partner_member_sequences"
} }
type CustomerPointsDB struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement"`
CustomerID uint64 `gorm:"column:customer_id;not null"`
TotalPoints int `gorm:"column:total_points;not null;default:0"`
AvailablePoints int `gorm:"column:available_points;not null;default:0"`
LastUpdated time.Time `gorm:"column:last_updated;default:CURRENT_TIMESTAMP"`
}
func (CustomerPointsDB) TableName() string {
return "customer_points"
}
type CustomerPointTransactionDB struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement"`
CustomerID int64 `gorm:"column:customer_id;not null"`
Reference string `gorm:"column:transaction_id"`
PointsEarned int `gorm:"column:points_earned;not null"`
TransactionDate time.Time `gorm:"column:transaction_date;not null"`
ExpirationDate *time.Time `gorm:"column:expiration_date"`
Status string `gorm:"column:status;default:active"`
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"`
}
func (CustomerPointTransactionDB) TableName() string {
return "customer_point_transactions"
}
type CustomerVerificationCodeDB struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement"`
CustomerID uint64 `gorm:"column:customer_id;not null"`
Code string `gorm:"column:code;not null"`
Type string `gorm:"column:type;not null"`
ExpiresAt time.Time `gorm:"column:expires_at;not null"`
IsUsed bool `gorm:"column:is_used;default:false"`
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"`
VerificationID uuid.UUID `gorm:"column:verification_id;type:uuid;default:uuid_generate_v4()"`
}
func (CustomerVerificationCodeDB) TableName() string {
return "customer_verification_codes"
}

View File

@ -0,0 +1,35 @@
package models
import "time"
type InProgressOrderDB struct {
ID string `gorm:"primaryKey;column:id"`
PartnerID int64 `gorm:"column:partner_id"`
CustomerID *int64 `gorm:"column:customer_id"`
CustomerName string `gorm:"column:customer_name"`
PaymentType string `gorm:"column:payment_type"`
CreatedBy int64 `gorm:"column:created_by"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
TableNumber string `gorm:"column:table_number"`
OrderItems []InProgressOrderItemDB `gorm:"foreignKey:InProgressOrderIO"`
OrderType string `gorm:"column:order_type"`
}
type InProgressOrderItemDB struct {
ID int64 `gorm:"primaryKey;column:id"`
InProgressOrderIO string `gorm:"column:in_progress_order_id"`
ItemID int64 `gorm:"column:item_id"`
Quantity int `gorm:"column:quantity"`
CreatedBy int64 `gorm:"column:created_by"`
CreatedAt time.Time `gorm:"column:created_at"`
Product ProductDB `gorm:"foreignKey:ItemID;references:ID"`
}
func (InProgressOrderItemDB) TableName() string {
return "in_progress_order_items"
}
func (InProgressOrderDB) TableName() string {
return "in_progress_order"
}

View File

@ -58,6 +58,9 @@ type OrderInquiryDB struct {
UpdatedAt time.Time `gorm:"column:updated_at"` UpdatedAt time.Time `gorm:"column:updated_at"`
ExpiresAt time.Time `gorm:"column:expires_at"` ExpiresAt time.Time `gorm:"column:expires_at"`
InquiryItems []InquiryItemDB `gorm:"foreignKey:InquiryID"` InquiryItems []InquiryItemDB `gorm:"foreignKey:InquiryID"`
PaymentProvider string `gorm:"column:payment_provider"`
TableNumber string `gorm:"column:table_number"`
OrderType string `gorm:"column:order_type"`
} }
func (OrderInquiryDB) TableName() string { func (OrderInquiryDB) TableName() string {

View File

@ -15,6 +15,7 @@ type ProductDB struct {
Status string `gorm:"column:status"` Status string `gorm:"column:status"`
CreatedAt time.Time `gorm:"column:created_at"` CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"` UpdatedAt time.Time `gorm:"column:updated_at"`
Image string `gorm:"column:image"`
} }
func (ProductDB) TableName() string { func (ProductDB) TableName() string {

View File

@ -61,6 +61,18 @@ func (r *orderRepository) Create(ctx mycontext.Context, order *entity.Order) (*e
item.ID = itemDB.ID item.ID = itemDB.ID
} }
if order.InProgressOrderID != "" {
if err := tx.Where("in_progress_order_id = ?", order.InProgressOrderID).Delete(&models.InProgressOrderItemDB{}).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to delete in-progress order items")
}
if err := tx.Where("id = ?", order.InProgressOrderID).Delete(&models.InProgressOrderDB{}).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to delete in-progress order")
}
}
if err := tx.Commit().Error; err != nil { if err := tx.Commit().Error; err != nil {
return nil, errors.Wrap(err, "failed to commit transaction") return nil, errors.Wrap(err, "failed to commit transaction")
} }
@ -263,6 +275,9 @@ func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) mo
CustomerName: inquiry.CustomerName, CustomerName: inquiry.CustomerName,
CustomerPhoneNumber: inquiry.CustomerPhoneNumber, CustomerPhoneNumber: inquiry.CustomerPhoneNumber,
CustomerEmail: inquiry.CustomerEmail, CustomerEmail: inquiry.CustomerEmail,
PaymentProvider: inquiry.PaymentProvider,
OrderType: inquiry.OrderType,
TableNumber: inquiry.TableNumber,
} }
} }

View File

@ -141,9 +141,8 @@ func (b *OrderRepository) GetAllHystoryOrders(ctx context.Context, req entity.Or
query := b.db.Table("orders"). query := b.db.Table("orders").
Select("orders.id as id, users.name as employee, sites.name as site, orders.created_at as timestamp, orders.created_at as booking_time, STRING_AGG(ticket_summary.name || ' x' || ticket_summary.total_qty, ', ') AS tickets, orders.payment_type as payment_type, orders.status as status, orders.amount as amount, orders.visit_date as visit_date, orders.ticket_status as ticket_status, orders.source as source"). Select("orders.id as id, users.name as employee, sites.name as site, orders.created_at as timestamp, orders.created_at as booking_time, STRING_AGG(ticket_summary.name || ' x' || ticket_summary.total_qty, ', ') AS tickets, orders.payment_type as payment_type, orders.status as status, orders.amount as amount, orders.visit_date as visit_date, orders.ticket_status as ticket_status, orders.source as source").
Joins("left join (SELECT items.order_id, products.name, SUM(items.qty) AS total_qty FROM order_items items LEFT JOIN products ON items.item_id = products.id GROUP BY items.order_id, products.name) AS ticket_summary ON orders.id = ticket_summary.order_id"). Joins("left join (SELECT items.order_id, products.name, SUM(items.quantity) AS total_qty FROM order_items items LEFT JOIN products ON items.item_id = products.id GROUP BY items.order_id, products.name) AS ticket_summary ON orders.id = ticket_summary.order_id").
Joins("left join users on orders.created_by = users.id"). Joins("left join users on orders.created_by = users.id").
Joins("left join sites on orders.site_id = sites.id").
Where("orders.payment_type != ?", "NEW") Where("orders.payment_type != ?", "NEW")
if req.PaymentType != "" { if req.PaymentType != "" {
@ -176,7 +175,7 @@ func (b *OrderRepository) GetAllHystoryOrders(ctx context.Context, req entity.Or
} }
if req.SiteID != nil { if req.SiteID != nil {
query = query.Where("orders.site_id = ?", req.SiteID) query = query.Where("orders.partner_id = ?", req.SiteID)
} }
if req.Source != "" { if req.Source != "" {
@ -253,10 +252,6 @@ func (r *OrderRepository) SumAmount(ctx mycontext.Context, req entity.OrderSearc
query = query.Where("orders.partner_id = ?", req.PartnerID) query = query.Where("orders.partner_id = ?", req.PartnerID)
} }
if req.SiteID != nil {
query = query.Where("orders.site_id = ?", req.SiteID)
}
if err := query.Scan(&amount).Error; err != nil { if err := query.Scan(&amount).Error; err != nil {
logger.ContextLogger(ctx).Error("error when get cash amount", zap.Error(err)) logger.ContextLogger(ctx).Error("error when get cash amount", zap.Error(err))
return nil, err return nil, err

View File

@ -52,11 +52,12 @@ type RepoManagerImpl struct {
PG PaymentGateway PG PaymentGateway
LinkQu LinkQu LinkQu LinkQu
OrderRepo OrderRepository OrderRepo OrderRepository
CustomerRepo CustomerRepo InProgressOrderRepo InProgressOrderRepository
ProductRepo ProductRepository CustomerRepo CustomerRepo
TransactionRepo TransactionRepo ProductRepo ProductRepository
MemberRepository MemberRepository TransactionRepo TransactionRepo
MemberRepository MemberRepository
} }
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
@ -81,11 +82,12 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
PG: pg.NewPaymentGatewayRepo(&cfg.Midtrans, &cfg.LinkQu), PG: pg.NewPaymentGatewayRepo(&cfg.Midtrans, &cfg.LinkQu),
LinkQu: linkqu.NewLinkQuService(&cfg.LinkQu), LinkQu: linkqu.NewLinkQuService(&cfg.LinkQu),
OrderRepo: NeworderRepository(db), OrderRepo: NeworderRepository(db),
CustomerRepo: NewCustomerRepository(db), CustomerRepo: NewCustomerRepository(db),
ProductRepo: NewproductRepository(db), ProductRepo: NewproductRepository(db),
TransactionRepo: NewTransactionRepository(db), TransactionRepo: NewTransactionRepository(db),
MemberRepository: NewMemberRepository(db), MemberRepository: NewMemberRepository(db),
InProgressOrderRepo: NewInProgressOrderRepository(db),
} }
} }

View File

@ -19,7 +19,7 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan
serverRoutes := []HTTPHandlerRoutes{ serverRoutes := []HTTPHandlerRoutes{
discovery.NewHandler(serviceManager.DiscoverService), discovery.NewHandler(serviceManager.DiscoverService),
customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc), customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc, serviceManager.CustomerV2Svc),
customerorder.NewHandler(serviceManager.OrderSvc), customerorder.NewHandler(serviceManager.OrderSvc),
} }

View File

@ -80,6 +80,7 @@ func RegisterPrivateRoutesV2(app *app.Server, serviceManager *services.ServiceMa
http2.NewOrderHandler(serviceManager.OrderV2Svc), http2.NewOrderHandler(serviceManager.OrderV2Svc),
http2.NewMemberRegistrationHandler(serviceManager.MemberRegistrationSvc), http2.NewMemberRegistrationHandler(serviceManager.MemberRegistrationSvc),
http2.NewCustomerHandler(serviceManager.CustomerV2Svc), http2.NewCustomerHandler(serviceManager.CustomerV2Svc),
http2.NewInProgressOrderHandler(serviceManager.InProgressSvc),
} }
for _, handler := range serverRoutes { for _, handler := range serverRoutes {

View File

@ -83,7 +83,6 @@ func (u *AuthServiceImpl) AuthenticateUser(ctx context.Context, email, password
} }
func (u *AuthServiceImpl) SendPasswordResetLink(ctx context.Context, email string) error { func (u *AuthServiceImpl) SendPasswordResetLink(ctx context.Context, email string) error {
// Check if the user exists
user, err := u.authRepo.CheckExistsUserAccount(ctx, email) user, err := u.authRepo.CheckExistsUserAccount(ctx, email)
if err != nil { if err != nil {
logger.ContextLogger(ctx).Error("error when getting user", zap.Error(err)) logger.ContextLogger(ctx).Error("error when getting user", zap.Error(err))

View File

@ -162,7 +162,6 @@ func (s *memberSvc) ResendOTP(
) (*entity.ResendOTPResponse, error) { ) (*entity.ResendOTPResponse, error) {
logger.ContextLogger(ctx).Info("resending OTP", zap.String("token", token)) logger.ContextLogger(ctx).Info("resending OTP", zap.String("token", token))
// Get registration by token
registration, err := s.repo.GetRegistrationByToken(ctx, token) registration, err := s.repo.GetRegistrationByToken(ctx, token)
if err != nil { if err != nil {
logger.ContextLogger(ctx).Error("failed to get registration", zap.Error(err)) logger.ContextLogger(ctx).Error("failed to get registration", zap.Error(err))
@ -211,7 +210,7 @@ func (s *memberSvc) sendRegistrationOTP(
Recipient: registration.Email, Recipient: registration.Email,
Subject: "Enaklo - Registration Verification Code", Subject: "Enaklo - Registration Verification Code",
TemplateName: "member_registration_otp", TemplateName: "member_registration_otp",
TemplatePath: "/templates/member_registration_otp.html", TemplatePath: "templates/member_registration_otp.html",
Data: emailData, Data: emailData,
}) })

View File

@ -240,32 +240,6 @@ func (s *OrderService) Execute(ctx mycontext.Context, req *entity.OrderExecuteRe
Order: order, Order: order,
} }
if order.PaymentType != "CASH" {
if order.PaymentType == "VA" {
paymentResponse, err := s.processVAPayment(ctx, order, partnerID, req.CreatedBy)
if err != nil {
return nil, err
}
resp.VirtualAccount = paymentResponse.VirtualAccountNumber
resp.BankName = paymentResponse.BankName
resp.BankCode = paymentResponse.BankCode
}
if order.PaymentType == "QRIS" {
paymentResponse, err := s.processQRPayment(ctx, order, partnerID, req.CreatedBy)
if err != nil {
return nil, err
}
resp.QRCode = paymentResponse.QRCodeURL
} else {
paymentResponse, err := s.processNonCashPayment(ctx, order, partnerID, req.CreatedBy)
if err != nil {
return nil, err
}
resp.PaymentToken = paymentResponse.Token
resp.RedirectURL = paymentResponse.RedirectURL
}
}
order.SetExecutePaymentStatus() order.SetExecutePaymentStatus()
order, err = s.repo.Update(ctx, order) order, err = s.repo.Update(ctx, order)
if err != nil { if err != nil {

View File

@ -16,6 +16,7 @@ import (
"enaklo-pos-be/internal/services/transaction" "enaklo-pos-be/internal/services/transaction"
"enaklo-pos-be/internal/services/users" "enaklo-pos-be/internal/services/users"
customerSvc "enaklo-pos-be/internal/services/v2/customer" customerSvc "enaklo-pos-be/internal/services/v2/customer"
"enaklo-pos-be/internal/services/v2/inprogress_order"
orderSvc "enaklo-pos-be/internal/services/v2/order" orderSvc "enaklo-pos-be/internal/services/v2/order"
productSvc "enaklo-pos-be/internal/services/v2/product" productSvc "enaklo-pos-be/internal/services/v2/product"
@ -47,12 +48,14 @@ type ServiceManagerImpl struct {
CustomerV2Svc customerSvc.Service CustomerV2Svc customerSvc.Service
ProductV2Svc productSvc.Service ProductV2Svc productSvc.Service
MemberRegistrationSvc member.RegistrationService MemberRegistrationSvc member.RegistrationService
InProgressSvc inprogress_order.InProgressOrderService
} }
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl { func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
custSvcV2 := customerSvc.New(repo.CustomerRepo) custSvcV2 := customerSvc.New(repo.CustomerRepo, repo.EmailService)
productSvcV2 := productSvc.New(repo.ProductRepo) productSvcV2 := productSvc.New(repo.ProductRepo)
inprogressOrder := inprogress_order.NewInProgressOrderService(repo.InProgressOrderRepo)
return &ServiceManagerImpl{ return &ServiceManagerImpl{
AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License), AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License),
@ -72,6 +75,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService), OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService),
MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2), MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2),
CustomerV2Svc: custSvcV2, CustomerV2Svc: custSvcV2,
InProgressSvc: inprogressOrder,
} }
} }

View File

@ -1,6 +1,8 @@
package customer package customer
import ( import (
"context"
errors2 "enaklo-pos-be/internal/common/errors"
"enaklo-pos-be/internal/common/logger" "enaklo-pos-be/internal/common/logger"
"enaklo-pos-be/internal/common/mycontext" "enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/constants" "enaklo-pos-be/internal/constants"
@ -8,7 +10,9 @@ import (
"enaklo-pos-be/internal/utils" "enaklo-pos-be/internal/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.uber.org/zap" "go.uber.org/zap"
"log"
"strings" "strings"
"time"
) )
type Repository interface { type Repository interface {
@ -16,26 +20,35 @@ type Repository interface {
FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error) FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error)
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) error AddPoints(ctx mycontext.Context, id int64, points int, reference string) 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)
} }
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) 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)
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)
RegistrationMember(ctx mycontext.Context, req *entity.Customer) (*entity.Customer, error)
VerifyOTP(ctx mycontext.Context, verificationID, otpCode string) error
}
type EmailService interface {
SendEmailTransactional(ctx context.Context, param entity.SendEmailNotificationParam) error
} }
type customerSvc struct { type customerSvc struct {
repo Repository repo Repository
notification EmailService
} }
func New(repo Repository) Service { func New(repo Repository, notification EmailService) Service {
return &customerSvc{ return &customerSvc{
repo: repo, repo: repo,
notification: notification,
} }
} }
@ -106,12 +119,68 @@ func (s *customerSvc) ResolveCustomer(ctx mycontext.Context, req *entity.Custome
return customer.ID, nil return customer.ID, nil
} }
func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int) error { func (s *customerSvc) RegistrationMember(ctx mycontext.Context, req *entity.Customer) (*entity.Customer, error) {
if req.Email == "" && req.PhoneNumber == "" {
return nil, errors2.ErrorPhoneNumberEmailIsRequired
}
if req.PhoneNumber != "" {
customer, err := s.repo.FindByPhone(ctx, req.PhoneNumber)
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, errors2.ErrorInternalServer
}
if customer != nil {
return nil, errors2.ErrorPhoneNumberIsAlreadyRegistered
}
}
if req.Email != "" {
customer, err := s.repo.FindByEmail(ctx, req.Email)
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, errors2.ErrorInternalServer
}
if customer != nil {
return nil, errors2.ErrorEmailIsAlreadyRegistered
}
}
newCustomer := &entity.Customer{
Name: req.Name,
Email: req.Email,
Phone: req.PhoneNumber,
CreatedAt: constants.TimeNow(),
UpdatedAt: constants.TimeNow(),
BirthDate: req.BirthDate,
Password: req.HashedPassword(),
}
customer, err := s.repo.Create(ctx, newCustomer)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create customer", zap.Error(err))
return nil, errors2.ErrorInternalServer
}
errs := s.sendRegistrationOTP(ctx, &entity.MemberRegistration{
Name: customer.Name,
Email: customer.Email,
OTP: customer.OTP,
})
if err != nil {
logger.ContextLogger(ctx).Error("failed to send OTP", zap.Error(errs))
}
return customer, nil
}
func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
if points <= 0 { if points <= 0 {
return nil return nil
} }
err := s.repo.AddPoints(ctx, customerID, points) err := s.repo.AddPoints(ctx, customerID, points, reference)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to add points to customer") return errors.Wrap(err, "failed to add points to customer")
} }
@ -202,3 +271,75 @@ func (s *customerSvc) GetAllCustomers(ctx mycontext.Context, req *entity.MemberS
return &customers, totalCount, nil return &customers, totalCount, nil
} }
func (s *customerSvc) sendRegistrationOTP(
ctx mycontext.Context,
registration *entity.MemberRegistration,
) error {
emailData := map[string]interface{}{
"UserName": registration.Name,
"OTPCode": registration.OTP,
}
err := s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
Sender: "noreply@enaklo.co.id",
Recipient: registration.Email,
Subject: "Enaklo - Registration Verification Code",
TemplateName: "member_registration_otp",
TemplatePath: "templates/member_registration_otp.html",
Data: emailData,
})
if err != nil {
return err
}
return nil
}
func (s *customerSvc) VerifyOTP(ctx mycontext.Context, verificationID, otpCode string) error {
customerID, err := s.repo.VerifyOTP(ctx, verificationID, otpCode)
if err != nil {
return errors.Wrap(err, "verification failed")
}
customer, _ := s.repo.FindByID(ctx, customerID)
go func(customer *entity.Customer) {
newCtx := context.Background()
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in sendWelcomeEmail: %v", r)
}
}()
s.sendWelcomeEmail(newCtx, customer)
}(customer)
return nil
}
func (s *customerSvc) sendWelcomeEmail(
ctx context.Context,
customer *entity.Customer,
) error {
welcomeData := map[string]interface{}{
"UserName": customer.Name,
"MemberID": customer.CustomerID,
"PointsName": "EnakPoint",
"PointsBalance": customer.Points,
"RedeemLink": "https://enaklo.co.id/redeem",
"CurrentDate": time.Now().Format("01-2006"),
}
return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
Sender: "noreply@enaklo.co.id",
Recipient: customer.Email,
Subject: "Welcome to Enaklo Membership Program",
TemplateName: "welcome_member",
TemplatePath: "templates/welcome_member.html",
Data: welcomeData,
})
}

View File

@ -0,0 +1,51 @@
package inprogress_order
import (
"enaklo-pos-be/internal/common/logger"
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type InProgressOrderService interface {
Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error)
GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error)
}
type inProgressOrderSvc struct {
repo repository.InProgressOrderRepository
}
func NewInProgressOrderService(repo repository.InProgressOrderRepository) InProgressOrderService {
return &inProgressOrderSvc{
repo: repo,
}
}
func (s *inProgressOrderSvc) Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) {
createdOrder, err := s.repo.CreateOrUpdate(ctx, order)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create in-progress order",
zap.Error(err),
zap.Int64("partnerID", order.PartnerID))
return nil, errors.Wrap(err, "failed to create in-progress order")
}
return createdOrder, nil
}
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) {
orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset)
if err != nil {
logger.ContextLogger(ctx).Error("failed to get in-progress orders by partner ID",
zap.Error(err),
zap.Int64("partnerID", partnerID),
zap.Int("limit", limit),
zap.Int("offset", offset))
return nil, errors.Wrap(err, "failed to get in-progress orders")
}
return orders, nil
}

View File

@ -51,6 +51,9 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
req.CustomerName, req.CustomerName,
req.CustomerPhoneNumber, req.CustomerPhoneNumber,
req.CustomerEmail, req.CustomerEmail,
req.PaymentProvider,
req.TableNumber,
req.OrderType,
) )
for _, item := range req.OrderItems { for _, item := range req.OrderItems {

View File

@ -10,13 +10,14 @@ import (
) )
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context, func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod string) (*entity.OrderResponse, error) { token string, paymentMethod, paymentProvider, inprogressOrderID string) (*entity.OrderResponse, error) {
inquiry, err := s.validateInquiry(ctx, token) inquiry, err := s.validateInquiry(ctx, token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
order := inquiry.ToOrder(paymentMethod) order := inquiry.ToOrder(paymentMethod, paymentProvider)
order.InProgressOrderID = inprogressOrderID
savedOrder, err := s.repo.Create(ctx, order) savedOrder, err := s.repo.Create(ctx, order)
if err != nil { if err != nil {
@ -51,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)) err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/1000), 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))
} }
@ -78,8 +79,8 @@ func (s *orderSvc) createTransaction(ctx mycontext.Context, order *entity.Order,
return transaction, err return transaction, err
} }
func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int) error { func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
return s.customer.AddPoints(ctx, customerID, points) return s.customer.AddPoints(ctx, customerID, points, reference)
} }
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 {

View File

@ -21,7 +21,7 @@ type ProductService interface {
type CustomerService interface { 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) 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)
} }
@ -42,7 +42,7 @@ type Service interface {
CreateOrderInquiry(ctx mycontext.Context, CreateOrderInquiry(ctx mycontext.Context,
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
ExecuteOrderInquiry(ctx mycontext.Context, ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod string) (*entity.OrderResponse, error) token string, paymentMethod, paymentProvider, inProgressOrderID string) (*entity.OrderResponse, error)
} }
type Config interface { type Config interface {

View File

@ -168,7 +168,7 @@
<div class="title">Kode Verifikasi Pendaftaran Member</div> <div class="title">Kode Verifikasi Pendaftaran Member</div>
<div class="text"> <div class="text">
Hai {{ .UserName }},<br><br> Hai {{ .UserName }},<br><br>
Terima kasih telah mendaftar sebagai member Enaklo. Berikan kode verifikasi berikut kepada staf kasir kami untuk menyelesaikan pendaftaran Anda: Terima kasih telah mendaftar sebagai member Enaklo. Masukan kode verifikasi berikut untuk menyelesaikan pendaftaran Anda:
</div> </div>
<div class="otp-code">{{ .OTPCode }}</div> <div class="otp-code">{{ .OTPCode }}</div>
<div class="expiry">Kode ini berlaku selama 10 menit</div> <div class="expiry">Kode ini berlaku selama 10 menit</div>