using Neo.Afx.Mvc; using Neo.Afx.Services.Documents; using Neo.Afx.Services.Integration; using Neo.Afx.Services.Reports; using Neo.Afx.ViewModels; using Neo.LegitimateLicences.Common; using Neo.LegitimateLicences.Data.Models; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Web.Mvc; using DevExpress.Office; using DevExpress.XtraRichEdit.API.Native; using DevExpress.XtraPrinting; using Neo.Afx.Common; namespace Neo.LegitimateLicences.GazetteConsole { public partial class GazetteConsoleController : Neo.LegitimateLicences.Common.ControllerBase { private const long _entityID = 1006; private const long _documentStoreLocationID = 3; #region Console public ActionResult Console(long gazetteDetailID) { if (!SecurityHelper.UserHasAccessToSecurable(Profile.Securables.ToList(), SecurableEnum.ApplicationsModule_Gazettes)) { return RedirectToAction("ShowError", "Error", new { msg = "Security error" }); } var database = new LegitimateDatabase(); var model = new ConsoleModel() { GazetteDetailID = gazetteDetailID, GazetteDetail = database.GetGazetteDetail(gazetteDetailID, Profile.UserID) }; //model.BrowserTitle = model.Association.AssociationName; ViewBag.Controller = "Gazette Details"; ViewBag.Page = "Gazette Details"; return View("Console", model); } #endregion #region Default public ActionResult Default(long gazetteDetailID) { var database = new LegitimateDatabase(); var model = new DefaultModel { GazetteDetail = database.GetGazetteDetail(gazetteDetailID, Profile.UserID), HasApplications = database.GetGazetteApplications(gazetteDetailID).Count > 0, Provinces = database.GetProvinces(), Regions = database.GetRegions(), GazettePeriodDays = Convert.ToInt32(database.GetSettingValue("GazettePeriodDays")), entityID = _entityID, itemID = gazetteDetailID, DocumentStoreLocationID = _documentStoreLocationID, RegionID = -1, Lookups = GetLookups() }; //model.BrowserTitle = (model.GazetteDetail.GazetteDetailID == -1) ? "New Gazette" : model.GazetteDetail.GazetteNumber; ViewBag.Controller = "Gazette Details"; ViewBag.Page = "Gazette Details"; return View("Default", model); } #endregion #region Applications public ActionResult Applications(long gazetteDetailID) { var database = new LegitimateDatabase(); var applications = database.GetGazetteApplications(gazetteDetailID); var canBypassGazetteExpiry = SecurityHelper.UserHasAccessToSecurable(Profile.Securables.ToList(), SecurableEnum.ApplicationsModule_Gazettes_Console_Bypassgazetteexpiry); // Show Bypass for e-hailing only when setting AllowBypassGazetteExpiryForEhailing is TRUE (dev/test). Treat missing as FALSE (production). var showBypassForEhailing = false; if (canBypassGazetteExpiry) { try { var val = database.GetSettingValue("AllowBypassGazetteExpiryForEhailing"); showBypassForEhailing = string.Equals(val, "TRUE", StringComparison.OrdinalIgnoreCase) || val == "1"; } catch { showBypassForEhailing = false; } } var model = new ApplicationsModel { GazetteDetail = database.GetGazetteDetail(gazetteDetailID, Profile.UserID), Applications = applications, HasApplications = applications.Count > 0, entityID = _entityID, itemID = gazetteDetailID, DocumentStoreLocationID = _documentStoreLocationID, CanBypassGazetteExpiry = canBypassGazetteExpiry, ShowBypassForEhailing = showBypassForEhailing }; //model.BrowserTitle = (model.GazetteDetail.GazetteDetailID == -1) ? "New Gazette" : model.GazetteDetail.GazetteNumber; ViewBag.Controller = "Applications"; ViewBag.Page = "Applications"; return View("Applications", model); } #endregion #region Save [HttpPost] public JsonResult Save(GazetteDetail value) { if (!SecurityHelper.UserHasAccessToSecurable(Profile.Securables.ToList(), SecurableEnum.ApplicationsModule_Createnewgazette)) { throw new Exception("Security error"); } var database = new LegitimateDatabase(); var errorMessage = ""; var currentGazette = database.SaveGazetteDetail(value, Profile.UserID, out errorMessage); var results = new { GazetteDetail = currentGazette, ErrorMessage = errorMessage }; return Json(results, JsonSerializerOptions.None); } #endregion #region FinaliseAndPrintGazette [HttpPost] public JsonNetResult FinaliseAndPrintGazette(long gazetteDetailID, string comments) { try { if (!SecurityHelper.UserHasAccessToSecurable(Profile.Securables.ToList(), SecurableEnum.ApplicationsModule_Gazettes_Console_FinaliseAndPublishGazette)) { throw new Exception("Security error"); } var database = new LegitimateDatabase(); var reportDb = new ReportDatabase(); var documentDb = new DocumentsDatabase(); var gazette = database.GetGazetteDetail(gazetteDetailID, Profile.UserID); if (gazette == null) { return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = "Gazette not found." } }; } var documentID = gazette.GazetteDocumentID ?? -1; #region generate gazette and upload to db var db = new LegitimateDatabase(); var docDb = new DocumentsDatabase(); var generatedDocumentBytes = new byte[0]; var gazetteDetails = db.GetGazetteDetailPack(gazetteDetailID, 0, Profile.UserID); var gazetteRoutes = db.GetGazetteRoutes(gazetteDetailID, Profile.UserID); // Validate before generating document: avoid NRE and prevent finalising empty gazette if (gazetteDetails == null || !gazetteDetails.Any()) { return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = "No applications in this gazette pack." } }; } // Load the Gazette template directly from disk to avoid relying on outbound HTTP from the web server var templatePath = Server.MapPath("~/Content/Templates/GazettePack.docx"); Byte[] templateFileContent = null; #region Get Template File // Read all bytes from the local template file; any IO exception will be logged and surfaced via the existing error handling templateFileContent = System.IO.File.ReadAllBytes(templatePath); using (var srv = new DevExpress.XtraRichEdit.RichEditDocumentServer()) { DevExpress.XtraRichEdit.API.Native.Document doc = srv.Document; using (var stream = new MemoryStream(templateFileContent)) { doc.AppendDocumentContent(stream); } #region Create table stylesheet doc.BeginUpdate(); TableStyle tStyleMain = doc.TableStyles.CreateNew(); tStyleMain.FontName = "Arial"; tStyleMain.FontSize = 7; doc.TableStyles.Add(tStyleMain); doc.EndUpdate(); #endregion #region Print Gazette Details Table table = doc.Tables[0]; table.BeginUpdate(); int rowIndex = 0; List indexToBeMerged = new List(); foreach (var gazetteApplication in gazetteDetails) { #region Row 1 doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "1)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, "APPLICATION NUMBER:" + gazetteApplication.ReferenceNumber); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[2].Range.Start, "2)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[3].Range.Start, "GAZETTE NUMBER:" + gazetteApplication.GazetteNumber); table.Rows.InsertAfter(rowIndex); rowIndex++; #endregion #region Row 2 doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "3)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, "APPLICANT:" + gazetteApplication.FirstNames + " " + gazetteApplication.SurnameOrCompanyName + Characters.LineBreak.ToString() + "ID NO: " + gazetteApplication.IDNumberOrBusinessRegistrationNo + Characters.LineBreak.ToString() + "ASSOCIATION: " + gazetteApplication.AssociationName); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[2].Range.Start, "4)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[3].Range.Start, "APPLICANT ADDRESS:" + gazetteApplication.PostalAddress + Characters.LineBreak.ToString() + gazetteApplication.PostalAddressCode); table.Rows.InsertAfter(rowIndex); rowIndex++; #endregion #region Row 3 doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "5)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, "EXISTING LICENCE HOLDER:" + gazetteApplication.ExistingFirstname + " " + gazetteApplication.ExistingSurname + Characters.LineBreak.ToString() + "ID NO: " + gazetteApplication.ExistingIDNumber); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[2].Range.Start, "6)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[3].Range.Start, "EXISTING LICENCE HOLDER ADDRESS:" + gazetteApplication.ExistingPostalAddress + Characters.LineBreak.ToString() + gazetteApplication.ExistingPostalCode); table.Rows.InsertAfter(rowIndex); rowIndex++; #endregion #region Row 4 doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "7)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, "TYPE OF APPLICATION:" + gazetteApplication.ApplicationRequestTypesTitle); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[2].Range.Start, "8)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[3].Range.Start, "OPERATING LICENCE NUMBER:" + gazetteApplication.OperatingLicenceNumber); table.Rows.InsertAfter(rowIndex); rowIndex++; #endregion #region Row 5 doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "9)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, "VEHICLE TYPE:" + gazetteApplication.VehicleType); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[2].Range.Start, "10)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[3].Range.Start, gazetteApplication.CapacityDescription); table.Rows.InsertAfter(rowIndex); rowIndex++; #endregion #region Row 6 doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "11)"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, "PROPOSED LIST OF ROUTES"); //indexToBeMerged.Add(rowIndex); table.Rows.InsertAfter(rowIndex); table.MergeCells(table[rowIndex, 1], table[rowIndex, 3]); rowIndex++; #endregion #region Row 7 Route Details var licenceRouteDetails = gazetteRoutes.Where(a => a.ApplicationDetailID == gazetteApplication.ApplicationDetailID && a.ApplicationLicenceID == gazetteApplication.ApplicationLicenceID).ToList(); var ServiceTypeNumbers = licenceRouteDetails.OrderBy(z => z.ServiceTypeNumber).Select(z => z.ServiceTypeNumber).Distinct().ToList(); #region Spin through services foreach (var ServiceTypeNumber in ServiceTypeNumbers) { var serviceType = licenceRouteDetails.Where(a => a.ServiceTypeNumber == ServiceTypeNumber).FirstOrDefault(); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "11." + serviceType.ServiceTypeNumber + ")"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, serviceType.ServiceType); //indexToBeMerged.Add(rowIndex); table.Rows.InsertAfter(rowIndex); table.MergeCells(table[rowIndex, 1], table[rowIndex, 3]); rowIndex++; foreach (var route in licenceRouteDetails.Where(a => a.ServiceTypeNumber == ServiceTypeNumber).ToList().OrderBy(o => o.RouteNumber)) { doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, "11." + ServiceTypeNumber + "." + route.RouteNumber + ")"); doc.InsertSingleLineText(table.Rows[rowIndex].Cells[1].Range.Start, route.RouteDescription); //indexToBeMerged.Add(rowIndex); table.Rows.InsertAfter(rowIndex); table.MergeCells(table[rowIndex, 1], table[rowIndex, 3]); rowIndex++; } } #endregion doc.InsertSingleLineText(table.Rows[rowIndex].Cells[0].Range.Start, Characters.LineBreak.ToString()); table.Rows.InsertAfter(rowIndex); rowIndex++; #endregion } //foreach (var index in indexToBeMerged) //{ // table.MergeCells(table[index, 1], table[index, 3]); //} table.Style = tStyleMain; table.EndUpdate(); #endregion #region Additional Replacements // Use gazette header for number; use first detail's Region with null-safe fallback (guard above ensures pack has items) doc.ReplaceAll("$GazetteNumber$", gazette.GazetteNumber ?? "", DevExpress.XtraRichEdit.API.Native.SearchOptions.None); doc.ReplaceAll("$Region$", gazetteDetails.FirstOrDefault()?.Region ?? "", DevExpress.XtraRichEdit.API.Native.SearchOptions.None); #endregion using (var generatedocumentStream = new MemoryStream()) { var pdfOptions = new PdfExportOptions(); pdfOptions.DocumentOptions.Author = Profile.FullName; pdfOptions.Compressed = false; pdfOptions.ImageQuality = PdfJpegImageQuality.Highest; srv.ExportToPdf(generatedocumentStream, pdfOptions); generatedDocumentBytes = new byte[generatedocumentStream.Length]; generatedDocumentBytes = generatedocumentStream.ToArray(); } var fileName = gazette.GazetteNumber + '-' + DateTime.Now.ToString("ddMMyyyyhhmmss") + ".pdf"; var document = documentDb.UploadFile( Profile.UserID, Profile.Email, documentID, Convert.ToInt32(_entityID), gazette.GazetteDetailID, 500, /*gazette*/ _documentStoreLocationID, fileName, "application/pdf", generatedDocumentBytes ); if (documentID < 0) { documentID = document.DocumentID; documentDb.UpdateDocumentIDOnEntity(document.DocumentID, Convert.ToInt32(_entityID), gazette.GazetteDetailID, "GazetteDocumentID", Profile.UserID); } } #endregion #endregion #region Update gazette status var errorMessage = ""; var success = database.GazetteStatusUpdate(gazetteDetailID, Profile.UserID, comments, GazetteDetailStatusEnum.AwaitingGazettePrintDate, out errorMessage); return new JsonNetResult() { Data = new JsonBaseResult() { Success = success, Error = "", ErrorCode = "", Result = new { GazetteDocumentID = documentID } } }; #endregion } catch(Exception ex) { #region Error Handling #region Error Additional Data Dictionary additionalData = new Dictionary(); additionalData.Add("gazetteDetailID", gazetteDetailID); additionalData.Add("comments", comments); #endregion string error = ErrorHelper.ProcessError(ex, "GazetteConsole", "FinaliseAndPrintGazette", additionalData); #endregion return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = error } }; } } #endregion #region SetGazetteAsPublished [HttpPost] public JsonResult SetGazetteAsPublished(long gazetteDetailID, string comments) { if (!SecurityHelper.UserHasAccessToSecurable(Profile.Securables.ToList(), SecurableEnum.ApplicationsModule_Gazettes_Console_FinaliseAndPublishGazette)) { throw new Exception("Security error"); } var database = new LegitimateDatabase(); var errorMessage = ""; var success = database.GazetteStatusUpdate(gazetteDetailID, Profile.UserID, comments, GazetteDetailStatusEnum.AwaitingNoticePeriodExpiry, out errorMessage); if (success) { try { // Set notice expiry: 14 days if gazette has e-hailing apps, else 21 (per System.Settings) database.SetGazetteNoticeExpiryForPublish(gazetteDetailID); } catch (Exception ex) { // Status already updated; surface warning so expiry can be corrected manually if needed errorMessage = "Gazette status was updated but notice expiry date could not be set: " + (ex.Message ?? ex.GetType().Name); } } var results = new { Success = success, ErrorMessage = errorMessage }; return Json(results, JsonSerializerOptions.None); } #endregion #region BypassGazetteExpiry [HttpPost] public JsonResult BypassGazetteExpiry(long applicationDetailID, string comments) { if (!SecurityHelper.UserHasAccessToSecurable(Profile.Securables.ToList(), SecurableEnum.ApplicationsModule_Gazettes_Console_Bypassgazetteexpiry)) { throw new Exception("Security error"); } var database = new LegitimateDatabase(); var errorMessage = ""; var success = database.BypassGazetteExpiry(applicationDetailID, comments, Profile.UserID, out errorMessage); var results = new { Success = success, ErrorMessage = errorMessage }; return Json(results, JsonSerializerOptions.None); } #endregion #region GetLookups public List GetLookups() { var lookups = new List(); var integrationStore = new SqlIntegrationStore(); try { integrationStore.Open(); lookups.Add(new LookupList("GazetteTypes", integrationStore.GetDataEntities("GazetteTypes", string.Empty))); } finally { integrationStore.Close(); } return lookups; } #endregion } }