feat: Implement new General Ledger frontend section with comprehensive report screens and cash/bank account management

- Added a new Ledgers sidebar section for statutory-format financial reports and cash/bank-account management.
- Introduced dedicated GL client for API interactions, handling response envelopes and error management.
- Developed report screens for Trial Balance, Balance Sheet, General Ledger, Profit & Loss, Cash Flow, Budget vs Actual, and a new Tax Report.
- Implemented CSV download functionality alongside existing PDF downloads for all report screens.
- Separated Cash and Bank accounts into distinct tables/endpoints, with updated create forms and unified list view.
- Created a new Accounts section for Cheque Management, moving Cash/Bank Accounts from the Ledgers section.
- Updated RBAC navigation to include new permissions and sub-navigation items for the added features.
- Ensured compliance with GL's updated API contract, including renaming fields and adjusting response shapes.
- Addressed various bugs and presentation issues, enhancing user experience across the new module.
This commit is contained in:
2026-07-31 17:57:55 +05:30
parent 76484c7268
commit 22657f0910
49 changed files with 5707 additions and 27 deletions
@@ -0,0 +1,43 @@
using ERPCore.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace ERPCore.Controllers;
/// <summary>
/// Generic reverse proxy into the external General Ledger service — forwards every
/// method/path/query/body under this prefix verbatim via
/// <see cref="IGeneralLedgerService"/> and returns GL's response (status, content-type,
/// body) unchanged. No endpoint-specific shape lives here; see
/// docs/12-GENERAL-LEDGER-INTEGRATION.md for the full GL contract and what this proxy
/// does and doesn't do. Gated by the same ERP door policy as every other v1 endpoint
/// (<see cref="ApiControllerBase"/>) — the shared GL API key is attached server-side
/// only and is never exposed to the frontend.
/// </summary>
[Route("api/v1/gl")]
public sealed class GeneralLedgerController : ApiControllerBase
{
private readonly IGeneralLedgerService _gl;
public GeneralLedgerController(IGeneralLedgerService gl) => _gl = gl;
[HttpGet("{**path}")]
public Task<IActionResult> Get(string path, CancellationToken ct) => ForwardAsync(HttpMethod.Get, path, ct);
[HttpPost("{**path}")]
public Task<IActionResult> Post(string path, CancellationToken ct) => ForwardAsync(HttpMethod.Post, path, ct);
[HttpPut("{**path}")]
public Task<IActionResult> Put(string path, CancellationToken ct) => ForwardAsync(HttpMethod.Put, path, ct);
private async Task<IActionResult> ForwardAsync(HttpMethod method, string path, CancellationToken ct)
{
var body = method == HttpMethod.Get ? null : Request.Body;
var result = await _gl.ForwardAsync(method, path, Request.QueryString.Value, Request.ContentType, body, ct);
return new ContentResult
{
StatusCode = result.StatusCode,
Content = result.Body,
ContentType = result.ContentType ?? "application/json"
};
}
}
@@ -0,0 +1,55 @@
using ERPCore.System.Errors;
namespace ERPCore.Infra.Gl;
/// <summary>
/// HTTP implementation of <see cref="IGeneralLedgerClient"/>. Registered as a typed
/// client (`AddHttpClient&lt;IGeneralLedgerClient, GeneralLedgerClient&gt;`) with its
/// `BaseAddress` bound from `GeneralLedgerService:BaseUrl`. Every call attaches the
/// shared `GeneralLedgerService:ApiKey` as `X-Api-Key` and streams the request/response
/// body straight through, unparsed — GL's own response (status, content-type, body) is
/// returned exactly as received; nothing here reshapes it.
/// </summary>
public sealed class GeneralLedgerClient(HttpClient http, IConfiguration configuration) : IGeneralLedgerClient
{
private readonly HttpClient _http = http;
private readonly string _apiKey = configuration["GeneralLedgerService:ApiKey"] ?? string.Empty;
public async Task<GeneralLedgerResponse> SendAsync(
HttpMethod method, string path, string? queryString, string? contentType, Stream? body, CancellationToken ct)
{
var relativeUri = path.TrimStart('/') + queryString;
using var request = new HttpRequestMessage(method, relativeUri);
request.Headers.TryAddWithoutValidation("X-Api-Key", _apiKey);
if (body is not null && method != HttpMethod.Get)
{
var content = new StreamContent(body);
if (!string.IsNullOrEmpty(contentType))
content.Headers.TryAddWithoutValidation("Content-Type", contentType);
request.Content = content;
}
HttpResponseMessage response;
try
{
response = await _http.SendAsync(request, ct);
}
catch (HttpRequestException)
{
throw new DomainException(ErrorCodes.GlServiceUnavailable, "The General Ledger service is unreachable.", 503);
}
catch (TaskCanceledException) when (!ct.IsCancellationRequested)
{
throw new DomainException(ErrorCodes.GlServiceUnavailable, "The General Ledger service timed out.", 503);
}
var responseBody = await response.Content.ReadAsStringAsync(ct);
return new GeneralLedgerResponse
{
StatusCode = (int)response.StatusCode,
ContentType = response.Content.Headers.ContentType?.ToString(),
Body = responseBody
};
}
}
@@ -0,0 +1,15 @@
namespace ERPCore.Infra.Gl;
/// <summary>
/// Raw HTTP result from the external General Ledger service — status code, content
/// type, and body exactly as GL returned them. Deliberately un-reshaped: GL's own
/// envelope (see the GL service's own API reference) is passed through byte-for-byte
/// so its camelCase-success/PascalCase-error inconsistency and full decimal precision
/// survive the hop unchanged (docs/12-GENERAL-LEDGER-INTEGRATION.md).
/// </summary>
public sealed class GeneralLedgerResponse
{
public int StatusCode { get; init; }
public string? ContentType { get; init; }
public string Body { get; init; } = string.Empty;
}
@@ -0,0 +1,13 @@
namespace ERPCore.Infra.Gl;
/// <summary>
/// Typed HTTP transport to the external General Ledger service. Injects the shared
/// `X-Api-Key` secret and forwards method/path/query/body/content-type verbatim —
/// see docs/12-GENERAL-LEDGER-INTEGRATION.md. Internal: only
/// <see cref="ERPCore.Services.Interfaces.IGeneralLedgerService"/> consumes this.
/// </summary>
public interface IGeneralLedgerClient
{
Task<GeneralLedgerResponse> SendAsync(
HttpMethod method, string path, string? queryString, string? contentType, Stream? body, CancellationToken ct);
}
@@ -36,7 +36,9 @@ public sealed class NavItemConfiguration : IEntityTypeConfiguration<NavItem>
new NavItem { NavItemId = 7, Code = "warehouses", Label = "Warehouses", Href = "/dashboard/warehouse", SortOrder = 7 },
new NavItem { NavItemId = 8, Code = "orders", Label = "Orders", Href = "/dashboard/orders", SortOrder = 8 },
new NavItem { NavItemId = 9, Code = "settings", Label = "Settings", Href = "/dashboard/settings", SortOrder = 9 },
new NavItem { NavItemId = 10, Code = "help", Label = "Help", Href = "/dashboard/help", SortOrder = 10 }
new NavItem { NavItemId = 10, Code = "help", Label = "Help", Href = "/dashboard/help", SortOrder = 10 },
new NavItem { NavItemId = 11, Code = "ledgers", Label = "Ledgers", Href = "/dashboard/ledgers", SortOrder = 11 },
new NavItem { NavItemId = 12, Code = "accounts", Label = "Accounts", Href = "/dashboard/accounts", SortOrder = 12 }
);
}
}
@@ -42,10 +42,26 @@ public sealed class PermissionConfiguration : IEntityTypeConfiguration<Permissio
new Permission { PermissionId = 16, Code = "NAV:products.configuration", SubNavItemId = 6 },
new Permission { PermissionId = 17, Code = "NAV:settings.roles", SubNavItemId = 7 },
new Permission { PermissionId = 18, Code = "NAV:settings.users", SubNavItemId = 8 },
new Permission { PermissionId = 19, Code = "NAV:procurement.requisitions", SubNavItemId = 9 },
new Permission { PermissionId = 20, Code = "NAV:procurement.rfqs", SubNavItemId = 10 },
new Permission { PermissionId = 21, Code = "NAV:procurement.purchase-orders", SubNavItemId = 11 },
new Permission { PermissionId = 22, Code = "NAV:procurement.purchase-returns", SubNavItemId = 12 }
// IDs 28-31 (not 19-22): 19-22 were already claimed by the Ledgers permissions below;
// these procurement rows were never actually migrated into the database before now.
new Permission { PermissionId = 28, Code = "NAV:procurement.requisitions", SubNavItemId = 17 },
new Permission { PermissionId = 29, Code = "NAV:procurement.rfqs", SubNavItemId = 18 },
new Permission { PermissionId = 30, Code = "NAV:procurement.purchase-orders", SubNavItemId = 19 },
new Permission { PermissionId = 31, Code = "NAV:procurement.purchase-returns", SubNavItemId = 20 },
new Permission { PermissionId = 19, Code = "NAV:ledgers", NavItemId = 11 },
new Permission { PermissionId = 20, Code = "NAV:ledgers.trial-balance", SubNavItemId = 9 },
new Permission { PermissionId = 21, Code = "NAV:ledgers.balance-sheet", SubNavItemId = 10 },
new Permission { PermissionId = 22, Code = "NAV:ledgers.general-ledger", SubNavItemId = 11 },
new Permission { PermissionId = 23, Code = "NAV:ledgers.profit-and-loss", SubNavItemId = 12 },
new Permission { PermissionId = 24, Code = "NAV:ledgers.cash-flow", SubNavItemId = 13 },
new Permission { PermissionId = 25, Code = "NAV:ledgers.budget-vs-actual", SubNavItemId = 14 },
new Permission { PermissionId = 27, Code = "NAV:ledgers.tax-report", SubNavItemId = 16 },
// Moved under the new Accounts nav item (2026-07-31) — same PermissionId (26), just a
// renamed Code, so any role already granted this permission keeps it.
new Permission { PermissionId = 26, Code = "NAV:accounts.bank-accounts", SubNavItemId = 15 },
new Permission { PermissionId = 32, Code = "NAV:accounts", NavItemId = 12 },
new Permission { PermissionId = 33, Code = "NAV:accounts.cheque-books", SubNavItemId = 21 },
new Permission { PermissionId = 34, Code = "NAV:accounts.received-cheques", SubNavItemId = 22 }
);
}
}
@@ -34,10 +34,25 @@ public sealed class SubNavItemConfiguration : IEntityTypeConfiguration<SubNavIte
new SubNavItem { SubNavItemId = 7, NavItemId = 9, Code = "settings.roles", Label = "Roles", Href = "/dashboard/settings/roles", SortOrder = 1 },
new SubNavItem { SubNavItemId = 8, NavItemId = 9, Code = "settings.users", Label = "Users", Href = "/dashboard/settings/users", SortOrder = 2 },
// Procurement (NavItemId 4) children — mirror the hub page order.
new SubNavItem { SubNavItemId = 9, NavItemId = 4, Code = "procurement.requisitions", Label = "Requisitions", Href = "/dashboard/procurement/requisitions", SortOrder = 1 },
new SubNavItem { SubNavItemId = 10, NavItemId = 4, Code = "procurement.rfqs", Label = "RFQs", Href = "/dashboard/procurement/rfqs", SortOrder = 2 },
new SubNavItem { SubNavItemId = 11, NavItemId = 4, Code = "procurement.purchase-orders", Label = "Purchase Orders", Href = "/dashboard/procurement/purchase-orders", SortOrder = 3 },
new SubNavItem { SubNavItemId = 12, NavItemId = 4, Code = "procurement.purchase-returns", Label = "Purchase Returns", Href = "/dashboard/procurement/purchase-returns", SortOrder = 4 }
// IDs 17-20 (not 9-12): 9-12 were already claimed by the Ledgers sub-items below;
// these procurement rows were never actually migrated into the database before now.
new SubNavItem { SubNavItemId = 17, NavItemId = 4, Code = "procurement.requisitions", Label = "Requisitions", Href = "/dashboard/procurement/requisitions", SortOrder = 1 },
new SubNavItem { SubNavItemId = 18, NavItemId = 4, Code = "procurement.rfqs", Label = "RFQs", Href = "/dashboard/procurement/rfqs", SortOrder = 2 },
new SubNavItem { SubNavItemId = 19, NavItemId = 4, Code = "procurement.purchase-orders", Label = "Purchase Orders", Href = "/dashboard/procurement/purchase-orders", SortOrder = 3 },
new SubNavItem { SubNavItemId = 20, NavItemId = 4, Code = "procurement.purchase-returns", Label = "Purchase Returns", Href = "/dashboard/procurement/purchase-returns", SortOrder = 4 },
new SubNavItem { SubNavItemId = 9, NavItemId = 11, Code = "ledgers.trial-balance", Label = "Trial Balance", Href = "/dashboard/ledgers/trial-balance", SortOrder = 1 },
new SubNavItem { SubNavItemId = 10, NavItemId = 11, Code = "ledgers.balance-sheet", Label = "Balance Sheet", Href = "/dashboard/ledgers/balance-sheet", SortOrder = 2 },
new SubNavItem { SubNavItemId = 11, NavItemId = 11, Code = "ledgers.general-ledger", Label = "General Ledger", Href = "/dashboard/ledgers/general-ledger", SortOrder = 3 },
new SubNavItem { SubNavItemId = 12, NavItemId = 11, Code = "ledgers.profit-and-loss", Label = "Profit & Loss", Href = "/dashboard/ledgers/profit-and-loss", SortOrder = 4 },
new SubNavItem { SubNavItemId = 13, NavItemId = 11, Code = "ledgers.cash-flow", Label = "Cash Flow", Href = "/dashboard/ledgers/cash-flow", SortOrder = 5 },
new SubNavItem { SubNavItemId = 14, NavItemId = 11, Code = "ledgers.budget-vs-actual", Label = "Budget vs Actual", Href = "/dashboard/ledgers/budget-vs-actual", SortOrder = 6 },
new SubNavItem { SubNavItemId = 16, NavItemId = 11, Code = "ledgers.tax-report", Label = "Tax Report", Href = "/dashboard/ledgers/tax-report", SortOrder = 7 },
// Moved under the new Accounts nav item (2026-07-31) — kept the same SubNavItemId (15)
// rather than delete+recreate, so any role that already had this permission granted
// doesn't silently lose it just because the section it lives under changed.
new SubNavItem { SubNavItemId = 15, NavItemId = 12, Code = "accounts.bank-accounts", Label = "Cash / Bank Accounts", Href = "/dashboard/accounts/bank-accounts", SortOrder = 1 },
new SubNavItem { SubNavItemId = 21, NavItemId = 12, Code = "accounts.cheque-books", Label = "Cheque Books", Href = "/dashboard/accounts/cheque-books", SortOrder = 2 },
new SubNavItem { SubNavItemId = 22, NavItemId = 12, Code = "accounts.received-cheques", Label = "Received Cheques", Href = "/dashboard/accounts/received-cheques", SortOrder = 3 }
);
}
}
@@ -1832,6 +1832,24 @@ namespace ERPCore.Infra.Persistence.Migrations
Label = "Help",
SortOrder = 10,
Status = "Active"
},
new
{
NavItemId = 11,
Code = "ledgers",
Href = "/dashboard/ledgers",
Label = "Ledgers",
SortOrder = 11,
Status = "Active"
},
new
{
NavItemId = 12,
Code = "accounts",
Href = "/dashboard/accounts",
Label = "Accounts",
SortOrder = 12,
Status = "Active"
});
});
@@ -2285,27 +2303,99 @@ namespace ERPCore.Infra.Persistence.Migrations
},
new
{
PermissionId = 19,
PermissionId = 28,
Code = "NAV:procurement.requisitions",
SubNavItemId = 9
SubNavItemId = 17
},
new
{
PermissionId = 29,
Code = "NAV:procurement.rfqs",
SubNavItemId = 18
},
new
{
PermissionId = 30,
Code = "NAV:procurement.purchase-orders",
SubNavItemId = 19
},
new
{
PermissionId = 31,
Code = "NAV:procurement.purchase-returns",
SubNavItemId = 20
},
new
{
PermissionId = 19,
Code = "NAV:ledgers",
NavItemId = 11
},
new
{
PermissionId = 20,
Code = "NAV:procurement.rfqs",
SubNavItemId = 10
Code = "NAV:ledgers.trial-balance",
SubNavItemId = 9
},
new
{
PermissionId = 21,
Code = "NAV:procurement.purchase-orders",
SubNavItemId = 11
Code = "NAV:ledgers.balance-sheet",
SubNavItemId = 10
},
new
{
PermissionId = 22,
Code = "NAV:procurement.purchase-returns",
Code = "NAV:ledgers.general-ledger",
SubNavItemId = 11
},
new
{
PermissionId = 23,
Code = "NAV:ledgers.profit-and-loss",
SubNavItemId = 12
},
new
{
PermissionId = 24,
Code = "NAV:ledgers.cash-flow",
SubNavItemId = 13
},
new
{
PermissionId = 25,
Code = "NAV:ledgers.budget-vs-actual",
SubNavItemId = 14
},
new
{
PermissionId = 27,
Code = "NAV:ledgers.tax-report",
SubNavItemId = 16
},
new
{
PermissionId = 26,
Code = "NAV:accounts.bank-accounts",
SubNavItemId = 15
},
new
{
PermissionId = 32,
Code = "NAV:accounts",
NavItemId = 12
},
new
{
PermissionId = 33,
Code = "NAV:accounts.cheque-books",
SubNavItemId = 21
},
new
{
PermissionId = 34,
Code = "NAV:accounts.received-cheques",
SubNavItemId = 22
});
});
@@ -3948,7 +4038,7 @@ namespace ERPCore.Infra.Persistence.Migrations
},
new
{
SubNavItemId = 9,
SubNavItemId = 17,
Code = "procurement.requisitions",
Href = "/dashboard/procurement/requisitions",
Label = "Requisitions",
@@ -3958,7 +4048,7 @@ namespace ERPCore.Infra.Persistence.Migrations
},
new
{
SubNavItemId = 10,
SubNavItemId = 18,
Code = "procurement.rfqs",
Href = "/dashboard/procurement/rfqs",
Label = "RFQs",
@@ -3968,7 +4058,7 @@ namespace ERPCore.Infra.Persistence.Migrations
},
new
{
SubNavItemId = 11,
SubNavItemId = 19,
Code = "procurement.purchase-orders",
Href = "/dashboard/procurement/purchase-orders",
Label = "Purchase Orders",
@@ -3978,13 +4068,113 @@ namespace ERPCore.Infra.Persistence.Migrations
},
new
{
SubNavItemId = 12,
SubNavItemId = 20,
Code = "procurement.purchase-returns",
Href = "/dashboard/procurement/purchase-returns",
Label = "Purchase Returns",
NavItemId = 4,
SortOrder = 4,
Status = "Active"
},
new
{
SubNavItemId = 9,
Code = "ledgers.trial-balance",
Href = "/dashboard/ledgers/trial-balance",
Label = "Trial Balance",
NavItemId = 11,
SortOrder = 1,
Status = "Active"
},
new
{
SubNavItemId = 10,
Code = "ledgers.balance-sheet",
Href = "/dashboard/ledgers/balance-sheet",
Label = "Balance Sheet",
NavItemId = 11,
SortOrder = 2,
Status = "Active"
},
new
{
SubNavItemId = 11,
Code = "ledgers.general-ledger",
Href = "/dashboard/ledgers/general-ledger",
Label = "General Ledger",
NavItemId = 11,
SortOrder = 3,
Status = "Active"
},
new
{
SubNavItemId = 12,
Code = "ledgers.profit-and-loss",
Href = "/dashboard/ledgers/profit-and-loss",
Label = "Profit & Loss",
NavItemId = 11,
SortOrder = 4,
Status = "Active"
},
new
{
SubNavItemId = 13,
Code = "ledgers.cash-flow",
Href = "/dashboard/ledgers/cash-flow",
Label = "Cash Flow",
NavItemId = 11,
SortOrder = 5,
Status = "Active"
},
new
{
SubNavItemId = 14,
Code = "ledgers.budget-vs-actual",
Href = "/dashboard/ledgers/budget-vs-actual",
Label = "Budget vs Actual",
NavItemId = 11,
SortOrder = 6,
Status = "Active"
},
new
{
SubNavItemId = 16,
Code = "ledgers.tax-report",
Href = "/dashboard/ledgers/tax-report",
Label = "Tax Report",
NavItemId = 11,
SortOrder = 7,
Status = "Active"
},
new
{
SubNavItemId = 15,
Code = "accounts.bank-accounts",
Href = "/dashboard/accounts/bank-accounts",
Label = "Cash / Bank Accounts",
NavItemId = 12,
SortOrder = 1,
Status = "Active"
},
new
{
SubNavItemId = 21,
Code = "accounts.cheque-books",
Href = "/dashboard/accounts/cheque-books",
Label = "Cheque Books",
NavItemId = 12,
SortOrder = 2,
Status = "Active"
},
new
{
SubNavItemId = 22,
Code = "accounts.received-cheques",
Href = "/dashboard/accounts/received-cheques",
Label = "Received Cheques",
NavItemId = 12,
SortOrder = 3,
Status = "Active"
});
});
+10
View File
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using ERPCore.Infra.Auth;
using ERPCore.Infra.Auth.AuthHex;
using ERPCore.Infra.Gl;
using ERPCore.Infra.Persistence;
using ERPCore.Infra.Storage;
using ERPCore.Infra.UoW;
@@ -51,6 +52,15 @@ builder.Services.AddScoped<IAuthUserService, AuthUserService>();
builder.Services.AddScoped<IAuthRecoveryService, AuthRecoveryService>();
builder.Services.AddScoped<IAuthAltService, AuthAltService>();
// General Ledger service proxy → external GL microservice (docs/12-GENERAL-LEDGER-INTEGRATION.md)
builder.Services.AddHttpClient<IGeneralLedgerClient, GeneralLedgerClient>(c =>
{
var baseUrl = builder.Configuration["GeneralLedgerService:BaseUrl"]
?? throw new InvalidOperationException("GeneralLedgerService:BaseUrl is not configured.");
c.BaseAddress = new Uri(baseUrl);
});
builder.Services.AddScoped<IGeneralLedgerService, GeneralLedgerService>();
// Current-user (audit actor). AuthHex has no sub/nameid → a claims transformation
// JIT-provisions a local shadow user and injects the local `int` id as `nameid`.
builder.Services.AddHttpContextAccessor();
@@ -0,0 +1,16 @@
using ERPCore.Infra.Gl;
using ERPCore.Services.Interfaces;
namespace ERPCore.Services;
/// <inheritdoc cref="IGeneralLedgerService"/>
public sealed class GeneralLedgerService : IGeneralLedgerService
{
private readonly IGeneralLedgerClient _client;
public GeneralLedgerService(IGeneralLedgerClient client) => _client = client;
public Task<GeneralLedgerResponse> ForwardAsync(
HttpMethod method, string path, string? queryString, string? contentType, Stream? body, CancellationToken ct)
=> _client.SendAsync(method, path, queryString, contentType, body, ct);
}
@@ -0,0 +1,16 @@
using ERPCore.Infra.Gl;
namespace ERPCore.Services.Interfaces;
/// <summary>
/// Single entry point into the external General Ledger service — the one function
/// used both by <see cref="ERPCore.Controllers.GeneralLedgerController"/> (frontend
/// requests, forwarded verbatim) and, once wired, other ERPCore services that need to
/// post directly to GL (e.g. GRN confirm, adjustments — docs/12-GENERAL-LEDGER-INTEGRATION.md).
/// No business logic lives here yet; this pass only connects the transport.
/// </summary>
public interface IGeneralLedgerService
{
Task<GeneralLedgerResponse> ForwardAsync(
HttpMethod method, string path, string? queryString, string? contentType, Stream? body, CancellationToken ct);
}
@@ -66,4 +66,7 @@ public static class ErrorCodes
public const string LeftoverExceedsConsumed = "LEFTOVER_EXCEEDS_CONSUMED";
public const string RunCostClosed = "RUN_COST_CLOSED";
public const string RunNotCancellable = "RUN_NOT_CANCELLABLE";
// General Ledger service proxy (GeneralLedgerController → external GL service, docs/12)
public const string GlServiceUnavailable = "GL_SERVICE_UNAVAILABLE";
}
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=ERPCore;Username=postgres;Password=root"
"DefaultConnection": "Host=localhost;Port=5432;Database=ERPCore;Username=postgres;Password=dbuser"
},
"AuthHex": {
"BaseUrl": "http://localhost:5011"
+4
View File
@@ -22,5 +22,9 @@
"RootPath": "App_Data/hr-documents",
"MaxSizeBytes": 10485760
},
"GeneralLedgerService": {
"BaseUrl": "https://localhost:7024/api/v1/",
"ApiKey": "1A5FE0E389C029B1FBCD2A94650CEBF0656083BF06FF48E5E11987040ABA262D"
},
"AllowedHosts": "*"
}