using FastEndpoints; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.DTO.Report; using GB5Shared.FastEndPoint; using MMBLL.Production; using MMDAL.DTO.Production; using Newtonsoft.Json; using System.Globalization; using System.Reflection; using System.Text.Json; using static GB5Shared.GB5Constant.Constant; namespace MMSL.EndPoints.Production.Reports { // GB4 Production.svc ProductionSummaryRegister migration. Type: 0=Summary, 1=Register (detail), // 2=Machine-Wise Summary, 3=Scale Reading. public class ProductionSummaryRegister : BaseReportEndpoint { private readonly IProductionReportBLL _bll; public ProductionSummaryRegister(IProductionReportBLL bll) => _bll = bll; public override void Configure() { Post("/Production/ProductionSummaryRegister"); AllowAnonymous(); } public record Parameters( [property: FromHeader] string Login, [property: FromBody] ReportCallingDTO ReportCallingDTO, [property: QueryParam] int Type, [property: QueryParam] int FirstNumber, [property: QueryParam] int MaxResult ); // Export path: like AccountAgeingDetailReport, retrieve the complete result set so every // ReportFormat uses the report data rather than only the currently visible page. protected override async IAsyncEnumerable ExecuteReportAsync( Parameters req, LoginDTO loginDTO, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct) { var (json, _) = await _bll.ProductionSummaryRegister( req.Type, 1, int.MaxValue, ToProductionCriteria(req.ReportCallingDTO.CriteriaDTO), loginDTO, ct) .ConfigureAwait(false); foreach (var row in JsonConvert.DeserializeObject>(json) ?? Enumerable.Empty()) { ct.ThrowIfCancellationRequested(); yield return row; } } // UI table path: retain the endpoint's existing page and total semantics. protected override async Task> ExecutePagedAsync(Parameters req, LoginDTO loginDTO, CancellationToken ct) { try { var (json, total) = await _bll.ProductionSummaryRegister( req.Type, req.FirstNumber, req.MaxResult, ToProductionCriteria(req.ReportCallingDTO.CriteriaDTO), loginDTO, ct).ConfigureAwait(false); var rows = JsonConvert.DeserializeObject>(json) ?? new List(); var response = await GB5Shared.ResponseStandard.Response.CreateSuccessResponse(rows, CacheKeyLevel.NOT_REQUIRED, loginDTO); response.Total = total; return response; } catch (Exception ex) { return await GB5Shared.ResponseStandard.Response.CreateExceptionError(ex, CacheKeyLevel.NOT_REQUIRED, loginDTO, ex.Message, 500); } } // ProductionSummaryRegister predates the ReportCallingDTO contract and has a typed filter // object. The report shell supplies those values as CriteriaDTO attributes, so adapt by // property name (FieldName first, then CriteriaAttributeName). This keeps all existing // production filters available without weakening the common report-export contract. private static ProductionSummaryRegisterCriteria ToProductionCriteria(CriteriaDTO? criteriaDTO) { var criteria = new ProductionSummaryRegisterCriteria(); if (criteriaDTO?.SectionCriteriaList == null) return criteria; var properties = typeof(ProductionSummaryRegisterCriteria) .GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(p => p.CanWrite) .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); foreach (var attribute in criteriaDTO.SectionCriteriaList .Where(section => section?.AttributesCriteriaList != null) .SelectMany(section => section.AttributesCriteriaList!)) { var name = string.IsNullOrWhiteSpace(attribute.FieldName) ? attribute.CriteriaAttributeName : attribute.FieldName; if (string.IsNullOrWhiteSpace(name) || !properties.TryGetValue(name, out var property)) continue; if (TryConvert(attribute.FieldValue, property.PropertyType, out var value)) property.SetValue(criteria, value); } return criteria; } private static bool TryConvert(object? source, Type targetType, out object? value) { value = null; if (source == null) return false; if (source is JsonElement element) { if (element.ValueKind == JsonValueKind.Null) return false; source = element.ValueKind == JsonValueKind.String ? element.GetString() : element.ToString(); } var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; if (underlyingType.IsInstanceOfType(source)) { value = source; return true; } try { value = underlyingType == typeof(DateTime) ? DateTime.Parse(Convert.ToString(source, CultureInfo.InvariantCulture)!, CultureInfo.InvariantCulture) : Convert.ChangeType(source, underlyingType, CultureInfo.InvariantCulture); return true; } catch (FormatException) { return false; } catch (InvalidCastException) { return false; } catch (OverflowException) { return false; } } } }