This commit is contained in:
aditya.siregar
2025-04-10 11:21:08 +07:00
parent c642c5c61b
commit 09c9a4d59d
30 changed files with 1797 additions and 758 deletions
@@ -3,28 +3,98 @@ package inprogress_order
import (
"enaklo-pos-be/internal/common/logger"
"enaklo-pos-be/internal/common/mycontext"
order2 "enaklo-pos-be/internal/constants/order"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository"
"enaklo-pos-be/internal/services/v2/order"
"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)
Save(ctx mycontext.Context, order *entity.OrderRequest) (*entity.Order, error)
GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.Order, error)
}
type OrderRepository interface {
CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error)
}
type OrderCalculator interface {
CalculateOrderTotals(
ctx mycontext.Context,
items []entity.OrderItemRequest,
productDetails *entity.ProductDetails,
source string,
) (*entity.OrderCalculation, error)
ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error)
}
type inProgressOrderSvc struct {
repo repository.InProgressOrderRepository
repo OrderRepository
orderCalculator OrderCalculator
product order.ProductService
}
func NewInProgressOrderService(repo repository.InProgressOrderRepository) InProgressOrderService {
func NewInProgressOrderService(repo OrderRepository, calculator OrderCalculator, product order.ProductService) InProgressOrderService {
return &inProgressOrderSvc{
repo: repo,
repo: repo,
orderCalculator: calculator,
product: product,
}
}
func (s *inProgressOrderSvc) Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) {
func (s *inProgressOrderSvc) Save(ctx mycontext.Context, req *entity.OrderRequest) (*entity.Order, error) {
productIDs, filteredItems, err := s.orderCalculator.ValidateOrderItems(ctx, req.OrderItems)
if err != nil {
return nil, err
}
req.OrderItems = filteredItems
productDetails, err := s.product.GetProductDetails(ctx, productIDs, req.PartnerID)
if err != nil {
logger.ContextLogger(ctx).Error("failed to get product details", zap.Error(err))
return nil, err
}
orderCalculation, err := s.orderCalculator.CalculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
if err != nil {
return nil, err
}
orderItems := make([]entity.OrderItem, len(req.OrderItems))
for i, item := range req.OrderItems {
product, exists := productDetails.Products[item.ProductID]
productName := ""
if exists {
productName = product.Name
}
orderItems[i] = entity.OrderItem{
ItemID: item.ProductID,
ItemName: productName,
Quantity: item.Quantity,
Price: product.Price,
ItemType: product.Type,
}
}
order := &entity.Order{
ID: req.ID,
PartnerID: req.PartnerID,
CustomerID: req.CustomerID,
CustomerName: req.CustomerName,
CreatedBy: req.CreatedBy,
OrderItems: orderItems,
TableNumber: req.TableNumber,
OrderType: req.OrderType,
Total: orderCalculation.Total,
Tax: orderCalculation.Tax,
Amount: orderCalculation.Subtotal,
Status: order2.Pending.String(),
Source: req.Source,
}
createdOrder, err := s.repo.CreateOrUpdate(ctx, order)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create in-progress order",
@@ -36,8 +106,8 @@ func (s *inProgressOrderSvc) Save(ctx mycontext.Context, order *entity.InProgres
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)
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.Order, error) {
orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset, order2.Pending.String())
if err != nil {
logger.ContextLogger(ctx).Error("failed to get in-progress orders by partner ID",
zap.Error(err),
@@ -7,11 +7,12 @@ import (
"enaklo-pos-be/internal/constants"
"enaklo-pos-be/internal/entity"
"go.uber.org/zap"
"math"
)
func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) {
productIDs, filteredItems, err := s.validateOrderItems(ctx, req.OrderItems)
productIDs, filteredItems, err := s.ValidateOrderItems(ctx, req.OrderItems)
if err != nil {
return nil, err
}
@@ -23,7 +24,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
return nil, err
}
orderCalculation, err := s.calculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
orderCalculation, err := s.CalculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
if err != nil {
return nil, err
}
@@ -43,7 +44,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
req.PartnerID,
customerID,
orderCalculation.Subtotal,
orderCalculation.Fee,
orderCalculation.Tax,
orderCalculation.Total,
req.PaymentMethod,
req.Source,
@@ -79,7 +80,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
}, nil
}
func (s *orderSvc) validateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error) {
func (s *orderSvc) ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error) {
var productIDs []int64
var filteredItems []entity.OrderItemRequest
@@ -98,7 +99,7 @@ func (s *orderSvc) validateOrderItems(ctx mycontext.Context, items []entity.Orde
return productIDs, filteredItems, nil
}
func (s *orderSvc) calculateOrderTotals(
func (s *orderSvc) CalculateOrderTotals(
ctx mycontext.Context,
items []entity.OrderItemRequest,
productDetails *entity.ProductDetails,
@@ -114,12 +115,23 @@ func (s *orderSvc) calculateOrderTotals(
subtotal += product.Price * float64(item.Quantity)
}
fee := s.cfg.GetOrderFee(source)
partnerID := ctx.GetPartnerID()
setting, err := s.partnerSetting.GetSettings(ctx, *partnerID)
if err != nil {
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "failed to get partner settings")
}
tax := 0.0
if setting.TaxEnabled {
tax = (setting.TaxPercentage / 100) * subtotal
tax = math.Round(tax/100) * 100
}
return &entity.OrderCalculation{
Subtotal: subtotal,
Fee: fee,
Total: subtotal + fee,
Tax: tax,
Total: subtotal + tax,
}, nil
}
@@ -145,3 +157,79 @@ func (s *orderSvc) validateInquiry(ctx mycontext.Context, token string) (*entity
return inquiry, nil
}
func (s *orderSvc) GetOrderPaymentAnalysis(
ctx mycontext.Context,
partnerID int64,
req entity.SearchRequest,
) (*entity.OrderPaymentAnalysis, error) {
paymentBreakdown, err := s.repo.GetOrderPaymentMethodBreakdown(ctx, partnerID, req)
if err != nil {
return nil, err
}
var totalAmount float64
var totalTransactions int64
for _, breakdown := range paymentBreakdown {
totalAmount += breakdown.TotalAmount
totalTransactions += breakdown.TotalTransactions
}
return &entity.OrderPaymentAnalysis{
TotalAmount: totalAmount,
TotalTransactions: totalTransactions,
PaymentMethodBreakdown: paymentBreakdown,
}, nil
}
func (s *orderSvc) GetRevenueOverview(
ctx mycontext.Context,
partnerID int64,
year int,
granularity string,
status string,
) ([]entity.RevenueOverviewItem, error) {
req := entity.RevenueOverviewRequest{
PartnerID: partnerID,
Year: year,
Granularity: granularity,
Status: status,
}
return s.repo.GetRevenueOverview(ctx, req)
}
func (s *orderSvc) GetSalesByCategory(
ctx mycontext.Context,
partnerID int64,
period string,
status string,
) ([]entity.SalesByCategoryItem, error) {
req := entity.SalesByCategoryRequest{
PartnerID: partnerID,
Period: period,
Status: status,
}
return s.repo.GetSalesByCategory(ctx, req)
}
func (s *orderSvc) GetPopularProducts(
ctx mycontext.Context,
partnerID int64,
period string,
status string,
limit int,
sortBy string,
) ([]entity.PopularProductItem, error) {
req := entity.PopularProductsRequest{
PartnerID: partnerID,
Period: period,
Status: status,
Limit: limit,
SortBy: sortBy,
}
return s.repo.GetPopularProducts(ctx, req)
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
)
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod, paymentProvider, inprogressOrderID string) (*entity.OrderResponse, error) {
token string, paymentMethod, paymentProvider string, inprogressOrderID int64) (*entity.OrderResponse, error) {
inquiry, err := s.validateInquiry(ctx, token)
if err != nil {
return nil, err
+79 -15
View File
@@ -12,6 +12,24 @@ type Repository interface {
CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error)
FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error)
UpdateInquiryStatus(ctx mycontext.Context, id string, status string) error
GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
GetOrderPaymentMethodBreakdown(
ctx mycontext.Context,
partnerID int64,
req entity.SearchRequest,
) ([]entity.PaymentMethodBreakdown, error)
GetRevenueOverview(
ctx mycontext.Context,
req entity.RevenueOverviewRequest,
) ([]entity.RevenueOverviewItem, error)
GetSalesByCategory(
ctx mycontext.Context,
req entity.SalesByCategoryRequest,
) ([]entity.SalesByCategoryItem, error)
GetPopularProducts(
ctx mycontext.Context,
req entity.PopularProductsRequest,
) ([]entity.PopularProductItem, error)
}
type ProductService interface {
@@ -42,21 +60,65 @@ type Service interface {
CreateOrderInquiry(ctx mycontext.Context,
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod, paymentProvider, inProgressOrderID string) (*entity.OrderResponse, error)
token string, paymentMethod, paymentProvider string, inProgressOrderID int64) (*entity.OrderResponse, error)
GetOrderHistory(ctx mycontext.Context, partnerID int64, request entity.SearchRequest) ([]*entity.Order, int64, error)
CalculateOrderTotals(
ctx mycontext.Context,
items []entity.OrderItemRequest,
productDetails *entity.ProductDetails,
source string,
) (*entity.OrderCalculation, error)
ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error)
GetOrderPaymentAnalysis(
ctx mycontext.Context,
partnerID int64,
req entity.SearchRequest,
) (*entity.OrderPaymentAnalysis, error)
GetRevenueOverview(
ctx mycontext.Context,
partnerID int64,
year int,
granularity string,
status string,
) ([]entity.RevenueOverviewItem, error)
GetSalesByCategory(
ctx mycontext.Context,
partnerID int64,
period string,
status string,
) ([]entity.SalesByCategoryItem, error)
GetPopularProducts(
ctx mycontext.Context,
partnerID int64,
period string,
status string,
limit int,
sortBy string,
) ([]entity.PopularProductItem, error)
}
type Config interface {
GetOrderFee(source string) float64
}
type PartnerSettings interface {
GetSettings(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error)
}
type InProgressOrderRepository interface {
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error)
}
type orderSvc struct {
repo Repository
product ProductService
customer CustomerService
transaction TransactionService
crypt CryptService
cfg Config
notification NotificationService
repo Repository
product ProductService
customer CustomerService
transaction TransactionService
crypt CryptService
cfg Config
notification NotificationService
partnerSetting PartnerSettings
inprogressOrder InProgressOrderRepository
}
func New(
@@ -67,14 +129,16 @@ func New(
crypt CryptService,
cfg Config,
notification NotificationService,
partnerSetting PartnerSettings,
) Service {
return &orderSvc{
repo: repo,
product: product,
customer: customer,
transaction: transaction,
crypt: crypt,
cfg: cfg,
notification: notification,
repo: repo,
product: product,
customer: customer,
transaction: transaction,
crypt: crypt,
cfg: cfg,
notification: notification,
partnerSetting: partnerSetting,
}
}
@@ -0,0 +1,10 @@
package order
import (
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
)
func (s *orderSvc) GetOrderHistory(ctx mycontext.Context, partnerID int64, request entity.SearchRequest) ([]*entity.Order, int64, error) {
return s.repo.GetOrderHistoryByPartnerID(ctx, partnerID, request)
}
@@ -0,0 +1,149 @@
package partner_settings
import (
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository"
"encoding/json"
"github.com/pkg/errors"
)
type PartnerSettingsService interface {
GetSettings(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error)
UpdateSettings(ctx mycontext.Context, settings *entity.PartnerSettings) error
GetPaymentMethods(ctx mycontext.Context, partnerID int64) ([]entity.PartnerPaymentMethod, error)
AddPaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error
UpdatePaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error
DeletePaymentMethod(ctx mycontext.Context, id int64, partnerID int64) error
ReorderPaymentMethods(ctx mycontext.Context, partnerID int64, methodIDs []int64) error
GetBusinessHours(ctx mycontext.Context, partnerID int64) (*entity.BusinessHoursSetting, error)
UpdateBusinessHours(ctx mycontext.Context, partnerID int64, hours *entity.BusinessHoursSetting) error
}
type partnerSettingsService struct {
settingsRepo repository.PartnerSettingsRepository
}
func NewPartnerSettingsService(settingsRepo repository.PartnerSettingsRepository) PartnerSettingsService {
return &partnerSettingsService{
settingsRepo: settingsRepo,
}
}
func (s *partnerSettingsService) GetSettings(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error) {
return s.settingsRepo.GetByPartnerID(ctx, partnerID)
}
func (s *partnerSettingsService) UpdateSettings(ctx mycontext.Context, settings *entity.PartnerSettings) error {
if settings == nil {
return errors.New("settings cannot be nil")
}
// Validate tax percentage
if settings.TaxEnabled && (settings.TaxPercentage < 0 || settings.TaxPercentage > 100) {
return errors.New("tax percentage must be between 0 and 100")
}
return s.settingsRepo.Upsert(ctx, settings)
}
func (s *partnerSettingsService) GetPaymentMethods(ctx mycontext.Context, partnerID int64) ([]entity.PartnerPaymentMethod, error) {
return s.settingsRepo.GetPaymentMethods(ctx, partnerID)
}
func (s *partnerSettingsService) AddPaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error {
if method == nil {
return errors.New("payment method cannot be nil")
}
method.ID = 0
return s.settingsRepo.UpsertPaymentMethod(ctx, method)
}
func (s *partnerSettingsService) UpdatePaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error {
if method == nil {
return errors.New("payment method cannot be nil")
}
if method.ID <= 0 {
return errors.New("invalid payment method ID")
}
return s.settingsRepo.UpsertPaymentMethod(ctx, method)
}
func (s *partnerSettingsService) DeletePaymentMethod(ctx mycontext.Context, id int64, partnerID int64) error {
if id <= 0 {
return errors.New("invalid payment method ID")
}
return s.settingsRepo.DeletePaymentMethod(ctx, id, partnerID)
}
func (s *partnerSettingsService) ReorderPaymentMethods(ctx mycontext.Context, partnerID int64, methodIDs []int64) error {
if len(methodIDs) == 0 {
return errors.New("method IDs cannot be empty")
}
return s.settingsRepo.UpdatePaymentMethodOrder(ctx, partnerID, methodIDs)
}
// GetBusinessHours retrieves parsed business hours for a partner
func (s *partnerSettingsService) GetBusinessHours(ctx mycontext.Context, partnerID int64) (*entity.BusinessHoursSetting, error) {
settings, err := s.settingsRepo.GetByPartnerID(ctx, partnerID)
if err != nil {
return nil, err
}
// Create default hours if not set
if settings.BusinessHours == "" {
defaultHours := createDefaultBusinessHours()
return defaultHours, nil
}
var hours entity.BusinessHoursSetting
if err := json.Unmarshal([]byte(settings.BusinessHours), &hours); err != nil {
return nil, errors.Wrap(err, "failed to parse business hours")
}
return &hours, nil
}
func (s *partnerSettingsService) UpdateBusinessHours(ctx mycontext.Context, partnerID int64, hours *entity.BusinessHoursSetting) error {
if hours == nil {
return errors.New("business hours cannot be nil")
}
settings, err := s.settingsRepo.GetByPartnerID(ctx, partnerID)
if err != nil {
return err
}
// Serialize hours to JSON
hoursJSON, err := json.Marshal(hours)
if err != nil {
return errors.Wrap(err, "failed to serialize business hours")
}
settings.BusinessHours = string(hoursJSON)
return s.settingsRepo.Upsert(ctx, settings)
}
func createDefaultBusinessHours() *entity.BusinessHoursSetting {
defaultDay := entity.DayHours{
Open: "08:00",
Close: "22:00",
}
return &entity.BusinessHoursSetting{
Monday: defaultDay,
Tuesday: defaultDay,
Wednesday: defaultDay,
Thursday: defaultDay,
Friday: defaultDay,
Saturday: defaultDay,
Sunday: defaultDay,
}
}