Files
ERP-core/Backend/ERPCore/Program.cs
T
ImanThiyanga 8e472ac996
CI/CD - ERP Core / scripts (pull_request) Failing after 12m21s
CI/CD - ERP Core / e2e-typecheck (pull_request) Failing after 13m13s
CI/CD - ERP Core / frontend (pull_request) Failing after 14m5s
CI/CD - ERP Core / backend (pull_request) Failing after 14m57s
CI/CD - ERP Core / detect (pull_request) Failing after 13m42s
CI/CD - ERP Core / Build & Deploy (pull_request) Failing after 12m47s
CI/CD - ERP Core / ci-ok (pull_request) Failing after 14m58s
initialising for ci-cd pipeline
2026-09-16 11:53:55 +05:30

269 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
using ERPCore.Repositories;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services;
using ERPCore.Services.Interfaces;
using ERPCore.Services.Auth;
using ERPCore.Services.Hrm;
using ERPCore.Services.Production;
using ERPCore.Services.Stock;
using ERPCore.System.Errors;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.OpenApi;
using Npgsql;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Fail fast on missing deployment config. Outside Development these arrive as environment
// variables (docs/40-DEPLOYMENT.md §5); a blank value would otherwise surface later as an
// unrelated crash (e.g. `new Uri("")` for AuthHex:BaseUrl) instead of naming the key.
if (!builder.Environment.IsDevelopment())
{
string[] requiredKeys =
[
"ConnectionStrings:DefaultConnection",
"AuthHex:BaseUrl",
"GeneralLedgerService:BaseUrl",
"GeneralLedgerService:ApiKey",
"Auth:RsaPublicKeyXml",
];
var missing = requiredKeys.Where(k => string.IsNullOrWhiteSpace(builder.Configuration[k])).ToArray();
if (missing.Length > 0)
throw new InvalidOperationException(
$"Missing required configuration for environment '{builder.Environment.EnvironmentName}': " +
$"{string.Join(", ", missing)}. Set them as environment variables (':' becomes '__', " +
"e.g. ConnectionStrings__DefaultConnection).");
}
// Serilog (file sink) — reads levels from configuration, plus a rolling daily file. The path is
// configurable so deployed instances log outside their release folder (LogFile:Path).
builder.Host.UseSerilog((ctx, cfg) => cfg
.ReadFrom.Configuration(ctx.Configuration)
.WriteTo.File(ctx.Configuration["LogFile:Path"] ?? "logs/erpcore-.log", rollingInterval: RollingInterval.Day));
// Controllers + JSON: serialize enums as their string names (docs/11 §8, camelCase).
builder.Services.AddControllers()
.AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
// EF Core + PostgreSQL
builder.Services.AddDbContext<ErpDbContext>(o =>
{
o.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
o.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
});
// ProblemDetails (RFC 7807) + domain-exception mapping
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<DomainExceptionHandler>();
// Auth: validate external AuthHex RS256 tokens + ERP door policy (docs/10 A.4)
builder.Services.AddErpJwtAuth(builder.Configuration);
// AuthController proxy → AuthHex (docs/11 §2.0)
builder.Services.AddHttpClient<IAuthHexClient, AuthHexClient>(c =>
{
// Blank counts as missing: the base appsettings.json ships these keys empty on purpose.
var baseUrl = builder.Configuration["AuthHex:BaseUrl"];
if (string.IsNullOrWhiteSpace(baseUrl))
throw new InvalidOperationException("AuthHex:BaseUrl is not configured (set it in appsettings.Development.json locally).");
c.BaseAddress = new Uri(baseUrl);
});
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"];
if (string.IsNullOrWhiteSpace(baseUrl))
throw new InvalidOperationException("GeneralLedgerService:BaseUrl is not configured (set it in appsettings.Development.json locally).");
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();
builder.Services.AddScoped<ICurrentUser, CurrentUser>();
builder.Services.AddScoped<IClaimsTransformation, ShadowUserClaimsTransformation>();
// Unit of work + generic repository base
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
// Master-data services (docs/11 §2)
builder.Services.AddScoped<ICustomerService, CustomerService>();
builder.Services.AddScoped<IItemService, ItemService>();
builder.Services.AddScoped<IUomService, UomService>();
builder.Services.AddScoped<ICategoryService, CategoryService>();
builder.Services.AddScoped<IBrandService, BrandService>();
builder.Services.AddScoped<IItemTypeService, ItemTypeService>();
//builder.Services.AddScoped<ICompanyProfileService, CompanyProfileService>();
builder.Services.AddScoped<IProductConfigService, ProductConfigService>();
builder.Services.AddScoped<IBranchConfigService, BranchConfigService>();
builder.Services.AddScoped<IBranchAccessService, BranchAccessService>();
builder.Services.AddSingleton<INotificationModuleCatalog, NotificationModuleCatalog>();
builder.Services.AddScoped<INotificationConfigService, NotificationConfigService>();
builder.Services.AddScoped<IVendorService, VendorService>();
builder.Services.AddScoped<IWarehouseService, WarehouseService>();
// RBAC / sidebar: Role (shadow of AuthHex) + Permission assignment + user management
builder.Services.AddScoped<IRoleService, RoleService>();
builder.Services.AddScoped<IUserManagementService, UserManagementService>();
// Cross-cutting + procurement services (docs/11 §3)
builder.Services.AddScoped<INumberSequenceService, NumberSequenceService>();
builder.Services.AddScoped<IRequisitionService, RequisitionService>();
builder.Services.AddScoped<IRfqService, RfqService>();
builder.Services.AddScoped<IPurchaseOrderService, PurchaseOrderService>();
// Stock core + goods receipt (docs/11 §4–5)
builder.Services.AddScoped<IItemMeasure, ItemMeasure>();
builder.Services.AddScoped<IFifoCostingService, FifoCostingService>();
builder.Services.AddScoped<IStockService, StockService>();
builder.Services.AddScoped<IGrnService, GrnService>();
// Purchase invoices (vendor invoices consolidating Confirmed GRNs)
builder.Services.AddScoped<IPurchaseInvoiceService, PurchaseInvoiceService>();
builder.Services.AddScoped<IPurchaseInvoicePaymentService, PurchaseInvoicePaymentService>();
// Sales (Phase 1)
builder.Services.AddScoped<ISalesPricingService, SalesPricingService>();
builder.Services.AddScoped<ISalesDomainService, SalesDomainService>();
builder.Services.AddScoped<ISalesPostingService, SalesPostingService>();
builder.Services.AddScoped<ISalesDocumentWorkflowService, SalesDocumentWorkflowService>();
builder.Services.AddScoped<ISalesMappingService, SalesMappingService>();
builder.Services.AddScoped<ISalesInvoiceService, SalesInvoiceService>();
builder.Services.AddScoped<ISalesInvoicePaymentService, SalesInvoicePaymentService>();
builder.Services.AddScoped<ISalesSlipService, SalesSlipService>();
builder.Services.AddScoped<IBundleSaleService, BundleSaleService>();
builder.Services.AddScoped<ISalesPromotionSuggestionService, SalesPromotionSuggestionService>();
builder.Services.AddScoped<ISalesReportService, SalesReportService>();
builder.Services.AddScoped<ISalesReturnService, SalesReturnService>();
builder.Services.AddScoped<ISalesDayEndService, SalesDayEndService>();
// Stock transactions + reference data (docs/11 §5–6)
builder.Services.AddScoped<IStockMutator, StockMutator>();
builder.Services.AddScoped<IReasonCodeService, ReasonCodeService>();
builder.Services.AddScoped<IAdjustmentService, AdjustmentService>();
builder.Services.AddScoped<ITransferService, TransferService>();
builder.Services.AddScoped<ICountService, CountService>();
builder.Services.AddScoped<IReorderService, ReorderService>();
builder.Services.AddScoped<IPurchaseReturnService, PurchaseReturnService>();
// Cross-cutting: audit trail + GL-ready journal read access (docs/11 §6 / FR-X-02, FR-STK-13)
builder.Services.AddScoped<IAuditService, AuditService>();
// Dashboard aggregate stats (cross-domain read: stock, GRN, procurement)
builder.Services.AddScoped<IDashboardService, DashboardService>();
// HRM (docs/13-BACKEND-HRM-API.md): org masters, employee core, staff documents
builder.Services.AddSingleton<IFileStorageService, LocalFileStorageService>();
builder.Services.AddScoped<IBranchService, BranchService>();
builder.Services.AddScoped<IDepartmentService, DepartmentService>();
builder.Services.AddScoped<IDesignationService, DesignationService>();
builder.Services.AddScoped<IEmploymentTypeService, EmploymentTypeService>();
builder.Services.AddScoped<IWorkShiftService, WorkShiftService>();
builder.Services.AddScoped<IEmployeeUserLinkService, EmployeeUserLinkService>();
builder.Services.AddScoped<IEmployeeService, EmployeeService>();
builder.Services.AddScoped<IHrDocumentTypeService, HrDocumentTypeService>();
builder.Services.AddScoped<IEmployeeDocumentService, EmployeeDocumentService>();
// HRM: Leave (docs/13-BACKEND-HRM-API.md §5) — LeaveType/LeaveBalance built before
// LeaveRequest since approval increments balances; Attendance depends on LeaveRequest.
builder.Services.AddScoped<ILeaveTypeService, LeaveTypeService>();
builder.Services.AddScoped<ILeaveBalanceService, LeaveBalanceService>();
builder.Services.AddScoped<ILeaveRequestService, LeaveRequestService>();
// HRM: Attendance (docs/13-BACKEND-HRM-API.md §4)
builder.Services.AddScoped<IAttendanceComputationService, AttendanceComputationService>();
builder.Services.AddScoped<IAttendanceUploadService, AttendanceUploadService>();
// HRM: Payroll (docs/13-BACKEND-HRM-API.md §6) — masters/settings before the
// calculation service, which composes them; PayrollRunService orchestrates last.
builder.Services.AddScoped<ISalaryComponentService, SalaryComponentService>();
builder.Services.AddScoped<IEmployeeSalaryStructureService, EmployeeSalaryStructureService>();
builder.Services.AddScoped<IEmployeeLoanService, EmployeeLoanService>();
builder.Services.AddScoped<IPayrollStatutorySettingService, PayrollStatutorySettingService>();
builder.Services.AddScoped<ITaxSlabService, TaxSlabService>();
builder.Services.AddScoped<IPayrollCalculationService, PayrollCalculationService>();
builder.Services.AddScoped<IPayrollRunService, PayrollRunService>();
builder.Services.AddScoped<IPayslipService, PayslipService>();
// HRM: Reports (docs/13-BACKEND-HRM-API.md §6) — read-only, no new entities
builder.Services.AddScoped<IHrReportService, HrReportService>();
// Manufacturing / Production Lines (docs/30-BACKEND-PHASE2.md Part A). Templates stand
// alone; runs consume FifoCostingService for all stock movement. ProductionGraphValidator
// is deliberately unregistered — it is a pure static algorithm, not an injected service.
builder.Services.AddScoped<IProductionTemplateService, ProductionTemplateService>();
builder.Services.AddScoped<IProductionRunService, ProductionRunService>();
// Health checks (EF Core DB)
builder.Services.AddHealthChecks().AddDbContextCheck<ErpDbContext>();
// Swagger / OpenAPI (Swashbuckle v10 → OpenAPI 3.1)
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(o =>
{
o.SwaggerDoc("v1", new OpenApiInfo { Title = "ERPCore API", Version = "v1" });
o.CustomSchemaIds(t => t.FullName!.Replace("+", "."));
});
var app = builder.Build();
// Seed configurable reference data (reason codes) idempotently at startup.
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<ErpDbContext>();
// await EnsureMigrationBaselineAsync(db);
await db.Database.MigrateAsync();
try
{
// Demo rows (sample catalog, stock, customers, sales docs) default on only in Development;
// the test deployment opts in with Seed__DemoData=true, release never does.
var includeDemoData = app.Configuration.GetValue("Seed:DemoData", app.Environment.IsDevelopment());
await DataSeeder.SeedAsync(db, includeDemoData);
}
catch (Exception ex)
{
throw new InvalidOperationException("Database migration succeeded, but startup seeding failed.", ex);
}
}
// Behind the co-located Apache reverse proxy (docs/40-DEPLOYMENT.md): take the client IP and
// scheme from X-Forwarded-*. Only loopback proxies are trusted by default, which is exactly it.
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseSerilogRequestLogging();
// Off in Production only. The test deployment (Staging) keeps it; it is still not public there
// because Apache proxies only /api/ — reach it through an SSH tunnel to the backend port.
if (!app.Environment.IsProduction())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseExceptionHandler(); // -> DomainExceptionHandler / ProblemDetails
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks("/health");
app.Run();