macxred / cashctrl_ledger

Implementation of the abstract pyledger.LegderEngine interface with CashCtrl accounting service.
MIT License
0 stars 0 forks source link

Convert Individual to Collective Transaction #27

Open lasuk opened 3 months ago

lasuk commented 3 months ago

CashCtrl doesn`t allow to convert a single transaction with an assigned taxID into a collective transaction.

As a workaround, we reset the taxId manually before update with a collective transaction in test_ledger_accessor_mutators_single_transaction(). See tests/test_ledger.py#L84-L88.

Tasks:

AlexTheWizardL commented 1 month ago

This bug isn't reproducing anymore. Here is a commit that proves this: https://github.com/macxred/cashctrl_ledger/commit/2d91efe12605e6179f5b6fa8290a189875c05214

But we still have some unexpected behavior - on the UI in the grid of Journals it still says the same

Example:

  1. Creating a journal with a payload:
    {
        "dateAdded": "2024-05-24",
        "amount": 100.0,
        "debitId": 369,
        "creditId": 372,
        "currencyId": 1,
        "title": "pytest single transaction 1",
        "taxId": "215",
        "currencyRate": 1,
        "reference": "/file1.txt"
    }
  2. Updating it with this payload:
    {
        "dateAdded": "2024-05-24",
        "currencyId": 3,
        "reference": "/subdir/file2.txt",
        "currencyRate": 0.8888,
        "items": [
            {
                "accountId": 368,
                "credit": 100.0,
                "debit": None,
                "taxId": "216",
                "description": "pytest collective txn 1 - line 1"
            },
            {
                "accountId": 368,
                "credit": None,
                "debit": 1.0,
                "taxId": "216",
                "description": "pytest collective txn 1 - line 1"
            },
            {
                "accountId": 368,
                "credit": None,
                "debit": 99.0,
                "taxId": "216",
                "description": "pytest collective txn 1 - line 1"
            }
        ],
        "id": 386
    }
  3. "journal/list.json" endpoint replies with old tax code: "Test_VAT_code_single"
    {
        "total": 1,
        "data": [
            {
                "id": 386,
                "created": "2024-08-07 16:39:48.0",
                "createdBy": "API:9PGU",
                "lastUpdated": "2024-08-07 16:41:49.0",
                "lastUpdatedBy": "API:9PGU",
                "debitId": 368,
                "debitName": "10022 Test USD Bank Account",
                "creditId": 368,
                "creditName": "10022 Test USD Bank Account",
                "associateId": null,
                "associateName": null,
                "taxId": 215,
                "taxName": "Test_VAT_code_single",
            }
        ],
        "summary": null,
        "properties": {}
    }
  4. But "journal/read.json" that search by id returns with a correct tax code
    {
        "id": 386,
        "created": "2024-08-07 16:39:48.0",
        "createdBy": "API:9PGU",
        "lastUpdated": "2024-08-07 16:41:49.0",
        "lastUpdatedBy": "API:9PGU",
        "creditId": 368,
        "debitId": 368,
        "associateId": null,
        "taxId": 215,
        "orderId": null,
        "orderBookEntryId": null,
        "inventoryId": null,
        "currencyId": 3,
        "importEntryId": null,
        "recurrenceId": null,
        "items": [
            {
                "id": 574,
                "created": "2024-08-07 16:41:49.0",
                "createdBy": "API:9PGU",
                "lastUpdated": "2024-08-07 16:41:49.0",
                "lastUpdatedBy": "API:9PGU",
                "journalId": 386,
                "accountId": 368,
                "taxId": 216,
                "associateId": null,
                "description": "pytest collective txn 1 - line 1",
                "debit": null,
                "credit": 100.0,
                "pos": 0,
                "allocations": [],
                "amount": null,
                "associateName": null,
                "taxName": "Test_VAT_code_collective",

Code snippet to try it yourself:

from cashctrl_ledger import CashCtrlLedger
from io import StringIO
import pandas as pd

ACCOUNT_CSV = """
    group, account, currency, vat_code, text
    /Assets, 10022,      USD,         , Test USD Bank Account
    /Assets, 10023,      CHF,         , Test CHF Bank Account
    /Assets, 19993,      CHF,         , Transitory Account CHF
    /Assets, 22000,      CHF,         , Input Tax
"""

VAT_CSV = """
    id,             rate, account, inclusive, text
    Test_VAT_code_single,  0.02,   22000,      True, Input Tax 2%
    Test_VAT_code_collective,  0.04,   22000,      True, Input Tax 4%
"""
LEDGER_CSV = """
    id,     date, account, counter_account, currency,     amount, base_currency_amount,      vat_code, text,                             document
    1,  2024-05-24, 10023,           19993,      CHF,     100.00,                     , Test_VAT_code_single, pytest single transaction 1,      /file1.txt
    2,  2024-05-24, 10022,                ,      USD,    -100.00,               -88.88, Test_VAT_code_collective, pytest collective txn 1 - line 1, /subdir/file2.txt
    2,  2024-05-24, 10022,                ,      USD,       1.00,                 0.89, Test_VAT_code_collective, pytest collective txn 1 - line 1, /subdir/file2.txt
    2,  2024-05-24, 10022,                ,      USD,      99.00,                87.99, Test_VAT_code_collective, pytest collective txn 1 - line 1,
"""
STRIPPED_CSV = '\n'.join([line.strip() for line in LEDGER_CSV.split("\n")])
LEDGER_ENTRIES = pd.read_csv(StringIO(STRIPPED_CSV), skipinitialspace=True, comment="#", skip_blank_lines=True)
TEST_ACCOUNTS = pd.read_csv(StringIO(ACCOUNT_CSV), skipinitialspace=True)
TEST_VAT_CODE = pd.read_csv(StringIO(VAT_CSV), skipinitialspace=True)

cashctrl = CashCtrlLedger()
initial_vat_codes = cashctrl.vat_codes().reset_index()
initial_account_chart = cashctrl.account_chart().reset_index()
initial_ledger = cashctrl.ledger()
cashctrl.mirror_account_chart(TEST_ACCOUNTS, delete=False)
cashctrl.mirror_vat_codes(TEST_VAT_CODE, delete=False)

target = LEDGER_ENTRIES.query('id == 1')
id = cashctrl.add_ledger_entry(target)

# Test replace with an collective ledger entry
target = LEDGER_ENTRIES.query('id == 2').copy()
target['id'] = id
cashctrl.update_ledger_entry(target)

print(cashctrl._client.get("journal/list.json")["data"])
print('\n------------------------------------\n')
print(cashctrl._client.get("journal/read.json", params={'id': id})['data'])

cashctrl._client.post("journal/delete.json", {'ids': id})
cashctrl.mirror_vat_codes(initial_vat_codes, delete=True)
cashctrl.mirror_account_chart(initial_account_chart, delete=True)

Message for CashCtrl:

When updating a single journal entry with a tax rate to a collective entry which transactions that have a different tax rate the "journal/list.json" endpoint returns a tax rate of an old single journal entry. When trying to get this journal entry with the "journal/read.json" endpoint it returns the correct tax rate.

Also we can link payload/response examples