Hello,

It's working again :-)

I coded a bit on a better fix for issue 1187. What I did:
- the computation of the diff between what is in the account and the
assertion is now a balance_t instead of amount_t, so it handles multiple
currencies.
- it then checks only the the commodity specified in the assertion amount
when verifying the assertion
- if the assertion value is 0 without a commodity, it will ensure that the
diff is zero for all commodities.

I have added a test, and added this info to the documentation as well. See
attached patch. At least my 29k lines ledger file makes ledger happy again
with this fix and all my assertions, and I saw no regression in the tests.

Feel free to use this patch as you wish (If you prefer to have me go the
pull request route, please coach me a bit, I've never done this with
git&github...)

Pascal


On Thu, Jul 12, 2018 at 12:50 AM, Pascal Fleury <
[email protected]> wrote:

> I did some iterations to figure out where this changed, and it is commit
> d541cceaf7d4e77c239849679e3ae7a449ae8c8b that was written as a fix to bug
> 1187
> <https://github.com/ledger/ledger/pull/472/commits/d541cceaf7d4e77c239849679e3ae7a449ae8c8b>
>  that
> broke my files containing such balances.
> I have turned my example into a test file (see attached), and it passes
> just before (on commit e27ae09e2285482f2c95f1ec53d8629f4072f081, titled
> 'Remove ledger-mode.texi') and fails once I apply above bug fix.
>
> Would be nice to know if the way of my example file with multiple currency
> is how should work as it makes these balance statements very useful. I also
> tried to "fix" it but did not get far.
>
> Pascal
>
> On Wed, Jul 11, 2018 at 11:31 PM, Pascal Fleury <[email protected]>
> wrote:
>
>> Ok, I tried a few more things, and I think I know why this is happening.
>>
>> Note that if we add 'CHF' to the balance, it seems to ignore the other
>> currencies (which were also checked when simply using '0').
>> Here is another snippet to illustrate my point, with some references in
>> comments (e.g. #1):
>>
>> 2013/12/01 * Initial State
>>     Crédit:Viseca:MasterCard P1                        -618.50 CHF
>>     Crédit:Viseca:MasterCard P2                         -52.10 CHF
>>     Equity:Opening Balances
>>
>> 2013/12/15 * Buy Some Chocolate
>>     Dépenses:Nourriture                                  19.00 EUR *; #1*
>>     Crédit:Viseca:MasterCard P1
>>
>> 2013/12/15 * Buy Some Chocolate
>>     Crédit:Viseca:MasterCard P1                          19.00 EUR *; #2*
>>     Recettes:Erreurs
>>
>> 2013/12/23 * Facture Viseca
>>     Crédit:Viseca:MasterCard P2                          52.10 CHF = 0 *;
>> #3*
>>     Crédit:Viseca:MasterCard P1                         618.50 CHF = 0 *;
>> #4*
>>     Dépenses:Frais:Gestion Comptes                        1.50 CHF
>>     Crédit:Viseca                                      -672.10 CHF
>>
>> 2014/01/03 * Facture Viseca
>>     Crédit:Viseca                                       672.10 CHF = 0
>>     Actif:Comptes:CP courant
>>
>> This is running on ubuntu 18.04
>>
>> $ ledger --version
>> Ledger 3.1.2-20160801, the command-line accounting tool
>>
>> Copyright (c) 2003-2016, John Wiegley.  All rights reserved.
>>
>>  - without the transactions on 2013/12/15, all is fine, with or without
>> the 'CHF' suffix on lines #3 and #4.
>>  - if I add the transactions of 2013/12/15, the balance fails if I use
>> 'EUR' in #1 and #2 (even though they balance out), but works again if those
>> transactions are also in CHF.
>>  - if I use 'EUR' in #1 and #2, and balance with '= 0 CHF' in #4 then it
>> works again, but it still works even if the amounts in EUR do not cancel
>> out. #3 works fine in this case too, it has seen only a single currency.
>>
>> So the balance assertion does not do the right thing if the account has
>> seen some transaction in several currencies, even if all the amounts end up
>> at zero.
>>
>> Also, the error message seems to round things to the nearest integer
>> value, which is unhelpful at times.
>>
>> Is there a way to assert that an account is at 0 overall, independent of
>> currency ? Also related, I don't know how to assert that an account is at
>> "0 CHF + 19.00 EUR". If that works, it could be added to the documentation.
>>
>> I think these balances are a very neat functionality, as with sorting
>> files, if I add a transaction with e.g. the wrong year, it will move it far
>> too early, and would then be caught by the suddenly wrong balance. Using
>> git, I can then easily find which transaction went too far into the past
>> due to such a typo.
>>
>> Thanks,
>> Pascal
>>
>>
>> On Thu, Jun 28, 2018 at 5:59 AM, Colin Dean <[email protected]> wrote:
>>
>>> Running your original snippet under 3.1.1-20160111 from Homebrew, it
>>> passes.
>>>
>>> Running under HEAD of next branch, it fails.
>>>
>>> If I add "CHF" to the 0 assertions, it passes.
>>>
>>> --
>>>
>>> ---
>>> You received this message because you are subscribed to the Google
>>> Groups "Ledger" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>
>>
>

-- 

--- 
You received this message because you are subscribed to the Google Groups 
"Ledger" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
diff --git a/doc/ledger3.texi b/doc/ledger3.texi
index a094d977..45afe1ae 100644
--- a/doc/ledger3.texi
+++ b/doc/ledger3.texi
@@ -3230,6 +3230,45 @@ A balance assertion has this general form:
 This simply asserts that after subtracting $20.00 from Assets:Cash,
 that the resulting total matches $500.00.  If not, it is an error.
 
+The assertion has an effect only on the specified commodity. If an account has
+multiple commodities, then only the one asserted is verified:
+
+@smallexample
+2012-03-10 KFC New York
+    Expenses:Food                $20.00
+    Assets:Cash                 $-20.00 = $500.00
+
+2012-03-11 KFC Montreal
+    Expenses:Food                 15.00 CAD
+    Assets:Cash                  -15.00 CAD = $500.00
+@end smallexample
+
+In this case, the amount in USD of cash (which has not changed) is validated.
+Nothing is asserted about the current amount of Canadian dollars in @samp{Asset:Cash}.
+
+@subsubsection Special assertion value 0
+
+The only value that can be asserted without a commodity is @samp{0}.
+This results in a cross-commodities assertion, which makes it possible to
+assert that an account is totally empty.
+
+@smallexample
+2012-03-09 Fill Wallet
+    Revenue                      $20.00
+    Revenue                       15.00 CAD
+    Assets:Cash
+
+2012-03-10 KFC New York
+    Expenses:Food                $20.00
+    Assets:Cash                 $-20.00
+
+2012-03-11 KFC Montreal
+    Expenses:Food                 15.00 CAD
+    Assets:Cash                  -15.00 CAD = 0
+@end smallexample
+
+The last transaction will assert that we are out of cash of any sort.
+
 @node Balance assignments, Resetting a balance, Balance assertions, Balance verification
 @subsection Balance assignments
 
diff --git a/src/textual.cc b/src/textual.cc
index 8fbc5c08..246db751 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -1644,29 +1644,30 @@ post_t * instance_t::parse_post(char *          line,
       }
 
       DEBUG("textual.parse", "line " << context.linenum << ": "
-            << "POST assign: parsed amt = " << *post->assigned_amount);
+            << "POST assign: parsed balance amount = " << *post->assigned_amount);
 
