stock update

This commit is contained in:
2026-07-21 16:17:51 +05:30
parent b7af245910
commit 5e998a54eb
3 changed files with 126 additions and 6 deletions

View File

@@ -322,8 +322,12 @@ func (ctl *OrderController) CreateOrderv3(c *fiber.Ctx) error {
order, err := ctl.orderService.CreateOrder(data) order, err := ctl.orderService.CreateOrder(data)
if err != nil { if err != nil {
log.Println("CreateOrder service error:", err) log.Println("CreateOrder service error:", err)
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ statusCode := http.StatusInternalServerError
"code": http.StatusInternalServerError, if strings.Contains(strings.ToLower(err.Error()), "insufficient stock") {
statusCode = http.StatusConflict
}
return c.Status(statusCode).JSON(fiber.Map{
"code": statusCode,
"message": err.Error(), "message": err.Error(),
"status": false, "status": false,
}) })

View File

@@ -38,7 +38,7 @@ func main() {
fmt.Println("🌐 Connecting to databases...") fmt.Println("🌐 Connecting to databases...")
db.Connect() db.Connect()
fmt.Println("✅ Database connections established!") fmt.Println("✅ Database connections established!")
// Ensure schema is updated // Ensure schema is updated
db.DB.AutoMigrate(&models.StockRequest{}) db.DB.AutoMigrate(&models.StockRequest{})

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"nearle/models" "nearle/models"
"strings"
"time" "time"
"gorm.io/gorm" "gorm.io/gorm"
@@ -1008,6 +1009,48 @@ func (r *orderRepository) GetOrderDetails(orderHeaderID int) ([]models.OrderDeta
func (r *orderRepository) UpdateOrder(order *models.Orders) error { func (r *orderRepository) UpdateOrder(order *models.Orders) error {
tx := r.db.Begin() tx := r.db.Begin()
// Handle stock restoration on order cancellation
var existingOrder models.Orders
if err := tx.Where("orderheaderid = ?", order.Orderheaderid).First(&existingOrder).Error; err == nil {
newStatus := strings.ToLower(strings.TrimSpace(order.Orderstatus))
oldStatus := strings.ToLower(strings.TrimSpace(existingOrder.Orderstatus))
if newStatus == "cancelled" && oldStatus != "cancelled" {
var items []models.OrderDetail
if err := tx.Table("orderdetails").Where("orderheaderid = ?", order.Orderheaderid).Find(&items).Error; err == nil {
for _, item := range items {
itemLocID := item.Locationid
if itemLocID == 0 {
itemLocID = existingOrder.Locationid
}
qty := int(item.Orderqty)
if qty <= 0 {
qty = 1
}
restoredStock := models.Productstock{
Tenantid: existingOrder.Tenantid,
Stockdate: time.Now(),
Locationid: itemLocID,
Productid: item.Productid,
Quantity: qty,
Stocktype: "in",
Status: "Active",
}
if err := tx.Table("productstocks").Create(&restoredStock).Error; err != nil {
tx.Rollback()
return err
}
// Update productlocation status back to available if stock is restored
tx.Table("productlocations").
Where("productid = ? AND tenantid = ? AND locationid = ?", item.Productid, existingOrder.Tenantid, itemLocID).
Update("status", "available")
}
}
}
}
if err := tx.Where("orderheaderid = ?", order.Orderheaderid).Updates(order).Error; err != nil { if err := tx.Where("orderheaderid = ?", order.Orderheaderid).Updates(order).Error; err != nil {
tx.Rollback() tx.Rollback()
return err return err
@@ -1082,6 +1125,50 @@ func (r *orderRepository) updateSeqno(tid int, prefix string) error {
func (r *orderRepository) CreateOrder(data models.Orders) (models.Orders, error) { func (r *orderRepository) CreateOrder(data models.Orders) (models.Orders, error) {
tx := r.db.Begin() tx := r.db.Begin()
locID := data.Locationid
if locID == 0 {
locID = data.Applocationid
}
// 🛠️ Step 1: Pre-validate stock availability for all items before placing order
for _, item := range data.Items {
itemLocID := item.Locationid
if itemLocID == 0 {
itemLocID = locID
}
requestedQty := int(item.Orderqty)
if requestedQty <= 0 {
requestedQty = 1
}
var availableStock int
stockQuery := `
SELECT COALESCE(
SUM(CASE WHEN LOWER(stocktype) = 'in' THEN quantity ELSE 0 END) -
SUM(CASE WHEN LOWER(stocktype) = 'out' THEN quantity ELSE 0 END),
0
)
FROM productstocks
WHERE productid = ? AND tenantid = ? AND locationid = ?
`
if err := tx.Raw(stockQuery, item.Productid, data.Tenantid, itemLocID).Scan(&availableStock).Error; err != nil {
tx.Rollback()
return models.Orders{}, fmt.Errorf("failed to verify stock for product %d: %w", item.Productid, err)
}
// If stock tracking exists and available stock is less than requested quantity, block order placement
if availableStock < requestedQty {
tx.Rollback()
pName := item.Productname
if pName == "" {
pName = fmt.Sprintf("ID %d", item.Productid)
}
return models.Orders{}, fmt.Errorf("insufficient stock for product '%s': requested %d, available %d", pName, requestedQty, availableStock)
}
}
// 🛠️ Step 2: Create Order Header
data.Orderid = r.getSequenceno(data.Tenantid, "ORD") data.Orderid = r.getSequenceno(data.Tenantid, "ORD")
if err := tx.Create(&data).Error; err != nil { if err := tx.Create(&data).Error; err != nil {
@@ -1089,22 +1176,32 @@ func (r *orderRepository) CreateOrder(data models.Orders) (models.Orders, error)
return models.Orders{}, err return models.Orders{}, err
} }
// 🛠️ Step 3: Insert Order Details & Record "out" stock deduction
for _, item := range data.Items { for _, item := range data.Items {
item.Orderheaderid = data.Orderheaderid item.Orderheaderid = data.Orderheaderid
item.Tenantid = data.Tenantid item.Tenantid = data.Tenantid
item.Locationid = data.Locationid itemLocID := item.Locationid
if itemLocID == 0 {
itemLocID = locID
}
item.Locationid = itemLocID
if err := tx.Table("orderdetails").Create(&item).Error; err != nil { if err := tx.Table("orderdetails").Create(&item).Error; err != nil {
tx.Rollback() tx.Rollback()
return models.Orders{}, err return models.Orders{}, err
} }
qty := int(item.Orderqty)
if qty <= 0 {
qty = 1
}
stock := models.Productstock{ stock := models.Productstock{
Tenantid: data.Tenantid, Tenantid: data.Tenantid,
Stockdate: time.Now(), Stockdate: time.Now(),
Locationid: data.Locationid, Locationid: itemLocID,
Productid: item.Productid, Productid: item.Productid,
Quantity: int(item.Orderqty), Quantity: qty,
Stocktype: "out", Stocktype: "out",
Status: "Active", Status: "Active",
} }
@@ -1112,6 +1209,25 @@ func (r *orderRepository) CreateOrder(data models.Orders) (models.Orders, error)
tx.Rollback() tx.Rollback()
return models.Orders{}, err return models.Orders{}, err
} }
// Update productlocation status to 'outofstock' if remaining stock drops to 0 or below
var remainingStock int
remStockQuery := `
SELECT COALESCE(
SUM(CASE WHEN LOWER(stocktype) = 'in' THEN quantity ELSE 0 END) -
SUM(CASE WHEN LOWER(stocktype) = 'out' THEN quantity ELSE 0 END),
0
)
FROM productstocks
WHERE productid = ? AND tenantid = ? AND locationid = ?
`
if err := tx.Raw(remStockQuery, item.Productid, data.Tenantid, itemLocID).Scan(&remainingStock).Error; err == nil {
if remainingStock <= 0 {
tx.Table("productlocations").
Where("productid = ? AND tenantid = ? AND locationid = ?", item.Productid, data.Tenantid, itemLocID).
Update("status", "outofstock")
}
}
} }
if err := r.updateSeqno(data.Tenantid, "ORD"); err != nil { if err := r.updateSeqno(data.Tenantid, "ORD"); err != nil {