Skip to content

API — Overview

The NKO ERP has no separate REST API layer. It is a server-rendered ASP.NET Core MVC application, so its HTTP surface is the set of controller actions: some return full pages, some return HTML fragments for modals, and a large minority return JSON to the jQuery/AJAX code in the views. This section documents every one of them — route, HTTP verb, parameters, permission key and response — grouped by business module.

Inventory

947 routable actions across 44 controller classes in three source folders — NKO.Main/NKO.Main/Controllers/ (main), NKO.Main/NKO.Main/WarehouseControllers/ (warehouse), and NKO.Lib/NKO.Shared/Controllers/ (shared). Counted directly from source; the per-module pages below list each action individually.

Seven public static GetMonthDifference(DateTime, DateTime) helpers — on Advance, HumanResource, Overtime, Payroll, Timesheet, Training and User — are not counted. ASP.NET Core's action discovery (DefaultApplicationModelProvider.IsAction) rejects static methods, so they are ordinary helpers, not endpoints. An earlier revision of this page listed them as ungated routes; they are unreachable over HTTP.

Module Controllers Actions
Administration & platform 12 227
Logistics 10 174
Procurement 6 157
HR 3 115
Payroll 4 97
Grant 5 84
Recruitment 3 82
Training 1 11
Total 44 947

The gate counts under Authentication & authorization reconcile exactly against this total: 887 + 14 + 2 + 44 = 947.

SidebarViewComponent and TopbarViewComponent are not included: they derive from ViewComponent, so their InvokeAsync methods are rendered from a layout rather than routed. See Administration → View components.

Routing

A single conventional route is registered — there are no areas and (almost) no attribute routing:

// NKO.Main/NKO.Main/Startup.cs (line ~356)
endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

So the route for an action is simply /{ControllerNameWithoutSuffix}/{ActionName}, with /{Controller} alone hitting Index, and a single optional {id} segment. Everything else arrives as a query string on GET or as form fields on POST.

/PurchaseRequest                 → PurchaseRequestController.Index
/PurchaseRequest/Details/{guid}  → PurchaseRequestController.Details(string id)
/Stock/SRR?page=2                → StockController.SRR(int? page)

The four exceptions carry an explicit [Route] and are the only paths that look like a conventional web API:

Route Controller Auth Purpose
/api/attachments/{id} FilesController.Attachment [Authorize] (class) Download a stored file by its storage key
/api/viewattachments/{id} FilesController.ViewAttachment [Authorize] (class) Same, rendered inline as application/pdf
/api/pubattachments/{id} PublicFilesController.Attachment none Download by storage key, unauthenticated
/api/viewpubattachments/{id} PublicFilesController.ViewAttachment none Inline PDF by storage key, unauthenticated

Authentication & authorization

Sign-in is ASP.NET Core Identity (AccountController, ApplicationUser). Authorization is applied per action, by one of three attributes — there is no global authorization filter and no fallback policy, so an action with none of them is reachable by anyone.

Gate Count Meaning
[ClaimRequirement("<Key>")] 887 Requires sign-in and that the key is in the user's permission set (219 distinct keys in force). Two further copies of the attribute sit on the view components, where they are inert, and a 220th literal — GRNDelete — appears only inside a commented-out method in GRNReportController.
[Authorize] 14 Requires sign-in only — any employee passes
[AllowAnonymous] 2 Explicitly public (/Document/ExportationRotative{Header,Footer})
(no attribute) 44 Implicitly public — see Ungated actions

ClaimRequirementFilter (NKO.Lib/NKO.Shared/Helpers/Sec/ClaimRequirementFilter.cs) runs as an IAsyncAuthorizationFilter before the action:

  1. Not authenticated → 302 to ~/Account/Login?ReturnUrl={path}.
  2. Authenticated, key missing → ForbidResult, status set to 403, then redirected to ~/Error/UnAuthorised.
  3. Authenticated, key present → populates CurrentRequestData.Permissions and CurrentRequestData.Employee for the rest of the request, then the action runs.

