QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142) getrackt, ohne .gitmodules und ohne erreichbares Remote. Der Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen. Umstellung: - Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6) - Gitlink aus dem Index entfernt - Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
494 lines
22 KiB
C#
494 lines
22 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// End-to-end tests for the ContractBL.CloseContract() method.
|
|
/// Tests the automatic contract closure functionality based on termination/end dates.
|
|
/// </summary>
|
|
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());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that no contracts are closed when automatic closure is disabled
|
|
/// </summary>
|
|
private void TestAutomaticClosureDisabled(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
private void TestAutomatedProlongationContract_ShouldNotClose_WhenTerminationEqualsLastBookingTo(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that automated prolongation contracts should close when termination is exceeded
|
|
/// and reference date is after the termination date
|
|
/// </summary>
|
|
private void TestAutomatedProlongationContract_ShouldClose_WhenTerminationExceeded(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that normal contracts should NOT close when end date equals LastBookingTo
|
|
/// but the end date has not been reached yet (future date)
|
|
/// </summary>
|
|
private void TestNormalContract_ShouldNotClose_WhenEndDateEqualsLastBookingTo(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that normal contracts should close when end date is exceeded
|
|
/// and reference date is after the end date
|
|
/// </summary>
|
|
private void TestNormalContract_ShouldClose_WhenEndDateExceeded(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that contracts without termination or end date are not processed
|
|
/// </summary>
|
|
private void TestContract_ShouldNotClose_WhenNoTerminationOrEndDate(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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)
|
|
/// </summary>
|
|
private void TestBugScenario_ContractBilledOnTerminationDate_ShouldNotCloseUntilNextDay(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that manual calculation contracts are not processed
|
|
/// </summary>
|
|
private void TestContract_ShouldNotClose_WhenManualCalculation(BLSession session)
|
|
{
|
|
// Arrange
|
|
var contractBL = session.GetBL<ContractBL>();
|
|
var settingsBL = session.GetBL<AppSettingsBL>();
|
|
|
|
// 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
|
|
|
|
/// <summary>
|
|
/// Creates a test contract with specified parameters
|
|
/// </summary>
|
|
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<ReceiptWebServiceBL>();
|
|
|
|
// 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<ReceiptBL>();
|
|
var contract = receiptBL.GetReceiptByI3D<ReceiptContract>(saveResult.Data.Receipt.I3D);
|
|
contract.LastBookingTo = lastBookingTo;
|
|
receiptBL.SaveReceipt(contract, loggedInUser.User,
|
|
new SaveReceiptData { IgnoreCallbacks = true }, false, CreatedThroughApplication.CentronNet);
|
|
}
|
|
|
|
return session.GetBL<ReceiptBL>().GetReceiptByI3D<ReceiptContract>(saveResult.Data.Receipt.I3D);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all active contracts
|
|
/// </summary>
|
|
private List<ReceiptContract> GetActiveContracts(BLSession session)
|
|
{
|
|
var receiptBL = session.GetBL<ReceiptBL>();
|
|
return receiptBL.GetReceipts<ReceiptContract>(f => f.State == ReceiptState.Active).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a contract by ID
|
|
/// </summary>
|
|
private ReceiptContract GetContractById(BLSession session, int contractI3D)
|
|
{
|
|
var receiptBL = session.GetBL<ReceiptBL>();
|
|
return receiptBL.GetReceiptByI3D<ReceiptContract>(contractI3D);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up test contract
|
|
/// </summary>
|
|
private void CleanupTestContract(BLSession session, int contractI3D)
|
|
{
|
|
try
|
|
{
|
|
var receiptBL = session.GetBL<ReceiptBL>();
|
|
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
|
|
}
|
|
}
|