using General_Ledger_Service.DTOs.Common; using General_Ledger_Service.DTOs.CostCenters; using General_Ledger_Service.Enums; using General_Ledger_Service.Exceptions; using General_Ledger_Service.Infra.UoW; using General_Ledger_Service.Interfaces.Repositories; using General_Ledger_Service.Interfaces.Services; using General_Ledger_Service.Models; namespace General_Ledger_Service.Services { public class CostCenterService( ICostCenterRepository costCenterRepository, ISystemConfigRepository systemConfigRepository, IAuditLogRepository auditLogRepository, IUnitOfWork unitOfWork) : ICostCenterService { private readonly ICostCenterRepository _costCenterRepository = costCenterRepository; private readonly ISystemConfigRepository _systemConfigRepository = systemConfigRepository; private readonly IAuditLogRepository _auditLogRepository = auditLogRepository; private readonly IUnitOfWork _unitOfWork = unitOfWork; public async Task CreateCostCenterAsync(CreateCostCenterRequest request) { CostCenter? parent = null; if (request.ParentCostCenterId.HasValue) { parent = await _costCenterRepository.GetByIdAsync(request.ParentCostCenterId.Value) ?? throw new NotFoundException("Parent cost center not found"); var resultingDepth = await _costCenterRepository.GetAncestorDepthAsync(parent.CostCenterId); await DepthGuard.EnsureWithinMaxDepthAsync( _systemConfigRepository, "COST_CENTER_MAX_DEPTH", null, resultingDepth, "Maximum Cost Center depth exceeded"); } var code = await GenerateCostCenterCodeAsync(parent); var costCenter = new CostCenter { Code = code, Name = request.Name, ParentCostCenterId = request.ParentCostCenterId, IsActive = request.IsActive }; await _unitOfWork.BeginAsync(); try { await _costCenterRepository.AddAsync(costCenter); await _unitOfWork.SaveChangesAsync(); await _auditLogRepository.AddAsync(new AuditLog { Category = AuditCategory.Administrative, EntityName = "CostCenter", EntityId = costCenter.CostCenterId.ToString(), Action = AuditAction.Create, Description = $"Cost center {costCenter.Code} created", PerformedBy = null }); await _unitOfWork.SaveChangesAsync(); await _unitOfWork.CommitAsync(); } catch { await _unitOfWork.RollbackAsync(); throw; } return ResponseHelpers.Created("Cost center created successfully", costCenter); } private async Task GenerateCostCenterCodeAsync(CostCenter? parent) { if (parent != null) { for (var suffix = 1; suffix <= 99; suffix++) { var code = $"{parent.Code}-{suffix:D2}"; if (await _costCenterRepository.GetByCodeAsync(code) is null) { return code; } } throw new BadRequestException("Maximum number of child cost centers under this parent has been reached."); } // Probes each candidate slot directly (same fix as AccountService.GenerateAccountCodeAsync) // rather than trusting a root count, so a pre-existing, manually-coded cost center can // never collide with a generated one. for (var slot = 1; slot <= 999; slot++) { var code = $"CC-{slot:D3}"; if (await _costCenterRepository.GetByCodeAsync(code) is null) { return code; } } throw new BadRequestException("Maximum number of root-level cost centers has been reached."); } public async Task GetCostCenterByCodeAsync(string costCenterCode, string? expand) { var costCenter = await _costCenterRepository.GetByCodeAsync(costCenterCode) ?? throw new NotFoundException("Cost center not found"); var breadcrumb = await _costCenterRepository.GetAncestorBreadcrumbAsync(costCenter.CostCenterId); List? descendantTree = null; if (string.Equals(expand, "tree", StringComparison.OrdinalIgnoreCase)) { descendantTree = await _costCenterRepository.GetDescendantTreeAsync(costCenter.CostCenterId); } var data = new CostCenterDetailResponse { CostCenter = costCenter, AncestorBreadcrumb = breadcrumb, DescendantTree = descendantTree }; return ResponseHelpers.Ok("Cost center retrieved successfully", data); } } }