Skip to content

Installation — First Run

Starting the application for the first time, getting an account into it, and checking that the parts that depend on external services actually work.

Start the application

cd NKO.Main/NKO.Main
dotnet run

Startup will fail fast if any of these is missing, so fix them in this order:

Symptom at startup Cause
Cannot open database / login failed A connection string is wrong, or one of the three databases does not exist
Null reference in Startup.ConfigureServices The StorageConfigs section is missing — Startup reads StorageConfigs.Region while registering services. (The S3 and SNS clients themselves are created lazily; a bad AWS key only shows up at the first upload.)
Hangfire schema error The Hangfire database does not exist (Hangfire creates the schema, not the database)

Once it is up, / resolves to HomeController.Index, which is behind a permission key — so an anonymous visitor is redirected to /Account/Login.

The first-account problem

A freshly migrated database cannot be signed into — R5-1

Migrations create schema only. There is no seed data in the solution (zero HasData( calls), no SQL bootstrap script, and no self-registration action. ApplicationUsers is empty, and every screen that could create a user is itself behind a permission key.

There is no supported "create the first administrator" path. The two workable routes are below.

The realistic path, and how new environments are actually stood up today: take a backup of NKO_DB and NKO_DB_Warehouse from an existing environment and restore it instead of running migrations into empty databases.

This brings the identity rows, permission keys, groups and all reference data with it. If the source is production, treat the restored copy as production data — it contains employee records, salaries and identity documents — and sanitise it before using it for development.

Route B — hand-craft the identity rows

Only if no backup exists. Insert directly into NKO_DB, in this order:

# Table Required values
1 ApplicationUsers UserName, NormalizedUserName (upper case), PasswordHash, SecurityStamp, IsEnableLogin = 1, IsPasswordDefault = 0, TwoFactorEnabled = 0, LockoutEnabled = 0
2 AppUser IdentityUserId = the ApplicationUsers.Id from step 1, Status = 1, IsDeleted = 0, plus the employee fields (Firstname, Lastname, Email, BranchId, DateOfStart)
3 AppUserGroup One group — this is what the system calls a role
4 AppPermission One row per permission key you want to grant (219 keys are in force across the application)
5 AppUserGroupPermission Links the group to those AppPermission rows
6 AppUserGroupMember Links the AppUser to the group, IsEnable = 1

Two details that make or break this route

  • The users table is dbo.ApplicationUsers, not AspNetUsers. OnModelCreating remaps Identity's default (.ToTable("ApplicationUsers", "dbo")). An insert into AspNetUsers fails — the table does not exist.
  • AppUser.Status must be 1 and IsDeleted must be 0. Both columns are NOT NULL with no database default, so the insert must supply them, and AppUser carries a global query filter — !IsDeleted && Status == 1. Any other value hides the row from the application: a correct password then fails at SetCustomTracking(appUser.UserId) and every gated page throws in PermissionsHandler (R6-8).

The password hash is BCrypt, not the ASP.NET Identity default

ApplicationUserManager sets PasswordHasher = new BCryptPasswordHasher() in its constructor, so PasswordHash must be a BCrypt hash generated at work factor 12 (BCrypt.Net.BCrypt.HashPassword(password, BCrypt.Net.BCrypt.GenerateSalt(12))). A hash produced by the standard Identity hasher — or by any online PBKDF2 tool — will never verify.

Step 2 is not optional even though it looks like employee data: AccountController.Login loads the matching AppUser and dereferences it, so an ApplicationUsers row with no AppUser throws on the wrong-password branch before it can report a bad password.

Both AppUserGroupMember.IsEnable = 1 and ApplicationUser.IsEnableLogin = 1 are required. PermissionsHandler.GetUserPermissions returns an empty list when IsEnableLogin is false, and only reads groups whose membership row is enabled — either one left at 0 gives a login that can reach nothing.

What happens at sign-in

  1. /Account/Login — username and password.
  2. The password is verified with BCrypt.
  3. IsEnableLogin must be true, otherwise sign-in stops.
  4. If Is2FAMandatory or TwoFactorEnabled is set, a verification code is sent.
  5. If IsPasswordDefault is true the user is redirected to Security to change the password before anything else. New accounts created through the HR screens start with the hard-coded password "321321321" (R6-3).

Verification checklist

Once you can sign in, confirm each subsystem. Failures here are configuration, not code.

# Check Where Confirms
1 The top navigation lists modules any page Permission keys resolve — the top bar (TopbarViewComponent) filters its menus with HasPermission, so an empty menu means no keys
2 Open an employee record /HumanResource Core database and data scoping
3 Open the warehouse stock grid /Stock The second database and its connection string
4 Download any attachment an employee's Attachments tab AWS S3 credentials
5 Trigger an approval on a procurement document a PR's Details screen E-signature gate and notifications (payroll, training and a few others skip the gate — R3-9)
6 Watch for a notification badge the top bar SignalR hub at /hub
7 Export any grid to Excel any list screen EPPlus
8 Export any document to PDF a purchase order Rotativa and wkhtmltopdf — see below
9 SELECT * FROM ExchangeRates WHERE IsEnable = 1 returns one row SQL The global exchange rate exists. Without it every TL/EUR purchase request is valued at $0 and skips the segregation-of-duties sign-offs (R7-1). The application cannot create it.

Known to fail on a fresh install

PDF export — R5-3

Step 8 will fail. Startup.Configure calls RotativaConfiguration.Setup(env.WebRootPath, "Rotativa"), but wwwroot/Rotativa/ does not exist in the repository and no wkhtmltopdf binary is anywhere in it. Obtain the binaries matching Rotativa.AspNetCore 1.2.0 and place them in that folder.

E-mail failures are invisible — R1-6

If SMTP is misconfigured nothing will tell you. EmailManager wraps both send paths in an empty catch, so mail simply never arrives. Verify by triggering an approval and checking the recipient's mailbox directly — do not rely on the application to report a problem.

Background jobs

Three schedulers start with the application (see External Services):

Mechanism On first run
Quartz UpdateNextUserJob runs nightly at 03:00. ExportHistoryJob has no schedule — it runs only when an administrator starts a login-history export, which triggers it with TriggerJob (R1-3)
Hangfire Creates its schema on first start; jobs are queued on demand from the warehouse screens. No dashboard is mapped, so failures are invisible
IHostedService BirthdayGreetingsJob, controlled by ScheduledJobsSettings. LoanJob exists but is never registered (R7-4)

After a successful first run

  1. Create the real permission groups and assign keys — Permissions.
  2. Enter the reference data the modules depend on: branches, departments, positions, leave types, the work calendar, donors and stock categories — Administration.
  3. Set up the role-holders the approval chains route to — three different mechanisms:
  4. the HR manager, finance manager and general manager are not configured anywhere: they are whoever holds a position titled exactly "HR and Admin Manager", "Finance Manager" and "General Manager" (and the supply-chain approver "Supply Chain Director", in a department named "Supply Chain"). Create those positions with exactly those names, once each (R6-10);
  5. each branch's manager, HR, finance, logistics, treasury and accounting holders are set on the branch edit screen (/Admin/EditBranch);
  6. the allocator, procurement manager, HR approval, director approval, timesheet officers and cash boxes are set in /Admin/GeneralSettings. On a new database that screen crashes until the 21 CommonSetting rows exist — insert them by SQL first, SettingId 1–21 in order, row 14 named ips (R5-5).

A chain whose role-holder cannot be found stalls without an error. 4. Have each approver upload an e-signature — procurement, warehouse, leave, advance and overtime approvals check BSign.HasSign (payroll, training, jobs, evaluation and destruction do not — R3-9).