using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.FileImport; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Validation; using MMDAL.CustomCode.NestingPlan; using MMDAL.DTO.NestingPlan; using Newtonsoft.Json; namespace MMBLL.NestingPlan { public class NestingPlanBLL : INestingPlanBLL { private readonly INestingPlanDAL _NestingPlanDAL; private readonly AutoNumber _AutoNumber; private readonly IValidation _Validation; private readonly IFileToDataTableService _FileReader; public NestingPlanBLL(INestingPlanDAL INestingPlanDAL, AutoNumber AutoNumber, IValidation validation, IFileToDataTableService fileReader) { _NestingPlanDAL = INestingPlanDAL; _AutoNumber = AutoNumber; _Validation = validation; _FileReader = fileReader; } public async Task GetSelectListNestingPlan(int FirstNumber, int MaxResult, CriteriaDTO criteriaDTO, LoginDTO LoginDTO) { try { // Call the existing DAL method (matches the interface signature) return await _NestingPlanDAL.GetSelectListNestingPlan(FirstNumber, MaxResult, criteriaDTO, LoginDTO); } catch (Exception) { throw; } } public async Task GetNestingPlan(int NestingPlanId, LoginDTO login, CancellationToken ct) => await _NestingPlanDAL.GetNestingPlan(NestingPlanId, login, ct).ConfigureAwait(false); public async Task SaveNestingPlan(NestingPlanDTO dto, LoginDTO login, CancellationToken ct) { if (dto == null) throw new ArgumentNullException(nameof(dto)); AutoNumberDTO? autoNumberDTO = null; try { await _Validation.NotEmpty(dto.NestingPlanCode, nameof(dto.NestingPlanCode)); dto.TenantId = login.ClientId; dto.NestingPlanModifiedById = login.UserId; dto.NestingPlanModifiedOn = DateTime.UtcNow; bool isNew = dto.NestingPlanId == 0; // ✅ decide BEFORE changing ID if (isNew) { autoNumberDTO = await _AutoNumber.GetNumberAsync(1, "NESTINGPLAN", login); dto.NestingPlanId = autoNumberDTO.StartNumber; // ID changes here dto.NestingPlanCreatedById = login.UserId; dto.NestingPlanCreatedOn = DateTime.UtcNow; } // ✅ pass isNew to DAL await _NestingPlanDAL.SaveNestingPlan(dto, isNew, login, ct); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.NestingPlanId}" : $"{SuccessResponse.UpdateSuccessMessage} {dto.NestingPlanId}"; } catch { if (autoNumberDTO != null && dto.NestingPlanId > 0) { await _AutoNumber.RollbackAutoNumber("NESTINGPLAN", dto.NestingPlanId, login); } throw; } } public async Task DeleteNestingPlan(int NestingPlanId, LoginDTO loginDTO) { try { return await _NestingPlanDAL.DeleteNestingPlan(NestingPlanId, loginDTO); } catch (Exception) { throw; } } // ── Import (nesting_import.html: "Drop CSV / Excel file here") ───────── // Parses the uploaded file via the shared GB5Shared.FileImport reader, maps each row // to a NestingPlanDetailDTO (Type=0 Part), appends to the existing plan's detail array, // and delegates to the already-built SaveNestingPlan for persistence — no new detail-save // logic duplicated here. Expected columns (case-insensitive, all but PartNumber optional): // PartNumber, Quantity, Length, Width, Weight, Remarks. public async Task ImportNestingPlanDetails(NestingPlanImportRequestDTO request, LoginDTO login, CancellationToken ct) { if (request == null) throw new ArgumentNullException(nameof(request)); await _Validation.NotEmpty(request.Base64Content, nameof(request.Base64Content)); if (request.NestingPlanId <= 0) throw new ArgumentException("NestingPlanId is required."); byte[] bytes = Convert.FromBase64String(request.Base64Content); var fileType = (ImportFileTypeEnum)request.ImportFileType; DataTable rows = await _FileReader.ReadFromBytesAsync(bytes, fileType, ct).ConfigureAwait(false); string existingJson = await _NestingPlanDAL.GetNestingPlan(request.NestingPlanId, login, ct).ConfigureAwait(false); var plan = JsonConvert.DeserializeObject(existingJson) ?? throw new InvalidOperationException($"NestingPlanId {request.NestingPlanId} not found."); plan.NestingPlanDetailArray ??= new List(); short nextSlNo = (short)(plan.NestingPlanDetailArray.Count > 0 ? plan.NestingPlanDetailArray.Max(d => d.NestingPlanDetailSlNo) + 1 : 1); int imported = 0; int skipped = 0; foreach (DataRow row in rows.Rows) { string partNumber = GetCell(row, "PartNumber"); if (string.IsNullOrWhiteSpace(partNumber)) { skipped++; continue; } plan.NestingPlanDetailArray.Add(new NestingPlanDetailDTO { NestingPlanDetailId = 0, NestingPlanId = plan.NestingPlanId, NestingPlanDetailSlNo = nextSlNo++, NestingPlanDetailType = 0, // Part PartNumber = partNumber, NestingPlanDetailQuantity = ParseShort(GetCell(row, "Quantity")), D1 = ParseDecimal(GetCell(row, "Length")), D2 = ParseDecimal(GetCell(row, "Width")), NestingPlanDetailWeight = ParseDouble(GetCell(row, "Weight")), NestingPlanDetailRemarks = GetCell(row, "Remarks"), }); imported++; } if (imported == 0) throw new InvalidOperationException("No valid rows found (PartNumber column is required on every row)."); await SaveNestingPlan(plan, login, ct).ConfigureAwait(false); return $"Imported {imported} part row(s){(skipped > 0 ? $", skipped {skipped} row(s) missing PartNumber" : "")}."; } private static string GetCell(DataRow row, string columnName) { var col = row.Table.Columns.Cast() .FirstOrDefault(c => string.Equals(c.ColumnName, columnName, StringComparison.OrdinalIgnoreCase)); return col == null ? string.Empty : (row[col]?.ToString() ?? string.Empty).Trim(); } private static short ParseShort(string value) => short.TryParse(value, out var v) ? v : (short)0; private static decimal ParseDecimal(string value) => decimal.TryParse(value, out var v) ? v : 0m; private static double ParseDouble(string value) => double.TryParse(value, out var v) ? v : 0d; } }