Add Checkin

This commit is contained in:
aditya.siregar 2024-08-13 23:09:05 +07:00
parent 4c8999a3cf
commit e544ef8f71
18 changed files with 362 additions and 101 deletions

View File

@ -8,6 +8,7 @@ const (
BadRequest Code = "40000" BadRequest Code = "40000"
InvalidRequest Code = "40001" InvalidRequest Code = "40001"
Unauthorized Code = "40100" Unauthorized Code = "40100"
CheckinInvalid Code = "40002"
Forbidden Code = "40300" Forbidden Code = "40300"
Timeout Code = "50400" Timeout Code = "50400"
) )
@ -23,6 +24,7 @@ var (
ServerError: "Internal Server Error", ServerError: "Internal Server Error",
Forbidden: "Forbidden", Forbidden: "Forbidden",
InvalidRequest: "Invalid Request", InvalidRequest: "Invalid Request",
CheckinInvalid: "Ticket Already Used or Expired",
} }
codeHTTPMap = map[Code]int{ codeHTTPMap = map[Code]int{
@ -33,6 +35,7 @@ var (
ServerError: http.StatusInternalServerError, ServerError: http.StatusInternalServerError,
Forbidden: http.StatusForbidden, Forbidden: http.StatusForbidden,
InvalidRequest: http.StatusUnprocessableEntity, InvalidRequest: http.StatusUnprocessableEntity,
CheckinInvalid: http.StatusBadRequest,
} }
) )

View File

