Add void print

This commit is contained in:
aditya.siregar
2025-06-24 02:47:44 +07:00
parent 53014d90ab
commit 1201b2e45b
18 changed files with 2033 additions and 12 deletions
+431 -1
View File
@@ -30,12 +30,15 @@ func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
route.POST("/inquiry", jwt, h.Inquiry)
route.POST("/execute", jwt, h.Execute)
route.POST("/refund", jwt, h.Refund)
route.POST("/partial-refund", jwt, h.PartialRefund)
route.POST("/void", jwt, h.VoidOrder)
route.POST("/split-bill", jwt, h.SplitBill)
route.GET("/history", jwt, h.GetOrderHistory)
route.GET("/refund-history", jwt, h.GetRefundHistory)
route.GET("/payment-analysis", jwt, h.GetPaymentMethodAnalysis)
route.GET("/revenue-overview", jwt, h.GetRevenueOverview)
route.GET("/sales-by-category", jwt, h.GetSalesByCategory)
route.GET("/popular-products", jwt, h.GetPopularProducts)
}
type InquiryRequest struct {
@@ -77,6 +80,123 @@ type RefundRequest struct {
Reason string `json:"reason" validate:"required"`
}
type PartialRefundRequest struct {
OrderID int64 `json:"order_id" validate:"required"`
Reason string `json:"reason" validate:"required"`
Items []PartialRefundItemRequest `json:"items" validate:"required,min=1,dive"`
}
type PartialRefundItemRequest struct {
OrderItemID int64 `json:"order_item_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
}
type VoidOrderRequest struct {
OrderID int64 `json:"order_id" validate:"required"`
Reason string `json:"reason" validate:"required"`
Type string `json:"type" validate:"required,oneof=ALL ITEM"`
Items []VoidItemRequest `json:"items,omitempty" validate:"required_if=Type ITEM,dive"`
}
type VoidItemRequest struct {
OrderItemID int64 `json:"order_item_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
}
type SplitBillRequest struct {
OrderID int64 `json:"order_id" validate:"required"`
Type string `json:"type" validate:"required,oneof=ITEM AMOUNT"`
PaymentMethod string `json:"payment_method" validate:"required"`
PaymentProvider string `json:"payment_provider"`
Items []SplitBillItemRequest `json:"items,omitempty" validate:"required_if=Type ITEM,dive"`
Amount float64 `json:"amount,omitempty" validate:"required_if=Type AMOUNT,min=0"`
}
type SplitBillItemRequest struct {
OrderItemID int64 `json:"order_item_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
}
type RefundResponse struct {
OrderID int64 `json:"order_id"`
Status string `json:"status"`
RefundAmount float64 `json:"refund_amount"`
Reason string `json:"reason"`
RefundedAt string `json:"refunded_at"`
CustomerName string `json:"customer_name"`
PaymentType string `json:"payment_type"`
}
type RefundHistoryResponse struct {
OrderID int64 `json:"order_id"`
CustomerName string `json:"customer_name"`
CustomerID *int64 `json:"customer_id"`
IsMember bool `json:"is_member"`
Status string `json:"status"`
Amount float64 `json:"amount"`
Total float64 `json:"total"`
PaymentType string `json:"payment_type"`
TableNumber string `json:"table_number"`
OrderType string `json:"order_type"`
CreatedAt string `json:"created_at"`
RefundedAt string `json:"refunded_at"`
Tax float64 `json:"tax"`
}
type PartialRefundResponse struct {
OrderID int64 `json:"order_id"`
Status string `json:"status"`
RefundedAmount float64 `json:"refunded_amount"`
RemainingAmount float64 `json:"remaining_amount"`
Reason string `json:"reason"`
RefundedAt string `json:"refunded_at"`
CustomerName string `json:"customer_name"`
PaymentType string `json:"payment_type"`
RefundedItems []RefundedItemResponse `json:"refunded_items"`
}
type RefundedItemResponse struct {
OrderItemID int64 `json:"order_item_id"`
ItemName string `json:"item_name"`
Quantity int `json:"quantity"`
UnitPrice float64 `json:"unit_price"`
TotalPrice float64 `json:"total_price"`
}
type VoidOrderResponse struct {
OrderID int64 `json:"order_id"`
Status string `json:"status"`
Reason string `json:"reason"`
VoidedAt string `json:"voided_at"`
CustomerName string `json:"customer_name"`
VoidedItems []VoidedItemResponse `json:"voided_items,omitempty"`
}
type VoidedItemResponse struct {
OrderItemID int64 `json:"order_item_id"`
ItemName string `json:"item_name"`
Quantity int `json:"quantity"`
UnitPrice float64 `json:"unit_price"`
TotalPrice float64 `json:"total_price"`
}
type SplitBillResponse struct {
OriginalOrderID int64 `json:"original_order_id"`
SplitOrders []SplitOrderResponse `json:"split_orders"`
SplitAt string `json:"split_at"`
}
type SplitOrderResponse struct {
OrderID int64 `json:"order_id"`
CustomerName string `json:"customer_name"`
CustomerID *int64 `json:"customer_id"`
Amount float64 `json:"amount"`
Total float64 `json:"total"`
Tax float64 `json:"tax"`
Status string `json:"status"`
Items []response.OrderItemResponse `json:"items"`
}
func (h *Handler) Inquiry(c *gin.Context) {
ctx := request.GetMyContext(c)
userID := ctx.RequestedBy()
@@ -181,9 +301,30 @@ func (h *Handler) Refund(c *gin.Context) {
return
}
order, err := h.service.GetOrderByID(ctx, req.OrderID)
if err != nil {
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Message: "Refund processed successfully",
})
return
}
refundResponse := RefundResponse{
OrderID: order.ID,
Status: order.Status,
RefundAmount: order.Total,
Reason: req.Reason,
RefundedAt: order.UpdatedAt.Format("2006-01-02T15:04:05Z"),
CustomerName: order.CustomerName,
PaymentType: h.formatPayment(order.PaymentType, order.PaymentProvider),
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: refundResponse,
})
}
@@ -488,3 +629,292 @@ func (h *Handler) GetPopularProducts(c *gin.Context) {
Data: popularProducts,
})
}
func (h *Handler) GetRefundHistory(c *gin.Context) {
ctx := request.GetMyContext(c)
partnerID := ctx.GetPartnerID()
limitStr := c.Query("limit")
offsetStr := c.Query("offset")
startDateStr := c.Query("start_date")
endDateStr := c.Query("end_date")
searchReq := entity.SearchRequest{}
limit := 20
if limitStr != "" {
parsedLimit, err := strconv.Atoi(limitStr)
if err == nil && parsedLimit > 0 {
limit = parsedLimit
}
}
if limit > 100 {
limit = 100
}
searchReq.Limit = limit
offset := 0
if offsetStr != "" {
parsedOffset, err := strconv.Atoi(offsetStr)
if err == nil && parsedOffset >= 0 {
offset = parsedOffset
}
}
searchReq.Offset = offset
// Set status to REFUNDED to get only refunded orders
searchReq.Status = "REFUNDED"
if startDateStr != "" {
startDate, err := time.Parse(time.RFC3339, startDateStr)
if err == nil {
searchReq.Start = startDate
}
}
if endDateStr != "" {
endDate, err := time.Parse(time.RFC3339, endDateStr)
if err == nil {
searchReq.End = endDate
}
}
orders, total, err := h.service.GetOrderHistory(ctx, *partnerID, searchReq)
if err != nil {
response.ErrorWrapper(c, err)
return
}
responseData := []RefundHistoryResponse{}
for _, order := range orders {
responseData = append(responseData, RefundHistoryResponse{
OrderID: order.ID,
CustomerName: order.CustomerName,
CustomerID: order.CustomerID,
IsMember: order.IsMemberOrder(),
Status: order.Status,
Amount: order.Amount,
Total: order.Total,
PaymentType: h.formatPayment(order.PaymentType, order.PaymentProvider),
TableNumber: order.TableNumber,
OrderType: order.OrderType,
CreatedAt: order.CreatedAt.Format("2006-01-02T15:04:05Z"),
RefundedAt: order.UpdatedAt.Format("2006-01-02T15:04:05Z"),
Tax: order.Tax,
})
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: responseData,
PagingMeta: &response.PagingMeta{
Page: offset + 1,
Total: int64(total),
Limit: limit,
},
})
}
func (h *Handler) PartialRefund(c *gin.Context) {
ctx := request.GetMyContext(c)
var req PartialRefundRequest
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
}
items := make([]entity.PartialRefundItem, len(req.Items))
for i, item := range req.Items {
items[i] = entity.PartialRefundItem{
OrderItemID: item.OrderItemID,
Quantity: item.Quantity,
}
}
err := h.service.PartialRefundRequest(ctx, *ctx.GetPartnerID(), req.OrderID, req.Reason, items)
if err != nil {
response.ErrorWrapper(c, err)
return
}
// Get updated order to return details
order, err := h.service.GetOrderByID(ctx, req.OrderID)
if err != nil {
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Message: "Partial refund processed successfully",
})
return
}
// Calculate refunded amount
refundedAmount := 0.0
var refundedItems []RefundedItemResponse
for _, reqItem := range req.Items {
for _, orderItem := range order.OrderItems {
if orderItem.ID == reqItem.OrderItemID {
itemTotal := orderItem.Price * float64(reqItem.Quantity)
refundedAmount += itemTotal
refundedItems = append(refundedItems, RefundedItemResponse{
OrderItemID: orderItem.ID,
ItemName: orderItem.ItemName,
Quantity: reqItem.Quantity,
UnitPrice: orderItem.Price,
TotalPrice: itemTotal,
})
break
}
}
}
partialRefundResponse := PartialRefundResponse{
OrderID: order.ID,
Status: order.Status,
RefundedAmount: refundedAmount,
RemainingAmount: order.Total,
Reason: req.Reason,
RefundedAt: order.UpdatedAt.Format("2006-01-02T15:04:05Z"),
CustomerName: order.CustomerName,
PaymentType: h.formatPayment(order.PaymentType, order.PaymentProvider),
RefundedItems: refundedItems,
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: partialRefundResponse,
})
}
func (h *Handler) VoidOrder(c *gin.Context) {
ctx := request.GetMyContext(c)
var req VoidOrderRequest
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
}
// Convert request items to entity items
var items []entity.VoidItem
if req.Type == "ITEM" {
items = make([]entity.VoidItem, len(req.Items))
for i, item := range req.Items {
items[i] = entity.VoidItem{
OrderItemID: item.OrderItemID,
Quantity: item.Quantity,
}
}
}
err := h.service.VoidOrderRequest(ctx, *ctx.GetPartnerID(), req.OrderID, req.Reason, req.Type, items)
if err != nil {
response.ErrorWrapper(c, err)
return
}
// Get updated order to return details
order, err := h.service.GetOrderByID(ctx, req.OrderID)
if err != nil {
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Message: "Order voided successfully",
})
return
}
var voidedItems []VoidedItemResponse
if req.Type == "ITEM" {
for _, reqItem := range req.Items {
for _, orderItem := range order.OrderItems {
if orderItem.ID == reqItem.OrderItemID {
itemTotal := orderItem.Price * float64(reqItem.Quantity)
voidedItems = append(voidedItems, VoidedItemResponse{
OrderItemID: orderItem.ID,
ItemName: orderItem.ItemName,
Quantity: reqItem.Quantity,
UnitPrice: orderItem.Price,
TotalPrice: itemTotal,
})
break
}
}
}
}
voidOrderResponse := VoidOrderResponse{
OrderID: order.ID,
Status: order.Status,
Reason: req.Reason,
VoidedAt: order.UpdatedAt.Format("2006-01-02T15:04:05Z"),
CustomerName: order.CustomerName,
VoidedItems: voidedItems,
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: voidOrderResponse,
})
}
func (h *Handler) SplitBill(c *gin.Context) {
ctx := request.GetMyContext(c)
var req SplitBillRequest
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
}
var items []entity.SplitBillItem
if req.Type == "ITEM" {
items = make([]entity.SplitBillItem, len(req.Items))
for i, item := range req.Items {
items[i] = entity.SplitBillItem{
OrderItemID: item.OrderItemID,
Quantity: item.Quantity,
}
}
}
splitOrder, err := h.service.SplitBillRequest(ctx, *ctx.GetPartnerID(), req.OrderID, req.Type, req.PaymentMethod, req.PaymentProvider, items, req.Amount)
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: response.MapToOrderResponse(&entity.OrderResponse{Order: splitOrder}),
})
}
@@ -6,6 +6,7 @@ import (
"enaklo-pos-be/internal/handlers/request"
"enaklo-pos-be/internal/handlers/response"
"enaklo-pos-be/internal/services"
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"net/http"
@@ -50,6 +51,7 @@ func (h *Handler) Create(c *gin.Context) {
var req request.Product
if err := c.ShouldBindJSON(&req); err != nil {
fmt.Println(err)
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}