diff --git a/ENI2/Controls/MaerskListControl.xaml.cs b/ENI2/Controls/MaerskListControl.xaml.cs index a4d275b6..f3630fab 100644 --- a/ENI2/Controls/MaerskListControl.xaml.cs +++ b/ENI2/Controls/MaerskListControl.xaml.cs @@ -338,7 +338,8 @@ namespace ENI2.Controls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); // Get first worksheet var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row diff --git a/ENI2/DetailRootControl.xaml.cs b/ENI2/DetailRootControl.xaml.cs index d6066145..6e50b0b8 100644 --- a/ENI2/DetailRootControl.xaml.cs +++ b/ENI2/DetailRootControl.xaml.cs @@ -17,6 +17,7 @@ using ENI2.EditControls; using System.Windows.Input; using System.Linq; using ENI2.SheetDisplayControls; +using System.Threading.Tasks; namespace ENI2 { @@ -34,6 +35,9 @@ namespace ENI2 // private readonly Dictionary messageClassControlDict = new Dictionary(); private readonly object messageListLock = new object(); private readonly HighlightService highlightService = new HighlightService(); + private readonly object validationLock = new object(); + private Task activeValidationTask = null; + private bool pendingShowMessages = false; // Referenzen für Fehler/Violation Dialoge (können, müssen aber nicht offen bleiben) protected ErrorListDialog _errorListDialog = null; @@ -703,7 +707,13 @@ namespace ENI2 private void DetailControl_RequestSendValidation() { - this.Validate(false, out _, out List errorList); + DetailControl_RequestSendValidationAsync(); + } + + private async void DetailControl_RequestSendValidationAsync() + { + ValidationResult result = await ValidateAndApplyAsync(false); + List errorList = result.Errors; foreach (Message aMessage in this._messages) { @@ -777,43 +787,66 @@ namespace ENI2 } } - private void DetailControl_RequestValidate(bool showDialog) + private async void DetailControl_RequestValidate(bool showDialog) { - this.Validate(showDialog, out _, out _); + await ValidateAndApplyAsync(showDialog); } - private void Validate(bool showMessages, out List vViolations, out List vErrors) + private async Task ValidateAndApplyAsync(bool showMessages) { - vViolations = new List(); - vErrors = new List(); + Task validationTask = null; + lock (validationLock) + { + if (activeValidationTask != null && !activeValidationTask.IsCompleted) + { + if (showMessages) + pendingShowMessages = true; + validationTask = activeValidationTask; + } + else + { + pendingShowMessages = showMessages; + activeValidationTask = ValidateAndApplyCoreAsync(); + validationTask = activeValidationTask; + } + } - // TODO: clear highlighting + return await validationTask; + } + private async Task ValidateAndApplyCoreAsync() + { Util.UIHelper.SetBusyState(); + ApplyCrewEffectsWarningSuppression(); + + List messagesSnapshot = _messages.ToList(); + ValidationResult result = await Task.Run(() => ValidateCore(messagesSnapshot)); + + bool showMessages; + lock (validationLock) + { + showMessages = pendingShowMessages; + pendingShowMessages = false; + activeValidationTask = null; + } + + ApplyValidationResult(result, showMessages); + return result; + } + + private ValidationResult ValidateCore(List messagesSnapshot) + { + List vViolations = new List(); + List vErrors = new List(); RuleEngine ruleEngine = new RuleEngine(); - foreach (Message aMessage in _messages) + foreach (Message aMessage in messagesSnapshot) { if (!aMessage.EvaluateForValidation(this.Core.IsTransit)) continue; List errors = new List(); List violations = new List(); - ruleEngine.ValidateMessage(aMessage, out errors, out violations); - - if (errors.Count > 0) - aMessage.ErrorCount = errors.Count; - else - aMessage.ErrorCount = null; - if (violations.Count > 0) - { - aMessage.ViolationCount = violations.Count; - aMessage.PositionViolationCount = violations.Count(v => !v.Identifier.IsNullOrEmpty()); - } - else - { - aMessage.ViolationCount = null; - aMessage.PositionViolationCount = null; - } + ruleEngine.ValidateMessage(aMessage, out errors, out violations); string messageGroup = this.MessageGroupForMessage(aMessage); @@ -829,22 +862,22 @@ namespace ENI2 vViolations.AddRange(violations); } - #region 12.11.18 / 6.3.21 / 23.5.22 / 26.10.24: globale Plausi-Prüfungen + #region 12.11.18 / 6.3.21 / 23.5.22 / 26.10.24 / 27.1.26: globale Plausi-Prüfungen - Message crewaMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.CREWA); - Message crewdMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.CREWD); - Message pasaMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.PASA); - Message pasdMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.PASD); - Message pobaMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.POBA); - Message pobdMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.POBD); - Message secMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.SEC); - Message noanodMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.NOA_NOD); - Message mdhMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.MDH); - Message was_rcptMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.WAS_RCPT); - Message wasMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.WAS); - Message servMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.SERV); - Message statMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.STAT); - Message pre72hMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.PRE72H); + Message crewaMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.CREWA); + Message crewdMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.CREWD); + Message pasaMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.PASA); + Message pasdMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.PASD); + Message pobaMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.POBA); + Message pobdMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.POBD); + Message secMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.SEC); + Message noanodMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.NOA_NOD); + Message mdhMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.MDH); + Message was_rcptMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.WAS_RCPT); + Message wasMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.WAS); + Message servMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.SERV); + Message statMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.STAT); + Message pre72hMessage = messagesSnapshot.Find(message => message.MessageNotificationClass == Message.NotificationClass.PRE72H); #region CREW / PAS Count Plausibility @@ -984,22 +1017,6 @@ namespace ENI2 #endregion - #region 4.1.23 no CREW effects warning for DE - if (crewaMessage != null) - { - MessageViolation mv = crewaMessage.ViolationList.Find((x) => x.PropertyName.Equals("Effects") && (x.ViolationCode == (int)ValidationCode.TRUNCATE)); - if ((mv != null) && !Core.IsDK) - crewaMessage.ViolationList.Remove(mv); - } - - if(crewdMessage != null) - { - MessageViolation mvd = crewdMessage.ViolationList.Find((x) => x.PropertyName.Equals("Effects") && (x.ViolationCode == (int)ValidationCode.TRUNCATE)); - if ((mvd != null) && !Core.IsDK) - crewdMessage.ViolationList.Remove(mvd); - } - #endregion - #region WAS_RCPT double numbers Dictionary identDict = new Dictionary(); @@ -1027,35 +1044,37 @@ namespace ENI2 #region Kiel Canal Timing Plausibility - if (!this.Core.IsTransit && (secMessage?.Elements.Count > 0) && (noanodMessage?.Elements.Count > 0)) { - SEC sec = secMessage.Elements[0] as SEC; - NOA_NOD noa_nod = noanodMessage.Elements[0] as NOA_NOD; - - if (sec.KielCanalPassagePlanned ?? false) + if (!this.Core.IsTransit && (secMessage?.Elements.Count > 0) && (noanodMessage?.Elements.Count > 0)) { - // Überprüfung, ob die eingehende NOK-Durchfahrt auch wirklich innerhalb der eingehenden Reise liegt (bei VISIT) - bool isValidIncoming = (noa_nod.ETDFromLastPort < sec.KielCanalPassagePlannedIncomming) && - (sec.KielCanalPassagePlannedIncomming < noa_nod.ETAToPortOfCall); - if (!sec.KielCanalPassagePlannedIncomming.HasValue) isValidIncoming = true; + SEC sec = secMessage.Elements[0] as SEC; + NOA_NOD noa_nod = noanodMessage.Elements[0] as NOA_NOD; - if(!isValidIncoming) + if (sec.KielCanalPassagePlanned ?? false) { - MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Kiel Canal incoming implausible (ETD / ETA ports)!", null, "Kiel Canal timing", null, "SEC"); - mv.MessageGroupName = Properties.Resources.textOverview; - vViolations.Add(mv); - } + // Überprüfung, ob die eingehende NOK-Durchfahrt auch wirklich innerhalb der eingehenden Reise liegt (bei VISIT) + bool isValidIncoming = (noa_nod.ETDFromLastPort < sec.KielCanalPassagePlannedIncomming) && + (sec.KielCanalPassagePlannedIncomming < noa_nod.ETAToPortOfCall); + if (!sec.KielCanalPassagePlannedIncomming.HasValue) isValidIncoming = true; + + if (!isValidIncoming) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Kiel Canal incoming implausible (ETD / ETA ports)!", null, "Kiel Canal timing", null, "SEC"); + mv.MessageGroupName = Properties.Resources.textOverview; + vViolations.Add(mv); + } - bool isValidOutgoing = (noa_nod.ETDFromPortOfCall < sec.KielCanalPassagePlannedOutgoing) && - ((noa_nod.NextPort == "ZZUKN") || (sec.KielCanalPassagePlannedOutgoing < noa_nod.ETAToNextPort)); - if (!sec.KielCanalPassagePlannedOutgoing.HasValue) isValidOutgoing = true; + bool isValidOutgoing = (noa_nod.ETDFromPortOfCall < sec.KielCanalPassagePlannedOutgoing) && + ((noa_nod.NextPort == "ZZUKN") || (sec.KielCanalPassagePlannedOutgoing < noa_nod.ETAToNextPort)); + if (!sec.KielCanalPassagePlannedOutgoing.HasValue) isValidOutgoing = true; - if(!isValidOutgoing) - { - MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Kiel Canal outgoing implausible (ETD / ETA ports)!", null, "Kiel Canal timing", null, "SEC"); - mv.MessageGroupName = Properties.Resources.textOverview; - vViolations.Add(mv); + if (!isValidOutgoing) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Kiel Canal outgoing implausible (ETD / ETA ports)!", null, "Kiel Canal timing", null, "SEC"); + mv.MessageGroupName = Properties.Resources.textOverview; + vViolations.Add(mv); + } } } } @@ -1212,19 +1231,133 @@ namespace ENI2 #endregion + #region WAS special max capa rules regarding next port + + { + string wasMessageGroup = this.MessageGroupForMessage(wasMessage); + + // see WAS-Regulation.docx in parent projects misc folder + if ((noanodMessage?.Elements.Count > 0) && wasMessage?.Elements.Count > 0) + { + NOA_NOD noa_nod = noanodMessage.Elements[0] as NOA_NOD; + WAS was = wasMessage.Elements[0] as WAS; + + bool isSpecialNextPort = RuleEngine.IsSpecialNextPort(noa_nod.NextPort); + + if (isSpecialNextPort) + { + foreach (Waste waste in was.Waste) + { + switch (waste.WasteType) + { + case 101: + case 102: + case 103: + case 104: + case 105: + case 401: + if (waste.WasteAmountRetained_MTQ > waste.WasteCapacity_MTQ * 0.5) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Acc. to EU Regulation 2022/89 the amount retained on board is noticeably", null, "Waste", waste.Identifier, was.Tablename); + mv.MessageGroupName = wasMessageGroup; + vViolations.Add(mv); + } + break; + case 501: + case 502: + case 503: + case 504: + case 505: + case 506: + case 507: + case 508: + case 509: + case 510: + case 511: + if (waste.WasteAmountRetained_MTQ > waste.WasteCapacity_MTQ * 0.25) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Acc. to EU Regulation 2022/89 the amount retained on board is noticeably", null, "Waste", waste.Identifier, was.Tablename); + mv.MessageGroupName = wasMessageGroup; + vViolations.Add(mv); + } + break; + case 601: + case 602: + if (waste.WasteAmountRetained_MTQ > waste.WasteCapacity_MTQ * 0.75) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Acc. to EU Regulation 2022/89 the amount retained on board is noticeably", null, "Waste", waste.Identifier, was.Tablename); + mv.MessageGroupName = wasMessageGroup; + vViolations.Add(mv); + } + break; + } + } + } + else + { + foreach (Waste waste in was.Waste) + { + switch (waste.WasteType) + { + case 101: + case 102: + case 103: + case 104: + case 105: + if (waste.WasteAmountRetained_MTQ > waste.WasteCapacity_MTQ * 0.25) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Acc. to EU Regulation 2022/89 the amount retained on board is noticeably", null, "Waste", waste.Identifier, was.Tablename); + mv.MessageGroupName = wasMessageGroup; + vViolations.Add(mv); + } + break; + case 401: + if (waste.WasteAmountRetained_MTQ > waste.WasteCapacity_MTQ * 0.5) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Acc. to EU Regulation 2022/89 the amount retained on board is noticeably", null, "Waste", waste.Identifier, was.Tablename); + mv.MessageGroupName = wasMessageGroup; + vViolations.Add(mv); + } + break; + case 501: + case 502: + case 503: + case 504: + case 505: + case 506: + case 507: + case 508: + case 509: + case 510: + case 511: + if (waste.WasteAmountRetained_MTQ > waste.WasteCapacity_MTQ * 0.2) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Acc. to EU Regulation 2022/89 the amount retained on board is noticeably", null, "Waste", waste.Identifier, was.Tablename); + mv.MessageGroupName = wasMessageGroup; + vViolations.Add(mv); + } + break; + case 601: + case 602: + if (waste.WasteAmountRetained_MTQ > waste.WasteCapacity_MTQ * 0.25) + { + MessageViolation mv = RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Acc. to EU Regulation 2022/89 the amount retained on board is noticeably", null, "Waste", waste.Identifier, was.Tablename); + mv.MessageGroupName = wasMessageGroup; + vViolations.Add(mv); + } + break; + } + } + } + } + } + #endregion - foreach (MessageError me in vErrors) - { - this.highlightService.HighlightError(me, this.GetContainerForMessageGroupName(me.MessageGroupName)); - } - foreach (MessageViolation mv in vViolations) - { - this.highlightService.HighlightViolation(mv, this.GetContainerForMessageGroupName(mv.MessageGroupName)); - } + #endregion // "neue" regelbasierte Validierung: Hier werden die einzelnen Regeln geprüft. - bsmd.database.ValidationRule.PrepareNameLookupDict(this.Core, this._messages); + bsmd.database.ValidationRule.PrepareNameLookupDict(this.Core, messagesSnapshot); List validationRules = DBManager.Instance.GetValidationRules(); @@ -1251,10 +1384,94 @@ namespace ENI2 } } + + // Recompute per-message counts after all global validations + Dictionary counts = new Dictionary(); + + int assignViolationCounter = 0; + int assignErrorCounter = 0; + + List tmpVList = new List(vViolations); + List tmpEList = new List(vErrors); + + foreach (Message aMessage in messagesSnapshot) + { + counts[aMessage] = new MessageValidationCounts(); + counts[aMessage].ErrorCount = tmpEList.Count(me => me.NotificationClass == aMessage.MessageNotificationClassDisplay); // generic + // special cases + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.WAS_RCPT) && (me.NotificationClass == "WasteReceived")); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IMDGPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IGCPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IBCPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IMSBCPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "MARPOL_Annex_I_Position") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IMDGPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IGCPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IBCPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IMSBCPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "MARPOL_Annex_I_Position") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.WAS) && (me.NotificationClass == "Waste")); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.SEC) && (me.NotificationClass == "LastTenPortFacilitiesCalled")); + counts[aMessage].ErrorCount += tmpEList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.SEC) && (me.NotificationClass == "ShipToShipActivitiesDuringLastTenPortFacilitiesCalled")); + + if (counts[aMessage].ErrorCount == 0) counts[aMessage].ErrorCount = null; // set to empty if nothing counted + assignErrorCounter += counts[aMessage].ErrorCount ?? 0; + + counts[aMessage].ViolationCount = vViolations.Count(mv => mv.NotificationClass == aMessage.MessageNotificationClassDisplay); // generic + // special cases + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.WAS_RCPT) && (me.NotificationClass == "WasteReceived")); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IMDGPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IGCPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IBCPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "IMSBCPosition") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZA) && (me.NotificationClass == "MARPOL_Annex_I_Position") && me.MessageGroupName.Contains("arrival", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IMDGPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IGCPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IBCPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "IMSBCPosition") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.HAZD) && (me.NotificationClass == "MARPOL_Annex_I_Position") && me.MessageGroupName.Contains("departure", StringComparison.OrdinalIgnoreCase)); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.WAS) && (me.NotificationClass == "Waste")); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.SEC) && (me.NotificationClass == "LastTenPortFacilitiesCalled")); + counts[aMessage].ViolationCount += tmpVList.Count(me => (aMessage.MessageNotificationClass == Message.NotificationClass.SEC) && (me.NotificationClass == "ShipToShipActivitiesDuringLastTenPortFacilitiesCalled")); + + if (counts[aMessage].ViolationCount == 0) counts[aMessage].ViolationCount = null; + + assignViolationCounter += counts[aMessage].ViolationCount ?? 0; + } + + + Trace.WriteLine(string.Format("Unassigned errors: {0}, unassigned violations: {1}", vErrors.Count - assignErrorCounter, vViolations.Count - assignViolationCounter)); + + + return new ValidationResult(vViolations, vErrors, counts); + } + + + + private void ApplyValidationResult(ValidationResult result, bool showMessages) + { + // TODO: clear highlighting + foreach (KeyValuePair entry in result.Counts) + { + Message aMessage = entry.Key; + MessageValidationCounts messageCounts = entry.Value; + aMessage.ErrorCount = messageCounts.ErrorCount; + aMessage.ViolationCount = messageCounts.ViolationCount; + } + + foreach (MessageError me in result.Errors) + { + this.highlightService.HighlightError(me, this.GetContainerForMessageGroupName(me.MessageGroupName)); + } + foreach (MessageViolation mv in result.Violations) + { + this.highlightService.HighlightViolation(mv, this.GetContainerForMessageGroupName(mv.MessageGroupName)); + } + if (showMessages) { // Show error and violation dialog - if (vErrors.Count > 0) + if (result.Errors.Count > 0) { if(this._errorListDialog == null) { @@ -1269,10 +1486,10 @@ namespace ENI2 { this._errorListDialog.BringUp(); } - this._errorListDialog.Errors = vErrors; + this._errorListDialog.Errors = result.Errors; } - if (vViolations.Count > 0) + if (result.Violations.Count > 0) { if(this._violationListDialog == null) { @@ -1287,15 +1504,62 @@ namespace ENI2 { this._violationListDialog.BringUp(); } - _violationListDialog.Violations = vViolations; - } + _violationListDialog.Violations = result.Violations; + } - if((vErrors.Count == 0) && (vViolations.Count == 0)) + if((result.Errors.Count == 0) && (result.Violations.Count == 0)) { MessageBox.Show(Properties.Resources.textValidationOK, Properties.Resources.textValidation, MessageBoxButton.OK, MessageBoxImage.Information); } - } + + if (controlCache.TryGetValue(Properties.Resources.textOverview, out DetailBaseControl overviewControl) && + overviewControl is OverViewDetailControl ovdc) + { + ovdc.RefreshMessageGrid(); + } + } + + private void ApplyCrewEffectsWarningSuppression() + { + // 4.1.23 no CREW effects warning for DE + Message crewaMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.CREWA); + Message crewdMessage = _messages.Find(message => message.MessageNotificationClass == Message.NotificationClass.CREWD); + + if (crewaMessage != null) + { + MessageViolation mv = crewaMessage.ViolationList.Find((x) => x.PropertyName.Equals("Effects") && (x.ViolationCode == (int)ValidationCode.TRUNCATE)); + if ((mv != null) && !Core.IsDK) + crewaMessage.ViolationList.Remove(mv); + } + + if(crewdMessage != null) + { + MessageViolation mvd = crewdMessage.ViolationList.Find((x) => x.PropertyName.Equals("Effects") && (x.ViolationCode == (int)ValidationCode.TRUNCATE)); + if ((mvd != null) && !Core.IsDK) + crewdMessage.ViolationList.Remove(mvd); + } + } + + private sealed class ValidationResult + { + internal ValidationResult(List violations, List errors, Dictionary counts) + { + Violations = violations; + Errors = errors; + Counts = counts; + } + + internal List Violations { get; } + internal List Errors { get; } + internal Dictionary Counts { get; } + } + + private sealed class MessageValidationCounts + { + internal int? ErrorCount { get; set; } + internal int? ViolationCount { get; set; } + internal int? PositionViolationCount { get; set; } } private void _errorListDialog_RefreshClicked() diff --git a/ENI2/DetailViewControls/BorderPoliceDetailControl.xaml.cs b/ENI2/DetailViewControls/BorderPoliceDetailControl.xaml.cs index 91608b6f..0cb64f10 100644 --- a/ENI2/DetailViewControls/BorderPoliceDetailControl.xaml.cs +++ b/ENI2/DetailViewControls/BorderPoliceDetailControl.xaml.cs @@ -1051,7 +1051,8 @@ namespace ENI2.DetailViewControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); // Get first worksheet var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present @@ -1121,7 +1122,8 @@ namespace ENI2.DetailViewControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); // Get first worksheet var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present @@ -1192,7 +1194,8 @@ namespace ENI2.DetailViewControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present @@ -1268,7 +1271,8 @@ namespace ENI2.DetailViewControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present diff --git a/ENI2/DetailViewControls/OverViewDetailControl.xaml.cs b/ENI2/DetailViewControls/OverViewDetailControl.xaml.cs index 6a850f5f..98f3422f 100644 --- a/ENI2/DetailViewControls/OverViewDetailControl.xaml.cs +++ b/ENI2/DetailViewControls/OverViewDetailControl.xaml.cs @@ -370,6 +370,11 @@ namespace ENI2.DetailViewControls this.Core.IsDirty = false; // ist ja schon gespeichert.. } + internal void RefreshMessageGrid() + { + this.dataGridMessages?.Items?.Refresh(); + } + #endregion #region private methods diff --git a/ENI2/DetailViewControls/WasteDetailControl.xaml.cs b/ENI2/DetailViewControls/WasteDetailControl.xaml.cs index d3fe3ea7..2a8d9c60 100644 --- a/ENI2/DetailViewControls/WasteDetailControl.xaml.cs +++ b/ENI2/DetailViewControls/WasteDetailControl.xaml.cs @@ -5,6 +5,7 @@ using bsmd.database; using ClosedXML.Excel; using ENI2.EditControls; +using ENI2.Excel; using ENI2.Util; using Microsoft.Win32; using System; @@ -479,87 +480,14 @@ namespace ENI2.DetailViewControls private void buttonImportFromExcel_Click(object sender, RoutedEventArgs e) { - OpenFileDialog ofd = new OpenFileDialog(); - ofd.Filter = "Excel Files|*.xls;*.xlsx"; - if (ofd.ShowDialog() ?? false) + int importWasteListCnt = ExcelLocalImportHelper.ImportWaste(_was); + + if (importWasteListCnt > 0) { - try - { - using (var workbook = new XLWorkbook(ofd.FileName)) - { - var worksheet = workbook.Worksheet(1); // Get first worksheet - var rows = worksheet.RangeUsed().RowsUsed().Skip(3); // Skip first three rows - - List importWasteList = new List(); - - int cnt = 0; - object o = null; - - // Diese Funktion kann das "alte" Sheet Format nicht mehr einlesen! - - foreach (var row in rows) - { - if (cnt >= 35) break; // Maximum 35 rows - - if (worksheet.RangeUsed().ColumnCount() < 9) - { - throw new InvalidDataException("Sheet must have 9 Columns of data"); - } - - if (!row.Cell(2).IsEmpty()) o = row.Cell(2).Value; else o = null; - if ((o != null) && Int32.TryParse(o.ToString(), out int wasteType)) - { - Waste waste = _was.GetWasteForType(wasteType); - if (waste == null) - { - waste = new Waste(); - waste.WasteType = wasteType; - waste.WAS = this._was; - waste.IsDirty = true; - waste.Identifier = Waste.GetNewIdentifier(this._was.Waste); - this._was.Waste.Add(waste); - } - else - { - waste.IsDirty = true; - } - - if (!row.Cell(5).IsEmpty()) waste.WasteDescription = row.Cell(5).GetString(); - if (waste.WasteDescription.IsNullOrEmpty()) - waste.WasteDescription = "-"; - - if (!row.Cell(6).IsEmpty()) o = row.Cell(6).Value; else o = null; - if (o != null) waste.WasteDisposalAmount_MTQ = Convert.ToDouble(o); - - if (!row.Cell(7).IsEmpty()) o = row.Cell(7).Value; else o = null; - if (o != null) waste.WasteCapacity_MTQ = Convert.ToDouble(o); - - if (!row.Cell(8).IsEmpty()) o = row.Cell(8).Value; else o = null; - if (o != null) waste.WasteAmountRetained_MTQ = Convert.ToDouble(o); - - if (!row.Cell(9).IsEmpty()) waste.WasteDisposalPort = row.Cell(9).GetString().ToUpper(); - - if (!row.Cell(10).IsEmpty()) o = row.Cell(10).Value; else o = null; - if (o != null) waste.WasteAmountGeneratedTillNextPort_MTQ = Convert.ToDouble(o); - - importWasteList.Add(waste); - cnt++; - } - } - - if (importWasteList.Count > 0) - { - this.dataGridWaste.Items.Refresh(); - this.SublistElementChanged(Message.NotificationClass.WAS); - MessageBox.Show(String.Format(Properties.Resources.textWasteImported, importWasteList.Count), Properties.Resources.textCaptionInformation, MessageBoxButton.OK, MessageBoxImage.Information); - } - } - } - catch (Exception ex) - { - MessageBox.Show("Error reading Excel: " + ex.Message, Properties.Resources.textCaptionError, MessageBoxButton.OK, MessageBoxImage.Error); - } - } + this.dataGridWaste.Items.Refresh(); + this.SublistElementChanged(Message.NotificationClass.WAS); + MessageBox.Show(String.Format(Properties.Resources.textWasteImported, importWasteListCnt), Properties.Resources.textCaptionInformation, MessageBoxButton.OK, MessageBoxImage.Information); + } } diff --git a/ENI2/ENI2.csproj b/ENI2/ENI2.csproj index cd12f97a..32b7a81b 100644 --- a/ENI2/ENI2.csproj +++ b/ENI2/ENI2.csproj @@ -37,7 +37,7 @@ true publish.html 2 - 7.2.13.2 + 7.2.14.2 false true true diff --git a/ENI2/Excel/ExcelBase.cs b/ENI2/Excel/ExcelBase.cs index 7c54a00d..b19dea3a 100644 --- a/ENI2/Excel/ExcelBase.cs +++ b/ENI2/Excel/ExcelBase.cs @@ -9,6 +9,7 @@ using System.Globalization; using System.Text.RegularExpressions; using System.Drawing; using System.Linq; +using System.IO; namespace ENI2.Excel { @@ -22,6 +23,7 @@ namespace ENI2.Excel protected CountryMode _countryMode = CountryMode.DE; protected XLWorkbook _workBook; + protected FileStream _workBookStream; protected Dictionary _nameDict; protected ILog _log; @@ -101,6 +103,12 @@ namespace ENI2.Excel } } + protected void OpenWorkbookReadOnly(string path) + { + _workBookStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + _workBook = new XLWorkbook(_workBookStream); + } + private static double? ParseAnyDouble(string val) { double? result = null; @@ -212,6 +220,11 @@ namespace ENI2.Excel _workBook.Dispose(); _workBook = null; } + if (_workBookStream != null) + { + _workBookStream.Dispose(); + _workBookStream = null; + } } catch (Exception ex) { @@ -222,4 +235,4 @@ namespace ENI2.Excel #endregion } -} \ No newline at end of file +} diff --git a/ENI2/Excel/ExcelComparer.cs b/ENI2/Excel/ExcelComparer.cs index 6db1ae7e..f13d5d04 100644 --- a/ENI2/Excel/ExcelComparer.cs +++ b/ENI2/Excel/ExcelComparer.cs @@ -53,7 +53,8 @@ namespace ENI2.Excel { File.Copy(targetPath, comparisonFileName, true); - using (var sourceWorkbook = new XLWorkbook(sourcePath)) + using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var sourceWorkbook = new XLWorkbook(sourceStream)) using (var comparisonWorkbook = new XLWorkbook(comparisonFileName)) { // Es werden Zellen der "used range" miteinander verglichen @@ -76,6 +77,7 @@ namespace ENI2.Excel { int maxRows = Math.Max(sourceRows, targetRows); int maxCols = Math.Max(sourceCols, targetCols); + bool sheetHasDiffs = false; for (int rowidx = 1; rowidx <= maxRows; rowidx++) { @@ -111,6 +113,7 @@ namespace ENI2.Excel var cellToHighlight = targetSheet.Cell(rowidx, colidx); cellToHighlight.Style.Fill.BackgroundColor = diffColor; counter++; + sheetHasDiffs = true; } } else if (targetText == null) @@ -120,6 +123,7 @@ namespace ENI2.Excel var cellToHighlight = targetSheet.Cell(rowidx, colidx); cellToHighlight.Style.Fill.BackgroundColor = diffColor; counter++; + sheetHasDiffs = true; } } else if ((sourceText != null) && (targetText != null)) @@ -129,10 +133,16 @@ namespace ENI2.Excel var cellToHighlight = targetSheet.Cell(rowidx, colidx); cellToHighlight.Style.Fill.BackgroundColor = diffColor; counter++; + sheetHasDiffs = true; } } } } + + if (sheetHasDiffs) + { + targetSheet.SetTabColor(diffColor); + } } else { @@ -152,4 +162,4 @@ namespace ENI2.Excel return comparisonFileName; } } -} \ No newline at end of file +} diff --git a/ENI2/Excel/ExcelLocalImportHelper.cs b/ENI2/Excel/ExcelLocalImportHelper.cs index 84545948..88dac012 100644 --- a/ENI2/Excel/ExcelLocalImportHelper.cs +++ b/ENI2/Excel/ExcelLocalImportHelper.cs @@ -4,6 +4,7 @@ using Microsoft.Win32; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Windows; namespace ENI2.Excel @@ -26,7 +27,8 @@ namespace ENI2.Excel { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); var rows = worksheet.RangeUsed().RowsUsed(); @@ -40,7 +42,7 @@ namespace ENI2.Excel List importL10C = new List(); - + foreach (var row in rows) { if (cnt >= 10) break; // Maximum 10 rows @@ -114,7 +116,7 @@ namespace ENI2.Excel { throw new InvalidDataException($"Error processing row {row.RowNumber()}: {ex.Message}", ex); } - } + } } } catch (Exception ex) @@ -144,7 +146,8 @@ namespace ENI2.Excel { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); var rows = worksheet.RangeUsed().RowsUsed(); @@ -227,7 +230,7 @@ namespace ENI2.Excel poc30.MDH = mdh; mdh.PortOfCallLast30Days.Add(poc30); importPoC30.Add(poc30); - } + } } } catch (Exception ex) @@ -242,5 +245,89 @@ namespace ENI2.Excel #endregion + #region Waste from Excel + + public static int ImportWaste(WAS was) + { + int cnt = 0; + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Filter = "Excel Files|*.xls;*.xlsx"; + if (ofd.ShowDialog() ?? false) + { + try + { + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) + { + var worksheet = workbook.Worksheet(1); // Get first worksheet + var rows = worksheet.RangeUsed().RowsUsed().Skip(3); // Skip first three rows + + List importWasteList = new List(); + + object o = null; + + // Diese Funktion kann das "alte" Sheet Format nicht mehr einlesen! + + foreach (var row in rows) + { + if (cnt >= 35) break; // Maximum 35 rows + + if (worksheet.RangeUsed().ColumnCount() < 9) + { + throw new InvalidDataException("Sheet must have 9 Columns of data"); + } + + if (!row.Cell(2).IsEmpty()) o = row.Cell(2).Value; else o = null; + if ((o != null) && Int32.TryParse(o.ToString(), out int wasteType)) + { + Waste waste = was.GetWasteForType(wasteType); + if (waste == null) + { + waste = new Waste(); + waste.WasteType = wasteType; + waste.WAS = was; + waste.IsDirty = true; + waste.Identifier = Waste.GetNewIdentifier(was.Waste); + was.Waste.Add(waste); + } + else + { + waste.IsDirty = true; + } + + if (!row.Cell(5).IsEmpty()) waste.WasteDescription = row.Cell(5).GetString(); + if (waste.WasteDescription.IsNullOrEmpty()) + waste.WasteDescription = "-"; + + if (!row.Cell(6).IsEmpty()) + waste.WasteDisposalAmount_MTQ = row.Cell(6).GetDouble(); + + if (!row.Cell(7).IsEmpty()) + waste.WasteCapacity_MTQ = row.Cell(7).GetDouble(); + + if (!row.Cell(8).IsEmpty()) waste.WasteAmountRetained_MTQ = row.Cell(8).GetDouble(); + + if (!row.Cell(9).IsEmpty()) + waste.WasteDisposalPort = row.Cell(9).GetString().ToUpper(); + + if (!row.Cell(10).IsEmpty()) + waste.WasteAmountGeneratedTillNextPort_MTQ = row.Cell(10).GetDouble(); + + importWasteList.Add(waste); + cnt++; + } + } + } + } + catch (Exception ex) + { + MessageBox.Show("Error reading Excel: " + ex.Message, Properties.Resources.textCaptionError, MessageBoxButton.OK, MessageBoxImage.Error); + } + } + return cnt; + } + + #endregion + } } diff --git a/ENI2/Excel/ExcelReader.cs b/ENI2/Excel/ExcelReader.cs index 8a5c8edf..941ac9c4 100644 --- a/ENI2/Excel/ExcelReader.cs +++ b/ENI2/Excel/ExcelReader.cs @@ -32,7 +32,7 @@ namespace ENI2.Excel public ExcelReader(string filePath, bool createNameFields = true) { - this._workBook = new XLWorkbook(filePath); + this.OpenWorkbookReadOnly(filePath); if (createNameFields) this.InitNameFields(); @@ -793,4 +793,4 @@ namespace ENI2.Excel #endregion Dakosy-specific functions } -} \ No newline at end of file +} diff --git a/ENI2/Excel/ExcelWriter.cs b/ENI2/Excel/ExcelWriter.cs index de1c3ab7..34db3b88 100644 --- a/ENI2/Excel/ExcelWriter.cs +++ b/ENI2/Excel/ExcelWriter.cs @@ -26,7 +26,7 @@ namespace ENI2.Excel if (isRefSheet) filename = @"Excel\Reference_Sheet_DE.xlsx"; string refFilePath = System.IO.Path.Combine(Environment.CurrentDirectory, filename); - this._workBook = new XLWorkbook(refFilePath); + this.OpenWorkbookReadOnly(refFilePath); this.InitNameFields(); } @@ -1426,4 +1426,4 @@ namespace ENI2.Excel #endregion } -} \ No newline at end of file +} diff --git a/ENI2/SheetDisplayControls/CrewDepartureControl.xaml.cs b/ENI2/SheetDisplayControls/CrewDepartureControl.xaml.cs index 0f186fbd..c3d9e323 100644 --- a/ENI2/SheetDisplayControls/CrewDepartureControl.xaml.cs +++ b/ENI2/SheetDisplayControls/CrewDepartureControl.xaml.cs @@ -128,7 +128,8 @@ namespace ENI2.SheetDisplayControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); // Get first worksheet var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present diff --git a/ENI2/SheetDisplayControls/CrewPreArrivalControl.xaml.cs b/ENI2/SheetDisplayControls/CrewPreArrivalControl.xaml.cs index 0bf4e98f..57d89f44 100644 --- a/ENI2/SheetDisplayControls/CrewPreArrivalControl.xaml.cs +++ b/ENI2/SheetDisplayControls/CrewPreArrivalControl.xaml.cs @@ -168,7 +168,8 @@ namespace ENI2.SheetDisplayControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); // Get first worksheet var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present diff --git a/ENI2/SheetDisplayControls/PassengerDepartureControl.xaml.cs b/ENI2/SheetDisplayControls/PassengerDepartureControl.xaml.cs index 8e93f552..5abb1965 100644 --- a/ENI2/SheetDisplayControls/PassengerDepartureControl.xaml.cs +++ b/ENI2/SheetDisplayControls/PassengerDepartureControl.xaml.cs @@ -146,7 +146,8 @@ namespace ENI2.SheetDisplayControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); // Get first worksheet var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present diff --git a/ENI2/SheetDisplayControls/PassengerPreArrivalControl.xaml.cs b/ENI2/SheetDisplayControls/PassengerPreArrivalControl.xaml.cs index 8beaa753..8bbb5361 100644 --- a/ENI2/SheetDisplayControls/PassengerPreArrivalControl.xaml.cs +++ b/ENI2/SheetDisplayControls/PassengerPreArrivalControl.xaml.cs @@ -128,7 +128,8 @@ namespace ENI2.SheetDisplayControls { try { - using (var workbook = new XLWorkbook(ofd.FileName)) + using (var stream = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var workbook = new XLWorkbook(stream)) { var worksheet = workbook.Worksheet(1); // Get first worksheet var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row if present diff --git a/ENI2/SheetDisplayControls/PortControl.xaml.cs b/ENI2/SheetDisplayControls/PortControl.xaml.cs index 988fca9e..f061d0c6 100644 --- a/ENI2/SheetDisplayControls/PortControl.xaml.cs +++ b/ENI2/SheetDisplayControls/PortControl.xaml.cs @@ -5,6 +5,7 @@ using bsmd.database; using ClosedXML.Excel; using ENI2.EditControls; +using ENI2.Excel; using Microsoft.Win32; using System; @@ -514,88 +515,14 @@ namespace ENI2.SheetDisplayControls private void buttonImportFromExcel_Click(object sender, RoutedEventArgs e) { - OpenFileDialog ofd = new OpenFileDialog(); - ofd.Filter = "Excel Files|*.xls;*.xlsx"; - if (ofd.ShowDialog() ?? false) + int importWasteListCnt = ExcelLocalImportHelper.ImportWaste(_was); + + if (importWasteListCnt > 0) { - try - { - using (var workbook = new XLWorkbook(ofd.FileName)) - { - var worksheet = workbook.Worksheet(1); // Get first worksheet - var rows = worksheet.RangeUsed().RowsUsed().Skip(3); // Skip first three rows - - List importWasteList = new List(); - - int cnt = 0; - - // Diese Funktion kann das "alte" Sheet Format nicht mehr einlesen! - - foreach (var row in rows) - { - if (cnt >= 35) break; // Maximum 35 rows - - if (worksheet.RangeUsed().ColumnCount() < 9) - { - throw new InvalidDataException("Sheet must have 9 Columns of data"); - } - - object o = null; - - if (!row.Cell(2).IsEmpty()) o = row.Cell(2).Value; else o = null; - if ((o != null) && Int32.TryParse(o.ToString(), out int wasteType)) - { - Waste waste = _was.GetWasteForType(wasteType); - if (waste == null) - { - waste = new Waste(); - waste.WasteType = wasteType; - waste.WAS = this._was; - waste.IsDirty = true; - waste.Identifier = Waste.GetNewIdentifier(this._was.Waste); - this._was.Waste.Add(waste); - } - else - { - waste.IsDirty = true; - } - - if (!row.Cell(5).IsEmpty()) waste.WasteDescription = row.Cell(5).GetString(); - if (waste.WasteDescription.IsNullOrEmpty()) - waste.WasteDescription = "-"; - - if (!row.Cell(6).IsEmpty()) o = row.Cell(6).Value; else o = null; - if (o != null) waste.WasteDisposalAmount_MTQ = Convert.ToDouble(o); - - if (!row.Cell(7).IsEmpty()) o = row.Cell(7).Value; else o = null; - if (o != null) waste.WasteCapacity_MTQ = Convert.ToDouble(o); - - if (!row.Cell(8).IsEmpty()) o = row.Cell(8).Value; else o = null; - if (o != null) waste.WasteAmountRetained_MTQ = Convert.ToDouble(o); - - if (!row.Cell(9).IsEmpty()) waste.WasteDisposalPort = row.Cell(9).GetString().ToUpper(); - - if (!row.Cell(10).IsEmpty()) o = row.Cell(10).Value; else o = null; - if (o != null) waste.WasteAmountGeneratedTillNextPort_MTQ = Convert.ToDouble(o); - - importWasteList.Add(waste); - cnt++; - } - } - - if (importWasteList.Count > 0) - { - this.dataGridWaste.Items.Refresh(); - this.SublistElementChanged(Message.NotificationClass.WAS); - MessageBox.Show(String.Format(Properties.Resources.textWasteImported, importWasteList.Count), Properties.Resources.textCaptionInformation, MessageBoxButton.OK, MessageBoxImage.Information); - } - } - } - catch (Exception ex) - { - MessageBox.Show("Error reading Excel: " + ex.Message, Properties.Resources.textCaptionError, MessageBoxButton.OK, MessageBoxImage.Error); - } - } + this.dataGridWaste.Items.Refresh(); + this.SublistElementChanged(Message.NotificationClass.WAS); + MessageBox.Show(String.Format(Properties.Resources.textWasteImported, importWasteListCnt), Properties.Resources.textCaptionInformation, MessageBoxButton.OK, MessageBoxImage.Information); + } } #endregion diff --git a/bsmd.ExcelReadService/ExcelReader.cs b/bsmd.ExcelReadService/ExcelReader.cs index f344d063..b7f68eb2 100644 --- a/bsmd.ExcelReadService/ExcelReader.cs +++ b/bsmd.ExcelReadService/ExcelReader.cs @@ -36,7 +36,23 @@ namespace bsmd.ExcelReadService this._excelApp.DisplayAlerts = false; this._excelWorkbooks = _excelApp.Workbooks; - this._portcall = _excelWorkbooks.Open(filePath, 0, true, 5, "", "", false, XlPlatform.xlWindows, "", false, false, 0, false, false, false); + this._portcall = _excelWorkbooks.Open( + filePath, + UpdateLinks: 0, + ReadOnly: true, + Format: 5, + Password: "", + WriteResPassword: "", + IgnoreReadOnlyRecommended: true, + Origin: XlPlatform.xlWindows, + Delimiter: "", + Editable: false, + Notify: false, + Converter: 0, + AddToMru: false, + Local: false, + CorruptLoad: false); + this._portcall.ChangeFileAccess(XlFileAccess.xlReadOnly); _nameDict = new Dictionary(); int bookCnt = 0; foreach(Name name in _portcall.Names) diff --git a/bsmd.database/Message.cs b/bsmd.database/Message.cs index 8f32a139..c95fb682 100644 --- a/bsmd.database/Message.cs +++ b/bsmd.database/Message.cs @@ -431,11 +431,6 @@ namespace bsmd.database /// public int? ViolationCount { get; set; } - /// - /// Number of violations during last validation that have the "identifier" set and are thus detected violations - /// from underlying list elements - /// - public int? PositionViolationCount { get; set; } /// /// Number of errors during last validation diff --git a/bsmd.database/Properties/AssemblyProductInfo.cs b/bsmd.database/Properties/AssemblyProductInfo.cs index 19b3f3d5..c1cb50b2 100644 --- a/bsmd.database/Properties/AssemblyProductInfo.cs +++ b/bsmd.database/Properties/AssemblyProductInfo.cs @@ -2,6 +2,6 @@ [assembly: AssemblyCompany("schick Informatik")] [assembly: AssemblyProduct("BSMD NSW interface")] -[assembly: AssemblyInformationalVersion("7.2.13")] +[assembly: AssemblyInformationalVersion("7.2.14")] [assembly: AssemblyCopyright("Copyright © 2014-2025 schick Informatik")] [assembly: AssemblyTrademark("")] \ No newline at end of file diff --git a/bsmd.database/Properties/AssemblyProjectInfo.cs b/bsmd.database/Properties/AssemblyProjectInfo.cs index 9a544667..16239876 100644 --- a/bsmd.database/Properties/AssemblyProjectInfo.cs +++ b/bsmd.database/Properties/AssemblyProjectInfo.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("7.2.13.*")] +[assembly: AssemblyVersion("7.2.14.*")] diff --git a/bsmd.database/RuleEngine.cs b/bsmd.database/RuleEngine.cs index f6c682d6..082712c4 100644 --- a/bsmd.database/RuleEngine.cs +++ b/bsmd.database/RuleEngine.cs @@ -97,6 +97,22 @@ namespace bsmd.database #endregion + #region for extra waste validation + + private static readonly HashSet SpecialNextPortPrefixes = new HashSet + { + "BE", "BG", "DK", "DE", "EE", "FI", "FR", "GR", "IR", "IT", + "HR", "LV", "LT", "LU", "MT", "NL", "AT", "PL", "PT", "RO", + "SE", "SK", "SL", "ES", "CZ", "HU", "CY", "NO", "IS", "GB", "GI" + }; + + private static readonly HashSet SpecialNextPortExact = new HashSet + { + "RUPRI", "RUULU", "RULED", "RUBLT", "RUKGD", "RUVYS" + }; + + #endregion + public enum LocodeMode { STANDARD, @@ -131,6 +147,20 @@ namespace bsmd.database public static LocodeValidHandler LocodeChecker { get { return _locodeChecker; } } public static NationalityValidHandler NationalityChecker { get { return _nationalityChecker; } } + /// + /// Used to test for special next port values for waste messages + /// + /// a locode + /// true if the next port is a special waste port + public static bool IsSpecialNextPort(string nextPort) + { + if (string.IsNullOrEmpty(nextPort) || nextPort.Length < 2) + return false; + + return SpecialNextPortExact.Contains(nextPort) || + SpecialNextPortPrefixes.Contains(nextPort.Substring(0, 2)); + } + #region public static property validation public static void RegisterLocodeChecker(LocodeValidHandler handler) { _locodeChecker = handler; } diff --git a/bsmd.database/Waste.cs b/bsmd.database/Waste.cs index feffc008..42e2b6f2 100644 --- a/bsmd.database/Waste.cs +++ b/bsmd.database/Waste.cs @@ -266,11 +266,13 @@ namespace bsmd.database if (this.WasteAmountGeneratedTillNextPort_MTQ >= 10000) errors.Add(RuleEngine.CreateError(ValidationCode.IMPLAUSIBLE, "Waste generated till next port too high", null, this.Title, this.Identifier, this.Tablename)); - if(this.WasteDisposalAmount_MTQ > this.WasteCapacity_MTQ) - violations.Add(RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Disposal greater than capacity!", null, this.Title, this.Identifier, this.Tablename)); + // Removed this for version 7.2.14 (new rules depending on next port implemented in ENI / DetailRootControl.cs) - if((this.WasteAmountGeneratedTillNextPort_MTQ + this.WasteAmountRetained_MTQ) > this.WasteCapacity_MTQ) - violations.Add(RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Waste generated+retained greater than capacity!", null, this.Title, this.Identifier, this.Tablename)); + // if(this.WasteDisposalAmount_MTQ > this.WasteCapacity_MTQ) + // violations.Add(RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Disposal greater than capacity!", null, this.Title, this.Identifier, this.Tablename)); + + // if((this.WasteAmountGeneratedTillNextPort_MTQ + this.WasteAmountRetained_MTQ) > this.WasteCapacity_MTQ) + // violations.Add(RuleEngine.CreateViolation(ValidationCode.IMPLAUSIBLE, "Waste generated+retained greater than capacity!", null, this.Title, this.Identifier, this.Tablename)); } #endregion diff --git a/bsmd.database/WasteReceived.cs b/bsmd.database/WasteReceived.cs index e7d6dd40..1b4d8b14 100644 --- a/bsmd.database/WasteReceived.cs +++ b/bsmd.database/WasteReceived.cs @@ -76,7 +76,7 @@ namespace bsmd.database wasteReceived.id = reader.GetGuid(0); if (!reader.IsDBNull(1)) wasteReceived.WasteCode = reader.GetString(1); if (!reader.IsDBNull(2)) wasteReceived.WasteDescription = reader.GetString(2); - if (!reader.IsDBNull(3)) wasteReceived.AmountWasteReceived_MTQ = (float)reader.GetDouble(3); + if (!reader.IsDBNull(3)) wasteReceived.AmountWasteReceived_MTQ = (float)reader.GetDouble(3); if (!reader.IsDBNull(4)) wasteReceived.Identifier = reader.GetString(4); result.Add(wasteReceived); } @@ -111,7 +111,7 @@ namespace bsmd.database scmd.Parameters.AddWithValue("@P1", this.WAS_RCPT.Id); scmd.Parameters.AddWithNullableValue("@P2", this.WasteCode); scmd.Parameters.AddWithNullableValue("@P3", this.WasteDescription); - scmd.Parameters.AddWithNullableValue("@P4", this.AmountWasteReceived_MTQ); + scmd.Parameters.AddWithNullableValue("@P4", this.AmountWasteReceived_MTQ); scmd.Parameters.AddWithNullableValue("@P5", this.Identifier); if (this.IsNew) diff --git a/misc/WAS-Regulation.docx b/misc/WAS-Regulation.docx new file mode 100644 index 00000000..9f016a00 Binary files /dev/null and b/misc/WAS-Regulation.docx differ