[IMP] account: add 'partial' and 'reversed' payment states to invoices

- 'partial' payment state corresponds to invoices whose payable/receivable move line has been partially reconciled with some other line.

- 'reversed' payment state corresponds to entries that have been cancelled by the creation of a single reverse entry (using the dedicated button on the form view). This state can be set on invoice as well as on regular entries.

=> To stay consistent with the naming conventions, this commit also renames invoice_payment_state field to payment_state, since it's no longer only applicable on invoices.

closes odoo/odoo#41723

Related: odoo/enterprise#7202
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
This commit is contained in:
oco-odoo
2020-01-16 18:04:26 +00:00
parent 1a7c21328f
commit 8e4158af81
27 changed files with 159 additions and 126 deletions
+1 -1
View File
@@ -32,7 +32,7 @@
% endif
amounting in <strong>${format_amount(object.amount_total, object.currency_id)}</strong>
from ${object.company_id.name}.
% if object.invoice_payment_state == 'paid':
% if object.payment_state == 'paid':
This invoice is already paid.
% else:
Please remit payment at your earliest convenience.
@@ -208,7 +208,7 @@ class account_journal(models.Model):
FROM account_move move
WHERE move.journal_id = %(journal_id)s
AND move.state = 'posted'
AND move.invoice_payment_state = 'not_paid'
AND move.payment_state in ('not_paid', 'partial')
AND move.type IN %(invoice_types)s
''', {
'invoice_types': tuple(self.env['account.move'].get_invoice_types(True)),
@@ -251,7 +251,7 @@ class account_journal(models.Model):
#TODO need to check if all invoices are in the same currency than the journal!!!!
elif self.type in ['sale', 'purchase']:
title = _('Bills to pay') if self.type == 'purchase' else _('Invoices owed to you')
self.env['account.move'].flush(['amount_residual', 'currency_id', 'type', 'invoice_date', 'company_id', 'journal_id', 'date', 'state', 'invoice_payment_state'])
self.env['account.move'].flush(['amount_residual', 'currency_id', 'type', 'invoice_date', 'company_id', 'journal_id', 'date', 'state', 'payment_state'])
(query, query_args) = self._get_open_bills_to_pay_query()
self.env.cr.execute(query, query_args)
@@ -273,7 +273,7 @@ class account_journal(models.Model):
WHERE journal_id = %s
AND date <= %s
AND state = 'posted'
AND invoice_payment_state = 'not_paid'
AND payment_state in ('not_paid', 'partial')
AND type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt');
'''
self.env.cr.execute(query, (self.id, today))
@@ -331,7 +331,7 @@ class account_journal(models.Model):
FROM account_move move
WHERE move.journal_id = %(journal_id)s
AND move.state = 'posted'
AND move.invoice_payment_state = 'not_paid'
AND move.payment_state in ('not_paid', 'partial')
AND move.type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt');
''', {'journal_id': self.id})
@@ -351,7 +351,7 @@ class account_journal(models.Model):
FROM account_move move
WHERE move.journal_id = %(journal_id)s
AND move.state = 'draft'
AND move.invoice_payment_state = 'not_paid'
AND move.payment_state in ('not_paid', 'partial')
AND move.type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt');
''', {'journal_id': self.id})
+42 -25
View File
@@ -156,6 +156,14 @@ class AccountMove(models.Model):
amount_by_group = fields.Binary(string="Tax amount by group",
compute='_compute_invoice_taxes_by_group',
help='Edit Tax amounts if you encounter rouding issues.')
payment_state = fields.Selection(selection=[
('not_paid', 'Not Paid'),
('in_payment', 'In Payment'),
('paid', 'Paid'),
('partial', 'Partially Paid'),
('reversed', 'Reversed'),],
string='Payment', store=True, readonly=True, copy=False, tracking=True,
compute='_compute_amount')
# ==== Cash basis feature fields ====
tax_cash_basis_rec_id = fields.Many2one(
@@ -186,12 +194,6 @@ class AccountMove(models.Model):
default=lambda self: self.env.user)
user_id = fields.Many2one(string='User', related='invoice_user_id',
help='Technical field used to fit the generic behavior in mail templates.')
invoice_payment_state = fields.Selection(selection=[
('not_paid', 'Not Paid'),
('in_payment', 'In Payment'),
('paid', 'Paid')],
string='Payment', store=True, readonly=True, copy=False, tracking=True,
compute='_compute_amount')
invoice_date = fields.Date(string='Invoice/Bill Date', readonly=True, index=True, copy=False,
states={'draft': [('readonly', False)]},
default=_get_default_invoice_date)
@@ -945,7 +947,8 @@ class AccountMove(models.Model):
'line_ids.amount_currency',
'line_ids.amount_residual',
'line_ids.amount_residual_currency',
'line_ids.payment_id.state')
'line_ids.payment_id.state',
'line_ids.full_reconcile_id')
def _compute_amount(self):
invoice_ids = [move.id for move in self if move.id and move.is_invoice(include_receipts=True)]
self.env['account.payment'].flush(['state'])
@@ -976,6 +979,7 @@ class AccountMove(models.Model):
total_untaxed_currency = 0.0
total_tax = 0.0
total_tax_currency = 0.0
total_to_pay = 0.0
total_residual = 0.0
total_residual_currency = 0.0
total = 0.0
@@ -1003,6 +1007,7 @@ class AccountMove(models.Model):
total_currency += line.amount_currency
elif line.account_id.user_type_id.type in ('receivable', 'payable'):
# Residual amount.
total_to_pay += line.balance
total_residual += line.amount_residual
total_residual_currency += line.amount_residual_currency
else:
@@ -1025,18 +1030,30 @@ class AccountMove(models.Model):
move.amount_residual_signed = total_residual
currency = len(currencies) == 1 and currencies.pop() or move.company_id.currency_id
is_paid = currency and currency.is_zero(move.amount_residual) or not move.amount_residual
# Compute 'invoice_payment_state'.
if move.type == 'entry':
move.invoice_payment_state = False
elif move.state == 'posted' and is_paid:
if move.id in in_payment_set:
move.invoice_payment_state = 'in_payment'
else:
move.invoice_payment_state = 'paid'
else:
move.invoice_payment_state = 'not_paid'
# Compute 'payment_state'.
new_pmt_state = 'not_paid' if move.type != 'entry' else False
if move.is_invoice(include_receipts=True) and move.state == 'posted':
if currency.is_zero(move.amount_residual):
if move.id in in_payment_set:
new_pmt_state = 'in_payment'
else:
new_pmt_state = 'paid'
elif currency.compare_amounts(total_to_pay, total_residual) != 0:
new_pmt_state = 'partial'
if new_pmt_state == 'paid' and move.type in ('in_invoice', 'out_invoice', 'entry'):
reverse_type = move.type == 'in_invoice' and 'in_refund' or move.type == 'out_invoice' and 'out_refund' or 'entry'
reverse_moves = self.env['account.move'].search([('reversed_entry_id', '=', move.id), ('state', '=', 'posted'), ('type', '=', reverse_type)])
# We only set 'reversed' state in cas of 1 to 1 full reconciliation with a reverse entry; otherwise, we use the regular 'paid' state
reverse_moves_full_recs = reverse_moves.mapped('line_ids.full_reconcile_id')
if reverse_moves_full_recs.mapped('reconciled_line_ids.move_id').filtered(lambda x: x not in (reverse_moves + reverse_moves_full_recs.mapped('exchange_move_id'))) == move:
new_pmt_state = 'reversed'
move.payment_state = new_pmt_state
def _inverse_amount_total(self):
for move in self:
@@ -1075,12 +1092,12 @@ class AccountMove(models.Model):
def _compute_has_matching_suspense_amount(self):
for r in self:
res = False
if r.state == 'posted' and r.is_invoice() and r.invoice_payment_state == 'not_paid':
if r.state == 'posted' and r.is_invoice() and r.payment_state == 'not_paid':
domain = r._get_domain_matching_suspense_moves()
#there are more than one but less than 5 suspense moves matching the residual amount
if (0 < self.env['account.move.line'].search_count(domain) < 5):
domain2 = [
('invoice_payment_state', '=', 'not_paid'),
('payment_state', '=', 'not_paid'),
('state', '=', 'posted'),
('amount_residual', '=', r.amount_residual),
('type', '=', r.type)]
@@ -1168,7 +1185,7 @@ class AccountMove(models.Model):
move.invoice_outstanding_credits_debits_widget = json.dumps(False)
move.invoice_has_outstanding = False
if move.state != 'posted' or move.invoice_payment_state != 'not_paid' or not move.is_invoice(include_receipts=True):
if move.state != 'posted' or move.payment_state != 'not_paid' or not move.is_invoice(include_receipts=True):
continue
pay_term_line_ids = move.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
@@ -1557,12 +1574,12 @@ class AccountMove(models.Model):
moves = super(AccountMove, self).create(vals_list)
# Trigger 'action_invoice_paid' when the invoice is directly paid at its creation.
moves.filtered(lambda move: move.is_invoice(include_receipts=True) and move.invoice_payment_state in ('paid', 'in_payment')).action_invoice_paid()
moves.filtered(lambda move: move.is_invoice(include_receipts=True) and move.payment_state in ('paid', 'in_payment')).action_invoice_paid()
return moves
def write(self, vals):
not_paid_invoices = self.filtered(lambda move: move.is_invoice(include_receipts=True) and move.invoice_payment_state not in ('paid', 'in_payment'))
not_paid_invoices = self.filtered(lambda move: move.is_invoice(include_receipts=True) and move.payment_state not in ('paid', 'in_payment'))
for move in self:
if (move.restrict_mode_hash_table and move.state == "posted" and set(vals).intersection(INTEGRITY_HASH_MOVE_FIELDS)):
@@ -1606,7 +1623,7 @@ class AccountMove(models.Model):
self._check_balanced()
# Trigger 'action_invoice_paid' when the invoice becomes paid after a write.
not_paid_invoices.filtered(lambda move: move.invoice_payment_state in ('paid', 'in_payment')).action_invoice_paid()
not_paid_invoices.filtered(lambda move: move.payment_state in ('paid', 'in_payment')).action_invoice_paid()
return res
@@ -1646,7 +1663,7 @@ class AccountMove(models.Model):
if not self.is_invoice(include_receipts=True):
return super(AccountMove, self)._track_subtype(init_values)
if 'invoice_payment_state' in init_values and self.invoice_payment_state == 'paid':
if 'payment_state' in init_values and self.payment_state == 'paid':
return self.env.ref('account.mt_invoice_paid')
elif 'state' in init_values and self.state == 'posted' and self.is_sale_document(include_receipts=True):
return self.env.ref('account.mt_invoice_validated')
+1 -1
View File
@@ -735,7 +735,7 @@ class payment_register(models.TransientModel):
invoices = self.env['account.move'].browse(active_ids)
# Check all invoices are open
if any(invoice.state != 'posted' or invoice.invoice_payment_state != 'not_paid' or not invoice.is_invoice() for invoice in invoices):
if any(invoice.state != 'posted' or invoice.payment_state not in ('not_paid', 'partial') or not invoice.is_invoice() for invoice in invoices):
raise UserError(_("You can only register payments for open invoices"))
# Check all invoices are inbound or all invoices are outbound
outbound_list = [invoice.is_outbound() for invoice in invoices]
@@ -32,7 +32,7 @@ class AccountInvoiceReport(models.Model):
('posted', 'Open'),
('cancel', 'Cancelled')
], string='Invoice Status', readonly=True)
invoice_payment_state = fields.Selection(selection=[
payment_state = fields.Selection(selection=[
('not_paid', 'Not Paid'),
('in_payment', 'In Payment'),
('paid', 'paid')
@@ -92,7 +92,7 @@ class AccountInvoiceReport(models.Model):
move.partner_id,
move.invoice_user_id,
move.fiscal_position_id,
move.invoice_payment_state,
move.payment_state,
move.invoice_date,
move.invoice_date_due,
move.invoice_payment_term_id,
@@ -154,7 +154,7 @@ class AccountInvoiceReport(models.Model):
move.partner_id,
move.invoice_user_id,
move.fiscal_position_id,
move.invoice_payment_state,
move.payment_state,
move.invoice_date,
move.invoice_date_due,
move.invoice_payment_term_id,
@@ -1147,6 +1147,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'not_paid', "Refunding with a draft credit note should keep the invoice 'not_paid'.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1181,7 +1182,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'draft',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'not_paid',
'payment_state': 'not_paid',
})
move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({
@@ -1192,6 +1193,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'reversed', "After cancelling it with a reverse invoice, an invoice should be in 'reversed' state.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1226,7 +1228,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'posted',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'paid',
'payment_state': 'paid',
})
def test_in_invoice_create_refund_multi_currency(self):
@@ -1249,6 +1251,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'not_paid', "Refunding with a draft credit note should keep the invoice 'not_paid'.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1294,7 +1297,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'draft',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'not_paid',
'payment_state': 'not_paid',
})
move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({
@@ -1305,6 +1308,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'reversed', "After cancelling it with a reverse invoice, an invoice should be in 'reversed' state.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1350,7 +1354,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'posted',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'paid',
'payment_state': 'paid',
})
def test_in_invoice_create_1(self):
@@ -1274,6 +1274,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'not_paid', "Refunding with a draft credit note should keep the invoice 'not_paid'.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1309,7 +1310,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'draft',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'not_paid',
'payment_state': 'not_paid',
})
move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({
@@ -1320,6 +1321,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'reversed', "After cancelling it with a reverse invoice, an invoice should be in 'reversed' state.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1354,7 +1356,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'posted',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'paid',
'payment_state': 'paid',
})
def test_out_invoice_create_refund_multi_currency(self):
@@ -1377,6 +1379,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'not_paid', "Refunding with a draft credit note should keep the invoice 'not_paid'.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1422,7 +1425,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'draft',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'not_paid',
'payment_state': 'not_paid',
})
move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({
@@ -1433,6 +1436,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertEqual(self.invoice.payment_state, 'reversed', "After cancelling it with a reverse invoice, an invoice should be in 'reversed' state.")
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
@@ -1478,7 +1482,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'date': move_reversal.date,
'state': 'posted',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'paid',
'payment_state': 'paid',
})
def test_out_invoice_create_1(self):
@@ -49,7 +49,7 @@ class TestBankStatementReconciliation(AccountTestCommon):
self.assertTrue(rcv_mv_line.reconciled)
self.assertTrue(counterpart_mv_line.reconciled)
self.assertEqual(counterpart_mv_line.matched_credit_ids, rcv_mv_line.matched_debit_ids)
self.assertEqual(rcv_mv_line.move_id.invoice_payment_state, 'paid', "The related invoice's state should now be 'paid'")
self.assertEqual(rcv_mv_line.move_id.payment_state, 'paid', "The related invoice's state should now be 'paid'")
def test_reconcile_with_write_off(self):
pass
+9 -9
View File
@@ -122,7 +122,7 @@ class TestPayment(AccountTestCommon):
self.assertAlmostEqual(payment.amount, 300)
self.assertEqual(payment.state, 'posted')
self.assertEqual(payment.state, 'posted')
self.assertEqual(inv_1.invoice_payment_state, 'paid')
self.assertEqual(inv_1.payment_state, 'paid')
rec_line = payment.move_line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
self.assertRecordValues(payment.move_line_ids.sorted('credit'), [
@@ -249,7 +249,7 @@ class TestPayment(AccountTestCommon):
{'account_id': self.account_payable.id, 'debit': 8.65, 'credit': 0.0, 'amount_currency': 13.22, 'currency_id': self.currency_usd_id},
])
self.assertTrue(receivable_line.full_reconcile_id)
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertEqual(invoice.payment_state, 'paid')
# Use case:
# Company is in EUR, create a vendor bill for 25 EUR and register payment of 25 USD.
@@ -277,7 +277,7 @@ class TestPayment(AccountTestCommon):
{'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 8.65, 'amount_currency': -13.22, 'currency_id': self.currency_usd_id},
])
self.assertTrue(payable_line.full_reconcile_id)
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertEqual(invoice.payment_state, 'paid')
def test_payment_and_writeoff_out_refund(self):
# Use case:
@@ -304,7 +304,7 @@ class TestPayment(AccountTestCommon):
{'account_id': self.account_eur.id, 'debit': 0.0, 'credit': 90.0, 'amount_currency': 0.0, 'currency_id': False},
{'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 10.0, 'amount_currency': 0.0, 'currency_id': False},
])
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertEqual(invoice.payment_state, 'paid')
def test_payment_and_writeoff_in_other_currency_2(self):
# Use case:
@@ -351,7 +351,7 @@ class TestPayment(AccountTestCommon):
])
#check the invoice status
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertEqual(invoice.payment_state, 'paid')
def test_payment_and_writeoff_in_other_currency_3(self):
# Use case related in revision 20935462a0cabeb45480ce70114ff2f4e91eaf79
@@ -397,7 +397,7 @@ class TestPayment(AccountTestCommon):
# Check the invoice status and the full reconciliation: the difference on the receivable account
# should have been completed by an exchange rate difference entry
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertEqual(invoice.payment_state, 'paid')
self.assertTrue(receivable_line.full_reconcile_id)
def test_post_at_bank_reconciliation_payment(self):
@@ -441,7 +441,7 @@ class TestPayment(AccountTestCommon):
invoice = self.create_invoice(amount=53, partner=self.partner_agrolait.id)
(payment_one.move_line_ids + payment_two.move_line_ids + invoice.line_ids).filtered(lambda x: x.account_id.user_type_id.type == 'receivable').reconcile()
self.assertEqual(invoice.invoice_payment_state, 'in_payment', "Invoice should be in 'in payment' state")
self.assertEqual(invoice.payment_state, 'in_payment', "Invoice should be in 'in payment' state")
# Match the first payment with a bank statement line
bank_statement_one = self.reconcile(payment_one.move_line_ids.filtered(lambda x: x.account_id.user_type_id.type == 'liquidity'), 42)
@@ -450,7 +450,7 @@ class TestPayment(AccountTestCommon):
self.assertEqual(payment_one.mapped('move_line_ids.move_id.state'), ['posted'], "After bank reconciliation, payment one's account.move should be posted.")
self.assertEqual(payment_one.mapped('move_line_ids.move_id.date'), stmt_line_date_one, "After bank reconciliation, payment one's account.move should share the same date as the bank statement.")
self.assertEqual([payment_one.payment_date], stmt_line_date_one, "After bank reconciliation, payment one should share the same date as the bank statement.")
self.assertEqual(invoice.invoice_payment_state, 'in_payment', "The invoice should still be 'in payment', not all its payments are reconciled with a statement")
self.assertEqual(invoice.payment_state, 'in_payment', "The invoice should still be 'in payment', not all its payments are reconciled with a statement")
# Match the second payment with a bank statement line
bank_statement_two = self.reconcile(payment_two.move_line_ids.filtered(lambda x: x.account_id.user_type_id.type == 'liquidity'), 42)
@@ -461,7 +461,7 @@ class TestPayment(AccountTestCommon):
self.assertEqual([payment_two.payment_date], stmt_line_date_two, "After bank reconciliation, payment two should share the same date as the bank statement.")
# The invoice should now be paid
self.assertEqual(invoice.invoice_payment_state, 'paid', "Invoice should be in 'paid' state after having reconciled the two payments with a bank statement")
self.assertEqual(invoice.payment_state, 'paid', "Invoice should be in 'paid' state after having reconciled the two payments with a bank statement")
def test_payment_draft_keep_name(self):
payment = self.payment_model.create({
+19 -19
View File
@@ -179,7 +179,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
])
# The invoice should be paid, as the payments totally cover its total
self.assertEqual(move.invoice_payment_state, 'paid', 'The invoice should be paid by now')
self.assertEqual(move.payment_state, 'paid', 'The invoice should be paid by now')
self.assertTrue(receivable_line.reconciled, 'The invoice should be totally reconciled')
self.assertTrue(receivable_line.full_reconcile_id, 'The invoice should have a full reconcile number')
self.assertEqual(receivable_line.amount_residual, 0, 'The invoice should be totally reconciled')
@@ -772,7 +772,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
credit_aml = payment.move_line_ids.filtered('credit')
inv.js_assign_outstanding_line(credit_aml.id)
self.assertTrue(inv.invoice_payment_state == 'paid', 'The invoice should be paid')
self.assertTrue(inv.payment_state == 'paid', 'The invoice should be paid')
exchange_reconcile = payment.move_line_ids.mapped('full_reconcile_id')
exchange_move = exchange_reconcile.exchange_move_id
@@ -853,9 +853,9 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
'invoice_ids': [(6, 0, invoice_cust_1.ids)],
})
payment.post()
# We expect at this point that the invoice should still be open,
# We expect at this point that the invoice should still be open, in 'partial' state,
# because they owe us still 50 CC.
self.assertEqual(invoice_cust_1.invoice_payment_state, 'not_paid', 'Invoice is in status %s' % invoice_cust_1.state)
self.assertEqual(invoice_cust_1.payment_state, 'partial', 'Invoice is in status %s' % invoice_cust_1.state)
def test_multiple_term_reconciliation_opw_1906665(self):
'''Test that when registering a payment to an invoice with multiple
@@ -1686,7 +1686,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
# Second Payment
payment1 = self.make_payment(invoice, journal, 0.01)
self.assertEqual(invoice.amount_residual, 0)
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertEqual(invoice.payment_state, 'paid')
self.assertTrue(tax_waiting_line.reconciled)
move_caba1 = tax_waiting_line.matched_debit_ids.mapped('debit_move_id').mapped('move_id').filtered(lambda m: m != move_caba0)
@@ -1816,8 +1816,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
self.assertEqual(inv2.payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic3(self):
"""
@@ -1888,8 +1888,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
exchange_rcv = inv1_receivable.full_reconcile_id.exchange_move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(exchange_rcv.amount_currency, 0.01)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
self.assertEqual(inv2.payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic4(self):
"""
@@ -1969,8 +1969,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
self.assertEqual(inv2.payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic5(self):
"""
@@ -2037,8 +2037,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
self.assertEqual(inv2.payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic6(self):
"""
@@ -2101,8 +2101,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
self.assertEqual(inv2.payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic6bis(self):
"""
@@ -2176,8 +2176,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
self.assertEqual(inv2.payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic7(self):
"""
@@ -2235,7 +2235,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic8(self):
"""
@@ -2298,4 +2298,4 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv1.payment_state, 'paid')
+24 -15
View File
@@ -399,9 +399,9 @@
<field name="invoice_partner_display_name" invisible="context.get('default_type') not in ('out_invoice', 'out_refund','out_receipt')" groups="base.group_user" string="Customer" />
<field name="invoice_date" optional="show" invisible="context.get('default_type') not in ('in_invoice', 'in_refund','in_receipt')" string="Bill Date"/>
<field name="invoice_date" optional="show" invisible="context.get('default_type') not in ('out_invoice', 'out_refund','out_receipt')" string="Invoice Date"/>
<field name="invoice_origin" optional="show" string="Source Document"/>
<field name="invoice_origin" optional="hide" string="Source Document"/>
<field name="invoice_payment_ref" optional="hide" invisible="context.get('default_type') in ('out_invoice', 'out_refund','out_receipt')"/>
<field name="invoice_user_id" optional="show" invisible="context.get('default_type') not in ('out_invoice', 'out_refund','out_receipt')" string="Sales Person"/>
<field name="invoice_user_id" optional="hide" invisible="context.get('default_type') not in ('out_invoice', 'out_refund','out_receipt')" string="Sales Person"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" optional="show"/>
<field name="invoice_date_due" optional="show"/>
<field name="amount_untaxed_signed" string="Tax Excluded" sum="Total" optional="show"/>
@@ -410,8 +410,8 @@
<field name="amount_residual_signed" string="Amount Due" sum="Amount Due" optional="show"/>
<field name="currency_id" invisible="1"/>
<field name="company_currency_id" invisible="1"/>
<field name="state" optional="show"/>
<field name="invoice_payment_state" optional="hide"/>
<field name="state" optional="hide"/>
<field name="payment_state"/>
<field name="type" invisible="context.get('default_type', True)"/>
</tree>
</field>
@@ -485,7 +485,7 @@
<!-- Register Payment (only invoices / receipts) -->
<button name="action_invoice_register_payment" id="account_invoice_payment_btn"
type="object" class="oe_highlight"
attrs="{'invisible': ['|', '|', ('state', '!=', 'posted'), ('invoice_payment_state', '!=', 'not_paid'), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"
attrs="{'invisible': ['|', '|', ('state', '!=', 'posted'), ('payment_state', 'not in', ('not_paid', 'partial')), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"
string="Register Payment"
groups="account.group_account_invoice"/>
<!-- Preview (only customer invoices) -->
@@ -494,7 +494,7 @@
<!-- Reverse -->
<button name="%(action_view_account_move_reversal)d" string="Reverse Entry"
type="action" groups="account.group_account_invoice"
attrs="{'invisible': ['|', ('type', '!=', 'entry'), ('state', '!=', 'posted')]}"/>
attrs="{'invisible': ['|', ('type', '!=', 'entry'), '|', ('state', '!=', 'posted'), ('payment_state', '=', 'reversed')]}"/>
<button name="action_reverse" string='Add Credit Note'
type='object' groups="account.group_account_invoice"
attrs="{'invisible': ['|', ('type', 'not in', ('out_invoice', 'in_invoice')), ('state', '!=', 'posted')]}"/>
@@ -516,12 +516,12 @@
</div>
<div groups="account.group_account_invoice"
class="alert alert-info" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': ['|', '|', ('type', 'not in', ('out_invoice', 'out_refund')), ('invoice_has_outstanding', '=', False), ('invoice_payment_state', '!=', 'not_paid')]}">
attrs="{'invisible': ['|', '|', ('type', 'not in', ('out_invoice', 'out_refund')), ('invoice_has_outstanding', '=', False), ('payment_state', 'not in', ('not_paid', 'partial'))]}">
You have <bold><a class="alert-link" href="#outstanding" role="button">outstanding payments</a></bold> for this customer. You can allocate them to mark this invoice as paid.
</div>
<div groups="account.group_account_invoice"
class="alert alert-info" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': ['|', '|', ('type', 'not in', ('in_invoice', 'in_refund')), ('invoice_has_outstanding', '=', False), ('invoice_payment_state', '!=', 'not_paid')]}">
attrs="{'invisible': ['|', '|', ('type', 'not in', ('in_invoice', 'in_refund')), ('invoice_has_outstanding', '=', False), ('payment_state', 'not in', ('not_paid', 'partial'))]}">
You have <bold><a class="alert-link" href="#outstanding" role="button">outstanding debits</a></bold> for this supplier. You can allocate them to mark this bill as paid.
</div>
<sheet>
@@ -537,14 +537,19 @@
<!-- Payment status for invoices / receipts -->
<widget name="web_ribbon" title="Paid"
attrs="{'invisible': ['|', ('invoice_payment_state', '!=', 'paid'), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
attrs="{'invisible': ['|', ('payment_state', '!=', 'paid'), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
<widget name="web_ribbon" title="In Payment"
attrs="{'invisible': ['|', ('invoice_payment_state', '!=', 'in_payment'), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
attrs="{'invisible': ['|', ('payment_state', '!=', 'in_payment'), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
<widget name="web_ribbon" title="Partial"
attrs="{'invisible': ['|', ('payment_state', '!=', 'partial'), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
<widget name="web_ribbon" title="Reversed"
bg_color="bg-danger"
attrs="{'invisible': [('payment_state', '!=', 'reversed')]}"/>
<!-- Invisible fields -->
<field name="id" invisible="1"/>
<field name="type" invisible="1"/>
<field name="invoice_payment_state" invisible="1" force_save="1"/>
<field name="payment_state" invisible="1" force_save="1"/>
<field name="invoice_filter_type_domain" invisible="1"/>
<field name="company_currency_id" invisible="1"/>
<field name="commercial_partner_id" invisible="1"/>
@@ -1038,6 +1043,8 @@
<filter string="Unposted" name="unposted" domain="[('state', '=', 'draft')]" help="Unposted Journal Entries"/>
<filter string="Posted" name="posted" domain="[('state', '=', 'posted')]" help="Posted Journal Entries"/>
<separator/>
<filter string="Reversed" name="reversed" domain="[('payment_state', '=', 'reversed')]"/>
<separator/>
<filter string="To Check" name="to_check" domain="[('to_check', '=', True)]"/>
<separator/>
<filter string="Sales" name="sales" domain="[('journal_id.type', '=', 'sale')]" context="{'default_journal_type': 'sale'}"/>
@@ -1077,13 +1084,15 @@
<filter domain="[('invoice_user_id', '=', uid)]" name="myinvoices" help="My Invoices"/>
<separator/>
<filter name="draft" string="Draft" domain="[('state','=','draft')]"/>
<filter name="unpaid" string="Posted" domain="[('state', '=', 'posted'), ('invoice_payment_state', '=', 'not_paid')]"/>
<filter name="unpaid" string="Posted" domain="[('state', '=', 'posted'), ('payment_state', 'in', ('not_paid', 'partial'))]"/>
<separator/>
<filter string="To Check" name="to_check" domain="[('to_check', '=', True)]"/>
<separator/>
<filter name="in_payment" string="In Payment" domain="[('invoice_payment_state', '=', 'in_payment')]"/>
<filter name="paid" string="Paid" domain="[('invoice_payment_state', '=', 'paid')]"/>
<filter name="late" string="Overdue" domain="['&amp;', ('invoice_date_due', '&lt;', time.strftime('%%Y-%%m-%%d')), ('state', '=', 'posted'), ('invoice_payment_state', '=', 'not_paid')]" help="Overdue invoices, maturity date passed"/>
<filter name="in_payment" string="In Payment" domain="[('payment_state', '=', 'in_payment')]"/>
<filter name="partial" string="Partially Paid" domain="[('payment_state', '=', 'partial')]"/>
<filter name="paid" string="Paid" domain="[('payment_state', '=', 'paid')]"/>
<filter name="reversed" string="Reversed" domain="[('payment_state', '=', 'reversed')]"/>
<filter name="late" string="Overdue" domain="['&amp;', ('invoice_date_due', '&lt;', time.strftime('%%Y-%%m-%%d')), ('state', '=', 'posted'), ('payment_state', 'in', ('not_paid', 'partial'))]" help="Overdue invoices, maturity date passed"/>
<separator/>
<filter name="invoice_date" string="Invoice Date" date="invoice_date"/>
<filter name="due_date" string="Due Date" date="invoice_date_due"/>
@@ -7,7 +7,7 @@
<td class="text-center">
<t t-set="tx_ids" t-value="invoice.transaction_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))"/>
<t t-set="pending_manual_txs" t-value="tx_ids.filtered(lambda tx: tx.state == 'pending' and tx.acquirer_id.provider in ('transfer', 'manual'))"/>
<a t-if="invoice.state == 'posted' and invoice.invoice_payment_state == 'not_paid' and invoice.amount_total and invoice.type == 'out_invoice' and (pending_manual_txs or not tx_ids)"
<a t-if="invoice.state == 'posted' and invoice.payment_state in ('not_paid', 'partial') and invoice.amount_total and invoice.type == 'out_invoice' and (pending_manual_txs or not tx_ids)"
t-att-href="invoice.get_portal_url(anchor='portal_pay')" title="Pay Now" aria-label="Pay now" class="btn btn-sm btn-primary" role="button">
<i class="fa fa-arrow-circle-right"/><span class='d-none d-md-inline'> Pay Now</span>
</a>
@@ -16,7 +16,7 @@
<xpath expr="//t[@t-foreach='invoices']/tr/td[hasclass('tx_status')]" position="replace">
<t t-set="last_tx" t-value="invoice.get_portal_last_transaction()"/>
<td class="tx_status text-center">
<t t-if="invoice.state == 'posted' and invoice.invoice_payment_state == 'not_paid' and (last_tx.state not in ['pending', 'authorized', 'done', 'cancel'] or (last_tx.state == 'pending' and last_tx.acquirer_id.provider in ('transfer', 'manual')))">
<t t-if="invoice.state == 'posted' and invoice.payment_state in ('not_paid', 'partial') and (last_tx.state not in ['pending', 'authorized', 'done', 'cancel'] or (last_tx.state == 'pending' and last_tx.acquirer_id.provider in ('transfer', 'manual')))">
<span class="badge badge-pill badge-info"><i class="fa fa-fw fa-clock-o"></i><span class="d-none d-md-inline"> Waiting for Payment</span></span>
</t>
<t t-if="invoice.state == 'posted' and last_tx.state == 'authorized'">
@@ -25,7 +25,7 @@
<t t-if="invoice.state == 'posted' and last_tx.state == 'pending' and last_tx.acquirer_id.provider not in ('transfer', 'manual')">
<span class="badge badge-pill badge-warning"><span class="d-none d-md-inline"> Pending</span></span>
</t>
<t t-if="invoice.state == 'posted' and invoice.invoice_payment_state == 'paid' or last_tx.state == 'done'">
<t t-if="invoice.state == 'posted' and invoice.payment_state == 'paid' or last_tx.state == 'done'">
<span class="badge badge-pill badge-success"><i class="fa fa-fw fa-check"></i><span class="d-none d-md-inline"> Paid</span></span>
</t>
<t t-if="invoice.state == 'cancel'">
@@ -36,7 +36,7 @@
</template>
<template id="portal_invoice_payment" name="Invoice Payment">
<div class="row" t-if="not tx_ids and invoice.state == 'posted' and invoice.invoice_payment_state == 'not_paid' and invoice.amount_total" id="portal_pay">
<div class="row" t-if="not tx_ids and invoice.state == 'posted' and invoice.payment_state in ('not_paid', 'partial') and invoice.amount_total" id="portal_pay">
<div class="modal fade" id="pay_with" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
@@ -71,14 +71,14 @@
<t t-set="tx_ids" t-value="invoice.transaction_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))"/>
<t t-set="pending_manual_txs" t-value="tx_ids.filtered(lambda tx: tx.state == 'pending' and tx.acquirer_id.provider in ('transfer', 'manual'))"/>
<div>
<a href="#" t-if="invoice.state == 'posted' and invoice.invoice_payment_state == 'not_paid' and invoice.amount_total and invoice.type == 'out_invoice' and (pending_manual_txs or not tx_ids)"
<a href="#" t-if="invoice.state == 'posted' and invoice.payment_state in ('not_paid', 'partial') and invoice.amount_total and invoice.type == 'out_invoice' and (pending_manual_txs or not tx_ids)"
class="btn btn-primary btn-block mb-2" data-toggle="modal" data-target="#pay_with">
<i class="fa fa-fw fa-arrow-circle-right"/> Pay Now
</a>
<div t-if="tx_ids and not pending_manual_txs and invoice.invoice_payment_state != 'paid'" class="alert alert-info py-1 mb-2" >
<div t-if="tx_ids and not pending_manual_txs and invoice.payment_state != 'paid'" class="alert alert-info py-1 mb-2" >
<i class="fa fa-fw fa-check-circle"/> Pending
</div>
<div t-if="invoice.invoice_payment_state == 'paid'" class="alert alert-success py-1 mb-2" >
<div t-if="invoice.payment_state == 'paid'" class="alert alert-success py-1 mb-2" >
<i class="fa fa-fw fa-check-circle"/> Paid
</div>
</div>
@@ -91,7 +91,7 @@
</t>
</div>
<t t-set="tx_ids" t-value="invoice.transaction_ids.filtered(lambda tx: tx.state in ('authorized', 'done'))"/>
<div t-if="not tx_ids and invoice.state == 'posted' and invoice.invoice_payment_state == 'not_paid' and invoice.amount_total" id="portal_pay">
<div t-if="not tx_ids and invoice.state == 'posted' and invoice.payment_state in ('not_paid', 'partial') and invoice.amount_total" id="portal_pay">
<div t-if="pms or acquirers" id="payment_method">
<t t-call="account_payment.portal_invoice_payment"/>
</div>
@@ -136,7 +136,7 @@
<b>Communication: </b><span t-esc='invoice.ref'/>
</div>
</t>
<t t-if="success == 'pay_invoice' and invoice.invoice_payment_state == 'paid'">
<t t-if="success == 'pay_invoice' and invoice.payment_state == 'paid'">
Done, your online payment has been successfully processed. Thank you for your order.
</t>
</xpath>
@@ -10,7 +10,7 @@
(${object.partner_id.parent_id.name})
% endif
<br /><br />
Here is, in attachment, your
Here is, in attachment, your
% if object.name:
invoice <strong>${object.name}</strong>
% else:
@@ -21,7 +21,7 @@
% endif
amounting in <strong>${format_amount(object.amount_total, object.currency_id)}</strong>
from ${object.company_id.name}.
% if object.invoice_payment_state=='paid':
% if object.payment_state=='paid':
This invoice is already paid.
% else:
Please remit payment at your earliest convenience.
@@ -20,7 +20,7 @@
('type', 'not in', ('out_invoice', 'out_refund')),
('l10n_ch_isr_sent', '=', True),
('state', '!=', 'posted'),
('invoice_payment_state', '!=', 'not_paid'),
('payment_state', 'not in', ('not_paid', 'partial')),
('l10n_ch_currency_name', 'not in', ['EUR', 'CHF'])]}"
class="oe_highlight"
groups="base.group_user"
@@ -36,7 +36,7 @@
('type', 'not in', ('out_invoice', 'out_refund')),
('l10n_ch_isr_sent', '=', False),
('state', '!=', 'posted'),
('invoice_payment_state', '!=', 'not_paid'),
('payment_state', 'not in', ('not_paid', 'partial')),
('l10n_ch_currency_name', 'not in', ['EUR', 'CHF'])]}"
groups="base.group_user"
/>
+1 -1
View File
@@ -38,7 +38,7 @@
<field name="currency_id" invisible="1"/>
<field name="company_currency_id" invisible="1"/>
<field name="state" optional="show"/>
<field name="invoice_payment_state" optional="hide"/>
<field name="payment_state" optional="hide"/>
<field name="type" invisible="context.get('default_type', True)"/>
</tree>
</field>
+3 -3
View File
@@ -44,7 +44,7 @@ class MembershipLine(models.Model):
@api.depends('account_invoice_id.state',
'account_invoice_id.amount_residual',
'account_invoice_id.invoice_payment_state')
'account_invoice_id.payment_state')
def _compute_state(self):
"""Compute the state lines """
if not self:
@@ -59,7 +59,7 @@ class MembershipLine(models.Model):
reverse_map = dict(self._cr.fetchall())
for line in self:
move_state = line.account_invoice_id.state
payment_state = line.account_invoice_id.invoice_payment_state
payment_state = line.account_invoice_id.payment_state
line.state = 'none'
if move_state == 'draft':
@@ -72,7 +72,7 @@ class MembershipLine(models.Model):
line.state = 'paid'
elif payment_state == 'in_payment':
line.state = 'paid'
elif payment_state == 'not_paid':
elif payment_state in ('not_paid', 'partial'):
line.state = 'invoiced'
elif move_state == 'cancel':
line.state = 'canceled'
+1 -1
View File
@@ -39,7 +39,7 @@ class Partner(models.Model):
@api.depends('member_lines.account_invoice_line',
'member_lines.account_invoice_line.move_id.state',
'member_lines.account_invoice_line.move_id.invoice_payment_state',
'member_lines.account_invoice_line.move_id.payment_state',
'member_lines.account_invoice_line.move_id.partner_id',
'free_member',
'member_lines.date_to', 'member_lines.date_from',
@@ -10,7 +10,7 @@
The user must capture/void the authorized transactions before registering a new payment.
-->
<xpath expr="//button[@id='account_invoice_payment_btn']" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', '|', ('state', '!=', 'posted'), ('invoice_payment_state', '!=', 'not_paid'), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')), ('authorized_transaction_ids', '!=', [])]}</attribute>
<attribute name="attrs">{'invisible': ['|', '|', '|', ('state', '!=', 'posted'), ('payment_state', 'not in', ('partial', 'not_paid')), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')), ('authorized_transaction_ids', '!=', [])]}</attribute>
</xpath>
<xpath expr="//button[@id='account_invoice_payment_btn']" position="after">
<field name="authorized_transaction_ids" invisible="1"/>
+1 -1
View File
@@ -21,7 +21,7 @@ class AccountMove(models.Model):
super(AccountMove, self)._compute_amount()
pos_invoices = self.filtered(lambda i: i.type in ['out_invoice', 'out_refund'] and i.pos_order_ids)
for invoice in pos_invoices:
invoice.invoice_payment_state = 'paid'
invoice.payment_state = 'paid'
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
@@ -90,21 +90,21 @@ class ProductProduct(models.Model):
res[val.id]['date_to'] = date_to
res[val.id]['invoice_state'] = invoice_state
states = ()
invoice_payment_states = ()
payment_states = ()
if invoice_state == 'paid':
states = ('posted',)
invoice_payment_states = ('paid',)
payment_states = ('paid',)
elif invoice_state == 'open_paid':
states = ('posted',)
invoice_payment_states = ('not_paid', 'paid')
payment_states = ('not_paid', 'paid')
elif invoice_state == 'draft_open_paid':
states = ('posted', 'draft')
invoice_payment_states = ('not_paid', 'paid')
payment_states = ('not_paid', 'paid')
company_id = self.env.company.id
#Cost price is calculated afterwards as it is a property
self.env['account.move.line'].flush(['price_unit', 'quantity', 'balance', 'product_id', 'display_type'])
self.env['account.move'].flush(['state', 'invoice_payment_state', 'type', 'invoice_date', 'company_id'])
self.env['account.move'].flush(['state', 'payment_state', 'type', 'invoice_date', 'company_id'])
self.env['product.template'].flush(['list_price'])
sqlstr = """
WITH currency_rate AS ({})
@@ -124,14 +124,14 @@ class ProductProduct(models.Model):
(cr.date_end IS NULL OR cr.date_end > COALESCE(i.invoice_date, NOW())))
WHERE l.product_id = %s
AND i.state IN %s
AND i.invoice_payment_state IN %s
AND i.payment_state IN %s
AND i.type IN %s
AND i.invoice_date BETWEEN %s AND %s
AND i.company_id = %s
AND l.display_type IS NULL
""".format(self.env['res.currency']._select_companies_rates())
invoice_types = ('out_invoice', 'in_refund')
self.env.cr.execute(sqlstr, (val.id, states, invoice_payment_states, invoice_types, date_from, date_to, company_id))
self.env.cr.execute(sqlstr, (val.id, states, payment_states, invoice_types, date_from, date_to, company_id))
result = self.env.cr.fetchall()[0]
res[val.id]['sale_avg_price'] = result[0] and result[0] or 0.0
res[val.id]['sale_num_invoiced'] = result[1] and result[1] or 0.0
@@ -139,7 +139,7 @@ class ProductProduct(models.Model):
res[val.id]['sale_expected'] = result[3] and result[3] or 0.0
res[val.id]['sales_gap'] = res[val.id]['sale_expected'] - res[val.id]['turnover']
invoice_types = ('in_invoice', 'out_refund')
self.env.cr.execute(sqlstr, (val.id, states, invoice_payment_states, invoice_types, date_from, date_to, company_id))
self.env.cr.execute(sqlstr, (val.id, states, payment_states, invoice_types, date_from, date_to, company_id))
result = self.env.cr.fetchall()[0]
res[val.id]['purchase_avg_price'] = result[0] and result[0] or 0.0
res[val.id]['purchase_num_invoiced'] = result[1] and result[1] or 0.0
@@ -138,7 +138,8 @@ class TestValuationReconciliation(ValuationReconciliationTestCommon):
refund_invoice = self.env['account.move'].browse(refund_invoice_wiz.reverse_moves()['res_id'])
# Check the result
self.assertTrue(invoice.invoice_payment_state == refund_invoice.invoice_payment_state == 'paid'), "Invoice and refund should both be in 'Paid' state"
self.assertEqual(invoice.payment_state, 'reversed', "Invoice should be in 'reversed' state")
self.assertEqual(refund_invoice.payment_state, 'paid', "Refund should be in 'paid' state")
self.check_reconciliation(refund_invoice, return_pick)
def test_multiple_shipments_invoices(self):
+2 -2
View File
@@ -80,7 +80,7 @@ class CrmTeam(models.Model):
FROM account_move move
LEFT JOIN account_move_line line ON line.move_id = move.id
WHERE move.type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')
AND move.invoice_payment_state IN ('in_payment', 'paid')
AND move.payment_state IN ('in_payment', 'paid')
AND move.state = 'posted'
AND move.team_id IN %s
AND move.date BETWEEN %s AND %s
@@ -96,7 +96,7 @@ class CrmTeam(models.Model):
data_map = dict((v[0], v[1]) for v in self._cr.fetchall())
for team in self:
team.invoiced = data_map.get(team.id, 0.0)
def _graph_get_model(self):
if self._context.get('in_sales_app'):
return 'sale.report'
+1 -1
View File
@@ -199,7 +199,7 @@ class TestSaleToInvoice(TestCommonSaleNoChart):
# Check invoice's type and number
self.assertEqual(invoice_refund.type, 'out_refund', 'The last created invoiced should be a customer invoice')
self.assertEqual(invoice_refund.invoice_payment_state, 'paid', 'Last Customer creadit note should be in paid state')
self.assertEqual(invoice_refund.payment_state, 'paid', 'Last Customer creadit note should be in paid state')
self.assertEqual(self.sale_order.invoice_count, 2, "The SO should have 3 related invoices: the original, the refund, and the new one")
self.assertEqual(len(self.sale_order.invoice_ids.filtered(lambda inv: inv.type == 'out_refund')), 1, "The SO should be linked to only one refund")
self.assertEqual(len(self.sale_order.invoice_ids.filtered(lambda inv: inv.type == 'out_invoice')), 1, "The SO should be linked to only one customer invoices")
+1 -1
View File
@@ -47,7 +47,7 @@ class TestSaleTransaction(AccountTestCommon):
invoice = self.order._create_invoices()
invoice.post()
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertEqual(invoice.payment_state, 'paid')
def test_sale_transaction_mismatch(self):
"""Test that a transaction for the incorrect amount does not validate the SO."""
@@ -157,10 +157,8 @@ class TestValuationReconciliation(ValuationReconciliationTestCommon):
'refund_method': 'cancel',
})
refund_invoice = self.env['account.move'].browse(refund_invoice_wiz.reverse_moves()['res_id'])
self.assertTrue(
invoice.invoice_payment_state == refund_invoice.invoice_payment_state == 'paid',
"Invoice and refund should both be in 'Paid' state"
)
self.assertEqual(invoice.payment_state, 'reversed', "Invoice should be in 'reversed' state.")
self.assertEqual(refund_invoice.payment_state, 'paid', "Refund should be in 'paid' state.")
self.check_reconciliation(refund_invoice, return_pick, operation='sale')
def test_multiple_shipments_invoices(self):
@@ -36,7 +36,7 @@ class WebsiteSaleDigital(CustomerPortal):
if not 'sale_order' in response.qcontext:
return response
order = response.qcontext['sale_order']
invoiced_lines = request.env['account.move.line'].sudo().search([('move_id', 'in', order.invoice_ids.ids), ('move_id.invoice_payment_state', '=', 'paid')])
invoiced_lines = request.env['account.move.line'].sudo().search([('move_id', 'in', order.invoice_ids.ids), ('move_id.payment_state', '=', 'paid')])
products = invoiced_lines.mapped('product_id') | order.order_line.filtered(lambda r: not r.price_subtotal).mapped('product_id')
if not order.amount_total:
# in that case, we should add all download links to the products
@@ -14,7 +14,7 @@ class AccountInvoiceLine(models.Model):
# Get paid invoices
purchases = self.sudo().search_read(
domain=[
('move_id.invoice_payment_state', '=', 'paid'),
('move_id.payment_state', '=', 'paid'),
('move_id.partner_id', '=', partner.id),
('product_id', '!=', False),
],