Backend Releases¶
Source: truload-backend — continuous-release model; each merge to main is collected into a versioned release.
v1.3.2 — 2026-05-22¶
Subscription Uniform Integration¶
This release brings TruLoad's subscription enforcement to full parity with the uniform subscription workflow deployed across all other BengoBox platform services (ordering, POS, inventory, logistics, treasury, marketflow).
Critical bug fix — subscription enforcement was non-functional
SubscriptionEnforcementMiddleware was reading the org_id JWT claim but JwtService.GenerateAccessToken was emitting organization_id. These names never matched, so orgIdClaim was always null and enforcement was silently skipped for every commercial tenant. Fixed in this release:
JwtService.GenerateAccessTokennow emitsorg_id- Middleware gains a backward-compat fallback to
organization_idfor sessions with tokens issued before this fix
Organisation bypass fields (billing_mode, is_demo)
New columns on the organizations table (migration 20260522000928_AddOrganizationBypassFields):
| Column | Type | Purpose |
|---|---|---|
billing_mode |
VARCHAR(50) nullable |
"service_charge" → bypass subscription gating; tenant is billed per-transaction instead of via subscription |
is_demo |
BOOLEAN DEFAULT FALSE |
Demo/training orgs bypass all subscription enforcement |
Both values are now embedded as JWT claims by JwtService.GenerateAccessToken (no extra DB query — the Organization record is already loaded for org_code).
Subscription enforcement middleware — bypass logic
SubscriptionEnforcementMiddleware now has two fast-path bypasses before the Redis cache lookup:
- JWT claim
billing_mode == "service_charge"→ pass through immediately - JWT claim
is_demo == "true"→ pass through immediately - Belt-and-suspenders: if neither claim is present (stale token), the org model
BillingMode/IsDemois checked after the DB load on a cache miss
NATS subscription cache invalidation
New Services/Background/SubscriptionCacheInvalidationService (ASP.NET Core BackgroundService):
- Subscribes to the
tenant.subscription.updatedNATS subject published by subscriptions-api on every plan or status change - Extracts
tenant_slugfrom the event payload (supports both flat{ tenant_slug }and nested{ payload: { tenant_slug } }formats) - Resolves
tenant_slug → Organization.SsoTenantSlug → org.Idvia a scoped DB query - Deletes
sub:status:{orgId}from Redis — forces the next request to re-fetch from subscriptions-api rather than serving stale 60-second cached status - Controlled by
Nats:Enabled(defaultsfalse— safe for dev without a NATS server)
NuGet added: NATS.Net.
New appsettings.json section:
"Nats": {
"Url": "nats://localhost:4222",
"Enabled": false
}
Production K8s env override:
NATS__URL=nats://nats.platform.svc.cluster.local:4222
NATS__ENABLED=true
Frontend — no changes required
truload-frontend was already aligned with the uniform subscription pattern (v0.1.10 of shared-ui-lib). The use-subscription hook already reads billing_mode and is_demo from JWT claims and the SubscriptionBanner already passes isServiceCharge and isDemo to the shared component. These features activate automatically once the backend starts embedding the bypass claims in new tokens.
v1.3.1 — 2026-05-21¶
Tolerance Precedence Fix and Standard Config Updates¶
Axle config tolerance now correctly takes precedence over global tolerance
CalculateGroupToleranceAsync: config-specificToleranceKg/TolerancePercentageis now evaluated first (was priority #3, now priority #1), ensuring per-config overrides always win over Act-level global tolerance- Weight tickets now display
Axle tolerance: X,XXX kg (config)when a per-config override is active (was always showing0% (strict))
Standard axle config tolerance updates no longer blocked
UpdateStandardConfigAsyncadded toAxleConfigurationRepository— standard configurations can now have theirToleranceKgand notes updated viaPUT /api/v1/AxleConfiguration/{id}without returning 400- Previously all standard-config updates returned
400 Cannot modify standard EAC configurations
Tenant database auto-migration on startup
- Backend now applies EF Core migrations and seeds all dedicated tenant databases (e.g. kuraweigh) automatically on startup — no manual migration job required
TenantConnectionStringProvider.GetDedicatedTenantDatabases()addedDatabase:DirectHostconfig controls PgBouncer bypass for migration advisory locks
v1.3.0 — 2026-05-20¶
Commercial Weighing Workflows, Subscriptions, and Cleanup¶
Two-pass resume flow — pending-by-plate endpoint
GET /api/v1/commercial-weighing/pending-by-plate/{regNo}— returns open first-weight-only transactions for a vehicle within the configured threshold (default 8 h)CommercialWeighingService.GetPendingByPlateAsyncadded; respects tenant isolation and configurable hour threshold
Stale transaction notifications
- New Hangfire recurring job
StaleWeighingNotificationJobruns every 30 minutes - Finds commercial transactions stuck at
first_weight_capturedpast the threshold and emails Commercial Weighing Managers and Station Managers - Deduplication via
StaleAlertSentAtcolumn — one alert per transaction - Migration
20260520174750_AddStaleAlertSentAtToWeighingTransactionapplied
Configurable pending threshold setting
- New system setting
commercial.pending_weighing_threshold_hours(default 8) seeded inSystemConfigurationSeeder - Constant
SettingKeys.CommercialPendingWeighingThresholdHoursadded toApplicationSettings
Completion notifications
CommercialWeighingServicenow sendstruload/weight_ticketemail to the transporter on second-weight completion or stored-tare use- Tolerance exception alerts (
truload/tolerance_exception_alert) sent to station managers when net weight discrepancy exceeds configured tolerance band
Subscription validation
CommercialWeighingController.Initiatechecks subscription status before creating a transaction- Returns HTTP 402
{ code: "subscription_inactive" }if the org's subscription is not ACTIVE or TRIAL - Fail-open: if subscriptions-api is unreachable, the weighing proceeds
Treasury pay portal URL from config
Treasury:PayPortalBaseUrladded toappsettings.json(defaulthttps://books.codevertexitsolutions.com/pay)- Both
CommercialWeighingServiceandInvoiceServicenow read this fromIConfiguration; hardcoded URL removed
Module visibility fixes
TenantModules.SetupNotifications = "setup_notifications"constant addedDefaultCommercialWeighingModulesnow includessetup_notificationsso commercial tenants see the Notifications setup page- TRULOAD-DEMO org
EnabledModulesJsonupdated to includesetup_notificationsandbilling
Notifications and Integrations page cleanup
IntegrationConfigControllerreturns HTTP 400 for notification-managed providers (sms_twilio,sms_africastalking,email_smtp) — these are managed by notifications-service
v1.2.0 — 2026-04-22¶
Enforcement Fixes and Charge Accuracy¶
Driver + owner joint-liability charge split (Cap 403 / EAC VLC)
Under the Kenya Traffic Act Cap 403 and the EAC Vehicle Load Control Act, the registered owner and the driver are jointly and severally liable for overload penalties. The fee schedule gives the per-party amount; the billed total must therefore be doubled.
ProsecutionService.CalculateChargesAsync: total fee is nowperPartyFee × 2ChargeCalculationResultgainsPerPartyFeeKesandPerPartyFeeUsdso ticket PDFs and downstream systems can show the splitProsecutionCaseDtogains the same two fields (derived asTotalFee / 2from stored data — no migration required)- Repeat-offender handling is unchanged: conviction number 2 selects a higher fee band from the schedule; no blanket multiplier is applied on top
Special releases — pending filter, search, and pagination
The special-release approval queue previously returned all records (including already-approved and rejected releases), causing supervisors to see closed items in the pending list.
SpecialReleaseRepository.GetPendingApprovalsAsync: filter changed to!IsApproved && !IsRejected- Search params added:
caseNo,releaseType,from,to - Response shape changed from
IEnumerable<SpecialRelease>to(List<SpecialRelease> Items, int TotalCount)for pagination ISpecialReleaseService.GetPendingApprovalsAsyncreturnsPagedResponse<SpecialReleaseDto>
Vehicle registration search — space normalization
Kenyan plates are issued both with and without an internal space (e.g. KCX091X and KCX 091X are the same physical plate). All search paths now normalize spaces before comparing.
VehicleRepository.GetByRegNoAsync: strips spaces before equality check using PostgreSQLREPLACE()VehicleRepository.SearchAsync: ILike + REPLACE onRegNo,ChassisNo,EngineNoCaseRegisterRepository.SearchAsync: vehicle reg filter added with the same ILike + REPLACE pattern (was declared but never wired into the WHERE clause)
Vehicle makes — restore soft-deleted records on create
When a make is soft-deleted (e.g. HOWO, MAZDA, TOYOTA from a data-cleanup run), users cannot see it in the select dropdown and attempt to recreate it. Previously this hit a DB unique-index constraint and returned a confusing 409. Now:
VehicleMakesRepository.GetByCodeIncludingDeletedAsyncadded — queries without theDeletedAt == nullfilterVehicleMakesController.Create: checks for a soft-deleted entry by code before attempting an insert; if found, clearsDeletedAt, restoresIsActive, updates name/country/description, and returns the restored record
v1.1.0 — 2026-04-21¶
Commercial Weighing Polish¶
Commercial Settings
Organizationmodel gainsDefaultTareExpiryDays,CommercialWeighingFeeKes, andPaymentGatewayfields- Migration
20260421064138_AddDefaultTareExpiryDaysToOrganizationapplied PATCH /api/v1/Organizations/current/commercial-settings— update weighing fee and tare expiry per organisation
Tolerance Management
DELETE /api/v1/commercial-weighing/tolerance-settings/{id}— remove a tolerance ruleCommercialWeighingService.DeleteToleranceSetting()added
v1.0.1 — 2026-04-18¶
Commercial Weighing — Core¶
New: CommercialWeighingController — 13 endpoints under api/v1/commercial-weighing:
| Method | Path | Action |
|---|---|---|
| POST | / |
Initiate transaction |
| GET | /{id} |
Get transaction result |
| POST | /{id}/first-weight |
Capture first pass |
| POST | /{id}/second-weight |
Capture second pass |
| POST | /{id}/use-stored-tare |
Apply stored tare |
| PUT | /{id}/quality-deduction |
Apply quality deduction |
| GET | /vehicles/{vehicleId}/tare-history |
Vehicle tare history |
| GET | /{id}/ticket/pdf |
Final weight ticket PDF |
| GET | /{id}/interim-ticket/pdf |
Interim ticket PDF (after first pass) |
| POST | /{id}/approve-tolerance-exception |
Supervisor tolerance override |
| GET | /tolerance-settings |
List tolerance settings |
| POST | /tolerance-settings |
Create tolerance setting |
| PUT | /tolerance-settings/{id} |
Update tolerance setting |
New: CommercialWeighingService (703 lines)
- Two-pass weighing flow: initiate → first weight → second weight → net calculation
- Stored tare support: apply pre-registered vehicle tare for single-pass operations
- Net weight calculation:
Net = Gross − Tare; quality deduction:AdjustedNet = Net − QualityDeduction - Tolerance exception approval (supervisor role required)
- Tare history recording per vehicle
New: CommercialWeightTicketDocument (QuestPDF)
- Weight summary hero: tare / gross / net prominently displayed
- Axle weights table via
ComposeAxleWeights() - Quality deduction section (conditional)
- Organisation logo, station header, operator signature line
New: CommercialReportGenerator — 10 report types:
- Daily Weighing Summary
- Tonnage by Transporter
- Cargo Volume by Type
- Weight Discrepancy Report
- Revenue Summary
- Throughput Analysis
- Tare Audit
- Fleet Utilisation
- Driver Productivity
- Quality / Commodity Report
Database schema additions (applied via EF Core migrations):
| Table | New columns |
|---|---|
weighing.weighing_transactions |
WeighingMode, FirstWeightKg, FirstWeightType, FirstWeightAt, SecondWeightKg, SecondWeightType, SecondWeightAt, TareWeightKg, TareSource, GrossWeightKg, NetWeightKg, ConsignmentNo, ExpectedNetWeightKg, WeightDiscrepancyKg, OrderReference, SealNumbers, TrailerRegNo, Remarks, QualityDeductionKg, AdjustedNetWeightKg, IndustryMetadata (JSONB) |
weighing.vehicles |
DefaultTareWeightKg, LastTareWeightKg, LastTareWeighedAt, TareExpiryDays |
weighing.commercial_tolerance_settings |
New table: Id, OrgId, CargoTypeId, TolerancePct, ToleranceKg, AppliesTo |
weighing.vehicle_tare_history |
New table: Id, VehicleId, TareWeightKg, WeighedAt, Source |
weighing.drivers |
LicenseExpiryDate |
weighing.transporters |
PortalAccountEmail, PortalAccountId |
WeighingService — commercial mode bypass: skips compliance checks, enforcement fees, yard tracking, and prohibition logic when WeighingMode == Commercial.
v1.0.0 — 2026-04-14¶
Transporter Portal¶
New: TransporterPortalController — 9 endpoints under api/v1/portal:
POST /register— create portal account linked to transporter recordGET /weighings— paginated weighing history for the authenticated transporterGET /weighings/{id}— single transaction detailGET /vehicles— transporter's registered vehicle fleetGET /weight-trends— net weight trend data for chartsGET /drivers— transporter's driversGET /driver-performance— per-driver trip and tonnage statisticsGET /consignments— consignment tracking with statusGET /subscription— current plan and feature entitlements
New: TransporterPortalService (471 lines)
- Cross-tenant query using
IgnoreQueryFilters()— a transporter can see weighings from any tenant they are linked to - Feature gating via
ISubscriptionService.GetFeaturesAsync():portal_access,ticket_download,multi_site_access,data_export,driver_reports,vehicle_trends,api_access,analytics,consignment_tracking,webhooks
v0.9.0 — 2026-03-15¶
Initial production release covering axle-load enforcement module, case management, prosecution, invoicing, and M-PESA integration.