5578 lines
148 KiB
JSON
5578 lines
148 KiB
JSON
{
|
|
"info": {
|
|
"name": "General Ledger Service",
|
|
"description": "Full request catalog for every endpoint in the General Ledger Service API (v1) - success and failure variations. Uses collection variables {{baseUrl}} and {{apiKey}}; every request (except /health, /swagger, /dev-tools/generate-api-key) sends X-Api-Key automatically via the collection-level auth/header.",
|
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
},
|
|
"variable": [
|
|
{
|
|
"key": "baseUrl",
|
|
"value": "https://localhost:7024/api/v1"
|
|
},
|
|
{
|
|
"key": "apiKey",
|
|
"value": "dev-only-api-key-please-change-me"
|
|
}
|
|
],
|
|
"auth": {
|
|
"type": "apikey",
|
|
"apikey": [
|
|
{
|
|
"key": "key",
|
|
"value": "X-Api-Key"
|
|
},
|
|
{
|
|
"key": "value",
|
|
"value": "{{apiKey}}"
|
|
},
|
|
{
|
|
"key": "in",
|
|
"value": "header"
|
|
}
|
|
]
|
|
},
|
|
"item": [
|
|
{
|
|
"name": "Accounts",
|
|
"item": [
|
|
{
|
|
"name": "Create Account - success (root)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"Test Doc Account Root\",\n \"accountTypeId\": 1,\n \"isControlAccount\": false,\n \"isPostable\": true,\n \"currencyCode\": \"LKR\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts"
|
|
]
|
|
},
|
|
"description": "201 Created. parentAccountId omitted -> root account. currencyCode omitted would default to LKR (see next request). accountCode is system-generated (retrofit 2026-07-20, CLAUDE.md Rule 8.1) - e.g. 1000 for the first root Asset account; substitute the actual returned value (<rootAccountCode> below) into the requests that follow."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Account - success (child, currencyCode omitted -> defaults LKR)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"Test Doc Account Child\",\n \"parentAccountId\": 1,\n \"accountTypeId\": 1,\n \"isControlAccount\": false,\n \"isPostable\": true\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts"
|
|
]
|
|
},
|
|
"description": "201 Created. Depth-guard (COA_MAX_DEPTH/COA_ENFORCE_MAX_DEPTH) runs because parentAccountId is present. accountCode is system-generated as {parentCode}-01 (<childAccountCode> below)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Account - 400 invalid accountTypeId",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"bad type\",\n \"accountTypeId\": 9,\n \"isControlAccount\": false,\n \"isPostable\": true,\n \"currencyCode\": \"LKR\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid accountTypeId '9'. Must be one of: 1 (Asset), 2 (Liability), 3 (Equity), 4 (Income), 5 (Expense).\" New validation added by the 2026-07-20 code-generation retrofit (accountTypeId now has to map to a known LKAS block to generate a root code)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Account - 404 parentAccountId not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"orphan\",\n \"parentAccountId\": 999999,\n \"accountTypeId\": 1,\n \"isControlAccount\": false,\n \"isPostable\": true,\n \"currencyCode\": \"LKR\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Parent account not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Account - 400 max COA depth exceeded",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"too deep\",\n \"parentAccountId\": <id of an account already at COA_MAX_DEPTH>,\n \"accountTypeId\": 1,\n \"isControlAccount\": false,\n \"isPostable\": true,\n \"currencyCode\": \"LKR\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Maximum Chart of Accounts depth exceeded\". Only fires when resulting depth > system_config COA_MAX_DEPTH AND COA_ENFORCE_MAX_DEPTH=TRUE. Not exercised with a live request here (would require building a chain COA_MAX_DEPTH levels deep first) - included for completeness; see DepthGuard.EnsureWithinMaxDepthAsync in Services/DepthGuard.cs."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Account - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<rootAccountCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<rootAccountCode>"
|
|
]
|
|
},
|
|
"description": "200. Returns { account, ancestorBreadcrumb, descendantTree: null }. <rootAccountCode> = the accountCode returned by \"Create Account - success (root)\" above (system-generated, e.g. 1000)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Account - success (?expand=tree)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<rootAccountCode>?expand=tree",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<rootAccountCode>"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "expand",
|
|
"value": "tree"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Same shape, descendantTree populated with the recursive subtree."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Account - 404 not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/NOTFOUND-XYZ",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"NOTFOUND-XYZ"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Account not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Accounts - success (no filters, no paging - full list)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts"
|
|
]
|
|
},
|
|
"description": "200. page/pageSize omitted -> both null in response, items = every matching row (no defaulting to page=1/pageSize=20)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Accounts - success (filters + paging)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts?accountTypeId=1&isPostable=true&isActive=true&page=1&pageSize=2",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "accountTypeId",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "isPostable",
|
|
"value": "true"
|
|
},
|
|
{
|
|
"key": "isActive",
|
|
"value": "true"
|
|
},
|
|
{
|
|
"key": "page",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "pageSize",
|
|
"value": "2"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. All four query params optional and independent; page/pageSize only slice when BOTH are supplied."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Account - success (rename)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"Renamed Child\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<childAccountCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<childAccountCode>"
|
|
]
|
|
},
|
|
"description": "200. Only supplied fields change. parentAccountId not in payload -> audit category ADMINISTRATIVE."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Account - success (re-parent, LEDGER audit)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"parentAccountId\": 9\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<childAccountCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<childAccountCode>"
|
|
]
|
|
},
|
|
"description": "200. parentAccountId present and changed -> cycle check + depth check run, audit category LEDGER."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Account - success (clear parent, explicit null)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"parentAccountId\": null\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<childAccountCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<childAccountCode>"
|
|
]
|
|
},
|
|
"description": "200. Distinguishing 'field omitted' from 'field explicitly null' matters here - omitting parentAccountId entirely leaves it unchanged; sending null clears it. Handled via ParentAccountIdProvided in AccountService (payload.ContainsKey check)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Account - 400 self-parent",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"parentAccountId\": 10\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<childAccountCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<childAccountCode>"
|
|
]
|
|
},
|
|
"description": "400 (send the child account's own id as its new parentAccountId). Message: \"An account cannot be its own parent\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Account - 400 cycle (new parent is a descendant)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"parentAccountId\": 10\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<rootAccountCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<rootAccountCode>"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Cannot re-parent: the new parent is a descendant of this account\". (the root account is currently an ancestor of the child account; trying to make the child its parent would create a cycle.)"
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Account - success (accountCode in body is silently ignored - immutable)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"Still Renamed\",\n \"accountCode\": \"9999-HACK\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/<childAccountCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"<childAccountCode>"
|
|
]
|
|
},
|
|
"description": "200. accountName updates normally; accountCode is not part of UpdateAccountRequest at all (retrofit 2026-07-20, CLAUDE.md Rule 8.1) so the accountCode key in the body is simply never read - response's accountCode is unchanged from before this call, not '9999-HACK'."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Account - 404 not found",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"x\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/accounts/NOTFOUND-XYZ",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"accounts",
|
|
"NOTFOUND-XYZ"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Account not found\"."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Fiscal Calendar",
|
|
"item": [
|
|
{
|
|
"name": "Create Fiscal Year - success (autoGeneratePeriods=false)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"yearName\": \"FY2035-Doc\",\n \"startDate\": \"2035-01-01\",\n \"endDate\": \"2035-12-31\",\n \"autoGeneratePeriods\": false\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years"
|
|
]
|
|
},
|
|
"description": "201. data.generatedPeriods is [] when autoGeneratePeriods is false/omitted."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Fiscal Year - success (autoGeneratePeriods=true, periodCount omitted -> 12)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"yearName\": \"FY2036-Doc\",\n \"startDate\": \"2036-01-01\",\n \"endDate\": \"2036-12-31\",\n \"autoGeneratePeriods\": true\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years"
|
|
]
|
|
},
|
|
"description": "201. data.generatedPeriods has 12 monthly periods, internally calling the same BuildPeriodsAsync logic as the standalone periods endpoint."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Fiscal Year - 400 endDate <= startDate",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"yearName\": \"bad\",\n \"startDate\": \"2030-01-01\",\n \"endDate\": \"2030-01-01\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years"
|
|
]
|
|
},
|
|
"description": "400. Message: \"End date must be after start date\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Fiscal Year - 400 overlapping range",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"yearName\": \"overlap\",\n \"startDate\": \"2035-06-01\",\n \"endDate\": \"2035-08-01\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Fiscal year date range overlaps with an existing fiscal year\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Generate Periods - success",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodCount\": 12\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years/5/periods",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years",
|
|
"5",
|
|
"periods"
|
|
]
|
|
},
|
|
"description": "201. Extends/adds periods to an existing fiscal year that has none yet (or fewer than a full year)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Generate Periods - 404 fiscal year not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodCount\": 12\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years/999999/periods",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years",
|
|
"999999",
|
|
"periods"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Fiscal year not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Generate Periods - 400 exceeds fiscal year date range",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodCount\": 1\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years/5/periods",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years",
|
|
"5",
|
|
"periods"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Requested period count exceeds the fiscal year's date range\". Fires when generating past a fiscal year already fully covered by prior period generation."
|
|
}
|
|
},
|
|
{
|
|
"name": "Lock Period - success LOCK",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"LOCK\",\n \"reason\": \"Doc test lock\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/periods/37/lock",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"periods",
|
|
"37",
|
|
"lock"
|
|
]
|
|
},
|
|
"description": "200. action one of LOCK|UNLOCK|SOFT_CLOSE|CLOSE (case-insensitive). Writes both a period_lock_log row and an audit_log row (category LEDGER)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Lock Period - success UNLOCK (reopen)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"UNLOCK\",\n \"reason\": \"reopen\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/periods/37/lock",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"periods",
|
|
"37",
|
|
"lock"
|
|
]
|
|
},
|
|
"description": "200. UNLOCK is the only action accepted on a CLOSED period (see the 400 case below)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Lock Period - success CLOSE",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"CLOSE\",\n \"reason\": \"year end\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/periods/37/lock",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"periods",
|
|
"37",
|
|
"lock"
|
|
]
|
|
},
|
|
"description": "200."
|
|
}
|
|
},
|
|
{
|
|
"name": "Lock Period - 400 action other than UNLOCK on a CLOSED period",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"LOCK\",\n \"reason\": \"x\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/periods/37/lock",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"periods",
|
|
"37",
|
|
"lock"
|
|
]
|
|
},
|
|
"description": "400 (run right after the CLOSE request above). Message: \"Period is closed; only Unlock is allowed to reopen it\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Lock Period - 400 invalid action",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"BOGUS\",\n \"reason\": \"x\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/periods/37/lock",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"periods",
|
|
"37",
|
|
"lock"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid action 'BOGUS'. Must be one of: Lock, Unlock, SoftClose, Close.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Lock Period - 404 not found",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"LOCK\",\n \"reason\": \"x\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/periods/999999/lock",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"periods",
|
|
"999999",
|
|
"lock"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Period not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Period By Date - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years/periods/by-date?date=2028-01-15",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years",
|
|
"periods",
|
|
"by-date"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "date",
|
|
"value": "2028-01-15"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Returns the AccountingPeriod covering the given date (periodId, fiscalYearId, periodNumber, startDate, endDate, status, lockGl). Added Phase 38 for callers (e.g. ERPCore GRN module) that need a periodId for POST /journal-entries."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Period By Date - 404 not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fiscal-years/periods/by-date?date=1999-01-01",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fiscal-years",
|
|
"periods",
|
|
"by-date"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "date",
|
|
"value": "1999-01-01"
|
|
}
|
|
]
|
|
},
|
|
"description": "404. Message: \"Period not found\". No fiscal year/period covers this date."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Currency",
|
|
"item": [
|
|
{
|
|
"name": "List Currencies - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/currencies",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"currencies"
|
|
]
|
|
},
|
|
"description": "200. Flat list, all 142 seeded ISO 4217 currencies, no filters/paging. Exactly one row has isBaseCurrency:true (LKR)."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Cost Centers",
|
|
"item": [
|
|
{
|
|
"name": "Create Cost Center - success (root)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"name\": \"Doc Test Dept\",\n \"isActive\": true\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cost-centers",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cost-centers"
|
|
]
|
|
},
|
|
"description": "201. code is system-generated (retrofit 2026-07-20, CLAUDE.md Rule 8.1) - e.g. CC-001 for the first root cost center; substitute the returned value (<rootCostCenterCode> below) into the requests that follow."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cost Center - success (child)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"name\": \"Doc Test Sub-Dept\",\n \"parentCostCenterId\": 1,\n \"isActive\": true\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cost-centers",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cost-centers"
|
|
]
|
|
},
|
|
"description": "201. Depth-guard runs against COST_CENTER_MAX_DEPTH (unconditional - no separate enforce flag for cost centers, unlike accounts). code is system-generated as {parentCode}-01."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cost Center - 404 parent not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"name\": \"orphan\",\n \"parentCostCenterId\": 999999,\n \"isActive\": true\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cost-centers",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cost-centers"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Parent cost center not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cost Center - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cost-centers/<rootCostCenterCode>",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cost-centers",
|
|
"<rootCostCenterCode>"
|
|
]
|
|
},
|
|
"description": "200. data: { costCenter, ancestorBreadcrumb, descendantTree: null }. <rootCostCenterCode> = the code returned by \"Create Cost Center - success (root)\" above."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cost Center - success (?expand=tree)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cost-centers/<rootCostCenterCode>?expand=tree",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cost-centers",
|
|
"<rootCostCenterCode>"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "expand",
|
|
"value": "tree"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. descendantTree populated."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cost Center - 404 not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cost-centers/NOTFOUND-XYZ",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cost-centers",
|
|
"NOTFOUND-XYZ"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Cost center not found\"."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Journal Entries (core double-entry action)",
|
|
"item": [
|
|
{
|
|
"name": "Create - success (plain, balanced lines - created AND posted in one call)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-06\",\n \"journalType\": \"MANUAL\",\n \"reference\": \"DOC-TEST-2\",\n \"narration\": \"Inline lines\",\n \"preparedBy\": \"Doc Tester\",\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 100, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 100 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "201. CLAUDE.md Rule 14 (retrofit 2026-07-22): there is no client-facing DRAFT state anymore - this one call validates, inserts, balance-checks, period-lock-checks, and posts, all in one transaction. Response has isPosted:true, entryStatus:2 (Posted) immediately. lines use accountCode/costCenterCode (Rule 8.2), not accountId/costCenterId. journalNo is server-generated (JV-{year}-{6-digit sequence}), never client-supplied."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 400 lines missing/empty",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-05\",\n \"journalType\": \"MANUAL\",\n \"reference\": \"DOC-TEST-1\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "400. Message: \"lines is required\". A plain create (no correction) with no lines is no longer a valid DRAFT - lines are mandatory now that create-and-post is one call."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 400 lines don't balance (checked immediately, not deferred to a separate post)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-07\",\n \"journalType\": \"MANUAL\",\n \"reference\": \"DOC-TEST-3\",\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 100, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 75 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Journal entry does not balance\". Checked inside the same transaction as the insert, so the whole call rolls back - nothing is left half-inserted (unlike the old create-then-post flow, where an unbalanced draft could sit around indefinitely)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 404 period not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 999999,\n \"entryDate\": \"2035-01-01\",\n \"journalType\": \"MANUAL\",\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 10, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 10 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Period not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 400 period is locked/closed",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 37,\n \"entryDate\": \"2031-01-01\",\n \"journalType\": \"MANUAL\",\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 10, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 10 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "400 (periodId 37 is Locked or Closed). Message: \"Period is locked\". Checked AFTER the balance check, inside the same transaction - an unbalanced entry in a locked period returns the balance error first."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 404 an accountCode in lines doesn't exist",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-07\",\n \"journalType\": \"MANUAL\",\n \"lines\": [\n { \"accountCode\": \"NOTFOUND-XYZ\", \"debitAmount\": 10, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 10 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Account NOTFOUND-XYZ not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 400 an accountCode in lines isn't postable",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-07\",\n \"journalType\": \"MANUAL\",\n \"lines\": [\n { \"accountCode\": \"<nonPostableAccountCode>\", \"debitAmount\": 10, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 10 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Account <code> is not postable\" (a control/header account with isPostable:false)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - success (correction, FullReversal against a posted entry - auto-flips the original)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-08\",\n \"journalType\": \"CORRECTION\",\n \"reference\": \"REV-JE2\",\n \"correction\": {\n \"originalJournalNo\": \"JV-2035-000001\",\n \"correctionType\": \"FullReversal\",\n \"reason\": \"Wrong account used\",\n \"correctedBy\": \"Doc Tester\"\n }\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "201. correction.originalJournalNo (retrofit 2026-07-22, replaces originalJournalId) must reference an already-Posted entry. Lines are auto-generated by mirroring the original's lines with debit/credit swapped - request.lines is ignored for a correction. Created AND posted in this one call (Rule 14); in the SAME transaction, the ORIGINAL entry flips to entryStatus:3 (Reversed), isReversed:true - verified by re-fetching the original afterward. Two audit_log rows written: one Reverse for this entry's create+post, one Reverse against the original."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - success (correction, PartialAdjustment - lines now required inline, no separate Set Lines call)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-09\",\n \"journalType\": \"CORRECTION\",\n \"reference\": \"ADJ-JE1\",\n \"correction\": {\n \"originalJournalNo\": \"JV-2035-000002\",\n \"correctionType\": \"PartialAdjustment\",\n \"reason\": \"Add missing accrual\",\n \"correctedBy\": \"Doc Tester\"\n },\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 50, \"creditAmount\": 0, \"memo\": \"accrual\" },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 50 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "201. PUT .../lines is removed entirely (Rule 14) - a PartialAdjustment now takes its lines inline in the same body as a plain create, and is created AND posted immediately. The ORIGINAL entry's status is untouched by this call (only FullReversal flips the original)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 400 correction against a non-posted (or already-reversed) original",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-10\",\n \"journalType\": \"CORRECTION\",\n \"correction\": {\n \"originalJournalNo\": \"JV-2035-000001\",\n \"correctionType\": \"FullReversal\",\n \"reason\": \"x\",\n \"correctedBy\": \"y\"\n }\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "400 (run after JV-2035-000001 has already been reversed by the request above, so its status is now Reversed, not Posted). Message: \"Original journal entry must be posted before it can be corrected\" - same message whether the original was never posted or has already been reversed."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create - 400 invalid correctionType",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"periodId\": 49,\n \"entryDate\": \"2035-01-10\",\n \"journalType\": \"CORRECTION\",\n \"correction\": {\n \"originalJournalNo\": \"JV-2035-000002\",\n \"correctionType\": \"BOGUS\",\n \"reason\": \"x\",\n \"correctedBy\": \"y\"\n }\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid correctionType 'BOGUS'. Must be one of: FullReversal, PartialAdjustment.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "PUT .../lines (REMOVED, CLAUDE.md Rule 14) - 404, route no longer exists",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "[\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 50, \"creditAmount\": 0 }\n]"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries/JV-2035-000001/lines",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries",
|
|
"JV-2035-000001",
|
|
"lines"
|
|
]
|
|
},
|
|
"description": "404 (no matching route). Kept in this collection deliberately, as a regression check - confirms the retrofit actually removed the route rather than leaving it silently reachable. lines are now supplied inline in POST /journal-entries."
|
|
}
|
|
},
|
|
{
|
|
"name": "PUT .../post (REMOVED, CLAUDE.md Rule 14) - 404, route no longer exists",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries/JV-2035-000001/post",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries",
|
|
"JV-2035-000001",
|
|
"post"
|
|
]
|
|
},
|
|
"description": "404 (no matching route). Kept as a regression check, same reasoning as the removed .../lines route above - POST /journal-entries now creates AND posts in one call."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get by journalNo - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries/JV-2035-000002",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries",
|
|
"JV-2035-000002"
|
|
]
|
|
},
|
|
"description": "200. Looked up by journalNo, not the numeric journalId (CLAUDE.md Rule 8). Includes lines[]."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get by journalNo - 404 not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/journal-entries/JV-NOTFOUND",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"journal-entries",
|
|
"JV-NOTFOUND"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Journal entry not found\"."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Recurring Entries",
|
|
"item": [
|
|
{
|
|
"name": "Create Template - success",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"templateName\": \"Doc Monthly Rent\",\n \"recurrencePattern\": \"Monthly\",\n \"nextRunDate\": \"2026-07-01\",\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 30, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 30 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates"
|
|
]
|
|
},
|
|
"description": "201. recurrencePattern one of Monthly|Quarterly|Annual. lines use accountCode (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replaces accountId; stored internally as the resolved numeric AccountId). Lines must balance at creation time (unlike journal entries, which balance-check at create time too now, but this template-level check is independent - a template is never itself posted)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Template - 400 lines don't balance",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"templateName\": \"bad\",\n \"recurrencePattern\": \"Monthly\",\n \"nextRunDate\": \"2026-08-01\",\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 30, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 20 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Recurring template lines do not balance\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Template - 400 invalid recurrencePattern",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"templateName\": \"bad\",\n \"recurrencePattern\": \"Weekly\",\n \"nextRunDate\": \"2026-08-01\",\n \"lines\": [\n { \"accountCode\": \"<accountCode1>\", \"debitAmount\": 30, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 30 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid recurrencePattern 'Weekly'. Must be one of: Monthly, Quarterly, Annual.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Template - 400 non-postable account in a line",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"templateName\": \"bad\",\n \"recurrencePattern\": \"Monthly\",\n \"nextRunDate\": \"2026-08-01\",\n \"lines\": [\n { \"accountCode\": \"<nonPostableAccountCode>\", \"debitAmount\": 30, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 30 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Account <code> is not postable\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Template - 404 account not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"templateName\": \"bad\",\n \"recurrencePattern\": \"Monthly\",\n \"nextRunDate\": \"2026-08-01\",\n \"lines\": [\n { \"accountCode\": \"NOTFOUND-XYZ\", \"debitAmount\": 30, \"creditAmount\": 0 },\n { \"accountCode\": \"<accountCode2>\", \"debitAmount\": 0, \"creditAmount\": 30 }\n ]\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Account NOTFOUND-XYZ not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Generate - success (due, nextRunDate <= today - now generates AND posts in one call)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates/3/generate",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates",
|
|
"3",
|
|
"generate"
|
|
]
|
|
},
|
|
"description": "201 (retrofit 2026-07-22). Resolves 'today's' period via GetPeriodByDateAsync, resolves each line's stored numeric AccountId back to its AccountCode, delegates to IJournalEntryService.CreateAsync (journalType RECURLING) - which now creates AND posts in the same call (Rule 14), so the generated entry comes back already Posted, not a DRAFT needing a separate PUT .../post (removed). Then advances the template's nextRunDate by its recurrencePattern (+1 month/+3 months/+1 year) so a repeat call the same day doesn't duplicate."
|
|
}
|
|
},
|
|
{
|
|
"name": "Generate - 400 not yet due",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates/3/generate",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates",
|
|
"3",
|
|
"generate"
|
|
]
|
|
},
|
|
"description": "400 (call a second time right after the success case - nextRunDate has already advanced past today). Message: \"Recurring template is not yet due to run\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Generate - 404 template not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/recurring-templates/999999/generate",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"recurring-templates",
|
|
"999999",
|
|
"generate"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Recurring template not found\"."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Tax",
|
|
"item": [
|
|
{
|
|
"name": "Create Tax Code - success",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"taxTypeCode\": \"VAT\",\n \"name\": \"VAT 18% Doc\",\n \"ratePercent\": 18,\n \"calculationBasis\": \"Exclusive\",\n \"appliesTo\": \"Sales\",\n \"effectiveFrom\": \"2026-01-01\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes"
|
|
]
|
|
},
|
|
"description": "201. taxTypeCode (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replaces taxTypeId). calculationBasis: Exclusive|Inclusive. appliesTo: Sales|Purchase|Both. outputTaxAccountCode/inputTaxAccountCode/effectiveTo all optional. code is system-generated as {TaxType.Code}-{seq:D3}, e.g. VAT-001 (retrofit 2026-07-20, CLAUDE.md Rule 8.1) - no longer accepted in the request body, and a duplicate-code scenario is now structurally unreachable (see 03_Progress_Tracker.md)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Tax Code - success (with output/input tax account codes)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"taxTypeCode\": \"WHT\",\n \"name\": \"WHT 5% Doc\",\n \"ratePercent\": 5,\n \"calculationBasis\": \"Exclusive\",\n \"appliesTo\": \"Purchase\",\n \"outputTaxAccountCode\": \"<accountCode1>\",\n \"inputTaxAccountCode\": \"<accountCode2>\",\n \"effectiveFrom\": \"2026-01-01\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes"
|
|
]
|
|
},
|
|
"description": "201. outputTaxAccountCode/inputTaxAccountCode (retrofit 2026-07-22, replace outputTaxAccountId/inputTaxAccountId) resolved via IAccountRepository.GetByCodeAsync."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Tax Code - 400 effectiveFrom missing",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"taxTypeCode\": \"VAT\",\n \"name\": \"x\",\n \"ratePercent\": 18,\n \"calculationBasis\": \"Exclusive\",\n \"appliesTo\": \"Sales\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes"
|
|
]
|
|
},
|
|
"description": "400. Message: \"effectiveFrom is required\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Tax Code - 404 taxTypeCode not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"taxTypeCode\": \"NOTFOUND\",\n \"name\": \"x\",\n \"ratePercent\": 18,\n \"calculationBasis\": \"Exclusive\",\n \"appliesTo\": \"Sales\",\n \"effectiveFrom\": \"2026-01-01\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Tax type not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Tax Code - 404 outputTaxAccountCode/inputTaxAccountCode not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"taxTypeCode\": \"VAT\",\n \"name\": \"x\",\n \"ratePercent\": 18,\n \"calculationBasis\": \"Exclusive\",\n \"appliesTo\": \"Sales\",\n \"outputTaxAccountCode\": \"NOTFOUND-XYZ\",\n \"effectiveFrom\": \"2026-01-01\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Output tax account not found\" (input side reports \"Input tax account not found\")."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Tax Code - 400 invalid calculationBasis/appliesTo",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"taxTypeCode\": \"VAT\",\n \"name\": \"x\",\n \"ratePercent\": 18,\n \"calculationBasis\": \"Bogus\",\n \"appliesTo\": \"Sales\",\n \"effectiveFrom\": \"2026-01-01\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid calculationBasis 'Bogus'. Must be one of: Exclusive, Inclusive.\" (appliesTo validated the same way, checked second)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Calculate Tax - success (Exclusive: 1000 @ 18% = 180)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes/calculate?taxCode=VAT-001&amount=1000&txnDate=2026-07-19",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes",
|
|
"calculate"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "taxCode",
|
|
"value": "VAT-001"
|
|
},
|
|
{
|
|
"key": "amount",
|
|
"value": "1000"
|
|
},
|
|
{
|
|
"key": "txnDate",
|
|
"value": "2026-07-19"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. taxCode (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replaces taxCodeId). Exclusive: taxAmount = amount * rate/100. Inclusive: taxAmount = amount - (amount / (1 + rate/100)) - i.e. amount is treated as already including tax. No persistence - pure computation."
|
|
}
|
|
},
|
|
{
|
|
"name": "Calculate Tax - 404 not found / not effective for date",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes/calculate?taxCode=VAT-001&amount=1000&txnDate=2020-01-01",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes",
|
|
"calculate"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "taxCode",
|
|
"value": "VAT-001"
|
|
},
|
|
{
|
|
"key": "amount",
|
|
"value": "1000"
|
|
},
|
|
{
|
|
"key": "txnDate",
|
|
"value": "2020-01-01"
|
|
}
|
|
]
|
|
},
|
|
"description": "404. Message: \"Tax code not found or not effective for the given date\" - same message whether taxCode doesn't exist at all, or exists but txnDate falls outside [effectiveFrom, effectiveTo]."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Tax Types - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-types",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-types"
|
|
]
|
|
},
|
|
"description": "200. Flat list, all 5 seeded rows (VAT, WHT, NBT, SSCL, INCOME_TAX)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Tax Codes - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/tax-codes",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"tax-codes"
|
|
]
|
|
},
|
|
"description": "200. Flat list, every tax code ever created, no filters/paging."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Fixed Assets",
|
|
"item": [
|
|
{
|
|
"name": "Create Asset - success (StraightLine)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"assetName\": \"Doc Test Laptop\",\n \"assetAccountCode\": \"<accountCode1>\",\n \"accumDeprAccountCode\": \"<accountCode2>\",\n \"deprExpenseAccountCode\": \"<accountCode3>\",\n \"gainLossAccountCode\": \"<accountCode4>\",\n \"acquisitionDate\": \"2035-01-01\",\n \"acquisitionCost\": 12000,\n \"residualValue\": 0,\n \"usefulLifeMonths\": 12,\n \"depreciationMethod\": \"StraightLine\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets"
|
|
]
|
|
},
|
|
"description": "201. All four account fields take a code now (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replace assetAccountId/accumDeprAccountId/deprExpenseAccountId/gainLossAccountId). StraightLine: depreciationRatePercent not needed. Formula per period: (acquisitionCost - residualValue) / usefulLifeMonths. assetCode is system-generated (retrofit 2026-07-20, CLAUDE.md Rule 8.1) - e.g. FA-000001; substitute the returned value (<firstAssetCode> below) into the transaction requests that follow."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Asset - success (ReducingBalance)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"assetName\": \"Doc Test Server\",\n \"assetAccountCode\": \"<accountCode1>\",\n \"accumDeprAccountCode\": \"<accountCode2>\",\n \"deprExpenseAccountCode\": \"<accountCode3>\",\n \"gainLossAccountCode\": \"<accountCode4>\",\n \"acquisitionDate\": \"2035-01-01\",\n \"acquisitionCost\": 10000,\n \"residualValue\": 1000,\n \"usefulLifeMonths\": 24,\n \"depreciationMethod\": \"ReducingBalance\",\n \"depreciationRatePercent\": 20\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets"
|
|
]
|
|
},
|
|
"description": "201. ReducingBalance requires depreciationRatePercent. Formula per period: (acquisitionCost - priorAccumulatedDepreciation) * (rate/100), clamped so accumulated depreciation never exceeds acquisitionCost - residualValue. assetCode is system-generated; substitute the returned value (<secondAssetCode> below) into the transaction requests that follow."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Asset - 400 ReducingBalance missing rate",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"assetName\": \"bad\",\n \"assetAccountCode\": \"<accountCode1>\",\n \"accumDeprAccountCode\": \"<accountCode2>\",\n \"deprExpenseAccountCode\": \"<accountCode3>\",\n \"gainLossAccountCode\": \"<accountCode4>\",\n \"acquisitionDate\": \"2035-01-01\",\n \"acquisitionCost\": 5000,\n \"residualValue\": 0,\n \"usefulLifeMonths\": 12,\n \"depreciationMethod\": \"ReducingBalance\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets"
|
|
]
|
|
},
|
|
"description": "400. Message: \"depreciationRatePercent is required when depreciationMethod is ReducingBalance\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Asset - 400 residualValue > acquisitionCost",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"assetName\": \"bad\",\n \"assetAccountCode\": \"<accountCode1>\",\n \"accumDeprAccountCode\": \"<accountCode2>\",\n \"deprExpenseAccountCode\": \"<accountCode3>\",\n \"gainLossAccountCode\": \"<accountCode4>\",\n \"acquisitionDate\": \"2035-01-01\",\n \"acquisitionCost\": 1000,\n \"residualValue\": 5000,\n \"usefulLifeMonths\": 12,\n \"depreciationMethod\": \"StraightLine\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets"
|
|
]
|
|
},
|
|
"description": "400. Message: \"residualValue cannot exceed acquisitionCost\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Asset - 400 an account is not postable",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"assetName\": \"bad\",\n \"assetAccountCode\": \"<nonPostableAccountCode>\",\n \"accumDeprAccountCode\": \"<accountCode2>\",\n \"deprExpenseAccountCode\": \"<accountCode3>\",\n \"gainLossAccountCode\": \"<accountCode4>\",\n \"acquisitionDate\": \"2035-01-01\",\n \"acquisitionCost\": 1000,\n \"residualValue\": 0,\n \"usefulLifeMonths\": 12,\n \"depreciationMethod\": \"StraightLine\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Asset account (<code>) is not postable\" - all four account fields (asset/accumDepr/deprExpense/gainLoss) are checked the same way, whichever fails first is named."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - Depreciate success (StraightLine)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Depreciate\",\n \"periodId\": 49\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/<firstAssetCode>/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"<firstAssetCode>",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "201. (12000-0)/12 = 1000. Builds+creates+posts a real journal entry internally (Debit Depreciation Expense, Credit Accum. Depreciation) via IJournalEntryService, posted as of the period's endDate. Returns the resulting DepreciationSchedule row, which embeds the journal."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - Depreciate success (ReducingBalance)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Depreciate\",\n \"periodId\": 49\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/<secondAssetCode>/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"<secondAssetCode>",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "201. (10000-0)*20% = 2000 (first period; prior accumulated depreciation is 0)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - Depreciate 400 period is locked",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Depreciate\",\n \"periodId\": 37\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/<firstAssetCode>/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"<firstAssetCode>",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Period is locked\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - 404 asset not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Depreciate\",\n \"periodId\": 49\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/NOTFOUND-XYZ/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"NOTFOUND-XYZ",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Fixed asset not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - 400 invalid transactionType",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Bogus\",\n \"periodId\": 49\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/<firstAssetCode>/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"<firstAssetCode>",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid transactionType 'Bogus'. Must be one of: Depreciate, Dispose.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - Dispose success (loss scenario)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Dispose\",\n \"disposalDate\": \"2035-01-20\",\n \"proceeds\": 9000,\n \"proceedsAccountCode\": \"<accountCode1>\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/<firstAssetCode>/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"<firstAssetCode>",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "201. proceedsAccountCode (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replaces proceedsAccountId). Real verified loss scenario: NBV = 12000 acquisition - 1000 depreciated = 11000; proceeds 9000 -> gainLoss = 9000-11000 = -2000 (a loss). Journal (4 lines, balanced at 12000 both sides): Debit proceedsAccount 9000, Debit AccumDepr 1000, Credit Asset 12000, Debit GainLoss 2000. Zero-value lines are omitted entirely (e.g. no proceeds line if proceeds=0) - journal_entry_line's own one-sided check constraint rejects a zero-amount line. Internally the flow now creates AND posts its journal entry in one call (CLAUDE.md Rule 14) - no separate .PostAsync afterward."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - Dispose 400 already disposed",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Dispose\",\n \"disposalDate\": \"2035-01-20\",\n \"proceeds\": 9000,\n \"proceedsAccountCode\": \"<accountCode1>\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/<firstAssetCode>/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"<firstAssetCode>",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Asset has already been disposed\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Transaction - Depreciate 400 asset already disposed",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"transactionType\": \"Depreciate\",\n \"periodId\": 49\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets/<firstAssetCode>/transactions",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets",
|
|
"<firstAssetCode>",
|
|
"transactions"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Asset has been disposed\" (both transaction types are rejected once an asset is Disposed)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Fixed Assets - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/fixed-assets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"fixed-assets"
|
|
]
|
|
},
|
|
"description": "200. Flat list, no filters/paging."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Bank",
|
|
"item": [
|
|
{
|
|
"name": "Create Bank Account - success",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"Doc Test Account\",\n \"bankName\": \"Test Bank\",\n \"accountNumber\": \"999888777\",\n \"glAccountCode\": \"<accountCode1>\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts"
|
|
]
|
|
},
|
|
"description": "201. glAccountCode (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replaces glAccountId). currencyCode omitted -> defaults \"LKR\" (same precedent as Accounts)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Bank Account - 404 GL account not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"bad\",\n \"glAccountCode\": \"NOTFOUND-XYZ\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts"
|
|
]
|
|
},
|
|
"description": "404. Message: \"GL account not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Bank Account - 400 GL account not postable",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"bad\",\n \"glAccountCode\": \"<nonPostableAccountCode>\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts"
|
|
]
|
|
},
|
|
"description": "400. Message: \"GL account (<code>) is not postable\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cash Account - success (cashAccountTypeName matches a seeded type)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"Doc Test Petty Cash\",\n \"cashAccountTypeName\": \"Petty Cash\",\n \"glAccountCode\": \"<accountCode1>\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cash-accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cash-accounts"
|
|
]
|
|
},
|
|
"description": "201. accountNumber omitted -> auto-generated sequential CASH-000001-style reference (CLAUDE.md Rule 8.1). glAccountCode required (CLAUDE.md Rule 8.2)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cash Account - success (cashAccountTypeName is new - creates the type on the fly)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"Doc Test Vault Reserve\",\n \"cashAccountTypeName\": \"Vault Reserve\",\n \"glAccountCode\": \"<accountCode1>\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cash-accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cash-accounts"
|
|
]
|
|
},
|
|
"description": "201. A cashAccountTypeName with no case-insensitive match against cash_account_type adds a new row on the fly - visible afterward via GET /cash-account-types."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cash Account - 400 cashAccountTypeName missing",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"accountName\": \"bad\",\n \"glAccountCode\": \"<accountCode1>\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cash-accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cash-accounts"
|
|
]
|
|
},
|
|
"description": "400. Message: \"cashAccountTypeName is required\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Cash Account Types - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cash-account-types",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cash-account-types"
|
|
]
|
|
},
|
|
"description": "200. Seeded: Petty Cash, Till Cash, Safe Cash, Cash in Transit - grows over time via Create Cash Account above."
|
|
}
|
|
},
|
|
{
|
|
"name": "Import Statement Lines - success (mixed valid + malformed rows)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "formdata",
|
|
"formdata": [
|
|
{
|
|
"key": "file",
|
|
"type": "file",
|
|
"src": "doc_test_statement.xlsx"
|
|
}
|
|
]
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts/2/statement-lines/import",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts",
|
|
"2",
|
|
"statement-lines",
|
|
"import"
|
|
]
|
|
},
|
|
"description": "201 (not 200 - it's a creation). multipart/form-data, field name 'file', .xlsx only. Column layout, header row required: TxnDate | Description | Amount. Verified live: a 2-row file (1 valid date+amount, 1 with a non-numeric Amount) -> data.imported has 1 row, data.failedRows has 1 entry { rowNumber, reason }. Valid rows are saved even when other rows in the same file fail - not all-or-nothing."
|
|
}
|
|
},
|
|
{
|
|
"name": "Import Statement Lines - 400 not a valid .xlsx",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "formdata",
|
|
"formdata": [
|
|
{
|
|
"key": "file",
|
|
"type": "file",
|
|
"src": "corrupt.txt"
|
|
}
|
|
]
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts/2/statement-lines/import",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts",
|
|
"2",
|
|
"statement-lines",
|
|
"import"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Uploaded file is not a valid .xlsx spreadsheet\" - a structurally corrupt/wrong-format file fails outright (unlike a bad row inside a valid .xlsx, which is partially salvaged)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Import Statement Lines - 404 bank account not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "formdata",
|
|
"formdata": [
|
|
{
|
|
"key": "file",
|
|
"type": "file",
|
|
"src": "doc_test_statement.xlsx"
|
|
}
|
|
]
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts/999999/statement-lines/import",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts",
|
|
"999999",
|
|
"statement-lines",
|
|
"import"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Bank account not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Reconcile - success (amounts match)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"matchedJournalNo\": \"JV-2035-000001\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-statement-lines/3/reconcile",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-statement-lines",
|
|
"3",
|
|
"reconcile"
|
|
]
|
|
},
|
|
"description": "200. matchedJournalNo (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replaces matchedJournalId; internal storage stays the numeric journalId). \"Amounts must match\" resolved as: matched journal entry must be Posted; sum its lines posted to the bank account's own glAccountId; compare net (debit-credit) movement to the statement line's Amount. Verified live: a 500-amount statement line reconciled against a journal entry whose net movement on the bank's GL account is exactly 500."
|
|
}
|
|
},
|
|
{
|
|
"name": "Reconcile - 400 already reconciled",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"matchedJournalNo\": \"JV-2035-000001\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-statement-lines/3/reconcile",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-statement-lines",
|
|
"3",
|
|
"reconcile"
|
|
]
|
|
},
|
|
"description": "400 (call a second time on the same line). Message: \"Statement line is already reconciled\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Reconcile - 400 journal entry not posted",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"matchedJournalNo\": \"JV-NONPOSTED\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-statement-lines/2/reconcile",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-statement-lines",
|
|
"2",
|
|
"reconcile"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Journal entry must be posted before it can be used for reconciliation\". Rare in practice now that create-and-post is one call (Rule 14) - would need a journal entry that failed post-time balance/lock checks, which never happens since those checks are now inside the same atomic create call."
|
|
}
|
|
},
|
|
{
|
|
"name": "Reconcile - 400 amount mismatch",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"matchedJournalNo\": \"JV-2035-000002\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-statement-lines/4/reconcile",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-statement-lines",
|
|
"4",
|
|
"reconcile"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Statement line amount (500.00) does not match the journal entry's movement on the bank's GL account (300.00)\" (verified live with a 500 statement line vs a 300-net-movement journal entry)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Reconcile - 404 statement line not found",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"matchedJournalNo\": \"JV-2035-000001\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-statement-lines/999999/reconcile",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-statement-lines",
|
|
"999999",
|
|
"reconcile"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Bank statement line not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Reconcile - 404 journal entry not found",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"matchedJournalNo\": \"JV-NOTFOUND\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-statement-lines/2/reconcile",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-statement-lines",
|
|
"2",
|
|
"reconcile"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Journal entry not found\" (checked before the posted-status check, so a nonexistent journalNo reports 404 not 400)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Cash and Bank Accounts - success (unified, default Both)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts"
|
|
]
|
|
},
|
|
"description": "200 (retrofit 2026-07-22). Combines bank_account + cash_account into one flat, filterable list - { accountType, accountId, accountName, bankName, cashAccountTypeName, accountNumber, glAccountId, currencyCode, createdAt } per row (accountType is response-only, not a stored column)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Cash and Bank Accounts - success (accountType=Bank)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts?accountType=Bank",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "accountType",
|
|
"value": "Bank"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Bank accounts only."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Cash and Bank Accounts - success (accountType=Cash)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts?accountType=Cash",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "accountType",
|
|
"value": "Cash"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Cash accounts only."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Cash and Bank Accounts - 400 invalid accountType",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/bank-accounts?accountType=Bogus",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"bank-accounts"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "accountType",
|
|
"value": "Bogus"
|
|
}
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid accountType 'Bogus'. Must be one of: Cash, Bank, Both.\"."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Budget",
|
|
"item": [
|
|
{
|
|
"name": "Create Budget - success",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"fiscalYearId\": 5,\n \"name\": \"Doc Test Budget 2035\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets"
|
|
]
|
|
},
|
|
"description": "201. lines: [] on creation - always added afterward via the lines endpoint."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Budget - 404 fiscal year not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"fiscalYearId\": 999999,\n \"name\": \"bad\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Fiscal year not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Add Lines - success (raw array body, not wrapped in an object)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "[\n { \"accountCode\": \"<accountCode1>\", \"periodId\": 49, \"budgetedAmount\": 1000 },\n { \"accountCode\": \"<accountCode2>\", \"periodId\": 49, \"costCenterCode\": \"<costCenterCode1>\", \"budgetedAmount\": 500 }\n]"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets/2/lines",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets",
|
|
"2",
|
|
"lines"
|
|
]
|
|
},
|
|
"description": "201. accountCode/costCenterCode (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replace accountId/costCenterId; periodId stays numeric, AccountingPeriod has no unique code column). costCenterCode optional per line. No postable check on the account - budgeting against a summary/control account is allowed here, unlike journal entry lines."
|
|
}
|
|
},
|
|
{
|
|
"name": "Add Lines - success (APPENDS, does not replace existing lines)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "[\n { \"accountCode\": \"<accountCode1>\", \"periodId\": 49, \"budgetedAmount\": 250 }\n]"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets/2/lines",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets",
|
|
"2",
|
|
"lines"
|
|
]
|
|
},
|
|
"description": "201. Deliberately different from Journal Entries' now-removed 'Set Lines' (which replaced wholesale) - budget lines accumulate incrementally over time. Calling this twice results in 3 total lines, not 1."
|
|
}
|
|
},
|
|
{
|
|
"name": "Add Lines - 404 account not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "[\n { \"accountCode\": \"NOTFOUND-XYZ\", \"periodId\": 49, \"budgetedAmount\": 100 }\n]"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets/2/lines",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets",
|
|
"2",
|
|
"lines"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Account NOTFOUND-XYZ not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Add Lines - 404 period not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "[\n { \"accountCode\": \"<accountCode1>\", \"periodId\": 999999, \"budgetedAmount\": 100 }\n]"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets/2/lines",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets",
|
|
"2",
|
|
"lines"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Period 999999 not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Add Lines - 404 cost center not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "[\n { \"accountCode\": \"<accountCode1>\", \"periodId\": 49, \"costCenterCode\": \"NOTFOUND-XYZ\", \"budgetedAmount\": 100 }\n]"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets/2/lines",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets",
|
|
"2",
|
|
"lines"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Cost center NOTFOUND-XYZ not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Add Lines - 404 budget not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "[\n { \"accountCode\": \"<accountCode1>\", \"periodId\": 49, \"budgetedAmount\": 100 }\n]"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets/999999/lines",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets",
|
|
"999999",
|
|
"lines"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Budget not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Budgets - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/budgets",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"budgets"
|
|
]
|
|
},
|
|
"description": "200. Flat list. Note: data[].lines is always [] here - GetAllAsync doesn't eager-load lines, unlike the AddLines response which does return them. Use the BudgetVsActual report (GET /reports) to see a budget's lines with actuals."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Cheque Management",
|
|
"item": [
|
|
{
|
|
"name": "Create Cheque Book - success (auto-generates all leaves)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"branchId\": 1,\n \"bankAccountId\": 1,\n \"chequeBookNo\": \"CB-2028-001\",\n \"startChequeNo\": \"000101\",\n \"endChequeNo\": \"000110\",\n \"totalLeaves\": 10,\n \"receivedDate\": \"2028-01-15\",\n \"description\": \"Doc test book\",\n \"createdBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books"
|
|
]
|
|
},
|
|
"description": "201. New module (03_Progress_Tracker.md Phase 37, added 2026-07-30). bankAccountId stays numeric - BankAccount has no unique code column (CLAUDE.md Rule 8.2's carve-out). branchId required, no companyId on this table, per explicit direction - plain unvalidated long, no Company/Branch table exists in this system. Auto-generates 10 ChequePage rows (000101-000110), all status Unused, in the same transaction - data.pages has all 10."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cheque Book - 404 bank account not found",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"branchId\": 1,\n \"bankAccountId\": 999999,\n \"chequeBookNo\": \"CB-2028-BAD1\",\n \"startChequeNo\": \"000201\",\n \"endChequeNo\": \"000210\",\n \"totalLeaves\": 10,\n \"receivedDate\": \"2028-01-15\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Bank account not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cheque Book - 400 chequeBookNo already exists",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"branchId\": 1,\n \"bankAccountId\": 1,\n \"chequeBookNo\": \"CB-2028-001\",\n \"startChequeNo\": \"000301\",\n \"endChequeNo\": \"000310\",\n \"totalLeaves\": 10,\n \"receivedDate\": \"2028-01-15\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books"
|
|
]
|
|
},
|
|
"description": "400 (run after the success case above, same chequeBookNo). Message: \"Cheque book number 'CB-2028-001' already exists\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cheque Book - 400 totalLeaves doesn't match the numeric range",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"branchId\": 1,\n \"bankAccountId\": 1,\n \"chequeBookNo\": \"CB-2028-BAD2\",\n \"startChequeNo\": \"000201\",\n \"endChequeNo\": \"000210\",\n \"totalLeaves\": 5,\n \"receivedDate\": \"2028-01-15\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books"
|
|
]
|
|
},
|
|
"description": "400. Message: \"totalLeaves (5) does not match the range startChequeNo-endChequeNo (10 cheques)\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Cheque Book - 400 startChequeNo/endChequeNo not numeric",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"branchId\": 1,\n \"bankAccountId\": 1,\n \"chequeBookNo\": \"CB-2028-BAD3\",\n \"startChequeNo\": \"ABC\",\n \"endChequeNo\": \"XYZ\",\n \"totalLeaves\": 10,\n \"receivedDate\": \"2028-01-15\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books"
|
|
]
|
|
},
|
|
"description": "400. Message: \"startChequeNo and endChequeNo must be numeric\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cheque Book - success (no expand, pages: [])",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books/CB-2028-001",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books",
|
|
"CB-2028-001"
|
|
]
|
|
},
|
|
"description": "200. Looked up by chequeBookNo (CLAUDE.md Rule 8), not the numeric chequeBookId. expand omitted -> data.pages: []."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cheque Book - success (?expand=pages)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books/CB-2028-001?expand=pages",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books",
|
|
"CB-2028-001"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "expand",
|
|
"value": "pages"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. data.pages has the 10 generated ChequePage rows."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cheque Book - 404 not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books/NOTFOUND-XYZ",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books",
|
|
"NOTFOUND-XYZ"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Cheque book not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Cheque Books - success (filters + paging)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-books?bankAccountId=1&branchId=1&status=Active&page=1&pageSize=10",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-books"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "bankAccountId",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "branchId",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "status",
|
|
"value": "Active"
|
|
},
|
|
{
|
|
"key": "page",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "pageSize",
|
|
"value": "10"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. All filters optional and independent. Ordered by receivedDate descending, then chequeBookNo."
|
|
}
|
|
},
|
|
{
|
|
"name": "Issue Cheque - success",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"payeeType\": \"Supplier\",\n \"payeeName\": \"ACME Supplies\",\n \"issueDate\": \"2028-01-16\",\n \"amount\": 15000.50,\n \"referenceNo\": \"INV-9001\",\n \"purpose\": \"Payment for goods\",\n \"isCrossCheque\": true,\n \"isAccountPayee\": true,\n \"printedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000101/issue",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000101",
|
|
"issue"
|
|
]
|
|
},
|
|
"description": "200. payeeType (Supplier|Customer|Employee|Other) sent as a STRING, parsed server-side via Enum.TryParse - this project has no global JsonStringEnumConverter (same pattern as recurrencePattern). payeeId omitted -> null (loose reference, no Supplier/Customer/Employee table exists here). currencyCode omitted -> defaults LKR. Only a page currently Unused can be issued. issueStatus -> Issued (2), printedAt set to now."
|
|
}
|
|
},
|
|
{
|
|
"name": "Issue Cheque - 400 page must be Unused",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"payeeType\": \"Supplier\",\n \"payeeName\": \"ACME Supplies\",\n \"issueDate\": \"2028-01-16\",\n \"amount\": 100\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000101/issue",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000101",
|
|
"issue"
|
|
]
|
|
},
|
|
"description": "400 (run a second time on the same, now-Issued cheque). Message: \"Cheque page must be Unused to be issued (currently Issued)\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Issue Cheque - 400 invalid payeeType",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"payeeType\": \"Bogus\",\n \"payeeName\": \"x\",\n \"issueDate\": \"2028-01-16\",\n \"amount\": 100\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000102/issue",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000102",
|
|
"issue"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid payeeType 'Bogus'. Must be one of: Supplier, Customer, Employee, Other.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Issue Cheque - 400 amount not > 0",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"payeeType\": \"Customer\",\n \"payeeName\": \"x\",\n \"issueDate\": \"2028-01-16\",\n \"amount\": 0\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000102/issue",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000102",
|
|
"issue"
|
|
]
|
|
},
|
|
"description": "400. Message: \"amount must be greater than 0\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Issue Cheque - 404 chequeNo not found",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"payeeType\": \"Customer\",\n \"payeeName\": \"x\",\n \"issueDate\": \"2028-01-16\",\n \"amount\": 100\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/NOTFOUND/issue",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"NOTFOUND",
|
|
"issue"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Cheque page not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Issue Cheque - success (second page, Customer payee, for the status-transition requests below)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"payeeType\": \"Customer\",\n \"payeeName\": \"Some Customer Refund\",\n \"issueDate\": \"2028-01-16\",\n \"amount\": 500\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000102/issue",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000102",
|
|
"issue"
|
|
]
|
|
},
|
|
"description": "200. Issues 000102 so the Bounce example below has an Issued page to act on."
|
|
}
|
|
},
|
|
{
|
|
"name": "Cheque Page Status - Clear success (requires clearedDate)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Clear\",\n \"clearedDate\": \"2028-01-20\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000101/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000101",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200. Action-based, same pattern as PUT /periods/{id}/lock. Only valid from Issued. issueStatus -> Cleared (3), clearedByBank set true."
|
|
}
|
|
},
|
|
{
|
|
"name": "Cheque Page Status - Bounce success",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Bounce\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000102/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000102",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200. Only valid from Issued. issueStatus -> Bounced (4)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Cheque Page Status - Cancel success (requires cancelReason, from Unused)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Cancel\",\n \"cancelReason\": \"Printing error\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000103/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000103",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200. Valid from Unused or Issued. issueStatus -> Cancelled (5)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Cheque Page Status - Void success (from Unused only)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Void\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000104/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000104",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200. Valid from Unused only. issueStatus -> Void (6)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Cheque Page Status - 400 Clear on a still-Unused page",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Clear\",\n \"clearedDate\": \"2028-01-20\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000105/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000105",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Cheque must be Issued to be cleared (currently Unused)\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Cheque Page Status - 400 invalid action",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Bogus\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000106/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000106",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid action 'Bogus'. Must be one of: Clear, Bounce, Cancel, Void.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cheque Page - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/000101",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"000101"
|
|
]
|
|
},
|
|
"description": "200. Looked up by chequeNo. Includes the parent chequeBook."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Cheque Page - 404 not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages/NOTFOUND",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages",
|
|
"NOTFOUND"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Cheque page not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Cheque Pages - success (filters + paging)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/cheque-pages?chequeBookNo=CB-2028-001&issueStatus=Cleared&page=1&pageSize=10",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"cheque-pages"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "chequeBookNo",
|
|
"value": "CB-2028-001"
|
|
},
|
|
{
|
|
"key": "issueStatus",
|
|
"value": "Cleared"
|
|
},
|
|
{
|
|
"key": "page",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "pageSize",
|
|
"value": "10"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. All filters optional and independent (chequeBookNo, issueStatus, payeeType, payeeId). Ordered by chequeNo."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Received Cheque - success",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"companyId\": 1,\n \"branchId\": 1,\n \"receivedFromType\": \"Customer\",\n \"receivedFromName\": \"Big Customer Ltd\",\n \"drawerBankName\": \"HNB\",\n \"drawerBankBranch\": \"Colombo 03\",\n \"accountHolderName\": \"Big Customer Ltd\",\n \"chequeNo\": \"777001\",\n \"chequeDate\": \"2028-01-18\",\n \"amount\": 25000,\n \"receivedDate\": \"2028-01-19\",\n \"referenceType\": \"Invoice\",\n \"referenceId\": 501,\n \"createdBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques"
|
|
]
|
|
},
|
|
"description": "201. No chequeBookId link, by design - this is a cheque received from the other side, not from this company's own cheque book (per the client's spec). companyId required, branchId nullable (asymmetric with ChequeBook, per explicit direction). drawerBankName/drawerBankBranch are plain free text, not a FK - no bank-master table exists in this service, same precedent as BankAccount.BankName. receivedFromType (Customer|Supplier|Other) sent as a string, parsed server-side. Status always defaults to Received (1)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Received Cheque - 400 chequeNo missing",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"companyId\": 1,\n \"receivedFromType\": \"Customer\",\n \"receivedFromName\": \"x\",\n \"chequeDate\": \"2028-01-18\",\n \"amount\": 100,\n \"receivedDate\": \"2028-01-19\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques"
|
|
]
|
|
},
|
|
"description": "400. Message: \"chequeNo is required\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Create Received Cheque - 400 invalid receivedFromType",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"companyId\": 1,\n \"receivedFromType\": \"Bogus\",\n \"receivedFromName\": \"x\",\n \"chequeNo\": \"777002\",\n \"chequeDate\": \"2028-01-18\",\n \"amount\": 100,\n \"receivedDate\": \"2028-01-19\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid receivedFromType 'Bogus'. Must be one of: Customer, Supplier, Other.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Received Cheque - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/1",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"1"
|
|
]
|
|
},
|
|
"description": "200. Looked up by the numeric id - ChequeNo isn't guaranteed globally unique across different drawers/banks here, unlike our own ChequeBook/ChequePage."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Received Cheque - 404 not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/999999",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"999999"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Received cheque not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Received Cheque Status - Deposit success (requires depositBankAccountId + depositDate)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Deposit\",\n \"depositBankAccountId\": 1,\n \"depositDate\": \"2028-01-20\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/1/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"1",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200. Only valid from Received. depositBankAccountId validated against a real bank account (numeric - BankAccount has no unique code column, same carve-out as ChequeBook.bankAccountId). status -> Deposited (2)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Received Cheque Status - Clear success (from Deposited)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Clear\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/1/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"1",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200. Only valid from Deposited. status -> Cleared (3)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Received Cheque Status - Return success (from Deposited)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Return\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/2/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"2",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200 (run against a different received cheque, still Deposited). Only valid from Deposited. status -> Returned (4)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Received Cheque Status - Cancel success (from Received)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Cancel\",\n \"performedBy\": \"Doc Tester\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/3/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"3",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200 (run against a still-Received cheque). Only valid from Received. status -> Cancelled (5)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Received Cheque Status - 400 wrong current status",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Deposit\",\n \"depositBankAccountId\": 1,\n \"depositDate\": \"2028-01-20\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/1/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"1",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "400 (run a second time, after the Deposit + Clear requests above already moved this record past Received). Message: \"Received cheque must be in Received status to be deposited (currently Cleared)\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Received Cheque Status - 404 depositBankAccountId not found",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Deposit\",\n \"depositBankAccountId\": 999999,\n \"depositDate\": \"2028-01-20\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/2/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"2",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "404. Message: \"Deposit bank account not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "Received Cheque Status - 400 invalid action",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"action\": \"Bogus\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques/2/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques",
|
|
"2",
|
|
"status"
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid action 'Bogus'. Must be one of: Deposit, Clear, Return, Cancel.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Received Cheques - success (filters + paging)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/received-cheques?companyId=1&status=Cleared&page=1&pageSize=10",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"received-cheques"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "companyId",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "status",
|
|
"value": "Cleared"
|
|
},
|
|
{
|
|
"key": "page",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "pageSize",
|
|
"value": "10"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. All filters optional and independent (companyId, branchId, status, receivedFromType). Ordered by receivedDate descending."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Reporting",
|
|
"item": [
|
|
{
|
|
"name": "TrialBalance - Json",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=TrialBalance&outputFormat=Json&asOfDate=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "TrialBalance"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "asOfDate",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Requires asOfDate. Rows: { depth, indentedCode, accountName, debit, credit } - only postable accounts with a non-zero posted balance as of the date."
|
|
}
|
|
},
|
|
{
|
|
"name": "BalanceSheet - Json",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=BalanceSheet&outputFormat=Json&asOfDate=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "BalanceSheet"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "asOfDate",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Requires asOfDate. Asset/Liability/Equity account types only (account_type_id IN (1,2,3)). Rows: { depth, lineItem, accountType, balance } - balance is a rolled-up subtree total, not just the leaf's own postings."
|
|
}
|
|
},
|
|
{
|
|
"name": "GeneralLedger - Json (single account + descendants)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=GeneralLedger&outputFormat=Json&accountCode=<accountCode1>&periodStart=2035-01-01&periodEnd=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "GeneralLedger"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "accountCode",
|
|
"value": "<accountCode1>"
|
|
},
|
|
{
|
|
"key": "periodStart",
|
|
"value": "2035-01-01"
|
|
},
|
|
{
|
|
"key": "periodEnd",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. accountCode (retrofit 2026-07-22, CLAUDE.md Rule 8.2 - replaces accountId, closing the gap this report used to be the one exception to) + periodStart + periodEnd. accountCode is now optional (retrofit 2026-07-20 - two modes): supplied here -> includes that account's own postings AND all descendant accounts' postings (recursive), with a running balance column."
|
|
}
|
|
},
|
|
{
|
|
"name": "GeneralLedger - Json (accountCode omitted - every postable account)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=GeneralLedger&outputFormat=Json&periodStart=2035-01-01&periodEnd=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "GeneralLedger"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "periodStart",
|
|
"value": "2035-01-01"
|
|
},
|
|
{
|
|
"key": "periodEnd",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. accountCode omitted -> a true General Ledger across every postable account (no descendant CTE, just filters is_postable = TRUE), not just one account + its descendants."
|
|
}
|
|
},
|
|
{
|
|
"name": "GeneralLedger - 404 accountCode supplied but not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=GeneralLedger&outputFormat=Json&accountCode=NOTFOUND-XYZ&periodStart=2035-01-01&periodEnd=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "GeneralLedger"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "accountCode",
|
|
"value": "NOTFOUND-XYZ"
|
|
},
|
|
{
|
|
"key": "periodStart",
|
|
"value": "2035-01-01"
|
|
},
|
|
{
|
|
"key": "periodEnd",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "404. Message: \"Account not found\" - only checked when accountCode is present at all."
|
|
}
|
|
},
|
|
{
|
|
"name": "ProfitAndLoss - Json",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=ProfitAndLoss&outputFormat=Json&periodStart=2035-01-01&periodEnd=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "ProfitAndLoss"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "periodStart",
|
|
"value": "2035-01-01"
|
|
},
|
|
{
|
|
"key": "periodEnd",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Requires periodStart + periodEnd. Income/Expense account types only (4,5). Rows: { depth, lineItem, accountType, amount }."
|
|
}
|
|
},
|
|
{
|
|
"name": "CashFlow - Json (documented simplification, not full LKAS 7)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=CashFlow&outputFormat=Json&periodStart=2035-01-01&periodEnd=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "CashFlow"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "periodStart",
|
|
"value": "2035-01-01"
|
|
},
|
|
{
|
|
"key": "periodEnd",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Requires periodStart + periodEnd. Returns a single object, not an array: { periodStart, periodEnd, netProfitForPeriod, netCashFromInvestingActivities, netCashFromFinancingActivities, netIncreaseDecreaseInCash }. Simplification: Operating = P&L's net profit (no non-cash/working-capital adjustments - the schema has no account-level tag for those); Investing/Financing come from the cash_flow_category rollup; no opening/closing cash balance line (no reliable way to identify 'cash' accounts from the current schema). Not full LKAS 7 compliance - see 03_Progress_Tracker.md."
|
|
}
|
|
},
|
|
{
|
|
"name": "BudgetVsActual - Json",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=BudgetVsActual&outputFormat=Json&budgetId=2",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "BudgetVsActual"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "budgetId",
|
|
"value": "2"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Requires budgetId. Rows: { budgetLineId, accountCode, accountName, periodId, budgetedAmount, actualAmount, variance }. This is the endpoint to use for a budget's lines with real figures - GET /budgets itself never returns lines (see Budget module notes)."
|
|
}
|
|
},
|
|
{
|
|
"name": "TrialBalance - Pdf",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=TrialBalance&outputFormat=Pdf&asOfDate=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "TrialBalance"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Pdf"
|
|
},
|
|
{
|
|
"key": "asOfDate",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "200 (not a raw binary response - stays inside the normal ApiResponse envelope, per CLAUDE.md Rule 7). data: { fileName, contentType: \"application/pdf\", contentBase64 }. Client must base64-decode contentBase64 to get real PDF bytes (verified live: decodes to a file starting with %PDF-1.4). Every reportType supports outputFormat=Pdf via the same shared reflection-based table renderer, not just TrialBalance."
|
|
}
|
|
},
|
|
{
|
|
"name": "400 - missing/empty reportType",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?outputFormat=Json",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
}
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid reportType ''. Must be one of: GeneralLedger, TrialBalance, BalanceSheet, ProfitAndLoss, CashFlow, BudgetVsActual.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "400 - invalid reportType",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=Bogus&outputFormat=Json",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "Bogus"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
}
|
|
]
|
|
},
|
|
"description": "400. Same message shape as the missing case above."
|
|
}
|
|
},
|
|
{
|
|
"name": "400 - invalid outputFormat",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=TrialBalance&outputFormat=Bogus&asOfDate=2035-12-31",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "TrialBalance"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Bogus"
|
|
},
|
|
{
|
|
"key": "asOfDate",
|
|
"value": "2035-12-31"
|
|
}
|
|
]
|
|
},
|
|
"description": "400. Message: \"Invalid outputFormat 'Bogus'. Must be one of: Json, Pdf.\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "400 - missing required parameter for reportType (TrialBalance without asOfDate)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=TrialBalance&outputFormat=Json",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "TrialBalance"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
}
|
|
]
|
|
},
|
|
"description": "400. Message names the specific missing parameter: \"asOfDate is required for the TrialBalance report\". Each reportType has its own required-parameter set, validated in the Service before dispatch. (Not accountCode/GeneralLedger - accountCode is optional there since the 2026-07-20 retrofit, two modes: single-account+descendants when supplied, every postable account when omitted.)"
|
|
}
|
|
},
|
|
{
|
|
"name": "404 - BudgetVsActual, budgetId not found",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/reports?reportType=BudgetVsActual&outputFormat=Json&budgetId=999999",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"reports"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "reportType",
|
|
"value": "BudgetVsActual"
|
|
},
|
|
{
|
|
"key": "outputFormat",
|
|
"value": "Json"
|
|
},
|
|
{
|
|
"key": "budgetId",
|
|
"value": "999999"
|
|
}
|
|
]
|
|
},
|
|
"description": "404. Message: \"Budget not found\" - the only reportType that does an existence check before running its query (the others simply return an empty array if nothing matches)."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "System Config",
|
|
"item": [
|
|
{
|
|
"name": "Update Config - success (auto-locks)",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"configValue\": \"5\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/system-config/COST_CENTER_MAX_DEPTH",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"system-config",
|
|
"COST_CENTER_MAX_DEPTH"
|
|
]
|
|
},
|
|
"description": "200. Every successful write sets isLocked:true in the same call - each key is editable exactly once via this endpoint, ever. Audit category is per-key (a static lookup dictionary; all 3 current keys map to Ledger, any future unmapped key defaults to Administrative)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Config - 400 already locked",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"configValue\": \"6\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/system-config/COST_CENTER_MAX_DEPTH",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"system-config",
|
|
"COST_CENTER_MAX_DEPTH"
|
|
]
|
|
},
|
|
"description": "400 (call a second time on the same key). Message: \"System config key 'COST_CENTER_MAX_DEPTH' cannot be edited\". Rule: reject if !IsEditable || IsLocked."
|
|
}
|
|
},
|
|
{
|
|
"name": "Update Config - 404 key not found",
|
|
"request": {
|
|
"method": "PUT",
|
|
"header": [
|
|
{
|
|
"key": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"configValue\": \"x\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/system-config/NOTFOUND_KEY",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"system-config",
|
|
"NOTFOUND_KEY"
|
|
]
|
|
},
|
|
"description": "404. Message: \"System config key not found\"."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Config - success",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/system-config",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"system-config"
|
|
]
|
|
},
|
|
"description": "200. All seeded keys: COA_MAX_DEPTH, COST_CENTER_MAX_DEPTH, COA_ENFORCE_MAX_DEPTH."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Audit Log",
|
|
"item": [
|
|
{
|
|
"name": "List - success (no filters)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/audit-log",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"audit-log"
|
|
]
|
|
},
|
|
"description": "200. category defaults to both LEDGER+ADMINISTRATIVE when omitted. No page/pageSize -> full result set (same optional-paging pattern as Accounts)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List - success (category=LEDGER)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/audit-log?category=LEDGER",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"audit-log"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "category",
|
|
"value": "LEDGER"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. category is a repeatable query param: ?category=LEDGER&category=ADMINISTRATIVE selects both explicitly (same result as omitting it)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List - success (entityName + action filters)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/audit-log?entityName=Account&action=Create",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"audit-log"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "entityName",
|
|
"value": "Account"
|
|
},
|
|
{
|
|
"key": "action",
|
|
"value": "Create"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. entityName/performedBy are exact-match string filters."
|
|
}
|
|
},
|
|
{
|
|
"name": "List - success (invalid category -> empty result, not an error)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/audit-log?category=BOGUS",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"audit-log"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "category",
|
|
"value": "BOGUS"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. data: { items: [], totalCount: 0, page: null, pageSize: null }. A typo'd filter value is a read-side no-match, not a validation error - deliberate, documented design choice."
|
|
}
|
|
},
|
|
{
|
|
"name": "List - success (invalid action -> empty result)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/audit-log?action=BOGUS",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"audit-log"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "action",
|
|
"value": "BOGUS"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Same treatment as an invalid category - empty result, 200, not 400."
|
|
}
|
|
},
|
|
{
|
|
"name": "List - success (date range on occurredAt)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/audit-log?from=2026-07-19&to=2026-07-19",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"audit-log"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "from",
|
|
"value": "2026-07-19"
|
|
},
|
|
{
|
|
"key": "to",
|
|
"value": "2026-07-19"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. from/to filter occurredAt (when the audit row was written - real wall-clock time), NOT any business/entryDate field on the underlying record. A journal entry dated far in the future/past still writes its audit_log row with occurredAt = right now."
|
|
}
|
|
},
|
|
{
|
|
"name": "List - success (page+pageSize)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/audit-log?page=1&pageSize=3",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"audit-log"
|
|
],
|
|
"query": [
|
|
{
|
|
"key": "page",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"key": "pageSize",
|
|
"value": "3"
|
|
}
|
|
]
|
|
},
|
|
"description": "200. Ordered by occurredAt descending (newest first)."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Dev Tools",
|
|
"item": [
|
|
{
|
|
"name": "Generate API Key - success (Development only)",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/dev-tools/generate-api-key",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"dev-tools",
|
|
"generate-api-key"
|
|
]
|
|
},
|
|
"description": "200. No X-Api-Key needed - this path is excluded from ApiKeyMiddleware (it's the bootstrap tool that produces the very first key). Guarded separately: throws 403 outside the Development environment, verified live (Hosting environment: Production -> 403). Does not persist the key anywhere - copy data.apiKey into appsettings.*.json and the core backend's config by hand."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Status & Health (unauthenticated / excluded from ApiKeyMiddleware)",
|
|
"item": [
|
|
{
|
|
"name": "GET /status",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "{{baseUrl}}/status",
|
|
"host": [
|
|
"{{baseUrl}}"
|
|
],
|
|
"path": [
|
|
"status"
|
|
]
|
|
},
|
|
"description": "200. { service, status, timestampUtc }. NOT an ApiResponse - this is the one endpoint that predates that convention and still returns Ok(new {...}) directly from the Controller. Still requires X-Api-Key (not excluded from ApiKeyMiddleware)."
|
|
}
|
|
},
|
|
{
|
|
"name": "GET /health (no auth)",
|
|
"request": {
|
|
"method": "GET",
|
|
"header": [
|
|
{
|
|
"key": "X-Api-Key",
|
|
"value": "{{apiKey}}"
|
|
}
|
|
],
|
|
"url": {
|
|
"raw": "http://localhost:5299/health",
|
|
"host": [
|
|
"http://localhost:5299"
|
|
],
|
|
"path": [
|
|
"health"
|
|
]
|
|
},
|
|
"description": "200, plain text \"Healthy\". Excluded from ApiKeyMiddleware and lives outside the /api/v1 prefix entirely (ASP.NET Core's built-in health check middleware, not an action on GeneralLedgerController)."
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|