The permission set is re-read from the database on every request (PermissionsHandler.GetUserPermissions), so a group change or a disabled login takes effect on the user's next click. Keys are free-form strings, conventionally "<Area><Action>" — HumanResourceIndex, PurchaseOrderDetails, StockSRR. The special key General means "any authenticated employee".

Row-level scoping is not done by the filter. Actions that need it call BUser.GetPermission(SharedPermissions.<Area>, userId) inside the body and narrow the query to All / Location / Custom / Default. See Permissions.

Request conventions

  • Verbs. 604 actions answer GET, 343 answer POST. No PUT, DELETE or PATCH is used anywhere — deletes are GET or POST actions named Delete… / Remove….
  • Anti-forgery. 229 of the 343 POST actions carry [ValidateAntiForgeryToken]; the remaining 114 do not (all of them behind a permission key). The public POSTs — /Account/Login and the /Apps/* portals — are protected.
  • Identifiers. Most Details / Edit / Delete actions take a GUID string (string id), not the integer primary key; grids and lookups take int id.
  • List parameters. Index actions share a paging/filter vocabulary: int? page, string search_param, string searchString, string currentFilter, string sortOrder, int? filterYear, int? filterMonth, int? filterStatus, int? filterProject, int? filterBranch.
  • Model binding. POSTs bind either a full entity (TrainingRequest model) or a view model (TrainingVM model) from form fields; AJAX POSTs usually take scalars.

The approval endpoint

Every multi-step document exposes one ContextActions POST that drives its whole state machine instead of a verb-per-transition. There are 23 of them:

POST /{Document}/ContextActions
     id            document GUID
     operationType which transition (…OpTypes constant)
     userType      which role is acting (…UserTypes constant)
     idText        free-text note / reason

The action resolves the constants from NKO.Lib/NKO.Shared/Helpers/CommonConstants.cs (warehouse: NKO.Main/NKO.Main/Helpers/OperationConstants.cs), dispatches to the matching BAL Set…Accepted / Set…Refused method, and redirects back to the document's Details page with a msg or errorMsg query parameter. Endpoints: /Advance, /Asset (+ RegisterContextActions), /Destruction, /Evaluation, /GRN, /GRNReport, /Jobs (+ ContextActionsApplicant, TechnicalInterViewContextActions), /MaintenanceReq, /Overtime, /PaymentOrder, /Payroll, /PurchaseOrder (+ JustificationContextActions), /PurchaseRequest, /RFQ (+ TechContextActions), /Stock, /Timesheet (+ LeaveContextActions), /Training.

Most workflow transitions additionally require an approved e-signature — BSign.HasSign(userId) is checked first and the transition is refused with "Please upload your E-signature." otherwise. Not all of them, though. The check runs on Purchase Request, RFQ (both endpoints), Purchase Order, GRN, Payment Order, and the warehouse Asset, Stock, GRN-report and Maintenance flows; Advance, Overtime and Leave check the approver's PIN (VerifyPin) and the e-signature inside the business class. It does not run on Payroll, Training, Jobs (all three endpoints), Evaluation, Purchase Order justification or Destruction, and Timesheet checks it only when the requester sends. See R3-9 in Recommendations.

Responses

Actions are not typed by response; the same Task<ActionResult> may return a page, a fragment or JSON depending on the branch taken. Across all 947 actions, counting only live code — return statements that are commented out are ignored:

Response Actions Notes
View(...) 379 A full page rendered into _Layout (the public /Apps/* pages use _AppLayout)
RedirectToAction(...) / Redirect(...) 303 Post-Redirect-Get, usually carrying msg / errorMsg
PartialView(...) 276 HTML fragment loaded into a Bootstrap modal
Json(...) / new JsonResult(...) 270 AJAX payloads for grids, cascading dropdowns and validation
File(...) / FileStreamResult 16 Excel / PDF exports and attachment downloads
NotFound() 10 Missing storage object or record
Content(...) 6 Plain strings
BadRequest() / StatusCodeResult(400) 5 Missing or invalid input — HumanResource, Account and the two WareBase look-ups
Unauthorized() 3 A wrong E-signature PIN on Advance, Overtime and Leave approvals

No action returns Forbid(); a missing permission key is turned into a 403 by ClaimRequirementFilter before the action runs. Counts exceed 947 because most actions have several return paths.

Partial views have no layout when requested directly

The 276 PartialView actions are meant to be fetched by AJAX into a modal. Opening one directly by URL returns a bare, unstyled HTML fragment with no navigation — see R6-1 in Recommendations.

Errors

There is no uniform error envelope. Behaviour by layer:

Condition Result
Not signed in, gated action 302 → /Account/Login?ReturnUrl={path}
Signed in, permission key missing 403 → redirect to /Error/UnAuthorised
Unhandled exception The developer exception page — full stack trace, source excerpt and request data. Startup.Configure calls UseDeveloperExceptionPage() unconditionally and registers no UseExceptionHandler, so this is what production returns. See R1-1 in Recommendations.
Handled BAL failure Redirect back to the list/detail view with ?errorMsg=… rendered as a banner
Workflow guard failed (wrong state, not the next user, no e-signature) Same redirect + message; no distinct status code
Missing file in object storage 404 from NotFound()

Because most failures are expressed as a redirect with a message rather than a status code, an HTTP client cannot distinguish success from a refused workflow transition by status alone.

Ungated actions

44 actions carry no authorization attribute at all. Some are public by design; others look accidental. They are listed in full on each module page (marked — open); the security-relevant ones are tracked as recommendations.

Group Actions Assessment
/Apps/* — public job-application, reference and supplier-quotation portals 12 By design. Unauthenticated, but they write Applicants and RFQProvider* rows. Anti-forgery is present; no rate limiting or CAPTCHA is.
/Account/Login (GET + POST), /Culture/SetLanguage 3 By design.
/api/pubattachments/{id}, /api/viewpubattachments/{id} 2 Unauthenticated file download by storage key, no ownership check — see R3-1.
/Account/LogOut 1 A genuine endpoint. Linked from the "Sign out" control in Views/Base/TopNavBar.cshtml and the shared topbar component. It returns plain Task rather than an IActionResult, so it does not look like an action at a glance — but it is one, and it is used on every sign-out.
/Account/SignIn, /Account/SignOut, /Account/GetUserClaims 3 Not intended as endpoints. Internal helpers that happen to be public and non-static, so MVC routes them. SignIn(ApplicationUser user, string pass) calls PasswordSignInAsync — see R3-7.
/Attendances/PercentageFilled, /HourPercentageFilled, /GetMissingList, /GetMissingListByUser, /General/FillDepartmentsOrg 5 Not intended as endpoints either — internal calculation helpers returning view models and double/Tuple values straight to the caller. See R3-7.
/Attendances/TypeShouldHaveCheckinCheckout, /Apps/ValidateDateEqualOrGreater, /RFQ/Can* (4) 6 Stateless probes exposed as endpoints; the RFQ/Can* four leak workflow state to anonymous callers — see R3-3.
/Admin/CopyProfile, /General/FileToBase64, /Common/Redirect, /Evidences (2), /WareBase/GetStocks, /WareBase/GetStockSpecs, /Report/ExportStocks, /HumanResource/DownloadAttachment, /Asset/DownloadAttachment, /BudgetLines/DownloadTemplate, /MasterBudget/DownloadTemplate, /Account/Notifications 12 Look unintentional — administrative, file-serving or data-returning actions with no gate. See R3-2.

[AllowAnonymous] is counted separately

/Document/ExportationRotativeHeader and /Document/ExportationRotativeFooter carry an explicit [AllowAnonymous], so they are deliberately public rather than ungated and are not part of the 44 above.