-      amount_t& amt(*post->assigned_amount);
+      const amount_t& amt(*post->assigned_amount);
       value_t account_total
         (post->account->amount().strip_annotations(keep_details_t()));
 
       DEBUG("post.assign", "line " << context.linenum << ": "
             << "account balance = " << account_total);
-      DEBUG("post.assign",
-            "line " << context.linenum << ": " << "post amount = " << amt);
+      DEBUG("post.assign", "line " << context.linenum << ": "
+            << "post amount = " << amt << " (is_zero = " << amt.is_zero() << ")");
 
-      amount_t diff = amt;
+      balance_t diff = amt;
 
       switch (account_total.type()) {
       case value_t::AMOUNT:
-        if (account_total.as_amount().commodity_ptr() == diff.commodity_ptr())
-          diff -= account_total.as_amount();
+        diff -= account_total.as_amount();
+        DEBUG("textual.parse", "line " << context.linenum << ": "
+              << "Subtracting amount " << account_total.as_amount() << " from diff, yielding " << diff);
         break;
 
       case value_t::BALANCE:
-        if (optional<amount_t> comm_bal =
-            account_total.as_balance().commodity_amount(amt.commodity()))
-          diff -= *comm_bal;
+        diff -= account_total.as_balance();
+        DEBUG("textual.parse", "line " << context.linenum << ": "
+              << "Subtracting balance " << account_total.as_balance() << " from diff, yielding " << diff);
         break;
 
       default:
@@ -1680,18 +1681,34 @@ post_t * instance_t::parse_post(char *          line,
 
       // Subtract amounts from previous posts to this account in the xact.
       for (post_t* p : xact->posts) {
-        if (p->account == post->account &&
-            p->amount.commodity_ptr() == diff.commodity_ptr()) {
+        if (p->account == post->account) {
           diff -= p->amount;
           DEBUG("textual.parse", "line " << context.linenum << ": "
-                << "Subtract " << p->amount << ", diff = " << diff);
+                << "Subtracting " << p->amount << ", diff = " << diff);
+        }
+      }
+
+      // If amt has a commodity, restrict balancing to that. Otherwise, it's the blanket '0' and
+      // check that all of them are zero.
+      if (amt.has_commodity()) {
+        DEBUG("textual.parse", "line " << context.linenum << ": "
+              << "Finding commodity " << amt.commodity() << " (" << amt << ") in balance " << diff);
+        optional<amount_t> wanted_commodity = diff.commodity_amount(amt.commodity());
+        if (!wanted_commodity) {
+          diff = amt - amt;  // this is '0' with the correct commodity.
+        } else {
+          diff = *wanted_commodity;
         }
+        DEBUG("textual.parse", "line " << context.linenum << ": "
+              << "Diff is now " << diff);
       }
 
       if (post->amount.is_null()) {
         // balance assignment
         if (! diff.is_zero()) {
-          post->amount = diff;
+          // This will fail if there are more than 1 commodity in diff, which is wanted,
+          // as amount cannot store more than 1 commodity.
+          post->amount = diff.to_amount();
           DEBUG("textual.parse", "line " << context.linenum << ": "
                 << "Overwrite null posting");
         }
@@ -1699,10 +1716,11 @@ post_t * instance_t::parse_post(char *          line,
         // balance assertion
         diff -= post->amount;
         if (! no_assertions && ! diff.is_zero()) {
-          amount_t tot = amt - diff;
+          balance_t tot = -diff + amt;
+          DEBUG("textual.parse", "Balance assertion: off by " << diff << " (expected to see " << tot << ")");
           throw_(parse_error,
                   _f("Balance assertion off by %1% (expected to see %2%)")
-                  % diff % tot);
+                  % diff.to_string() % tot.to_string());
         }
       }
 
diff --git a/test/regress/1187_5.test b/test/regress/1187_5.test
new file mode 100644
index 00000000..4aa5fdd8
--- /dev/null
+++ b/test/regress/1187_5.test
@@ -0,0 +1,36 @@
+2013/12/01 * Initial State
+    Crédit:Viseca:MasterCard P1                        -618.50 CHF
+    Crédit:Viseca:MasterCard P2                         -52.10 CHF
+    Equity:Opening Balances
+
+2013/12/15 * Buy Some Chocolate
+    Dépenses:Nourriture                                  19.00 EUR ; #1
+    Crédit:Viseca:MasterCard P1
+
+2013/12/15 * Buy Some Chocolate
+    Crédit:Viseca:MasterCard P1                          18.00 EUR ; #2
+    Recettes:Erreurs
+
+2013/12/23 * Facture Viseca
+    Crédit:Viseca:MasterCard P2                          52.10 CHF = 0 ; #3
+    Crédit:Viseca:MasterCard P1                         618.50 CHF = 0 CHF ; #4
+    Dépenses:Frais:Gestion Comptes                        1.50 CHF
+    Crédit:Viseca                                      -672.10 CHF
+
+2014/01/03 * Facture Viseca
+    Crédit:Viseca                                       672.10 CHF = 0
+    Actif:Comptes:CP courant
+
+test bal
+         -672.10 CHF  Actif:Comptes:CP courant
+           -1.00 EUR  Crédit:Viseca
+           -1.00 EUR    MasterCard P1
+            1.50 CHF
+           19.00 EUR  Dépenses
+            1.50 CHF    Frais:Gestion Comptes
+           19.00 EUR    Nourriture
+          670.60 CHF  Equity:Opening Balances
+          -18.00 EUR  Recettes:Erreurs
+--------------------
+                   0
+end test

Reply via email to