/// <summary> /// init the exchange rate, to avoid messages "Cannot find exchange rate for EUR USD" /// </summary> public static void InitExchangeRate() { TAccountPeriodInfo AccountingPeriodInfo = new TAccountPeriodInfo(FLedgerNumber, 1); ADailyExchangeRateTable dailyrates = new ADailyExchangeRateTable(); ADailyExchangeRateRow row = dailyrates.NewRowTyped(true); row.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; row.TimeEffectiveFrom = 100; row.FromCurrencyCode = "USD"; row.ToCurrencyCode = "EUR"; row.RateOfExchange = 1.34m; dailyrates.Rows.Add(row); row = dailyrates.NewRowTyped(true); row.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; row.TimeEffectiveFrom = 100; row.FromCurrencyCode = "USD"; row.ToCurrencyCode = "GBP"; row.RateOfExchange = 1.57m; dailyrates.Rows.Add(row); if (!ADailyExchangeRateAccess.Exists(row.FromCurrencyCode, row.ToCurrencyCode, row.DateEffectiveFrom, row.TimeEffectiveFrom, null)) { ADailyExchangeRateAccess.SubmitChanges(dailyrates, null); } }
/// <summary> /// init the exchange rate, to avoid messages "Cannot find exchange rate for EUR USD" /// </summary> public static void InitExchangeRate() { TAccountPeriodInfo AccountingPeriodInfo = new TAccountPeriodInfo(FLedgerNumber, 1); ADailyExchangeRateTable dailyrates = new ADailyExchangeRateTable(); ADailyExchangeRateRow row = dailyrates.NewRowTyped(true); row.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; row.TimeEffectiveFrom = 100; row.FromCurrencyCode = "USD"; row.ToCurrencyCode = "EUR"; row.RateOfExchange = 1.34m; dailyrates.Rows.Add(row); row = dailyrates.NewRowTyped(true); row.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; row.TimeEffectiveFrom = 100; row.FromCurrencyCode = "USD"; row.ToCurrencyCode = "GBP"; row.RateOfExchange = 1.57m; dailyrates.Rows.Add(row); if (!ADailyExchangeRateAccess.Exists(row.FromCurrencyCode, row.ToCurrencyCode, row.DateEffectiveFrom, row.TimeEffectiveFrom, null)) { ADailyExchangeRateAccess.SubmitChanges(dailyrates, null); } ALedgerTable Ledger = ALedgerAccess.LoadByPrimaryKey(FLedgerNumber, null); for (int periodCounter = 1; periodCounter <= Ledger[0].NumberOfAccountingPeriods + Ledger[0].NumberFwdPostingPeriods; periodCounter++) { AccountingPeriodInfo = new TAccountPeriodInfo(FLedgerNumber, periodCounter); ACorporateExchangeRateTable corprates = new ACorporateExchangeRateTable(); ACorporateExchangeRateRow corprow = corprates.NewRowTyped(true); corprow.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; corprow.TimeEffectiveFrom = 100; corprow.FromCurrencyCode = "USD"; corprow.ToCurrencyCode = "EUR"; corprow.RateOfExchange = 1.34m; corprates.Rows.Add(corprow); corprow = corprates.NewRowTyped(true); corprow.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; corprow.TimeEffectiveFrom = 100; corprow.FromCurrencyCode = "USD"; corprow.ToCurrencyCode = "GBP"; corprow.RateOfExchange = 1.57m; corprates.Rows.Add(corprow); if (!ACorporateExchangeRateAccess.Exists(corprow.FromCurrencyCode, corprow.ToCurrencyCode, corprow.DateEffectiveFrom, null)) { ACorporateExchangeRateAccess.SubmitChanges(corprates, null); } } }
public static void AddARow(String FromCurrency, String ToCurrency, DateTime EffectiveDate, decimal Rate) { ADailyExchangeRateRow newRow = FMainDS.ADailyExchangeRate.NewRowTyped(); newRow.FromCurrencyCode = FromCurrency; newRow.ToCurrencyCode = ToCurrency; newRow.DateEffectiveFrom = EffectiveDate; newRow.TimeEffectiveFrom = 7200; newRow.RateOfExchange = Rate; FMainDS.ADailyExchangeRate.Rows.Add(newRow); }
public void LoadModalEmptyTable() { // Initialise data - create an empty table and our test ledger FMainDS.LoadAll(); FMainDS.DeleteAllRows(); FMainDS.SaveChanges(); FLedgerDS.CreateTestLedger(); // variables to hold the dialog result output decimal selectedRate; DateTime selectedDate; int selectedTime; // Open the screen modally on our test ledger using a 'from' currency of GBP TFrmSetupDailyExchangeRate mainScreen = new TFrmSetupDailyExchangeRate(null); DialogBoxHandler = delegate(string name, IntPtr hWnd) { LoadModalEmptyTableHandler(); }; DialogResult dlgResult = mainScreen.ShowDialog(STANDARD_TEST_LEDGER_NUMBER, FStandardEffectiveDate, "GBP", 1.0m, out selectedRate, out selectedDate, out selectedTime); // Check the result for any assertions if (dlgResult == DialogResult.Abort) { Assert.Fail(FModalAssertResult); } // Check we returned the correct data to the caller Assert.AreEqual(DialogResult.OK, dlgResult); Assert.AreEqual(STANDARD_RATE_OF_EXCHANGE, selectedRate); Assert.AreEqual(FStandardEffectiveDate, selectedDate); Assert.AreEqual(7200, selectedTime); // Check we did also save the result FMainDS.LoadAll(); ADailyExchangeRateRow row = (ADailyExchangeRateRow)FMainDS.ADailyExchangeRate.Rows.Find(new object[] { "GBP", STANDARD_TEST_CURRENCY, FStandardEffectiveDate, 7200 }); Assert.IsNotNull(row, "The selected exchange rate was not saved"); Assert.AreEqual(STANDARD_RATE_OF_EXCHANGE, row.RateOfExchange); }
private void ValidateDataDetailsManual(ADailyExchangeRateRow ARow) { if ((ARow.RowState == DataRowState.Detached) || FSkipValidation) { return; } TVerificationResultCollection VerificationResultCollection = FPetraUtilsObject.VerificationResultCollection; TSharedFinanceValidation_GLSetup.ValidateDailyExchangeRate(this, ARow, ref VerificationResultCollection, FPetraUtilsObject.ValidationControlsDict, minModalEffectiveDate, maxModalEffectiveDate, blnIsInModalMode, FMainDS.ALedgerInfo, FEarliestAccountingPeriodStartDate, FLatestAccountingPeriodEndDate); // Now make an additional manual check that the rate is sensible TScreenVerificationResult verificationResultSensible = null; TScreenVerificationResult verificationResultDuplicate = null; if ((ARow.RowState == DataRowState.Added) || (ARow.RowState == DataRowState.Modified)) { // We are going to check if the rate of exchange is sensible. We need our own view because we don't know how the grid is currently sorted // If we are in modal mode we may not be seeing the 'neighbouring' rate. But this filter will include it in the test. string filter = ADailyExchangeRateTable.GetFromCurrencyCodeDBName() + " = '" + ARow.FromCurrencyCode + "' and " + ADailyExchangeRateTable.GetToCurrencyCodeDBName() + " = '" + ARow.ToCurrencyCode + "'"; DataView myView = new DataView(FMainDS.ADailyExchangeRate, filter, SortByDateDescending, DataViewRowState.CurrentRows); // Find our current row int nThis = FindRowInDataView(myView, ARow.FromCurrencyCode, ARow.ToCurrencyCode, ARow.DateEffectiveFrom, ARow.TimeEffectiveFrom); ADailyExchangeRateRow drThis = null; ADailyExchangeRateRow drPrev = null; ADailyExchangeRateRow drNext = null; decimal ratio = 1.0m; if ((nThis >= 0) && (ARow.RateOfExchange != 0.0m)) { drThis = (ADailyExchangeRateRow)(myView[nThis]).Row; if (nThis >= 1) { drPrev = (ADailyExchangeRateRow)(myView[nThis - 1]).Row; } if (nThis < myView.Count - 1) { drNext = (ADailyExchangeRateRow)(myView[nThis + 1]).Row; } if ((drPrev != null) && (drPrev.RateOfExchange > 0.0m)) { ratio = drThis.RateOfExchange / drPrev.RateOfExchange; if (ratio < 1.0m) { ratio = drPrev.RateOfExchange / drThis.RateOfExchange; } } if ((drNext != null) && (drNext.RateOfExchange > 0.0m)) { decimal tryRatio = drThis.RateOfExchange / drNext.RateOfExchange; if (tryRatio < 1.0m) { tryRatio = drNext.RateOfExchange / drThis.RateOfExchange; } if (tryRatio > ratio) { ratio = tryRatio; } } if (ratio > EXCHANGE_RATE_WARNING_RATIO) { string validationMessage = String.Format( ErrorCodes.GetErrorInfo(PetraErrorCodes.ERR_EXCH_RATE_MAY_BE_INCORRECT).ErrorMessageText, ARow.RateOfExchange, ARow.FromCurrencyCode, ARow.ToCurrencyCode, dtpDetailDateEffectiveFrom.Text, txtDetailTimeEffectiveFrom.Text, ratio - 1.0m); // So we have a new warning to raise on a row that has been added/edited verificationResultSensible = new TScreenVerificationResult( this, ARow.Table.Columns[ADailyExchangeRateTable.ColumnRateOfExchangeId], validationMessage, Catalog.GetString("Exchange Rate Alert"), PetraErrorCodes.ERR_EXCH_RATE_MAY_BE_INCORRECT, txtDetailRateOfExchange, TResultSeverity.Resv_Noncritical); } } // Now check to see if it is a duplicate rate filter += String.Format(" and {0}={1} and {2}=#{3}#", ADailyExchangeRateTable.GetRateOfExchangeDBName(), ARow.RateOfExchange.ToString(CultureInfo.InvariantCulture), ADailyExchangeRateTable.GetDateEffectiveFromDBName(), ARow.DateEffectiveFrom.ToString("d", CultureInfo.InvariantCulture)); myView = new DataView(FMainDS.ADailyExchangeRate, filter, String.Empty, DataViewRowState.CurrentRows); if (myView.Count > 1) { verificationResultDuplicate = new TScreenVerificationResult( this, ARow.Table.Columns[ADailyExchangeRateTable.ColumnRateOfExchangeId], Catalog.GetString("A row with this date and exchange rate already exists"), Catalog.GetString("Exchange Rate Alert"), PetraErrorCodes.ERR_EXCH_RATE_IS_DUPLICATE, txtDetailRateOfExchange, TResultSeverity.Resv_Noncritical); } } VerificationResultCollection.Auto_Add_Or_AddOrRemove(this, verificationResultSensible, ARow.Table.Columns[ADailyExchangeRateTable.ColumnRateOfExchangeId]); VerificationResultCollection.Auto_Add_Or_AddOrRemove(this, verificationResultDuplicate, ARow.Table.Columns[ADailyExchangeRateTable.ColumnRateOfExchangeId]); }
/// <summary> /// Validates the Daily Exchange Rates screen data. /// </summary> /// <param name="AContext">Context that describes where the data validation failed.</param> /// <param name="ARow">The <see cref="DataRow" /> which holds the the data against which the validation is run.</param> /// <param name="AVerificationResultCollection">Will be filled with any <see cref="TVerificationResult" /> items if /// data validation errors occur.</param> /// <param name="AValidationControlsDict">A <see cref="TValidationControlsDict" /> containing the Controls that /// display data that is about to be validated.</param> /// <param name="AMinDateTime">The earliest allowable date.</param> /// <param name="AMaxDateTime">The latest allowable date.</param> public static void ValidateDailyExchangeRate(object AContext, ADailyExchangeRateRow ARow, ref TVerificationResultCollection AVerificationResultCollection, TValidationControlsDict AValidationControlsDict, DateTime AMinDateTime, DateTime AMaxDateTime) { DataColumn ValidationColumn; TValidationControlsData ValidationControlsData; TVerificationResult VerificationResult; // Don't validate deleted DataRows if (ARow.RowState == DataRowState.Deleted) { return; } // RateOfExchange must be positive (definitely not zero because we can invert it) ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnRateOfExchangeId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { VerificationResult = TNumericalChecks.IsPositiveDecimal(ARow.RateOfExchange, ValidationControlsData.ValidationControlLabel, AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition/removal to/from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } // Date must not be empty and must be in range ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnDateEffectiveFromId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { VerificationResult = TSharedValidationControlHelper.IsNotInvalidDate(ARow.DateEffectiveFrom, ValidationControlsData.ValidationControlLabel, AVerificationResultCollection, true, AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); if (VerificationResult == null) { if ((AMinDateTime > DateTime.MinValue) && (AMaxDateTime < DateTime.MaxValue)) { // Check that the date is in range VerificationResult = TDateChecks.IsDateBetweenDates(ARow.DateEffectiveFrom, AMinDateTime, AMaxDateTime, ValidationControlsData.ValidationControlLabel, TDateBetweenDatesCheckType.dbdctUnspecific, TDateBetweenDatesCheckType.dbdctUnspecific, AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } else if (AMaxDateTime < DateTime.MaxValue) { VerificationResult = TDateChecks.FirstLesserOrEqualThanSecondDate(ARow.DateEffectiveFrom, AMaxDateTime, ValidationControlsData.ValidationControlLabel, Ict.Common.StringHelper.DateToLocalizedString(AMaxDateTime), AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } else if (AMinDateTime > DateTime.MinValue) { VerificationResult = TDateChecks.FirstGreaterOrEqualThanSecondDate(ARow.DateEffectiveFrom, AMinDateTime, ValidationControlsData.ValidationControlLabel, Ict.Common.StringHelper.DateToLocalizedString(AMinDateTime), AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } } } // Time must not be negative (indicating an error) ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnTimeEffectiveFromId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { VerificationResult = TTimeChecks.IsValidIntegerTime(ARow.TimeEffectiveFrom, ValidationControlsData.ValidationControlLabel, AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } }
/// <summary> /// get daily exchange rate for the given currencies and date; /// TODO: might even collect the latest exchange rate from the web /// </summary> /// <param name="ACurrencyFrom"></param> /// <param name="ACurrencyTo"></param> /// <param name="ADateEffective"></param> /// <returns>Zero if no exchange rate found</returns> public static decimal GetDailyExchangeRate(string ACurrencyFrom, string ACurrencyTo, DateTime ADateEffective) { if (ACurrencyFrom == ACurrencyTo) { return(1.0M); } bool NewTransaction; TDBTransaction Transaction = DBAccess.GDBAccessObj.GetNewOrExistingTransaction (IsolationLevel.ReadCommitted, TEnforceIsolationLevel.eilMinimum, out NewTransaction); ADailyExchangeRateRow fittingRate = null; // TODO: get the most recent exchange rate for the given date and currencies bool oppositeRate = false; ADailyExchangeRateTable rates = ADailyExchangeRateAccess.LoadByPrimaryKey(ACurrencyFrom, ACurrencyTo, ADateEffective, 0, Transaction); if (rates.Count == 0) { // try other way round rates = ADailyExchangeRateAccess.LoadByPrimaryKey(ACurrencyTo, ACurrencyFrom, ADateEffective, 0, Transaction); oppositeRate = true; } if (rates.Count == 1) { fittingRate = rates[0]; } else if (rates.Count == 0) { // TODO: collect exchange rate from the web; save to db // see tracker http://sourceforge.net/apps/mantisbt/openpetraorg/view.php?id=87 // Or look for most recent exchange rate??? ADailyExchangeRateTable tempTable = new ADailyExchangeRateTable(); ADailyExchangeRateRow templateRow = tempTable.NewRowTyped(false); templateRow.FromCurrencyCode = ACurrencyFrom; templateRow.ToCurrencyCode = ACurrencyTo; oppositeRate = false; rates = ADailyExchangeRateAccess.LoadUsingTemplate(templateRow, Transaction); if (rates.Count == 0) { templateRow.FromCurrencyCode = ACurrencyTo; templateRow.ToCurrencyCode = ACurrencyFrom; oppositeRate = true; rates = ADailyExchangeRateAccess.LoadUsingTemplate(templateRow, Transaction); } if (rates.Count > 0) { // sort rates by date, look for rate just before the date we are looking for rates.DefaultView.Sort = ADailyExchangeRateTable.GetDateEffectiveFromDBName(); rates.DefaultView.RowFilter = ADailyExchangeRateTable.GetDateEffectiveFromDBName() + "<= #" + ADateEffective.ToString("yyyy-MM-dd") + "#"; if (rates.DefaultView.Count > 0) { fittingRate = (ADailyExchangeRateRow)rates.DefaultView[rates.DefaultView.Count - 1].Row; } } } if (NewTransaction) { DBAccess.GDBAccessObj.RollbackTransaction(); } if (fittingRate != null) { if (oppositeRate) { return(1.0M / fittingRate.RateOfExchange); } return(fittingRate.RateOfExchange); } TLogging.Log("Cannot find daily exchange rate for " + ACurrencyFrom + " " + ACurrencyTo); //return 1.0M; //Instead, cause a validation error to force the user to select an exchange rate return(0M); }
/// <summary> /// Gets the best (latest) exchange rate for a pair of currencies from a table of rates. /// </summary> /// <param name="ADailyRateTable">A table of rates that has been obtained by calling LoadExchangeRateData for a period of interest.</param> /// <param name="ACurrencyFrom">The 'from' currency</param> /// <param name="ACurrencyTo">The 'to' currency</param> /// <param name="AEnforceUniqueRate">If true the method will only return true if there is a unique rate for the specified currencies on the 'latest' date. /// If false the method returns the rate for the latest 'time' on the latest date.</param> /// <param name="ARateOfExchange">The returned rate</param> /// <param name="AEffectiveDate">The date associated with the returned rate.</param> /// <returns>True if a matching rate was found. The rate and date are in the out parameters.</returns> public static bool GetBestExchangeRate(ExchangeRateTDSADailyExchangeRateTable ADailyRateTable, string ACurrencyFrom, string ACurrencyTo, Boolean AEnforceUniqueRate, out decimal ARateOfExchange, out DateTime AEffectiveDate) { ARateOfExchange = 0.0m; AEffectiveDate = DateTime.MinValue; ADailyExchangeRateRow uniqueFittingRow = null; bool oppositeRate = false; // sort rates by date, look for rate just before the date we are looking for string rowFilter = String.Format("{0}='{1}' AND {2}='{3}'", ADailyExchangeRateTable.GetFromCurrencyCodeDBName(), ACurrencyFrom, ADailyExchangeRateTable.GetToCurrencyCodeDBName(), ACurrencyTo); ADailyRateTable.DefaultView.RowFilter = rowFilter; ADailyRateTable.DefaultView.Sort = String.Format("{0} DESC, {1} DESC", ADailyExchangeRateTable.GetDateEffectiveFromDBName(), ADailyExchangeRateTable.GetTimeEffectiveFromDBName()); // If we are after a unique row we need to remember the one from this view, if it exists if (AEnforceUniqueRate && (ADailyRateTable.DefaultView.Count == 1)) { uniqueFittingRow = (ADailyExchangeRateRow)ADailyRateTable.DefaultView[0].Row; } if ((ADailyRateTable.DefaultView.Count == 0) || AEnforceUniqueRate) { // try other way round rowFilter = String.Format("{0}='{1}' AND {2}='{3}'", ADailyExchangeRateTable.GetToCurrencyCodeDBName(), ACurrencyFrom, ADailyExchangeRateTable.GetFromCurrencyCodeDBName(), ACurrencyTo); ADailyRateTable.DefaultView.RowFilter = rowFilter; oppositeRate = true; if (AEnforceUniqueRate) { if (ADailyRateTable.DefaultView.Count > 1) { // This way round does not have a unique rate uniqueFittingRow = null; } else if ((ADailyRateTable.DefaultView.Count == 1) && (uniqueFittingRow == null)) { // we will use this as our unique rate uniqueFittingRow = (ADailyExchangeRateRow)ADailyRateTable.DefaultView[0].Row; } else { // put this variable back as it was oppositeRate = false; } } } ADailyExchangeRateRow fittingRate = null; if (AEnforceUniqueRate) { // Did we get a unique rate? if (uniqueFittingRow != null) { fittingRate = uniqueFittingRow; } } else if (ADailyRateTable.DefaultView.Count > 0) { // Just return the first in the list fittingRate = (ADailyExchangeRateRow)ADailyRateTable.DefaultView[0].Row; } if (fittingRate != null) { if (oppositeRate) { ARateOfExchange = GLRoutines.Divide(1.0m, fittingRate.RateOfExchange); } else { ARateOfExchange = fittingRate.RateOfExchange; } AEffectiveDate = fittingRate.DateEffectiveFrom; } //Returning 0 causes a validation error to force the user to select an exchange rate: return(ARateOfExchange != 0.0m); }
/// <summary> /// Imports currency exchange rates, daily and corporate, /// from a one-of-two-styles formatted CSV file /// </summary> /// <param name="AExchangeRDT">Daily or Corporate exchange rate table</param> /// <param name="ADataFilename">The .CSV file to process</param> /// <param name="ACSVSeparator"></param> /// <param name="ANumberFormat"></param> /// <param name="ADateFormat"></param> /// <param name="AImportMode">Daily or Corporate</param> /// <param name="AResultCollection">A validation collection to which errors will be added</param> /// <returns>The number of rows that were actually imported. Rows that duplicate existing rows do not count. /// This is usually because this is an attempt to import again after a failed previous attempt.</returns> private static int ImportCurrencyExRatesFromCSV(TTypedDataTable AExchangeRDT, string ADataFilename, string ACSVSeparator, string ANumberFormat, string ADateFormat, string AImportMode, TVerificationResultCollection AResultCollection) { // Keep a list of errors/warnings and severity List <Tuple <string, TResultSeverity> > InvalidRows = new List <Tuple <string, TResultSeverity> >(); // keep a variable that becomes true if any row has an invalid column count, so we can show a helpful message bool InvalidColumnCount = false; // Check our method parameters if ((AImportMode != "Corporate") && (AImportMode != "Daily")) { throw new ArgumentException("Invalid value '" + AImportMode + "' for mode argument: Valid values are Corporate and Daily"); } else if ((AImportMode == "Corporate") && !(AExchangeRDT is ACorporateExchangeRateTable)) { throw new ArgumentException("Invalid type of exchangeRateDT argument for mode: 'Corporate'. Needs to be: ACorporateExchangeRateTable"); } else if ((AImportMode == "Daily") && !(AExchangeRDT is ADailyExchangeRateTable)) { throw new ArgumentException("Invalid type of exchangeRateDT argument for mode: 'Daily'. Needs to be: ADailyExchangeRateTable"); } bool IsShortFileFormat; int x, y; // To store the From and To currencies // Use an array to store these to make for easy // inverting of the two currencies when calculating // the inverse value. string[] Currencies = new string[2]; Type DataTableType; int RowsImported = 0; // This table will tell us the base currencies used by the current set of ledgers ALedgerTable ledgers = TRemote.MFinance.Setup.WebConnectors.GetAvailableLedgers(); List <string> allBaseCurrencies = new List <string>(); DateTime maxRecommendedEffectiveDate = DateTime.MaxValue; DateTime minRecommendedEffectiveDate = DateTime.MinValue; int preferredPeriodStartDay = 0; // Use the ledger table rows to get a list of base currencies and current period end dates for (int i = 0; i < ledgers.Rows.Count; i++) { ALedgerRow ledger = (ALedgerRow)ledgers.Rows[i]; string code = ledger.BaseCurrency; if (ledger.LedgerStatus == true) { if (allBaseCurrencies.Contains(code) == false) { allBaseCurrencies.Add(code); } DataTable AccountingPeriods = TDataCache.TMFinance.GetCacheableFinanceTable(TCacheableFinanceTablesEnum.AccountingPeriodList, ledger.LedgerNumber); AAccountingPeriodRow currentPeriodRow = (AAccountingPeriodRow)AccountingPeriods.Rows.Find(new object[] { ledger.LedgerNumber, ledger.CurrentPeriod }); AAccountingPeriodRow forwardPeriodRow = (AAccountingPeriodRow)AccountingPeriods.Rows.Find( new object[] { ledger.LedgerNumber, ledger.CurrentPeriod + ledger.NumberFwdPostingPeriods }); if ((forwardPeriodRow != null) && ((maxRecommendedEffectiveDate == DateTime.MaxValue) || (forwardPeriodRow.PeriodEndDate > maxRecommendedEffectiveDate))) { maxRecommendedEffectiveDate = forwardPeriodRow.PeriodEndDate; } if ((currentPeriodRow != null) && ((minRecommendedEffectiveDate == DateTime.MinValue) || (currentPeriodRow.PeriodStartDate > minRecommendedEffectiveDate))) { minRecommendedEffectiveDate = currentPeriodRow.PeriodStartDate; } if ((currentPeriodRow != null) && (preferredPeriodStartDay == 0)) { preferredPeriodStartDay = currentPeriodRow.PeriodStartDate.Day; } else if ((currentPeriodRow != null) && (currentPeriodRow.PeriodStartDate.Day != preferredPeriodStartDay)) { preferredPeriodStartDay = -1; } } } // This will tell us the complete list of all available currencies ACurrencyTable allCurrencies = new ACurrencyTable(); DataTable CacheDT = TDataCache.GetCacheableDataTableFromCache("CurrencyCodeList", String.Empty, null, out DataTableType); allCurrencies.Merge(CacheDT); allCurrencies.CaseSensitive = true; // Start reading the file using (StreamReader DataFile = new StreamReader(ADataFilename, System.Text.Encoding.Default)) { string ThousandsSeparator = (ANumberFormat == TDlgSelectCSVSeparator.NUMBERFORMAT_AMERICAN ? "," : "."); string DecimalSeparator = (ANumberFormat == TDlgSelectCSVSeparator.NUMBERFORMAT_AMERICAN ? "." : ","); CultureInfo MyCultureInfoDate = new CultureInfo("en-GB"); MyCultureInfoDate.DateTimeFormat.ShortDatePattern = ADateFormat; // TODO: disconnect the grid from the datasource to avoid flickering? string FileNameWithoutExtension = Path.GetFileNameWithoutExtension(ADataFilename).ToUpper(); if ((FileNameWithoutExtension.IndexOf("_") == 3) && (FileNameWithoutExtension.LastIndexOf("_") == 3) && (FileNameWithoutExtension.Length == 7)) { // File name format assumed to be like this: USD_HKD.csv IsShortFileFormat = true; Currencies = FileNameWithoutExtension.Split(new char[] { '_' }); } else { IsShortFileFormat = false; } int LineNumber = 0; while (!DataFile.EndOfStream) { string Line = DataFile.ReadLine(); LineNumber++; // See if the first line is a special case?? if (LineNumber == 1) { // see if the first line is a text header - look for digits // A valid header would look like: From,To,Date,Rate bool bFoundDigit = false; for (int i = 0; i < Line.Length; i++) { char c = Line[i]; if ((c >= '0') && (c <= '9')) { bFoundDigit = true; break; } } if (!bFoundDigit) { // No digits so we will assume the line is a header continue; } } //Convert separator to a char char Sep = ACSVSeparator[0]; //Turn current line into string array of column values string[] CsvColumns = Line.Split(Sep); int NumCols = CsvColumns.Length; // Do we have the correct number of columns? int minColCount = IsShortFileFormat ? 2 : 4; int maxColCount = (AImportMode == "Daily") ? minColCount + 1 : minColCount; if ((NumCols < minColCount) || (NumCols > maxColCount)) { // raise an error string resultText = String.Format(Catalog.GetPluralString( "Line {0}: contains 1 column", "Line {0}: contains {1} columns", NumCols, true), LineNumber, NumCols.ToString()); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Critical)); InvalidColumnCount = true; continue; } if (!IsShortFileFormat) { //Read the values for the current line //From currency Currencies[0] = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).ToString().ToUpper(); //To currency Currencies[1] = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).ToString().ToUpper(); } // Perform validation on the From and To currencies at this point!! if ((allCurrencies.Rows.Find(Currencies[0]) == null) && (allCurrencies.Rows.Find(Currencies[1]) == null)) { // raise an error string resultText = String.Format(Catalog.GetString( "Line {0}: invalid currency codes ({1} and {2})"), LineNumber.ToString(), Currencies[0], Currencies[1]); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Critical)); continue; } else if (allCurrencies.Rows.Find(Currencies[0]) == null) { // raise an error string resultText = String.Format(Catalog.GetString( "Line {0}: invalid currency code ({1})"), LineNumber.ToString(), Currencies[0]); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Critical)); continue; } else if (allCurrencies.Rows.Find(Currencies[1]) == null) { // raise an error string resultText = String.Format(Catalog.GetString( "Line {0}: invalid currency code ({1})"), LineNumber.ToString(), Currencies[1]); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Critical)); continue; } if ((allBaseCurrencies.Contains(Currencies[0]) == false) && (allBaseCurrencies.Contains(Currencies[1]) == false)) { //raise a non-critical error string resultText = String.Format(Catalog.GetString( "Line {0}: Warning: One of '{1}' and '{2}' should be a base currency in one of the active ledgers."), LineNumber.ToString(), Currencies[0], Currencies[1]); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Noncritical)); } // Date parsing as in Petra 2.x instead of using XML date format!!! string DateEffectiveStr = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).Replace("\"", String.Empty); DateTime DateEffective; if (!DateTime.TryParse(DateEffectiveStr, MyCultureInfoDate, DateTimeStyles.None, out DateEffective)) { // raise an error string resultText = String.Format(Catalog.GetString( "Line {0}: invalid date ({1})"), LineNumber.ToString(), DateEffectiveStr); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Critical)); continue; } if (DateEffective > maxRecommendedEffectiveDate) { // raise a warning string resultText = String.Format(Catalog.GetString( "Line {0}: Warning: The date '{1}' is after the latest forwarding period of any active ledger"), LineNumber.ToString(), DateEffectiveStr); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Noncritical)); } else if (DateEffective < minRecommendedEffectiveDate) { // raise a warning string resultText = String.Format(Catalog.GetString( "Line {0}: Warning: The date '{1}' is before the current accounting period of any active ledger"), LineNumber.ToString(), DateEffectiveStr); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Noncritical)); } if (AImportMode == "Corporate") { if ((preferredPeriodStartDay >= 1) && (DateEffective.Day != preferredPeriodStartDay)) { // raise a warning string resultText = String.Format(Catalog.GetString( "Line {0}: Warning: The date '{1}' should be the first day of an accounting period used by all the active ledgers."), LineNumber.ToString(), DateEffectiveStr); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Noncritical)); } } decimal ExchangeRate = 0.0m; try { string ExchangeRateString = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).Replace(ThousandsSeparator, "").Replace( DecimalSeparator, ".").Replace("\"", String.Empty); ExchangeRate = Convert.ToDecimal(ExchangeRateString, System.Globalization.CultureInfo.InvariantCulture); if (ExchangeRate == 0) { throw new Exception(); } } catch (Exception) { // raise an error string resultText = String.Format(Catalog.GetString( "Line {0}: invalid rate of exchange ({1})"), LineNumber.ToString(), ExchangeRate); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Critical)); continue; } int TimeEffective = 7200; if (AImportMode == "Daily") { // Daily rate imports can have an optional final column which is the time // Otherwise we assume the time is a default of 7200 (02:00am) if ((IsShortFileFormat && (NumCols == 3)) || (!IsShortFileFormat && (NumCols == 5))) { string timeEffectiveStr = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true); int t = (int)new Ict.Common.TypeConverter.TShortTimeConverter().ConvertTo(timeEffectiveStr, typeof(int)); if (t < 0) { // it wasn't in the format 02:00 if (!Int32.TryParse(timeEffectiveStr, out t)) { // Not a regular Int32 either t = -1; } } if ((t >= 0) && (t < 86400)) { TimeEffective = t; } else { // raise an error string resultText = String.Format(Catalog.GetString( "Line {0}: invalid effective time ({1})"), LineNumber.ToString(), t); InvalidRows.Add(new Tuple <string, TResultSeverity>(resultText, TResultSeverity.Resv_Critical)); continue; } } } if ((AImportMode == "Corporate") && AExchangeRDT is ACorporateExchangeRateTable) { ACorporateExchangeRateTable ExchangeRateDT = (ACorporateExchangeRateTable)AExchangeRDT; // run this code in the loop twice to get ExchangeRate value and its inverse for (int i = 0; i <= 1; i++) { //this will cause x and y to go from 0 to 1 and 1 to 0 respectively x = i; y = Math.Abs(i - 1); ACorporateExchangeRateRow ExchangeRow = (ACorporateExchangeRateRow)ExchangeRateDT.Rows. Find(new object[] { Currencies[x], Currencies[y], DateEffective }); if (ExchangeRow == null) // remove 0 for Corporate { ExchangeRow = (ACorporateExchangeRateRow)ExchangeRateDT.NewRowTyped(); ExchangeRow.FromCurrencyCode = Currencies[x]; ExchangeRow.ToCurrencyCode = Currencies[y]; ExchangeRow.DateEffectiveFrom = DateEffective; ExchangeRateDT.Rows.Add(ExchangeRow); RowsImported++; } if (i == 0) { ExchangeRow.RateOfExchange = ExchangeRate; } else { ExchangeRow.RateOfExchange = Math.Round(1 / ExchangeRate, 10); } } } else if ((AImportMode == "Daily") && AExchangeRDT is ADailyExchangeRateTable) { ADailyExchangeRateTable ExchangeRateDT = (ADailyExchangeRateTable)AExchangeRDT; // run this code in the loop twice to get ExchangeRate value and its inverse for (int i = 0; i <= 1; i++) { //this will cause x and y to go from 0 to 1 and 1 to 0 respectively x = i; y = Math.Abs(i - 1); ADailyExchangeRateRow ExchangeRow = (ADailyExchangeRateRow)ExchangeRateDT.Rows. Find(new object[] { Currencies[x], Currencies[y], DateEffective, TimeEffective }); if (ExchangeRow == null) // remove 0 for Corporate { ExchangeRow = (ADailyExchangeRateRow)ExchangeRateDT.NewRowTyped(); ExchangeRow.FromCurrencyCode = Currencies[x]; ExchangeRow.ToCurrencyCode = Currencies[y]; ExchangeRow.DateEffectiveFrom = DateEffective; ExchangeRow.TimeEffectiveFrom = TimeEffective; ExchangeRateDT.Rows.Add(ExchangeRow); RowsImported++; } if (i == 0) { ExchangeRow.RateOfExchange = ExchangeRate; } else { ExchangeRow.RateOfExchange = Math.Round(1 / ExchangeRate, 10); } } } } // if there are rows that could not be imported if ((InvalidRows != null) && (InvalidRows.Count > 0)) { int errorCount = 0; int warningCount = 0; // Go through once just to count the errors and warnings foreach (Tuple <string, TResultSeverity> Row in InvalidRows) { if (Row.Item2 == TResultSeverity.Resv_Noncritical) { warningCount++; } else { errorCount++; } } string resultText = String.Empty; bool messageListIsFull = false; int counter = 0; if (errorCount > 0) { resultText = string.Format(Catalog.GetPluralString("1 row was not imported due to invalid data:", "{0} rows were not imported due to invalid data:", errorCount, true), errorCount) + Environment.NewLine; } if (warningCount > 0) { resultText = string.Format(Catalog.GetPluralString("There was 1 warning associated with the imported rows:", "There were {0} warnings associated with the imported rows:", warningCount, true), warningCount) + Environment.NewLine; } // Now go through again itemising each one foreach (Tuple <string, TResultSeverity> Row in InvalidRows) { counter++; if (counter <= MAX_MESSAGE_COUNT) { resultText += Environment.NewLine + Row.Item1; } else if (!messageListIsFull) { resultText += String.Format(Catalog.GetString( "{0}{0}{1} errors/warnings were reported in total. This message contains the first {2}."), Environment.NewLine, InvalidRows.Count, MAX_MESSAGE_COUNT); messageListIsFull = true; } } // additional message if one or more rows has an invalid number of columns if (InvalidColumnCount && IsShortFileFormat) { resultText += String.Format("{0}{0}" + Catalog.GetString("Each row should contain 2 or 3 columns as follows:") + "{0}" + Catalog.GetString( " 1. Effective Date{0} 2. Exchange Rate{0} 3. Effective time in seconds (Optional for Daily Rate only)"), Environment.NewLine); } else if (InvalidColumnCount && !IsShortFileFormat) { resultText += String.Format("{0}{0}" + Catalog.GetString("Each row should contain 4 or 5 columns as follows:") + "{0}" + Catalog.GetString( " 1. From Currency{0} 2. To Currency{0} 3. Effective Date{0} 4. Exchange Rate{0} 5. Effective time in seconds (Optional for Daily Rate only)"), Environment.NewLine); } TVerificationResult result = new TVerificationResult(AImportMode, resultText, CommonErrorCodes.ERR_INCONGRUOUSSTRINGS, (errorCount > 0) ? TResultSeverity.Resv_Critical : TResultSeverity.Resv_Noncritical); AResultCollection.Add(result); } DataFile.Close(); return(RowsImported); } }
/// <summary> /// Validates the Daily Exchange Rates screen data. /// </summary> /// <param name="AContext">Context that describes where the data validation failed.</param> /// <param name="ARow">The <see cref="DataRow" /> which holds the the data against which the validation is run.</param> /// <param name="AVerificationResultCollection">Will be filled with any <see cref="TVerificationResult" /> items if /// data validation errors occur.</param> /// <param name="AMinDateTime">The earliest allowable date.</param> /// <param name="AMaxDateTime">The latest allowable date.</param> /// <param name="AIgnoreZeroRateCheck">If true a zero rate will be allowed. This will be the case when the Daily Exchange Rate screen is modal.</param> /// <param name="ALedgerTableRef">A ledger table containg the available ledgers and their base currencies</param> /// <param name="AEarliestAccountingPeriodStartDate">The earliest accounting period start date in all the active ledgers</param> /// <param name="ALatestAccountingPeriodEndDate">The latest accounting period end date in all the active ledgers</param> public static void ValidateDailyExchangeRate(object AContext, ADailyExchangeRateRow ARow, ref TVerificationResultCollection AVerificationResultCollection, DateTime AMinDateTime, DateTime AMaxDateTime, bool AIgnoreZeroRateCheck, ALedgerTable ALedgerTableRef, DateTime AEarliestAccountingPeriodStartDate, DateTime ALatestAccountingPeriodEndDate) { DataColumn ValidationColumn; TVerificationResult VerificationResult; // Don't validate deleted DataRows if (ARow.RowState == DataRowState.Deleted) { return; } // RateOfExchange must be positive (definitely not zero unless in modal mode) ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnRateOfExchangeId]; if (true) { if (AIgnoreZeroRateCheck) { VerificationResult = TNumericalChecks.IsPositiveOrZeroDecimal(ARow.RateOfExchange, String.Empty, AContext, ValidationColumn); } else { VerificationResult = TNumericalChecks.IsPositiveDecimal(ARow.RateOfExchange, String.Empty, AContext, ValidationColumn); } // Handle addition/removal to/from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult); } // Date must not be empty and must be in range ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnDateEffectiveFromId]; if (true) { VerificationResult = TValidationControlHelper.IsNotInvalidDate(ARow.DateEffectiveFrom, String.Empty, AVerificationResultCollection, true, AContext, ValidationColumn); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult); if (VerificationResult == null) { if ((AMinDateTime > DateTime.MinValue) && (AMaxDateTime < DateTime.MaxValue)) { // Check that the date is in range VerificationResult = TDateChecks.IsDateBetweenDates(ARow.DateEffectiveFrom, AMinDateTime, AMaxDateTime, String.Empty, TDateBetweenDatesCheckType.dbdctUnspecific, TDateBetweenDatesCheckType.dbdctUnspecific, AContext, ValidationColumn); } else if (AMaxDateTime < DateTime.MaxValue) { VerificationResult = TDateChecks.FirstLesserOrEqualThanSecondDate(ARow.DateEffectiveFrom, AMaxDateTime, String.Empty, Ict.Common.StringHelper.DateToLocalizedString(AMaxDateTime), AContext, ValidationColumn); if ((VerificationResult == null) && (ARow.RowState == DataRowState.Added)) { // even without a specific minimum date it should not be too far back if (ARow.DateEffectiveFrom < AEarliestAccountingPeriodStartDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is before the start of the earliest current accounting period of any active ledger."), TResultSeverity.Resv_Noncritical); } } } else if (AMinDateTime > DateTime.MinValue) { VerificationResult = TDateChecks.FirstGreaterOrEqualThanSecondDate(ARow.DateEffectiveFrom, AMinDateTime, String.Empty, Ict.Common.StringHelper.DateToLocalizedString(AMinDateTime), AContext, ValidationColumn); if ((VerificationResult == null) && (ARow.RowState == DataRowState.Added)) { // even without a specific maximum date it should not be too far ahead if (ARow.DateEffectiveFrom > ALatestAccountingPeriodEndDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is after the end of the latest forwarding period of any active ledger."), TResultSeverity.Resv_Noncritical); } } } else if ((AMinDateTime == DateTime.MinValue) && (AMaxDateTime == DateTime.MaxValue) && (ARow.RowState == DataRowState.Added)) { // even without a specific maximum date it should not be too far ahead if (ARow.DateEffectiveFrom > ALatestAccountingPeriodEndDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is after the end of the latest forwarding period of any active ledger."), TResultSeverity.Resv_Noncritical); } // even without a specific minimum date it should not be too far back else if (ARow.DateEffectiveFrom < AEarliestAccountingPeriodStartDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is before the start of the earliest current accounting period of any active ledger."), TResultSeverity.Resv_Noncritical); } } // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult); } } // Time must not be negative (indicating an error) ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnTimeEffectiveFromId]; if (true) { VerificationResult = TTimeChecks.IsValidIntegerTime(ARow.TimeEffectiveFrom, String.Empty, AContext, ValidationColumn); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult); } if (true) { // These tests are for the GUI only ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnToCurrencyCodeId]; if (true) { // One of the currencies should be the base currency of one of the ledgers if (ARow.RowState == DataRowState.Added) { // Only do this test if the To Currency ComboBox is enabled TScreenVerificationResult vr = null; DataView fromView = new DataView(ALedgerTableRef, String.Format("{0}='{1}'", ALedgerTable.GetBaseCurrencyDBName(), ARow.FromCurrencyCode), String.Empty, DataViewRowState.CurrentRows); if (fromView.Count == 0) { DataView toView = new DataView(ALedgerTableRef, String.Format("{0}='{1}'", ALedgerTable.GetBaseCurrencyDBName(), ARow.ToCurrencyCode), String.Empty, DataViewRowState.CurrentRows); if (toView.Count == 0) { vr = new TScreenVerificationResult(AContext, ValidationColumn, "One of the currencies should normally be a base currency for one of the Ledgers", TResultSeverity.Resv_Noncritical); } } // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, vr); } } } }
public static ExchangeRateTDS LoadDailyExchangeRateData() { ExchangeRateTDS WorkingDS = new ExchangeRateTDS(); WorkingDS.EnforceConstraints = false; TDBTransaction Transaction = null; DBAccess.GDBAccessObj.GetNewOrExistingAutoReadTransaction(IsolationLevel.ReadCommitted, TEnforceIsolationLevel.eilMinimum, ref Transaction, delegate { // Load the table so we can read bits of it into our dataset ADailyExchangeRateTable exchangeRates = ADailyExchangeRateAccess.LoadAll(Transaction); // Populate the ExchangeRateTDSADailyExchangeRate table //-- This is the complete query for the DAILYEXCHANGERATE TABLE //-- It returns all the rows from the DailyExchangeRate table //-- PLUS all rows from the Journal and Gift Batch tables that are NOT referenced by the Daily Exchange Rate table. string strSQL = "SELECT * FROM ( "; // -- This part of the query returns all the rows from the ExchangeRate table // -- All rows in Journal and Gift that DO match an entry are reported in the usage count // -- It will always return exactly the same number of rows that are in the Daily Exchange Rate table itself. // -- In the development database case it returns 312 rows. // -- It includes 86 Journal entries and 1 Gift Batch entry strSQL += "SELECT der.a_from_currency_code_c, "; strSQL += " der.a_to_currency_code_c, "; strSQL += " der.a_rate_of_exchange_n, "; strSQL += " der.a_date_effective_from_d, "; strSQL += " der.a_time_effective_from_i, "; strSQL += String.Format( " count(j.a_journal_number_i) AS {0}, count(gb.a_batch_number_i) AS {1}, 'DEX' as {2} ", ExchangeRateTDSADailyExchangeRateTable.GetJournalUsageDBName(), ExchangeRateTDSADailyExchangeRateTable.GetGiftBatchUsageDBName(), ExchangeRateTDSADailyExchangeRateTable.GetTableSourceDBName()); strSQL += "FROM PUB_a_daily_exchange_rate AS der "; strSQL += "LEFT OUTER JOIN PUB_a_journal j "; strSQL += " ON j.a_transaction_currency_c = der.a_from_currency_code_c "; strSQL += " AND j.a_base_currency_c = der.a_to_currency_code_c "; strSQL += " AND j.a_exchange_rate_to_base_n = der.a_rate_of_exchange_n "; strSQL += " AND j.a_date_effective_d = der.a_date_effective_from_d "; strSQL += " AND a_exchange_rate_time_i = der.a_time_effective_from_i "; strSQL += "LEFT OUTER JOIN PUB_a_gift_batch AS gb "; strSQL += " ON gb.a_currency_code_c = der.a_from_currency_code_c "; strSQL += " AND gb.a_exchange_rate_to_base_n = der.a_rate_of_exchange_n "; strSQL += " AND gb.a_gl_effective_date_d = der.a_date_effective_from_d "; strSQL += "LEFT OUTER JOIN PUB_a_ledger AS ldg "; strSQL += " ON ldg.a_ledger_number_i = gb.a_ledger_number_i "; strSQL += " AND ldg.a_base_currency_c = der.a_to_currency_code_c "; strSQL += "GROUP BY der.a_from_currency_code_c, der.a_to_currency_code_c, der.a_rate_of_exchange_n, der.a_date_effective_from_d, der.a_time_effective_from_i "; strSQL += Environment.NewLine; strSQL += "UNION "; strSQL += Environment.NewLine; // -- This part of the query returns all the rows from the journal table that do NOT have an // -- entry in the exchange rate table // -- Using the devlopment database it returns 41 unique rows associated with 53 Journal entries strSQL += "SELECT "; strSQL += " j.a_transaction_currency_c AS a_from_currency_code_c, "; strSQL += " j.a_base_currency_c AS a_to_currency_code_c, "; strSQL += " j.a_exchange_rate_to_base_n AS a_rate_of_exchange_n, "; strSQL += " j.a_date_effective_d AS a_date_effective_from_d, "; strSQL += " j.a_exchange_rate_time_i AS a_time_effective_from_i, "; strSQL += String.Format( " count(j.a_transaction_currency_c) AS {0}, 0 AS {1}, 'J' AS {2} ", ExchangeRateTDSADailyExchangeRateTable.GetJournalUsageDBName(), ExchangeRateTDSADailyExchangeRateTable.GetGiftBatchUsageDBName(), ExchangeRateTDSADailyExchangeRateTable.GetTableSourceDBName()); strSQL += "FROM a_journal j "; strSQL += "LEFT JOIN a_daily_exchange_rate der "; strSQL += " ON der.a_from_currency_code_c = j.a_transaction_currency_c "; strSQL += " AND der.a_to_currency_code_c = j.a_base_currency_c "; strSQL += " AND der.a_date_effective_from_d = j.a_date_effective_d "; strSQL += " AND der.a_time_effective_from_i = j.a_exchange_rate_time_i "; strSQL += " AND der.a_rate_of_exchange_n = j.a_exchange_rate_to_base_n "; strSQL += "WHERE j.a_transaction_currency_c <> j.a_base_currency_c "; strSQL += " AND der.a_from_currency_code_c IS NULL "; strSQL += "GROUP BY j.a_transaction_currency_c, j.a_base_currency_c, j.a_exchange_rate_to_base_n, j.a_date_effective_d, j.a_exchange_rate_time_i "; strSQL += Environment.NewLine; strSQL += "UNION "; strSQL += Environment.NewLine; // -- This part of the query returns all the rows in the gift batch table that do NOT have an // -- entry in the exchange rate table // -- Using the devlopment database it returns 0 rows, because the one row in the gift batch table has already been included // -- in the first query (on the Daily Exchange rate table) strSQL += "SELECT "; strSQL += " gb.a_currency_code_c AS a_from_currency_code_c, "; strSQL += " ldg.a_base_currency_c AS a_to_currency_code_c, "; strSQL += " gb.a_exchange_rate_to_base_n AS a_rate_of_exchange_n, "; strSQL += " gb.a_gl_effective_date_d AS a_date_effective_from_d, "; strSQL += " 0 AS a_time_effective_from_i, "; strSQL += String.Format( " 0 AS {0}, count(gb.a_currency_code_c) AS {1}, 'GB' AS {2} ", ExchangeRateTDSADailyExchangeRateTable.GetJournalUsageDBName(), ExchangeRateTDSADailyExchangeRateTable.GetGiftBatchUsageDBName(), ExchangeRateTDSADailyExchangeRateTable.GetTableSourceDBName()); strSQL += "FROM a_gift_batch gb "; strSQL += "LEFT JOIN a_ledger ldg "; strSQL += " ON ldg.a_ledger_number_i = gb.a_ledger_number_i "; strSQL += "LEFT JOIN a_daily_exchange_rate der "; strSQL += " ON der.a_from_currency_code_c = gb.a_currency_code_c "; strSQL += " AND der.a_to_currency_code_c = ldg.a_base_currency_c "; strSQL += " AND der.a_date_effective_from_d = gb.a_gl_effective_date_d "; strSQL += " AND der.a_rate_of_exchange_n = gb.a_exchange_rate_to_base_n "; strSQL += "WHERE gb.a_currency_code_c <> ldg.a_base_currency_c "; strSQL += " AND der.a_from_currency_code_c IS NULL "; strSQL += "GROUP BY gb.a_currency_code_c, ldg.a_base_currency_c, gb.a_exchange_rate_to_base_n, gb.a_gl_effective_date_d "; strSQL += ") AS allrates "; strSQL += "ORDER BY allrates.a_date_effective_from_d DESC, allrates.a_time_effective_from_i DESC "; DBAccess.GDBAccessObj.Select(WorkingDS, strSQL, WorkingDS.ADailyExchangeRate.TableName, Transaction); // Now populate the ExchangeRateTDSADailyExchangerateUsage table //-- COMPLETE QUERY TO RETURN ADailyExchangeRateUsage //-- Query to return the Daily Exchange Rate Usage details //-- Only returns rows that are in a foreign currency //-- Querying this table by from/to/date/time will return one row per use case //-- If the Journal is 0 the batch refers to a gift batch, otherwise it is a GL batch strSQL = "SELECT * FROM ( "; //-- This part of the query returns the use cases from the Journal table strSQL += "SELECT "; strSQL += " j.a_transaction_currency_c AS a_from_currency_code_c, "; strSQL += " j.a_base_currency_c AS a_to_currency_code_c, "; strSQL += " j.a_exchange_rate_to_base_n AS a_rate_of_exchange_n, "; strSQL += " j.a_date_effective_d AS a_date_effective_from_d, "; strSQL += " j.a_exchange_rate_time_i AS a_time_effective_from_i, "; strSQL += String.Format( " j.a_ledger_number_i AS {0}, j.a_batch_number_i AS {1}, j.a_journal_number_i AS {2}, b.a_batch_status_c AS {3}, b.a_batch_year_i AS {4}, b.a_batch_period_i AS {5}, 'J' AS {6} ", ExchangeRateTDSADailyExchangeRateUsageTable.GetLedgerNumberDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchNumberDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetJournalNumberDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchStatusDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchYearDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchPeriodDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetTableSourceDBName()); strSQL += "FROM a_journal j "; strSQL += "JOIN a_batch b "; strSQL += " ON b.a_batch_number_i = j.a_batch_number_i "; strSQL += " AND b.a_ledger_number_i = j.a_ledger_number_i "; strSQL += "WHERE j.a_transaction_currency_c <> j.a_base_currency_c "; strSQL += Environment.NewLine; strSQL += "UNION "; strSQL += Environment.NewLine; //-- This part of the query returns the use cases from the Gift Batch table strSQL += "SELECT "; strSQL += " gb.a_currency_code_c AS a_from_currency_code_c, "; strSQL += " ldg.a_base_currency_c AS a_to_currency_code_c, "; strSQL += " gb.a_exchange_rate_to_base_n AS a_rate_of_exchange_n, "; strSQL += " gb.a_gl_effective_date_d AS a_date_effective_from_d, "; strSQL += " 0 AS a_time_effective_from_i, "; strSQL += String.Format( " gb.a_ledger_number_i AS {0}, gb.a_batch_number_i AS {1}, 0 AS {2}, gb.a_batch_status_c AS {3}, gb.a_batch_year_i AS {4}, gb.a_batch_period_i AS {5}, 'GB' AS {6} ", ExchangeRateTDSADailyExchangeRateUsageTable.GetLedgerNumberDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchNumberDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetJournalNumberDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchStatusDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchYearDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetBatchPeriodDBName(), ExchangeRateTDSADailyExchangeRateUsageTable.GetTableSourceDBName()); strSQL += "FROM a_gift_batch gb "; strSQL += "JOIN a_ledger ldg "; strSQL += " ON ldg.a_ledger_number_i = gb.a_ledger_number_i "; strSQL += "WHERE gb.a_currency_code_c <> ldg.a_base_currency_c "; strSQL += ") AS usage "; strSQL += "ORDER BY usage.a_date_effective_from_d DESC, usage.a_time_effective_from_i DESC "; DBAccess.GDBAccessObj.Select(WorkingDS, strSQL, WorkingDS.ADailyExchangeRateUsage.TableName, Transaction); // Now we start a tricky bit to resolve potential primary key conflicts when the constraints are turned on. // By combining the Journal and Gift Batch data that is not referenced in the exchange rate table we can easily // have introduced conflicts where more than one rate has been used for a given currency pair and effective date/time. // This is because there is no constraint that enforces the batch/journal tables to use a time from the exch rate table. // So we have to go through all the rows in our data table and potentially change the time to make it possible to get our primary key. // Start by creating a data view on the whole result set. The oredering is important because we are going to step through the set row by row. // We order on: From, To, Date, Time, JournalUsage, GiftBatchUsage // We need to deal with the following possibilities: // From To Date Time Rate Journals Gifts TableSource // EUR GBP 2014-01-01 1234 2.115 0 0 DEX // EUR GBP 2014-01-01 1234 2.11 3 0 J // EUR GBP 2014-01-01 1234 2.22 1 0 J // EUR GBP 2014-01-01 1234 3.11 0 1 GB // // In the first row we have an entry from the DEX table that is not used anywhere, but a (slightly) different rate is actually used // in a Journal. So we actually don't show the DEX row. // In the other rows we have 3 different rates - all used somewhere. We need to adjust the times so they are different. DataView dv = new DataView(WorkingDS.ADailyExchangeRate, "", String.Format("{0}, {1}, {2} DESC, {3} DESC, {4}, {5}", ADailyExchangeRateTable.GetFromCurrencyCodeDBName(), ADailyExchangeRateTable.GetToCurrencyCodeDBName(), ADailyExchangeRateTable.GetDateEffectiveFromDBName(), ADailyExchangeRateTable.GetTimeEffectiveFromDBName(), ExchangeRateTDSADailyExchangeRateTable.GetJournalUsageDBName(), ExchangeRateTDSADailyExchangeRateTable.GetGiftBatchUsageDBName()), DataViewRowState.CurrentRows); for (int i = 0; i < dv.Count - 1; i++) { // Get the 'current' row and the 'next' one... ExchangeRateTDSADailyExchangeRateRow drThis = (ExchangeRateTDSADailyExchangeRateRow)dv[i].Row; ExchangeRateTDSADailyExchangeRateRow drNext = (ExchangeRateTDSADailyExchangeRateRow)dv[i + 1].Row; if ((drThis.JournalUsage == 0) && (drThis.GiftBatchUsage == 0)) { // This will be a row that the client can edit/delete, so we need to add the modification info ADailyExchangeRateRow foundRow = (ADailyExchangeRateRow)exchangeRates.Rows.Find(new object[] { drThis.FromCurrencyCode, drThis.ToCurrencyCode, drThis.DateEffectiveFrom, drThis.TimeEffectiveFrom }); if (foundRow != null) { // it should always be non-null drThis.BeginEdit(); drThis.ModificationId = foundRow.ModificationId; drThis.DateModified = foundRow.DateModified; drThis.ModifiedBy = foundRow.ModifiedBy; drThis.DateCreated = foundRow.DateCreated; drThis.CreatedBy = foundRow.CreatedBy; drThis.EndEdit(); } } if (!drThis.FromCurrencyCode.Equals(drNext.FromCurrencyCode) || !drThis.ToCurrencyCode.Equals(drNext.ToCurrencyCode) || !drThis.DateEffectiveFrom.Equals(drNext.DateEffectiveFrom) || !drThis.TimeEffectiveFrom.Equals(drNext.TimeEffectiveFrom)) { // Something is different so our primary key will be ok for the current row continue; } // We have got two (or more) rows with the same potential primary key and different rates. // We need to work out how many rows ahead also have the same time and adjust them all bool moveForwards = (drThis.TimeEffectiveFrom < 43200); int timeOffset = 60; // 1 minute // Start by adjusting our 'next' row we are already working with drNext.BeginEdit(); int prevTimeEffectiveFrom = drNext.TimeEffectiveFrom; drNext.TimeEffectiveFrom = (moveForwards) ? prevTimeEffectiveFrom + timeOffset : prevTimeEffectiveFrom - timeOffset; timeOffset = (moveForwards) ? timeOffset + 60 : timeOffset - 60; drNext.EndEdit(); i++; // we can increment our main loop counter now that we have dealt with our 'next' row. // Modify all the rows in the usage table that refer to the previous time OnModifyEffectiveTime(WorkingDS.ADailyExchangeRateUsage, drNext.FromCurrencyCode, drNext.ToCurrencyCode, drNext.DateEffectiveFrom, prevTimeEffectiveFrom, drNext.TimeEffectiveFrom, drNext.RateOfExchange); // Now look ahead even further than the 'next' row and modify those times too, adding 1 more minute to each for (int k = i + 2;; k++) { ExchangeRateTDSADailyExchangeRateRow drLookAhead = (ExchangeRateTDSADailyExchangeRateRow)dv[k].Row; if (!drThis.FromCurrencyCode.Equals(drLookAhead.FromCurrencyCode) || !drThis.ToCurrencyCode.Equals(drLookAhead.ToCurrencyCode) || !drThis.DateEffectiveFrom.Equals(drLookAhead.DateEffectiveFrom) || !drThis.TimeEffectiveFrom.Equals(drLookAhead.TimeEffectiveFrom)) { // No more rows match our potential primary key conflict on the 'current' row. break; } // Do exactly the same to this row as we did to the 'next' row above drLookAhead.BeginEdit(); prevTimeEffectiveFrom = drLookAhead.TimeEffectiveFrom; drLookAhead.TimeEffectiveFrom = (moveForwards) ? prevTimeEffectiveFrom + timeOffset : prevTimeEffectiveFrom - timeOffset; timeOffset = (moveForwards) ? timeOffset + 60 : timeOffset - 60; drLookAhead.EndEdit(); i++; OnModifyEffectiveTime(WorkingDS.ADailyExchangeRateUsage, drLookAhead.FromCurrencyCode, drLookAhead.ToCurrencyCode, drLookAhead.DateEffectiveFrom, prevTimeEffectiveFrom, drLookAhead.TimeEffectiveFrom, drLookAhead.RateOfExchange); } } // check the next row in the table so that it becomes the 'current' row. WorkingDS.EnforceConstraints = true; // Load the Corporate exchange rate table using the usual method ACorporateExchangeRateAccess.LoadAll(WorkingDS, Transaction); }); // Accept row changes here so that the Client gets 'unmodified' rows WorkingDS.AcceptChanges(); return(WorkingDS); }
/// <summary> /// Validates the Daily Exchange Rates screen data. /// </summary> /// <param name="AContext">Context that describes where the data validation failed.</param> /// <param name="ARow">The <see cref="DataRow" /> which holds the the data against which the validation is run.</param> /// <param name="AVerificationResultCollection">Will be filled with any <see cref="TVerificationResult" /> items if /// data validation errors occur.</param> /// <param name="AValidationControlsDict">A <see cref="TValidationControlsDict" /> containing the Controls that /// display data that is about to be validated.</param> /// <param name="AMinDateTime">The earliest allowable date.</param> /// <param name="AMaxDateTime">The latest allowable date.</param> /// <param name="AIgnoreZeroRateCheck">If true a zero rate will be allowed. This will be the case when the Daily Exchange Rate screen is modal.</param> /// <param name="ALedgerTableRef">A ledger table containg the available ledgers and their base currencies</param> /// <param name="AEarliestAccountingPeriodStartDate">The earliest accounting period start date in all the active ledgers</param> /// <param name="ALatestAccountingPeriodEndDate">The latest accounting period end date in all the active ledgers</param> public static void ValidateDailyExchangeRate(object AContext, ADailyExchangeRateRow ARow, ref TVerificationResultCollection AVerificationResultCollection, TValidationControlsDict AValidationControlsDict, DateTime AMinDateTime, DateTime AMaxDateTime, bool AIgnoreZeroRateCheck, ALedgerTable ALedgerTableRef, DateTime AEarliestAccountingPeriodStartDate, DateTime ALatestAccountingPeriodEndDate) { DataColumn ValidationColumn; TValidationControlsData ValidationControlsData; TVerificationResult VerificationResult; // Don't validate deleted DataRows if (ARow.RowState == DataRowState.Deleted) { return; } // RateOfExchange must be positive (definitely not zero unless in modal mode) ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnRateOfExchangeId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { if (AIgnoreZeroRateCheck) { VerificationResult = TNumericalChecks.IsPositiveOrZeroDecimal(ARow.RateOfExchange, ValidationControlsData.ValidationControlLabel, AContext, ValidationColumn, ValidationControlsData.ValidationControl); } else { VerificationResult = TNumericalChecks.IsPositiveDecimal(ARow.RateOfExchange, ValidationControlsData.ValidationControlLabel, AContext, ValidationColumn, ValidationControlsData.ValidationControl); } // Handle addition/removal to/from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } // Date must not be empty and must be in range ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnDateEffectiveFromId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { VerificationResult = TSharedValidationControlHelper.IsNotInvalidDate(ARow.DateEffectiveFrom, ValidationControlsData.ValidationControlLabel, AVerificationResultCollection, true, AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); if (VerificationResult == null) { if ((AMinDateTime > DateTime.MinValue) && (AMaxDateTime < DateTime.MaxValue)) { // Check that the date is in range VerificationResult = TDateChecks.IsDateBetweenDates(ARow.DateEffectiveFrom, AMinDateTime, AMaxDateTime, ValidationControlsData.ValidationControlLabel, TDateBetweenDatesCheckType.dbdctUnspecific, TDateBetweenDatesCheckType.dbdctUnspecific, AContext, ValidationColumn, ValidationControlsData.ValidationControl); } else if (AMaxDateTime < DateTime.MaxValue) { VerificationResult = TDateChecks.FirstLesserOrEqualThanSecondDate(ARow.DateEffectiveFrom, AMaxDateTime, ValidationControlsData.ValidationControlLabel, Ict.Common.StringHelper.DateToLocalizedString(AMaxDateTime), AContext, ValidationColumn, ValidationControlsData.ValidationControl); if ((VerificationResult == null) && (ARow.RowState == DataRowState.Added) && (AContext is System.Windows.Forms.Form || AContext is System.Windows.Forms.UserControl)) { // even without a specific minimum date it should not be too far back if (ARow.DateEffectiveFrom < AEarliestAccountingPeriodStartDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is before the start of the earliest current accounting period of any active ledger."), ValidationControlsData.ValidationControl, TResultSeverity.Resv_Noncritical); } } } else if (AMinDateTime > DateTime.MinValue) { VerificationResult = TDateChecks.FirstGreaterOrEqualThanSecondDate(ARow.DateEffectiveFrom, AMinDateTime, ValidationControlsData.ValidationControlLabel, Ict.Common.StringHelper.DateToLocalizedString(AMinDateTime), AContext, ValidationColumn, ValidationControlsData.ValidationControl); if ((VerificationResult == null) && (ARow.RowState == DataRowState.Added) && (AContext is System.Windows.Forms.Form || AContext is System.Windows.Forms.UserControl)) { // even without a specific maximum date it should not be too far ahead if (ARow.DateEffectiveFrom > ALatestAccountingPeriodEndDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is after the end of the latest forwarding period of any active ledger."), ValidationControlsData.ValidationControl, TResultSeverity.Resv_Noncritical); } } } else if ((AMinDateTime == DateTime.MinValue) && (AMaxDateTime == DateTime.MaxValue) && (ARow.RowState == DataRowState.Added) && (AContext is System.Windows.Forms.Form || AContext is System.Windows.Forms.UserControl)) { // even without a specific maximum date it should not be too far ahead if (ARow.DateEffectiveFrom > ALatestAccountingPeriodEndDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is after the end of the latest forwarding period of any active ledger."), ValidationControlsData.ValidationControl, TResultSeverity.Resv_Noncritical); } // even without a specific minimum date it should not be too far back else if (ARow.DateEffectiveFrom < AEarliestAccountingPeriodStartDate) { VerificationResult = new TScreenVerificationResult(AContext, ValidationColumn, Catalog.GetString( "The date is before the start of the earliest current accounting period of any active ledger."), ValidationControlsData.ValidationControl, TResultSeverity.Resv_Noncritical); } } // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } } // Time must not be negative (indicating an error) ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnTimeEffectiveFromId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { VerificationResult = TTimeChecks.IsValidIntegerTime(ARow.TimeEffectiveFrom, ValidationControlsData.ValidationControlLabel, AContext, ValidationColumn, ValidationControlsData.ValidationControl); // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); } if (AContext is System.Windows.Forms.Form || AContext is System.Windows.Forms.UserControl) { // These tests are for the GUI only ValidationColumn = ARow.Table.Columns[ADailyExchangeRateTable.ColumnToCurrencyCodeId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { // One of the currencies should be the base currency of one of the ledgers if (ValidationControlsData.ValidationControl.Enabled && (ARow.RowState == DataRowState.Added)) { // Only do this test if the To Currency ComboBox is enabled TScreenVerificationResult vr = null; DataView fromView = new DataView(ALedgerTableRef, String.Format("{0}='{1}'", ALedgerTable.GetBaseCurrencyDBName(), ARow.FromCurrencyCode), String.Empty, DataViewRowState.CurrentRows); if (fromView.Count == 0) { DataView toView = new DataView(ALedgerTableRef, String.Format("{0}='{1}'", ALedgerTable.GetBaseCurrencyDBName(), ARow.ToCurrencyCode), String.Empty, DataViewRowState.CurrentRows); if (toView.Count == 0) { vr = new TScreenVerificationResult(AContext, ValidationColumn, "One of the currencies should normally be a base currency for one of the Ledgers", ValidationControlsData.ValidationControl, TResultSeverity.Resv_Noncritical); } } // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, vr, ValidationColumn); } } } }
/// <summary> /// init the exchange rate, to avoid messages "Cannot find exchange rate for EUR USD" /// </summary> public static void InitExchangeRate(TDataBase ADataBase = null) { TDataBase db = DBAccess.Connect("InitExchangeRate", ADataBase); TDBTransaction Transaction = new TDBTransaction(); bool SubmitOK = false; db.WriteTransaction(ref Transaction, ref SubmitOK, delegate { TAccountPeriodInfo AccountingPeriodInfo = new TAccountPeriodInfo(FLedgerNumber, 1); ADailyExchangeRateTable dailyrates = new ADailyExchangeRateTable(); ADailyExchangeRateRow row = dailyrates.NewRowTyped(true); row.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; row.TimeEffectiveFrom = 100; row.FromCurrencyCode = "USD"; row.ToCurrencyCode = "EUR"; row.RateOfExchange = 1.34m; dailyrates.Rows.Add(row); row = dailyrates.NewRowTyped(true); row.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; row.TimeEffectiveFrom = 100; row.FromCurrencyCode = "USD"; row.ToCurrencyCode = "GBP"; row.RateOfExchange = 1.57m; dailyrates.Rows.Add(row); if (!ADailyExchangeRateAccess.Exists(row.FromCurrencyCode, row.ToCurrencyCode, row.DateEffectiveFrom, row.TimeEffectiveFrom, Transaction)) { ADailyExchangeRateAccess.SubmitChanges(dailyrates, Transaction); } ALedgerTable Ledger = ALedgerAccess.LoadByPrimaryKey(FLedgerNumber, Transaction); for (int periodCounter = 1; periodCounter <= Ledger[0].NumberOfAccountingPeriods + Ledger[0].NumberFwdPostingPeriods; periodCounter++) { AccountingPeriodInfo = new TAccountPeriodInfo(FLedgerNumber, periodCounter); ACorporateExchangeRateTable corprates = new ACorporateExchangeRateTable(); ACorporateExchangeRateRow corprow = corprates.NewRowTyped(true); corprow.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; corprow.TimeEffectiveFrom = 100; corprow.FromCurrencyCode = "USD"; corprow.ToCurrencyCode = "EUR"; corprow.RateOfExchange = 1.34m; corprates.Rows.Add(corprow); corprow = corprates.NewRowTyped(true); corprow.DateEffectiveFrom = AccountingPeriodInfo.PeriodStartDate; corprow.TimeEffectiveFrom = 100; corprow.FromCurrencyCode = "USD"; corprow.ToCurrencyCode = "GBP"; corprow.RateOfExchange = 1.57m; corprates.Rows.Add(corprow); if (!ACorporateExchangeRateAccess.Exists(corprow.FromCurrencyCode, corprow.ToCurrencyCode, corprow.DateEffectiveFrom, Transaction)) { ACorporateExchangeRateAccess.SubmitChanges(corprates, Transaction); } } SubmitOK = true; }); if (ADataBase == null) { db.CloseDBConnection(); } }
/// <summary> /// Imports currency exchange rates, daily and corporate, /// from a one-of-two-styles formatted CSV file /// </summary> /// <param name="AExchangeRDT">Daily or Corporate exchange rate table</param> /// <param name="ADataFilename">The .CSV file to process</param> /// <param name="ACSVSeparator"></param> /// <param name="ANumberFormat"></param> /// <param name="ADateFormat"></param> /// <param name="AImportMode">Daily or Corporate</param> /// <param name="AResultCollection">A validation collection to which errors will be added</param> /// <returns>The number of rows that were actually imported. Rows that duplicate existing rows do not count. /// This is usually because this is an attempt to import again after a failed previous attempt.</returns> private static int ImportCurrencyExRatesFromCSV(TTypedDataTable AExchangeRDT, string ADataFilename, string ACSVSeparator, string ANumberFormat, string ADateFormat, string AImportMode, TVerificationResultCollection AResultCollection) { if ((AImportMode != "Corporate") && (AImportMode != "Daily")) { throw new ArgumentException("Invalid value '" + AImportMode + "' for mode argument: Valid values are Corporate and Daily"); } else if ((AImportMode == "Corporate") && (AExchangeRDT.GetType() != typeof(ACorporateExchangeRateTable))) { throw new ArgumentException("Invalid type of exchangeRateDT argument for mode: 'Corporate'. Needs to be: ACorporateExchangeRateTable"); } else if ((AImportMode == "Daily") && (AExchangeRDT.GetType() != typeof(ADailyExchangeRateTable))) { throw new ArgumentException("Invalid type of exchangeRateDT argument for mode: 'Daily'. Needs to be: ADailyExchangeRateTable"); } bool IsShortFileFormat; int x, y; // To store the From and To currencies // Use an array to store these to make for easy // inverting of the two currencies when calculating // the inverse value. string[] Currencies = new string[2]; Type DataTableType; int RowsImported = 0; ACurrencyTable allCurrencies = new ACurrencyTable(); DataTable CacheDT = TDataCache.GetCacheableDataTableFromCache("CurrencyCodeList", String.Empty, null, out DataTableType); allCurrencies.Merge(CacheDT); using (StreamReader DataFile = new StreamReader(ADataFilename, System.Text.Encoding.Default)) { string ThousandsSeparator = (ANumberFormat == TDlgSelectCSVSeparator.NUMBERFORMAT_AMERICAN ? "," : "."); string DecimalSeparator = (ANumberFormat == TDlgSelectCSVSeparator.NUMBERFORMAT_AMERICAN ? "." : ","); CultureInfo MyCultureInfoDate = new CultureInfo("en-GB"); MyCultureInfoDate.DateTimeFormat.ShortDatePattern = ADateFormat; // TODO: disconnect the grid from the datasource to avoid flickering? string FileNameWithoutExtension = Path.GetFileNameWithoutExtension(ADataFilename); if ((FileNameWithoutExtension.IndexOf("_") == 3) && (FileNameWithoutExtension.LastIndexOf("_") == 3) && (FileNameWithoutExtension.Length == 7)) { // File name format assumed to be like this: USD_HKD.csv IsShortFileFormat = true; Currencies = FileNameWithoutExtension.Split(new char[] { '_' }); } else { IsShortFileFormat = false; } int LineNumber = 0; while (!DataFile.EndOfStream) { string Line = DataFile.ReadLine(); LineNumber++; // See if the first line is a special case?? if (LineNumber == 1) { // see if the first line is a text header - look for digits // A valid header would look like: From,To,Date,Rate bool bFoundDigit = false; for (int i = 0; i < Line.Length; i++) { char c = Line[i]; if ((c >= '0') && (c <= '9')) { bFoundDigit = true; break; } } if (!bFoundDigit) { // No digits so we will assume the line is a header continue; } } //Convert separator to a char char Sep = ACSVSeparator[0]; //Turn current line into string array of column values string[] CsvColumns = Line.Split(Sep); int NumCols = CsvColumns.Length; //If number of columns is not 4 then import csv file is wrongly formed. if (IsShortFileFormat && (NumCols < 2)) { // raise an error string resultText = String.Format(Catalog.GetString("Failed to import the CSV currency file:{0} {1}{0}{0}"), Environment.NewLine, ADataFilename); resultText += String.Format(Catalog.GetString( "Line #{1} contains {2} column(s). Import files with names like 'USD_HKD.csv', where the From and To currencies are given in the name, should contain 2 or 3 columns:{0}{0}"), Environment.NewLine, LineNumber, NumCols.ToString()); resultText += String.Format(Catalog.GetString( " 1. Effective Date{0} 2. Exchange Rate{0} 3. Effective time in seconds (Optional for Daily Rate only)"), Environment.NewLine); TVerificationResult result = new TVerificationResult(AImportMode, resultText, CommonErrorCodes.ERR_INFORMATIONMISSING, TResultSeverity.Resv_Critical); AResultCollection.Add(result); return(RowsImported); } else if (!IsShortFileFormat && (NumCols < 4)) { string resultText = String.Format(Catalog.GetString("Failed to import the CSV currency file:{0} {1}{0}{0}"), Environment.NewLine, ADataFilename); resultText += String.Format(Catalog.GetString("Line #{1} contains {2} column(s). It should have 4 or 5 as follows:{0}{0}"), Environment.NewLine, LineNumber, NumCols.ToString()); resultText += String.Format(Catalog.GetString( " 1. From Currency{0} 2. To Currency{0} 3. Effective Date{0} 4. Exchange Rate{0} 5. Effective time in seconds (Optional for Daily Rate only)"), Environment.NewLine); TVerificationResult result = new TVerificationResult(AImportMode, resultText, CommonErrorCodes.ERR_INFORMATIONMISSING, TResultSeverity.Resv_Critical); AResultCollection.Add(result); return(RowsImported); } if (!IsShortFileFormat) { //Read the values for the current line //From currency Currencies[0] = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).ToString(); //To currency Currencies[1] = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).ToString(); } // Perform validation on the From and To currencies at this point!! if ((allCurrencies.Rows.Find(Currencies[0]) == null) || (allCurrencies.Rows.Find(Currencies[1]) == null)) { // raise an error string resultText = String.Format(Catalog.GetString("Invalid currency in import file in line #{0}"), LineNumber.ToString()); TVerificationResult result = new TVerificationResult(AImportMode, resultText, CommonErrorCodes.ERR_INCONGRUOUSSTRINGS, TResultSeverity.Resv_Critical); AResultCollection.Add(result); return(RowsImported); } // Date parsing as in Petra 2.x instead of using XML date format!!! string DateEffectiveStr = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).Replace("\"", String.Empty); DateTime DateEffective; if (!DateTime.TryParse(DateEffectiveStr, MyCultureInfoDate, DateTimeStyles.None, out DateEffective)) { // raise an error string resultText = String.Format(Catalog.GetString( "Invalid date ({0}) in import file in line #{1}"), DateEffectiveStr, LineNumber.ToString()); TVerificationResult result = new TVerificationResult(AImportMode, resultText, CommonErrorCodes.ERR_INVALIDDATE, TResultSeverity.Resv_Critical); AResultCollection.Add(result); return(RowsImported); } decimal ExchangeRate = 0.0m; try { string ExchangeRateString = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true).Replace(ThousandsSeparator, "").Replace( DecimalSeparator, ".").Replace("\"", String.Empty); ExchangeRate = Convert.ToDecimal(ExchangeRateString, System.Globalization.CultureInfo.InvariantCulture); } catch (Exception) { // raise an error string resultText = String.Format(Catalog.GetString( "Invalid rate of exchange in import file in line #{0}"), LineNumber.ToString()); TVerificationResult result = new TVerificationResult(AImportMode, resultText, CommonErrorCodes.ERR_INVALIDNUMBER, TResultSeverity.Resv_Critical); AResultCollection.Add(result); return(RowsImported); } int TimeEffective = 7200; if (AImportMode == "Daily") { // Daily rate imports can have an optional final column which is the time // Otherwise we assume the time is a default of 7200 (02:00am) if ((IsShortFileFormat && (NumCols == 3)) || (!IsShortFileFormat && (NumCols == 5))) { string timeEffectiveStr = StringHelper.GetNextCSV(ref Line, ACSVSeparator, false, true); int t = (int)new Ict.Common.TypeConverter.TShortTimeConverter().ConvertTo(timeEffectiveStr, typeof(int)); if (t < 0) { // it wasn't in the format 02:00 if (!Int32.TryParse(timeEffectiveStr, out t)) { // Not a regular Int32 either t = -1; } } if ((t >= 0) && (t < 86400)) { TimeEffective = t; } else { // raise an error string resultText = String.Format(Catalog.GetString( "Invalid effective time in import file in line #{0}"), LineNumber.ToString()); TVerificationResult result = new TVerificationResult(AImportMode, resultText, CommonErrorCodes.ERR_INVALIDINTEGERTIME, TResultSeverity.Resv_Critical); AResultCollection.Add(result); return(RowsImported); } } } if ((AImportMode == "Corporate") && AExchangeRDT is ACorporateExchangeRateTable) { ACorporateExchangeRateTable ExchangeRateDT = (ACorporateExchangeRateTable)AExchangeRDT; // run this code in the loop twice to get ExchangeRate value and its inverse for (int i = 0; i <= 1; i++) { //this will cause x and y to go from 0 to 1 and 1 to 0 respectively x = i; y = Math.Abs(i - 1); ACorporateExchangeRateRow ExchangeRow = (ACorporateExchangeRateRow)ExchangeRateDT.Rows. Find(new object[] { Currencies[x], Currencies[y], DateEffective }); if (ExchangeRow == null) // remove 0 for Corporate { ExchangeRow = (ACorporateExchangeRateRow)ExchangeRateDT.NewRowTyped(); ExchangeRow.FromCurrencyCode = Currencies[x]; ExchangeRow.ToCurrencyCode = Currencies[y]; ExchangeRow.DateEffectiveFrom = DateEffective; ExchangeRateDT.Rows.Add(ExchangeRow); RowsImported++; } if (i == 0) { ExchangeRow.RateOfExchange = ExchangeRate; } else { ExchangeRow.RateOfExchange = 1 / ExchangeRate; } } } else if ((AImportMode == "Daily") && AExchangeRDT is ADailyExchangeRateTable) { ADailyExchangeRateTable ExchangeRateDT = (ADailyExchangeRateTable)AExchangeRDT; // run this code in the loop twice to get ExchangeRate value and its inverse for (int i = 0; i <= 1; i++) { //this will cause x and y to go from 0 to 1 and 1 to 0 respectively x = i; y = Math.Abs(i - 1); ADailyExchangeRateRow ExchangeRow = (ADailyExchangeRateRow)ExchangeRateDT.Rows. Find(new object[] { Currencies[x], Currencies[y], DateEffective, TimeEffective }); if (ExchangeRow == null) // remove 0 for Corporate { ExchangeRow = (ADailyExchangeRateRow)ExchangeRateDT.NewRowTyped(); ExchangeRow.FromCurrencyCode = Currencies[x]; ExchangeRow.ToCurrencyCode = Currencies[y]; ExchangeRow.DateEffectiveFrom = DateEffective; ExchangeRow.TimeEffectiveFrom = TimeEffective; ExchangeRateDT.Rows.Add(ExchangeRow); RowsImported++; } if (i == 0) { ExchangeRow.RateOfExchange = ExchangeRate; } else { ExchangeRow.RateOfExchange = 1 / ExchangeRate; } } } } DataFile.Close(); return(RowsImported); } }