Skip to content
102 changes: 102 additions & 0 deletions openupgrade_scripts/scripts/account/18.0.1.3/post-migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,107 @@ def _handle_outstanding_accounts(env):
)


def fix_payment_paid_state(env):
"""Set 'paid' on the payments that fill_account_payment left as 'in_process'.

It reads the payment's own move, whose payment_state is always 'not_paid'
below v18 as it is only computed for invoices, so every posted payment ends
up 'in_process'. Apply the two conditions of account.payment._compute_state.
"""
# liquidity line fully reconciled, or on a non reconcilable account
openupgrade.logged_query(
env.cr,
"""
WITH liquidity AS (
SELECT ap.id AS payment_id,
SUM(aml.amount_residual) AS residual,
BOOL_OR(aa.reconcile) AS reconcilable,
cur.rounding AS rounding
FROM account_payment ap
JOIN account_move am ON am.id = ap.move_id
JOIN res_company rc ON rc.id = am.company_id
JOIN res_currency cur ON cur.id = rc.currency_id
JOIN account_journal aj ON aj.id = ap.journal_id
JOIN account_move_line aml ON aml.move_id = am.id
JOIN account_account aa ON aa.id = aml.account_id
LEFT JOIN account_payment_method_line apml
ON apml.id = ap.payment_method_line_id
WHERE ap.state = 'in_process'
AND (
aml.account_id = aj.default_account_id
OR aml.account_id = ap.outstanding_account_id
OR aml.account_id = apml.payment_account_id
OR aml.account_id IN (
SELECT payment_account_id
FROM account_payment_method_line
WHERE journal_id = ap.journal_id
AND payment_account_id IS NOT NULL
)
)
GROUP BY ap.id, cur.rounding
)
UPDATE account_payment ap
SET state = 'paid'
FROM liquidity
WHERE ap.id = liquidity.payment_id
AND (
NOT liquidity.reconcilable
OR ABS(liquidity.residual) < liquidity.rounding / 2
)
""",
)
# all the reconciled invoices, or all the reconciled bills, are paid
openupgrade.logged_query(
env.cr,
"""
WITH reconciled AS (
SELECT DISTINCT ap.id AS payment_id, inv.id AS invoice_id,
inv.move_type, inv.payment_state
FROM account_payment ap
JOIN account_move am ON am.id = ap.move_id
JOIN account_move_line aml ON aml.move_id = am.id
JOIN account_account aa ON aa.id = aml.account_id
JOIN account_partial_reconcile apr
ON apr.debit_move_id = aml.id OR apr.credit_move_id = aml.id
JOIN account_move_line counterpart
ON (apr.debit_move_id = counterpart.id
OR apr.credit_move_id = counterpart.id)
AND counterpart.id != aml.id
JOIN account_move inv ON inv.id = counterpart.move_id
WHERE ap.state = 'in_process'
AND aa.account_type IN ('asset_receivable', 'liability_payable')
AND inv.move_type IN (
'out_invoice', 'out_refund', 'out_receipt',
'in_invoice', 'in_refund', 'in_receipt'
)
), counts AS (
SELECT payment_id,
COUNT(*) FILTER (WHERE move_type IN (
'out_invoice', 'out_refund', 'out_receipt')) AS invoices,
COUNT(*) FILTER (WHERE move_type IN (
'out_invoice', 'out_refund', 'out_receipt')
AND payment_state = 'paid') AS paid_invoices,
COUNT(*) FILTER (WHERE move_type IN (
'in_invoice', 'in_refund', 'in_receipt')) AS bills,
COUNT(*) FILTER (WHERE move_type IN (
'in_invoice', 'in_refund', 'in_receipt')
AND payment_state = 'paid') AS paid_bills
FROM reconciled
GROUP BY payment_id
)
UPDATE account_payment ap
SET state = 'paid'
FROM counts
WHERE ap.id = counts.payment_id
AND ap.state = 'in_process'
AND (
(counts.invoices > 0 AND counts.invoices = counts.paid_invoices)
OR (counts.bills > 0 AND counts.bills = counts.paid_bills)
)
""",
)


