upodate system

This commit is contained in:
aditya.siregar
2025-06-27 13:01:39 +07:00
parent 1201b2e45b
commit f31f83e485
36 changed files with 2400 additions and 689 deletions
+214 -159
View File
@@ -1,112 +1,116 @@
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"
"enaklo-pos-be/internal/services/v2/inprogress_order"
time2 "time"
"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)
FindByID(ctx mycontext.Context, id int64) (*entity.Order, error)
CreateOrder(ctx mycontext.Context, order *entity.Order, tx *gorm.DB) (*entity.Order, error)
CreateOrderItems(ctx mycontext.Context, orderID int64, items []entity.OrderItem, tx *gorm.DB) error
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error)
FindByIDAndPartnerID(ctx mycontext.Context, id int64, partnerID int64) (*entity.Order, error)
}
type inprogressOrderRepository struct {
db *gorm.DB
}
func NewInProgressOrderRepository(db *gorm.DB) *inprogressOrderRepository {
func NewInProgressOrderRepository(db *gorm.DB) inprogress_order.OrderRepository {
return &inprogressOrderRepository{db: db}
}
func (r *inprogressOrderRepository) CreateOrUpdate(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) {
isUpdate := order.ID != ""
func (r *inprogressOrderRepository) FindByID(ctx mycontext.Context, id int64) (*entity.Order, error) {
var orderDB models.OrderDB
tx := r.db.Begin()
if tx.Error != nil {
return nil, errors.Wrap(tx.Error, "failed to begin transaction")
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")
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
orderDB := r.toInProgressOrderDBModel(order)
order := r.toDomainOrderModel(&orderDB)
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")
}
return order, nil
}
if err := tx.Model(&orderDB).Updates(orderDB).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to update order")
}
func (r *inprogressOrderRepository) CreateOrder(ctx mycontext.Context, order *entity.Order, tx *gorm.DB) (*entity.Order, error) {
orderDB := r.toOrderDBModel(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")
}
// Use provided transaction or create new one
var dbTx *gorm.DB
if tx != nil {
dbTx = tx
} else {
if err := tx.Create(&orderDB).Error; err != nil {
tx.Rollback()
return nil, errors.Wrap(err, "failed to insert order")
dbTx = r.db.Begin()
if dbTx.Error != nil {
return nil, errors.Wrap(dbTx.Error, "failed to begin transaction")
}
order.ID = orderDB.ID
defer func() {
if r := recover(); r != nil {
dbTx.Rollback()
}
}()
}
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")
if err := dbTx.Create(&orderDB).Error; err != nil {
if tx == nil {
dbTx.Rollback()
}
return nil, errors.Wrap(err, "failed to insert order")
}
order.ID = orderDB.ID
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")
// Only commit if we created the transaction
if tx == nil {
if err := dbTx.Commit().Error; err != nil {
return nil, errors.Wrap(err, "failed to commit transaction")
}
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")
func (r *inprogressOrderRepository) CreateOrderItems(ctx mycontext.Context, orderID int64, items []entity.OrderItem, tx *gorm.DB) error {
if len(items) == 0 {
return nil
}
itemsDB := make([]models.OrderItemDB, len(items))
for i, item := range items {
itemDB := r.toOrderItemDBModel(&item)
itemDB.OrderID = orderID
itemsDB[i] = itemDB
}
if err := tx.Create(&itemsDB).Error; err != nil {
return errors.Wrap(err, "failed to bulk insert order items")
}
for i := range items {
items[i].ID = itemsDB[i].ID
}
return nil
}
func (r *inprogressOrderRepository) 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)
@@ -116,27 +120,39 @@ func (r *inprogressOrderRepository) GetListByPartnerID(ctx mycontext.Context, pa
query = query.Offset(offset)
}
if err := query.Preload("OrderItems.Product").Find(&ordersDB).Error; err != nil {
if err := query.Find(&ordersDB).Error; err != nil {
return nil, errors.Wrap(err, "failed to find orders by partner ID")
}
orders := make([]*entity.InProgressOrder, 0, len(ordersDB))
orders := make([]*entity.Order, 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 {
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.InProgressOrderItem{
orderItem := entity.OrderItem{
ID: item.ID,
ItemID: item.ItemID,
Quantity: item.Quantity,
ItemName: item.ItemName,
}
if itemDB.Product.ID > 0 {
productDomain := r.toDomainProductModel(&itemDB.Product)
orderItem.Product = productDomain
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)
@@ -148,106 +164,122 @@ func (r *inprogressOrderRepository) GetListByPartnerID(ctx mycontext.Context, pa
return orders, nil
}
func (r *inprogressOrderRepository) toInProgressOrderDBModel(order *entity.InProgressOrder) models.InProgressOrderDB {
func (r *inprogressOrderRepository) 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
}
func (r *inprogressOrderRepository) toOrderDBModel(order *entity.Order) models.OrderDB {
now := time2.Now()
return models.InProgressOrderDB{
ID: order.ID,
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,
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 *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) toDomainOrderModel(dbModel *models.OrderDB) *entity.Order {
orderItems := make([]entity.OrderItem, 0, len(dbModel.OrderItems))
for _, itemDB := range dbModel.OrderItems {
orderItems = append(orderItems, entity.OrderItem{
ItemID: itemDB.ItemID,
ItemType: itemDB.ItemType,
ItemName: itemDB.ItemName,
Price: itemDB.Price,
Quantity: itemDB.Quantity,
Status: itemDB.Status,
CreatedBy: itemDB.CreatedBy,
CreatedAt: itemDB.CreatedAt,
Notes: itemDB.Notes,
})
}
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: orderItems,
CustomerName: dbModel.CustomerName,
TableNumber: dbModel.TableNumber,
OrderType: dbModel.OrderType,
PaymentProvider: dbModel.PaymentProvider,
}
}
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) 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,
Status: item.Status,
CreatedBy: item.CreatedBy,
CreatedAt: item.CreatedAt,
Notes: item.Notes,
}
}
func (r *inprogressOrderRepository) toDomainOrderItemModel(dbModel *models.InProgressOrderItemDB) *entity.OrderItem {
func (r *inprogressOrderRepository) 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,
Status: dbModel.Status,
CreatedBy: dbModel.CreatedBy,
CreatedAt: dbModel.CreatedAt,
ItemName: dbModel.ItemName,
Notes: dbModel.Notes,
Product: &entity.Product{
ID: dbModel.ItemID,
Name: dbModel.ItemName,
},
}
}
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,
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 *inprogressOrderRepository) 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{},
}
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
@@ -264,3 +296,26 @@ func (r *inprogressOrderRepository) toDomainProductModel(productDB *models.Produ
Image: productDB.Image,
}
}
func (r *inprogressOrderRepository) UpdateOrderTotalsWithTx(ctx mycontext.Context, trx *gorm.DB, orderID int64, amount, tax, total float64) error {
now := time2.Now()
result := trx.Model(&models.OrderDB{}).
Where("id = ?", orderID).
Updates(map[string]interface{}{
"amount": amount,
"tax": tax,
"total": total,
"updated_at": now,
})
if result.Error != nil {
return errors.Wrap(result.Error, "failed to update order totals")
}
if result.RowsAffected == 0 {
logger.ContextLogger(ctx).Warn("no order updated")
}
return nil
}
+41 -1
View File
@@ -4,9 +4,10 @@ import (
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository/models"
"time"
"github.com/pkg/errors"
"gorm.io/gorm"
"time"
)
type CashierSessionRepository interface {
@@ -15,6 +16,7 @@ type CashierSessionRepository interface {
GetOpenSessionByCashierID(ctx mycontext.Context, cashierID int64) (*entity.CashierSession, error)
GetSessionByID(ctx mycontext.Context, sessionID int64) (*entity.CashierSession, error)
GetPaymentSummaryBySessionID(ctx mycontext.Context, sessionID int64) ([]entity.PaymentSummary, error)
GetSessionHistoryByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.CashierSession, int64, error)
}
type cashierSessionRepository struct {
@@ -27,6 +29,7 @@ func NewCashierSessionRepository(db *gorm.DB) CashierSessionRepository {
func (r *cashierSessionRepository) CreateSession(ctx mycontext.Context, session *entity.CashierSession) (*entity.CashierSession, error) {
dbModel := models.CashierSessionDB{
PartnerID: session.PartnerID,
CashierID: session.CashierID,
OpenedAt: time.Now(),
OpeningAmount: session.OpeningAmount,
@@ -94,6 +97,7 @@ func (r *cashierSessionRepository) GetSessionByID(ctx mycontext.Context, session
func (r *cashierSessionRepository) toEntity(db *models.CashierSessionDB) *entity.CashierSession {
return &entity.CashierSession{
ID: db.ID,
PartnerID: db.PartnerID,
CashierID: db.CashierID,
OpenedAt: db.OpenedAt,
ClosedAt: db.ClosedAt,
@@ -135,3 +139,39 @@ func (r *cashierSessionRepository) GetPaymentSummaryBySessionID(ctx mycontext.Co
return summary, nil
}
func (r *cashierSessionRepository) GetSessionHistoryByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.CashierSession, int64, error) {
var sessionsDB []models.CashierSessionDB
var totalCount int64
// Count total records
if err := r.db.Model(&models.CashierSessionDB{}).
Where("partner_id = ?", partnerID).
Count(&totalCount).Error; err != nil {
return nil, 0, errors.Wrap(err, "failed to count cashier sessions")
}
// Get sessions with pagination
query := r.db.Where("partner_id = ?", partnerID).
Order("opened_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&sessionsDB).Error; err != nil {
return nil, 0, errors.Wrap(err, "failed to get cashier session history")
}
// Convert to entity
sessions := make([]*entity.CashierSession, len(sessionsDB))
for i, sessionDB := range sessionsDB {
sessions[i] = r.toEntity(&sessionDB)
}
return sessions, totalCount, nil
}
@@ -4,6 +4,7 @@ import "time"
type CashierSessionDB struct {
ID int64 `gorm:"primaryKey"`
PartnerID int64 `gorm:"not null"`
CashierID int64 `gorm:"not null"`
OpenedAt time.Time `gorm:"not null"`
ClosedAt *time.Time
+1
View File
@@ -39,6 +39,7 @@ type OrderItemDB struct {
ItemType string `gorm:"column:item_type"`
Price float64 `gorm:"column:price"`
Quantity int `gorm:"column:quantity"`
Status string `gorm:"column:status;default:ACTIVE"`
CreatedBy int64 `gorm:"column:created_by"`
CreatedAt time.Time `gorm:"column:created_at"`
Product ProductDB `gorm:"foreignKey:ItemID;references:ID"`
+181 -169
View File
@@ -5,10 +5,11 @@ import (
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository/models"
"time"
"github.com/pkg/errors"
"go.uber.org/zap"
"gorm.io/gorm"
"time"
)
type OrderRepository interface {
@@ -17,32 +18,22 @@ type OrderRepository 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)
CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID *int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
CreateOrder(ctx mycontext.Context, order *entity.Order, tx *gorm.DB) (*entity.Order, error)
CreateOrderItems(ctx mycontext.Context, orderID int64, items []entity.OrderItem, tx *gorm.DB) error
CreateOrderItem(ctx mycontext.Context, orderID int64, item *entity.OrderItem) 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)
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)
GetOrderHistoryByUserID(ctx mycontext.Context, userID int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
FindByIDAndCustomerID(ctx mycontext.Context, id int64, customerID int64) (*entity.Order, error)
UpdateOrder(ctx mycontext.Context, id int64, status string, description string) error
UpdateOrderItem(ctx mycontext.Context, orderItemID int64, quantity int) error
UpdateOrderTotals(ctx mycontext.Context, orderID int64, amount, tax, total float64) error
UpdateOrderTotalsWithTx(ctx mycontext.Context, trx *gorm.DB, orderID int64, amount, tax, total float64) error
}
type orderRepository struct {
@@ -129,11 +120,6 @@ func (r *orderRepository) FindByID(ctx mycontext.Context, id int64) (*entity.Ord
order := r.toDomainOrderModel(&orderDB)
for _, itemDB := range orderDB.OrderItems {
item := r.toDomainOrderItemModel(&itemDB)
order.OrderItems = append(order.OrderItems, *item)
}
return order, nil
}
@@ -281,15 +267,16 @@ func (r *orderRepository) toOrderDBModel(order *entity.Order) models.OrderDB {
}
func (r *orderRepository) toDomainOrderModel(dbModel *models.OrderDB) *entity.Order {
orderItems := make([]entity.OrderItem, 0, len(dbModel.OrderItems))
for _, itemDB := range dbModel.OrderItems {
orderItems = append(orderItems, entity.OrderItem{
ID: itemDB.ID,
ItemID: itemDB.ItemID,
ItemType: itemDB.ItemType,
ItemName: itemDB.ItemName,
Price: itemDB.Price,
Quantity: itemDB.Quantity,
Status: itemDB.Status,
CreatedBy: itemDB.CreatedBy,
CreatedAt: itemDB.CreatedAt,
Notes: itemDB.Notes,
@@ -327,6 +314,7 @@ func (r *orderRepository) toOrderItemDBModel(item *entity.OrderItem) models.Orde
ItemName: item.ItemName,
Price: item.Price,
Quantity: item.Quantity,
Status: item.Status,
CreatedBy: item.CreatedBy,
CreatedAt: item.CreatedAt,
Notes: item.Notes,
@@ -341,9 +329,11 @@ func (r *orderRepository) toDomainOrderItemModel(dbModel *models.OrderItemDB) *e
ItemType: dbModel.ItemType,
Price: dbModel.Price,
Quantity: dbModel.Quantity,
Status: dbModel.Status,
CreatedBy: dbModel.CreatedBy,
CreatedAt: dbModel.CreatedAt,
ItemName: dbModel.ItemName,
Notes: dbModel.Notes,
Product: &entity.Product{
ID: dbModel.ItemID,
Name: dbModel.ItemName,
@@ -406,65 +396,70 @@ func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiry
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
func (r *orderRepository) GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID *int64, req entity.SearchRequest) ([]*entity.Order, int64, error) {
queryBuilder := NewQueryBuilder[models.OrderDB](r.db)
filters := []Filter{
Equal("partner_id", partnerID),
}
// 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)
filters = append(filters, Equal("status", req.Status))
}
if !req.Start.IsZero() {
baseQuery = baseQuery.Where("created_at >= ?", req.Start)
filters = append(filters, GreaterEqual("created_at", req.Start))
}
if !req.End.IsZero() {
baseQuery = baseQuery.Where("created_at <= ?", req.End)
filters = append(filters, LessEqual("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")
options := QueryOptions{
Filters: filters,
Limit: req.Limit,
Offset: req.Offset,
OrderBy: []string{"created_at DESC"},
Preloads: []string{"OrderItems"},
}
// 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)
baseQuery := queryBuilder.BuildQuery(options)
totalCount, err := queryBuilder.Count(baseQuery)
if err != nil {
return nil, 0, err
}
if req.Offset > 0 {
query = query.Offset(req.Offset)
query := queryBuilder.ExecuteQuery(baseQuery, options)
ordersDB, err := queryBuilder.Find(query)
if err != nil {
return nil, 0, err
}
// 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")
}
orders := r.convertOrdersToEntity(ordersDB)
// Map to domain models
return orders, totalCount, nil
}
func (r *orderRepository) convertOrdersToEntity(ordersDB []models.OrderDB) []*entity.Order {
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)
}
order.OrderItems = r.convertOrderItemsToEntity(orderDB.OrderItems)
orders = append(orders, order)
}
return orders, totalCount, nil
return orders
}
func (r *orderRepository) convertOrderItemsToEntity(itemsDB []models.OrderItemDB) []entity.OrderItem {
items := make([]entity.OrderItem, 0, len(itemsDB))
for _, itemDB := range itemsDB {
item := r.toDomainOrderItemModel(&itemDB)
items = append(items, *item)
}
return items
}
func (r *orderRepository) GetOrderHistoryByUserID(ctx mycontext.Context, userID int64, req entity.SearchRequest) ([]*entity.Order, int64, error) {
@@ -521,126 +516,108 @@ func (r *orderRepository) GetOrderHistoryByUserID(ctx mycontext.Context, userID
return orders, totalCount, nil
}
func (r *orderRepository) CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error) {
isUpdate := order.ID != 0
func (r *orderRepository) CreateOrder(ctx mycontext.Context, order *entity.Order, tx *gorm.DB) (*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()
}
}()
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")
}
// Use provided transaction or create new one
var dbTx *gorm.DB
if tx != nil {
dbTx = tx
} else {
if err := tx.Create(&orderDB).Error; err != nil {
tx.Rollback()
dbTx = r.db.Begin()
if dbTx.Error != nil {
return nil, errors.Wrap(dbTx.Error, "failed to begin transaction")
}
defer func() {
if r := recover(); r != nil {
dbTx.Rollback()
}
}()
}
if order.InProgressOrderID != 0 {
// Update existing order
orderDB.ID = order.InProgressOrderID
if err := dbTx.Omit("customer_id", "partner_id", "customer_name", "created_by").Save(&orderDB).Error; err != nil {
if tx == nil {
dbTx.Rollback()
}
return nil, errors.Wrap(err, "failed to update in-progress order")
}
order.ID = order.InProgressOrderID
} else {
// Create new order
if err := dbTx.Create(&orderDB).Error; err != nil {
if tx == nil {
dbTx.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")
// Only commit if we created the transaction
if tx == nil {
if err := dbTx.Commit().Error; err != nil {
return nil, errors.Wrap(err, "failed to commit transaction")
}
}
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 the order with the ID set, but without items (items will be added separately)
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) CreateOrderItems(ctx mycontext.Context, orderID int64, items []entity.OrderItem, tx *gorm.DB) error {
// Use provided transaction or create new one
var dbTx *gorm.DB
if tx != nil {
dbTx = tx
} else {
dbTx = r.db.Begin()
if dbTx.Error != nil {
return errors.Wrap(dbTx.Error, "failed to begin transaction")
}
defer func() {
if r := recover(); r != nil {
dbTx.Rollback()
}
}()
}
for _, item := range items {
itemDB := r.toOrderItemDBModel(&item)
itemDB.OrderID = orderID
if err := dbTx.Create(&itemDB).Error; err != nil {
if tx == nil {
dbTx.Rollback()
}
return errors.Wrap(err, "failed to insert order item")
}
item.ID = itemDB.ID
}
// Only commit if we created the transaction
if tx == nil {
if err := dbTx.Commit().Error; err != nil {
return errors.Wrap(err, "failed to commit transaction")
}
}
return nil
}
func (r *orderRepository) toDomainProductModel(productDB *models.ProductDB) *entity.Product {
if productDB == nil {
return nil
func (r *orderRepository) CreateOrderItem(ctx mycontext.Context, orderID int64, item *entity.OrderItem) error {
itemDB := r.toOrderItemDBModel(item)
itemDB.OrderID = orderID
if err := r.db.Create(&itemDB).Error; err != nil {
return errors.Wrap(err, "failed to insert order item")
}
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,
}
item.ID = itemDB.ID
return nil
}
func (r *orderRepository) GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error) {
@@ -954,11 +931,6 @@ func (r *orderRepository) FindByIDAndPartnerID(ctx mycontext.Context, id int64,
order := r.toDomainOrderModel(&orderDB)
for _, itemDB := range orderDB.OrderItems {
item := r.toDomainOrderItemModel(&itemDB)
order.OrderItems = append(order.OrderItems, *item)
}
return order, nil
}
@@ -1025,3 +997,43 @@ func (r *orderRepository) UpdateOrderTotals(ctx mycontext.Context, orderID int64
return nil
}
func (r *orderRepository) UpdateOrderTotalsWithTx(ctx mycontext.Context, trx *gorm.DB, orderID int64, amount, tax, total float64) error {
now := time.Now()
result := trx.Model(&models.OrderDB{}).
Where("id = ?", orderID).
Updates(map[string]interface{}{
"amount": amount,
"tax": tax,
"total": total,
"updated_at": now,
})
if result.Error != nil {
return errors.Wrap(result.Error, "failed to update order totals")
}
if result.RowsAffected == 0 {
logger.ContextLogger(ctx).Warn("no order updated")
}
return nil
}
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,
}
}
+193
View File
@@ -0,0 +1,193 @@
package repository
import (
"github.com/pkg/errors"
"gorm.io/gorm"
)
type QueryBuilder[T any] struct {
db *gorm.DB
model T
}
func NewQueryBuilder[T any](db *gorm.DB) *QueryBuilder[T] {
var model T
return &QueryBuilder[T]{
db: db,
model: model,
}
}
type Filter struct {
Field string
Operator string // "=", "!=", ">", "<", ">=", "<=", "LIKE", "IN", "NOT IN", "IS NULL", "IS NOT NULL"
Value interface{}
}
type QueryOptions struct {
Filters []Filter
Limit int
Offset int
OrderBy []string
Preloads []string
GroupBy []string
Having []Filter
Distinct []string
CountOnly bool
}
func (qb *QueryBuilder[T]) BuildQuery(options QueryOptions) *gorm.DB {
query := qb.db.Model(&qb.model)
for _, filter := range options.Filters {
query = qb.applyFilter(query, filter)
}
if len(options.Distinct) > 0 {
for _, distinct := range options.Distinct {
query = query.Distinct(distinct)
}
}
if len(options.GroupBy) > 0 {
for _, groupBy := range options.GroupBy {
query = query.Group(groupBy)
}
}
for _, having := range options.Having {
query = qb.applyFilter(query, having)
}
return query
}
func (qb *QueryBuilder[T]) applyFilter(query *gorm.DB, filter Filter) *gorm.DB {
switch filter.Operator {
case "=", "":
return query.Where(filter.Field+" = ?", filter.Value)
case "!=":
return query.Where(filter.Field+" != ?", filter.Value)
case ">":
return query.Where(filter.Field+" > ?", filter.Value)
case "<":
return query.Where(filter.Field+" < ?", filter.Value)
case ">=":
return query.Where(filter.Field+" >= ?", filter.Value)
case "<=":
return query.Where(filter.Field+" <= ?", filter.Value)
case "LIKE":
return query.Where(filter.Field+" LIKE ?", filter.Value)
case "IN":
return query.Where(filter.Field+" IN ?", filter.Value)
case "NOT IN":
return query.Where(filter.Field+" NOT IN ?", filter.Value)
case "IS NULL":
return query.Where(filter.Field + " IS NULL")
case "IS NOT NULL":
return query.Where(filter.Field + " IS NOT NULL")
case "BETWEEN":
if values, ok := filter.Value.([]interface{}); ok && len(values) == 2 {
return query.Where(filter.Field+" BETWEEN ? AND ?", values[0], values[1])
}
return query
default:
return query.Where(filter.Field+" = ?", filter.Value)
}
}
func (qb *QueryBuilder[T]) ExecuteQuery(baseQuery *gorm.DB, options QueryOptions) *gorm.DB {
query := baseQuery.Session(&gorm.Session{})
if len(options.OrderBy) > 0 {
for _, orderBy := range options.OrderBy {
query = query.Order(orderBy)
}
}
for _, preload := range options.Preloads {
query = query.Preload(preload)
}
if options.Limit > 0 {
query = query.Limit(options.Limit)
}
if options.Offset > 0 {
query = query.Offset(options.Offset)
}
return query
}
func (qb *QueryBuilder[T]) Count(baseQuery *gorm.DB) (int64, error) {
var count int64
if err := baseQuery.Count(&count).Error; err != nil {
return 0, errors.Wrap(err, "failed to count records")
}
return count, nil
}
func (qb *QueryBuilder[T]) Find(query *gorm.DB) ([]T, error) {
var results []T
if err := query.Find(&results).Error; err != nil {
return nil, errors.Wrap(err, "failed to find records")
}
return results, nil
}
func (qb *QueryBuilder[T]) First(query *gorm.DB) (*T, error) {
var result T
if err := query.First(&result).Error; err != nil {
return nil, errors.Wrap(err, "failed to find record")
}
return &result, nil
}
func Equal(field string, value interface{}) Filter {
return Filter{Field: field, Operator: "=", Value: value}
}
func NotEqual(field string, value interface{}) Filter {
return Filter{Field: field, Operator: "!=", Value: value}
}
func GreaterThan(field string, value interface{}) Filter {
return Filter{Field: field, Operator: ">", Value: value}
}
func LessThan(field string, value interface{}) Filter {
return Filter{Field: field, Operator: "<", Value: value}
}
func GreaterEqual(field string, value interface{}) Filter {
return Filter{Field: field, Operator: ">=", Value: value}
}
func LessEqual(field string, value interface{}) Filter {
return Filter{Field: field, Operator: "<=", Value: value}
}
func Like(field string, value string) Filter {
return Filter{Field: field, Operator: "LIKE", Value: value}
}
func In(field string, values interface{}) Filter {
return Filter{Field: field, Operator: "IN", Value: values}
}
func NotIn(field string, values interface{}) Filter {
return Filter{Field: field, Operator: "NOT IN", Value: values}
}
func IsNull(field string) Filter {
return Filter{Field: field, Operator: "IS NULL"}
}
func IsNotNull(field string) Filter {
return Filter{Field: field, Operator: "IS NOT NULL"}
}
func Between(field string, start, end interface{}) Filter {
return Filter{Field: field, Operator: "BETWEEN", Value: []interface{}{start, end}}
}
+8 -9
View File
@@ -16,7 +16,6 @@ import (
"enaklo-pos-be/internal/repository/products"
"enaklo-pos-be/internal/repository/sites"
transactions "enaklo-pos-be/internal/repository/transaction"
"enaklo-pos-be/internal/repository/trx"
"enaklo-pos-be/internal/repository/users"
repository "enaklo-pos-be/internal/repository/wallet"
@@ -38,7 +37,7 @@ type RepoManagerImpl struct {
OSS OSSRepository
Partner PartnerRepository
Site SiteRepository
Trx TransactionManager
Trx Trx
Wallet WalletRepository
Midtrans Midtrans
Payment Payment
@@ -70,7 +69,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
OSS: oss.NewOssRepositoryImpl(cfg.OSSConfig),
Partner: partners.NewPartnerRepository(db),
Site: sites.NewSiteRepository(db),
Trx: trx.NewGormTransactionManager(db),
Trx: NewTransactionManager(db),
Wallet: repository.NewWalletRepository(db),
Midtrans: mdtrns.New(&cfg.Midtrans),
Payment: payment.NewPaymentRepository(db),
@@ -188,12 +187,6 @@ type SiteRepository interface {
SearchSites(ctx context.Context, search *entity.DiscoverySearch) ([]entity.SiteProductInfo, int64, error)
}
type TransactionManager interface {
Begin(ctx context.Context, opts ...*sql.TxOptions) (*gorm.DB, error)
Commit(session *gorm.DB) *gorm.DB
Rollback(session *gorm.DB) *gorm.DB
}
type WalletRepository interface {
Create(ctx context.Context, tx *gorm.DB, wallet *entity.Wallet) (*entity.Wallet, error)
Update(ctx context.Context, db *gorm.DB, wallet *entity.Wallet) (*entity.Wallet, error)
@@ -244,3 +237,9 @@ type PaymentGateway interface {
CreateQRISPayment(request entity.PaymentRequest) (*entity.PaymentResponse, error)
CreatePaymentVA(request entity.PaymentRequest) (*entity.PaymentResponse, error)
}
type Trx interface {
Begin(ctx context.Context, opts ...*sql.TxOptions) (*gorm.DB, error)
Commit(session *gorm.DB) *gorm.DB
Rollback(session *gorm.DB) *gorm.DB
}
+32
View File
@@ -0,0 +1,32 @@
package repository
import (
"context"
"database/sql"
"gorm.io/gorm"
)
type TransactionManager struct {
db *gorm.DB
}
func NewTransactionManager(db *gorm.DB) *TransactionManager {
return &TransactionManager{db: db}
}
func (tm *TransactionManager) Begin(ctx context.Context, opts ...*sql.TxOptions) (*gorm.DB, error) {
tx := tm.db.Begin(opts...)
if tx.Error != nil {
return nil, tx.Error
}
return tx, nil
}
func (tm *TransactionManager) Commit(session *gorm.DB) *gorm.DB {
return session.Commit()
}
func (tm *TransactionManager) Rollback(session *gorm.DB) *gorm.DB {
return session.Rollback()
}