{
  "openapi": "3.1.1",
  "info": {
    "title": "FloBill API",
    "description": "FloBill — Sell. Collect. Flow.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.qa.flobill.in/"
    }
  ],
  "paths": {
    "/api/auth/me": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Current user's permission list — the frontend fetches this once\nafter login (and after switching tenants) to gate nav items and\naction buttons. Deliberately requires only [Authorize], not a\nspecific [RequirePermission] — every authenticated user is allowed\nto know what they themselves can do.\n\nThis is a UX/product-quality endpoint, not a security boundary —\nevery actual action is still enforced server-side by\nRequirePermissionAttribute regardless of what this returns.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentUserResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentUserResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentUserResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/register": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Register a new business account.\nCreates: tenant + 4 default roles + owner user in one transaction.\nReturns JWT tokens on success — user is logged in immediately.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/refresh": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Refresh access token using the HttpOnly cookie refresh token.\nOld refresh token is revoked — new one issued (rotation).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Logout — revokes the refresh token.\nAccess token expires naturally (no server-side blacklist needed at this scale).",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/auth/send-otp": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Send OTP to phone via WhatsApp.\nRate limited: max 3 requests per 10 minutes per phone.\nCreates a user row if this is a new phone (invitation flow).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendOtpRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/SendOtpRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/SendOtpRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/auth/verify-otp": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Verify OTP. Returns:\n- AuthResponse (with JWT) if user belongs to exactly one tenant\n- TenantPickerResponse if user belongs to multiple tenants\n- 401 if OTP invalid or expired",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyOtpRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyOtpRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyOtpRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/auth/register/send-otp": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Send OTP to a phone number during registration (purpose \"register\",\nkept separate from login OTPs so the two flows can't be mixed up).\nDoes NOT check whether the phone is already registered — that's\nhandled by RegisterAsync's own conflict checks.\nRate limited: max 3 requests per 10 minutes per phone.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendOtpRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/SendOtpRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/SendOtpRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/auth/register/verify-otp": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Verify a registration OTP. On success, issues a short-lived\nPhoneVerificationToken that must be supplied to POST /api/auth/register\n— proves the WhatsApp number was actually reachable, preventing\nbot/spam signups with unowned numbers.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyOtpRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyOtpRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyOtpRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/auth/select-tenant": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Select a specific tenant after multi-tenant OTP verification.\nCalled only when verify-otp returns a TenantPickerResponse.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SelectTenantRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/SelectTenantRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/SelectTenantRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/auth/invite/send-otp": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Step 1 of invite flow — staff clicks invite link, frontend sends token.\nValidates invite token and sends OTP to the invited phone number.\nReturns masked phone so frontend can show \"OTP sent to 98****3210\".",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteSendOtpRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteSendOtpRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/InviteSendOtpRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/auth/invite/verify": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Step 2 of invite flow — staff enters OTP to complete invite acceptance.\nValidates invite token + OTP together.\nCreates user account if new, adds tenant membership, returns JWT.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteVerifyRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteVerifyRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/InviteVerifyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/branches": {
      "get": {
        "tags": [
          "Branches"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Branches"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBranchRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBranchRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBranchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/branches/{id}": {
      "get": {
        "tags": [
          "Branches"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Branches"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBranchRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBranchRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBranchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Branches"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/branches/{id}/default": {
      "post": {
        "tags": [
          "Branches"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/brands": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "All brands for the current tenant.",
        "responses": {
          "200": {
            "description": "Returns the list of brands.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BrandDto"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Catalog"
        ],
        "summary": "Create a brand.",
        "requestBody": {
          "description": "Brand name and optional logo URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBrandRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBrandRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Brand created. The response body contains the created brand.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/brands/{id}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Brand detail.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Brand identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandDto"
                }
              }
            }
          },
          "404": {
            "description": "No brand exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Catalog"
        ],
        "summary": "Update a brand.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Brand identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Fields to update. Only supplied fields are changed.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBrandRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBrandRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Brand updated. The response body contains the updated brand.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandDto"
                }
              }
            }
          },
          "404": {
            "description": "No brand exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Catalog"
        ],
        "summary": "Delete a brand.",
        "description": "Deletion is blocked when products are linked to this brand.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Brand identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Brand deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No brand exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Brand cannot be deleted because products reference it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/categories": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "All categories as a hierarchy tree.",
        "description": "Returns root categories with their children nested recursively. Each category\nincludes a `productCount` of directly assigned products.",
        "responses": {
          "200": {
            "description": "Returns the category tree.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CategoryDto"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Catalog"
        ],
        "summary": "Create a category.",
        "description": "To create a root category leave `parentId` null.\nTo create a sub-category set `parentId` to an existing category's id.",
        "requestBody": {
          "description": "Category name, optional parent, description, and sort order.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCategoryRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCategoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Category created. The response body contains the created category.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/categories/{id}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Category detail.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Category identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the category.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryDto"
                }
              }
            }
          },
          "404": {
            "description": "No category exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Catalog"
        ],
        "summary": "Update a category.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Category identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Fields to update. Only supplied fields are changed.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCategoryRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCategoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Category updated. The response body contains the updated category.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryDto"
                }
              }
            }
          },
          "404": {
            "description": "No category exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Catalog"
        ],
        "summary": "Delete a category.",
        "description": "Deletion is blocked when products or sub-categories are linked to this category.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Category identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Category deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No category exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Category cannot be deleted because products or sub-categories reference it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Lists clients for the current tenant.",
        "description": "Returns a paginated, searchable list of B2C and B2B clients. System walk-in\ncontacts are excluded because they are reserved for internal POS use.\n\nSupported filters:\n- search: matches name, email, phone, GSTIN, or city.\n- type: b2c or b2b.\n- isActive: true for active clients, false for inactive clients.\n\npage is clamped to a minimum of 1. pageSize is clamped between 1 and 100.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "description": "Optional search text for client name, email, phone, GSTIN, or city.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "Optional client type filter. Use b2c or b2b.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "Optional active status filter.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the paged client list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfContactListItem"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Clients"
        ],
        "summary": "Creates a client.",
        "description": "Creates either a regular B2C customer or a B2B business client.\n\nContact type rules:\n- b2c: requires name and phone. Email is optional.\n- b2b: requires business contact details and clientProfile with GSTIN and payment terms.\n\nThe system walk-in contact is created internally and cannot be created through this endpoint.",
        "requestBody": {
          "description": "Request cancellation token.",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/CreateContactRequest"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/CreateContactRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client created. The response body contains the created client detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactDetail"
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing or failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/stats": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "KPI strip for the Clients list — total client count, outstanding and\noverdue amounts across all invoices (B2C and B2B alike), and store\ncredit (currently always 0 — see ClientStatsSummary's doc comment).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientStatsSummary"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Gets full client detail.",
        "description": "Returns the base contact fields and, for B2B clients, the associated client\nprofile such as GSTIN, PAN, payment terms, credit limit, and billing/shipping\naddresses. The client profile is null for B2C clients.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Client identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the client detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactDetail"
                }
              }
            }
          },
          "404": {
            "description": "No client exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Clients"
        ],
        "summary": "Updates a client.",
        "description": "Applies a partial update. Only supplied fields are changed.\n\nFor B2B profile updates, send clientProfile with the profile fields to change.\nB2C clients do not require a client profile.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Client identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Request cancellation token.",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/UpdateContactRequest"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/UpdateContactRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Client updated. The response body contains the latest client detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactDetail"
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing or failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No client exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Clients"
        ],
        "summary": "Deletes a client.",
        "description": "Performs a soft delete for the client. Deletion is blocked when the client has\noutstanding invoices. System walk-in contacts cannot be deleted.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Client identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Client deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No client exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Client cannot be deleted because business rules block the operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/summary": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Gets a lightweight client summary.",
        "description": "Designed for invoice creation screens and dashboard lookups. Includes the\ndisplay identity plus outstanding balance and total invoice count.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Client identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the client summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactSummary"
                }
              }
            }
          },
          "404": {
            "description": "No client exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/detail-stats": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Gets aggregate figures for the Client Detail page: overdue amount,\ntotal paid this financial year, and (for the individuals-only\n\"Purchase summary\" card) first purchase date, order count, and\naverage order value.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Client identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the client's detail-page stats.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientDetailStats"
                }
              }
            }
          },
          "404": {
            "description": "No client exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/walk-in": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Gets the tenant's system walk-in contact id.",
        "description": "Used by the invoice creation screen when the customer selects\n\"Walk-in Customer\" — every tenant has exactly one system walk-in\ncontact (created at registration; self-healing if missing) rather\nthan a new contact being created per anonymous sale.",
        "responses": {
          "200": {
            "description": "Returns the walk-in contact id.",
            "content": {
              "application/json": {
                "schema": { }
              }
            }
          }
        }
      }
    },
    "/api/credit-notes/register": {
      "get": {
        "tags": [
          "CreditNoteRegister"
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/credit-notes/register/stats": {
      "get": {
        "tags": [
          "CreditNoteRegister"
        ],
        "summary": "KPI strip for the register — see CreditNoteStatsSummary's\n    doc comment for exactly what each figure includes.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/credits/customers/{contactId}": {
      "get": {
        "tags": [
          "CustomerCredits"
        ],
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/credits/customers/{contactId}/allocate": {
      "post": {
        "tags": [
          "CustomerCredits"
        ],
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AllocateCreditRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/AllocateCreditRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/AllocateCreditRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/daily-closings": {
      "get": {
        "tags": [
          "DailyClosing"
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "DailyClosing"
        ],
        "summary": "Open a new closing period. Covers everything since the last closing\nended (or since registration, if this is the first ever). Sales,\nexpenses, and other income are all auto-computed from existing data\nfor the period — expenses/income are logged via the standalone\n/api/expenses and /api/income endpoints, not through this request.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenDailyClosingRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenDailyClosingRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/OpenDailyClosingRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/daily-closings/stats": {
      "get": {
        "tags": [
          "DailyClosing"
        ],
        "summary": "KPI strip for the Closing History list — see\n    ClosingStatsSummary's doc comment for exactly what each figure\n    includes.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/daily-closings/{id}": {
      "get": {
        "tags": [
          "DailyClosing"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "DailyClosing"
        ],
        "summary": "Update an open closing — sales recalculated live, through now.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDailyClosingRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDailyClosingRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDailyClosingRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/daily-closings/suggestion": {
      "get": {
        "tags": [
          "DailyClosing"
        ],
        "summary": "Returns suggested opening cash and the period a new closing would\ncover, before opening it. Call this before POST /api/daily-closings\nto preview what the next closing will look like.\nsuggestedOpeningCash = actualClosingCash from the last closed period.\nsuggestedPeriodStart = end of the last closed period (or tenant\n  registration time, if this would be the first closing ever).",
        "parameters": [
          {
            "name": "branchId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/daily-closings/current": {
      "get": {
        "tags": [
          "DailyClosing"
        ],
        "summary": "Get the currently open closing for this branch, if any. Returns 404 if none is open.",
        "parameters": [
          {
            "name": "branchId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/daily-closings/{id}/close": {
      "post": {
        "tags": [
          "DailyClosing"
        ],
        "summary": "Close the period — enter actual cash in drawer.\ncashDifference = actual - expected. Positive = excess. Negative = shortage.\nEvery invoice in the period is stamped as reconciled by this closing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloseDayRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CloseDayRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CloseDayRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/daily-closings/{id}/reopen": {
      "post": {
        "tags": [
          "DailyClosing"
        ],
        "summary": "Reopen a closed period for editing. Clears actual cash and difference,\nand releases any invoices this closing had claimed back to unreconciled.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/dashboard/summary": {
      "get": {
        "tags": [
          "Dashboard"
        ],
        "summary": "Returns all dashboard metrics in a single call — today's sales vs yesterday,\ntotal outstanding dues, inventory value, estimated monthly profit,\nlow stock count, and cash closing status.\nAll date windows (today, this month) are in IST.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/dashboard/recent": {
      "get": {
        "tags": [
          "Dashboard"
        ],
        "summary": "Recent invoices, payments, low-stock items, and a merged activity\nfeed for the dashboard widgets. Separate call from summary so the\ntwo can load independently on the frontend.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/dashboard/sales-trend": {
      "get": {
        "tags": [
          "Dashboard"
        ],
        "summary": "Daily sales series for the Sales Overview chart, plus the prior\nperiod's equivalent series for comparison. See SalesTrendResponse's\ndoc comment for exactly what \"prior period\" means per range.",
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "description": "\"7d\", \"30d\", or \"month\" (default).",
            "schema": {
              "type": "string",
              "default": "month"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/dashboard/categories": {
      "get": {
        "tags": [
          "Dashboard"
        ],
        "summary": "Sales broken down by product category for the Top Categories\ndonut. Same range semantics as sales-trend's Current series; no\ncomparison period since this widget only shows one at a time.",
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "description": "\"7d\", \"30d\", or \"month\" (default).",
            "schema": {
              "type": "string",
              "default": "month"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/dashboard/payment-collection": {
      "get": {
        "tags": [
          "Dashboard"
        ],
        "summary": "Total billed vs. collected for the Payment Collection gauge. See\nPaymentCollectionDto's doc comment for exactly what each figure\nincludes.",
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "description": "\"7d\", \"30d\", or \"month\" (default).",
            "schema": {
              "type": "string",
              "default": "month"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/discount-rules": {
      "get": {
        "tags": [
          "Discounts"
        ],
        "summary": "All discount rules with optional filters.",
        "description": "Supports filtering by `appliesTo` scope (`bill`, `product`,\n`category`, or `brand`) and by active status.",
        "parameters": [
          {
            "name": "appliesTo",
            "in": "query",
            "description": "Optional scope filter: bill | product | category | brand.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "true for active rules only; false for inactive rules only.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the list of discount rules.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DiscountRuleDto"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Discounts"
        ],
        "summary": "Create a discount rule.",
        "description": "Discount types: `percent` deducts a percentage; `flat` deducts a fixed amount.\n\nScopes (`appliesTo`):\n- `bill`: applies to the entire invoice total. Requires `minBillAmount` when relevant.\n- `product`: applies only to a specific product. Requires `targetId`.\n- `category`: applies to all products in a category. Requires `targetId`.\n- `brand`: applies to all products of a brand. Requires `targetId`.\n\nScheduling: set `validFrom` / `validUntil` for date ranges. Set `daysOfWeek`\n(1 = Monday … 7 = Sunday) and `timeFrom` / `timeUntil` for time windows.",
        "requestBody": {
          "description": "Discount rule configuration.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDiscountRuleRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDiscountRuleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Rule created. The response body contains the created discount rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountRuleDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/discount-rules/{id}": {
      "get": {
        "tags": [
          "Discounts"
        ],
        "summary": "Discount rule detail.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Discount rule identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the discount rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountRuleDto"
                }
              }
            }
          },
          "404": {
            "description": "No discount rule exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Discounts"
        ],
        "summary": "Update a discount rule.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Discount rule identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Fields to update. Only supplied fields are changed.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDiscountRuleRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDiscountRuleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Rule updated. The response body contains the updated discount rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountRuleDto"
                }
              }
            }
          },
          "404": {
            "description": "No discount rule exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Discounts"
        ],
        "summary": "Delete a discount rule.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Discount rule identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Rule deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No discount rule exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/discount-rules/applicable": {
      "get": {
        "tags": [
          "Discounts"
        ],
        "summary": "Discount rules applicable right now for an invoice context.",
        "description": "Call this when building an invoice to discover which rules apply. The response\nsplits rules into two groups:\n\n- `applied`: rules that match and are flagged `isAutoApply = true`.\n- `suggested`: rules that match but require the retailer to opt in.\n\nPass the relevant context — product, category, brand, and/or bill amount —\nto get the correct set of matching rules.",
        "parameters": [
          {
            "name": "productId",
            "in": "query",
            "description": "Optional product identifier for product-scoped rules.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "description": "Optional category identifier for category-scoped rules.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "brandId",
            "in": "query",
            "description": "Optional brand identifier for brand-scoped rules.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "billAmount",
            "in": "query",
            "description": "Current invoice total, used to match minimum-bill-amount rules. Defaults to 0.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
              "type": [
                "number",
                "string"
              ],
              "format": "double",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the applicable rules split into applied and suggested groups.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApplicableDiscounts"
                }
              }
            }
          }
        }
      }
    },
    "/api/expense-categories": {
      "get": {
        "tags": [
          "ExpenseCategories"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "ExpenseCategories"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExpenseCategoryRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExpenseCategoryRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExpenseCategoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/expense-categories/{id}": {
      "put": {
        "tags": [
          "ExpenseCategories"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateExpenseCategoryRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateExpenseCategoryRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateExpenseCategoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "ExpenseCategories"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/expenses": {
      "get": {
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fromDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "toDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "branchId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Expenses"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExpenseRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExpenseRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExpenseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/expenses/stats": {
      "get": {
        "tags": [
          "Expenses"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/expenses/{id}": {
      "get": {
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateExpenseRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateExpenseRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateExpenseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Basic liveness check — confirms the process is up. This is what\nAWS's load balancer/target group polls on every health-check\ninterval, so it can't require authentication — but that also\nmeans anything it returns is effectively public. Deliberately\nminimal: no product name, version, or timestamp, none of which a\npublic liveness probe needs and all of which help an attacker\nfingerprint the deployment for free.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/health/db": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Deep health check — confirms the API can reach PostgreSQL, plus\nbasic tenant-count diagnostics. Requires authentication (inherits\nthe app's default fallback policy — see Program.cs): use this from\nauthenticated ops/monitoring tooling, not a public uptime checker.\nThe underlying exception can contain connection strings, internal\nhostnames, etc. — logged server-side only, never echoed in the\nresponse even to an authenticated caller.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/income-categories": {
      "get": {
        "tags": [
          "IncomeCategories"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "IncomeCategories"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIncomeCategoryRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIncomeCategoryRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIncomeCategoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/income-categories/{id}": {
      "put": {
        "tags": [
          "IncomeCategories"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIncomeCategoryRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIncomeCategoryRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIncomeCategoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "IncomeCategories"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/income": {
      "get": {
        "tags": [
          "Income"
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fromDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "toDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "branchId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Income"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIncomeRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIncomeRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIncomeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/income/stats": {
      "get": {
        "tags": [
          "Income"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/income/{id}": {
      "get": {
        "tags": [
          "Income"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Income"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIncomeRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIncomeRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIncomeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Income"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoices/{invoiceId}/returns": {
      "post": {
        "tags": [
          "InvoiceReturns"
        ],
        "summary": "Create a DRAFT credit note. Nothing committed until /issue.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceReturnRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceReturnRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceReturnRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "get": {
        "tags": [
          "InvoiceReturns"
        ],
        "summary": "All credit notes for this invoice.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoices/{invoiceId}/returns/{rid}/issue": {
      "post": {
        "tags": [
          "InvoiceReturns"
        ],
        "summary": "Issue draft — commits stock restock, balance reduction, ledger credit.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoices/{invoiceId}/returns/{rid}/refund-online": {
      "post": {
        "tags": [
          "InvoiceReturns"
        ],
        "summary": "Executes the actual money movement for an ISSUED credit note whose\nrefund_mode is \"online\" — refunds the original Cashfree payment\nvia the gateway. Deliberately separate from Issue: a gateway call\ncan fail, be slow, or need retrying independently of whether the\ncredit note's own accounting effects already committed cleanly.\nSafe to retry — Cashfree treats the credit note's return number\n(used as refund_id) as an idempotency key on their side too.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceReturnDto"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceReturnDto"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceReturnDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{invoiceId}/returns/{rid}/cancel": {
      "post": {
        "tags": [
          "InvoiceReturns"
        ],
        "summary": "Cancel with reason. Reverses all effects if already issued.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelCreditNoteRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelCreditNoteRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CancelCreditNoteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoices/{invoiceId}/returns/{rid}": {
      "get": {
        "tags": [
          "InvoiceReturns"
        ],
        "summary": "Single credit note detail with items and allocations.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoices/{invoiceId}/returns/{rid}/pdf": {
      "get": {
        "tags": [
          "InvoiceReturns"
        ],
        "summary": "Download credit note as PDF. Must be issued (not draft).",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoices": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Paginated invoice list with optional filters.\nSupports search by invoice number or contact name.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fromDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "toDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "contactId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "branchId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfInvoiceListItem"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Create a new invoice in draft status.\nGST amounts are calculated automatically from line items.\nInvoice number is auto-generated (e.g. INV-2526-001).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/CreateInvoiceRequest"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/CreateInvoiceRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceDetail"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/summary": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Dashboard summary — draft/sent/paid/overdue counts and amounts.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceSummary"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Full invoice detail with all line items and charges.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Invoices"
        ],
        "summary": "Update a draft invoice — recalculates all amounts.\nOnly draft invoices can be updated.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateInvoiceRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateInvoiceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceDetail"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/send": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Send invoice to customer — changes status from draft to sent.\nAfter sending, the invoice can no longer be edited.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/mark-paid": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Mark invoice as paid — records payment details.\nSupports partial payments (balance_due = total - paid_amount).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/MarkPaidRequest"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/MarkPaidRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceDetail"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/payments": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Payment history for this invoice — every allocation from a\nnon-reversed payment, oldest first. Each entry's paymentLinkId is\nset only for gateway-confirmed (online) payments; null for\nmanually-recorded cash/UPI/card/bank_transfer/cheque entries.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/InvoicePaymentHistory"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/settlement": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Whether this invoice's online payment has actually settled to the\nbank yet — distinct from the invoice being marked paid (Cashfree\ncollecting the money) or the payment link's own PAID status.\n404 is a normal response here, not an error: no PAID link, or\nsettlement just hasn't landed yet (typically 1-2 business days\nafter payment).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SettlementResult"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/payment-link": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Current online payment link status for this invoice, if\n    one has ever been generated — the invoice detail screen's\n    reconciliation view. Never generates one; use the POST endpoint\n    below for that.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLinkDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Generate (or return the existing) online payment link for this invoice.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLinkDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/payment-link/qr": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Generate a dynamic UPI QR for this invoice, for the merchant app's\n\"show QR to customer\" screen. Reuses the same payment link/order\nas GeneratePaymentLink — same permission, since it's the same\nunderlying action (\"let this customer pay online\") via a\ndifferent channel.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentQrDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/void": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Void an invoice — cannot be undone.\nPaid invoices cannot be voided (create a credit note instead).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/pdf": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Stream invoice PDF directly — no storage, generated on demand.\nformat: a4 (default) | delivery\ndelivery = 2-page: delivery slip (page 1) + invoice copy (page 2)\nThermal printing is handled client-side via ESC/POS (QZ Tray / Flutter).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "a4"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/pdf/share": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Generate PDF, upload to S3, return presigned URL valid for 7 days.\nUse for WhatsApp sharing — send URL instead of file bytes.\nformat: a4 (default) | delivery",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "a4"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/api/ledger/customers/{contactId}/outstanding": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Outstanding invoices for a customer.",
        "description": "Returns all open invoices (sent or overdue) with their remaining balance.\nUse this to populate the payment-allocation UI — pass the invoice ids you\nwant to settle to the Payments → Create endpoint.",
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "description": "Client identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the list of outstanding invoices ordered by due date.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OutstandingInvoice"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/ledger/customers/{contactId}": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Full customer ledger with running balance.",
        "description": "Returns all invoices and payments for a customer in chronological order,\neach entry carrying the running balance after that transaction.\nUse this for the Digital Khata view and for generating a customer statement.",
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "description": "Client identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the customer ledger with totals and all entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerLedger"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-gateway/{provider}/status": {
      "get": {
        "tags": [
          "Payment Gateway"
        ],
        "summary": "Current status for a provider — \"not_started\" shape if no account exists yet.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentGatewayStatusDto"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-gateway/{provider}/refresh-status": {
      "post": {
        "tags": [
          "Payment Gateway"
        ],
        "summary": "LIVE Cashfree status check (platform-mode only — no-ops for BYO or\nno-account) — unlike GetStatus above, this actually calls Cashfree\nright now rather than just reading what's already in the database.\nThis is what the \"Refresh status\" button should call.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentGatewayStatusDto"
                }
              }
            }
          },
          "502": {
            "description": "Bad Gateway",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/api/payment-gateway/{provider}/platform-onboarding": {
      "post": {
        "tags": [
          "Payment Gateway"
        ],
        "summary": "Starts Cashfree platform-mode onboarding — creates the sub-merchant\nrecord using business details already on file in Settings.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Onboarding started; returns current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentGatewayStatusDto"
                }
              }
            }
          },
          "409": {
            "description": "An account already exists for this provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Required business details are missing from Settings, or onboarding isn't supported for this provider yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-gateway/{provider}/onboarding-link": {
      "post": {
        "tags": [
          "Payment Gateway"
        ],
        "summary": "Fetches a fresh 1-hour embeddable onboarding link (KYC form) for an\nin-progress platform-mode account — embed the returned link in an\niframe/webview, never hand it to the tenant as a bare URL.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetOnboardingLinkRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/GetOnboardingLinkRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/GetOnboardingLinkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentGatewayStatusDto"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-gateway/{provider}/byo-credentials": {
      "post": {
        "tags": [
          "Payment Gateway"
        ],
        "summary": "Saves and verifies BYO (bring-your-own) gateway credentials. The\nsecret key is verified with a live Cashfree call and encrypted at\nrest before storage — never echoed back in any response.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveByoCredentialsRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/SaveByoCredentialsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Credentials verified and saved; returns current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentGatewayStatusDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Cashfree rejected the credentials, or this account is platform-mode.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-gateway/{provider}/oauth/start": {
      "post": {
        "tags": [
          "Payment Gateway"
        ],
        "summary": "Starts Cashfree OAuth linking — returns the authorization link to\nredirect the tenant's browser to. The callback destination is\nfixed on Cashfree's OAuth App configuration, not passed here.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StartOAuthLinkResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-gateway/{provider}/oauth/unlink": {
      "post": {
        "tags": [
          "Payment Gateway"
        ],
        "summary": "Revokes Cashfree access for an OAuth-linked account. The tenant can reconnect later.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/api/webhooks/cashfree/onboarding": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "MERCHANT_ONBOARDING_STATUS webhook. Always returns 200 once the\nsignature is verified — even for events we can't act on (unknown\nevent type, unrecognized merchant_id) — to avoid Cashfree's retry\nmechanism hammering us for something that will never resolve.\nOnly an invalid/missing signature gets rejected outright.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/cashfree/orders": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "PAYMENT_SUCCESS_WEBHOOK / PAYMENT_FAILED_WEBHOOK / PAYMENT_USER_DROPPED_WEBHOOK,\nplus the Partner-channel MERCHANT_UNLINKED event (a business\ndisconnecting Cashfree from their own dashboard rather than\nthrough FloBill's Settings page). Confirmed payload shape:\n  { \"type\": \"MERCHANT_UNLINKED\", \"event_time\": \"...\", \"version\": \"...\",\n    \"data\": { \"merchant_id\": \"...\", \"merchant_name\": \"...\",\n              \"unlinked_at\": \"...\", \"reason\": \"USER_REVOKED_ACCESS\" } }\n\nVerification key depends on the account's credential mode:\n  - BYO: the tenant's own stored PG secret (Cashfree signs a\n    direct merchant's order/payment webhooks with it).\n  - platform / oauth: the Partner API key — both are Partner-\n    linked merchants from Cashfree's point of view, confirmed\n    against Cashfree's current Partner OAuth docs\n    (https://www.cashfree.com/docs/partners/embedded/oauth-flow):\n    \"Verify webhook signatures using your partner API key\".\n    MERCHANT_UNLINKED is always Partner-channel, since only an\n    OAuth-linked merchant can be unlinked this way.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/payments": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Paginated payment history.",
        "description": "Returns payments for the current tenant, newest first. Filter by\n`contactId` to retrieve a specific customer's payment history.\nFree-text search matches customer name or payment reference.\n\npage is clamped to a minimum of 1. pageSize is clamped between 1 and 100.",
        "parameters": [
          {
            "name": "contactId",
            "in": "query",
            "description": "Optional client identifier to filter to a specific customer's payments.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "paymentMode",
            "in": "query",
            "description": "Optional payment mode filter (cash, upi, card, bank_transfer, cheque, credit_note).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Optional search text matched against customer name or payment reference.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the paged payment list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfPaymentDto"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Record a customer payment and allocate across invoices.",
        "description": "Two allocation modes are supported:\n\n**Auto mode** (`allocationMode = \"auto\"`): provide `invoiceIds` in\npriority order. The system fills each invoice's balance sequentially until the\npayment amount runs out. Any remainder is carried as unallocated credit.\n\n**Manual mode** (`allocationMode = \"manual\"`): provide `allocations` with\nan explicit amount per invoice. Total allocations must not exceed the payment\namount. Any remainder is carried as unallocated credit.\n\nSupported payment modes: `cash`, `upi`, `card`, `bank_transfer`, `cheque`.",
        "requestBody": {
          "description": "Payment details and allocation instructions.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Payment recorded. The response body contains the payment detail with allocations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/payments/stats": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "KPI strip for the Payments list — total collected, this\n    month, total unallocated (overpayments/advances not yet applied to\n    an invoice), and payment count. All-time totals except\n    collectedThisMonth.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentStatsSummary"
                }
              }
            }
          }
        }
      }
    },
    "/api/payments/{id}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Payment detail with allocations.",
        "description": "Returns the full payment record including how the amount was split across invoices.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Payment identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the payment detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDto"
                }
              }
            }
          },
          "404": {
            "description": "No payment exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/payments/{id}/reverse": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Reverse a recorded payment — undoes its effect on every\n    invoice it was allocated to. The payment stays visible in payment\n    history with the reason, for audit transparency; it is not\n    deleted.",
        "description": "Only payments allocated entirely to invoices still in a payable\nstate (paid / partially_paid) can be reversed — if an allocated\ninvoice has since been voided or returned, this call fails naming\nthat invoice, since its own money accounting has already been\nreshaped by that other operation.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Payment identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Reason for the reversal — required, shown on the payment's audit trail.",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/ReversePaymentRequest"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/ReversePaymentRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment reversed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation (reason is required).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No payment exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "The payment is already reversed, or an allocated invoice's status blocks reversal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/products": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Paginated product list with optional filters.",
        "description": "Returns a paged list of products for the current tenant. Supports filtering by\nfree-text search (name, SKU, barcode), category, brand, preferred supplier,\nvariant type, active status, and low-stock flag.\n\npage is clamped to a minimum of 1. pageSize is clamped between 1 and 100.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "description": "Search text matched against product name, SKU, or barcode.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "description": "Filter to a specific category.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "brandId",
            "in": "query",
            "description": "Filter to a specific brand.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "supplierId",
            "in": "query",
            "description": "Filter to a specific preferred supplier.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "hasVariants",
            "in": "query",
            "description": "true for variant products only; false for simple products only.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "true for active products only; false for inactive products only.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "lowStock",
            "in": "query",
            "description": "true to return only products at or below their reorder point.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "name-asc (default) | name-desc | price-asc | price-desc. Unrecognized values fall back to name-asc.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the paged product list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfProductListItem"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Create a product.",
        "description": "Creates either a simple product or a variant product, depending on\n`hasVariants`.\n\nSimple products: set `openingStock` to record the initial quantity.\n\nVariant products: provide `variants` with at least one variant.\nEach variant carries its own SKU, attributes (e.g. Color/Size), and opening stock.\nPrices on variants override the product-level price when set.\n\n`stockBehaviour` controls what happens when stock runs out:\n- `block`: prevents the sale\n- `warn`: shows a warning but allows the sale\n- `allow`: no restriction",
        "requestBody": {
          "description": "Product details to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Product created. The response body contains the full product detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/low-stock": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Products at or below their reorder point.",
        "description": "Returns all products where current stock is at or below the configured reorder\npoint. For variant products, the low-stock variants are included in\n`lowStockVariants`. Use this endpoint for purchase planning.",
        "responses": {
          "200": {
            "description": "Returns the list of low-stock products.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProductListItem"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/products/stats": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "KPI strip for the Inventory list — total/variant product\n    counts and low-stock/out-of-stock counts. See ProductStatsSummary's\n    doc comment for exactly what counts as each.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductStatsSummary"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/unit-issues": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Products whose Sale unit and Stock unit aren't actually connected by a conversion factor.",
        "description": "Cleanup report for products that predate EnsureUnitsConnectedAsync\n(added after unit conversion first shipped) — these would be\nrejected if saved today, but nothing retroactively catches them\nuntil this report is checked or the product happens to be edited.\nSelling one of these in its Sale unit silently deducts the wrong\namount of stock, since the server has no way to convert between\nthe two units. Fix by editing the product (Settings → Units may\nneed a Base unit link added first) so Sale and Stock unit resolve\nto the same conversion chain.",
        "responses": {
          "200": {
            "description": "Returns the list of affected products.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProductUnitIssue"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/products/{id}": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Full product detail with variants and images.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the product detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "404": {
            "description": "No product exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Products"
        ],
        "summary": "Update a product.",
        "description": "Applies a partial update — only supplied fields are changed. Passing null for\nan optional field leaves it unchanged. To clear a nullable field, pass an\nexplicit null.\n\nUse the variant endpoints to add or update individual variants.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Product fields to update.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProductRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProductRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Product updated. The response body contains the updated product detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "404": {
            "description": "No product exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Products"
        ],
        "summary": "Delete a product.",
        "description": "Performs a soft delete. The product is hidden from active listings but its history is preserved.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Product deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No product exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/{id}/variants": {
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Add a variant to an existing product.",
        "description": "The parent product must have `hasVariants = true`. Each variant must carry\na unique combination of attributes (e.g. `{\"Color\":\"Red\",\"Size\":\"M\"}`).\nThe opening stock on the variant is recorded immediately.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Variant details to add.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateVariantRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateVariantRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Variant added. The response body contains the updated product detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No product exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/{id}/variants/{variantId}": {
      "put": {
        "tags": [
          "Products"
        ],
        "summary": "Update a variant.",
        "description": "Partial update — only supplied fields are changed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "variantId",
            "in": "path",
            "description": "Variant identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Variant fields to update.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateVariantRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateVariantRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Variant updated. The response body contains the updated product detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "404": {
            "description": "Product or variant not found for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/{id}/stock/adjustment": {
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Record a manual stock adjustment.",
        "description": "Use for corrections, damage write-offs, or count discrepancies.\n\nPositive `quantity` = stock in. Negative `quantity` = stock out.\n\nFor variant products, supply `variantId` to adjust a specific variant.\nOmitting `variantId` on a variant product will be rejected.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Adjustment details including quantity and unit cost.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StockAdjustmentRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/StockAdjustmentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Adjustment recorded. The response body contains the updated product detail with new stock levels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No product exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/{id}/stock/opening": {
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Set opening stock for a product or its variants.",
        "description": "Used when first configuring a product. Overwrites the existing stock level.\nDo not use this for day-to-day adjustments — use the adjustment endpoint instead.\n\nFor simple products, provide one item with no `variantId`.\nFor variant products, provide one item per variant.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Opening stock quantities and unit costs.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpeningStockRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/OpeningStockRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Opening stock set. The response body contains the updated product detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "404": {
            "description": "No product exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/{id}/stock/ledger": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Stock movement history for a product or specific variant.",
        "description": "Returns all stock movements in reverse-chronological order. Movements are\ncreated by sales (invoice), purchases, manual adjustments, and opening stock entries.\n\nSupply `variantId` to filter to a single variant's history.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Product identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "variantId",
            "in": "query",
            "description": "Optional variant identifier to filter movements for a specific variant.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque token from the previous page's NextCursor.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page. Defaults to 20, capped at 100.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the paged stock movement history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfStockLedgerEntry"
                }
              }
            }
          },
          "404": {
            "description": "No product exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/business-info/{tenantId}": {
      "get": {
        "tags": [
          "Public"
        ],
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/payment-gateway/oauth/cashfree/callback": {
      "post": {
        "tags": [
          "Public"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompleteOAuthLinkRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CompleteOAuthLinkRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CompleteOAuthLinkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/pay/{payToken}": {
      "get": {
        "tags": [
          "Public"
        ],
        "parameters": [
          {
            "name": "payToken",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/pay/{payToken}/refresh": {
      "post": {
        "tags": [
          "Public"
        ],
        "summary": "Live status check — what the pay page's \"check status\" action calls after checkout.",
        "parameters": [
          {
            "name": "payToken",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-orders": {
      "get": {
        "tags": [
          "PurchaseOrders"
        ],
        "parameters": [
          {
            "name": "supplierId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "PurchaseOrders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseOrderRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseOrderRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseOrderRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-orders/stats": {
      "get": {
        "tags": [
          "PurchaseOrders"
        ],
        "summary": "KPI strip for the Purchase Orders list — see\n    PurchaseOrderStatsSummary's doc comment for exactly what's\n    included/excluded.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-orders/{id}": {
      "get": {
        "tags": [
          "PurchaseOrders"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "PurchaseOrders"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePurchaseOrderRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePurchaseOrderRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePurchaseOrderRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-orders/{id}/send": {
      "post": {
        "tags": [
          "PurchaseOrders"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-orders/{id}/cancel": {
      "post": {
        "tags": [
          "PurchaseOrders"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-returns/{id}": {
      "get": {
        "tags": [
          "PurchaseReturns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-returns": {
      "post": {
        "tags": [
          "PurchaseReturns"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseReturnRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseReturnRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseReturnRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchase-returns/{id}/confirm": {
      "post": {
        "tags": [
          "PurchaseReturns"
        ],
        "summary": "Confirm return — reduces stock and generates debit note.\nSupplier outstanding balance updated automatically.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchases": {
      "get": {
        "tags": [
          "Purchases"
        ],
        "parameters": [
          {
            "name": "supplierId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "branchId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Purchases"
        ],
        "summary": "Create a purchase (GRN). Can be:\n- Direct purchase: no purchaseOrderId\n- Against a PO: provide purchaseOrderId — received quantities update PO status\nStock is NOT updated until confirmed.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePurchaseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchases/stats": {
      "get": {
        "tags": [
          "Purchases"
        ],
        "summary": "KPI strip for the Purchases (GRN) list — see\n    PurchaseStatsSummary's doc comment for why drafts are excluded.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchases/{id}": {
      "get": {
        "tags": [
          "Purchases"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Purchases"
        ],
        "summary": "Edit a draft purchase — items, supplier invoice number, purchase\ndate, and notes. Only draft purchases can be edited. Does not\nchange any payment already recorded on it — use the supplier\npayment reversal endpoint to correct that first if needed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePurchaseRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePurchaseRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePurchaseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchases/{id}/cancel": {
      "post": {
        "tags": [
          "Purchases"
        ],
        "summary": "Cancel a draft purchase. Only draft purchases can be cancelled — a\nconfirmed purchase has already updated stock; use a purchase\nreturn instead. If a payment was recorded on the purchase, reverse\nit first.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/purchases/{id}/confirm": {
      "post": {
        "tags": [
          "Purchases"
        ],
        "summary": "Confirm purchase — updates stock levels and records stock_ledger entries.\nCannot be undone — use purchase return if items need to go back.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/roles": {
      "get": {
        "tags": [
          "Roles"
        ],
        "summary": "All roles for this tenant, with permission and user counts.",
        "responses": {
          "200": {
            "description": "Returns the role list, system roles first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RoleSummary"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Roles"
        ],
        "summary": "Create a custom role.",
        "requestBody": {
          "description": "Role name, optional description, and initial permission IDs.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRoleRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRoleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Role created. The response body contains the created role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoleDetail"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "A role with this name already exists for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/roles/permissions": {
      "get": {
        "tags": [
          "Roles"
        ],
        "summary": "Full catalog of assignable permissions, grouped by module.",
        "description": "Used to populate the permission checkboxes when creating or editing a role.\nPermissions are global (not tenant-scoped) — every tenant sees the same catalog.",
        "responses": {
          "200": {
            "description": "Returns every permission in the system.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PermissionItem"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/roles/{id}": {
      "get": {
        "tags": [
          "Roles"
        ],
        "summary": "Role detail, including the full list of assigned permissions.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Role identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the role and its permissions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoleDetail"
                }
              }
            }
          },
          "404": {
            "description": "No role exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Roles"
        ],
        "summary": "Update a custom role's name, description, and permissions.",
        "description": "Permissions are replaced wholesale — send the full desired set, not a diff.\nSystem roles (e.g. Owner) cannot be modified and return 422.\nEvery user currently holding this role has their cached permissions\ninvalidated immediately, so the new permission set takes effect on\ntheir next request rather than after the cache TTL.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Role identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Full replacement name, description, and permission set.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRoleRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRoleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Role updated. The response body contains the updated role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoleDetail"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No role exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Another role with this name already exists for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "The role is a system role and cannot be modified.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Roles"
        ],
        "summary": "Delete a custom role.",
        "description": "Blocked in two cases:\n- The role is a system role (e.g. Owner) → 422.\n- The role still has active users assigned → 409. Reassign those\n  users to a different role first.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Role identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Role deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No role exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "The role has users assigned and cannot be deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "The role is a system role and cannot be deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/settings": {
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Get all tenant settings.\nReturns defaults for any setting not yet configured.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Settings"
        ],
        "summary": "Partial update — only pass the fields you want to change.\nAll fields are optional. Omitted fields retain their current value.\n\nNote: Changing invoicePrefix also updates the invoice number sequence prefix.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSettingsRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSettingsRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSettingsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/suppliers/{supplierId}/ledger": {
      "get": {
        "tags": [
          "SupplierLedger"
        ],
        "summary": "Full supplier ledger — all purchases, payments, returns with running balance.",
        "parameters": [
          {
            "name": "supplierId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/suppliers/{supplierId}/outstanding": {
      "get": {
        "tags": [
          "SupplierLedger"
        ],
        "summary": "Outstanding purchases — confirmed but not fully paid.",
        "parameters": [
          {
            "name": "supplierId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/supplier-payments": {
      "get": {
        "tags": [
          "SupplierPayments"
        ],
        "parameters": [
          {
            "name": "supplierId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "SupplierPayments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSupplierPaymentRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSupplierPaymentRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSupplierPaymentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/supplier-payments/{id}/reverse": {
      "post": {
        "tags": [
          "SupplierPayments"
        ],
        "summary": "Reverse a recorded supplier payment — undoes its effect on\n    the linked purchase's balance (if any) and the supplier's\n    outstanding balance. The payment stays visible in payment history\n    with the reason, for audit transparency; it is not deleted.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/ReverseSupplierPaymentRequest"
                  }
                ]
              }
            },
            "text/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/ReverseSupplierPaymentRequest"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/ReverseSupplierPaymentRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/suppliers": {
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Paginated supplier list with optional search and active-status filter.",
        "description": "Returns suppliers for the current tenant. Free-text search matches name,\nemail, GSTIN, and phone. page is clamped to minimum 1; pageSize is clamped\nbetween 1 and 100.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "description": "Optional search text matched against name, email, GSTIN, or phone.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "Optional active status filter.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the paged supplier list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfSupplierDto"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Create a supplier.",
        "requestBody": {
          "description": "Supplier contact details, GSTIN, payment terms, and credit limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSupplierRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSupplierRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Supplier created. The response body contains the created supplier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SupplierDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/suppliers/stats": {
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "KPI strip for the Suppliers list — see SupplierStatsSummary's\n    doc comment for exactly how each figure is derived.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SupplierStatsSummary"
                }
              }
            }
          }
        }
      }
    },
    "/api/suppliers/{id}": {
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Supplier detail.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Supplier identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the supplier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SupplierDto"
                }
              }
            }
          },
          "404": {
            "description": "No supplier exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Update a supplier.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Supplier identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Fields to update. Only supplied fields are changed.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSupplierRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSupplierRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Supplier updated. The response body contains the updated supplier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SupplierDto"
                }
              }
            }
          },
          "404": {
            "description": "No supplier exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Delete a supplier.",
        "description": "Soft delete. Deletion is blocked when products list this supplier as their preferred supplier.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Supplier identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Supplier deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No supplier exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Supplier cannot be deleted because products reference it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/units": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "All units of measurement for the current tenant.",
        "responses": {
          "200": {
            "description": "Returns the list of units.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/UnitDto"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Catalog"
        ],
        "summary": "Create a unit of measurement.",
        "description": "A unit can optionally reference a purchase unit with a conversion factor.\nFor example, a sale unit of \"Piece\" with a purchase unit of \"Box\" and a\n`conversionFactor` of 12 means one Box contains 12 Pieces.\n\nSet `purchaseUnitId` to null and `conversionFactor` to 1 when the\nsale and purchase unit are the same.",
        "requestBody": {
          "description": "Unit name, abbreviation, and optional purchase-unit conversion.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUnitRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUnitRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Unit created. The response body contains the created unit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnitDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/units/{id}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Unit detail.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unit identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the unit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnitDto"
                }
              }
            }
          },
          "404": {
            "description": "No unit exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Catalog"
        ],
        "summary": "Update a unit of measurement.",
        "description": "Fields to update. Only supplied fields are changed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unit identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Fields to update.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUnitRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUnitRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Unit updated. The response body contains the updated unit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnitDto"
                }
              }
            }
          },
          "404": {
            "description": "No unit exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "A unit with the supplied name already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "A unit cannot be set as its own purchase unit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Catalog"
        ],
        "summary": "Delete a unit.",
        "description": "Deletion is blocked when products reference this unit as their sale or purchase unit.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unit identifier.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Unit deleted successfully.",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "No unit exists for this tenant with the supplied id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unit cannot be deleted because products reference it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/users": {
      "get": {
        "tags": [
          "Users"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/users/{id}/role": {
      "put": {
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTenantUserRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTenantUserRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTenantUserRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/users/{id}/deactivate": {
      "post": {
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/users/{id}/reactivate": {
      "post": {
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/users/invitations": {
      "get": {
        "tags": [
          "Users"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/users/invite": {
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Invite a staff member by phone number.\nSends an OTP via WhatsApp — staff verifies OTP to accept invite and log in.\nIf the phone already has a FloBill account, they are added to this tenant.\nIf not, a new account is created when they verify the OTP.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteUserRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteUserRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/InviteUserRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/users/invitations/{id}": {
      "delete": {
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/users/{id}/branches": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "Branch IDs this user is restricted to. Empty list means\n    unrestricted — access to every branch they have permission for.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Users"
        ],
        "summary": "Replaces the full set of branch restrictions for this user.\n    Pass an empty array to remove all restrictions.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "text/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "application/*+json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AllocateCreditRequest": {
        "type": "object",
        "properties": {
          "customerCreditId": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "POST /api/credits/customers/{contactId}/allocate"
      },
      "AllocationDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceId": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "invoiceTotal": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "allocatedAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "invoiceStatus": {
            "type": "string"
          }
        }
      },
      "ApplicableDiscounts": {
        "required": [
          "applied",
          "suggested"
        ],
        "type": "object",
        "properties": {
          "applied": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiscountRuleDto"
            }
          },
          "suggested": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiscountRuleDto"
            }
          }
        },
        "description": "Returned by DiscountService when an invoice is being created.\nApplied = auto-applied. Suggested = retailer can activate."
      },
      "AuthResponse": {
        "required": [
          "accessToken",
          "refreshToken",
          "accessTokenExpiry",
          "user"
        ],
        "type": "object",
        "properties": {
          "accessToken": {
            "type": "string"
          },
          "refreshToken": {
            "type": "string"
          },
          "accessTokenExpiry": {
            "type": "string",
            "format": "date-time"
          },
          "user": {
            "$ref": "#/components/schemas/UserInfo"
          }
        },
        "description": "Returned on successful registration or login.\nClient stores AccessToken in memory and RefreshToken in HttpOnly cookie."
      },
      "BrandDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "logoUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "productCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "CancelCreditNoteRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string"
          }
        },
        "description": "POST /api/invoices/{id}/returns/{rid}/cancel"
      },
      "CategoryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "parentId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "parentName": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isActive": {
            "type": "boolean"
          },
          "productCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CategoryDto"
            }
          }
        }
      },
      "ClientDetailStats": {
        "type": "object",
        "properties": {
          "overdueAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "description": "Sum of balance_due across this client's invoices with status = overdue.",
            "format": "double"
          },
          "totalPaidThisFY": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "description": "Sum of payments.amount for this client, dated within the tenant's current financial year (per tenant_settings.financial_year_start_month).",
            "format": "double"
          },
          "firstPurchaseDate": {
            "type": [
              "null",
              "string"
            ],
            "description": "Earliest invoice_date across this client's non-draft, non-void invoices, or null if they have none yet.",
            "format": "date"
          },
          "totalOrders": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Count of this client's non-draft, non-void invoices.",
            "format": "int32"
          },
          "averageOrderValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "description": "Average total_amount across this client's non-draft, non-void invoices, 0 if they have none.",
            "format": "double"
          }
        },
        "description": "Aggregate figures shown on the Client Detail page that don't belong on\nContactSummary (which stays lightweight for invoice-creation lookups).\nBacks the \"Overdue amount\" / \"Total paid this FY\" KPIs shown for every\nclient, and the individuals-only \"Purchase summary\" card.\n\nTotalOrders/FirstPurchaseDate/AverageOrderValue all exclude draft and\nvoid invoices — a draft hasn't been issued to the customer yet and a\nvoid one was cancelled, so neither is \"a real order\" — matching how\nthis same distinction is made everywhere else in the app (Invoices\nlist's own totalInvoiced, Purchase stats, etc.). FirstPurchaseDate\nuses invoice_date (the business-chosen, editable date on the invoice)\nrather than created_at (the technical row-creation timestamp), since\nan invoice can be backdated to reflect when a sale actually happened.\n\nTotalPaidThisFY's boundary respects the tenant's own configured\nfinancial year start month (tenant_settings.financial_year_start_month,\ndefaults to April but not hardcoded — see GetDetailStatsAsync).\n\nSealed class with init-only properties, not a positional record — this\nis mapped straight from a Dapper query (see ContactRepository.GetDetailStatsAsync),\nand positional-record constructor matching bypasses MatchNamesWithUnderscores."
      },
      "ClientProfileDetail": {
        "required": [
          "id",
          "gstin",
          "pan",
          "paymentTermsDays",
          "creditLimit",
          "outstandingBalance",
          "billingAddressLine1",
          "billingAddressLine2",
          "billingCity",
          "billingState",
          "billingPincode",
          "shippingSameAsBilling",
          "shippingAddressLine1",
          "shippingAddressLine2",
          "shippingCity",
          "shippingState",
          "shippingPincode"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Client profile identifier.",
            "format": "uuid"
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ],
            "description": "GSTIN for the business client."
          },
          "pan": {
            "type": [
              "null",
              "string"
            ],
            "description": "PAN for the business client."
          },
          "paymentTermsDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Default payment terms in days.",
            "format": "int32"
          },
          "creditLimit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "description": "Maximum credit limit allowed for the client.",
            "format": "double"
          },
          "outstandingBalance": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "description": "Current outstanding balance for the client.",
            "format": "double"
          },
          "billingAddressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing address primary line."
          },
          "billingAddressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing address secondary line."
          },
          "billingCity": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing city."
          },
          "billingState": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing state."
          },
          "billingPincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing postal PIN code."
          },
          "shippingSameAsBilling": {
            "type": "boolean",
            "description": "True when the shipping address mirrors the billing address."
          },
          "shippingAddressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping address primary line."
          },
          "shippingAddressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping address secondary line."
          },
          "shippingCity": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping city."
          },
          "shippingState": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping state."
          },
          "shippingPincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping postal PIN code."
          }
        },
        "description": "Full B2B client profile returned with client details."
      },
      "ClientStatsSummary": {
        "type": "object",
        "properties": {
          "totalClients": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "outstandingAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "overdueAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "storeCreditAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        },
        "description": "Powers the Clients list's KPI strip. TotalClients excludes the tenant's\nsystem walk-in contact, same as the paginated list.\n\nOutstandingAmount and OverdueAmount are derived live from invoices\n(SUM of balance_due for sent+overdue, and overdue-only, respectively) —\ntenant-wide across both B2C and B2B clients. This deliberately does NOT\nsum client_profiles.outstanding_balance: that column only exists for\nB2B contacts (LEFT JOIN, null for individuals — see\nContactListItem.OutstandingBalance's own doc comment), so summing it\nwould silently under-count by excluding every B2C client's unpaid\ninvoices."
      },
      "CloseDayRequest": {
        "type": "object",
        "properties": {
          "actualClosingCash": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "Close the day — retailer enters actual cash in drawer.\nSystem computes difference vs expected."
      },
      "CompleteOAuthLinkRequest": {
        "required": [
          "state",
          "code",
          "merchantId"
        ],
        "type": "object",
        "properties": {
          "state": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "merchantId": {
            "type": "string"
          }
        },
        "description": "What the frontend's public callback page posts after reading Cashfree's redirect query params."
      },
      "ContactDetail": {
        "required": [
          "id",
          "contactType",
          "name",
          "email",
          "phone",
          "addressLine1",
          "addressLine2",
          "city",
          "state",
          "pincode",
          "notes",
          "tags",
          "isSystem",
          "isActive",
          "createdAt",
          "updatedAt",
          "preferredLanguage",
          "clientProfile"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Client identifier.",
            "format": "uuid"
          },
          "contactType": {
            "type": "string",
            "description": "Client type, such as b2c, b2b, or internal walkin."
          },
          "name": {
            "type": "string",
            "description": "Client display name or business name."
          },
          "email": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client email address."
          },
          "phone": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client phone number."
          },
          "addressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Primary address line."
          },
          "addressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Secondary address line."
          },
          "city": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client city."
          },
          "state": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client state."
          },
          "pincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client postal PIN code."
          },
          "notes": {
            "type": [
              "null",
              "string"
            ],
            "description": "Internal notes about the client."
          },
          "tags": {
            "type": [
              "null",
              "string"
            ],
            "description": "Comma-separated tags."
          },
          "isSystem": {
            "type": "boolean",
            "description": "True for system-managed contacts."
          },
          "isActive": {
            "type": "boolean",
            "description": "Whether the client is active."
          },
          "createdAt": {
            "type": "string",
            "description": "Client creation timestamp.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "description": "Client last update timestamp.",
            "format": "date-time"
          },
          "preferredLanguage": {
            "type": [
              "null",
              "string"
            ],
            "description": "Customer's preferred language for WhatsApp notifications. Nullable — falls back to tenant language."
          },
          "clientProfile": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "B2B profile details. Null for B2C and walk-in contacts.",
                "$ref": "#/components/schemas/ClientProfileDetail"
              }
            ]
          }
        },
        "description": "Full client detail returned by create, get, and update endpoints."
      },
      "ContactListItem": {
        "required": [
          "id",
          "contactType",
          "name",
          "email",
          "phone",
          "city",
          "gstin",
          "outstandingBalance",
          "isActive",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Client identifier.",
            "format": "uuid"
          },
          "contactType": {
            "type": "string",
            "description": "Client type, such as b2c or b2b."
          },
          "name": {
            "type": "string",
            "description": "Client display name or business name."
          },
          "email": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client email address."
          },
          "phone": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client phone number."
          },
          "city": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client city."
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ],
            "description": "GSTIN when the client is B2B; otherwise null."
          },
          "outstandingBalance": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "description": "Outstanding balance when the client is B2B; otherwise null.",
            "format": "double"
          },
          "isActive": {
            "type": "boolean",
            "description": "Whether the client is active."
          },
          "createdAt": {
            "type": "string",
            "description": "Client creation timestamp.",
            "format": "date-time"
          }
        },
        "description": "Lightweight client item returned by the paginated clients list."
      },
      "ContactSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": [
              "null",
              "string"
            ]
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "outstandingBalance": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalInvoices": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "CreateBranchRequest": {
        "required": [
          "name",
          "code",
          "address",
          "phone",
          "gstin"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "code": {
            "type": [
              "null",
              "string"
            ]
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "phone": {
            "type": [
              "null",
              "string"
            ]
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateBrandRequest": {
        "required": [
          "name",
          "logoUrl"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "logoUrl": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateCategoryRequest": {
        "required": [
          "name",
          "parentId",
          "description"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "parentId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32",
            "default": 0
          }
        }
      },
      "CreateClientProfileRequest": {
        "required": [
          "gstin",
          "pan",
          "paymentTermsDays",
          "creditLimit",
          "billingAddressLine1",
          "billingAddressLine2",
          "billingCity",
          "billingState",
          "billingPincode",
          "shippingSameAsBilling",
          "shippingAddressLine1",
          "shippingAddressLine2",
          "shippingCity",
          "shippingState",
          "shippingPincode"
        ],
        "type": "object",
        "properties": {
          "gstin": {
            "type": [
              "null",
              "string"
            ],
            "description": "GSTIN for the business client."
          },
          "pan": {
            "type": [
              "null",
              "string"
            ],
            "description": "PAN for the business client."
          },
          "paymentTermsDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Default payment terms in days.",
            "format": "int32"
          },
          "creditLimit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "description": "Maximum credit limit allowed for the client.",
            "format": "double"
          },
          "billingAddressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing address primary line."
          },
          "billingAddressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing address secondary line."
          },
          "billingCity": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing city."
          },
          "billingState": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing state."
          },
          "billingPincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Billing postal PIN code."
          },
          "shippingSameAsBilling": {
            "type": "boolean",
            "description": "True when the shipping address should mirror the billing address."
          },
          "shippingAddressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping address primary line."
          },
          "shippingAddressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping address secondary line."
          },
          "shippingCity": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping city."
          },
          "shippingState": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping state."
          },
          "shippingPincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shipping postal PIN code."
          }
        },
        "description": "B2B client profile fields used when creating a business client."
      },
      "CreateContactRequest": {
        "required": [
          "contactType",
          "name",
          "email",
          "phone",
          "addressLine1",
          "addressLine2",
          "city",
          "state",
          "pincode",
          "notes",
          "tags",
          "clientProfile"
        ],
        "type": "object",
        "properties": {
          "contactType": {
            "type": "string",
            "description": "Client type. Supported public values are b2c and b2b."
          },
          "name": {
            "type": "string",
            "description": "Client display name or business name."
          },
          "email": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional email address."
          },
          "phone": {
            "type": [
              "null",
              "string"
            ],
            "description": "Phone number. Required for B2C clients."
          },
          "addressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Primary address line."
          },
          "addressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Secondary address line."
          },
          "city": {
            "type": [
              "null",
              "string"
            ],
            "description": "City for the contact address."
          },
          "state": {
            "type": [
              "null",
              "string"
            ],
            "description": "State for the contact address."
          },
          "pincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Postal PIN code."
          },
          "notes": {
            "type": [
              "null",
              "string"
            ],
            "description": "Internal notes about the client."
          },
          "tags": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional comma-separated tags."
          },
          "clientProfile": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "B2B profile details. Required when ContactType is b2b.",
                "$ref": "#/components/schemas/CreateClientProfileRequest"
              }
            ]
          }
        },
        "description": "Request body for creating a B2C or B2B client."
      },
      "CreateDiscountRuleRequest": {
        "required": [
          "name",
          "discountType",
          "discountValue",
          "appliesTo",
          "targetId",
          "isAutoApply",
          "isStackable",
          "priority",
          "maxDiscountAmount",
          "minBillAmount",
          "minQty",
          "validFrom",
          "validUntil",
          "daysOfWeek",
          "timeFrom",
          "timeUntil"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "discountType": {
            "type": "string"
          },
          "discountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "appliesTo": {
            "type": "string"
          },
          "targetId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "isAutoApply": {
            "type": "boolean"
          },
          "isStackable": {
            "type": "boolean"
          },
          "priority": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "maxDiscountAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "minBillAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "minQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "validFrom": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "validUntil": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "daysOfWeek": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          "timeFrom": {
            "type": [
              "null",
              "string"
            ]
          },
          "timeUntil": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateExpenseCategoryRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "defaultAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "defaultPaymentMode": {
            "type": "string"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "CreateExpenseRequest": {
        "type": "object",
        "properties": {
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "categoryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "expenseDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client-generated key that makes this call idempotent for retries."
          }
        }
      },
      "CreateIncomeCategoryRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "defaultAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "defaultPaymentMode": {
            "type": "string"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "CreateIncomeRequest": {
        "type": "object",
        "properties": {
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "categoryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "incomeDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client-generated key that makes this call idempotent for retries."
          }
        }
      },
      "CreateInvoiceChargeRequest": {
        "required": [
          "description",
          "amount",
          "isTaxable",
          "gstRate",
          "sortOrder"
        ],
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "isTaxable": {
            "type": "boolean"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "CreateInvoiceItemRequest": {
        "required": [
          "productId",
          "variantId",
          "description",
          "hsnSacCode",
          "quantity",
          "unit",
          "unitId",
          "unitPrice",
          "gstRate"
        ],
        "type": "object",
        "properties": {
          "productId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "hsnSacCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "quantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "unitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "unitPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "discountType": {
            "type": [
              "null",
              "string"
            ]
          },
          "discountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double",
            "default": 0
          },
          "isDiscountExempt": {
            "type": "boolean",
            "default": false
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32",
            "default": 0
          }
        },
        "description": "A single line item on an invoice.\n\nProductId is optional — line items for non-catalogued goods (custom\ncharges, ad-hoc services) can be created without a product link.\nWhen ProductId is provided, stock is deducted from the product (or\nspecific variant if VariantId is also set) when the invoice is sent.\n\nUnitId selects which of the product's compatible units (see\nProductDetail.AvailableUnits) this item is being sold in — e.g. Gram\ninstead of the product's Kilogram stock unit. The server converts\nQuantity into the product's stock unit before deducting stock; it\ndoes NOT trust a client-computed converted quantity. Leave null for\nad-hoc items (no ProductId) or when selling in the product's own\nstock unit — either way Quantity is then used as-is."
      },
      "CreateInvoiceRequest": {
        "required": [
          "contactId",
          "branchId",
          "invoiceType",
          "gstType",
          "invoiceDate",
          "dueDate",
          "billDiscountType",
          "billDiscountValue",
          "notes",
          "termsConditions",
          "roundOffMode",
          "items",
          "charges"
        ],
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceType": {
            "type": "string"
          },
          "gstType": {
            "type": [
              "null",
              "string"
            ]
          },
          "invoiceDate": {
            "type": "string",
            "format": "date"
          },
          "dueDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "billDiscountType": {
            "type": [
              "null",
              "string"
            ]
          },
          "billDiscountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "termsConditions": {
            "type": [
              "null",
              "string"
            ]
          },
          "roundOffMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreateInvoiceItemRequest"
            }
          },
          "charges": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CreateInvoiceChargeRequest"
            }
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateInvoiceReturnItemRequest": {
        "type": "object",
        "properties": {
          "invoiceItemId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required for sales_return. Null for financial_adjustment.",
            "format": "uuid"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "unitPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "returnQuantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "CreateInvoiceReturnRequest": {
        "type": "object",
        "properties": {
          "returnDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "branchId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Branch the return is physically being processed at — may differ\nfrom the original invoice's branch (customer bought at Branch 1,\nreturns at Branch 2). Stock is restocked here at Issue, not at the\ninvoice's branch. Null defaults to the invoice's own branch,\npreserving the old same-branch assumption for callers that don't\nspecify it.",
            "format": "uuid"
          },
          "creditNoteType": {
            "type": "string",
            "description": "sales_return | financial_adjustment"
          },
          "reason": {
            "type": [
              "null",
              "string"
            ]
          },
          "refundMode": {
            "type": "string",
            "description": "credit_note | cash | upi | card | bank_transfer\nApplied at Issue time, not at Create."
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreateInvoiceReturnItemRequest"
            }
          }
        },
        "description": "POST /api/invoices/{id}/returns — creates a DRAFT credit note.\nNothing commits until POST .../issue is called.\n\ncredit_note_type:\n  sales_return         — goods returned, inventory restocked on issue\n  financial_adjustment — no inventory (pricing correction, goodwill, etc.)"
      },
      "CreatePaymentRequest": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "paymentReference": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentDate": {
            "type": "string",
            "format": "date"
          },
          "allocationMode": {
            "type": "string"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client-generated key that makes payment creation idempotent for retries."
          },
          "invoiceIds": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "allocations": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PaymentAllocationRequest"
            }
          },
          "paymentLinkId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Set only for gateway-confirmed payments — links this\n            payment row back to the payment_links row that produced it\n            (payments.payment_link_id, added by M061). Null for every\n            manually-entered payment (cash/UPI/card at counter, bank transfer).",
            "format": "uuid"
          }
        },
        "description": "Record a customer payment with allocation across invoices.\n\nallocationMode = \"auto\":\n  Provide invoiceIds in priority order. System fills each invoice's\n  balance sequentially until payment amount runs out.\n\nallocationMode = \"manual\":\n  Provide allocations with exact amount per invoice.\n  Total allocations must not exceed payment amount."
      },
      "CreateProductRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "barcode": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "categoryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "brandId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "preferredSupplierId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "saleUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "purchaseUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "mrp": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sellingPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "costPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "hsnSacCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "hasVariants": {
            "type": "boolean"
          },
          "productType": {
            "type": "string"
          },
          "stockBehaviour": {
            "type": "string"
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "openingStock": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "variants": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CreateVariantRequest"
            }
          },
          "branchId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Branch opening stock (simple product) or each variant's opening\nstock (see CreateVariantRequest — variants don't carry their own\nBranchId, they all share this one) lands at. Null defaults to the\ntenant's default branch, for callers that don't specify one.",
            "format": "uuid"
          }
        }
      },
      "CreatePurchaseItemRequest": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "unitCost": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "CreatePurchaseOrderItemRequest": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "orderedQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "unitCost": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "CreatePurchaseOrderRequest": {
        "type": "object",
        "properties": {
          "supplierId": {
            "type": "string",
            "format": "uuid"
          },
          "orderDate": {
            "type": "string",
            "format": "date"
          },
          "expectedDeliveryDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreatePurchaseOrderItemRequest"
            }
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client-generated key that makes this call idempotent for retries."
          }
        }
      },
      "CreatePurchaseRequest": {
        "type": "object",
        "properties": {
          "supplierId": {
            "type": "string",
            "format": "uuid"
          },
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "purchaseOrderId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "supplierInvoiceNumber": {
            "type": [
              "null",
              "string"
            ]
          },
          "purchaseDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "paidAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentReference": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreatePurchaseItemRequest"
            }
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client-generated key that makes purchase creation idempotent for retries."
          }
        }
      },
      "CreatePurchaseReturnItemRequest": {
        "type": "object",
        "properties": {
          "purchaseItemId": {
            "type": "string",
            "format": "uuid"
          },
          "productId": {
            "type": "string",
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "quantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "unitCost": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "CreatePurchaseReturnRequest": {
        "type": "object",
        "properties": {
          "purchaseId": {
            "type": "string",
            "format": "uuid"
          },
          "returnDate": {
            "type": "string",
            "format": "date"
          },
          "reason": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreatePurchaseReturnItemRequest"
            }
          }
        }
      },
      "CreateRoleRequest": {
        "required": [
          "name",
          "description",
          "permissionIds"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "permissionIds": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "CreateSupplierPaymentRequest": {
        "type": "object",
        "properties": {
          "supplierId": {
            "type": "string",
            "format": "uuid"
          },
          "purchaseId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "paymentReference": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ],
            "description": "Client-generated key that makes this call idempotent for retries."
          }
        }
      },
      "CreateSupplierRequest": {
        "required": [
          "name",
          "phone",
          "email",
          "gstin",
          "pan",
          "address",
          "paymentTermsDays",
          "creditLimit",
          "notes"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": [
              "null",
              "string"
            ]
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "pan": {
            "type": [
              "null",
              "string"
            ]
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentTermsDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "creditLimit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateUnitRequest": {
        "required": [
          "name",
          "abbreviation",
          "category",
          "purchaseUnitId",
          "conversionFactor"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "abbreviation": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "purchaseUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "conversionFactor": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "CreateVariantRequest": {
        "type": "object",
        "properties": {
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "barcode": {
            "type": [
              "null",
              "string"
            ]
          },
          "attributes": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "mrp": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "sellingPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "costPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "openingStock": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "branchId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Only read when this request is submitted standalone (adding a\nvariant to an existing product — see ProductRepository.\nAddVariantAsync). When nested inside CreateProductRequest.Variants\ninstead, that request's own BranchId is used for every variant in\nthe submission and this field is ignored — one product-creation\nsubmission shouldn't split its opening stock across branches\nper-variant. Null defaults to the tenant's default branch.",
            "format": "uuid"
          }
        }
      },
      "CreditNoteAllocationDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceId": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "allocationDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CurrentUserResponse": {
        "required": [
          "permissions"
        ],
        "type": "object",
        "properties": {
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "description": "Current authenticated user's permission list, for frontend UI gating."
      },
      "CursorPageOfContactListItem": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactListItem"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as `cursor` on the next request. Null when HasMore is false."
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "description": "Response envelope for keyset (cursor) pagination — the successor to\nPagedResult&lt;T&gt; for high-traffic lists.\n\nDeliberately has NO TotalCount: computing an exact total alongside a\nkeyset query means either a separate COUNT(*) (expensive on large\ntables, and it's stale the instant it's returned under concurrent\nwrites) or COUNT(*) OVER() in the same query (defeats the entire point\nof keyset pagination, since the DB still has to touch every matching\nrow instead of stopping at LIMIT). If a screen genuinely needs a count,\nit should be a separate, deliberately-called, cheap aggregate endpoint\n— not bundled into every page fetch."
      },
      "CursorPageOfInvoiceListItem": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceListItem"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as `cursor` on the next request. Null when HasMore is false."
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "description": "Response envelope for keyset (cursor) pagination — the successor to\nPagedResult&lt;T&gt; for high-traffic lists.\n\nDeliberately has NO TotalCount: computing an exact total alongside a\nkeyset query means either a separate COUNT(*) (expensive on large\ntables, and it's stale the instant it's returned under concurrent\nwrites) or COUNT(*) OVER() in the same query (defeats the entire point\nof keyset pagination, since the DB still has to touch every matching\nrow instead of stopping at LIMIT). If a screen genuinely needs a count,\nit should be a separate, deliberately-called, cheap aggregate endpoint\n— not bundled into every page fetch."
      },
      "CursorPageOfPaymentDto": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentDto"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as `cursor` on the next request. Null when HasMore is false."
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "description": "Response envelope for keyset (cursor) pagination — the successor to\nPagedResult&lt;T&gt; for high-traffic lists.\n\nDeliberately has NO TotalCount: computing an exact total alongside a\nkeyset query means either a separate COUNT(*) (expensive on large\ntables, and it's stale the instant it's returned under concurrent\nwrites) or COUNT(*) OVER() in the same query (defeats the entire point\nof keyset pagination, since the DB still has to touch every matching\nrow instead of stopping at LIMIT). If a screen genuinely needs a count,\nit should be a separate, deliberately-called, cheap aggregate endpoint\n— not bundled into every page fetch."
      },
      "CursorPageOfProductListItem": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductListItem"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as `cursor` on the next request. Null when HasMore is false."
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "description": "Response envelope for keyset (cursor) pagination — the successor to\nPagedResult&lt;T&gt; for high-traffic lists.\n\nDeliberately has NO TotalCount: computing an exact total alongside a\nkeyset query means either a separate COUNT(*) (expensive on large\ntables, and it's stale the instant it's returned under concurrent\nwrites) or COUNT(*) OVER() in the same query (defeats the entire point\nof keyset pagination, since the DB still has to touch every matching\nrow instead of stopping at LIMIT). If a screen genuinely needs a count,\nit should be a separate, deliberately-called, cheap aggregate endpoint\n— not bundled into every page fetch."
      },
      "CursorPageOfStockLedgerEntry": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StockLedgerEntry"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as `cursor` on the next request. Null when HasMore is false."
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "description": "Response envelope for keyset (cursor) pagination — the successor to\nPagedResult&lt;T&gt; for high-traffic lists.\n\nDeliberately has NO TotalCount: computing an exact total alongside a\nkeyset query means either a separate COUNT(*) (expensive on large\ntables, and it's stale the instant it's returned under concurrent\nwrites) or COUNT(*) OVER() in the same query (defeats the entire point\nof keyset pagination, since the DB still has to touch every matching\nrow instead of stopping at LIMIT). If a screen genuinely needs a count,\nit should be a separate, deliberately-called, cheap aggregate endpoint\n— not bundled into every page fetch."
      },
      "CursorPageOfSupplierDto": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupplierDto"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as `cursor` on the next request. Null when HasMore is false."
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "description": "Response envelope for keyset (cursor) pagination — the successor to\nPagedResult&lt;T&gt; for high-traffic lists.\n\nDeliberately has NO TotalCount: computing an exact total alongside a\nkeyset query means either a separate COUNT(*) (expensive on large\ntables, and it's stale the instant it's returned under concurrent\nwrites) or COUNT(*) OVER() in the same query (defeats the entire point\nof keyset pagination, since the DB still has to touch every matching\nrow instead of stopping at LIMIT). If a screen genuinely needs a count,\nit should be a separate, deliberately-called, cheap aggregate endpoint\n— not bundled into every page fetch."
      },
      "CustomerLedger": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "contactName": {
            "type": "string"
          },
          "contactPhone": {
            "type": [
              "null",
              "string"
            ]
          },
          "contactEmail": {
            "type": [
              "null",
              "string"
            ]
          },
          "totalInvoiced": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalPaid": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "outstandingBalance": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LedgerEntry"
            }
          }
        },
        "description": "Full customer ledger — all invoices and payments in chronological order.\nUsed for customer statement and Digital Khata view."
      },
      "DiscountRuleDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "discountType": {
            "type": "string"
          },
          "discountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "appliesTo": {
            "type": "string"
          },
          "targetId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "targetName": {
            "type": [
              "null",
              "string"
            ]
          },
          "isAutoApply": {
            "type": "boolean"
          },
          "isStackable": {
            "type": "boolean"
          },
          "priority": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "maxDiscountAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "minBillAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "minQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "validFrom": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "validUntil": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "daysOfWeek": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          "timeFrom": {
            "type": [
              "null",
              "string"
            ]
          },
          "timeUntil": {
            "type": [
              "null",
              "string"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "isCurrentlyActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GetOnboardingLinkRequest": {
        "required": [
          "returnUrl"
        ],
        "type": "object",
        "properties": {
          "returnUrl": {
            "type": "string"
          }
        },
        "description": "Request body for fetching a fresh embeddable onboarding link."
      },
      "InviteSendOtpRequest": {
        "type": "object",
        "properties": {
          "inviteToken": {
            "type": "string",
            "format": "uuid"
          }
        },
        "description": "Step 1 — Staff clicks invite link, frontend sends token to trigger OTP send."
      },
      "InviteUserRequest": {
        "type": "object",
        "properties": {
          "phone": {
            "type": "string"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "roleId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "InviteVerifyRequest": {
        "type": "object",
        "properties": {
          "inviteToken": {
            "type": "string",
            "format": "uuid"
          },
          "otpCode": {
            "type": "string"
          }
        },
        "description": "Step 2 — Staff enters OTP received on WhatsApp to complete invite acceptance."
      },
      "InvoiceChargeDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "isTaxable": {
            "type": "boolean"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "InvoiceDetail": {
        "required": [
          "id",
          "invoiceNumber",
          "invoiceType",
          "gstType",
          "status",
          "contactId",
          "branchId",
          "branchName",
          "buyerName",
          "buyerPhone",
          "buyerEmail",
          "buyerGstin",
          "buyerAddress",
          "billingAddress",
          "shippingAddress",
          "shippingSameAsBilling",
          "subtotal",
          "itemDiscount",
          "billDiscountType",
          "billDiscountValue",
          "billDiscountAmount",
          "taxableAmount",
          "cgstAmount",
          "sgstAmount",
          "igstAmount",
          "totalGst",
          "chargesAmount",
          "roundOff",
          "totalAmount",
          "paidAmount",
          "balanceDue",
          "invoiceDate",
          "dueDate",
          "paymentMode",
          "paymentReference",
          "paidAt",
          "notes",
          "termsConditions",
          "roundOffMode",
          "items",
          "charges",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "invoiceType": {
            "type": "string"
          },
          "gstType": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": "string"
          },
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "branchName": {
            "type": "string"
          },
          "buyerName": {
            "type": "string"
          },
          "buyerPhone": {
            "type": [
              "null",
              "string"
            ]
          },
          "buyerEmail": {
            "type": [
              "null",
              "string"
            ]
          },
          "buyerGstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "buyerAddress": {
            "type": [
              "null",
              "string"
            ]
          },
          "billingAddress": {
            "type": [
              "null",
              "string"
            ]
          },
          "shippingAddress": {
            "type": [
              "null",
              "string"
            ]
          },
          "shippingSameAsBilling": {
            "type": "boolean"
          },
          "subtotal": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "itemDiscount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "billDiscountType": {
            "type": [
              "null",
              "string"
            ]
          },
          "billDiscountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "billDiscountAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "taxableAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "cgstAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sgstAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "igstAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalGst": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "chargesAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "roundOff": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paidAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "balanceDue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "invoiceDate": {
            "type": "string",
            "format": "date"
          },
          "dueDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "paymentMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentReference": {
            "type": [
              "null",
              "string"
            ]
          },
          "paidAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "termsConditions": {
            "type": [
              "null",
              "string"
            ]
          },
          "roundOffMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceItemDetail"
            }
          },
          "charges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceChargeDetail"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "Full invoice detail DTO — returned when viewing a single invoice.\nIncludes client info joined from the clients table.\nUsed for: invoice detail screen, PDF generation, email sending."
      },
      "InvoiceItemDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "productId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "description": {
            "type": "string"
          },
          "hsnSacCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "quantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "unitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "unitPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "grossAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "discountType": {
            "type": [
              "null",
              "string"
            ]
          },
          "discountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "discountAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "taxableAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "cgstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sgstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "igstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "cgstAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sgstAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "igstAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalGst": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "itemTotal": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "InvoiceListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "invoiceType": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "contactName": {
            "type": "string"
          },
          "contactPhone": {
            "type": [
              "null",
              "string"
            ]
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "balanceDue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "invoiceDate": {
            "type": "string",
            "format": "date"
          },
          "dueDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "Lightweight DTO returned by GetInvoices list query.\nOnly the columns needed for the invoice list screen — not the full detail.\nKeeps the list query fast — no unnecessary data fetched from DB."
      },
      "InvoicePaymentHistory": {
        "type": "object",
        "properties": {
          "paymentDate": {
            "type": "string",
            "format": "date"
          },
          "paymentMode": {
            "type": "string"
          },
          "paymentReference": {
            "type": [
              "null",
              "string"
            ]
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentLinkId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Set only for gateway-confirmed payments — lets the UI show an \"Online\" badge distinct from manually-recorded cash/UPI/card/bank_transfer/cheque entries.",
            "format": "uuid"
          }
        },
        "description": "Payment history entry for a specific invoice.\nUsed by InvoicePdfService to render the payment history section, and\nby InvoicesController's /payments endpoint for the invoice detail\nscreen's reconciliation view."
      },
      "InvoiceReturnDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceId": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "branchName": {
            "type": "string"
          },
          "returnNumber": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "creditNoteType": {
            "type": "string"
          },
          "returnDate": {
            "type": "string",
            "format": "date"
          },
          "reason": {
            "type": [
              "null",
              "string"
            ]
          },
          "refundMode": {
            "type": "string"
          },
          "subtotal": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalGst": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "issuedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "cancelledAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "cancellationReason": {
            "type": [
              "null",
              "string"
            ]
          },
          "gatewayRefundId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Set only after refund-online has been called at least once — see M066."
          },
          "gatewayCfRefundId": {
            "type": [
              "null",
              "string"
            ]
          },
          "gatewayRefundStatus": {
            "type": [
              "null",
              "string"
            ],
            "description": "Cashfree's own refund_status (SUCCESS|PENDING|ONHOLD|CANCELLED), stored verbatim."
          },
          "gatewayRefundAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "gatewayRefundedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceReturnItemDto"
            }
          },
          "allocations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreditNoteAllocationDto"
            }
          }
        }
      },
      "InvoiceReturnItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceItemId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "productId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "originalQuantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "returnQuantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "unitPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "taxableAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalGst": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "itemTotal": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "InvoiceSummary": {
        "required": [
          "draftCount",
          "draftAmount",
          "sentCount",
          "sentAmount",
          "paidCount",
          "paidAmount",
          "overdueCount",
          "overdueAmount",
          "totalOutstanding",
          "totalCollectedThisMonth",
          "totalInvoiced",
          "invoicedThisMonth"
        ],
        "type": "object",
        "properties": {
          "draftCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "draftAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sentCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "sentAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paidCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "paidAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "overdueCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "overdueAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalOutstanding": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalCollectedThisMonth": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalInvoiced": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "invoicedThisMonth": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "LedgerEntry": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "type": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "referenceId": {
            "type": "string",
            "format": "uuid"
          },
          "debit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "credit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "runningBalance": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "status": {
            "type": "string"
          }
        }
      },
      "LowStockVariant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "attributes": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "currentStock": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "shortfallQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "MarkPaidRequest": {
        "required": [
          "paidAmount",
          "paymentMode",
          "paymentReference",
          "paidDate"
        ],
        "type": "object",
        "properties": {
          "paidAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "paymentReference": {
            "type": [
              "null",
              "string"
            ]
          },
          "paidDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "idempotencyKey": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "OpenDailyClosingRequest": {
        "type": "object",
        "properties": {
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "openingCash": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "Open a new closing period.\nSales are auto-computed from invoices since the last closing ended\n(or since tenant registration, for the very first closing) — no need\nto pass a date. The period this closing covers is determined entirely\nserver-side; see DailyClosingRepository class docs for the model.\nExpenses and other income are no longer part of this request — see\nthe standalone Expenses/Income endpoints. Any entries already logged\nfor this branch within the period are picked up automatically."
      },
      "OpeningStockItem": {
        "type": "object",
        "properties": {
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "quantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unitCost": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "OpeningStockRequest": {
        "type": "object",
        "properties": {
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpeningStockItem"
            }
          }
        }
      },
      "OutstandingInvoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "invoiceDate": {
            "type": "string",
            "format": "date"
          },
          "dueDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paidAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "balanceDue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "status": {
            "type": "string"
          },
          "isOverdue": {
            "type": "boolean"
          },
          "daysOverdue": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "Outstanding invoices for a customer — used for payment allocation UI."
      },
      "PaymentAllocationRequest": {
        "type": "object",
        "properties": {
          "invoiceId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "PaymentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "contactName": {
            "type": "string"
          },
          "contactPhone": {
            "type": [
              "null",
              "string"
            ]
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "allocatedAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unallocatedAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "paymentReference": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentDate": {
            "type": "string",
            "format": "date"
          },
          "allocationMode": {
            "type": "string"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "allocations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AllocationDto"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "reversedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "reversalReason": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentLinkId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Set only for gateway-confirmed payments — distinguishes an online Cashfree collection from a manually-recorded cash/UPI/card/bank_transfer/cheque entry.",
            "format": "uuid"
          }
        }
      },
      "PaymentGatewayStatusDto": {
        "required": [
          "provider",
          "credentialMode",
          "status",
          "canCollectPayments"
        ],
        "type": "object",
        "properties": {
          "provider": {
            "type": "string"
          },
          "credentialMode": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "canCollectPayments": {
            "type": "boolean",
            "description": "True once payments can actually be collected: platform-mode is\ntrue as soon as Status == \"active\" (no separate credential step);\nbyo-mode additionally requires a verified, active credential row."
          },
          "maskedIdentifier": {
            "type": [
              "null",
              "string"
            ]
          },
          "activatedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "onboardingLink": {
            "type": [
              "null",
              "string"
            ],
            "description": "Only set for platform-mode when onboarding is incomplete — pass straight to the frontend's embedded iframe."
          },
          "onboardingLinkExpiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "Clean, frontend-facing view of a tenant's payment gateway setup — never exposes secrets."
      },
      "PaymentLinkDto": {
        "required": [
          "id",
          "payToken",
          "linkUrl",
          "amount",
          "status"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "payToken": {
            "type": "string"
          },
          "linkUrl": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "status": {
            "type": "string"
          },
          "expiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "paidAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "Tenant-facing — returned to flobill-web when a merchant generates/views a payment link."
      },
      "PaymentQrDto": {
        "required": [
          "payToken",
          "qrImageBase64",
          "amount"
        ],
        "type": "object",
        "properties": {
          "payToken": {
            "type": "string"
          },
          "qrImageBase64": {
            "type": "string",
            "description": "Full data URI (e.g. \"data:image/png;base64,...\") — ready to drop straight into an &lt;img src&gt;."
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "expiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "Tenant-facing — the merchant-app \"show QR\" screen. PayToken is\nincluded so the merchant app can poll the SAME public endpoint the\ncustomer pay page uses (POST /api/public/pay/{payToken}/refresh) for\nlive status — no separate authenticated status endpoint needed, since\nthe pay token itself is the capability that gates it."
      },
      "PaymentStatsSummary": {
        "type": "object",
        "properties": {
          "totalCollected": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "collectedThisMonth": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalUnallocated": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "Powers the Payments list's KPI strip. All four figures are tenant-wide,\nall-time totals except CollectedThisMonth, which is scoped to the\ncurrent calendar month by payment_date.\n\nTotalUnallocated sums payments.unallocated_amount — the portion of each\npayment not yet applied to an invoice (overpayments, advance payments).\nNote this is the same underlying concept flagged as missing \"store\ncredit\" data on the Clients list's own stats endpoint: this table\nalready tracks it per-payment, just not aggregated per-client. A future\nper-client credit figure could be built from\nSUM(unallocated_amount) GROUP BY contact_id rather than needing new\nschema."
      },
      "PermissionItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "module": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ProductDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "barcode": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "categoryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "categoryName": {
            "type": [
              "null",
              "string"
            ]
          },
          "brandId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "brandName": {
            "type": [
              "null",
              "string"
            ]
          },
          "preferredSupplierId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "supplierName": {
            "type": [
              "null",
              "string"
            ]
          },
          "saleUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "saleUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "purchaseUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "purchaseUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "mrp": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "sellingPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "costPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "hsnSacCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "hasVariants": {
            "type": "boolean"
          },
          "productType": {
            "type": "string"
          },
          "currentStock": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "stockBehaviour": {
            "type": "string"
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "isActive": {
            "type": "boolean"
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantDto"
            }
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductImageDto"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "availableUnits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UnitOption"
            }
          },
          "defaultUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "ProductImageDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "isPrimary": {
            "type": "boolean"
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "ProductListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "barcode": {
            "type": [
              "null",
              "string"
            ]
          },
          "categoryName": {
            "type": [
              "null",
              "string"
            ]
          },
          "brandName": {
            "type": [
              "null",
              "string"
            ]
          },
          "sellingPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "mrp": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "hasVariants": {
            "type": "boolean"
          },
          "productType": {
            "type": "string"
          },
          "currentStock": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "isLowStock": {
            "type": "boolean"
          },
          "stockBehaviour": {
            "type": "string"
          },
          "isActive": {
            "type": "boolean"
          },
          "primaryImageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "lowStockVariants": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/LowStockVariant"
            }
          }
        }
      },
      "ProductStatsSummary": {
        "type": "object",
        "properties": {
          "totalProducts": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "variantProducts": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lowStockItems": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "outOfStockItems": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "Powers the Inventory list's KPI strip.\n\nTotalProducts / VariantProducts count every non-deleted product\nregardless of active status, matching GetAllAsync's default (no\nisActive filter = everything). LowStockItems / OutOfStockItems only\ncount active products — an inactive product isn't something you're\ncurrently trying to sell, so it doesn't belong in a \"needs restocking\"\ncount, same reasoning GetLowStockAsync already applies.\n\nStock source is products.current_stock / product_variants.current_stock\n(not the newer per-branch branch_stock table) — this matches\nGetLowStockAsync, the existing shipped low-stock report, rather than\nintroducing a second source of truth for what \"low stock\" means.\n\nLow-stock logic mirrors GetLowStockAsync exactly: a simple product is\nlow when its own stock is at/below its reorder point; a variant\nproduct is low when ANY single variant is at/below its own reorder\npoint (or the parent product's, if the variant doesn't override it).\nOut-of-stock is stricter and variant-aware differently: a simple\nproduct is out when its stock is &lt;= 0; a variant product is out only\nwhen the SUM across all its variants is &lt;= 0 — i.e. the whole product\nhas nothing sellable left, not just one size/color out of several."
      },
      "ProductUnitIssue": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "format": "uuid"
          },
          "productName": {
            "type": "string"
          },
          "saleUnitName": {
            "type": "string"
          },
          "purchaseUnitName": {
            "type": "string"
          }
        },
        "description": "A product whose Sale unit and Stock unit are both set, differ, and\naren't actually connected by a conversion factor — i.e. would fail\nEnsureUnitsConnectedAsync if the product were saved today, but\npredates that validation (added after unit conversion first shipped)\nso nothing has caught it yet. Selling this product in its Sale unit\nsilently deducts the wrong amount of stock, since the server has no\nway to convert between the two.\n\nSurfaced as a proactive cleanup report (Settings → Units) rather than\nwaiting for a tenant to discover it via a wrong stock deduction —\nsee GetUnitConfigIssuesAsync."
      },
      "RegisterRequest": {
        "required": [
          "businessName",
          "businessEmail",
          "phone",
          "gstin",
          "businessMode",
          "businessCategory",
          "ownerName",
          "ownerEmail",
          "locationName",
          "phoneVerificationToken"
        ],
        "type": "object",
        "properties": {
          "businessName": {
            "type": "string"
          },
          "businessEmail": {
            "type": "string"
          },
          "phone": {
            "type": [
              "null",
              "string"
            ]
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "businessMode": {
            "type": "string"
          },
          "businessCategory": {
            "type": [
              "null",
              "string"
            ]
          },
          "ownerName": {
            "type": "string"
          },
          "ownerEmail": {
            "type": "string"
          },
          "locationName": {
            "type": [
              "null",
              "string"
            ]
          },
          "phoneVerificationToken": {
            "type": "string"
          }
        },
        "description": "Tenant + owner user registration in one step."
      },
      "ReversePaymentRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string"
          }
        }
      },
      "ReverseSupplierPaymentRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string"
          }
        }
      },
      "RoleDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "isSystem": {
            "type": "boolean"
          },
          "isActive": {
            "type": "boolean"
          },
          "permissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PermissionItem"
            }
          }
        }
      },
      "RoleSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "isSystem": {
            "type": "boolean"
          },
          "isActive": {
            "type": "boolean"
          },
          "permissionCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "userCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "SaveByoCredentialsRequest": {
        "required": [
          "clientId",
          "secretKey"
        ],
        "type": "object",
        "properties": {
          "clientId": {
            "type": "string"
          },
          "secretKey": {
            "type": "string"
          }
        },
        "description": "Tenant-pasted BYO gateway credentials, as submitted from the Settings form."
      },
      "SelectTenantRequest": {
        "type": "object",
        "properties": {
          "selectionToken": {
            "type": "string",
            "description": "The SelectionToken from verify-otp's TenantPickerResponse —\n            proves this call is tied to a just-completed OTP verification,\n            rather than trusting a client-supplied phone number directly."
          },
          "tenantId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "SendOtpRequest": {
        "type": "object",
        "properties": {
          "phone": {
            "type": "string"
          }
        }
      },
      "SettlementResult": {
        "required": [
          "cfSettlementId",
          "settlementAmount",
          "serviceCharge",
          "serviceTax"
        ],
        "type": "object",
        "properties": {
          "cfSettlementId": {
            "type": "string"
          },
          "settlementAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "serviceCharge": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "serviceTax": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "transferUtr": {
            "type": [
              "null",
              "string"
            ],
            "description": "Bank UTR reference — the actual proof of transfer once settled."
          },
          "transferTime": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "Result of Cashfree's Get Settlements by Order ID API\n            (/orders/{order_id}/settlements) — the \"has this actually reached the\n            bank\" signal, distinct from OrderStatusResult's PAID (which only means\n            Cashfree collected the money, not that it's been transferred out)."
      },
      "StartOAuthLinkResponse": {
        "required": [
          "authLink"
        ],
        "type": "object",
        "properties": {
          "authLink": {
            "type": "string"
          }
        }
      },
      "StockAdjustmentRequest": {
        "type": "object",
        "properties": {
          "branchId": {
            "type": "string",
            "format": "uuid"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "quantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unitCost": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": "string"
          },
          "movementDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "StockLedgerEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "movementType": {
            "type": "string"
          },
          "quantity": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "unitCost": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "stockAfter": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "referenceType": {
            "type": [
              "null",
              "string"
            ]
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "movementDate": {
            "type": "string",
            "format": "date"
          },
          "variantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "variantAttributes": {
            "type": [
              "null",
              "object"
            ],
            "additionalProperties": {
              "type": "string"
            }
          },
          "performedByName": {
            "type": "string",
            "description": "Full name of the user who recorded this movement — stock_ledger.created_by\n            has existed since the table's first migration (M010), just wasn't\n            joined/selected before. \"System\" for movements with no resolvable\n            user (e.g. a deleted user account)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SupplierDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": [
              "null",
              "string"
            ]
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "pan": {
            "type": [
              "null",
              "string"
            ]
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentTermsDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "creditLimit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "outstandingBalance": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SupplierStatsSummary": {
        "type": "object",
        "properties": {
          "totalSuppliers": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalOutstanding": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "purchasedThisMonth": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "activeCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "Powers the Suppliers list's KPI strip.\n\nTotalOutstanding sums suppliers.outstanding_balance directly — a\nmaintained running total (incremented on purchase confirmation,\ndecremented on supplier payment — see PurchaseRepository and\nSupplierPaymentRepository), not derived live from purchases. This is a\ndeliberately different approach than ClientStatsSummary's own\nOutstandingAmount, which had to be derived live from invoices because\nthe equivalent client_profiles.outstanding_balance column is null for\nindividual (B2C) contacts — suppliers.outstanding_balance has no such\ngap, every supplier has one, so the stored figure is trustworthy here.\n\nPurchasedThisMonth excludes draft purchases, same reasoning as\nPurchaseStatsSummary.TotalPurchased."
      },
      "UnitDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "abbreviation": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "purchaseUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "purchaseUnitName": {
            "type": [
              "null",
              "string"
            ]
          },
          "conversionFactor": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "isActive": {
            "type": "boolean"
          }
        }
      },
      "UnitOption": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "abbreviation": {
            "type": "string"
          },
          "conversionFactor": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "isStockUnit": {
            "type": "boolean"
          },
          "isSaleUnit": {
            "type": "boolean"
          }
        },
        "description": "One unit a product can be sold in, with its conversion factor into\nthe product's stock unit. ConversionFactor = 1 for the stock unit\nitself; for any other unit it's \"how many of this unit equal 1 stock\nunit\" (e.g. Gram has ConversionFactor 1000 against a Kilogram stock\nunit) — matches units.conversion_factor's existing meaning."
      },
      "UpdateBranchRequest": {
        "required": [
          "name",
          "code",
          "address",
          "phone",
          "gstin",
          "isActive"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "code": {
            "type": [
              "null",
              "string"
            ]
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "phone": {
            "type": [
              "null",
              "string"
            ]
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        },
        "description": "All fields optional — partial update, only supplied fields change."
      },
      "UpdateBrandRequest": {
        "required": [
          "name",
          "logoUrl",
          "isActive"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "logoUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateCategoryRequest": {
        "required": [
          "name",
          "parentId",
          "description",
          "sortOrder",
          "isActive"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "parentId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateClientProfileRequest": {
        "required": [
          "gstin",
          "pan",
          "paymentTermsDays",
          "creditLimit",
          "billingAddressLine1",
          "billingAddressLine2",
          "billingCity",
          "billingState",
          "billingPincode",
          "shippingSameAsBilling",
          "shippingAddressLine1",
          "shippingAddressLine2",
          "shippingCity",
          "shippingState",
          "shippingPincode"
        ],
        "type": "object",
        "properties": {
          "gstin": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated GSTIN."
          },
          "pan": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated PAN."
          },
          "paymentTermsDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "Updated default payment terms in days.",
            "format": "int32"
          },
          "creditLimit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "description": "Updated credit limit.",
            "format": "double"
          },
          "billingAddressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated billing address primary line."
          },
          "billingAddressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated billing address secondary line."
          },
          "billingCity": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated billing city."
          },
          "billingState": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated billing state."
          },
          "billingPincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated billing postal PIN code."
          },
          "shippingSameAsBilling": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "True when the shipping address should mirror the billing address."
          },
          "shippingAddressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated shipping address primary line."
          },
          "shippingAddressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated shipping address secondary line."
          },
          "shippingCity": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated shipping city."
          },
          "shippingState": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated shipping state."
          },
          "shippingPincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated shipping postal PIN code."
          }
        },
        "description": "B2B client profile fields that can be updated."
      },
      "UpdateContactRequest": {
        "required": [
          "name",
          "email",
          "phone",
          "addressLine1",
          "addressLine2",
          "city",
          "state",
          "pincode",
          "notes",
          "tags",
          "isActive",
          "clientProfile"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated client display name or business name."
          },
          "email": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated email address."
          },
          "phone": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated phone number."
          },
          "addressLine1": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated primary address line."
          },
          "addressLine2": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated secondary address line."
          },
          "city": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated city."
          },
          "state": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated state."
          },
          "pincode": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated postal PIN code."
          },
          "notes": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated internal notes."
          },
          "tags": {
            "type": [
              "null",
              "string"
            ],
            "description": "Updated comma-separated tags."
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Set to false to deactivate the client without deleting it."
          },
          "clientProfile": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "B2B profile fields to update.",
                "$ref": "#/components/schemas/UpdateClientProfileRequest"
              }
            ]
          }
        },
        "description": "Request body for updating a client. All fields are optional."
      },
      "UpdateDailyClosingRequest": {
        "type": "object",
        "properties": {
          "openingCash": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateDiscountRuleRequest": {
        "required": [
          "name",
          "discountValue",
          "isAutoApply",
          "isStackable",
          "priority",
          "maxDiscountAmount",
          "minBillAmount",
          "minQty",
          "validFrom",
          "validUntil",
          "daysOfWeek",
          "timeFrom",
          "timeUntil",
          "isActive"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "discountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "isAutoApply": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "isStackable": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "priority": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "maxDiscountAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "minBillAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "minQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "validFrom": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "validUntil": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "daysOfWeek": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          "timeFrom": {
            "type": [
              "null",
              "string"
            ]
          },
          "timeUntil": {
            "type": [
              "null",
              "string"
            ]
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateExpenseCategoryRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "defaultAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "defaultPaymentMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateExpenseRequest": {
        "type": "object",
        "properties": {
          "categoryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "expenseDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateIncomeCategoryRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "defaultAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "defaultPaymentMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "sortOrder": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateIncomeRequest": {
        "type": "object",
        "properties": {
          "categoryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "paymentMode": {
            "type": "string"
          },
          "incomeDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateInvoiceRequest": {
        "required": [
          "contactId",
          "invoiceDate",
          "dueDate",
          "gstType",
          "billDiscountType",
          "billDiscountValue",
          "notes",
          "termsConditions",
          "roundOffMode",
          "items",
          "charges"
        ],
        "type": "object",
        "properties": {
          "contactId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "invoiceDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "dueDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "gstType": {
            "type": [
              "null",
              "string"
            ]
          },
          "billDiscountType": {
            "type": [
              "null",
              "string"
            ]
          },
          "billDiscountValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "termsConditions": {
            "type": [
              "null",
              "string"
            ]
          },
          "roundOffMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CreateInvoiceItemRequest"
            }
          },
          "charges": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CreateInvoiceChargeRequest"
            }
          }
        }
      },
      "UpdateProductRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "barcode": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "categoryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "brandId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "preferredSupplierId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "saleUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "purchaseUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "mrp": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "sellingPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "costPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "gstRate": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "hsnSacCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "productType": {
            "type": [
              "null",
              "string"
            ]
          },
          "stockBehaviour": {
            "type": [
              "null",
              "string"
            ]
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdatePurchaseOrderRequest": {
        "type": "object",
        "properties": {
          "expectedDeliveryDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CreatePurchaseOrderItemRequest"
            }
          }
        }
      },
      "UpdatePurchaseRequest": {
        "type": "object",
        "properties": {
          "supplierInvoiceNumber": {
            "type": [
              "null",
              "string"
            ]
          },
          "purchaseDate": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreatePurchaseItemRequest"
            }
          }
        },
        "description": "Editing a purchase (GRN) is only allowed while it's still a draft — see\nPurchaseRepository.UpdatePurchaseAsync. Deliberately does not include\nSupplierId, BranchId, PurchaseOrderId, PaidAmount, PaymentMode, or\nPaymentReference: the supplier/branch a purchase belongs to isn't\nmeant to change after creation, and any payment already recorded is a\nseparate committed transaction — correct it via the payment reversal\nflow, not by editing the purchase around it."
      },
      "UpdateRoleRequest": {
        "required": [
          "name",
          "description",
          "permissionIds"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "permissionIds": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "UpdateSettingsRequest": {
        "type": "object",
        "properties": {
          "businessName": {
            "type": [
              "null",
              "string"
            ]
          },
          "addressLine1": {
            "type": [
              "null",
              "string"
            ]
          },
          "addressLine2": {
            "type": [
              "null",
              "string"
            ]
          },
          "city": {
            "type": [
              "null",
              "string"
            ]
          },
          "state": {
            "type": [
              "null",
              "string"
            ]
          },
          "pincode": {
            "type": [
              "null",
              "string"
            ]
          },
          "businessPhone": {
            "type": [
              "null",
              "string"
            ]
          },
          "businessEmail": {
            "type": [
              "null",
              "string"
            ]
          },
          "businessGstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "businessPan": {
            "type": [
              "null",
              "string"
            ]
          },
          "logoUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "website": {
            "type": [
              "null",
              "string"
            ]
          },
          "contactUsUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "termsUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "privacyPolicyUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "refundPolicyUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "invoiceFooterNote": {
            "type": [
              "null",
              "string"
            ]
          },
          "invoiceTerms": {
            "type": [
              "null",
              "string"
            ]
          },
          "roundOffMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "defaultDueDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "defaultGstMode": {
            "type": [
              "null",
              "string"
            ]
          },
          "showMrpOnInvoice": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "showHsnOnInvoice": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "invoicePrefix": {
            "type": [
              "null",
              "string"
            ],
            "description": "Sets the prefix for future invoice numbers (e.g. \"INV\" in\n            \"INV-2627-001\"). Already-issued invoice numbers are unaffected."
          },
          "whatsappNumber": {
            "type": [
              "null",
              "string"
            ]
          },
          "notifyInvoiceOnCreate": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "notifyPaymentOnReceive": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "notifyDueReminder": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "dueReminderDaysBefore": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "notifyDailyClosingReminder": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "closingReminderTime": {
            "type": [
              "null",
              "string"
            ]
          },
          "notifyLowStock": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "currencySymbol": {
            "type": [
              "null",
              "string"
            ]
          },
          "financialYearStartMonth": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "defaultPaymentTermsDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lowStockAlertEnabled": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "timezone": {
            "type": [
              "null",
              "string"
            ]
          },
          "dateFormat": {
            "type": [
              "null",
              "string"
            ]
          },
          "businessCategory": {
            "type": [
              "null",
              "string"
            ],
            "description": "Same fixed set registration uses — see\n            TenantSettingsDto.BusinessCategory."
          },
          "preferredLanguage": {
            "type": [
              "null",
              "string"
            ]
          },
          "invoiceNumberingMode": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateSupplierRequest": {
        "required": [
          "name",
          "phone",
          "email",
          "gstin",
          "pan",
          "address",
          "paymentTermsDays",
          "creditLimit",
          "notes",
          "isActive"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "phone": {
            "type": [
              "null",
              "string"
            ]
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "gstin": {
            "type": [
              "null",
              "string"
            ]
          },
          "pan": {
            "type": [
              "null",
              "string"
            ]
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "paymentTermsDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "creditLimit": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateTenantUserRequest": {
        "type": "object",
        "properties": {
          "roleId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "UpdateUnitRequest": {
        "required": [
          "name",
          "abbreviation",
          "category",
          "purchaseUnitId",
          "conversionFactor",
          "isActive"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "abbreviation": {
            "type": [
              "null",
              "string"
            ]
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "purchaseUnitId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "conversionFactor": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        },
        "description": "All fields optional — partial update, only supplied fields change."
      },
      "UpdateVariantRequest": {
        "type": "object",
        "properties": {
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "barcode": {
            "type": [
              "null",
              "string"
            ]
          },
          "attributes": {
            "type": [
              "null",
              "object"
            ],
            "additionalProperties": {
              "type": "string"
            }
          },
          "mrp": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "sellingPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "costPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UserInfo": {
        "required": [
          "userId",
          "tenantId",
          "fullName",
          "email",
          "phone",
          "businessName",
          "plan",
          "businessMode"
        ],
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid"
          },
          "fullName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "businessName": {
            "type": "string"
          },
          "plan": {
            "type": "string"
          },
          "businessMode": {
            "type": "string"
          }
        },
        "description": "Basic user info embedded in auth response."
      },
      "ValidationErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "title": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "description": "Standard validation error response returned on 400 Bad Request.\nAll validation failures across FloBill return this same shape.\n\nRESPONSE SHAPE:\n{\n  \"status\": 400,\n  \"title\": \"Validation Error\",\n  \"errors\": {\n    \"email\": [\"Email is required.\", \"Email must be a valid email address.\"],\n    \"password\": [\"Password must contain at least one uppercase letter.\"]\n  }\n}"
      },
      "VariantDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "barcode": {
            "type": [
              "null",
              "string"
            ]
          },
          "attributes": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "mrp": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "sellingPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "costPrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "currentStock": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderPoint": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "reorderQty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "isLowStock": {
            "type": "boolean"
          },
          "isActive": {
            "type": "boolean"
          }
        }
      },
      "VerifyOtpRequest": {
        "type": "object",
        "properties": {
          "phone": {
            "type": "string"
          },
          "otpCode": {
            "type": "string"
          }
        }
      }
    },
    "securitySchemes": {
      "Bearer": {
        "type": "http",
        "description": "JWT Bearer Token",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  },
  "security": [
    {
      "Bearer": [ ]
    }
  ],
  "tags": [
    {
      "name": "Auth"
    },
    {
      "name": "Branches"
    },
    {
      "name": "Catalog"
    },
    {
      "name": "Clients"
    },
    {
      "name": "CreditNoteRegister"
    },
    {
      "name": "CustomerCredits"
    },
    {
      "name": "DailyClosing"
    },
    {
      "name": "Dashboard"
    },
    {
      "name": "Discounts"
    },
    {
      "name": "ExpenseCategories"
    },
    {
      "name": "Expenses"
    },
    {
      "name": "Health"
    },
    {
      "name": "IncomeCategories"
    },
    {
      "name": "Income"
    },
    {
      "name": "InvoiceReturns"
    },
    {
      "name": "Invoices"
    },
    {
      "name": "Ledger"
    },
    {
      "name": "Payment Gateway"
    },
    {
      "name": "Webhooks"
    },
    {
      "name": "Payments"
    },
    {
      "name": "Products"
    },
    {
      "name": "Public"
    },
    {
      "name": "PurchaseOrders"
    },
    {
      "name": "PurchaseReturns"
    },
    {
      "name": "Purchases"
    },
    {
      "name": "Roles"
    },
    {
      "name": "Settings"
    },
    {
      "name": "SupplierLedger"
    },
    {
      "name": "SupplierPayments"
    },
    {
      "name": "Suppliers"
    },
    {
      "name": "Users"
    },
    {
      "name": "Authentication",
      "description": "Register, login, refresh access tokens, and logout. All other endpoints require a Bearer token obtained from Login or Refresh."
    }
  ]
}