* Change the way that the invoices are setted to draft. With the

possibilty to enter a new law that not allow to remove any account move,
the invoice is setted to draft creating a compesantion move and
associateting it to the invoice throwe the additiona_move field.

Task: #047268

* Fix some bugs and improve code reducing lines.

Task: #047268

* Remove commented line not needed.

Tasks: #047268

* Remove Warning definition not used.

Tasks: #047268

* Fix some text.

Task: 047268

---------

Co-authored-by: Bernat Brunet <bernat@nan-tic.com>
This commit is contained in:
nan-tic-dev 2023-12-12 10:17:04 +01:00 committed by Bernat Brunet
parent 5b4f6c3dd4
commit 01edc57548
8 changed files with 200 additions and 89 deletions

View File

@ -5,12 +5,14 @@ from trytond.pool import Pool
from . import invoice
from . import commission
from . import payment
from . import move
def register():
Pool.register(
invoice.Invoice,
invoice.Move,
move.Move,
move.Line,
module='account_invoice_posted2draft', type_='model')
Pool.register(
payment.Invoice,

View File

@ -4,27 +4,43 @@
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice
__all__ = ['Invoice']
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
def get_allow_draft(self, name):
pool = Pool()
Commission = pool.get('commission')
result = super().get_allow_draft(name)
invoiced = Commission.search([
('origin.invoice', '=', self.id, 'account.invoice.line'),
('invoice_line', '!=', None),
])
if invoiced:
result = False
return result
@classmethod
def draft(cls, invoices):
Commission = Pool().get('commission')
pool = Pool()
Commission = pool.get('commission')
to_delete = []
for sub_invoices in grouped_slice(invoices):
ids = [i.id for i in sub_invoices]
commissions = Commission.search([
to_delete = Commission.search([
('origin.invoice', 'in', ids, 'account.invoice.line'),
('invoice_line', '=', None),
])
if commissions:
commissions_origin = Commission.search([
('origin.id', 'in', [c.id for c in commissions], 'commission'),
if to_delete:
to_delete_origin = Commission.search([
('origin.id', 'in',
[x.id for x in to_delete], 'commission'),
('invoice_line', '=', None),
])
if commissions_origin:
commissions += commissions_origin
Commission.delete(commissions)
if to_delete_origin:
to_delete += to_delete_origin
Commission.delete(to_delete)
return super(Invoice, cls).draft(invoices)

View File

@ -1,8 +1,9 @@
# This file is part account_invoice_posted2draft module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond.pyson import Eval
from trytond.pool import Pool, PoolMeta
from trytond.model import fields
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.i18n import gettext
from trytond.exceptions import UserError
@ -11,16 +12,35 @@ from trytond.exceptions import UserError
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
allow_draft = fields.Function(
fields.Boolean("Allow Draft Invoice"), 'get_allow_draft')
@classmethod
def __setup__(cls):
super(Invoice, cls).__setup__()
cls._check_modify_exclude.add('move')
cls._check_modify_exclude.add('validated_by')
cls._check_modify_exclude.add('posted_by')
cls._transitions |= set((('posted', 'draft'),))
cls._buttons['draft']['invisible'] = (
Eval('state').in_(['draft', 'paid']) | (
(Eval('state') == 'cancelled') & Eval('cancel_move')))
cls._buttons['draft']['invisible'] = ~Eval('allow_draft', False)
cls._buttons['draft']['depends'] += tuple(['allow_draft'])
def get_allow_draft(self, name):
# when IN invoice is validate from scratch, the move is in 'draft'
# state, so in this case could be draft in a "normal" way
if (self.state == 'validated' and self.move
and self.move.state != 'draft'):
return False
elif self.state == 'cancelled' and self.number is not None:
return False
elif self.state in {'paid', 'draft'}:
return False
elif self.state == 'posted':
lines_to_pay = [l for l in self.lines_to_pay
if not l.reconciliation]
# Invoice already paid or partial paid, should not be possible
# to change state to draft.
if (not lines_to_pay
or self.amount_to_pay != self.total_amount):
return False
return True
@classmethod
def draft(cls, invoices):
@ -28,10 +48,14 @@ class Invoice(metaclass=PoolMeta):
Move = pool.get('account.move')
MoveLine = pool.get('account.move.line')
JournalPeriod = pool.get('account.journal.period')
Warning = pool.get('res.user.warning')
moves = []
payment_lines = []
move_lines = []
to_draft = []
to_save = []
for invoice in invoices:
<<<<<<< HEAD
if invoice.move:
# check period is closed
if invoice.move.period.state == 'closed':
@ -59,33 +83,49 @@ class Invoice(metaclass=PoolMeta):
if payment_line.move and payment_line.move.lines:
for lines in payment_line.move.lines:
payment_lines.append(lines)
=======
if not invoice.allow_draft:
continue
>>>>>>> 13a65d8 (#047268 (#5))
if moves:
with Transaction().set_context(draft_invoices=True):
Move.write(moves, {'state': 'draft'})
# If the payment lines dont have a reconciliation, then the field
# invoice_payment will be fill up, and when we try to draft an
# invoice it will give us an error
if payment_lines:
MoveLine.write(payment_lines, {'invoice_payment':None})
cls.write(invoices, {
'invoice_report_format': None,
'invoice_report_cache': None,
})
with Transaction().set_context(draft_invoices=True):
return super(Invoice, cls).draft(invoices)
move = invoice.move
if move:
if move.state == 'draft':
to_draft.append(invoice)
else:
to_save.append(invoice)
cancel_move = move.cancel(reversal=True)
Move.post([cancel_move])
moves.extend((invoice.move, cancel_move))
invoice.move = None
else:
to_draft.append(invoice)
if invoice.cancel_move:
moves.append(invoice.cancel_move)
invoice.cancel_move = None
invoice.additional_moves += tuple(moves)
@classmethod
def credit(cls, invoices, refund=False, **values):
with Transaction().set_context(cancel_from_credit=True):
return super().credit(invoices, refund, **values)
# Only make the special steps for the invoices that came from 'posted'
# state or 'validated', 'cancelled' with number, so the invoice have one
# or more move associated.
# The other possible invoices follow the standard workflow.
if to_draft:
super().draft(to_draft)
if to_save:
cls.save(to_save)
with Transaction().set_context(invoice_posted2draft=True):
super().draft(to_save)
for invoice in to_save:
to_reconcile = []
for move in invoice.additional_moves:
for line in move.lines:
if (not line.reconciliation
and line.account == invoice.account):
to_reconcile.append(line)
if to_reconcile:
MoveLine.reconcile(to_reconcile)
class Move(metaclass=PoolMeta):
__name__ = 'account.move'
@classmethod
def check_modify(cls, *args, **kwargs):
if Transaction().context.get('draft_invoices', False):
return
return super(Move, cls).check_modify(*args, **kwargs)
# Remove links to lines which actually do not pay the invoice
if to_save:
cls._clean_payments(to_save)

View File

@ -2,22 +2,26 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_cancel_invoice_with_number"
msgid "You cannot cancel invoice \"%(invoice)s\" because it already has a number."
msgstr "No podeu cancel·lar la factura \"%(invoice)s\" amb un número assignat."
msgctxt "field:account.invoice,allow_draft:"
msgid "Allow Draft Invoice"
msgstr "Permet factura esborrany"
msgctxt "model:ir.message,text:msg_draft_closed_period"
msgid ""
"You can not set to draft invoice \"%(invoice)s\" because period "
"\"%(period)s\" is closed."
"You cannot create an account compesantion move in the period %(period)s "
"because is closed."
msgstr ""
"No es pot passar a esborrany la factura \"%(invoice)s\" perquè el període "
"\"%(period)s\" està tancat."
"No podeu crear un assentamet de compensació en el període %(period)s perquè "
"està tancat."
msgctxt "model:ir.message,text:msg_modify_closed_journal_period"
msgctxt "model:ir.message,text:msg_invoice_in_payment"
msgid ""
"You can not set to draft invoice \"%(invoice)s\" on closed journal-period "
"\"%(journal_period)s\"."
"The invoice %(invoice)s could not be possible to draft, becasue it have one "
"or more move lines in payments [IDs: %(payments)s]. This means that is "
"possible that this payment will be in a payment group and this group upload "
"on a Bank."
msgstr ""
"No es pot passar a esborrany la factura \"%(invoice)s\" perquè el diari-"
"període \"%(journal_period)s\". està tancat."
"La factura %(invoice)s no es pot passar a esborrany, perquè té un o més "
"apunts relacionats a pagaments [ID: %(payments)s]. Això vol dir que és "
"possible que aquest pagament estigui en una remesa bancària i aquesta estigui "
"pujada ja al Banc."

View File

@ -2,22 +2,26 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_cancel_invoice_with_number"
msgid "You cannot cancel invoice \"%(invoice)s\" because it already has a number."
msgstr "No puede cancelar la factura \"%(invoice)s\" con un número asignado."
msgctxt "field:account.invoice,allow_draft:"
msgid "Allow Draft Invoice"
msgstr "Permitir factura borrador"
msgctxt "model:ir.message,text:msg_draft_closed_period"
msgid ""
"You can not set to draft invoice \"%(invoice)s\" because period "
"\"%(period)s\" is closed."
"You cannot create an account compesantion move in the period %(period)s "
"because is closed."
msgstr ""
"No se puede pasar a borrador la factura \"%(invoice)s\" porqué el período "
"\"%(period)s\" está cerrado."
"No puedes crear un asiento de compensación en el periodo %(period)s porque "
"está cerrado."
msgctxt "model:ir.message,text:msg_modify_closed_journal_period"
msgctxt "model:ir.message,text:msg_invoice_in_payment"
msgid ""
"You can not set to draft invoice \"%(invoice)s\" on closed journal-period "
"\"%(journal_period)s\"."
"The invoice %(invoice)s could not be possible to draft, becasue it have one "
"or more move lines in payments [IDs: %(payments)s]. This means that is "
"possible that this payment will be in a payment group and this group upload "
"on a Bank."
msgstr ""
"No se puede pasar a borrador la factura \"%(invoice)s\" porqué el diario-"
"período \"%(journal_period)s\" está cerrado."
"La factura %(invoice)s no se puede pasar a borrador, porque tiene uno o más "
"apuntes relacionados a pagos [ID: %(payments)s]. Esto quiere decir que és "
"posible que estos pagos esten ya en una remesa bancárea y ésta esté subida "
"al Banco."

View File

@ -4,13 +4,10 @@ this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_draft_closed_period">
<field name="text">You can not set to draft invoice "%(invoice)s" because period "%(period)s" is closed.</field>
<field name="text">You cannot create an account compesantion move in the period %(period)s because is closed.</field>
</record>
<record model="ir.message" id="msg_cancel_invoice_with_number">
<field name="text">You cannot cancel invoice "%(invoice)s" because it already has a number.</field>
</record>
<record model="ir.message" id="msg_modify_closed_journal_period">
<field name="text">You can not set to draft invoice "%(invoice)s" on closed journal-period "%(journal_period)s".</field>
<record model="ir.message" id="msg_invoice_in_payment">
<field name="text">The invoice %(invoice)s could not be possible to draft, becasue it have one or more move lines in payments [IDs: %(payments)s]. This means that is possible that this payment will be in a payment group and this group upload on a Bank.</field>
</record>
</data>
</tryton>

40
move.py Normal file
View File

@ -0,0 +1,40 @@
# This file is part account_invoice_posted2draft module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
class Move(metaclass=PoolMeta):
__name__ = 'account.move'
@classmethod
def check_modify(cls, *args, **kwargs):
# As now the moves related to an invoice are not delete when 'draft'
# the invoice, is needed to modify some restricted fields when the
# move is in post state
if Transaction().context.get('invoice_posted2draft', False):
return
return super().check_modify(*args, **kwargs)
@classmethod
def delete(cls, moves):
# When invoice is set to 'draft', try to delete the move's associated
# in 'move' and 'additional_move' fields. If these moves are posted
# they cannot be deleted but keep them as history
if Transaction().context.get('invoice_posted2draft', False):
return
super().delete(moves)
class Line(metaclass=PoolMeta):
__name__ = 'account.move.line'
@classmethod
def check_modify(cls, lines, modified_fields=None):
# As now the moves related to an invoice are not delete when 'draft'
# the invoice, is needed to modify some restricted fields when the
# move is in post state
if Transaction().context.get('invoice_posted2draft', False):
return
return super().check_modify(lines, modified_fields)

View File

@ -2,8 +2,8 @@
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond.pool import Pool, PoolMeta
__all__ = ['Invoice']
from trytond.i18n import gettext
from trytond.exceptions import UserError
class Invoice(metaclass=PoolMeta):
@ -11,18 +11,26 @@ class Invoice(metaclass=PoolMeta):
@classmethod
def draft(cls, invoices):
Payment = Pool().get('account.payment')
pool = Pool()
Payment = pool.get('account.payment')
lines = []
for invoice in invoices:
moves = []
if invoice.move:
lines.extend([l.id for l in invoice.move.lines])
if lines:
payments = Payment.search([
('line', 'in', lines),
('state', '=', 'failed'),
])
if payments:
Payment.write(payments, {'line': None})
moves.append(invoice.move)
if invoice.additional_moves:
moves.extend(invoice.additional_moves)
return super(Invoice, cls).draft(invoices)
if moves:
lines = [l.id for m in moves for l in m.lines]
if lines:
payments = Payment.search([
('line', 'in', lines),
('state', '!=', 'failed'),
])
if payments:
raise UserError(gettext('account_invoice_posted2draft'
'.msg_invoice_in_payment',
invoice=invoice.rec_name,
payments=", ".join([p.id for p in payments])))
return super().draft(invoices)