859 lines
24 KiB
Go
859 lines
24 KiB
Go
package repository
|
|
|
|
import (
|
|
"enaklo-pos-be/internal/common/logger"
|
|
"enaklo-pos-be/internal/common/mycontext"
|
|
"enaklo-pos-be/internal/entity"
|
|
"enaklo-pos-be/internal/repository/models"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
"time"
|
|
)
|
|
|
|
type OrderRepository interface {
|
|
Create(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
|
|
FindByID(ctx mycontext.Context, id int64) (*entity.Order, error)
|
|
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)
|
|
CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
|
|
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, 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)
|
|
FindByIDAndPartnerID(ctx mycontext.Context, id int64, partnerID int64) (*entity.Order, error)
|
|
}
|
|
|
|
type orderRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NeworderRepository(db *gorm.DB) *orderRepository {
|
|
return &orderRepository{db: db}
|
|
}
|
|
|
|
func (r *orderRepository) Create(ctx mycontext.Context, order *entity.Order) (*entity.Order, error) {
|
|
orderDB := r.toOrderDBModel(order)
|
|
|
|
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()
|
|
}
|
|
}()
|
|
|
|
if order.InProgressOrderID != 0 {
|
|
orderDB.ID = order.InProgressOrderID
|
|
|
|
if err := tx.Omit("customer_id", "partner_id", "customer_name", "created_by").Save(&orderDB).Error; err != nil {
|
|
tx.Rollback()
|
|
return nil, errors.Wrap(err, "failed to update in-progress order")
|
|
}
|
|
|
|
order.ID = order.InProgressOrderID
|
|
|
|
if err := tx.Where("order_id = ?", order.ID).Delete(&models.OrderItemDB{}).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
|
|
}
|
|
|
|
for i := range order.OrderItems {
|
|
item := &order.OrderItems[i]
|
|
item.OrderID = order.ID
|
|
|
|
itemDB := r.toOrderItemDBModel(item)
|
|
|
|
if err := tx.Create(&itemDB).Error; err != nil {
|
|
tx.Rollback()
|
|
return nil, errors.Wrap(err, "failed to insert order item")
|
|
}
|
|
|
|
item.ID = itemDB.ID
|
|
}
|
|
|
|
// Commit the transaction
|
|
if err := tx.Commit().Error; err != nil {
|
|
return nil, errors.Wrap(err, "failed to commit transaction")
|
|
}
|
|
|
|
var updatedOrderDB models.OrderDB
|
|
if err := r.db.Preload("OrderItems").First(&updatedOrderDB, order.ID).Error; err != nil {
|
|
return nil, errors.Wrap(err, "failed to fetch updated order")
|
|
}
|
|
|
|
updatedOrder := r.toDomainOrderModel(&updatedOrderDB)
|
|
|
|
return updatedOrder, nil
|
|
}
|
|
|
|
func (r *orderRepository) FindByID(ctx mycontext.Context, id int64) (*entity.Order, error) {
|
|
var orderDB models.OrderDB
|
|
|
|
if err := r.db.Preload("OrderItems").First(&orderDB, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("order not found")
|
|
}
|
|
return nil, errors.Wrap(err, "failed to find order")
|
|
}
|
|
|
|
order := r.toDomainOrderModel(&orderDB)
|
|
|
|
for _, itemDB := range orderDB.OrderItems {
|
|
item := r.toDomainOrderItemModel(&itemDB)
|
|
order.OrderItems = append(order.OrderItems, *item)
|
|
}
|
|
|
|
return order, nil
|
|
}
|
|
|
|
func (r *orderRepository) CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error) {
|
|
inquiryDB := r.toOrderInquiryDBModel(inquiry)
|
|
inquiryItems := make([]models.InquiryItemDB, 0, len(inquiry.OrderItems))
|
|
|
|
for _, item := range inquiry.OrderItems {
|
|
inquiryItems = append(inquiryItems, models.InquiryItemDB{
|
|
InquiryID: inquiryDB.ID,
|
|
ItemID: item.ItemID,
|
|
ItemType: item.ItemType,
|
|
ItemName: item.ItemName,
|
|
Price: item.Price,
|
|
Quantity: item.Quantity,
|
|
CreatedBy: item.CreatedBy,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
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()
|
|
}
|
|
}()
|
|
|
|
if err := tx.Create(&inquiryDB).Error; err != nil {
|
|
tx.Rollback()
|
|
return nil, errors.Wrap(err, "failed to insert order inquiry")
|
|
}
|
|
|
|
if len(inquiryItems) > 0 {
|
|
if err := tx.CreateInBatches(inquiryItems, 100).Error; err != nil {
|
|
tx.Rollback()
|
|
return nil, errors.Wrap(err, "failed to insert inquiry items")
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit().Error; err != nil {
|
|
return nil, errors.Wrap(err, "failed to commit transaction")
|
|
}
|
|
|
|
return inquiry, nil
|
|
}
|
|
|
|
func (r *orderRepository) FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error) {
|
|
var inquiryDB models.OrderInquiryDB
|
|
|
|
if err := r.db.Preload("InquiryItems").First(&inquiryDB, "id = ?", id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("inquiry not found")
|
|
}
|
|
return nil, errors.Wrap(err, "failed to find inquiry")
|
|
}
|
|
|
|
inquiry := r.toDomainOrderInquiryModel(&inquiryDB)
|
|
|
|
orderItems := make([]entity.OrderItem, 0, len(inquiryDB.InquiryItems))
|
|
for _, itemDB := range inquiryDB.InquiryItems {
|
|
orderItems = append(orderItems, entity.OrderItem{
|
|
ItemID: itemDB.ItemID,
|
|
ItemType: itemDB.ItemType,
|
|
ItemName: itemDB.ItemName,
|
|
Price: itemDB.Price,
|
|
Quantity: itemDB.Quantity,
|
|
CreatedBy: itemDB.CreatedBy,
|
|
CreatedAt: itemDB.CreatedAt,
|
|
})
|
|
}
|
|
inquiry.OrderItems = orderItems
|
|
|
|
return inquiry, nil
|
|
}
|
|
|
|
func (r *orderRepository) UpdateInquiryStatus(ctx mycontext.Context, id string, status string) error {
|
|
now := time.Now()
|
|
|
|
result := r.db.Model(&models.OrderInquiryDB{}).
|
|
Where("id = ?", id).
|
|
Updates(map[string]interface{}{
|
|
"status": status,
|
|
"updated_at": now,
|
|
})
|
|
|
|
if result.Error != nil {
|
|
return errors.Wrap(result.Error, "failed to update inquiry status")
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
logger.ContextLogger(ctx).Warn("no inquiry updated", zap.String("id", id))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *orderRepository) toOrderDBModel(order *entity.Order) models.OrderDB {
|
|
return models.OrderDB{
|
|
ID: order.ID,
|
|
PartnerID: order.PartnerID,
|
|
CustomerID: order.CustomerID,
|
|
InquiryID: order.InquiryID,
|
|
Status: order.Status,
|
|
Amount: order.Amount,
|
|
Tax: order.Tax,
|
|
Total: order.Total,
|
|
PaymentType: order.PaymentType,
|
|
Source: order.Source,
|
|
CreatedBy: order.CreatedBy,
|
|
CreatedAt: order.CreatedAt,
|
|
UpdatedAt: order.UpdatedAt,
|
|
OrderType: order.OrderType,
|
|
TableNumber: order.TableNumber,
|
|
PaymentProvider: order.PaymentProvider,
|
|
CustomerName: order.CustomerName,
|
|
}
|
|
}
|
|
|
|
func (r *orderRepository) toDomainOrderModel(dbModel *models.OrderDB) *entity.Order {
|
|
return &entity.Order{
|
|
ID: dbModel.ID,
|
|
PartnerID: dbModel.PartnerID,
|
|
CustomerID: dbModel.CustomerID,
|
|
InquiryID: dbModel.InquiryID,
|
|
Status: dbModel.Status,
|
|
Amount: dbModel.Amount,
|
|
Tax: dbModel.Tax,
|
|
Total: dbModel.Total,
|
|
PaymentType: dbModel.PaymentType,
|
|
Source: dbModel.Source,
|
|
CreatedBy: dbModel.CreatedBy,
|
|
CreatedAt: dbModel.CreatedAt,
|
|
UpdatedAt: dbModel.UpdatedAt,
|
|
OrderItems: []entity.OrderItem{},
|
|
CustomerName: dbModel.CustomerName,
|
|
TableNumber: dbModel.TableNumber,
|
|
OrderType: dbModel.OrderType,
|
|
PaymentProvider: dbModel.PaymentProvider,
|
|
}
|
|
}
|
|
|
|
func (r *orderRepository) toOrderItemDBModel(item *entity.OrderItem) models.OrderItemDB {
|
|
return models.OrderItemDB{
|
|
ID: item.ID,
|
|
OrderID: item.OrderID,
|
|
ItemID: item.ItemID,
|
|
ItemType: item.ItemType,
|
|
ItemName: item.ItemName,
|
|
Price: item.Price,
|
|
Quantity: item.Quantity,
|
|
CreatedBy: item.CreatedBy,
|
|
CreatedAt: item.CreatedAt,
|
|
}
|
|
}
|
|
|
|
func (r *orderRepository) toDomainOrderItemModel(dbModel *models.OrderItemDB) *entity.OrderItem {
|
|
return &entity.OrderItem{
|
|
ID: dbModel.ID,
|
|
OrderID: dbModel.OrderID,
|
|
ItemID: dbModel.ItemID,
|
|
ItemType: dbModel.ItemType,
|
|
Price: dbModel.Price,
|
|
Quantity: dbModel.Quantity,
|
|
CreatedBy: dbModel.CreatedBy,
|
|
CreatedAt: dbModel.CreatedAt,
|
|
ItemName: dbModel.ItemName,
|
|
}
|
|
}
|
|
|
|
func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) models.OrderInquiryDB {
|
|
return models.OrderInquiryDB{
|
|
ID: inquiry.ID,
|
|
PartnerID: inquiry.PartnerID,
|
|
CustomerID: &inquiry.CustomerID,
|
|
Status: inquiry.Status,
|
|
Amount: inquiry.Amount,
|
|
Tax: inquiry.Tax,
|
|
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 *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiryDB) *entity.OrderInquiry {
|
|
inquiry := &entity.OrderInquiry{
|
|
ID: dbModel.ID,
|
|
PartnerID: dbModel.PartnerID,
|
|
Status: dbModel.Status,
|
|
Amount: dbModel.Amount,
|
|
Tax: dbModel.Tax,
|
|
Total: dbModel.Total,
|
|
PaymentType: dbModel.PaymentType,
|
|
Source: dbModel.Source,
|
|
CreatedBy: dbModel.CreatedBy,
|
|
CreatedAt: dbModel.CreatedAt,
|
|
ExpiresAt: dbModel.ExpiresAt,
|
|
OrderItems: []entity.OrderItem{},
|
|
OrderType: dbModel.OrderType,
|
|
CustomerName: dbModel.CustomerName,
|
|
PaymentProvider: dbModel.PaymentProvider,
|
|
TableNumber: dbModel.TableNumber,
|
|
}
|
|
|
|
if dbModel.CustomerID != nil {
|
|
inquiry.CustomerID = *dbModel.CustomerID
|
|
}
|
|
|
|
inquiry.UpdatedAt = dbModel.UpdatedAt
|
|
|
|
return inquiry
|
|
}
|
|
|
|
func (r *orderRepository) GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]*entity.Order, int64, error) {
|
|
var ordersDB []models.OrderDB
|
|
var totalCount int64
|
|
|
|
// Build the base query
|
|
baseQuery := r.db.Model(&models.OrderDB{}).Where("partner_id = ?", partnerID)
|
|
|
|
// Apply filters to the base query
|
|
if req.Status != "" {
|
|
baseQuery = baseQuery.Where("status = ?", req.Status)
|
|
}
|
|
|
|
if !req.Start.IsZero() {
|
|
baseQuery = baseQuery.Where("created_at >= ?", req.Start)
|
|
}
|
|
|
|
if !req.End.IsZero() {
|
|
baseQuery = baseQuery.Where("created_at <= ?", req.End)
|
|
}
|
|
|
|
// Get total count with the current filters before pagination
|
|
if err := baseQuery.Count(&totalCount).Error; err != nil {
|
|
return nil, 0, errors.Wrap(err, "failed to count total orders")
|
|
}
|
|
|
|
// Clone the query for fetching the actual data with pagination
|
|
query := baseQuery.Session(&gorm.Session{})
|
|
|
|
// Add ordering and pagination
|
|
query = query.Order("created_at DESC")
|
|
|
|
if req.Limit > 0 {
|
|
query = query.Limit(req.Limit)
|
|
}
|
|
|
|
if req.Offset > 0 {
|
|
query = query.Offset(req.Offset)
|
|
}
|
|
|
|
// Execute the query with preloading
|
|
if err := query.Preload("OrderItems").Find(&ordersDB).Error; err != nil {
|
|
return nil, 0, errors.Wrap(err, "failed to find order history by partner ID")
|
|
}
|
|
|
|
// Map to domain models
|
|
orders := make([]*entity.Order, 0, len(ordersDB))
|
|
for _, orderDB := range ordersDB {
|
|
order := r.toDomainOrderModel(&orderDB)
|
|
order.OrderItems = make([]entity.OrderItem, 0, len(orderDB.OrderItems))
|
|
|
|
for _, itemDB := range orderDB.OrderItems {
|
|
item := r.toDomainOrderItemModel(&itemDB)
|
|
order.OrderItems = append(order.OrderItems, *item)
|
|
}
|
|
|
|
orders = append(orders, order)
|
|
}
|
|
|
|
return orders, totalCount, nil
|
|
}
|
|
|
|
func (r *orderRepository) CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error) {
|
|
isUpdate := order.ID != 0
|
|
|
|
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.OrderDB
|
|
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("order_id = ?", order.ID).Delete(&models.OrderItemDB{}).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]
|
|
item.OrderID = orderDB.ID
|
|
itemDB := r.toOrderItemDBModel(item)
|
|
|
|
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 *orderRepository) toInProgressOrderDBModel(order *entity.Order) models.OrderDB {
|
|
now := time.Now()
|
|
|
|
return models.OrderDB{
|
|
ID: order.ID,
|
|
PartnerID: order.PartnerID,
|
|
CustomerID: order.CustomerID,
|
|
CustomerName: order.CustomerName,
|
|
PaymentType: order.PaymentType,
|
|
PaymentProvider: order.PaymentProvider,
|
|
CreatedBy: order.CreatedBy,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
TableNumber: order.TableNumber,
|
|
OrderType: order.OrderType,
|
|
Status: order.Status,
|
|
Amount: order.Amount,
|
|
Total: order.Total,
|
|
Tax: order.Tax,
|
|
Source: order.Source,
|
|
}
|
|
}
|
|
|
|
func (r *orderRepository) 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,
|
|
}
|
|
}
|
|
|
|
func (r *orderRepository) GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error) {
|
|
var ordersDB []models.OrderDB
|
|
query := r.db.Where("partner_id = ?", partnerID)
|
|
|
|
if status != "" {
|
|
query = query.Where("status = ?", status)
|
|
}
|
|
|
|
query = query.Order("created_at DESC")
|
|
|
|
if limit > 0 {
|
|
query = query.Limit(limit)
|
|
}
|
|
|
|
if offset > 0 {
|
|
query = query.Offset(offset)
|
|
}
|
|
|
|
if err := query.Find(&ordersDB).Error; err != nil {
|
|
return nil, errors.Wrap(err, "failed to find orders by partner ID")
|
|
}
|
|
|
|
orders := make([]*entity.Order, 0, len(ordersDB))
|
|
for _, orderDB := range ordersDB {
|
|
order := r.toDomainOrderModel(&orderDB)
|
|
|
|
var orderItems []models.OrderItemDB
|
|
if err := r.db.Where("order_id = ?", orderDB.ID).Find(&orderItems).Error; err != nil {
|
|
return nil, errors.Wrap(err, "failed to find order items")
|
|
}
|
|
|
|
order.OrderItems = make([]entity.OrderItem, 0, len(orderItems))
|
|
|
|
for _, itemDB := range orderItems {
|
|
item := r.toDomainOrderItemModel(&itemDB)
|
|
|
|
orderItem := entity.OrderItem{
|
|
ID: item.ID,
|
|
ItemID: item.ItemID,
|
|
Quantity: item.Quantity,
|
|
ItemName: item.ItemName,
|
|
}
|
|
|
|
if itemDB.ItemID > 0 {
|
|
var product models.ProductDB
|
|
err := r.db.First(&product, itemDB.ItemID).Error
|
|
|
|
if err == nil {
|
|
productDomain := r.toDomainProductModel(&product)
|
|
orderItem.Product = productDomain
|
|
}
|
|
}
|
|
|
|
order.OrderItems = append(order.OrderItems, orderItem)
|
|
}
|
|
|
|
orders = append(orders, order)
|
|
}
|
|
|
|
return orders, nil
|
|
}
|
|
|
|
func (r *orderRepository) GetOrderPaymentMethodBreakdown(
|
|
ctx mycontext.Context,
|
|
partnerID int64,
|
|
req entity.SearchRequest,
|
|
) ([]entity.PaymentMethodBreakdown, error) {
|
|
var breakdown []entity.PaymentMethodBreakdown
|
|
|
|
baseQuery := r.db.Model(&models.OrderDB{}).Where("partner_id = ?", partnerID)
|
|
|
|
if !req.Start.IsZero() {
|
|
baseQuery = baseQuery.Where("created_at >= ?", req.Start)
|
|
}
|
|
|
|
if !req.End.IsZero() {
|
|
baseQuery = baseQuery.Where("created_at <= ?", req.End)
|
|
}
|
|
|
|
if req.Status != "" {
|
|
baseQuery = baseQuery.Where("status = ?", req.Status)
|
|
}
|
|
|
|
err := baseQuery.Select(
|
|
"payment_type, " +
|
|
"payment_provider, " +
|
|
"COUNT(*) as total_transactions, " +
|
|
"SUM(total) as total_amount",
|
|
).Group(
|
|
"payment_type, payment_provider",
|
|
).Order("total_amount DESC").Scan(&breakdown).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get payment method breakdown")
|
|
}
|
|
|
|
return breakdown, nil
|
|
}
|
|
|
|
func (r *orderRepository) GetRevenueOverview(
|
|
ctx mycontext.Context,
|
|
req entity.RevenueOverviewRequest,
|
|
) ([]entity.RevenueOverviewItem, error) {
|
|
var overview []entity.RevenueOverviewItem
|
|
|
|
baseQuery := r.db.Model(&models.OrderDB{}).
|
|
Where("partner_id = ?", req.PartnerID).
|
|
Where("EXTRACT(YEAR FROM created_at) = ?", req.Year)
|
|
|
|
if req.Status != "" {
|
|
baseQuery = baseQuery.Where("status = ?", req.Status)
|
|
}
|
|
|
|
switch req.Granularity {
|
|
case "m": // Monthly
|
|
err := baseQuery.Select(
|
|
"TO_CHAR(created_at, 'YYYY-MM') as period, " +
|
|
"SUM(total) as total_amount, " +
|
|
"COUNT(*) as order_count",
|
|
).Group("period").
|
|
Order("period").
|
|
Scan(&overview).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get monthly revenue overview")
|
|
}
|
|
|
|
case "w": // Weekly
|
|
err := baseQuery.Select(
|
|
"CONCAT(EXTRACT(YEAR FROM created_at), '-W', " +
|
|
"LPAD(EXTRACT(WEEK FROM created_at)::text, 2, '0')) as period, " +
|
|
"SUM(total) as total_amount, " +
|
|
"COUNT(*) as order_count",
|
|
).Group("period").
|
|
Order("period").
|
|
Scan(&overview).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get weekly revenue overview")
|
|
}
|
|
|
|
case "d": // Daily
|
|
err := baseQuery.Select(
|
|
"TO_CHAR(created_at, 'YYYY-MM-DD') as period, " +
|
|
"SUM(total) as total_amount, " +
|
|
"COUNT(*) as order_count",
|
|
).Group("period").
|
|
Order("period").
|
|
Scan(&overview).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get daily revenue overview")
|
|
}
|
|
|
|
default:
|
|
return nil, errors.New("invalid granularity. Use 'm' (monthly), 'w' (weekly), or 'd' (daily)")
|
|
}
|
|
|
|
return overview, nil
|
|
}
|
|
|
|
func (r *orderRepository) GetSalesByCategory(
|
|
ctx mycontext.Context,
|
|
req entity.SalesByCategoryRequest,
|
|
) ([]entity.SalesByCategoryItem, error) {
|
|
salesByCategory := []entity.SalesByCategoryItem{}
|
|
|
|
baseQuery := r.db.Model(&models.OrderItemDB{}).
|
|
Joins("JOIN orders ON order_items.order_id = orders.id").
|
|
Where("orders.partner_id = ?", req.PartnerID)
|
|
|
|
if req.Status != "" {
|
|
baseQuery = baseQuery.Where("orders.status = ?", req.Status)
|
|
}
|
|
|
|
switch req.Period {
|
|
case "d": // Daily
|
|
baseQuery = baseQuery.Where("DATE(orders.created_at) = CURRENT_DATE")
|
|
case "w": // Weekly
|
|
baseQuery = baseQuery.Where("DATE_TRUNC('week', orders.created_at) = DATE_TRUNC('week', CURRENT_DATE)")
|
|
case "m": // Monthly
|
|
baseQuery = baseQuery.Where("DATE_TRUNC('month', orders.created_at) = DATE_TRUNC('month', CURRENT_DATE)")
|
|
default:
|
|
return nil, errors.New("invalid period. Use 'd' (daily), 'w' (weekly), or 'm' (monthly)")
|
|
}
|
|
|
|
var totalSales float64
|
|
err := r.db.Model(&models.OrderItemDB{}).
|
|
Joins("JOIN orders ON order_items.order_id = orders.id").
|
|
Where("orders.partner_id = ?", req.PartnerID).
|
|
Select("COALESCE(SUM(order_items.price * order_items.quantity), 0)").
|
|
Scan(&totalSales).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to calculate total sales")
|
|
}
|
|
|
|
err = baseQuery.Select(
|
|
"order_items.item_type AS category, " +
|
|
"COALESCE(SUM(order_items.price * order_items.quantity), 0) AS total_amount, " +
|
|
"COALESCE(SUM(order_items.quantity), 0) AS total_quantity",
|
|
).
|
|
Group("order_items.item_type").
|
|
Order("total_amount DESC").
|
|
Scan(&salesByCategory).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get sales by category")
|
|
}
|
|
|
|
for i := range salesByCategory {
|
|
if totalSales > 0 {
|
|
salesByCategory[i].Percentage =
|
|
(salesByCategory[i].TotalAmount / totalSales) * 100
|
|
}
|
|
}
|
|
|
|
return salesByCategory, nil
|
|
}
|
|
|
|
func (r *orderRepository) GetPopularProducts(
|
|
ctx mycontext.Context,
|
|
req entity.PopularProductsRequest,
|
|
) ([]entity.PopularProductItem, error) {
|
|
if req.Limit == 0 {
|
|
req.Limit = 10
|
|
}
|
|
|
|
if req.SortBy != "sales" && req.SortBy != "revenue" {
|
|
req.SortBy = "sales" // default to sales
|
|
}
|
|
|
|
// Base query
|
|
baseQuery := r.db.Model(&models.OrderItemDB{}).
|
|
Joins("JOIN orders ON order_items.order_id = orders.id").
|
|
Where("orders.partner_id = ?", req.PartnerID)
|
|
|
|
if req.Status != "" {
|
|
baseQuery = baseQuery.Where("orders.status = ?", req.Status)
|
|
}
|
|
|
|
switch req.Period {
|
|
case "d": // Daily
|
|
baseQuery = baseQuery.Where("DATE(orders.created_at) = CURRENT_DATE")
|
|
case "w": // Weekly
|
|
baseQuery = baseQuery.Where("DATE_TRUNC('week', orders.created_at) = DATE_TRUNC('week', CURRENT_DATE)")
|
|
case "m": // Monthly
|
|
baseQuery = baseQuery.Where("DATE_TRUNC('month', orders.created_at) = DATE_TRUNC('month', CURRENT_DATE)")
|
|
default:
|
|
return nil, errors.New("invalid period. Use 'd' (daily), 'w' (weekly), or 'm' (monthly)")
|
|
}
|
|
|
|
// Calculate total sales/revenue for percentage calculation
|
|
var totalSales struct {
|
|
TotalAmount float64
|
|
TotalQuantity int64
|
|
}
|
|
|
|
err := baseQuery.
|
|
Select("COALESCE(SUM(order_items.price * order_items.quantity), 0) as total_amount, " +
|
|
"COALESCE(SUM(order_items.quantity), 0) as total_quantity").
|
|
Scan(&totalSales).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to calculate total sales")
|
|
}
|
|
|
|
popularProducts := []entity.PopularProductItem{}
|
|
orderClause := "total_sales DESC"
|
|
if req.SortBy == "revenue" {
|
|
orderClause = "total_revenue DESC"
|
|
}
|
|
|
|
err = baseQuery.
|
|
Select(
|
|
"order_items.item_id AS product_id, " +
|
|
"order_items.item_name AS product_name, " +
|
|
"order_items.item_type AS category, " +
|
|
"COALESCE(SUM(order_items.quantity), 0) AS total_sales, " +
|
|
"COALESCE(SUM(order_items.price * order_items.quantity), 0) AS total_revenue, " +
|
|
"COALESCE(AVG(order_items.price), 0) AS average_price",
|
|
).
|
|
Group("order_items.item_id, order_items.item_name, order_items.item_type").
|
|
Order(orderClause).
|
|
Limit(req.Limit).
|
|
Scan(&popularProducts).Error
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get popular products")
|
|
}
|
|
|
|
for i := range popularProducts {
|
|
popularProducts[i].Percentage =
|
|
(float64(popularProducts[i].TotalSales) / float64(totalSales.TotalQuantity)) * 100
|
|
}
|
|
|
|
return popularProducts, nil
|
|
}
|
|
|
|
func (r *orderRepository) FindByIDAndPartnerID(ctx mycontext.Context, id int64, partnerID int64) (*entity.Order, error) {
|
|
var orderDB models.OrderDB
|
|
|
|
if err := r.db.Preload("OrderItems").Where("id = ? AND partner_id = ?", id, partnerID).First(&orderDB).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("order not found")
|
|
}
|
|
return nil, errors.Wrap(err, "failed to find order")
|
|
}
|
|
|
|
order := r.toDomainOrderModel(&orderDB)
|
|
|
|
for _, itemDB := range orderDB.OrderItems {
|
|
item := r.toDomainOrderItemModel(&itemDB)
|
|
order.OrderItems = append(order.OrderItems, *item)
|
|
}
|
|
|
|
return order, nil
|
|
}
|