diff --git a/controllers/orderController.go b/controllers/orderController.go index 16f25a1..a311a35 100644 --- a/controllers/orderController.go +++ b/controllers/orderController.go @@ -303,6 +303,24 @@ func (ctl *OrderController) CreateOrderv3(c *fiber.Ctx) error { } } + // 🛠️ Strategy 3: some clients send the header under "orders" but the line + // items as a SIBLING top-level "items" array rather than nested inside + // it. Strategy 2's OrderWrapper only has an "orders" field, so + // encoding/json silently drops that sibling key — the order header + // parses fine but data.Items ends up empty, which used to let the order + // go through with zero items and skip the stock check entirely (the + // pre-validation loop below iterates over data.Items). Pick it up here + // if strategies 1/2 left Items empty. + if len(data.Items) == 0 { + type ItemsWrapper struct { + Items []models.OrderDetail `json:"items"` + } + var itemsWrapper ItemsWrapper + if err := c.BodyParser(&itemsWrapper); err == nil && len(itemsWrapper.Items) > 0 { + data.Items = itemsWrapper.Items + } + } + // Double check we have the required ID if data.Tenantid == 0 { return c.Status(http.StatusConflict).JSON(fiber.Map{ @@ -312,6 +330,18 @@ func (ctl *OrderController) CreateOrderv3(c *fiber.Ctx) error { }) } + // An order with no line items has nothing to check stock against — the + // pre-validation loop in CreateOrder simply wouldn't run, silently + // creating a phantom header-only order that never deducted stock. + // Reject it outright instead. + if len(data.Items) == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Order must contain at least one item", + "status": false, + }) + } + if strings.TrimSpace(data.Orderdate) == "" { data.Orderdate = time.Now().Format("2006-01-02 15:04:05") }