Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
)
""",
)


@openupgrade.migrate()
def migrate(env, version):
handle_lock_dates(env)
Expand All @@ -219,6 +320,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