@ -19,23 +19,25 @@ const (
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."
) )
var ( var (
ErrorBadRequest = NewServiceException(errBadRequest) ErrorBadRequest = NewServiceException(errBadRequest)
ErrorInvalidRequest = NewServiceException(errInvalidRequest) ErrorInvalidRequest = NewServiceException(errInvalidRequest)
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)
) )
type Error interface { type Error interface {
@ -115,6 +117,9 @@ func (s *ServiceException) MapErrorsToCode() Code {
case errInactivePartner: case errInactivePartner:
return BadRequest return BadRequest
case errTicketAlreadyUsed:
return CheckinInvalid
default: default:
return BadRequest return BadRequest
} }

View File

@ -5,23 +5,25 @@ import (
) )
type Order struct { type Order struct {
ID int64 `gorm:"primaryKey;autoIncrement;column:id"` ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
RefID string `gorm:"type:varchar;column:ref_id"` RefID string `gorm:"type:varchar;column:ref_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"`
SiteID *int64 `gorm:"type:numeric;not null;column:site_id"` SiteID *int64 `gorm:"type:numeric;not null;column:site_id"`
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"` CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"` UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
CreatedBy int64 `gorm:"type:int;column:created_by"` CreatedBy int64 `gorm:"type:int;column:created_by"`
PaymentType string `gorm:"type:varchar;column:payment_type"` PaymentType string `gorm:"type:varchar;column:payment_type"`
UpdatedBy int64 `gorm:"type:int;column:updated_by"` UpdatedBy int64 `gorm:"type:int;column:updated_by"`
OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"` User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"`
Source string `gorm:"type:varchar;column:source"` Source string `gorm:"type:varchar;column:source"`
TicketStatus string `gorm:"type:varchar;column:ticket_status"`
VisitDate time.Time `gorm:"type:date;column:visit_date"`
} }
type OrderDB struct { type OrderDB struct {
@ -45,6 +47,16 @@ type OrderResponse struct {
Token string Token string
} }
type CheckinResponse struct {
Order *Order
Token string
}
type CheckinExecute struct {
Order *Order
Token string
}
type ExecuteOrderResponse struct { type ExecuteOrderResponse struct {
Order *Order Order *Order
QRCode string QRCode string
@ -107,19 +119,19 @@ type CallbackRequest struct {
} }
type HistoryOrder struct { type HistoryOrder struct {
ID int64 `gorm:"primaryKey;autoIncrement;column:id"` ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
Employee string `gorm:"type:varchar;column:employee"` Employee string `gorm:"type:varchar;column:employee"`
Site string `gorm:"type:varchar;column:site"` Site string `gorm:"type:varchar;column:site"`
Timestamp time.Time `gorm:"autoCreateTime;column:timestamp"` Timestamp time.Time `gorm:"autoCreateTime;column:timestamp"`
BookingTime time.Time `gorm:"autoCreateTime;column:booking_time"` BookingTime time.Time `gorm:"autoCreateTime;column:booking_time"`
Tickets []string `gorm:"-"` Tickets []string `gorm:"-"`
RawTickets string `gorm:"type:text;column:tickets"` RawTickets string `gorm:"type:text;column:tickets"`
PaymentType string `gorm:"type:varchar;column:payment_type"` PaymentType string `gorm:"type:varchar;column:payment_type"`
Status string `gorm:"type:varchar;column:status"` Status string `gorm:"type:varchar;column:status"`
Amount float64 `gorm:"type:numeric;column:amount"` Amount float64 `gorm:"type:numeric;column:amount"`
VisitDate time.Time `gorm:"type:date;column:visit_date"` VisitDate time.Time `gorm:"type:date;column:visit_date"`
TicketStatus string `gorm:"type:varchar;column:ticket_status"` TicketStatus string `gorm:"type:varchar;column:ticket_status"`
Source string `gorm:"type:numeric;column:source"` Source string `gorm:"type:numeric;column:source"`
} }
type HistoryOrderDB struct { type HistoryOrderDB struct {
@ -139,7 +151,7 @@ type OrderSearch struct {
EndDate string EndDate string
Period string Period string
IsCustomer bool IsCustomer bool
Source string Source string
} }
type HistoryOrderList []*HistoryOrderDB type HistoryOrderList []*HistoryOrderDB
@ -152,19 +164,19 @@ func (b *HistoryOrder) ToHistoryOrderDB() *HistoryOrderDB {
func (e *HistoryOrderDB) ToHistoryOrder() *HistoryOrder { func (e *HistoryOrderDB) ToHistoryOrder() *HistoryOrder {
return &HistoryOrder{ return &HistoryOrder{
ID: e.ID, ID: e.ID,
Employee: e.Employee, Employee: e.Employee,
Site: e.Site, Site: e.Site,
Timestamp: e.Timestamp, Timestamp: e.Timestamp,
BookingTime: e.BookingTime, BookingTime: e.BookingTime,
Tickets: e.Tickets, Tickets: e.Tickets,
RawTickets: e.RawTickets, RawTickets: e.RawTickets,
PaymentType: e.PaymentType, PaymentType: e.PaymentType,
Status: e.Status, Status: e.Status,
Amount: e.Amount, Amount: e.Amount,
VisitDate: e.VisitDate, VisitDate: e.VisitDate,
TicketStatus: e.TicketStatus, TicketStatus: e.TicketStatus,
Source: e.Source, Source: e.Source,
} }
} }

View File

@ -21,6 +21,8 @@ type Product struct {
DeletedAt *time.Time `gorm:"column:deleted_at"` DeletedAt *time.Time `gorm:"column:deleted_at"`
CreatedBy int64 `gorm:"type:int;column:created_by"` CreatedBy int64 `gorm:"type:int;column:created_by"`
UpdatedBy int64 `gorm:"type:int;column:updated_by"` UpdatedBy int64 `gorm:"type:int;column:updated_by"`
Region string `gorm:"type:varchar;column:region"`
Regency string `gorm:"type:varchar;column:regency"`
} }
func (Product) TableName() string { func (Product) TableName() string {

View File

@ -29,6 +29,8 @@ type Site struct {
Longitude *float64 `json:"longitude"` Longitude *float64 `json:"longitude"`
Region string `json:"region"` Region string `json:"region"`
Regency string `json:"regency"` Regency string `json:"regency"`
Lat float64 `json:"lat"`
Long float64 `json:"long"`
Distance float64 `gorm:"-"` Distance float64 `gorm:"-"`
} }

View File

@ -197,6 +197,8 @@ func ConvertEntityToGetByIDResp(resp *entity.Site) *response.SearchSiteByIDRespo
TnC: resp.TnC, TnC: resp.TnC,
AdditionalInfo: resp.AdditionalInfo, AdditionalInfo: resp.AdditionalInfo,
PartnerID: resp.PartnerID, PartnerID: resp.PartnerID,
Regency: resp.Regency,
Region: resp.Region,
} }
} }

