using System; using System.Collections.Generic; using System.Linq; using Centron.BusinessLogic; using Centron.BusinessLogic.Administration.Settings; using Centron.BusinessLogic.Sales.CustomerAssets.Contracts; using Centron.BusinessLogic.Sales.Receipts; using Centron.BusinessLogic.Sales.Receipts.DataAndResults; using Centron.BusinessLogic.WebServices.Sales.Receipts; using Centron.Data.Entities.Sales.Receipts.ContractLists; using Centron.Interfaces; using Centron.Interfaces.Administration.Environment; using Centron.Interfaces.Administration.Settings; using Centron.Interfaces.BL; using Centron.Interfaces.Sales.BillingCenter.Contracts; using Centron.Interfaces.Sales.Receipts; using Centron.Tests.EndToEnd.Infrastructure; using CentronSoftware.Centron.WebServices.Entities.Sales.Receipts.ContractLists; using Xunit.Abstractions; namespace Centron.Tests.EndToEnd.Tests.Contracts { /// /// End-to-end tests for the ContractBL.CloseContract() method. /// Tests the automatic contract closure functionality based on termination/end dates. /// public class ContractCloseTests : EndToEndTest { // Test data constants private const int _customerI3D = 10022; private const int _testUserI3D = 11; private const int _contractKindI3D = 12; // Fixed reference date for all tests to ensure deterministic behavior private readonly DateTime _referenceDate = new DateTime(2025, 5, 1); // May 1st, 2025 public ContractCloseTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { // Set to true only when you need to update expected files //Verifier.OverrideExpectedFiles = true; // Remove this line after updating expected files } public override void Execute() { TestAutomaticClosureDisabled(new BLSession()); TestAutomatedProlongationContract_ShouldNotClose_WhenTerminationEqualsLastBookingTo(new BLSession()); TestAutomatedProlongationContract_ShouldClose_WhenTerminationExceeded(new BLSession()); TestNormalContract_ShouldNotClose_WhenEndDateEqualsLastBookingTo(new BLSession()); TestNormalContract_ShouldClose_WhenEndDateExceeded(new BLSession()); TestContract_ShouldNotClose_WhenNoTerminationOrEndDate(new BLSession()); TestContract_ShouldNotClose_WhenManualCalculation(new BLSession()); TestBugScenario_ContractBilledOnTerminationDate_ShouldNotCloseUntilNextDay(new BLSession()); } /// /// Test that no contracts are closed when automatic closure is disabled /// private void TestAutomaticClosureDisabled(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Disable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, false); setting.SaveSettings(); // Create a contract that would normally be closed (termination 10 days before reference date) var expiredContract = CreateTestContract(session, contractTermination: _referenceDate.AddDays(-10), lastBookingTo: _referenceDate.AddDays(-10), automatedProlongation: true); var contractsBefore = GetActiveContracts(session); // Act - pass reference date to CloseContract contractBL.CloseContract(_referenceDate); // Assert var contractsAfter = GetActiveContracts(session); Verifier.Verify("AutomaticClosure_Disabled_NoContractsClosed", new { ContractsBeforeCount = contractsBefore.Count(), ContractsAfterCount = contractsAfter.Count(), TestContractStillActive = contractsAfter.Any(c => c.I3D == expiredContract.I3D) }); // Cleanup CleanupTestContract(session, expiredContract.I3D); } /// /// Test that automated prolongation contracts should NOT close when termination equals LastBookingTo /// but the termination date has not been reached yet (future date) /// This tests the specific bug fix where contracts were closing prematurely /// private void TestAutomatedProlongationContract_ShouldNotClose_WhenTerminationEqualsLastBookingTo(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Enable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, true); setting.SaveSettings(); // Termination date is in the future (30 days after reference date) var terminationDate = _referenceDate.AddDays(30); var testContract = CreateTestContract(session, contractTermination: terminationDate, lastBookingTo: terminationDate, // Fully billed, but termination date not reached yet automatedProlongation: true); // Act - pass reference date to CloseContract contractBL.CloseContract(_referenceDate); // Assert var contractAfter = GetContractById(session, testContract.I3D); Verifier.Verify("AutomatedProlongation_NotClosed_WhenTerminationEqualsLastBookingTo", new { ContractI3D = testContract.I3D, ContractState = contractAfter?.State.ToString(), ContractTermination = testContract.ContractTermination?.ToString("yyyy-MM-dd"), LastBookingTo = testContract.LastBookingTo?.ToString("yyyy-MM-dd"), ReferenceDate = _referenceDate.ToString("yyyy-MM-dd"), AutomatedProlongation = testContract.AutomatedProlongation, ShouldRemainActive = contractAfter?.State == ReceiptState.Active, Reason = "Termination date is in the future, contract should remain active" }); // Cleanup CleanupTestContract(session, testContract.I3D); } /// /// Test that automated prolongation contracts should close when termination is exceeded /// and reference date is after the termination date /// private void TestAutomatedProlongationContract_ShouldClose_WhenTerminationExceeded(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Enable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, true); setting.SaveSettings(); // Termination date is in the past (10 days before reference date) var terminationDate = _referenceDate.AddDays(-10); var testContract = CreateTestContract(session, contractTermination: terminationDate, lastBookingTo: terminationDate, // Fully billed and termination date passed automatedProlongation: true); // Act - pass reference date to CloseContract contractBL.CloseContract(_referenceDate); // Assert var contractAfter = GetContractById(session, testContract.I3D); Verifier.Verify("AutomatedProlongation_Closed_WhenTerminationExceeded", new { ContractI3D = testContract.I3D, ContractState = contractAfter?.State.ToString(), ContractTermination = testContract.ContractTermination?.ToString("yyyy-MM-dd"), LastBookingTo = testContract.LastBookingTo?.ToString("yyyy-MM-dd"), ReferenceDate = _referenceDate.ToString("yyyy-MM-dd"), AutomatedProlongation = testContract.AutomatedProlongation, ShouldBeClosed = contractAfter?.State == ReceiptState.Completed, Reason = "Termination date is in the past and contract is fully billed" }); // Cleanup CleanupTestContract(session, testContract.I3D); } /// /// Test that normal contracts should NOT close when end date equals LastBookingTo /// but the end date has not been reached yet (future date) /// private void TestNormalContract_ShouldNotClose_WhenEndDateEqualsLastBookingTo(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Enable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, true); setting.SaveSettings(); // End date is in the future (30 days after reference date) var endDate = _referenceDate.AddDays(30); var testContract = CreateTestContract(session, contractEnd: endDate, lastBookingTo: endDate, // Fully billed, but end date not reached yet automatedProlongation: false); // Act - pass reference date to CloseContract contractBL.CloseContract(_referenceDate); // Assert var contractAfter = GetContractById(session, testContract.I3D); Verifier.Verify("NormalContract_NotClosed_WhenEndDateEqualsLastBookingTo", new { ContractI3D = testContract.I3D, ContractState = contractAfter?.State.ToString(), ContractEnd = testContract.ContractEnd?.ToString("yyyy-MM-dd"), LastBookingTo = testContract.LastBookingTo?.ToString("yyyy-MM-dd"), ReferenceDate = _referenceDate.ToString("yyyy-MM-dd"), AutomatedProlongation = testContract.AutomatedProlongation, ShouldRemainActive = contractAfter?.State == ReceiptState.Active, Reason = "End date is in the future, contract should remain active" }); // Cleanup CleanupTestContract(session, testContract.I3D); } /// /// Test that normal contracts should close when end date is exceeded /// and reference date is after the end date /// private void TestNormalContract_ShouldClose_WhenEndDateExceeded(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Enable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, true); setting.SaveSettings(); // End date is in the past (10 days before reference date) var endDate = _referenceDate.AddDays(-10); var testContract = CreateTestContract(session, contractEnd: endDate, lastBookingTo: endDate, // Fully billed and end date passed automatedProlongation: false); // Act - pass reference date to CloseContract contractBL.CloseContract(_referenceDate); // Assert var contractAfter = GetContractById(session, testContract.I3D); Verifier.Verify("NormalContract_Closed_WhenEndDateExceeded", new { ContractI3D = testContract.I3D, ContractState = contractAfter?.State.ToString(), ContractEnd = testContract.ContractEnd?.ToString("yyyy-MM-dd"), LastBookingTo = testContract.LastBookingTo?.ToString("yyyy-MM-dd"), ReferenceDate = _referenceDate.ToString("yyyy-MM-dd"), AutomatedProlongation = testContract.AutomatedProlongation, ShouldBeClosed = contractAfter?.State == ReceiptState.Completed, Reason = "End date is in the past and contract is fully billed" }); // Cleanup CleanupTestContract(session, testContract.I3D); } /// /// Test that contracts without termination or end date are not processed /// private void TestContract_ShouldNotClose_WhenNoTerminationOrEndDate(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Enable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, true); setting.SaveSettings(); var testContract = CreateTestContract(session, contractTermination: null, contractEnd: null, lastBookingTo: _referenceDate.AddDays(10), automatedProlongation: true); // Act - pass reference date to CloseContract contractBL.CloseContract(_referenceDate); // Assert var contractAfter = GetContractById(session, testContract.I3D); Verifier.Verify("Contract_NotProcessed_WhenNoTerminationOrEndDate", new { ContractI3D = testContract.I3D, ContractState = contractAfter?.State.ToString(), HasContractTermination = testContract.ContractTermination.HasValue, HasContractEnd = testContract.ContractEnd.HasValue, ShouldRemainActive = contractAfter?.State == ReceiptState.Active }); // Cleanup CleanupTestContract(session, testContract.I3D); } /// /// Test the specific bug scenario from the bug report: /// Contract is billed on the termination date, but should not close until the next day /// Bug: Contract was closed on 14.04 when LastBookingTo was 30.04 and TerminationDate was 30.04 /// Expected: Contract should only close on 01.05 (day after termination date) /// private void TestBugScenario_ContractBilledOnTerminationDate_ShouldNotCloseUntilNextDay(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Enable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, true); setting.SaveSettings(); // Simulate the bug scenario: Contract termination is 10 days after reference date // and it's already billed up to that date var terminationDate = _referenceDate.AddDays(10); var testContract = CreateTestContract(session, contractTermination: terminationDate, lastBookingTo: terminationDate, // Billed on termination date (like 30.04) automatedProlongation: true); // Act - Run on the reference date (which is before termination date) contractBL.CloseContract(_referenceDate); // Assert var contractAfter = GetContractById(session, testContract.I3D); Verifier.Verify("BugScenario_ContractNotClosedWhenBilledOnTerminationDate", new { ContractI3D = testContract.I3D, ContractState = contractAfter?.State.ToString(), ContractTermination = testContract.ContractTermination?.ToString("yyyy-MM-dd"), LastBookingTo = testContract.LastBookingTo?.ToString("yyyy-MM-dd"), ReferenceDate = _referenceDate.ToString("yyyy-MM-dd"), DaysUntilTermination = (terminationDate - _referenceDate).Days, ShouldRemainActive = contractAfter?.State == ReceiptState.Active, Reason = "Contract is fully billed but termination date not yet reached - should remain active" }); // Cleanup CleanupTestContract(session, testContract.I3D); } /// /// Test that manual calculation contracts are not processed /// private void TestContract_ShouldNotClose_WhenManualCalculation(BLSession session) { // Arrange var contractBL = session.GetBL(); var settingsBL = session.GetBL(); // Enable automatic closure var setting = settingsBL.GetSettingsForUpdate(ApplicationSettingID.AutomaticallyCloseExpiredContracts); setting.UpdateBool(ApplicationSettingID.AutomaticallyCloseExpiredContracts, true); setting.SaveSettings(); var testContract = CreateTestContract(session, contractTermination: _referenceDate.AddDays(-10), lastBookingTo: _referenceDate.AddDays(-10), automatedProlongation: true, calculationKind: ContractCalculationKind.Manual); // Manual calculation // Act - pass reference date to CloseContract contractBL.CloseContract(_referenceDate); // Assert var contractAfter = GetContractById(session, testContract.I3D); Verifier.Verify("Contract_NotProcessed_WhenManualCalculation", new { ContractI3D = testContract.I3D, ContractState = contractAfter?.State.ToString(), CalculationKind = testContract.CalculationKind.ToString(), ShouldRemainActive = contractAfter?.State == ReceiptState.Active }); // Cleanup CleanupTestContract(session, testContract.I3D); } #region Helper Methods /// /// Creates a test contract with specified parameters /// private ReceiptContract CreateTestContract(BLSession session, DateTime? contractTermination = null, DateTime? contractEnd = null, DateTime? lastBookingTo = null, bool automatedProlongation = true, ContractCalculationKind calculationKind = ContractCalculationKind.Auto) { var loggedInUser = this.GetLoggedInUser(_testUserI3D); var receiptWebServiceBL = session.GetBL(); // Create new contract var result = receiptWebServiceBL.CreateNewReceipt( CentronObjectKindNumeric.ContractClass, loggedInUser, _customerI3D, new CreateReceiptData(), null, null, true, false, true); var contractDTO = (ReceiptContractDTO)result.Data.Receipt; // Set contract properties contractDTO.ContractKindI3D = _contractKindI3D; contractDTO.CalculationKind = calculationKind; contractDTO.AutomatedProlongation = automatedProlongation; contractDTO.ContractTermination = contractTermination; contractDTO.ContractEnd = contractEnd; contractDTO.FirstPaidDate = DateTime.Now.AddDays(-30); // Save the contract var saveResult = receiptWebServiceBL.SaveReceipt(contractDTO, loggedInUser.User, new SaveReceiptData { IgnoreCallbacks = true }, false, CreatedThroughApplication.CentronNet, null); if (saveResult.Status != ResultStatus.Success) { throw new InvalidOperationException($"Failed to create test contract: {saveResult.Message}"); } // Update LastBookingTo if specified (this needs to be done after saving) if (lastBookingTo.HasValue) { var receiptBL = session.GetBL(); var contract = receiptBL.GetReceiptByI3D(saveResult.Data.Receipt.I3D); contract.LastBookingTo = lastBookingTo; receiptBL.SaveReceipt(contract, loggedInUser.User, new SaveReceiptData { IgnoreCallbacks = true }, false, CreatedThroughApplication.CentronNet); } return session.GetBL().GetReceiptByI3D(saveResult.Data.Receipt.I3D); } /// /// Gets all active contracts /// private List GetActiveContracts(BLSession session) { var receiptBL = session.GetBL(); return receiptBL.GetReceipts(f => f.State == ReceiptState.Active).ToList(); } /// /// Gets a contract by ID /// private ReceiptContract GetContractById(BLSession session, int contractI3D) { var receiptBL = session.GetBL(); return receiptBL.GetReceiptByI3D(contractI3D); } /// /// Cleans up test contract /// private void CleanupTestContract(BLSession session, int contractI3D) { try { var receiptBL = session.GetBL(); var contract = this.GetContractById(session, contractI3D); if (contract != null) { var loggedInUser = this.GetLoggedInUser(_testUserI3D); contract.State = ReceiptState.Completed; receiptBL.SaveReceipt(contract, loggedInUser.User, new SaveReceiptData { IgnoreCallbacks = true }, false, CreatedThroughApplication.CentronNet); } } catch { // Ignore cleanup errors in tests } } #endregion } }