using Microsoft.EntityFrameworkCore; using vendorApproverBackend.Data; using vendorApproverBackend.Entities; using vendorApproverBackend.Dtos; using vendorApproverBackend.Enums; using vendorApproverBackend.Responses; using vendorApproverBackend.Requests; using vendorApproverBackend.Interfaces; namespace vendorApproverBackend.Services { public class RequestRenewService { private readonly DataContext _context; private readonly IFileStorageService _fileStorageService; // Assuming this exists based on your snippet private readonly UserService _userService; private readonly IEmailService _emailService; private readonly INotificationService _notificationService; public RequestRenewService(DataContext context, IFileStorageService fileStorageService, UserService userService, IEmailService emailService, INotificationService notificationService) { _context = context; _fileStorageService = fileStorageService; _userService = userService; _emailService = emailService; _notificationService = notificationService; } // Logic to save file and return FileModel private async Task ProcessFileAsync(IFormFile file, string subDirectory) { if (file == null || file.Length == 0) return null; // Using your existing logic pattern var result = await _fileStorageService.SaveFileAsync(file, subDirectory); return result; } public async Task InitiateRequestRenewAsync(long certificateId, InitiateRequesRenewtDto dto, User currentUser) { // 1. Calculate Request Number int nextNumber = 1; var lastRequest = await _context.RequestRenews .Where(r => r.CertificationId == certificateId) .OrderByDescending(r => r.RequestNumber) .FirstOrDefaultAsync(); if (lastRequest != null) nextNumber = lastRequest.RequestNumber + 1; // 2. Create the shell entity var request = new RequestRenew { CertificationId = certificateId, RequestNumber = nextNumber, Status = RequestRenewStatus.PENDING, // Assuming Renewals use a different type or default, // if you want to distinguish them from CHANGE types. RenewalStep = RenewalStep.RENEWAL_REQUEST_SUBMITTED, Timestamp = DateTime.UtcNow, RenewalDate = null, InspectionRequired = false, Comments = string.Empty, RenewComment = dto?.RenewComment }; // 3. Logic to handle the DTO and UpdatedVendorData (Same as Change Request) bool hasChanges = dto != null && ( !string.IsNullOrEmpty(dto.SupplierName) || !string.IsNullOrEmpty(dto.AgentContactPerson) || !string.IsNullOrEmpty(dto.OriginCountry)); if (hasChanges) { var updatedData = new UpdatedVendorData { SupplierName = dto.SupplierName, AgentContactPerson = dto.AgentContactPerson, OriginCountry = dto.OriginCountry, }; _context.UpdatedVendorDatas.Add(updatedData); request.UpdatedVendorData = updatedData; // Link the 1-to-1 relation } _context.RequestRenews.Add(request); await _context.SaveChangesAsync(); // 4. Notifications try { if (currentUser != null && currentUser.Vendor != null) { long vendorUserId = currentUser.Id; var adminIds = await _userService.GetAdminUserIdsAsync(); var usersToNotify = adminIds.Distinct().ToList(); if (usersToNotify.Any()) { var adminNotification = new NotificationModel { SenderId = vendorUserId, Subject = "New Renewal Request Submitted", Content = $"A new renewal request submitted by {currentUser.Vendor.ManifactoryName}", Action = Action.RENEWAL_REQUEST_SUBMITTED, ActionId = request.Id, LogUrl = "", Status = states.WAITING }; await _notificationService.SendNotificationToListOfUsers(usersToNotify, adminNotification); } var userNotification = new NotificationModel { SenderId = vendorUserId, Subject = "New Renewal Request Submitted Successfully", Content = "Your Renewal Request is successfully submitted", Action = Action.RENEWAL_REQUEST_SUBMITTED, ActionId = request.Id, LogUrl = "", Status = states.WAITING }; await _notificationService.SendNotificationToUser(vendorUserId, userNotification); } } catch (Exception) { // Log notification failure but don't break the return } return request; } /* public async Task InitiateRequestRenewAsync(long certificateId, User currentUser) { // 1. Calculate Request Number (Exactly like your old logic) int nextNumber = 1; var lastRequest = await _context.RequestRenews .Where(r => r.CertificationId == certificateId) .OrderByDescending(r => r.RequestNumber) .FirstOrDefaultAsync(); if (lastRequest != null) nextNumber = lastRequest.RequestNumber + 1; // 2. Create the shell entity var request = new RequestRenew { CertificationId = certificateId, RequestNumber = nextNumber, Status = RequestRenewStatus.PENDING, RenewalStep = RenewalStep.RENEWAL_REQUEST_SUBMITTED, // Set as requested Timestamp = DateTime.UtcNow, // These can stay null/default for now RenewalDate = null, InspectionRequired = false, Comments = string.Empty }; _context.RequestRenews.Add(request); await _context.SaveChangesAsync(); try { if (currentUser != null && currentUser.Vendor != null) { long vendorUserId = currentUser.Id; var adminIds = await _userService.GetAdminUserIdsAsync(); var usersToNotify = adminIds.Distinct().ToList(); if (usersToNotify.Any()) { var notification = new NotificationModel { SenderId = vendorUserId, Subject = "New Renewal Request Submitted", Content = "A new renewal request submitted by " + currentUser.Vendor.ManifactoryName, Action = Action.RENEWAL_REQUEST_SUBMITTED, ActionId = request.Id, LogUrl = "", Status = states.WAITING }; await _notificationService .SendNotificationToListOfUsers(usersToNotify, notification); } { var notification = new NotificationModel { SenderId = vendorUserId, Subject = "New Renewal Request Submitted Successfully", Content = "Your Renewal Request is successfully submitted", Action = Action.RENEWAL_REQUEST_SUBMITTED, ActionId = request.Id, LogUrl = "", Status = states.WAITING }; await _notificationService .SendNotificationToUser(vendorUserId, notification); } } } catch (Exception ex) { // } return request; } */ public async Task AddRequestRenewAsync( long certificateId, DateTime? renewalDate, bool? inspectionRequired, string comments, string signatureName, string signatureData, IFormFile expiredFile, string expiredRemark, IFormFile refOrdersFile, string refOrdersRemark, IFormFile approvedDesignFile, string approvedDesignRemark, IFormFile updateTdsFile, string updateTdsRemark, List performanceLetterFiles, bool? isExpiredOver2Years, bool? requiresFactoryAudit) { // 1. Generate Request Number (Last + 1) int nextNumber = 1; var lastRequest = await _context.RequestRenews .Where(r => r.CertificationId == certificateId) .OrderByDescending(r => r.RequestNumber) .FirstOrDefaultAsync(); if (lastRequest != null) nextNumber = lastRequest.RequestNumber + 1; // 2. Create the Request var request = new RequestRenew { CertificationId = certificateId, RequestNumber = nextNumber, RenewalDate = renewalDate ?? DateTime.UtcNow.AddYears(1), Status = RequestRenewStatus.PENDING, InspectionRequired = inspectionRequired ?? false, Comments = comments, Timestamp = DateTime.UtcNow, ExpiredProductCertificateRemark = expiredRemark, ReferenceOrdersRemark = refOrdersRemark, ApprovedDesignRemark = approvedDesignRemark, UpdateTDSRemark = updateTdsRemark, RenewalStep = RenewalStep.RENEWAL_REQUEST_SUBMITTED, IsExpiredOver2Years = isExpiredOver2Years, RequiresFactoryAudit = requiresFactoryAudit }; // 3. Handle Files request.ExpiredProductCertificateFile = await ProcessFileAsync(expiredFile, "Renewals/ExpiredCerts"); request.ReferenceOrdersFile = await ProcessFileAsync(refOrdersFile, "Renewals/RefOrders"); request.ApprovedDesignFile = await ProcessFileAsync(approvedDesignFile, "Renewals/ApprovedDesigns"); request.UpdateTDSFile = await ProcessFileAsync(updateTdsFile, "Renewals/TDSUpdates"); if (performanceLetterFiles != null && performanceLetterFiles.Count > 0) { for (int i = 0; i < performanceLetterFiles.Count; i++) { var fileModel = await ProcessFileAsync(performanceLetterFiles[i], "Renewals/PerformanceLetters"); request.PerformanceLetters.Add(new PerformanceLetter { File = fileModel, Order = i + 1 }); } } // 4. Handle Signature if (!string.IsNullOrEmpty(signatureName)) { request.Signature = new Signature { Name = signatureName, SignatureData = signatureData, Date = DateTime.UtcNow }; } _context.RequestRenews.Add(request); await _context.SaveChangesAsync(); return request; } public async Task UpdateAndFinalizeRenewAsync(long requestId, RequestRenewForm form) { // 1. Fetch the record with all necessary includes var request = await _context.RequestRenews .Include(r => r.PerformanceLetters) .Include(r => r.Signature) .Include(r => r.UpdatedVendorData) // Include this in case you need to verify type .Include(r => r.TdcDocument) .Include(r => r.OfficialLetterFile) .Include(r => r.ManagerRenewFile) .Include(r => r.OfficialLetterFileRemark) .Include(r => r.RenewRequestFile) .Include(r => r.ValidAgencyAgreementFile) .FirstOrDefaultAsync(r => r.Id == requestId); if (request == null) return null; // 2. Map Scalar Properties request.RenewalDate = form.RenewalDate ?? request.RenewalDate ?? DateTime.UtcNow.AddYears(1); request.InspectionRequired = form.InspectionRequired ?? request.InspectionRequired; request.Comments = form.Comments ?? request.Comments; request.IsExpiredOver2Years = form.IsExpiredOver2Years ?? request.IsExpiredOver2Years; request.RequiresFactoryAudit = form.RequiresFactoryAudit ?? request.RequiresFactoryAudit; // New Comment attributes (only update if not null) if (form.ChangeApprovalComment != null) request.ChangeApprovalComment = form.ChangeApprovalComment; if (form.ChangeRejectionComment != null) request.ChangeRejectionComment = form.ChangeRejectionComment; request.RenewalStep = form.RenewalStep ?? RenewalStep.RENEWAL_REQUEST_SUBMITTED; // Update Remarks if (!string.IsNullOrEmpty(form.ExpiredRemark)) request.ExpiredProductCertificateRemark = form.ExpiredRemark; if (!string.IsNullOrEmpty(form.RefOrdersRemark)) request.ReferenceOrdersRemark = form.RefOrdersRemark; if (!string.IsNullOrEmpty(form.ApprovedDesignRemark)) request.ApprovedDesignRemark = form.ApprovedDesignRemark; if (!string.IsNullOrEmpty(form.UpdateTdsRemark)) request.UpdateTDSRemark = form.UpdateTdsRemark; if (!string.IsNullOrEmpty(form.ValidAgencyAgreementFileRemark)) request.ValidAgencyAgreementFileRemark = form.ValidAgencyAgreementFileRemark; if (form.ManagerRenewComment != null) request.ManagerRenewComment = form.ManagerRenewComment; // 3. Handle Existing Files if (form.ExpiredFile != null) request.ExpiredProductCertificateFile = await ProcessFileAsync(form.ExpiredFile, "Renewals/ExpiredCerts"); if (form.RefOrdersFile != null) request.ReferenceOrdersFile = await ProcessFileAsync(form.RefOrdersFile, "Renewals/RefOrders"); if (form.ApprovedDesignFile != null) request.ApprovedDesignFile = await ProcessFileAsync(form.ApprovedDesignFile, "Renewals/ApprovedDesigns"); if (form.UpdateTdsFile != null) request.UpdateTDSFile = await ProcessFileAsync(form.UpdateTdsFile, "Renewals/TDSUpdates"); if (form.TdcDocument != null) request.TdcDocument = await ProcessFileAsync(form.TdcDocument, "Renewals/TDC"); if (form.OfficialLetterFile != null) request.OfficialLetterFile = await ProcessFileAsync(form.OfficialLetterFile, "Renewals/OfficialLetters"); if (form.ValidAgencyAgreementFile != null) request.ValidAgencyAgreementFile = await ProcessFileAsync(form.ValidAgencyAgreementFile, "Renewals/AgencyAgreements"); if (form.OfficialLetterFileRemark != null) request.OfficialLetterFileRemark = await ProcessFileAsync(form.OfficialLetterFileRemark, "Renewals/OfficialLetterRemarks"); if (form.ManagerRenewFile != null) { request.ManagerRenewFile = await ProcessFileAsync(form.ManagerRenewFile, "Renewals/ManagerUploads"); } if (form.RenewRequestFile != null) request.RenewRequestFile = await ProcessFileAsync(form.RenewRequestFile, "Renewals/RequestFile"); // 4. Handle Performance Letters if (form.PerformanceLetterFiles != null && form.PerformanceLetterFiles.Count > 0) { request.PerformanceLetters.Clear(); for (int i = 0; i < form.PerformanceLetterFiles.Count; i++) { var fileModel = await ProcessFileAsync(form.PerformanceLetterFiles[i], "Renewals/PerformanceLetters"); request.PerformanceLetters.Add(new PerformanceLetter { File = fileModel, Order = i + 1 }); } } // 5. Handle Signature if (!string.IsNullOrEmpty(form.SignatureName)) { if (request.Signature == null) request.Signature = new Signature(); request.Signature.Name = form.SignatureName; request.Signature.SignatureData = form.SignatureData; request.Signature.Date = DateTime.UtcNow; } await _context.SaveChangesAsync(); return request; } public async Task GetByIdAsync(long id) { return await _context.RequestRenews .Include(r => r.Signature) .Include(r => r.ExpiredProductCertificateFile) .Include(r => r.ReferenceOrdersFile) .Include(r => r.ApprovedDesignFile) .Include(r => r.PerformanceLetters) .ThenInclude(p => p.File) .Include(r => r.UpdateTDSFile) .Include(r => r.OfficialLetterFileRemark) .Include(r => r.UpdatedVendorData) .Include(r => r.TdcDocument) .Include(r => r.OfficialLetterFile) .Include(r => r.ValidAgencyAgreementFile) .FirstOrDefaultAsync(r => r.Id == id); } public async Task UpdateStatusAsync(long id, RequestRenewStatus status) { var request = await _context.RequestRenews.FindAsync(id); if (request == null) return false; request.Status = status; await _context.SaveChangesAsync(); return true; } public async Task UpdateRenewalStepAsync(long renewRequestId, RenewalStep renewalStep) { // 1. Fetch the request var request = await _context.RequestRenews.FindAsync(renewRequestId); if (request == null) { return null; } // 2. Update the step request.RenewalStep = renewalStep; // 3. Save changes await _context.SaveChangesAsync(); return request; } public async Task> GetByCertificateAsync(long certificateId, RequestRenewStatus? status) { // We start the query and use .Include to load the related FileModels and Signature var query = _context.RequestRenews .Include(r => r.ExpiredProductCertificateFile) .Include(r => r.ReferenceOrdersFile) .Include(r => r.ApprovedDesignFile) .Include(r => r.UpdateTDSFile) .Include(r => r.UpdatedVendorData) .Include(r => r.TdcDocument) .Include(r => r.OfficialLetterFile) .Include(r => r.OfficialLetterFileRemark) .Include(r => r.ValidAgencyAgreementFile) .Include(r => r.PerformanceLetters) .Include(r => r.Signature) .Where(r => r.CertificationId == certificateId); if (status.HasValue) { query = query.Where(r => r.Status == status.Value); } return await query.ToListAsync(); } public async Task AssignRequestRenewAsync(long requestRenewId, string azureEmail, string azureDisplayedName) { // 1. Get the RequestRenew by ID var requestRenew = await _context.RequestRenews.FindAsync(requestRenewId); if (requestRenew == null) { throw new Exception("RequestRenew not found"); } // 2. Get or Create User User userToAssign; string normalizedEmail = azureEmail.ToLower(); if (await _userService.ExistByEmailAsync(normalizedEmail)) { userToAssign = await _userService.FindByEmailAsync(normalizedEmail); } else { userToAssign = await CreateNewUserAsync(azureEmail, azureDisplayedName); } // 3. Assign the user and save requestRenew.AssignedToId = userToAssign.Id; requestRenew.AssignedTo = userToAssign; requestRenew.AssignDate = DateTime.UtcNow; await _context.SaveChangesAsync(); return requestRenew; } private async Task CreateNewUserAsync(string email, string displayName) { string password = _userService.GenerateNewPassword(); var userCount = await _userService.GetUserCountAsync(); var username = _userService.GenerateUsername(displayName, userCount); var newUser = new User { Name = displayName, Email = email, Username = username, Active = true, Role = RoleEnum.ROLE_RENWE_APPROVER, StartDate = DateTime.UtcNow, Timestamp = DateTime.UtcNow }; newUser.Password = BCrypt.Net.BCrypt.HashPassword(password); // Save the user using your existing service var savedUser = await _userService.SaveUserAsync(newUser); try { await _emailService.SendEmployeeEmailAsync(email, savedUser, password); } catch (Exception ex) { // Log error but continue since user is created Console.WriteLine($"Email failed: {ex.Message}"); } return savedUser; } /* public async Task AssignToPerformanceEvaluatorAsync(long renewRequestId, long userId) { var user = await _context.Users.FindAsync(userId); // Use same context if (user == null) throw new KeyNotFoundException("User not found."); if (user.Type != EmployeeTypeEnum.PERFORMANCE_EVALUATOR) throw new InvalidOperationException("User does not have the Performance Evaluator type."); var requestRenew = await _context.RequestRenews.FindAsync(renewRequestId); if (requestRenew == null) throw new KeyNotFoundException("Renewal Request not found."); // Just set the ID and the Date requestRenew.AssignedToId = userId; requestRenew.AssignDate = DateTime.UtcNow; try { await _context.SaveChangesAsync(); } catch (DbUpdateException dbEx) { // This will tell you if it's a Foreign Key or Unique Constraint issue throw new Exception($"Database Error: {dbEx.InnerException?.Message}"); } return requestRenew; } */ public async Task AssignToPerformanceEvaluatorAsync(long renewRequestId, long userId) { var user = await _context.Users.FindAsync(userId); if (user == null) throw new KeyNotFoundException("User not found."); if (user.Type != EmployeeTypeEnum.PERFORMANCE_EVALUATOR) throw new InvalidOperationException("User does not have the Performance Evaluator type."); var requestRenew = await _context.RequestRenews.FindAsync(renewRequestId); if (requestRenew == null) throw new KeyNotFoundException("Renewal Request not found."); // Update assignment details requestRenew.AssignedToId = userId; requestRenew.AssignDate = DateTime.UtcNow; try { await _context.SaveChangesAsync(); // 🔔 Trigger Notification Email try { if (!string.IsNullOrEmpty(user.Email)) { await _emailService.SendAuditAssignmentNotificationAsync(user.Email, user, requestRenew); } } catch (Exception mailEx) { // We log the error but don't fail the request because the DB save was successful // _logger.LogError(mailEx, "Failed to send assignment email to user {UserId}", userId); } } catch (DbUpdateException dbEx) { throw new Exception($"Database Error: {dbEx.InnerException?.Message}"); } return requestRenew; } public async Task> GetAllPagedAsync( int page, int size, long? certificationId = null, long? assignedToId = null, RequestRenewStatus? status = null) { // 1. Initialize Query with all includes for FileModels and Relationships var query = _context.RequestRenews .Include(r => r.ExpiredProductCertificateFile) .Include(r => r.ReferenceOrdersFile) .Include(r => r.ApprovedDesignFile) .Include(r => r.UpdateTDSFile) .Include(r => r.Signature) .Include(r => r.UpdatedVendorData) .Include(r => r.TdcDocument) .Include(r => r.OfficialLetterFile) .Include(r => r.OfficialLetterFileRemark) .Include(r => r.ValidAgencyAgreementFile) .Include(r => r.AssignedTo) .Include(r => r.PerformanceLetters) .ThenInclude(p => p.File) // Assuming PerformanceLetter has a FileModel .AsQueryable(); // 2. Apply Dynamic Filters if (certificationId.HasValue) { query = query.Where(r => r.CertificationId == certificationId.Value); } if (assignedToId.HasValue) { query = query.Where(r => r.AssignedToId == assignedToId.Value); } if (status.HasValue) { query = query.Where(r => r.Status == status.Value); } // 3. Order by Timestamp descending query = query.OrderByDescending(r => r.Timestamp); // 4. Count total records for PagedResult var totalCount = await query.CountAsync(); // 5. Apply Pagination var items = await query .Skip(page * size) .Take(size) .ToListAsync(); // 6. Return using your PagedResult class return new PagedResult(items, totalCount, page, size); } // **************************** all renew steps methodes ***************************************** public async Task UpdatePart2EvaluationAsync(long renewRequestId, UpdatePart2EvaluationDto dto) { // 1. Fetch the RequestRenew including existing evaluation if any var request = await _context.RequestRenews .Include(r => r.Part2Evaluation) .FirstOrDefaultAsync(r => r.Id == renewRequestId); if (request == null) return null; // 2. Create or Update the Evaluation entity if (request.Part2Evaluation == null) { request.Part2Evaluation = new RenewalPart2Evaluation(); } request.Part2Evaluation.TotalScore = dto.TotalScore; // Map the DTO to the Entity's CriteriaData request.Part2Evaluation.Criteria = new CriteriaData { ProductPerformance = new ScoreDetail { Score = dto.Criteria.ProductPerformance.Score, Remark = dto.Criteria.ProductPerformance.Remark, ScoreLabel = dto.Criteria.ProductPerformance.ScoreLabel }, ComplianceWithStandards = new ScoreDetail { Score = dto.Criteria.ComplianceWithStandards.Score, Remark = dto.Criteria.ComplianceWithStandards.Remark, ScoreLabel = dto.Criteria.ComplianceWithStandards.ScoreLabel }, AfterSaleService = new ScoreDetail { Score = dto.Criteria.AfterSaleService.Score, Remark = dto.Criteria.AfterSaleService.Remark, ScoreLabel = dto.Criteria.AfterSaleService.ScoreLabel } }; // 3. Automatically advance the step if desired request.RenewalStep = RenewalStep.TECHNICAL_EVALUATION_PART2; await _context.SaveChangesAsync(); return request; } public async Task PatchPart2EvaluationAsync(long evaluationId, PatchPart2EvaluationDto dto) { // 1. Fetch the existing evaluation record var evaluation = await _context.RenewalPart2Evaluations.FindAsync(evaluationId); if (evaluation == null) return null; // 2. Update TotalScore if provided if (dto.TotalScore.HasValue) { evaluation.TotalScore = dto.TotalScore.Value; } // 3. Update Criteria nested attributes if provided if (dto.Criteria != null) { if (evaluation.Criteria == null) evaluation.Criteria = new CriteriaData(); // Update ProductPerformance if (dto.Criteria.ProductPerformance != null) { evaluation.Criteria.ProductPerformance.Score = dto.Criteria.ProductPerformance.Score ?? evaluation.Criteria.ProductPerformance.Score; evaluation.Criteria.ProductPerformance.Remark = dto.Criteria.ProductPerformance.Remark ?? evaluation.Criteria.ProductPerformance.Remark; evaluation.Criteria.ProductPerformance.ScoreLabel = dto.Criteria.ProductPerformance.ScoreLabel ?? evaluation.Criteria.ProductPerformance.ScoreLabel; } // Update ComplianceWithStandards if (dto.Criteria.ComplianceWithStandards != null) { evaluation.Criteria.ComplianceWithStandards.Score = dto.Criteria.ComplianceWithStandards.Score ?? evaluation.Criteria.ComplianceWithStandards.Score; evaluation.Criteria.ComplianceWithStandards.Remark = dto.Criteria.ComplianceWithStandards.Remark ?? evaluation.Criteria.ComplianceWithStandards.Remark; evaluation.Criteria.ComplianceWithStandards.ScoreLabel = dto.Criteria.ComplianceWithStandards.ScoreLabel ?? evaluation.Criteria.ComplianceWithStandards.ScoreLabel; } // Update AfterSaleService if (dto.Criteria.AfterSaleService != null) { evaluation.Criteria.AfterSaleService.Score = dto.Criteria.AfterSaleService.Score ?? evaluation.Criteria.AfterSaleService.Score; evaluation.Criteria.AfterSaleService.Remark = dto.Criteria.AfterSaleService.Remark ?? evaluation.Criteria.AfterSaleService.Remark; evaluation.Criteria.AfterSaleService.ScoreLabel = dto.Criteria.AfterSaleService.ScoreLabel ?? evaluation.Criteria.AfterSaleService.ScoreLabel; } } await _context.SaveChangesAsync(); return evaluation; } /* public async Task UpdatePart3DecisionAsync(long renewRequestId, RenewalDecisionRequest dto) { // 1. Fetch Request with Part3Decision included var request = await _context.RequestRenews .Include(r => r.Part3Decision) .ThenInclude(d => d.SupportingDocument) .FirstOrDefaultAsync(r => r.Id == renewRequestId); if (request == null) return null; // 2. Initialize the Decision entity if it's new if (request.Part3Decision == null) { request.Part3Decision = new RenewalPart3Decision(); _context.RenewalPart3Decisions.Add(request.Part3Decision); } // 3. Update Text/Date Fields request.Part3Decision.Decision = dto.Decision ?? request.Part3Decision.Decision; request.Part3Decision.Reason = dto.Reason ?? request.Part3Decision.Reason; request.Part3Decision.DecidedBy = dto.DecidedBy ?? request.Part3Decision.DecidedBy; request.Part3Decision.DecisionDate = dto.DecisionDate ?? request.Part3Decision.DecisionDate; // 4. Process File logic (using your specific pattern) if (dto.SupportingDocument != null) { var processedFile = await ProcessFileAsync(dto.SupportingDocument, "Renewals/Decisions"); if (processedFile != null) { request.Part3Decision.SupportingDocument = processedFile; } } // 5. Update Step request.RenewalStep = RenewalStep.PERFORMANCE_MANAGER_DECISION_PART3; await _context.SaveChangesAsync(); return request; } */ public async Task UpdatePart3DecisionAsync(long renewRequestId, RenewalDecisionRequest dto) { // 1. Fetch Request with Part3Decision included var request = await _context.RequestRenews .Include(r => r.Part3Decision) .ThenInclude(d => d.SupportingDocument) .FirstOrDefaultAsync(r => r.Id == renewRequestId); if (request == null) return null; // 2. Initialize the Decision entity if it's new if (request.Part3Decision == null) { request.Part3Decision = new RenewalPart3Decision(); _context.RenewalPart3Decisions.Add(request.Part3Decision); } // 3. Update Text/Date Fields request.Part3Decision.Decision = dto.Decision ?? request.Part3Decision.Decision; request.Part3Decision.Reason = dto.Reason ?? request.Part3Decision.Reason; request.Part3Decision.DecidedBy = dto.DecidedBy ?? request.Part3Decision.DecidedBy; // FIX: Force the DateTime to be interpreted as UTC for PostgreSQL if (dto.DecisionDate.HasValue) { request.Part3Decision.DecisionDate = DateTime.SpecifyKind(dto.DecisionDate.Value, DateTimeKind.Utc); } // 4. Process File logic if (dto.SupportingDocument != null) { var processedFile = await ProcessFileAsync(dto.SupportingDocument, "Renewals/Decisions"); if (processedFile != null) { request.Part3Decision.SupportingDocument = processedFile; } } // 5. Update Step request.RenewalStep = RenewalStep.PERFORMANCE_MANAGER_DECISION_PART3; await _context.SaveChangesAsync(); return request; } public async Task PatchPart3DecisionAsync(long decisionId, PatchPart3DecisionDto dto) { // 1. Fetch the existing decision record var decision = await _context.RenewalPart3Decisions .Include(d => d.SupportingDocument) .FirstOrDefaultAsync(d => d.Id == decisionId); if (decision == null) return null; // 2. Update Text and Date attributes only if they are provided if (dto.Decision != null) decision.Decision = dto.Decision; if (dto.Reason != null) decision.Reason = dto.Reason; if (dto.DecidedBy != null) decision.DecidedBy = dto.DecidedBy; if (dto.DecisionDate.HasValue) decision.DecisionDate = dto.DecisionDate.Value; // 3. Update File if a new one is uploaded if (dto.SupportingDocument != null) { // Apply the specific logic using your ProcessFileAsync pattern var newFile = await ProcessFileAsync(dto.SupportingDocument, "Renewals/Decisions"); if (newFile != null) { decision.SupportingDocument = newFile; } } await _context.SaveChangesAsync(); return decision; } public async Task UpdateFinalDecisionAsync(long renewRequestId, FinalDecisionRequest dto) { // 1. Fetch Request with FinalDecision included var request = await _context.RequestRenews .Include(r => r.FinalDecision) .ThenInclude(d => d.SupportingDocument) .FirstOrDefaultAsync(r => r.Id == renewRequestId); if (request == null) return null; // 2. Initialize the Final Decision entity if it doesn't exist if (request.FinalDecision == null) { request.FinalDecision = new RenewalFinalDecision(); _context.RenewalFinalDecisions.Add(request.FinalDecision); } // 3. Update Text/Date Fields (using null check to preserve existing data) request.FinalDecision.Decision = dto.Decision ?? request.FinalDecision.Decision; request.FinalDecision.Comments = dto.Comments ?? request.FinalDecision.Comments; request.FinalDecision.ReviewDate = dto.ReviewDate ?? request.FinalDecision.ReviewDate; // 4. Process File logic using your standard pattern if (dto.SupportingDocument != null) { var processedFile = await ProcessFileAsync(dto.SupportingDocument, "Renewals/FinalDecisions"); if (processedFile != null) { request.FinalDecision.SupportingDocument = processedFile; } } // 5. Advance Step to Final Admin Decision request.RenewalStep = RenewalStep.FINAL_ADMIN_DECISION; await _context.SaveChangesAsync(); return request; } public async Task PatchFinalDecisionAsync(long finalDecisionId, FinalDecisionRequest dto) { var finalDecision = await _context.RenewalFinalDecisions .Include(d => d.SupportingDocument) .FirstOrDefaultAsync(d => d.Id == finalDecisionId); if (finalDecision == null) return null; if (dto.Decision != null) finalDecision.Decision = dto.Decision; if (dto.Comments != null) finalDecision.Comments = dto.Comments; if (dto.ReviewDate.HasValue) finalDecision.ReviewDate = dto.ReviewDate.Value; if (dto.SupportingDocument != null) { var newFile = await ProcessFileAsync(dto.SupportingDocument, "Renewals/FinalDecisions"); if (newFile != null) { finalDecision.SupportingDocument = newFile; } } await _context.SaveChangesAsync(); return finalDecision; } // Method 1: Add Clarification to RequestRenew public async Task AddClarificationToRenewAsync(long renewRequestId, RenewalClarificationRequest dto) { var request = await _context.RequestRenews .Include(r => r.Clarification) .FirstOrDefaultAsync(r => r.Id == renewRequestId); if (request == null) return null; if (request.Clarification == null) { request.Clarification = new RenewalClarification(); _context.RenewalClarifications.Add(request.Clarification); } request.Clarification.RequestedBy = dto.RequestedBy ?? request.Clarification.RequestedBy; request.Clarification.RequestedDate = dto.RequestedDate ?? request.Clarification.RequestedDate; request.Clarification.Comments = dto.Comments ?? request.Clarification.Comments; if (dto.RequiredDocuments != null && dto.RequiredDocuments.Any()) { request.Clarification.RequiredDocuments = dto.RequiredDocuments; } request.RenewalStep = RenewalStep.CLARIFICATION_RESPONSE; await _context.SaveChangesAsync(); return request; } // Method 2: Patch existing Clarification & Vendor Response public async Task PatchClarificationAsync(long clarificationId, VendorResponseRequest vendorDto) { var clarification = await _context.RenewalClarifications .Include(c => c.VendorResponse) .ThenInclude(v => v.Documents) .FirstOrDefaultAsync(c => c.Id == clarificationId); if (clarification == null) return null; // Initialize VendorResponse if it doesn't exist if (clarification.VendorResponse == null) { clarification.VendorResponse = new VendorResponse(); _context.VendorResponses.Add(clarification.VendorResponse); } // Update simple attributes if (vendorDto.ResponseDate.HasValue) clarification.VendorResponse.ResponseDate = vendorDto.ResponseDate.Value; if (vendorDto.Comments != null) clarification.VendorResponse.Comments = vendorDto.Comments; // Handle List of Files if (vendorDto.Documents != null && vendorDto.Documents.Any()) { // 1. Clear previous documents as per your requirement clarification.VendorResponse.Documents.Clear(); // 2. Process and add new ones foreach (var file in vendorDto.Documents) { var processedFile = await ProcessFileAsync(file, "Renewals/Clarifications"); if (processedFile != null) { clarification.VendorResponse.Documents.Add(processedFile); } } } await _context.SaveChangesAsync(); return clarification; } // Method 1: Add/Link Payment to RequestRenew public async Task AddPaymentToRenewAsync(long renewRequestId, RenewalPaymentRequest dto) { var request = await _context.RequestRenews .Include(r => r.Payment) .FirstOrDefaultAsync(r => r.Id == renewRequestId); if (request == null) return null; // Initialize Payment entity if it doesn't exist if (request.Payment == null) { request.Payment = new RenewalPayment(); _context.RenewalPayments.Add(request.Payment); } // Map attributes with null checks request.Payment.Amount = dto.Amount ?? request.Payment.Amount; request.Payment.Currency = dto.Currency ?? request.Payment.Currency; request.Payment.PaymentDate = dto.PaymentDate ?? request.Payment.PaymentDate; request.Payment.TransactionId = dto.TransactionId ?? request.Payment.TransactionId; request.Payment.Status = dto.Status ?? (request.Payment.Status ?? "PENDING"); // Advance step if payment is successful if (request.Payment.Status == "PAID") { request.RenewalStep = RenewalStep.CERTIFICATE_ISSUANCE; } await _context.SaveChangesAsync(); return request; } // Method 2: Patch existing Payment directly by ID public async Task PatchPaymentAsync(long paymentId, RenewalPaymentRequest dto) { var payment = await _context.RenewalPayments.FindAsync(paymentId); if (payment == null) return null; // Only update attributes that are not null in the request if (dto.Amount.HasValue) payment.Amount = dto.Amount.Value; if (dto.Currency != null) payment.Currency = dto.Currency; if (dto.PaymentDate.HasValue) payment.PaymentDate = dto.PaymentDate.Value; if (dto.TransactionId != null) payment.TransactionId = dto.TransactionId; if (dto.Status != null) payment.Status = dto.Status; await _context.SaveChangesAsync(); return payment; } public async Task GetRequestRenewDetailsByIdAsync(long id) { var request = await _context.RequestRenews .Include(r => r.AssignedTo) .Include(r => r.ExpiredProductCertificateFile) .Include(r => r.ReferenceOrdersFile) .Include(r => r.ApprovedDesignFile) .Include(r => r.UpdateTDSFile) .Include(r => r.Signature) .Include(r => r.PerformanceLetters) .Include(r => r.Part2Evaluation) .Include(r => r.UpdatedVendorData) .Include(r => r.TdcDocument) .Include(r => r.OfficialLetterFile) .Include(r => r.OfficialLetterFileRemark) .Include(r => r.ValidAgencyAgreementFile) .Include(r => r.Part3Decision).ThenInclude(d => d.SupportingDocument) .Include(r => r.FinalDecision).ThenInclude(d => d.SupportingDocument) .Include(r => r.Clarification).ThenInclude(c => c.VendorResponse).ThenInclude(v => v.Documents) .Include(r => r.Payment) .FirstOrDefaultAsync(r => r.Id == id); if (request == null) return null; return new RequestRenewDetailsDto { Id = request.Id, RequestNumber = request.RequestNumber, RenewalDate = request.RenewalDate.HasValue ? request.RenewalDate.Value : default, Status = request.Status.ToString(), RenewalStep = request.RenewalStep.ToString(), InspectionRequired = request.InspectionRequired, Comments = request.Comments, Timestamp = request.Timestamp, CertificationId = request.CertificationId, IsExpiredOver2Years = request.IsExpiredOver2Years, RequiresFactoryAudit = request.RequiresFactoryAudit, // Map the sub-entities directly PerformanceLetters = request.PerformanceLetters, Part2Evaluation = request.Part2Evaluation, Part3Decision = request.Part3Decision, FinalDecision = request.FinalDecision, Clarification = request.Clarification, Payment = request.Payment, // Create the PartOneSubmission object PartOneSubmission = new PartOneSubmissionDto { AssignToId = request.AssignedTo?.Id, AssignToName = request.AssignedTo?.Name, // Assuming the User entity has a 'Name' property AssignDate = request.AssignDate.Value, ExpiredProductCertificateFile = request.ExpiredProductCertificateFile, ExpiredProductCertificateRemark = request.ExpiredProductCertificateRemark, ReferenceOrdersFile = request.ReferenceOrdersFile, ReferenceOrdersRemark = request.ReferenceOrdersRemark, ApprovedDesignFile = request.ApprovedDesignFile, ApprovedDesignRemark = request.ApprovedDesignRemark, UpdateTDSFile = request.UpdateTDSFile, UpdateTDSRemark = request.UpdateTDSRemark, Signature = request.Signature } }; } //************************************************** Renew type change methods *********************************************************************** public async Task InitiateChangeRequestAsync(long certificateId, InitiateChangeRequestDto dto, User currentUser) { // 1. Calculate Request Number int nextNumber = 1; var lastRequest = await _context.RequestRenews .Where(r => r.CertificationId == certificateId) .OrderByDescending(r => r.RequestNumber) .FirstOrDefaultAsync(); if (lastRequest != null) nextNumber = lastRequest.RequestNumber + 1; // 2. Create the RequestRenew shell var request = new RequestRenew { CertificationId = certificateId, RequestNumber = nextNumber, Status = RequestRenewStatus.PENDING, RequestType = RequestRenewType.CHANGE, // Explicitly set to CHANGE RenewalStep = RenewalStep.RENEWAL_REQUEST_SUBMITTED, Timestamp = DateTime.UtcNow, Comments = string.Empty }; // 3. Conditionally create UpdatedVendorData // Check if at least one field is provided bool hasChanges = !string.IsNullOrEmpty(dto.SupplierName) || !string.IsNullOrEmpty(dto.AgentContactPerson) || !string.IsNullOrEmpty(dto.OriginCountry) || !string.IsNullOrEmpty(dto.OfficeLocation) || !string.IsNullOrEmpty(dto.ProductName) || !string.IsNullOrEmpty(dto.ChangeReasonComment); if (hasChanges) { var updatedData = new UpdatedVendorData { SupplierName = dto.SupplierName, AgentContactPerson = dto.AgentContactPerson, OriginCountry = dto.OriginCountry, OfficeLocation = dto.OfficeLocation, ProductName = dto.ProductName, ChangeReasonComment = dto.ChangeReasonComment }; _context.UpdatedVendorDatas.Add(updatedData); request.UpdatedVendorData = updatedData; // Establish the One-to-One link } _context.RequestRenews.Add(request); await _context.SaveChangesAsync(); try { if (currentUser != null && currentUser.Vendor != null) { long vendorUserId = currentUser.Id; var adminIds = await _userService.GetAdminUserIdsAsync(); var usersToNotify = adminIds.Distinct().ToList(); if (usersToNotify.Any()) { var notification = new NotificationModel { SenderId = vendorUserId, Subject = "New Change Renewal Request Submitted", Content = "A new change renewal request submitted by " + currentUser.Vendor.ManifactoryName, Action = Action.CHANGE_RENEWAL_REQUEST_SUBMITTED, ActionId = request.Id, LogUrl = "", Status = states.WAITING }; await _notificationService .SendNotificationToListOfUsers(usersToNotify, notification); } { var notification = new NotificationModel { SenderId = vendorUserId, Subject = "New Renewal Request Submitted Successfully", Content = "Your Change Renewal Request is successfully submitted", Action = Action.CHANGE_RENEWAL_REQUEST_SUBMITTED, ActionId = request.Id, LogUrl = "", Status = states.WAITING }; await _notificationService .SendNotificationToUser(vendorUserId, notification); } } } catch (Exception ex) { // } return request; } } }