View File

@ -24,6 +24,8 @@ func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
route.POST("/execute", jwt, h.Execute) route.POST("/execute", jwt, h.Execute)
route.GET("/history", jwt, h.GetAllHistoryOrders) route.GET("/history", jwt, h.GetAllHistoryOrders)
route.GET("/ticket-sold", jwt, h.CountSoldOfTicket) route.GET("/ticket-sold", jwt, h.CountSoldOfTicket)
route.POST("/checkin/inquiry", jwt, h.CheckInInquiry)
route.POST("/checkin/execute", jwt, h.CheckInExecute)
route.GET("/sum-amount", jwt, h.SumAmount) route.GET("/sum-amount", jwt, h.SumAmount)
route.GET("/daily-sales", jwt, h.GetDailySalesTicket) route.GET("/daily-sales", jwt, h.GetDailySalesTicket)
route.GET("/payment-distribution", jwt, h.GetPaymentDistributionChart) route.GET("/payment-distribution", jwt, h.GetPaymentDistributionChart)
@ -106,6 +108,66 @@ func (h *Handler) Execute(c *gin.Context) {
}) })
} }
func (h *Handler) CheckInInquiry(c *gin.Context) {
ctx := request.GetMyContext(c)
var req request.Checkin
if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
if !ctx.IsCasheer() || req.QRCode == "" {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
partnerID := ctx.GetPartnerID()
resp, err := h.service.CheckInInquiry(ctx, req.QRCode, partnerID)
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: response.CheckingInquiryResponse{
Token: resp.Token,
},
})
}
func (h *Handler) CheckInExecute(c *gin.Context) {
ctx := request.GetMyContext(c)
var req request.CheckinExecute
if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
if !ctx.IsCasheer() || req.Token == "" {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
partnerID := ctx.GetPartnerID()
resp, err := h.service.CheckInExecute(ctx, req.Token, partnerID)
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: MapOrderToExecuteCheckinResponse(resp.Order),
})
}
func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse) response.CreateOrderResponse { func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse) response.CreateOrderResponse {
order := orderResponse.Order order := orderResponse.Order
orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems)) orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems))
@ -162,20 +224,44 @@ func MapOrderToExecuteOrderResponse(orderResponse *entity.ExecuteOrderResponse)
} }
} }
func MapOrderToExecuteCheckinResponse(order *entity.Order) response.ExecuteCheckinResponse {
orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems))
for i, item := range order.OrderItems {
orderItems[i] = response.CreateOrderItemResponse{
ID: item.ID,
ItemID: item.ItemID,
Quantity: item.Quantity,
Price: item.Price,
Name: item.Product.Name,
}
}
return response.ExecuteCheckinResponse{
ID: order.ID,
RefID: order.RefID,
PartnerID: order.PartnerID,
Status: order.Status,
Amount: order.Amount,
PaymentType: order.PaymentType,
CreatedAt: order.CreatedAt,
OrderItems: orderItems,
}
}
func (h *Handler) toHistoryOrderResponse(resp *entity.HistoryOrder) response.HistoryOrder { func (h *Handler) toHistoryOrderResponse(resp *entity.HistoryOrder) response.HistoryOrder {
return response.HistoryOrder{ return response.HistoryOrder{
ID: resp.ID, ID: resp.ID,
Employee: resp.Employee, Employee: resp.Employee,
Site: resp.Site, Site: resp.Site,
Timestamp: resp.Timestamp.Format(time.RFC3339), Timestamp: resp.Timestamp.Format(time.RFC3339),
BookingTime: resp.BookingTime.Format(time.RFC3339), BookingTime: resp.BookingTime.Format(time.RFC3339),
Tickets: resp.Tickets, Tickets: resp.Tickets,
PaymentType: resp.PaymentType, PaymentType: resp.PaymentType,
Status: resp.Status, Status: resp.Status,
Amount: resp.Amount, Amount: resp.Amount,
VisitDate: resp.VisitDate.Format(time.RFC3339), VisitDate: resp.VisitDate.Format(time.RFC3339),
TicketStatus: resp.TicketStatus, TicketStatus: resp.TicketStatus,
Source: resp.Source, Source: resp.Source,
} }
} }

