update
This commit is contained in:
parent
118ec58521
commit
c642c5c61b
@ -5,44 +5,48 @@ import "net/http"
|
||||
type ErrType string
|
||||
|
||||
const (
|
||||
errRequestTimeOut ErrType = "Request Timeout to 3rd Party"
|
||||
errConnectTimeOut ErrType = "Connect Timeout to 3rd Party"
|
||||
errFailedExternalCall ErrType = "Failed response from 3rd Party call"
|
||||
errExternalCall ErrType = "error on 3rd Party call"
|
||||
errInvalidRequest ErrType = "Invalid Request"
|
||||
errBadRequest ErrType = "Bad Request"
|
||||
errOrderNotFound ErrType = "Astria order is not found"
|
||||
errCheckoutIDNotDefined ErrType = "Checkout client id not found"
|
||||
errInternalServer ErrType = "Internal Server error"
|
||||
errExternalServer ErrType = "External Server error"
|
||||
errUserIsNotFound ErrType = "User is not found"
|
||||
errInvalidLogin ErrType = "User email or password is invalid"
|
||||
errUnauthorized ErrType = "Unauthorized"
|
||||
errInsufficientBalance ErrType = "Insufficient Balance"
|
||||
errInactivePartner ErrType = "Partner's license is invalid or has expired. Please contact Admin Support."
|
||||
errTicketAlreadyUsed ErrType = "Ticket Already Used."
|
||||
errProductIsRequired ErrType = "Product"
|
||||
errEmailAndPhoneNumberRequired ErrType = "Email or Phone is required"
|
||||
errRequestTimeOut ErrType = "Request Timeout to 3rd Party"
|
||||
errConnectTimeOut ErrType = "Connect Timeout to 3rd Party"
|
||||
errFailedExternalCall ErrType = "Failed response from 3rd Party call"
|
||||
errExternalCall ErrType = "error on 3rd Party call"
|
||||
errInvalidRequest ErrType = "Invalid Request"
|
||||
errBadRequest ErrType = "Bad Request"
|
||||
errOrderNotFound ErrType = "Astria order is not found"
|
||||
errCheckoutIDNotDefined ErrType = "Checkout client id not found"
|
||||
errInternalServer ErrType = "Internal Server error"
|
||||
errExternalServer ErrType = "External Server error"
|
||||
errUserIsNotFound ErrType = "User is not found"
|
||||
errInvalidLogin ErrType = "User email or password is invalid"
|
||||
errUnauthorized ErrType = "Unauthorized"
|
||||
errInsufficientBalance ErrType = "Insufficient Balance"
|
||||
errInactivePartner ErrType = "Partner's license is invalid or has expired. Please contact Admin Support."
|
||||
errTicketAlreadyUsed ErrType = "Ticket Already Used."
|
||||
errProductIsRequired ErrType = "Product"
|
||||
errEmailAndPhoneNumberRequired ErrType = "Email or Phone is required"
|
||||
errEmailAlreadyRegistered ErrType = "Email is already registered"
|
||||
errPhoneNumberAlreadyRegistered ErrType = "Phone is already registered"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorBadRequest = NewServiceException(errBadRequest)
|
||||
ErrorInvalidRequest = NewServiceException(errInvalidRequest)
|
||||
ErrorExternalRequest = NewServiceException(errExternalServer)
|
||||
ErrorUnauthorized = NewServiceException(errUnauthorized)
|
||||
ErrorOrderNotFound = NewServiceException(errOrderNotFound)
|
||||
ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined)
|
||||
ErrorRequestTimeout = NewServiceException(errRequestTimeOut)
|
||||
ErrorExternalCall = NewServiceException(errExternalCall)
|
||||
ErrorFailedExternalCall = NewServiceException(errFailedExternalCall)
|
||||
ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut)
|
||||
ErrorInternalServer = NewServiceException(errInternalServer)
|
||||
ErrorUserIsNotFound = NewServiceException(errUserIsNotFound)
|
||||
ErrorUserInvalidLogin = NewServiceException(errInvalidLogin)
|
||||
ErrorInsufficientBalance = NewServiceException(errInsufficientBalance)
|
||||
ErrorInvalidLicense = NewServiceException(errInactivePartner)
|
||||
ErrorTicketInvalidOrAlreadyUsed = NewServiceException(errTicketAlreadyUsed)
|
||||
ErrorPhoneNumberEmailIsRequired = NewServiceException(errEmailAndPhoneNumberRequired)
|
||||
ErrorBadRequest = NewServiceException(errBadRequest)
|
||||
ErrorInvalidRequest = NewServiceException(errInvalidRequest)
|
||||
ErrorExternalRequest = NewServiceException(errExternalServer)
|
||||
ErrorUnauthorized = NewServiceException(errUnauthorized)
|
||||
ErrorOrderNotFound = NewServiceException(errOrderNotFound)
|
||||
ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined)
|
||||
ErrorRequestTimeout = NewServiceException(errRequestTimeOut)
|
||||
ErrorExternalCall = NewServiceException(errExternalCall)
|
||||
ErrorFailedExternalCall = NewServiceException(errFailedExternalCall)
|
||||
ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut)
|
||||
ErrorInternalServer = NewServiceException(errInternalServer)
|
||||
ErrorUserIsNotFound = NewServiceException(errUserIsNotFound)
|
||||
ErrorUserInvalidLogin = NewServiceException(errInvalidLogin)
|
||||
ErrorInsufficientBalance = NewServiceException(errInsufficientBalance)
|
||||
ErrorInvalidLicense = NewServiceException(errInactivePartner)
|
||||
ErrorTicketInvalidOrAlreadyUsed = NewServiceException(errTicketAlreadyUsed)
|
||||
ErrorPhoneNumberEmailIsRequired = NewServiceException(errEmailAndPhoneNumberRequired)
|
||||
ErrorPhoneNumberIsAlreadyRegistered = NewServiceException(errPhoneNumberAlreadyRegistered)
|
||||
ErrorEmailIsAlreadyRegistered = NewServiceException(errEmailAlreadyRegistered)
|
||||
)
|
||||
|
||||
type Error interface {
|
||||
|
||||
2
internal/entity/customer.go
Normal file
2
internal/entity/customer.go
Normal file
@ -0,0 +1,2 @@
|
||||
package entity
|
||||
|
||||
29
internal/entity/in_progress_order.go
Normal file
29
internal/entity/in_progress_order.go
Normal 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
|
||||
}
|
||||
@ -5,24 +5,29 @@ import (
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
PartnerID int64 `gorm:"type:int;column:partner_id"`
|
||||
Status string `gorm:"type:varchar;column:status"`
|
||||
Amount float64 `gorm:"type:numeric;not null;column:amount"`
|
||||
Total float64 `gorm:"type:numeric;not null;column:total"`
|
||||
Fee float64 `gorm:"type:numeric;not null;column:fee"`
|
||||
CustomerID *int64
|
||||
InquiryID *string
|
||||
Site *Site `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
PaymentType string `gorm:"type:varchar;column:payment_type"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
|
||||
Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
|
||||
User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"`
|
||||
Source string `gorm:"type:varchar;column:source"`
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
PartnerID int64 `gorm:"type:int;column:partner_id"`
|
||||
Status string `gorm:"type:varchar;column:status"`
|
||||
Amount float64 `gorm:"type:numeric;not null;column:amount"`
|
||||
Total float64 `gorm:"type:numeric;not null;column:total"`
|
||||
Fee float64 `gorm:"type:numeric;not null;column:fee"`
|
||||
CustomerID *int64
|
||||
CustomerName string
|
||||
InquiryID *string
|
||||
Site *Site `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
PaymentType string `gorm:"type:varchar;column:payment_type"`
|
||||
PaymentProvider string `gorm:"type:varchar;column:payment_provider"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
|
||||
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 {
|
||||
@ -97,6 +102,9 @@ type OrderRequest struct {
|
||||
CustomerName string
|
||||
CustomerEmail string
|
||||
CustomerPhoneNumber string
|
||||
TableNumber string
|
||||
PaymentProvider string
|
||||
OrderType string
|
||||
}
|
||||
|
||||
type OrderItemRequest struct {
|
||||
@ -111,11 +119,7 @@ type OrderExecuteRequest struct {
|
||||
}
|
||||
|
||||
func (o *Order) SetExecutePaymentStatus() {
|
||||
if o.PaymentType == "CASH" {
|
||||
o.Status = "PAID"
|
||||
return
|
||||
}
|
||||
o.Status = "PENDING"
|
||||
o.Status = "PAID"
|
||||
}
|
||||
|
||||
type CallbackRequest struct {
|
||||
|
||||
@ -23,6 +23,9 @@ type OrderInquiry struct {
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
OrderItems []OrderItem `json:"order_items"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
TableNumber string `json:"table_number"`
|
||||
OrderType string `json:"order_type"`
|
||||
}
|
||||
|
||||
type OrderCalculation struct {
|
||||
@ -48,6 +51,9 @@ func NewOrderInquiry(
|
||||
customerName string,
|
||||
customerPhoneNumber string,
|
||||
customerEmail string,
|
||||
paymentProvider string,
|
||||
tableNumber string,
|
||||
orderType string,
|
||||
) *OrderInquiry {
|
||||
return &OrderInquiry{
|
||||
ID: constants.GenerateUUID(),
|
||||
@ -66,6 +72,9 @@ func NewOrderInquiry(
|
||||
CustomerName: customerName,
|
||||
CustomerEmail: customerEmail,
|
||||
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()
|
||||
|
||||
order := &Order{
|
||||
PartnerID: i.PartnerID,
|
||||
CustomerID: &i.CustomerID,
|
||||
InquiryID: &i.ID,
|
||||
Status: constants.StatusPaid,
|
||||
Amount: i.Amount,
|
||||
Fee: i.Fee,
|
||||
Total: i.Total,
|
||||
PaymentType: paymentMethod,
|
||||
Source: i.Source,
|
||||
CreatedBy: i.CreatedBy,
|
||||
CreatedAt: now,
|
||||
OrderItems: make([]OrderItem, len(i.OrderItems)),
|
||||
PartnerID: i.PartnerID,
|
||||
CustomerID: &i.CustomerID,
|
||||
InquiryID: &i.ID,
|
||||
Status: constants.StatusPaid,
|
||||
Amount: i.Amount,
|
||||
Fee: i.Fee,
|
||||
Total: i.Total,
|
||||
PaymentType: paymentMethod,
|
||||
PaymentProvider: paymentProvider,
|
||||
Source: i.Source,
|
||||
CreatedBy: i.CreatedBy,
|
||||
CreatedAt: now,
|
||||
OrderItems: make([]OrderItem, len(i.OrderItems)),
|
||||
OrderType: i.OrderType,
|
||||
}
|
||||
|
||||
for idx, item := range i.OrderItems {
|
||||
|
||||
@ -30,27 +30,29 @@ type User struct {
|
||||
}
|
||||
|
||||
type Customer struct {
|
||||
ID int64
|
||||
Name string
|
||||
Email string
|
||||
Password string
|
||||
Phone string
|
||||
Points int
|
||||
Status userstatus.UserStatus
|
||||
NIK string
|
||||
UserType string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
RoleID role.Role
|
||||
PhoneNumber string
|
||||
RoleName string
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
SiteName string
|
||||
PartnerName string
|
||||
ResetPassword bool
|
||||
CustomerID string
|
||||
BirthDate time.Time
|
||||
ID int64
|
||||
Name string
|
||||
Email string
|
||||
Password string
|
||||
Phone string
|
||||
Points int
|
||||
Status userstatus.UserStatus
|
||||
NIK string
|
||||
UserType string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
RoleID role.Role
|
||||
PhoneNumber string
|
||||
RoleName string
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
SiteName string
|
||||
PartnerName string
|
||||
ResetPassword bool
|
||||
CustomerID string
|
||||
BirthDate time.Time
|
||||
VerificationID string
|
||||
OTP string
|
||||
}
|
||||
|
||||
type AuthenticateUser struct {
|
||||
@ -117,3 +119,9 @@ func (u User) HashedPassword(password string) (string, error) {
|
||||
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
func (c Customer) HashedPassword() string {
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(c.Password), bcrypt.DefaultCost)
|
||||
|
||||
return string(hashedPassword)
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package customerauth
|
||||
|
||||
import (
|
||||
auth2 "enaklo-pos-be/internal/handlers/request"
|
||||
"enaklo-pos-be/internal/services/v2/customer"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -8,7 +10,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"enaklo-pos-be/internal/common/errors"
|
||||
auth2 "enaklo-pos-be/internal/handlers/request"
|
||||
"enaklo-pos-be/internal/handlers/response"
|
||||
"enaklo-pos-be/internal/services"
|
||||
)
|
||||
@ -16,6 +17,7 @@ import (
|
||||
type AuthHandler struct {
|
||||
service services.Auth
|
||||
userService services.User
|
||||
customerSvc customer.Service
|
||||
}
|
||||
|
||||
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("/reset-password", jwt, a.ResetPassword)
|
||||
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{
|
||||
service: service,
|
||||
userService: userService,
|
||||
customerSvc: customerSvc,
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,31 +151,47 @@ func (h *AuthHandler) ResetPassword(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 {
|
||||
response.ErrorWrapper(c, errors.ErrorBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := auth2.GetMyContext(c)
|
||||
res, err := h.userService.Create(ctx, req.ToEntity())
|
||||
customer, err := h.customerSvc.RegistrationMember(ctx, req.ToEntity())
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := response.UserRegister{
|
||||
ID: res.ID,
|
||||
Name: res.Name,
|
||||
Email: res.Email,
|
||||
Status: string(res.Status),
|
||||
CreatedAt: res.CreatedAt,
|
||||
UpdatedAt: res.UpdatedAt,
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: response.CustomerRegistrationResp{
|
||||
EmailVerificationRequired: true,
|
||||
PhoneVerificationRequired: false,
|
||||
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{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: resp,
|
||||
Message: "Email verification successful",
|
||||
})
|
||||
}
|
||||
|
||||
173
internal/handlers/http/inprogress_order.go
Normal file
173
internal/handlers/http/inprogress_order.go
Normal 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),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -35,6 +35,17 @@ type InquiryRequest struct {
|
||||
CustomerPhoneNumber string `json:"customer_phone_number"`
|
||||
PaymentMethod string `json:"payment_method" validate:"required"`
|
||||
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 {
|
||||
@ -43,8 +54,10 @@ type OrderItemRequest struct {
|
||||
}
|
||||
|
||||
type ExecuteRequest struct {
|
||||
PaymentMethod string `json:"payment_method" validate:"required"`
|
||||
Token string `json:"token"`
|
||||
PaymentMethod string `json:"payment_method" validate:"required"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
InProgressOrderID string `json:"in_progress_order_id"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (h *Handler) Inquiry(c *gin.Context) {
|
||||
@ -82,6 +95,9 @@ func (h *Handler) Inquiry(c *gin.Context) {
|
||||
CustomerName: req.CustomerName,
|
||||
CustomerEmail: req.CustomerEmail,
|
||||
CustomerPhoneNumber: req.CustomerPhoneNumber,
|
||||
OrderType: req.OrderType,
|
||||
PaymentProvider: req.GetPaymentProvider(),
|
||||
TableNumber: req.TableNumber,
|
||||
}
|
||||
|
||||
result, err := h.service.CreateOrderInquiry(ctx, orderReq)
|
||||
@ -112,7 +128,7 @@ func (h *Handler) Execute(c *gin.Context) {
|
||||
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 {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
|
||||
49
internal/handlers/request/customer.go
Normal file
49
internal/handlers/request/customer.go
Normal 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"`
|
||||
}
|
||||
@ -7,11 +7,13 @@ import (
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
CustomerName string `json:"customer_name"`
|
||||
CustomerPhone string `json:"customer_phone"`
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
OrderItems []OrderItem `json:"order_items"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
CustomerPhone string `json:"customer_phone"`
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
TableNumber string `json:"table_number"`
|
||||
OrderItems []OrderItem `json:"order_items"`
|
||||
}
|
||||
|
||||
type CustomerOrder struct {
|
||||
|
||||
@ -174,7 +174,5 @@ func (u *UserRegister) ToEntity() *entity.User {
|
||||
Email: strings.ToLower(u.Email),
|
||||
PhoneNumber: u.PhoneNumber,
|
||||
Password: u.Password,
|
||||
RoleID: role.Customer,
|
||||
UserType: "CUSTOMER",
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,3 +33,9 @@ func MapToCustomerListResponse(customers *entity.MemberList) []CustomerResponse
|
||||
|
||||
return responseList
|
||||
}
|
||||
|
||||
type CustomerRegistrationResp struct {
|
||||
EmailVerificationRequired bool `json:"email_verification_required"`
|
||||
PhoneVerificationRequired bool `json:"phone_verification_required"`
|
||||
VerificationID string `json:"verification_id"`
|
||||
}
|
||||
|
||||
@ -46,10 +46,10 @@ type CustomerList struct {
|
||||
}
|
||||
|
||||
type UserRegister struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
267
internal/repository/In_progress_orde_repo.go
Normal file
267
internal/repository/In_progress_orde_repo.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,11 @@ import (
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"enaklo-pos-be/internal/repository/models"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -14,9 +17,10 @@ type CustomerRepo interface {
|
||||
FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
||||
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
||||
AddPoints(ctx mycontext.Context, id int64, points int) error
|
||||
AddPoints(ctx mycontext.Context, id int64, points int, reference string) 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)
|
||||
}
|
||||
|
||||
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) {
|
||||
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")
|
||||
}
|
||||
|
||||
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.VerificationID = verificationCode.VerificationID.String()
|
||||
customer.OTP = otpCode
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
@ -84,22 +128,45 @@ func (r *customerRepository) FindByEmail(ctx mycontext.Context, email string) (*
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
func (r *customerRepository) AddPoints(ctx mycontext.Context, id int64, points int) error {
|
||||
now := time.Now()
|
||||
func (r *customerRepository) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
|
||||
tx := r.db.Begin()
|
||||
if tx.Error != nil {
|
||||
return errors.Wrap(tx.Error, "failed to begin transaction")
|
||||
}
|
||||
|
||||
result := r.db.Model(&models.CustomerDB{}).
|
||||
Where("id = ?", id).
|
||||
result := tx.Model(&models.CustomerPointsDB{}).
|
||||
Where("customer_id = ?", customerID).
|
||||
Updates(map[string]interface{}{
|
||||
"points": gorm.Expr("points + ?", points),
|
||||
"updated_at": now,
|
||||
"total_points": gorm.Expr("total_points + ?", points),
|
||||
"available_points": gorm.Expr("available_points + ?", points),
|
||||
"last_updated": time.Now(),
|
||||
})
|
||||
|
||||
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 {
|
||||
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
|
||||
@ -107,15 +174,15 @@ func (r *customerRepository) AddPoints(ctx mycontext.Context, id int64, points i
|
||||
|
||||
func (r *customerRepository) toCustomerDBModel(customer *entity.Customer) models.CustomerDB {
|
||||
return models.CustomerDB{
|
||||
ID: customer.ID,
|
||||
Name: customer.Name,
|
||||
Email: customer.Email,
|
||||
Phone: customer.Phone,
|
||||
Points: customer.Points,
|
||||
CreatedAt: customer.CreatedAt,
|
||||
UpdatedAt: customer.UpdatedAt,
|
||||
CustomerID: customer.CustomerID,
|
||||
BirthDate: customer.BirthDate,
|
||||
ID: customer.ID,
|
||||
Name: customer.Name,
|
||||
Email: customer.Email,
|
||||
Phone: customer.Phone,
|
||||
Points: customer.Points,
|
||||
CreatedAt: customer.CreatedAt,
|
||||
UpdatedAt: customer.UpdatedAt,
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CustomerDB struct {
|
||||
ID int64 `gorm:"primaryKey;column:id"`
|
||||
Name string `gorm:"column:name"`
|
||||
Email string `gorm:"column:email"`
|
||||
Phone string `gorm:"column:phone"`
|
||||
Points int `gorm:"column:points"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
CustomerID string `gorm:"column:customer_id"`
|
||||
BirthDate time.Time `gorm:"column:birth_date"`
|
||||
ID int64 `gorm:"primaryKey;column:id"`
|
||||
Name string `gorm:"column:name"`
|
||||
Email string `gorm:"column:email"`
|
||||
Phone string `gorm:"column:phone"`
|
||||
Points int `gorm:"column:points"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
CustomerID string `gorm:"column:customer_id"`
|
||||
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 {
|
||||
@ -30,3 +34,45 @@ type PartnerMemberSequence struct {
|
||||
func (PartnerMemberSequence) TableName() string {
|
||||
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"
|
||||
}
|
||||
|
||||
35
internal/repository/models/in_progress_order.go
Normal file
35
internal/repository/models/in_progress_order.go
Normal 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"
|
||||
}
|
||||
@ -58,6 +58,9 @@ type OrderInquiryDB struct {
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
ExpiresAt time.Time `gorm:"column:expires_at"`
|
||||
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 {
|
||||
|
||||
@ -15,6 +15,7 @@ type ProductDB struct {
|
||||
Status string `gorm:"column:status"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
Image string `gorm:"column:image"`
|
||||
}
|
||||
|
||||
func (ProductDB) TableName() string {
|
||||
|
||||
@ -61,6 +61,18 @@ func (r *orderRepository) Create(ctx mycontext.Context, order *entity.Order) (*e
|
||||
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 {
|
||||
return nil, errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
@ -263,6 +275,9 @@ func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) mo
|
||||
CustomerName: inquiry.CustomerName,
|
||||
CustomerPhoneNumber: inquiry.CustomerPhoneNumber,
|
||||
CustomerEmail: inquiry.CustomerEmail,
|
||||
PaymentProvider: inquiry.PaymentProvider,
|
||||
OrderType: inquiry.OrderType,
|
||||
TableNumber: inquiry.TableNumber,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -141,9 +141,8 @@ func (b *OrderRepository) GetAllHystoryOrders(ctx context.Context, req entity.Or
|
||||
|
||||
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").
|
||||
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 sites on orders.site_id = sites.id").
|
||||
Where("orders.payment_type != ?", "NEW")
|
||||
|
||||
if req.PaymentType != "" {
|
||||
@ -176,7 +175,7 @@ func (b *OrderRepository) GetAllHystoryOrders(ctx context.Context, req entity.Or
|
||||
}
|
||||
|
||||
if req.SiteID != nil {
|
||||
query = query.Where("orders.site_id = ?", req.SiteID)
|
||||
query = query.Where("orders.partner_id = ?", req.SiteID)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if req.SiteID != nil {
|
||||
query = query.Where("orders.site_id = ?", req.SiteID)
|
||||
}
|
||||
|
||||
if err := query.Scan(&amount).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when get cash amount", zap.Error(err))
|
||||
return nil, err
|
||||
|
||||
@ -52,11 +52,12 @@ type RepoManagerImpl struct {
|
||||
PG PaymentGateway
|
||||
LinkQu LinkQu
|
||||
|
||||
OrderRepo OrderRepository
|
||||
CustomerRepo CustomerRepo
|
||||
ProductRepo ProductRepository
|
||||
TransactionRepo TransactionRepo
|
||||
MemberRepository MemberRepository
|
||||
OrderRepo OrderRepository
|
||||
InProgressOrderRepo InProgressOrderRepository
|
||||
CustomerRepo CustomerRepo
|
||||
ProductRepo ProductRepository
|
||||
TransactionRepo TransactionRepo
|
||||
MemberRepository MemberRepository
|
||||
}
|
||||
|
||||
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),
|
||||
LinkQu: linkqu.NewLinkQuService(&cfg.LinkQu),
|
||||
|
||||
OrderRepo: NeworderRepository(db),
|
||||
CustomerRepo: NewCustomerRepository(db),
|
||||
ProductRepo: NewproductRepository(db),
|
||||
TransactionRepo: NewTransactionRepository(db),
|
||||
MemberRepository: NewMemberRepository(db),
|
||||
OrderRepo: NeworderRepository(db),
|
||||
CustomerRepo: NewCustomerRepository(db),
|
||||
ProductRepo: NewproductRepository(db),
|
||||
TransactionRepo: NewTransactionRepository(db),
|
||||
MemberRepository: NewMemberRepository(db),
|
||||
InProgressOrderRepo: NewInProgressOrderRepository(db),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan
|
||||
|
||||
serverRoutes := []HTTPHandlerRoutes{
|
||||
discovery.NewHandler(serviceManager.DiscoverService),
|
||||
customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc),
|
||||
customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc, serviceManager.CustomerV2Svc),
|
||||
customerorder.NewHandler(serviceManager.OrderSvc),
|
||||
}
|
||||
|
||||
|
||||
@ -80,6 +80,7 @@ func RegisterPrivateRoutesV2(app *app.Server, serviceManager *services.ServiceMa
|
||||
http2.NewOrderHandler(serviceManager.OrderV2Svc),
|
||||
http2.NewMemberRegistrationHandler(serviceManager.MemberRegistrationSvc),
|
||||
http2.NewCustomerHandler(serviceManager.CustomerV2Svc),
|
||||
http2.NewInProgressOrderHandler(serviceManager.InProgressSvc),
|
||||
}
|
||||
|
||||
for _, handler := range serverRoutes {
|
||||
|
||||
@ -83,7 +83,6 @@ func (u *AuthServiceImpl) AuthenticateUser(ctx context.Context, email, password
|
||||
}
|
||||
|
||||
func (u *AuthServiceImpl) SendPasswordResetLink(ctx context.Context, email string) error {
|
||||
// Check if the user exists
|
||||
user, err := u.authRepo.CheckExistsUserAccount(ctx, email)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when getting user", zap.Error(err))
|
||||
|
||||
@ -162,7 +162,6 @@ func (s *memberSvc) ResendOTP(
|
||||
) (*entity.ResendOTPResponse, error) {
|
||||
logger.ContextLogger(ctx).Info("resending OTP", zap.String("token", token))
|
||||
|
||||
// Get registration by token
|
||||
registration, err := s.repo.GetRegistrationByToken(ctx, token)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get registration", zap.Error(err))
|
||||
@ -211,7 +210,7 @@ func (s *memberSvc) sendRegistrationOTP(
|
||||
Recipient: registration.Email,
|
||||
Subject: "Enaklo - Registration Verification Code",
|
||||
TemplateName: "member_registration_otp",
|
||||
TemplatePath: "/templates/member_registration_otp.html",
|
||||
TemplatePath: "templates/member_registration_otp.html",
|
||||
Data: emailData,
|
||||
})
|
||||
|
||||
|
||||
@ -240,32 +240,6 @@ func (s *OrderService) Execute(ctx mycontext.Context, req *entity.OrderExecuteRe
|
||||
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, err = s.repo.Update(ctx, order)
|
||||
if err != nil {
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"enaklo-pos-be/internal/services/transaction"
|
||||
"enaklo-pos-be/internal/services/users"
|
||||
customerSvc "enaklo-pos-be/internal/services/v2/customer"
|
||||
"enaklo-pos-be/internal/services/v2/inprogress_order"
|
||||
orderSvc "enaklo-pos-be/internal/services/v2/order"
|
||||
productSvc "enaklo-pos-be/internal/services/v2/product"
|
||||
|
||||
@ -47,12 +48,14 @@ type ServiceManagerImpl struct {
|
||||
CustomerV2Svc customerSvc.Service
|
||||
ProductV2Svc productSvc.Service
|
||||
MemberRegistrationSvc member.RegistrationService
|
||||
InProgressSvc inprogress_order.InProgressOrderService
|
||||
}
|
||||
|
||||
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)
|
||||
inprogressOrder := inprogress_order.NewInProgressOrderService(repo.InProgressOrderRepo)
|
||||
|
||||
return &ServiceManagerImpl{
|
||||
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),
|
||||
MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2),
|
||||
CustomerV2Svc: custSvcV2,
|
||||
InProgressSvc: inprogressOrder,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"context"
|
||||
errors2 "enaklo-pos-be/internal/common/errors"
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/constants"
|
||||
@ -8,7 +10,9 @@ import (
|
||||
"enaklo-pos-be/internal/utils"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
@ -16,26 +20,35 @@ type Repository interface {
|
||||
FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
||||
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
||||
AddPoints(ctx mycontext.Context, id int64, points int) error
|
||||
AddPoints(ctx mycontext.Context, id int64, points int, reference string) 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)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
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)
|
||||
CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, 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 {
|
||||
repo Repository
|
||||
repo Repository
|
||||
notification EmailService
|
||||
}
|
||||
|
||||
func New(repo Repository) Service {
|
||||
func New(repo Repository, notification EmailService) Service {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.repo.AddPoints(ctx, customerID, points)
|
||||
err := s.repo.AddPoints(ctx, customerID, points, reference)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
51
internal/services/v2/inprogress_order/in_progress_order.go
Normal file
51
internal/services/v2/inprogress_order/in_progress_order.go
Normal 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
|
||||
}
|
||||
@ -51,6 +51,9 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
||||
req.CustomerName,
|
||||
req.CustomerPhoneNumber,
|
||||
req.CustomerEmail,
|
||||
req.PaymentProvider,
|
||||
req.TableNumber,
|
||||
req.OrderType,
|
||||
)
|
||||
|
||||
for _, item := range req.OrderItems {
|
||||
|
||||
@ -10,13 +10,14 @@ import (
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
order := inquiry.ToOrder(paymentMethod)
|
||||
order := inquiry.ToOrder(paymentMethod, paymentProvider)
|
||||
order.InProgressOrderID = inprogressOrderID
|
||||
|
||||
savedOrder, err := s.repo.Create(ctx, order)
|
||||
if err != nil {
|
||||
@ -51,7 +52,7 @@ func (s *orderSvc) processPostOrderActions(
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int) error {
|
||||
return s.customer.AddPoints(ctx, customerID, points)
|
||||
func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
|
||||
return s.customer.AddPoints(ctx, customerID, points, reference)
|
||||
}
|
||||
|
||||
func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error {
|
||||
|
||||
@ -21,7 +21,7 @@ type ProductService interface {
|
||||
|
||||
type CustomerService interface {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ type Service interface {
|
||||
CreateOrderInquiry(ctx mycontext.Context,
|
||||
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
|
||||
ExecuteOrderInquiry(ctx mycontext.Context,
|
||||
token string, paymentMethod string) (*entity.OrderResponse, error)
|
||||
token string, paymentMethod, paymentProvider, inProgressOrderID string) (*entity.OrderResponse, error)
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
|
||||
@ -168,7 +168,7 @@
|
||||
<div class="title">Kode Verifikasi Pendaftaran Member</div>
|
||||
<div class="text">
|
||||
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 class="otp-code">{{ .OTPCode }}</div>
|
||||
<div class="expiry">Kode ini berlaku selama 10 menit</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user