diff --git a/infra/development.yaml b/infra/development.yaml index 5baf5e8..7e90f8f 100644 --- a/infra/development.yaml +++ b/infra/development.yaml @@ -1,7 +1,7 @@ server: base-url: local-url: - self-order-url: http://localhost:5174 + self-order-url: http://localhost:5173 port: 4000 jwt: @@ -58,4 +58,4 @@ fonnte: fcm: credentials_file: "infra/firebase-service-account.json" - project_id: "your-firebase-project-id" \ No newline at end of file + project_id: "apskel-pos-v2" \ No newline at end of file diff --git a/internal/contract/self_order_contract.go b/internal/contract/self_order_contract.go index 85e0c31..f4e5148 100644 --- a/internal/contract/self_order_contract.go +++ b/internal/contract/self_order_contract.go @@ -97,6 +97,7 @@ type SelfOrderOrderItem struct { PaymentStatus string `json:"payment_status"` IsVoid bool `json:"is_void"` IsRefund bool `json:"is_refund"` + Metadata map[string]interface{} `json:"metadata,omitempty"` Items []SelfOrderOrderLineItem `json:"items,omitempty"` CreatedAt time.Time `json:"created_at"` } diff --git a/internal/handler/self_order_handler.go b/internal/handler/self_order_handler.go index 715e9fa..53a63c3 100644 --- a/internal/handler/self_order_handler.go +++ b/internal/handler/self_order_handler.go @@ -408,6 +408,7 @@ func (h *SelfOrderHandler) GetOrdersBySession(c *gin.Context) { RemainingAmount: o.RemainingAmount, PaymentStatus: string(o.PaymentStatus), IsVoid: o.IsVoid, + Metadata: o.Metadata, IsRefund: o.IsRefund, CreatedAt: o.CreatedAt, } diff --git a/internal/repository/inventory_repository.go b/internal/repository/inventory_repository.go index 36536f0..4fadfa5 100644 --- a/internal/repository/inventory_repository.go +++ b/internal/repository/inventory_repository.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "gorm.io/gorm" + "gorm.io/gorm/clause" ) type InventoryRepository interface { @@ -278,7 +279,12 @@ func (r *InventoryRepositoryImpl) UpdateReorderLevel(ctx context.Context, id uui } func (r *InventoryRepositoryImpl) BulkCreate(ctx context.Context, inventoryItems []*entities.Inventory) error { - return r.db.WithContext(ctx).CreateInBatches(inventoryItems, 100).Error + return r.db.WithContext(ctx). + Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "outlet_id"}, {Name: "product_id"}}, + DoNothing: true, + }). + CreateInBatches(inventoryItems, 100).Error } func (r *InventoryRepositoryImpl) BulkUpdate(ctx context.Context, inventoryItems []*entities.Inventory) error { @@ -301,21 +307,25 @@ func (r *InventoryRepositoryImpl) BulkAdjustQuantity(ctx context.Context, adjust return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { for productID, delta := range adjustments { var inventory entities.Inventory - if err := tx.Where("product_id = ? AND outlet_id = ?", productID, outletID).First(&inventory).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - // Inventory doesn't exist, create it with initial quantity - inventory = entities.Inventory{ - ProductID: productID, - OutletID: outletID, - Quantity: 0, - ReorderLevel: 0, - } - if err := tx.Create(&inventory).Error; err != nil { - return fmt.Errorf("failed to create inventory record for product %s: %w", productID, err) - } - } else { + err := tx.Set("gorm:query_option", "FOR UPDATE"). + Where("product_id = ? AND outlet_id = ?", productID, outletID). + First(&inventory).Error + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { return err } + // Use FirstOrCreate to handle race conditions — avoids duplicate key + // if another transaction already inserted this row concurrently. + inventory = entities.Inventory{ + ProductID: productID, + OutletID: outletID, + Quantity: 0, + ReorderLevel: 0, + } + if err := tx.Where(entities.Inventory{ProductID: productID, OutletID: outletID}). + FirstOrCreate(&inventory).Error; err != nil { + return fmt.Errorf("failed to create inventory record for product %s: %w", productID, err) + } } inventory.UpdateQuantity(delta)