View File

@ -1,6 +1,7 @@
package site package site
import ( import (
"fmt"
"furtuna-be/internal/common/errors" "furtuna-be/internal/common/errors"
"furtuna-be/internal/entity" "furtuna-be/internal/entity"
"furtuna-be/internal/handlers/request" "furtuna-be/internal/handlers/request"
@ -287,6 +288,7 @@ func (h *Handler) toSiteResponse(resp *entity.Site) response.Site {
CreatedAt: resp.CreatedAt.Format(time.RFC3339), CreatedAt: resp.CreatedAt.Format(time.RFC3339),
UpdatedAt: resp.UpdatedAt.Format(time.RFC3339), UpdatedAt: resp.UpdatedAt.Format(time.RFC3339),
Products: h.toProductResponseList(resp.Products), Products: h.toProductResponseList(resp.Products),
LatLong: fmt.Sprintf("%f,%f", resp.Lat, resp.Long),
} }
} }

View File

@ -47,6 +47,14 @@ type OrderParam struct {
Source string `form:"source" json:"source" example:"tes"` Source string `form:"source" json:"source" example:"tes"`
} }
type Checkin struct {
QRCode string `json:"qr_code" validate:"required"`
}
type CheckinExecute struct {
Token string `json:"token" validate:"required"`
}
func (o *OrderParam) ToOrderEntity(ctx mycontext.Context) entity.OrderSearch { func (o *OrderParam) ToOrderEntity(ctx mycontext.Context) entity.OrderSearch {
return entity.OrderSearch{ return entity.OrderSearch{
PartnerID: ctx.GetPartnerID(), PartnerID: ctx.GetPartnerID(),

View File

@ -3,6 +3,9 @@ package request
import ( import (
"furtuna-be/internal/common/mycontext" "furtuna-be/internal/common/mycontext"
"furtuna-be/internal/entity" "furtuna-be/internal/entity"
"log"
"strconv"
"strings"
) )
type Site struct { type Site struct {
@ -21,6 +24,9 @@ type Site struct {
IsSeasonTicket bool `json:"is_season_ticket"` IsSeasonTicket bool `json:"is_season_ticket"`
IsDiscountActive bool `json:"is_discount_active"` IsDiscountActive bool `json:"is_discount_active"`
Products []Product `json:"products"` Products []Product `json:"products"`
Region string `json:"region"`
Regency string `json:"regency"`
LatLong string `json:"lat_long"`
} }
func (r *Site) ToEntity(createdBy int64) *entity.Site { func (r *Site) ToEntity(createdBy int64) *entity.Site {
@ -39,6 +45,17 @@ func (r *Site) ToEntity(createdBy int64) *entity.Site {
CreatedBy: createdBy, CreatedBy: createdBy,
}) })
} }
latLong := strings.Split(r.LatLong, ".")
lat, err := strconv.ParseFloat(latLong[0], 64)
if err != nil {
log.Fatalf("Error converting latitude: %v", err)
}
long, err := strconv.ParseFloat(latLong[1], 64)
if err != nil {
log.Fatalf("Error converting longitude: %v", err)
}
return &entity.Site{ return &entity.Site{
ID: r.ID, ID: r.ID,
@ -56,6 +73,10 @@ func (r *Site) ToEntity(createdBy int64) *entity.Site {
IsSeasonTicket: r.IsSeasonTicket, IsSeasonTicket: r.IsSeasonTicket,
IsDiscountActive: r.IsDiscountActive, IsDiscountActive: r.IsDiscountActive,
Products: products, Products: products,
Region: r.Region,
Regency: r.Regency,
Lat: lat,
Long: long,
} }
} }

View File

@ -61,6 +61,8 @@ type SearchSiteByIDResponse struct {
TnC string `json:"tn_c"` TnC string `json:"tn_c"`
AdditionalInfo string `json:"additional_info"` AdditionalInfo string `json:"additional_info"`
Status string `json:"status"` Status string `json:"status"`
Region string `json:"region"`
Regency string `json:"regency"`
} }
type SearchProductSiteByIDResponse struct { type SearchProductSiteByIDResponse struct {

View File

@ -26,18 +26,18 @@ type OrderAmount struct {
} }
type HistoryOrder struct { type HistoryOrder struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Employee string `json:"employee"` Employee string `json:"employee"`
Site string `json:"site"` Site string `json:"site"`
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
BookingTime string `json:"booking_time"` BookingTime string `json:"booking_time"`
Tickets []string `json:"tickets"` Tickets []string `json:"tickets"`
PaymentType string `json:"payment_type"` PaymentType string `json:"payment_type"`
Status string `json:"status"` Status string `json:"status"`
Amount float64 `json:"amount"` Amount float64 `json:"amount"`
VisitDate string `json:"visit_date"` VisitDate string `json:"visit_date"`
TicketStatus string `json:"ticket_status"` TicketStatus string `json:"ticket_status"`
Source string `json:"source"` Source string `json:"source"`
} }
type OrderItem struct { type OrderItem struct {
@ -110,6 +110,24 @@ type ExecuteOrderResponse struct {
QRcode string `json:"qr_code"` QRcode string `json:"qr_code"`
} }
type ExecuteCheckinResponse struct {
ID int64 `json:"id"`
RefID string `json:"ref_id"`
PartnerID int64 `json:"partner_id"`
Status string `json:"status"`
Amount float64 `json:"amount"`
PaymentType string `json:"payment_type"`
CreatedAt time.Time `json:"created_at"`
OrderItems []CreateOrderItemResponse `json:"order_items"`
PaymentToken string `json:"payment_token"`
RedirectURL string `json:"redirect_url"`
QRcode string `json:"qr_code"`
}
type CheckingInquiryResponse struct {
Token string `json:"token"`
}
type CreateOrderItemResponse struct { type CreateOrderItemResponse struct {
ID int64 `json:"id"` ID int64 `json:"id"`
ItemID int64 `json:"item_id"` ItemID int64 `json:"item_id"`

View File

@ -1,23 +1,24 @@
package response package response
type Site struct { type Site struct {
ID *int64 `json:"id"` ID *int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
PartnerID int64 `json:"partner_id"` PartnerID int64 `json:"partner_id"`
Image string `json:"image"` Image string `json:"image"`
Address string `json:"address"` Address string `json:"address"`
LocationLink string `json:"location_link"` LocationLink string `json:"location_link"`
Description string `json:"description"` Description string `json:"description"`
Highlight string `json:"highlight"` Highlight string `json:"highlight"`
ContactPerson string `json:"contact_person"` ContactPerson string `json:"contact_person"`
TnC string `json:"tnc"` TnC string `json:"tnc"`
AdditionalInfo string `json:"additional_info"` AdditionalInfo string `json:"additional_info"`
Status string `json:"status"` Status string `json:"status"`
IsSeasonTicket bool `json:"is_season_ticket"` IsSeasonTicket bool `json:"is_season_ticket"`
IsDiscountActive bool `json:"is_discount_active"` IsDiscountActive bool `json:"is_discount_active"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
Products []Product `json:"products"` Products []Product `json:"products"`
LatLong string `json:"lat_long"`
} }
type SiteName struct { type SiteName struct {

View File

@ -64,6 +64,26 @@ func (r *OrderRepository) FindByID(ctx context.Context, id int64) (*entity.Order
return &order, nil return &order, nil
} }
func (r *OrderRepository) FindByQRCode(ctx context.Context, refID string) (*entity.Order, error) {
var order entity.Order
err := r.db.WithContext(ctx).
Preload("OrderItems", func(db *gorm.DB) *gorm.DB {
return db.Preload("Product")
}).
Preload("User").
Preload("Payment").
Where("ref_id = ?", refID).
First(&order).Error
if err != nil {
logger.ContextLogger(ctx).Error("error when finding order by refID", zap.Error(err))
return nil, err
}
return &order, nil
}
func (r *OrderRepository) SetOrderStatus(ctx context.Context, db *gorm.DB, orderID int64, status string) error { func (r *OrderRepository) SetOrderStatus(ctx context.Context, db *gorm.DB, orderID int64, status string) error {
var order entity.Order var order entity.Order
if err := db.WithContext(ctx).Preload("OrderItems").First(&order, orderID).Error; err != nil { if err := db.WithContext(ctx).Preload("OrderItems").First(&order, orderID).Error; err != nil {

View File

@ -141,6 +141,7 @@ type Product interface {
type Order interface { type Order interface {
Create(ctx context.Context, order *entity.Order) (*entity.Order, error) Create(ctx context.Context, order *entity.Order) (*entity.Order, error)
FindByID(ctx context.Context, id int64) (*entity.Order, error) FindByID(ctx context.Context, id int64) (*entity.Order, error)
FindByQRCode(ctx context.Context, refID string) (*entity.Order, error)
Update(ctx context.Context, order *entity.Order) (*entity.Order, error) Update(ctx context.Context, order *entity.Order) (*entity.Order, error)
SetOrderStatus(ctx context.Context, db *gorm.DB, orderID int64, status string) error SetOrderStatus(ctx context.Context, db *gorm.DB, orderID int64, status string) error
GetAllHystoryOrders(ctx context.Context, req entity.OrderSearch) (entity.HistoryOrderList, int, error) GetAllHystoryOrders(ctx context.Context, req entity.OrderSearch) (entity.HistoryOrderList, int, error)

View File

@ -93,6 +93,7 @@ func (r *SiteRepository) GetAll(ctx context.Context, req entity.SiteSearch) (ent
query := r.db query := r.db
query = query.Where("deleted_at IS NULL") query = query.Where("deleted_at IS NULL")
query = query.Where("status is ?", "Active")
if req.Search != "" { if req.Search != "" {
query = query.Where("name ILIKE ?", "%"+req.Search+"%") query = query.Where("name ILIKE ?", "%"+req.Search+"%")

View File

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
errors2 "furtuna-be/internal/common/errors"
"furtuna-be/internal/common/logger" "furtuna-be/internal/common/logger"
"furtuna-be/internal/common/mycontext" "furtuna-be/internal/common/mycontext"
order2 "furtuna-be/internal/constants/order" order2 "furtuna-be/internal/constants/order"
@ -121,6 +122,77 @@ func (s *OrderService) CreateOrder(ctx mycontext.Context, req *entity.OrderReque
}, nil }, nil
} }
func (s *OrderService) CheckInInquiry(ctx mycontext.Context, qrCode string, partnerID *int64) (*entity.CheckinResponse, error) {
order, err := s.repo.FindByQRCode(ctx, qrCode)
if err != nil {
logger.ContextLogger(ctx).Error("error when getting order by QR code", zap.Error(err))
return nil, err
}
if order.PartnerID != *partnerID {
return nil, errors2.ErrorBadRequest
}
if order.TicketStatus == "USED" {
return nil, errors2.ErrorTicketInvalidOrAlreadyUsed
}
today := time.Now().Format("2006-01-02")
visitDate := order.VisitDate.Format("2006-01-02")
if visitDate != today {
return nil, errors2.ErrorTicketInvalidOrAlreadyUsed
}
token, err := s.crypt.GenerateJWTOrder(order)
if err != nil {
logger.ContextLogger(ctx).Error("error when generate checkin token", zap.Error(err))
return nil, err
}
orderResponse := &entity.CheckinResponse{
Token: token,
}
return orderResponse, nil
}
func (s *OrderService) CheckInExecute(ctx mycontext.Context,
token string, partnerID *int64) (*entity.CheckinExecute, error) {
pID, orderID, err := s.crypt.ValidateJWTOrder(token)
if err != nil {
logger.ContextLogger(ctx).Error("error when validating JWT order", zap.Error(err))
return nil, err
}
if pID != *partnerID {
return nil, errors2.ErrorBadRequest
}
order, err := s.repo.FindByID(ctx, orderID)
if err != nil {
logger.ContextLogger(ctx).Error("error when getting order by ID", zap.Error(err))
return nil, err
}
resp := &entity.CheckinExecute{
Order: order,
}
if order.Status != "UNUSED" {
return resp, nil
}
order.Status = "USED"
order, err = s.repo.Update(ctx, order)
if err != nil {
logger.ContextLogger(ctx).Error("error when updating order status", zap.Error(err))
return nil, err
}
return resp, nil
}
func (s *OrderService) Execute(ctx context.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error) { func (s *OrderService) Execute(ctx context.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error) {
partnerID, orderID, err := s.crypt.ValidateJWTOrder(req.Token) partnerID, orderID, err := s.crypt.ValidateJWTOrder(req.Token)
if err != nil { if err != nil {
@ -376,7 +448,7 @@ func (s *OrderService) GetAllHistoryOrders(ctx mycontext.Context, req entity.Ord
return data, total, nil return data, total, nil
} }
func (s OrderService) CountSoldOfTicket(ctx mycontext.Context, req entity.OrderSearch) (*entity.TicketSold, error) { func (s *OrderService) CountSoldOfTicket(ctx mycontext.Context, req entity.OrderSearch) (*entity.TicketSold, error) {
ticket, err := s.repo.CountSoldOfTicket(ctx, req) ticket, err := s.repo.CountSoldOfTicket(ctx, req)
if err != nil { if err != nil {
@ -389,7 +461,7 @@ func (s OrderService) CountSoldOfTicket(ctx mycontext.Context, req entity.OrderS
return data, nil return data, nil
} }
func (s OrderService) GetDailySales(ctx mycontext.Context, req entity.OrderSearch) ([]entity.ProductDailySales, error) { func (s *OrderService) GetDailySales(ctx mycontext.Context, req entity.OrderSearch) ([]entity.ProductDailySales, error) {
dailySales, err := s.repo.GetDailySalesMetrics(ctx, req) dailySales, err := s.repo.GetDailySalesMetrics(ctx, req)
if err != nil { if err != nil {
@ -400,7 +472,7 @@ func (s OrderService) GetDailySales(ctx mycontext.Context, req entity.OrderSearc
return dailySales, nil return dailySales, nil
} }
func (s OrderService) GetPaymentDistribution(ctx mycontext.Context, req entity.OrderSearch) ([]entity.PaymentTypeDistribution, error) { func (s *OrderService) GetPaymentDistribution(ctx mycontext.Context, req entity.OrderSearch) ([]entity.PaymentTypeDistribution, error) {
paymentDistribution, err := s.repo.GetPaymentTypeDistribution(ctx, req) paymentDistribution, err := s.repo.GetPaymentTypeDistribution(ctx, req)
if err != nil { if err != nil {
@ -411,7 +483,7 @@ func (s OrderService) GetPaymentDistribution(ctx mycontext.Context, req entity.O
return paymentDistribution, nil return paymentDistribution, nil
} }
func (s OrderService) SumAmount(ctx mycontext.Context, req entity.OrderSearch) (*entity.Order, error) { func (s *OrderService) SumAmount(ctx mycontext.Context, req entity.OrderSearch) (*entity.Order, error) {
amount, err := s.repo.SumAmount(ctx, req) amount, err := s.repo.SumAmount(ctx, req)
if err != nil { if err != nil {

View File

@ -112,6 +112,9 @@ type Product interface {
type Order interface { type Order interface {
CreateOrder(ctx mycontext.Context, req *entity.OrderRequest) (*entity.OrderResponse, error) CreateOrder(ctx mycontext.Context, req *entity.OrderRequest) (*entity.OrderResponse, error)
CheckInInquiry(ctx mycontext.Context, qrCode string, partnerID *int64) (*entity.CheckinResponse, error)
CheckInExecute(ctx mycontext.Context,
token string, partnerID *int64) (*entity.CheckinExecute, error)
Execute(ctx context.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error) Execute(ctx context.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error)
ProcessCallback(ctx context.Context, req *entity.CallbackRequest) error ProcessCallback(ctx context.Context, req *entity.CallbackRequest) error
GetAllHistoryOrders(ctx mycontext.Context, req entity.OrderSearch) ([]*entity.HistoryOrder, int, error) GetAllHistoryOrders(ctx mycontext.Context, req entity.OrderSearch) ([]*entity.HistoryOrder, int, error)