多行交易

时间:2016-06-04 15:07:03

标签: c# transactions currency accounting

我有一个问题,我认为可能没有其他答案,然后完全重新考虑应用程序,但希望你们可以证明我错了!

我有一个应用程序使用汇率将货币值的基值计算回四位小数。计算非常简单,每次都通过所有相关的单元测试得出正确的结果:

    public static decimal GetBaseValue(decimal currencyAmount, RateOperator rateOperator, 
        double exchangeRate)
    {
        if (exchangeRate <= 0)
        {
            throw new ArgumentException(ErrorType.
               ExchangeRateLessThanOrEqualToZero.ErrorInfo().Description);
        }

        decimal baseValue = 0;

        if (currencyAmount != 0)
        {
            switch (rateOperator)
            {
                case RateOperator.Divide:
                    baseValue = Math.Round(currencyAmount * Convert.ToDecimal(exchangeRate), 
                        4, MidpointRounding.AwayFromZero);
                    break;
                case RateOperator.Multiply:
                    baseValue = Math.Round(currencyAmount / Convert.ToDecimal(exchangeRate), 
                        4, MidpointRounding.AwayFromZero);
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(rateOperator));
            }
        }

        return baseValue;
    }

我也有相当于从base计算货币,未显示以避免混淆问题,并且在任何情况下除了参数名称和switch语句中的数学运算符的反转之外几乎完全相同的代码。

当我需要将此流程应用于具有多行的事务时,我的问题出现了。规则是借方总额必须等于贷方总额,但有些数字只是一分钱的一小部分。这是因为我们总计两次或多次单独转换的数字与单次转换的结果,我相信累积的舍入会导致错误。

让我们假设以下内容:

  1. 项目价值$ 1.00
  2. 增值税值0.20美元(20%)
  3. 汇率为1.4540美元兑换英镑
  4. 现在让我们回顾一下示例交易:

    Line #  Debit    Credit
    1                $1.20
    2       $1.00
    3       $0.20
    

    这通过了测试,因为总学分与总借记相匹配。

    当我们转换(将每个美元价值除以1.454)时,我们会看到问题:

    Line #  Debit    Credit
    1                £0.8253
    2       £0.6878
    3       £0.1376
    ========================
    Total   £0.8254  £0.8253 
    ========================
    

    这会失败并违反规则,我相信借记栏中的两组舍入和信用列中只有一组。然而,我需要能够准确地计算倒退和转发,并且我需要确保交易在所有行中以货币和基准进行余额。

    所以我的问题是:如何最好地解决这个问题?任何人都经历过类似的事情,如果是这样,你是否介意分享你如何解决它?

    编辑 - 我使用的解决方案

    根据查尔斯的回答,我完全指出了正确的方向,我发布了我的工作方法,以造福其他可能面临类似问题的人。虽然理解这个特定的代码可能不适合直接复制和粘贴解决方案,但我希望这些评论和程序会有所帮助:

        private static void SetBaseValues(Transaction transaction)
        {
            // Get the initial currency totals
            decimal currencyDebitRunningTotal = transaction.CurrencyDebitTotal;
            decimal currencyCreditRunningTotal = transaction.CurrencyCreditTotal;
    
            // Only one conversion, but we do one per column
            // Note that the values should be the same anyway 
            // or the transaction would be invalid
            decimal baseDebitRunningTotal = 
                Functions.GetBaseValue(currencyDebitRunningTotal, 
                transaction.MasterLine.RateOperator, 
                transaction.MasterLine.ExchangeRate);
            decimal baseCreditRunningTotal = 
                Functions.GetBaseValue(currencyCreditRunningTotal,
                transaction.MasterLine.RateOperator, 
                transaction.MasterLine.ExchangeRate);
    
            // Create a list of transaction lines that belong to this transaction
            List<TransactionLineBase> list = new List<TransactionLineBase> 
            { transaction.MasterLine };
            list.AddRange(transaction.TransactionLines);
    
            // If there is no tax line, don't add a null entry 
            // as that would cause conversion failure
            if (transaction.TaxLine != null)
            {
                list.Add(transaction.TaxLine);
            }
    
            // Sort the list ascending by value
            var workingList = list.OrderBy(
                x => x.CurrencyCreditAmount ?? 0 + x.CurrencyDebitAmount ?? 0).ToList();
    
            // Iterate the lines excluding any entries where Credit and Debit 
            // values are both null (this is possible on some rows on 
            // some transactions types e.g. Reconciliations
            foreach (var line in workingList.Where(
                line => line.CurrencyCreditAmount != null || 
                line.CurrencyDebitAmount != null))
            {
                if (transaction.CanConvertCurrency)
                {
                    SetBaseValues(line);
                }
                else
                {
                    var isDebitLine = line.CurrencyCreditAmount == null;
    
                    if (isDebitLine)
                    {
                        if (line.CurrencyDebitAmount != 0)
                        {
                            line.BaseDebitAmount = 
                                line.CurrencyDebitAmount ?? 0 / 
                                currencyDebitRunningTotal * baseDebitRunningTotal;
                            currencyDebitRunningTotal -= 
                                line.CurrencyDebitAmount ?? 0;
                            baseDebitRunningTotal -= line.BaseDebitAmount ?? 0;                            
                        }
                    }
                    else
                    {
                        if (line.CurrencyCreditAmount != 0)
                        {
                            line.BaseCreditAmount = 
                                line.CurrencyCreditAmount ?? 0/
                           currencyCreditRunningTotal*baseCreditRunningTotal;
                            currencyCreditRunningTotal -= line.CurrencyCreditAmount ?? 0;
                            baseCreditRunningTotal -= line.BaseCreditAmount ?? 0;
                        }
                    }                    
                }
            }
        }
    

2 个答案:

答案 0 :(得分:1)

这取决于具体情况,但其中一个选项是仅转换总计,然后使用减少余额法按比例分配给每个部分。这可以确保零件的总和将始终完全等于总和。

您的借记卡和信用额度加起来都是1.20美元,因此您可以按照自己的汇率兑换为0.8253英镑。

然后按比例将此分配给您从最小到最大的借方金额。排序背后的理论是,你不太可能关心更多数量的额外/丢失的便士。

所以你从1.20美元和0.6878英镑的总数开始,然后计算适用于你最小美元金额的转换余额的比例:

$0.20 / $1.20 * 0.8253 = £0.1376

然后从总计中扣除金额(这是'减少余额'部分):

$1.20 - $0.20 = $1.00
£0.8253 - £0.1376 = £0.6877

然后计算下一个最大值(因为在这个例子中你只有1个量,这是微不足道的):

$1.00 / $1.00 * £0.6877 = £0.6877

所以这会给你:

Line #  Debit    Credit
1                £0.8253
2       £0.6877
3       £0.1376
========================
Total   £0.8253  £0.8253 
========================

答案 1 :(得分:0)

这通常是一个业务问题,而不是编程问题。您所看到的结果是数学四舍五入的结果。在财务计算中,企业将决定何时应用汇率并且必须应用一次。例如,在您的情况下,可能会决定将其应用于项目值,然后乘以增值税以获得总计。这也通常出现在您所在地区的会计/财务接受标准中。