[MIG] account_move_base_import: Migration to 12.0
parent
88bff7c92f
commit
d0c70ff134
|
@ -5,7 +5,7 @@
|
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||
{
|
||||
'name': "Journal Entry base import",
|
||||
'version': '11.0.1.0.0',
|
||||
'version': '12.0.1.0.0',
|
||||
'author': "Akretion,Camptocamp,Odoo Community Association (OCA)",
|
||||
'category': 'Finance',
|
||||
'depends': ['account'],
|
||||
|
@ -19,7 +19,7 @@
|
|||
"views/partner_view.xml",
|
||||
],
|
||||
'external_dependencies': {
|
||||
'python' : ['xlrd'],
|
||||
'python': ['xlrd'],
|
||||
},
|
||||
'installable': True,
|
||||
'license': 'AGPL-3',
|
||||
|
|
|
@ -9,12 +9,12 @@ import os
|
|||
from odoo import _, api, fields, models
|
||||
from ..parser.parser import new_move_parser
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from operator import attrgetter
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_name = 'account.journal'
|
||||
_inherit = ['account.journal', 'mail.thread']
|
||||
_order = 'sequence'
|
||||
|
||||
used_for_import = fields.Boolean(
|
||||
string="Journal used for import")
|
||||
|
@ -70,33 +70,6 @@ class AccountJournal(models.Model):
|
|||
help="Two counterparts will be automatically created : one for "
|
||||
"the refunds and one for the payments")
|
||||
|
||||
def _get_rules(self):
|
||||
# We need to respect the sequence order
|
||||
return sorted(self.rule_ids, key=attrgetter('sequence'))
|
||||
|
||||
def _find_values_from_rules(self, calls, line):
|
||||
"""This method will execute all related rules, in their sequence order,
|
||||
to retrieve all the values returned by the first rules that will match.
|
||||
:param calls: list of lookup function name available in rules
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the statement line or {}
|
||||
{'partner_id': value,
|
||||
'account_id: value,
|
||||
...}
|
||||
"""
|
||||
if not calls:
|
||||
calls = self._get_rules()
|
||||
rule_obj = self.env['account.move.completion.rule']
|
||||
for call in calls:
|
||||
method_to_call = getattr(rule_obj, call.function_to_call)
|
||||
result = method_to_call(line)
|
||||
if result:
|
||||
result['already_completed'] = True
|
||||
return result
|
||||
return None
|
||||
|
||||
@api.multi
|
||||
def _prepare_counterpart_line(self, move, amount, date):
|
||||
if amount > 0.0:
|
||||
|
@ -108,7 +81,6 @@ class AccountJournal(models.Model):
|
|||
credit = -amount
|
||||
debit = 0.0
|
||||
counterpart_values = {
|
||||
'name': _('/'),
|
||||
'date_maturity': date,
|
||||
'credit': credit,
|
||||
'debit': debit,
|
||||
|
@ -229,7 +201,7 @@ class AccountJournal(models.Model):
|
|||
|
||||
def prepare_move_line_vals(self, parser_vals, move):
|
||||
"""Hook to build the values of a line from the parser returned values.
|
||||
At least it fullfill the basic values. Overide it to add your own
|
||||
At least it fulfills the basic values. Override it to add your own
|
||||
completion if needed.
|
||||
|
||||
:param dict of vals from parser for account.bank.statement.line
|
||||
|
@ -295,9 +267,9 @@ class AccountJournal(models.Model):
|
|||
the given profile.
|
||||
|
||||
:param int/long profile_id: ID of the profile used to import the file
|
||||
:param filebuffer file_stream: binary of the providen file
|
||||
:param char: ftype represent the file exstension (csv by default)
|
||||
:return: list: list of ids of the created account.bank.statemênt
|
||||
:param filebuffer file_stream: binary of the provided file
|
||||
:param char: ftype represent the file extension (csv by default)
|
||||
:return: list: list of ids of the created account.bank.statement
|
||||
"""
|
||||
filename = self._context.get('file_name', None)
|
||||
if filename:
|
||||
|
@ -311,15 +283,15 @@ class AccountJournal(models.Model):
|
|||
|
||||
def _move_import(self, parser, file_stream, ftype="csv"):
|
||||
"""Create a bank statement with the given profile and parser. It will
|
||||
fullfill the bank statement with the values of the file providen, but
|
||||
fulfill the bank statement with the values of the file provided, but
|
||||
will not complete data (like finding the partner, or the right
|
||||
account). This will be done in a second step with the completion rules.
|
||||
|
||||
:param prof : The profile used to import the file
|
||||
:param parser: the parser
|
||||
:param filebuffer file_stream: binary of the providen file
|
||||
:param char: ftype represent the file exstension (csv by default)
|
||||
:return: ID of the created account.bank.statemênt
|
||||
:param filebuffer file_stream: binary of the provided file
|
||||
:param char: ftype represent the file extension (csv by default)
|
||||
:return: ID of the created account.bank.statement
|
||||
"""
|
||||
move_obj = self.env['account.move']
|
||||
move_line_obj = self.env['account.move.line']
|
||||
|
@ -346,10 +318,9 @@ class AccountJournal(models.Model):
|
|||
parser_vals = parser.get_move_line_vals(line)
|
||||
values = self.prepare_move_line_vals(parser_vals, move)
|
||||
move_store.append(values)
|
||||
# TODO Check if this is still needed
|
||||
# Hack to bypass ORM poor perfomance. Sob...
|
||||
move_line_obj._insert_lines(move_store)
|
||||
self.invalidate_cache()
|
||||
move_line_obj.with_context(check_move_validity=False).create(
|
||||
move_store
|
||||
)
|
||||
self._write_extra_move_lines(parser, move)
|
||||
if self.create_counterpart:
|
||||
self._create_counterpart(parser, move)
|
||||
|
|
|
@ -7,11 +7,8 @@ import traceback
|
|||
import sys
|
||||
import logging
|
||||
|
||||
import psycopg2
|
||||
|
||||
from odoo import _, api, fields, models, registry
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import float_repr
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
@ -34,15 +31,16 @@ class ErrorTooManyPartner(Exception):
|
|||
|
||||
class AccountMoveCompletionRule(models.Model):
|
||||
"""This will represent all the completion method that we can have to
|
||||
fullfill the bank statement lines. You'll be able to extend them in you own
|
||||
fulfill the bank statement lines. You'll be able to extend them in you own
|
||||
module and choose those to apply for every statement profile.
|
||||
The goal of a rule is to fullfill at least the partner of the line, but
|
||||
The goal of a rule is to fulfill at least the partner of the line, but
|
||||
if possible also the reference because we'll use it in the reconciliation
|
||||
process. The reference should contain the invoice number or the SO number
|
||||
or any reference that will be matched by the invoice accounting move.
|
||||
"""
|
||||
_name = "account.move.completion.rule"
|
||||
_order = "sequence asc"
|
||||
_description = "Account move completion method"
|
||||
|
||||
sequence = fields.Integer(
|
||||
string='Sequence',
|
||||
|
@ -224,7 +222,7 @@ class AccountMoveLine(models.Model):
|
|||
Add sparse field on the statement line to allow to store all the bank infos
|
||||
that are given by a bank/office. You can then add you own in your module.
|
||||
The idea here is to store all bank/office infos in the
|
||||
additionnal_bank_fields serialized field when importing the file. If many
|
||||
additional_bank_fields serialized field when importing the file. If many
|
||||
values, add a tab in the bank statement line to store your specific one.
|
||||
Have a look in account_move_base_import module to see how we've done
|
||||
it.
|
||||
|
@ -238,119 +236,51 @@ class AccountMoveLine(models.Model):
|
|||
help="When this checkbox is ticked, the auto-completion "
|
||||
"process/button will ignore this line.")
|
||||
|
||||
def _get_line_values_from_rules(self, rules):
|
||||
def _get_line_values_from_rules(self):
|
||||
"""We'll try to find out the values related to the line based on rules
|
||||
setted on the profile.. We will ignore line for which already_completed
|
||||
set on the profile.. We will ignore line for which already_completed
|
||||
is ticked.
|
||||
|
||||
:return:
|
||||
A dict of dict value that can be passed directly to the write
|
||||
method of the statement line or {}. The first dict has statement
|
||||
method of the move line or {}. The first dict has statement
|
||||
line ID as a key: {117009: {'partner_id': 100997,
|
||||
'account_id': 489L}}
|
||||
"""
|
||||
journal_obj = self.env['account.journal']
|
||||
for line in self:
|
||||
if not line.already_completed:
|
||||
# Ask the rule
|
||||
vals = journal_obj._find_values_from_rules(rules, line)
|
||||
if vals:
|
||||
vals['id'] = line['id']
|
||||
return vals
|
||||
return {}
|
||||
self.ensure_one()
|
||||
vals = {}
|
||||
if not self.already_completed:
|
||||
# Ask the rule
|
||||
vals = self._find_values_from_rules()
|
||||
return vals
|
||||
|
||||
def _get_available_columns(self, move_store):
|
||||
"""Return writeable by SQL columns"""
|
||||
model_cols = self._fields
|
||||
avail = [
|
||||
k for k, col in model_cols.items() if not hasattr(col, '_fnct')
|
||||
]
|
||||
keys = [k for k in list(move_store[0].keys()) if k in avail]
|
||||
keys.sort()
|
||||
return keys
|
||||
|
||||
def _prepare_insert(self, move, cols):
|
||||
""" Apply column formating to prepare data for SQL inserting
|
||||
Return a copy of statement
|
||||
def _find_values_from_rules(self):
|
||||
"""This method will execute all related rules, in their sequence order,
|
||||
to retrieve all the values returned by the first rules that will match.
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
the move line or {}
|
||||
{'partner_id': value,
|
||||
'account_id: value,
|
||||
...}
|
||||
"""
|
||||
move_copy = move
|
||||
for k, col in move_copy.items():
|
||||
if k in cols:
|
||||
# In v11, the record is needed to call convert_to_column on a
|
||||
# Monetary field because it must find the currency to apply the
|
||||
# proper rounding. So we mimic the call here as best as we can.
|
||||
if isinstance(self._fields[k], fields.Monetary):
|
||||
currency_field = move_copy.get(
|
||||
self._fields[k].currency_field
|
||||
)
|
||||
if currency_field:
|
||||
currency_id = (
|
||||
move_copy.get(currency_field)
|
||||
or move.get('company_currency_id')
|
||||
)
|
||||
if currency_id:
|
||||
currency = self.env['res.currency'].browse(
|
||||
currency_id
|
||||
)
|
||||
move_copy[k] = float_repr(
|
||||
currency.round(col), currency.decimal_places
|
||||
)
|
||||
continue
|
||||
# If no currency was found, set what convert_to_column
|
||||
# would do and used to do in v10
|
||||
move_copy[k] = float(col or 0.0)
|
||||
continue
|
||||
else:
|
||||
move_copy[k] = self._fields[k].convert_to_column(col, None)
|
||||
return move_copy
|
||||
|
||||
def _prepare_manyinsert(self, move_store, cols):
|
||||
""" Apply column formating to prepare multiple SQL inserts
|
||||
Return a copy of statement_store
|
||||
"""
|
||||
values = []
|
||||
for move in move_store:
|
||||
values.append(self._prepare_insert(move, cols))
|
||||
return values
|
||||
|
||||
def _insert_lines(self, move_store):
|
||||
""" Do raw insert into database because ORM is awfully slow
|
||||
when doing batch write. It is a shame that batch function
|
||||
does not exist"""
|
||||
self.check_access_rule('create')
|
||||
self.check_access_rights('create', raise_exception=True)
|
||||
cols = self._get_available_columns(move_store)
|
||||
move_store = self._prepare_manyinsert(move_store, cols)
|
||||
tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
|
||||
sql = "INSERT INTO account_move_line (%s) " \
|
||||
"VALUES (%s);" % tmp_vals
|
||||
try:
|
||||
self.env.cr.executemany(sql, tuple(move_store))
|
||||
except psycopg2.Error as sql_err:
|
||||
self.env.cr.rollback()
|
||||
raise ValidationError(
|
||||
_("ORM bypass error: %s") % sql_err.pgerror)
|
||||
|
||||
def _update_line(self, vals):
|
||||
""" Do raw update into database because ORM is awfully slow
|
||||
when cheking security.
|
||||
"""
|
||||
cols = self._get_available_columns([vals])
|
||||
vals = self._prepare_insert(vals, cols)
|
||||
tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
|
||||
sql = "UPDATE account_move_line " \
|
||||
"SET %s where id = %%(id)s;" % tmp_vals
|
||||
try:
|
||||
self.env.cr.execute(sql, vals)
|
||||
except psycopg2.Error as sql_err:
|
||||
self.env.cr.rollback()
|
||||
raise ValidationError(
|
||||
_("ORM bypass error: %s") % sql_err.pgerror)
|
||||
self.ensure_one()
|
||||
rules = self.journal_id.rule_ids
|
||||
for rule in rules:
|
||||
method_to_call = getattr(
|
||||
self.env['account.move.completion.rule'],
|
||||
rule.function_to_call
|
||||
)
|
||||
result = method_to_call(self)
|
||||
if result:
|
||||
result['already_completed'] = True
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
"""We add a basic button and stuff to support the auto-completion
|
||||
of the bank statement once line have been imported or manually fullfill.
|
||||
of the bank statement once line have been imported or manually fulfill.
|
||||
"""
|
||||
_name = 'account.move'
|
||||
_inherit = ['account.move', 'mail.thread']
|
||||
|
@ -401,18 +331,13 @@ class AccountMove(models.Model):
|
|||
already_completed checkbox so we won't compute them again unless the
|
||||
user untick them!
|
||||
"""
|
||||
move_line_obj = self.env['account.move.line']
|
||||
compl_lines = 0
|
||||
move_line_obj.check_access_rule('create')
|
||||
move_line_obj.check_access_rights('create', raise_exception=True)
|
||||
for move in self:
|
||||
msg_lines = []
|
||||
journal = move.journal_id
|
||||
rules = journal._get_rules()
|
||||
res = False
|
||||
for line in move.line_ids:
|
||||
try:
|
||||
res = line._get_line_values_from_rules(rules)
|
||||
res = line._get_line_values_from_rules()
|
||||
if res:
|
||||
compl_lines += 1
|
||||
except ErrorTooManyPartner as exc:
|
||||
|
@ -426,7 +351,7 @@ class AccountMove(models.Model):
|
|||
_logger.error(st)
|
||||
if res:
|
||||
try:
|
||||
move_line_obj._update_line(res)
|
||||
line.write(res)
|
||||
except Exception as exc:
|
||||
msg_lines.append(repr(exc))
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
|
|
|
@ -7,7 +7,7 @@ import base64
|
|||
import os
|
||||
from operator import attrgetter
|
||||
from odoo.tests import common
|
||||
from odoo import tools
|
||||
from odoo import tools, fields
|
||||
from odoo.modules import get_resource_path
|
||||
|
||||
|
||||
|
@ -73,6 +73,8 @@ class TestCodaImport(common.TransactionCase):
|
|||
move_line = sorted(move.line_ids,
|
||||
key=attrgetter('date_maturity'))[2]
|
||||
# common infos
|
||||
self.assertEqual(move_line.date_maturity, "2011-03-07")
|
||||
self.assertEqual(
|
||||
move_line.date_maturity, fields.Date.from_string('2011-03-07')
|
||||
)
|
||||
self.assertEqual(move_line.credit, 118.4)
|
||||
self.assertEqual(move_line.name, "label a")
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
import datetime as dt
|
||||
|
||||
from odoo import fields
|
||||
from odoo.modules import get_resource_path
|
||||
from odoo.tests import SingleTransactionCase
|
||||
from odoo.tools import convert_file
|
||||
|
@ -40,12 +39,11 @@ class TestInvoice(SingleTransactionCase):
|
|||
'product_id': product_3.id,
|
||||
'price_unit': 210.0,
|
||||
'quantity': 1.0,
|
||||
'uom_id': self.env.ref('product.product_uom_unit').id,
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'account_id': self.env.ref('account.a_sale').id,
|
||||
})],
|
||||
'journal_id': self.journal.id,
|
||||
'partner_id': self.partner.id,
|
||||
'reference_type': 'none',
|
||||
'account_id': self.env.ref('account.a_recv').id,
|
||||
})
|
||||
# I confirm the Invoice
|
||||
|
@ -55,19 +53,41 @@ class TestInvoice(SingleTransactionCase):
|
|||
# I check that it is given the number "TBNK/%Y/0001"
|
||||
self.assertEqual(
|
||||
self.invoice_for_completion_1.number,
|
||||
dt.date.today().strftime('TBNK/%Y/0001')
|
||||
fields.Date.today().strftime('TBNK/%Y/0001')
|
||||
)
|
||||
|
||||
def test_03_supplier_invoice(self):
|
||||
# I import account minimal data
|
||||
convert_file(
|
||||
self.cr, 'account', get_resource_path(
|
||||
'account', 'demo', 'account_invoice_demo.yml'
|
||||
), {}, 'init', False, 'test'
|
||||
)
|
||||
# I create a demo invoice
|
||||
product_delivery = self.env.ref('product.product_delivery_01')
|
||||
product_order = self.env.ref('product.product_order_01')
|
||||
exp_account = self.env.ref('account.a_expense')
|
||||
rec_account = self.env.ref('account.a_recv')
|
||||
demo_invoice_0 = self.env['account.invoice'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'payment_term_id': self.env.ref('account.account_payment_term').id,
|
||||
'type': 'in_invoice',
|
||||
'date_invoice': fields.Date.today().replace(day=1),
|
||||
'account_id': rec_account.id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'price_unit': 10.0,
|
||||
'quantity': 1.0,
|
||||
'product_id': product_delivery.id,
|
||||
'name': product_delivery.name,
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'account_id': exp_account.id,
|
||||
}), (0, 0, {
|
||||
'price_unit': 4.0,
|
||||
'quantity': 1.0,
|
||||
'product_id': product_order.id,
|
||||
'name': product_order.name,
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'account_id': exp_account.id,
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
# I check that my invoice is a supplier invoice
|
||||
demo_invoice_0 = self.env.ref('account.demo_invoice_0')
|
||||
self.assertEqual(
|
||||
demo_invoice_0.type, 'in_invoice', msg="Check invoice type"
|
||||
)
|
||||
|
@ -107,13 +127,12 @@ class TestInvoice(SingleTransactionCase):
|
|||
'product_id': product_3.id,
|
||||
'price_unit': 210.0,
|
||||
'quantity': 1.0,
|
||||
'uom_id': self.env.ref('product.product_uom_unit').id,
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'account_id': self.env.ref('account.a_sale').id,
|
||||
})],
|
||||
'journal_id': self.env.ref('account.expenses_journal').id,
|
||||
'partner_id': res_partner_12_child.id,
|
||||
'type': 'out_refund',
|
||||
'reference_type': 'none',
|
||||
'account_id': self.env.ref('account.a_recv').id,
|
||||
})
|
||||
# I confirm the refund
|
||||
|
@ -124,11 +143,10 @@ class TestInvoice(SingleTransactionCase):
|
|||
# I check that it is given the number "RTEXJ/%Y/0001"
|
||||
self.assertEqual(
|
||||
self.refund_for_completion_1.number,
|
||||
dt.date.today().strftime('RTEXJ/%Y/0001')
|
||||
fields.Date.today().strftime('RTEXJ/%Y/0001')
|
||||
)
|
||||
|
||||
def test_05_completion(self):
|
||||
|
||||
# In order to test the banking framework, I first need to create a
|
||||
# journal
|
||||
self.journal = self.env.ref("account.bank_journal")
|
||||
|
@ -165,7 +183,7 @@ class TestInvoice(SingleTransactionCase):
|
|||
'name': '\\',
|
||||
'account_id': self.env.ref('account.a_sale').id,
|
||||
'move_id': move_test1.id,
|
||||
'date_maturity': '2013-12-20',
|
||||
'date_maturity': fields.Date.from_string('2013-12-20'),
|
||||
'credit': 0.0,
|
||||
})
|
||||
# I create a move line for a SI
|
||||
|
@ -173,7 +191,7 @@ class TestInvoice(SingleTransactionCase):
|
|||
'name': '\\',
|
||||
'account_id': self.env.ref('account.a_expense').id,
|
||||
'move_id': move_test1.id,
|
||||
'date_maturity': '2013-12-19',
|
||||
'date_maturity': fields.Date.from_string('2013-12-19'),
|
||||
'debit': 0.0,
|
||||
})
|
||||
# I create a move line for a CR
|
||||
|
@ -181,15 +199,15 @@ class TestInvoice(SingleTransactionCase):
|
|||
'name': '\\',
|
||||
'account_id': self.env.ref('account.a_expense').id,
|
||||
'move_id': move_test1.id,
|
||||
'date_maturity': '2013-12-19',
|
||||
'date_maturity': fields.Date.from_string('2013-12-19'),
|
||||
'debit': 0.0,
|
||||
})
|
||||
# I create a move line for the Partner Name
|
||||
move_line_partner_name = self.env['account.move.line'].create({
|
||||
'name': 'Test autocompletion based on Partner Name Camptocamp',
|
||||
'name': 'Test autocompletion based on Partner Name Azure Interior',
|
||||
'account_id': self.env.ref('account.a_sale').id,
|
||||
'move_id': move_test1.id,
|
||||
'date_maturity': '2013-12-17',
|
||||
'date_maturity': fields.Date.from_string('2013-12-17'),
|
||||
'credit': 0.0,
|
||||
})
|
||||
# I create a move line for the Partner Label
|
||||
|
@ -202,7 +220,7 @@ class TestInvoice(SingleTransactionCase):
|
|||
})
|
||||
# and add the correct name
|
||||
move_line_ci.with_context(check_move_validity=False).write({
|
||||
'name': dt.date.today().strftime('TBNK/%Y/0001'),
|
||||
'name': fields.Date.today().strftime('TBNK/%Y/0001'),
|
||||
'credit': 210.0,
|
||||
})
|
||||
move_line_si.with_context(check_move_validity=False).write({
|
||||
|
@ -210,7 +228,7 @@ class TestInvoice(SingleTransactionCase):
|
|||
'debit': 65.0,
|
||||
})
|
||||
move_line_cr.with_context(check_move_validity=False).write({
|
||||
'name': dt.date.today().strftime('RTEXJ/%Y/0001'),
|
||||
'name': fields.Date.today().strftime('RTEXJ/%Y/0001'),
|
||||
'debit': 210.0,
|
||||
})
|
||||
move_line_partner_name.with_context(check_move_validity=False).write({
|
||||
|
@ -244,7 +262,7 @@ class TestInvoice(SingleTransactionCase):
|
|||
)
|
||||
# Line 4. I check that the partner name has been recognised.
|
||||
self.assertEqual(
|
||||
move_line_partner_name.partner_id.name, 'Camptocamp',
|
||||
move_line_partner_name.partner_id.name, 'Azure Interior',
|
||||
msg="Check completion by partner name"
|
||||
)
|
||||
# Line 5. I check that the partner special label has been recognised.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<field name="journal_id" position="after">
|
||||
<field name="used_for_completion" invisible="1"/>
|
||||
</field>
|
||||
<button name="post" position="after">
|
||||
<button name="action_duplicate" position="after">
|
||||
<button name="button_auto_completion"
|
||||
string="Auto Completion"
|
||||
type="object"
|
||||
|
@ -70,7 +70,7 @@
|
|||
<field name="inherit_id" ref="account.view_account_move_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<separator position="after">
|
||||
<filter
|
||||
<filter name="to_complete"
|
||||
string="To Complete"
|
||||
domain="[
|
||||
('state','!=','posted'),
|
||||
|
|
Loading…
Reference in New Issue