{"openapi":"3.0.0","info":{"title":"AmmiKids Ops API","version":"0.1.0","description":"Auth (WhatsApp OTP) + Phase 1 cost management + supplier dashboard."},"servers":[{"url":"http://localhost:3000"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"paths":{"/health":{"get":{"summary":"GET /health","description":"Liveness check. (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"service":"ammikids-ops","time":"2026-09-23T17:48:00.780Z"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/docs/openapi.json":{"get":{"summary":"GET /docs/openapi.json","description":"Machine-readable OpenAPI 3.0 spec generated from this catalog. (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"openapi":"3.0.0","info":{"title":"AmmiKids Ops API"}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/signup":{"post":{"summary":"POST /auth/signup","description":"Public self signup, non-admin types only. Starts UNAPPROVED: login works, dashboards hidden until admin approves. Auto-creates a PENDING supplier/karigar master for those types. (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"phone":"9876543210","roleCode":"supplier","displayName":"Sharma Fabrics"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"userId":9,"message":"Registered. Login with OTP; admin approval is required for dashboard access."}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/signup/roles":{"get":{"summary":"GET /auth/signup/roles","description":"Public account-type picker for the signup page. (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"roles":[{"code":"supplier","name":"Supplier"}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/bootstrap":{"post":{"summary":"POST /auth/bootstrap","description":"Create the very first Admin (one-time). Fails 409 once any user exists. (auth: setup-token)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"phone":"+910000000000","setupToken":"change-me-one-time-token","displayName":"Owner"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"userId":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/otp/request":{"post":{"summary":"POST /auth/otp/request","description":"Send WhatsApp OTP. Always returns the generic message (no account enumeration). Localhost/dev OTP is always 1234. (auth: none)","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"phone":"9876543210"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"message":"If registered and active, an OTP has been sent."}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/otp/verify":{"post":{"summary":"POST /auth/otp/verify","description":"Verify OTP → access (90d) + rotating refresh (90d, sliding). Tokens stored in app localStorage with silent refresh. Supplier first login includes isFirstLogin + profileNotice. Phones in config/superadmins.txt auto-provision as Admin, no activation needed. (auth: none)","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"phone":"9876543210","otp":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"access":"eyJhbGciOi...","refresh":"9f2c...","isFirstLogin":true,"profileNotice":"your profile is under verification, please contact Ammikids","user":{"id":7,"phone":"+919876543210","display":"Sharma Fabrics","role":"supplier"}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/refresh":{"post":{"summary":"POST /auth/refresh","description":"Rotate refresh token → new access + refresh pair. (auth: none)","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"refreshToken":"9f2c..."}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"access":"eyJhbGciOi...","refresh":"b71d..."}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/logout":{"post":{"summary":"POST /auth/logout","description":"Revoke this refresh token, or everything with {all:true} (bumps token_version). (auth: Bearer)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"all":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/auth/me":{"get":{"summary":"GET /auth/me","description":"Current user + server-resolved dashboard. Pending suppliers also get profileNotice. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"user":{"id":7,"phone":"+919876543210","display":"Sharma Fabrics","role":"supplier","perms":["fabrics.submit"],"dashboard":"Supplier Dashboard"}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users":{"post":{"summary":"POST /users","description":"Invite a user (Admin). Optionally link supplier/karigar master. (auth: Bearer + users.create)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"phone":"9811111111","roleCode":"supplier","supplierId":3}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"userId":8}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /users?limit=10&offset=0","description":"Paginated user list. Phones masked unless users.view_pii. Filters: ?role=<code>&active=1|0&q=<phone/name>. (auth: Bearer + users.create)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":8,"phone_e164":"XXXXXX1111","display_name":"9811111111","role":"supplier","is_active":1,"is_verified":0,"last_login_at":null,"created_at":"2026-09-23T10:00:00.000Z"}],"limit":10,"offset":0,"total":42,"users":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/roles":{"get":{"summary":"GET /users/roles","description":"All roles with their permission codes (drives admin create/role-change forms). (auth: Bearer + users.create)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"roles":[{"id":2,"code":"supplier","name":"Supplier","is_active":1,"permissions":["fabrics.submit"]}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/{id}/deactivate":{"patch":{"summary":"PATCH /users/:id/deactivate","description":"Instant block: sessions revoked via token_version. (auth: Bearer + users.deactivate)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"reason":"Left the company"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/{id}/reactivate":{"patch":{"summary":"PATCH /users/:id/reactivate","description":"Re-activate. User must OTP-login again. (auth: Bearer + users.deactivate)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/{id}/verified":{"patch":{"summary":"PATCH /users/:id/verified","description":"Mark verified/unverified. Unverified accounts with a prior login cannot request or verify OTPs until re-verified; fresh invites still work. (auth: Bearer + users.deactivate)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"is_verified":0,"reason":"Suspended pending KYC"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"is_verified":0}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/{id}/approve":{"patch":{"summary":"PATCH /users/:id/approve","description":"Approve/revoke a self-signed-up account (dashboard gate). Revoking kills sessions instantly. (auth: Bearer + users.deactivate)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"approved":1}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"is_approved":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/{id}/role":{"patch":{"summary":"PATCH /users/:id/role","description":"Change role (instant session revoke, audited old→new). (auth: Bearer + users.change_role)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"roleCode":"accounts","reason":"Moved to accounts team"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/{id}/phone/request":{"post":{"summary":"POST /users/:id/phone/request","description":"Step 1 of verified phone change: OTP goes to the NEW number. (auth: Bearer + users.change_phone)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"newPhone":"9822222222","reason":"Number ported"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"message":"OTP sent to new number"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/{id}/phone/confirm":{"post":{"summary":"POST /users/:id/phone/confirm","description":"Step 2: confirm NEW-number OTP (1234 works on localhost). (auth: Bearer + users.change_phone)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"newPhone":"9822222222","otp":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/me/display-name":{"patch":{"summary":"PATCH /users/me/display-name","description":"User edits own display name (2–100 chars). (auth: Bearer)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"displayName":"Sharma Fabrics"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/me/profile":{"patch":{"summary":"PATCH /users/me/profile","description":"Assignment manager or karigar edits own name and contact address (address ≤ 500 chars). (auth: Bearer + assignment_manager/karigar role)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"displayName":"Night Shift Manager","address":"Surat depot"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"name":"Night Shift Manager","address":"Surat depot"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/me/phone/request":{"post":{"summary":"POST /users/me/phone/request","description":"Self phone change, step 1: OTP goes to the NEW number (rate-limited). Karigar master phone syncs on confirm. (auth: Bearer + assignment_manager/karigar role)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"newPhone":"9822222222"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"message":"OTP sent to new number"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/users/me/phone/confirm":{"post":{"summary":"POST /users/me/phone/confirm","description":"Self phone change, step 2: confirm NEW-number OTP. Verifying the new number revokes all sessions, including the current one. (auth: Bearer + assignment_manager/karigar role)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"newPhone":"9822222222","otp":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/mine/{id}":{"get":{"summary":"GET /assignments/mine/:id","description":"One job's details for its karigar: source, product/fabric image, pattern + pattern image, per-metre source cost, instructions, sizes with cloth and row totals, plus the karigar-saved pattern cost list. Never includes vendor cost fields. (auth: Bearer (own karigar, or staff))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"job":{"id":31,"assign_no":"ASG-2026-00031","qty_base_m":25,"status":"ASSIGNED","pattern_number":"PAT-101"},"sizes":[{"size":24,"qty":40,"cloth":2.6,"total_m":104}],"costs":[{"id":1,"label":"Stitching","value_paise":4500,"unit":"Rs","qty":2,"total_paise":9000}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/pattern-numbers/{number}/costs":{"post":{"summary":"POST /assignments/pattern-numbers/:number/costs","description":"Karigar saves one cost row (free-text label + numeric value + unit Meter/Rs + whole-piece qty) on a pattern. Row total is value × qty. The same list reappears on every job with that pattern. (auth: Bearer (karigar with this pattern on own job, or assignments.create))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"label":"Stitching","value":45,"unit":"Rs","qty":2}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/pattern-costs/{id}":{"delete":{"summary":"DELETE /assignments/pattern-costs/:id","description":"Remove a saved pattern cost row. (auth: Bearer (cost owner, or assignments.create))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/patterns":{"get":{"summary":"GET /assignments/patterns?limit=10&offset=0","description":"Pattern master list: code + image + cloth per piece + full dynamic costing list each. (auth: Bearer (karigar, or assignments.create))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"limit":10,"offset":0,"total":1,"patterns":[{"pattern_number":"PAT-101","image_url":"/uploads/patterns/1727....jpg","cloth_per_piece_m":2.5,"costs":[{"id":1,"label":"Stitching","value_paise":4500,"unit":"Rs","qty":2,"total_paise":9000}]}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"post":{"summary":"POST /assignments/patterns","description":"Create a pattern: code + image required (image via /assignments/pattern-image), optional clothPerPiece fallback (metres, ≤1000), dynamic costs list + per-size cloth list optional. Cost rows are {label, value, unit, qty} (total = value × qty); size rows are {size, cloth} for even sizes 12–40. (auth: Bearer (karigar, or assignments.create))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"code":"PAT-101","imageUrl":"/uploads/patterns/1727....jpg","clothPerPiece":2.5,"costs":[{"label":"Stitching","value":45,"unit":"Rs","qty":2}],"sizes":[{"size":24,"cloth":2.6},{"size":26,"cloth":2.7}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"code":"PAT-101"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/patterns/{code}":{"patch":{"summary":"PATCH /assignments/patterns/:code","description":"Update a pattern image, fallback cloth, and/or replace its whole costing list and per-size cloth list. Code is immutable. (auth: Bearer (karigar, or assignments.create))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"imageUrl":"/uploads/patterns/1727....jpg","costs":[{"label":"Stitching","value":45,"unit":"Rs","qty":2}],"sizes":[{"size":24,"cloth":2.6}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"code":"PAT-101"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"delete":{"summary":"DELETE /assignments/patterns/:code","description":"Delete a pattern + its costs. Blocked (409) while any assignment uses the pattern. (auth: Bearer (karigar, or assignments.create))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/suppliers":{"post":{"summary":"POST /fabrics/suppliers","description":"Create supplier master (starts PENDING verification). (auth: Bearer + suppliers.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"firmName":"Sharma Fabrics","contact":"R. Sharma","phone":"+919811111111","gstin":"07ABCDE1234F1Z5","address":"Surat","userId":8}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":3}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /fabrics/suppliers?limit=10&offset=0","description":"Paginated supplier masters. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":3,"firm_name":"Sharma Fabrics","verification_status":"PENDING","is_active":1}],"limit":10,"offset":0,"total":12,"suppliers":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/suppliers/me":{"get":{"summary":"GET /fabrics/suppliers/me","description":"Own supplier profile (phone + ID included, read-only). (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"profile":{"id":3,"firm_name":"Sharma Fabrics","contact_person":"R. Sharma","phone_e164":"+919811111111","email":"a@b.com","gstin":"07ABCDE1234F1Z5","address":"Surat","verification_status":"PENDING"}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"patch":{"summary":"PATCH /fabrics/suppliers/me","description":"Update name/contact/address/email/GST. phone + ID rejected (422). (auth: Bearer)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"firmName":"Sharma Fabrics Pvt Ltd","email":"ops@sharma.com","gstin":"07ABCDE1234F1Z5","address":"Surat, Gujarat"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"profile":{"id":3,"firm_name":"Sharma Fabrics Pvt Ltd","email":"ops@sharma.com"}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/suppliers/{id}/verify":{"patch":{"summary":"PATCH /fabrics/suppliers/:id/verify","description":"Admin clears supplier profile (clears the first-login notice). (auth: Bearer + suppliers.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"decision":"VERIFIED"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/suppliers/payments":{"post":{"summary":"POST /fabrics/suppliers/payments","description":"Record a payment made TO a supplier (posts to the ledger's Paid column — cash out clears the payable). Finance roles only — suppliers get 403. Writes an audit row (critical). (auth: Bearer + suppliers.manage | fabrics.price_review)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"supplierId":3,"amount":5000,"mode":"Bank","reference":"TXN99","paidOn":"2026-10-01","remarks":"advance"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/suppliers/{id}/ledger":{"get":{"summary":"GET /fabrics/suppliers/:id/ledger?limit=10&offset=0","description":"Statement of account with running balance. bills = APPROVED supplies (goods received); paid = payments made. balance = bills − paid. `:id` may be `me`. Cross-supplier reads by a supplier return 403. (auth: Bearer (own supplier, or finance/report roles))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"supplier":{"id":3,"firm_name":"Sharma Fabrics"},"totals":{"supplied_paise":1055000,"paid_paise":500000,"balance_paise":555000},"total":4,"limit":10,"offset":0,"entries":[{"dt":"2026-10-01","particulars":"Supply SUP-2026-00001","bill_paise":370000,"paid_paise":0,"balance_paise":370000},{"dt":"2026-10-02","particulars":"Payment Bank TXN99","bill_paise":0,"paid_paise":500000,"balance_paise":-130000}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/suppliers/ledger-summary":{"get":{"summary":"GET /fabrics/suppliers/ledger-summary","description":"All-supplier outstanding snapshot (balance sheet of payables): supplied vs paid vs owed per supplier, plus grand totals. (auth: Bearer + finance/report roles)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"summary":[{"id":3,"firm_name":"Sharma Fabrics","supplied_paise":1055000,"paid_paise":500000,"balance_paise":555000}],"totals":{"supplied":6033500,"paid":500000,"balance":5533500}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/karigars":{"post":{"summary":"POST /fabrics/karigars","description":"Create karigar master. (auth: Bearer + karigars.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"name":"Mohan","phone":"+919833333333","skillTags":"stitching","capacityM":1000}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":5}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /fabrics/karigars?limit=10&offset=0","description":"Paginated karigar masters. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":5,"name":"Mohan","is_active":1,"capacity_m":1000}],"limit":10,"offset":0,"total":30,"karigars":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/karigars/{id}":{"patch":{"summary":"PATCH /fabrics/karigars/:id","description":"Rename a karigar master (2–150 chars). Names set here appear in assignment karigar dropdowns. (auth: Bearer + karigars.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"name":"Mohan"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":5,"name":"Mohan"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/vendors":{"post":{"summary":"POST /vendors","description":"Create an external processing vendor with one or more services. processing accepts comma-separated names; unknown names become active catalogue entries and are linked automatically. (auth: Bearer + vendors.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"name":"Fine Thread Works","phone":"+919811234567","address":"Surat","processing":"Embroidery, Lace"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1,"name":"Fine Thread Works","processing":[{"id":1,"name":"Embroidery"},{"id":2,"name":"Lace"}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /vendors?limit=10&offset=0","description":"Paginated external vendors with their offered processing services. (auth: Bearer + vendors.manage | assignments.create | reports.view | audit.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":1,"name":"Fine Thread Works","services":"Embroidery, Lace"}],"limit":10,"offset":0,"total":1,"vendors":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/vendors/processing-types":{"get":{"summary":"GET /vendors/processing-types?limit=10&offset=0","description":"Dynamic processing catalogue used by vendor and cost forms. (auth: Bearer + vendors.manage | assignments.create | reports.view | audit.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":1,"name":"Embroidery"},{"id":2,"name":"Lace"}],"limit":10,"offset":0,"total":2,"processingTypes":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"post":{"summary":"POST /vendors/processing-types","description":"Explicitly add a processing catalogue entry. Existing names return the existing ID. (auth: Bearer + vendors.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"name":"Printing"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":3,"name":"Printing"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/supplies/fabric-types":{"get":{"summary":"GET /supplies/fabric-types","description":"Active fabric names for the form dropdown (managed master). (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"fabrics":["Cotton","Rayon","Viscose","...31 total..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/supplies":{"post":{"summary":"POST /supplies","description":"Add a single-product supply. images_0..N gallery (up to 100 images, 720p-optimized) + items JSON with one line per image, each with its own quantity [{quantity, unit, price, taxPct?, tax?, extraCharges?, fabric?}]. Legacy image/images[] mapping still accepted. Fabric validated against master (optional). Status auto 1 In review; header total = sum of lines. (auth: Bearer + fabrics.submit)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object"},"example":"multipart: images_0=@a.jpg, images_1=@b.jpg, items=[{\"quantity\":10,\"unit\":\"M\",\"price\":100,\"taxPct\":12},{\"quantity\":20,\"unit\":\"M\",\"price\":100}]"}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":11,"supplyNo":"SUP-2026-00011","status":1,"status_label":"In review","total_paise":112000,"image_url":"/uploads/supplies/1727....jpg","items_count":1,"images_count":2}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /supplies?limit=10&offset=0&status=","description":"Paginated supplies (own only for suppliers; staff may add supplierId/status). Status: 1 In review, 2 Approved, 3 Change Request, 4 Rejected — single or comma list (?status=2,3,4) for decided views. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":11,"supply_no":"SUP-2026-00011","quantity":50,"unit":"M","total_paise":606000,"status":1,"status_label":"In review"}],"limit":10,"offset":0,"total":4,"supplies":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/supplies/{id}":{"get":{"summary":"GET /supplies/:id","description":"Supply detail + per-product gallery images + full supplier block + Ammikids company block + status history (ownership-checked for suppliers). Powers the printable invoice popup. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"supply":{"id":11,"supply_no":"SUP-2026-00011","status":1,"status_label":"In review"},"items":[{"line_no":1,"quantity":10,"unit":"M","total_paise":112000,"images":["/uploads/supplies/a.jpg","/uploads/supplies/b.jpg"]}],"history":[]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"patch":{"summary":"PATCH /supplies/:id","description":"Edit while In review only: per-line quantities + shared unit/price/taxPct/extra/fabric + remarks. Totals recomputed. Photos lock at submit (delete + resubmit to change photos). (auth: Bearer + fabrics.submit (owner))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"lines":[{"id":40,"quantity":20}],"price":200}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"total_paise":525000}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"delete":{"summary":"DELETE /supplies/:id","description":"Withdraw an In-review supply (cascades lines + gallery). Decided supplies cannot be deleted. (auth: Bearer + fabrics.submit (owner))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/supplies/{id}/status":{"patch":{"summary":"PATCH /supplies/:id/status","description":"Review supply: 2 Approved, 3/4 need reason. Only from 1/3. (auth: Bearer + fabrics.verify|suppliers.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"status":2}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"status":2,"status_label":"Approved"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics":{"post":{"summary":"POST /fabrics","description":"Create draft fabric entry with lots. Charges apportioned pro-rata; totals in paise. (auth: Bearer + fabrics.submit)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"supplierId":3,"invoiceNo":"INV-101","entryDate":"2026-09-20","receivedDate":"2026-09-21","taxMode":"EXCLUSIVE","taxRate":5,"freight":200,"lots":[{"fabricName":"Cotton","fabricCode":"CT-40","colour":"Red","lotNo":"L1","qty":500,"unit":"M","rate":120}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":21,"entryNo":"RF-2026-00021"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /fabrics?limit=10&offset=0","description":"Paginated entries (own firm for suppliers; cost hidden for karigars). (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":21,"entry_no":"RF-2026-00021","status":"SUBMITTED"}],"limit":10,"offset":0,"total":9,"entries":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/{id}/submit":{"post":{"summary":"POST /fabrics/:id/submit","description":"Submit draft (needs invoice attachment). → SUBMITTED. (auth: Bearer + fabrics.submit)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/fabrics/{id}":{"get":{"summary":"GET /fabrics/:id","description":"Entry + lots + status history + stock ledger. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"entry":{"id":21,"status":"VERIFIED"},"lots":[],"history":[],"ledger":[]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/verifications":{"post":{"summary":"POST /verifications","description":"Verify a lot (item-level). verifiedQtyM ≤ received; reason mandatory on shortage. Cannot verify own supply (SoD). (auth: Bearer + fabrics.verify)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"lotId":55,"verifiedQtyM":485,"defectCode":"DAMAGED","qcGrade":"B","reason":"15m water damage","priceFlag":"ok"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/verifications/{lotId}/correction":{"post":{"summary":"POST /verifications/:lotId/correction","description":"Send lot back to supplier (max 3 cycles, then escalated). (auth: Bearer + fabrics.verify)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"reason":"Invoice qty mismatch, recheck roll L1"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/samples":{"post":{"summary":"POST /samples","description":"Cut sample from VERIFIED lot (deducts stock in-txn). (auth: Bearer + samples.create)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"lotId":55,"qtyM":2,"purpose":"Colour approval"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":9,"sampleNo":"S-2026-00009"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/samples/approve":{"post":{"summary":"POST /samples/approve","description":"COO approve/reject (no self-approval; delegates allowed). Rejection needs reason. Unlocks the lot for assignment. (auth: Bearer + samples.approve)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"sampleId":9,"decision":"APPROVED"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/samples/pending":{"get":{"summary":"GET /samples/pending?limit=10&offset=0","description":"Paginated COO approval queue. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":9,"sample_no":"S-2026-00009","lot_no":"L1"}],"limit":10,"offset":0,"total":2,"samples":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/approved-supplies":{"get":{"summary":"GET /assignments/approved-supplies?limit=10&offset=0","description":"All approved supplies with remaining allocation balances and AVAILABLE/PARTIALLY_ALLOCATED/FULLY_ALLOCATED status. (auth: Bearer + assignments.create)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":2,"supply_no":"SUP-2026-00002","received_qty_m":10,"available_qty_m":9,"allocation_status":"PARTIALLY_ALLOCATED"}],"limit":10,"offset":0,"total":4,"supplies":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/pattern-image":{"post":{"summary":"POST /assignments/pattern-image","description":"Upload one pattern reference image (multipart field `image`, jpg/png/webp). Returns the URL to send as patternImageUrl. (auth: Bearer + assignments.create)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object"},"example":"multipart: image=@pattern.jpg"}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"imageUrl":"/uploads/patterns/1727....jpg"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/pattern-numbers":{"get":{"summary":"GET /assignments/pattern-numbers?q=&limit=10&offset=0","description":"Saved pattern numbers with their images, for autocomplete while typing a pattern number. (auth: Bearer + assignments.create)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"pattern_number":"PAT-101","image_url":"/uploads/patterns/1727....jpg"}],"limit":10,"offset":0,"total":1,"patterns":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments":{"post":{"summary":"POST /assignments","description":"Assign approved-supply qty to karigar (row-locked; over-allocation 422; capacity overload warns via overload:true). Supply allocations decrement the same supply for later use. supplyItemId allocates from one product line and decrements both line and header. Optional sizes is a piece breakdown using even sizes 12–40; when sizes and a pattern with cloth-per-piece are given, pieces × cloth must fit the assigned metres (else 422 Assigned Cloth is Less); patternNumber replaces the dashboard job reference; patternImageUrl (from /assignments/pattern-image) saves/refreshes the pattern library image. (auth: Bearer + assignments.create)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"supplyId":2,"supplyItemId":2,"karigarId":5,"qty":150,"unit":"M","expectedReturnDate":"2026-10-05","patternNumber":"PAT-101","patternImageUrl":"/uploads/patterns/1727....jpg","sizes":[{"size":24,"qty":40},{"size":26,"qty":35}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":31,"assignNo":"ASG-2026-00031","overload":false}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /assignments?limit=10&offset=0&status=&karigarId=","description":"Paginated assignment list for managers (status/karigar filters; source=supply|lot filter; includes lot- and supply-sourced rows with source labels). (auth: Bearer + assignments.create)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":31,"assign_no":"ASG-2026-00031","karigar_name":"Mohan","status":"ASSIGNED"}],"limit":10,"offset":0,"total":1,"assignments":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/{id}/ack":{"post":{"summary":"POST /assignments/:id/ack","description":"Ack receipt (handover photo proof; managers proxy-ack flagged). Or refuse with refusalReason (qty restored). (auth: Bearer + assignments.ack)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"handoverPhotoUrl":"/uploads/handover/1.jpg"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/{id}/cancel":{"post":{"summary":"POST /assignments/:id/cancel","description":"Cancel assignment (qty restored from ASSIGNED/ACKED/IN_PROCESS/CHALLAN_PENDING, audited). (auth: Bearer + assignments.create)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"reason":"Karigar unavailable"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/{id}/challan":{"post":{"summary":"POST /assignments/:id/challan","description":"Submit challan with final lines. All cloth rows and pattern cost lines stay on the challan; paidSizes/paidCostIds mark per-line Paid flags (unchecked by default). ASSIGNED/ACKED/IN_PROCESS → CHALLAN_PENDING. (auth: Bearer (own karigar, or assignments.create))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"paidSizes":[24],"paidCostIds":[3],"note":"Advance received"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/challan-pending":{"get":{"summary":"GET /assignments/challan-pending","description":"Challan approval queue: jobs with sizes+cloth, pattern costs, exclusions, and per-metre rate. (auth: Bearer + finance/report roles)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"limit":10,"offset":0,"total":1,"challans":[{"id":31,"assign_no":"ASG-2026-00031","status":"CHALLAN_PENDING"}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/{id}/challan-decide":{"post":{"summary":"POST /assignments/:id/challan-decide","description":"Decide a pending challan: APPROVED → CHALLAN_APPROVED (terminal), REJECTED → IN_PROCESS (reason mandatory). (auth: Bearer + assignments.create | suppliers.manage)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"decision":"APPROVED"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/{id}/process":{"post":{"summary":"POST /assignments/:id/process","description":"Karigar starts production: ASSIGNED/ACKED → IN_PROCESS (qty stays reserved, audited). Powers the Challan button in the job popup. (auth: Bearer (own karigar, or assignments.create))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/{id}/processing-costs":{"post":{"summary":"POST /assignments/:id/processing-costs","description":"Save an external-vendor processing charge on an active assignment. processingTypeId must be offered by the vendor; custom processing names are created and linked automatically. Qty is whole pieces in NOs/PCs, rate is Rs per piece, patternNumber is required, and amount is calculated server-side as qty x rate. (auth: Bearer + assignments.create)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"vendorId":1,"processing":"Embroidery","qty":25,"unit":"PCs","rate":50,"patternNumber":"PAT-101"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1,"totals":{"entries":1,"material_paise":150000,"processing_paise":125000,"total_paise":275000}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /assignments/:id/processing-costs","description":"Saved processing-cost entries and charge totals for one assignment. (auth: Bearer + assignments.create | vendors.manage | reports.view | audit.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"entries":[{"id":1,"vendor_name":"Fine Thread Works","processing_name":"Embroidery","qty":25,"unit":"PCs","rate_paise":5000,"amount_paise":125000,"pattern_number":"PAT-101"}],"totals":{"entries":1,"processing_paise":125000,"vendor_payable_paise":125000}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/{id}/cost-summary":{"get":{"summary":"GET /assignments/:id/cost-summary","description":"Material plus processing rollup for future end-product costing. pro-rates the source material by assigned metres and includes the assigned entry image when available. Closed assignments are excluded from product cost while vendor charges remain payable. (auth: Bearer + assignments.create | vendors.manage | reports.view | audit.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"assignment":{"id":31,"assign_no":"ASG-2026-00031","qty_base_m":25,"pattern_number":"PAT-101","source":{"kind":"supply","ref":"SUP-2026-00011","image":"/uploads/supplies/a.jpg"}},"sizes":[{"size":24,"qty":40},{"size":26,"qty":35}],"totals":{"material_paise":150000,"processing_paise":125000,"total_paise":275000,"vendor_payable_paise":125000,"included_in_product_cost":true}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/assignments/mine/list":{"get":{"summary":"GET /assignments/mine/list?limit=10&offset=0","description":"Karigar's own jobs, paginated. (auth: Bearer)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":31,"assign_no":"ASG-2026-00031","status":"ASSIGNED"}],"limit":10,"offset":0,"total":6,"assignments":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/delegates":{"post":{"summary":"POST /delegates","description":"Grant time-boxed COO approval cover. (auth: Bearer + samples.approve)","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"delegateUserId":12,"days":7}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/reports/stock":{"get":{"summary":"GET /reports/stock?limit=10&offset=0","description":"Per-lot received/verified/available. (auth: Bearer + reports.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"lot_id":55,"received":500,"verified":485,"available":333}],"limit":10,"offset":0,"total":40,"stock":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/reports/available":{"get":{"summary":"GET /reports/available?limit=10&offset=0","description":"COO-approved lots with qty left to assign. (auth: Bearer + reports.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[],"limit":10,"offset":0,"total":0,"lots":[]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/reports/overdue":{"get":{"summary":"GET /reports/overdue?limit=10&offset=0","description":"Unverified >48h + unacked >72h. (auth: Bearer + reports.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"limit":10,"offset":0,"overdueVerification":[],"unackedAssignments":[]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/reports/cost-summary":{"get":{"summary":"GET /reports/cost-summary?limit=10&offset=0","description":"Landed cost per entry (recomputable from components). (auth: Bearer + reports.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"entry_no":"RF-2026-00021","total_m":500,"total_rs":63000,"per_m_rs":126}],"limit":10,"offset":0,"total":9,"summary":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/reports/lot/{id}/timeline":{"get":{"summary":"GET /reports/lot/:id/timeline","description":"Full lot timeline: ledger + history + audit. (auth: Bearer + reports.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"lot":{},"ledger":[],"history":[],"audit":[]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/audit":{"get":{"summary":"GET /audit?limit=10&offset=0&entity=&id=","description":"Audit trail, optionally filtered by entity (e.g. ?entity=supply&id=11). (auth: Bearer + audit.view)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"action":"supply_created","entity_type":"supply","entity_id":"11"}],"limit":10,"offset":0,"total":120,"audit":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/customers":{"get":{"summary":"GET /sales/customers?q=9876&limit=8&offset=0","description":"Search customers by name or phone (autocomplete for the sales form). (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":1,"name":"ABC Customer","phone_e164":"+919876543210"}],"limit":8,"offset":0,"total":1,"customers":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"post":{"summary":"POST /sales/customers","description":"Add a customer. Contact number required and unique — 409 when it already exists. Address: area/city text, state from the all-India list, 6-digit pincode. (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"name":"ABC Customer","phone":"9876543210","area":"Ring Road","city":"Surat","state":"Gujarat","pincode":"395002"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1,"name":"ABC Customer","phone":"+919876543210","area":"Ring Road","city":"Surat","state":"Gujarat","pincode":"395002"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/next-invoice":{"get":{"summary":"GET /sales/next-invoice","description":"Advisory next invoice number for the form header (authoritative number is assigned on save). (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"invoiceNo":"INV-0001"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/invoices":{"post":{"summary":"POST /sales/invoices","description":"Save a sale: customerId or name/phone (+ optional details) plus 1–100 item rows {pcs, rate}. Amounts auto-computed; unknown phones create the customer, known phones reuse it. Optional: gstType IGST (5%) or CGST_SGST (2.5+2.5, default for Maharashtra), discountType percent/flat/per_piece + discountValue (discount on subtotal, GST on the net), agentId (type Agent) + commType percent (of net) / flat (per pc x pcs). (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"date":"2026-10-15","customerId":1,"items":[{"pcs":2,"rate":100},{"pcs":4,"rate":200}],"gstType":"IGST","discountType":"percent","discountValue":10,"agentId":2,"commType":"percent","commValue":2}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1,"invoiceNo":"INV-0001","totalPcs":6,"totalPaise":100000}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"get":{"summary":"GET /sales/invoices?q=ABC&limit=20&offset=0","description":"Sales history — search by invoice number, customer name, phone, or exact date (&date=YYYY-MM-DD). (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"invoice_no":"INV-0001","customer_name":"ABC Customer","total_pcs":12,"total_paise":280000}],"limit":20,"offset":0,"total":1,"sales":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/invoices/{id}":{"get":{"summary":"GET /sales/invoices/:id","description":"One sale with its item lines (used for View / Edit / Print). (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"invoice":{"invoice_no":"INV-0001","total_pcs":12,"total_paise":280000},"items":[{"lineNo":1,"pcs":2,"ratePaise":10000,"amountPaise":20000}]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"patch":{"summary":"PATCH /sales/invoices/:id","description":"Edit date, customer snapshot, item rows (pcs/rate plus per-line discountType percent/flat + discountValue), bill discount, GST, agent/commission. Totals recomputed. (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"items":[{"pcs":3,"rate":150,"discountType":"percent","discountValue":10}],"gstType":"CGST_SGST"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"delete":{"summary":"DELETE /sales/invoices/:id","description":"Delete a sale and its lines (client confirms first). (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"deleted":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/customers/{id}/ledger":{"get":{"summary":"GET /sales/customers/:id/ledger","description":"Customer statement — sales (bill), GR returns and payments received in one date-wise list with running balance. Balance = billed − returned − received. (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"entries":[{"kind":"sale","date":"2026-10-15","ref":"INV-0001","pcs":12,"billPaise":280000,"type":"sale","receivedPaise":0,"mop":null,"account":null,"balancePaise":280000}],"totals":{"billedPaise":280000,"returnedPaise":0,"receivedPaise":0,"balancePaise":280000}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/accounts":{"get":{"summary":"GET /sales/accounts","description":"Shop cash/bank accounts (payment destinations). (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"items":[{"id":1,"name":"Main Cash"}],"accounts":["...same as items..."]}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}},"post":{"summary":"POST /sales/accounts","description":"Add an account — same fields as the customer form (name + phone/address/note). type General/Cash/Bank/Agent (agents earn commission). (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"name":"Agent Raju","type":"Agent","phone":"9811111111"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":2,"name":"Agent Raju","type":"Agent"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/payments":{"post":{"summary":"POST /sales/payments","description":"Record money received: receipt auto-numbered (RCPT-), MOP Cash/Bank, optional To-account and optional against-bill link (invoiceId). (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"customerId":1,"amount":500,"mop":"Cash","accountId":1,"invoiceId":3}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1,"receiptNo":"RCPT-0001"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/payments/{id}":{"delete":{"summary":"DELETE /sales/payments/:id","description":"Delete a received entry (balances update). (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"deleted":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/returns":{"post":{"summary":"POST /sales/returns","description":"Goods return (GR): auto-numbered (GR-), optional link to a bill, reduces what the customer owes. (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"customerId":1,"invoiceId":3,"pcs":2,"amount":200}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1,"grNo":"GR-0001"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/returns/{id}":{"delete":{"summary":"DELETE /sales/returns/:id","description":"Delete a GR entry (balances update). (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"deleted":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/accounts/{id}/ledger":{"get":{"summary":"GET /sales/accounts/:id/ledger","description":"Agent statement — commission earned per bill minus payouts, oldest first with running balance payable. (auth: none)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"entries":[{"kind":"commission","date":"2026-10-15","ref":"INV-0001","commPaise":1800,"paidPaise":0,"balancePaise":1800}],"totals":{"earnedPaise":1800,"paidPaise":0,"balancePaise":1800}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/agent-payouts":{"post":{"summary":"POST /sales/agent-payouts","description":"Payout to an Agent account: auto-numbered (AGP-), MOP Cash/Bank. (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":{"accountId":2,"amount":500,"mop":"Cash"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"id":1,"payoutNo":"AGP-0001"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/sales/agent-payouts/{id}":{"delete":{"summary":"DELETE /sales/agent-payouts/:id","description":"Delete a payout (balances update). (auth: none (rate-limited))","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"deleted":1}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}}}}