def _create_batch_payment_sequence(env):
"""Creates a Batch Payment Number Sequence for every company that does not
have one yet. This covers companies that existed before the ``account``
Expand Down Expand Up @@ -236,6 +337,7 @@ def migrate(env, version):
)
convert_company_dependent(env)
fill_res_partner_property_x_payment_method_line_id(env)
fix_payment_paid_state(env)
openupgrade.load_data(env, "account", "18.0.1.3/noupdate_changes.xml")
openupgrade.delete_record_translations(
env.cr, "account", ["email_template_edi_invoice"]
Expand Down
148 changes: 148 additions & 0 deletions openupgrade_scripts/scripts/account/tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,151 @@
}
).action_send_and_print()
env.cr.commit()

# Payments for the state fix in post-migration, in the four reconciliation
# shapes: once on a journal paying from a plain bank account, once on a journal
# paying from a reconcilable outstanding account.
company = env.ref("base.main_company")

recv = env["account.account"].search(
[("account_type", "=", "asset_receivable"), ("company_id", "=", company.id)],
limit=1,
)
income = env["account.account"].search(
[("account_type", "=", "income"), ("company_id", "=", company.id)], limit=1
)

bank_acc = env["account.account"].create(
{
"name": "OU test direct bank",
"code": "OUDIR",
"account_type": "asset_cash",
"reconcile": False,
"company_id": company.id,
}
)
outstanding_acc = env["account.account"].create(
{
"name": "OU test outstanding",
"code": "OUOUT",
"account_type": "asset_current",
"reconcile": True,
"company_id": company.id,
}
)
outstanding_bank_acc = env["account.account"].create(
{
"name": "OU test outstanding bank",
"code": "OUOBK",
"account_type": "asset_cash",
"reconcile": False,
"company_id": company.id,
}
)

direct_journal = env["account.journal"].create(
{
"name": "OU test direct bank",
"code": "OUDBK",
"type": "bank",
"company_id": company.id,
"default_account_id": bank_acc.id,
}
)
# liquidity posts straight to the (non-reconcilable) bank account
direct_journal.inbound_payment_method_line_ids.payment_account_id = bank_acc.id
direct_journal.outbound_payment_method_line_ids.payment_account_id = bank_acc.id

outstanding_journal = env["account.journal"].create(
{
"name": "OU test outstanding bank",
"code": "OUOBK",
"type": "bank",
"company_id": company.id,
"default_account_id": outstanding_bank_acc.id,
}
)
# liquidity posts to the reconcilable outstanding account
outstanding_journal.inbound_payment_method_line_ids.payment_account_id = (
outstanding_acc.id
)
outstanding_journal.outbound_payment_method_line_ids.payment_account_id = (
outstanding_acc.id
)

sale_journal = env["account.journal"].search(
[("type", "=", "sale"), ("company_id", "=", company.id)], limit=1
)
ou_partner = env["res.partner"].create(
{
"name": "OU test payment partner",
"property_account_receivable_id": recv.id,
}
)


def _ou_invoice(amount):
inv = env["account.move"].create(
{
"move_type": "out_invoice",
"partner_id": ou_partner.id,
"journal_id": sale_journal.id,
"invoice_line_ids": [
(
0,
0,
{
"name": "ou test",
"quantity": 1,
"price_unit": amount,
"account_id": income.id,
"tax_ids": [(6, 0, [])],
},
)
],
}
)
inv.action_post()
return inv


def _ou_pay(invoices, journal, amount):
wizard = (
env["account.payment.register"]
.with_context(
active_model="account.move",
active_ids=invoices.ids,
)
.create({"journal_id": journal.id})
)
if len(invoices) > 1:
wizard.group_payment = True
wizard.amount = amount
return wizard._create_payments()


def _ou_build(prefix, journal):
# 1) partial payment of one invoice
p1 = _ou_pay(_ou_invoice(1000), journal, 400)
# 2) one payment paying multiple invoices partially
p2 = _ou_pay(_ou_invoice(1000) | _ou_invoice(1000), journal, 800)
# 3) a payment paying an invoice fully
p3 = _ou_pay(_ou_invoice(1000), journal, 1000)
# 4) two payments paying one invoice fully
inv = _ou_invoice(1000)
p4a = _ou_pay(inv, journal, 600)
p4b = _ou_pay(inv, journal, 400)
env["ir.model.data"]._update_xmlids(
[
{"xml_id": "openupgrade_test_account.%s_s1" % prefix, "record": p1},
{"xml_id": "openupgrade_test_account.%s_s2" % prefix, "record": p2},
{"xml_id": "openupgrade_test_account.%s_s3" % prefix, "record": p3},
{"xml_id": "openupgrade_test_account.%s_s4a" % prefix, "record": p4a},
{"xml_id": "openupgrade_test_account.%s_s4b" % prefix, "record": p4b},
]
)


_ou_build("ou_direct", direct_journal)
_ou_build("ou_outstanding", outstanding_journal)
env.cr.commit()
36 changes: 36 additions & 0 deletions openupgrade_scripts/scripts/account/tests/test_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,39 @@ def test_sending_data(self):
moves_with_sending_data[0].sending_data["author_partner_id"],
self.env.user.partner_id.id,
)

def test_payment_state_direct_to_bank(self):
"""Payments from a non reconcilable bank account are paid, whatever the
reconciliation shape.
"""
for name in ("s1", "s2", "s3", "s4a", "s4b"):
payment = self.env.ref("openupgrade_test_account.ou_direct_%s" % name)
self.assertEqual(
payment.state,
"paid",
"ou_direct_%s should have migrated to 'paid'" % name,
)

def test_payment_state_outstanding_not_fully_paid(self):
"""Payments from an outstanding account stay in process while the
invoices they are reconciled with are not paid.
"""
for name in ("s1", "s2"):
payment = self.env.ref("openupgrade_test_account.ou_outstanding_%s" % name)
self.assertEqual(
payment.state,
"in_process",
"ou_outstanding_%s should have stayed 'in_process'" % name,
)

def test_payment_state_outstanding_fully_paid(self):
"""Payments from an outstanding account are paid once all the invoices
they are reconciled with are.
"""
for name in ("s3", "s4a", "s4b"):
payment = self.env.ref("openupgrade_test_account.ou_outstanding_%s" % name)
self.assertEqual(
payment.state,
"paid",
"ou_outstanding_%s should have been promoted to 'paid'" % name,
)
Loading