diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index f3a2c492f54..b6297d153e9 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -446,7 +446,6 @@ class AccountBankStatementLine(models.Model):
""" Prepare the dict of values to balance the move.
:param recordset move: the account.move to link the move line
- :param dict move: a dict of vals of a account.move which will be created later
:param float amount: the amount of transaction that wasn't already reconciled
"""
company_currency = self.journal_id.company_id.currency_id
@@ -454,23 +453,19 @@ class AccountBankStatementLine(models.Model):
st_line_currency = self.currency_id or statement_currency
amount_currency = False
st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
- if isinstance(move, dict):
- amount_sum = sum(x[2].get('amount_currency', 0) for x in move['line_ids'])
- else:
- amount_sum = sum(x.amount_currency for x in move.line_ids)
# We have several use case here to compare the currency and amount currency of counterpart line to balance the move:
if st_line_currency != company_currency and st_line_currency == statement_currency:
# company in currency A, statement in currency B and transaction in currency B
# counterpart line must have currency B and correct amount is inverse of already existing lines
- amount_currency = -amount_sum
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])
elif st_line_currency != company_currency and statement_currency == company_currency:
# company in currency A, statement in currency A and transaction in currency B
# counterpart line must have currency B and correct amount is inverse of already existing lines
- amount_currency = -amount_sum
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])
elif st_line_currency != company_currency and st_line_currency != statement_currency:
# company in currency A, statement in currency B and transaction in currency C
# counterpart line must have currency B and use rate between B and C to compute correct amount
- amount_currency = -amount_sum/st_line_currency_rate
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])/st_line_currency_rate
elif st_line_currency == company_currency and statement_currency != company_currency:
# company in currency A, statement in currency B and transaction in currency A
# counterpart line must have currency B and amount is computed using the rate between A and B
@@ -479,8 +474,9 @@ class AccountBankStatementLine(models.Model):
# last case is company in currency A, statement in currency A and transaction in currency A
# and in this case counterpart line does not need any second currency nor amount_currency
- aml_dict = {
+ return {
'name': self.name,
+ 'move_id': move.id,
'partner_id': self.partner_id and self.partner_id.id or False,
'account_id': amount >= 0 \
and self.statement_id.journal_id.default_credit_account_id.id \
@@ -491,71 +487,22 @@ class AccountBankStatementLine(models.Model):
'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False),
'amount_currency': amount_currency,
}
- if isinstance(move, self.env['account.move'].__class__):
- aml_dict['move_id'] = move.id
- return aml_dict
- @api.multi
def fast_counterpart_creation(self):
"""This function is called when confirming a bank statement and will allow to automatically process lines without
going in the bank reconciliation widget. By setting an account_id on bank statement lines, it will create a journal
entry using that account to counterpart the bank account
"""
- payment_list = []
- move_list = []
- account_type_receivable = self.env.ref('account.data_account_type_receivable')
- already_done_stmt_line_ids = [a['statement_line_id'][0] for a in self.env['account.move.line'].read_group([('statement_line_id', 'in', self.ids)], ['statement_line_id'], ['statement_line_id'])]
- managed_st_line = []
for st_line in self:
# Technical functionality to automatically reconcile by creating a new move line
- if st_line.account_id and not st_line.id in already_done_stmt_line_ids:
- managed_st_line.append(st_line.id)
- # Create payment vals
- total = st_line.amount
- payment_methods = (total > 0) and st_line.journal_id.inbound_payment_method_ids or st_line.journal_id.outbound_payment_method_ids
- currency = st_line.journal_id.currency_id or st_line.company_id.currency_id
- partner_type = 'customer' if st_line.account_id.user_type_id == account_type_receivable else 'supplier'
- payment_list.append({
- 'payment_method_id': payment_methods and payment_methods[0].id or False,
- 'payment_type': total > 0 and 'inbound' or 'outbound',
- 'partner_id': st_line.partner_id.id,
- 'partner_type': partner_type,
- 'journal_id': st_line.statement_id.journal_id.id,
- 'payment_date': st_line.date,
- 'state': 'reconciled',
- 'currency_id': currency.id,
- 'amount': abs(total),
- 'communication': st_line._get_communication(payment_methods[0] if payment_methods else False),
- 'name': st_line.statement_id.name or _("Bank Statement %s") % st_line.date,
- })
-
- # Create move and move line vals
- move_vals = st_line._prepare_reconciliation_move(st_line.statement_id.name)
- aml_dict = {
+ if st_line.account_id and not st_line.journal_entry_ids.ids:
+ vals = {
'name': st_line.name,
'debit': st_line.amount < 0 and -st_line.amount or 0.0,
'credit': st_line.amount > 0 and st_line.amount or 0.0,
'account_id': st_line.account_id.id,
- 'partner_id': st_line.partner_id.id,
- 'statement_line_id': st_line.id,
}
- st_line._prepare_move_line_for_currency(aml_dict, st_line.date or fields.Date.context_today())
- move_vals['line_ids'] = [(0, 0, aml_dict)]
- balance_line = self._prepare_reconciliation_move_line(move_vals, st_line.amount)
- move_vals['line_ids'].append((0, 0, balance_line))
- move_list.append(move_vals)
-
- # Creates
- payment_ids = self.env['account.payment'].create(payment_list)
- for payment_id, move_vals in zip(payment_ids, move_list):
- for line in move_vals['line_ids']:
- line[2]['payment_id'] = payment_id.id
- move_ids = self.env['account.move'].create(move_list)
- move_ids.post()
-
- for move, st_line, payment in zip(move_ids, self.browse(managed_st_line), payment_ids):
- st_line.write({'move_name': move.name})
- payment.write({'payment_reference': move.name})
+ st_line.process_reconciliation(new_aml_dicts=[vals])
def _get_communication(self, payment_method_id):
return self.name or ''
@@ -681,6 +628,8 @@ class AccountBankStatementLine(models.Model):
# Create move line(s). Either matching an existing journal entry (eg. invoice), in which
# case we reconcile the existing and the new move lines together, or being a write-off.
if counterpart_aml_dicts or new_aml_dicts:
+ st_line_currency = self.currency_id or statement_currency
+ st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
# Create the move
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
@@ -703,12 +652,32 @@ class AccountBankStatementLine(models.Model):
# Complete dicts to create both counterpart move lines and write-offs
to_create = (counterpart_aml_dicts + new_aml_dicts)
+ company = self.company_id
date = self.date or fields.Date.today()
for aml_dict in to_create:
aml_dict['move_id'] = move.id
aml_dict['partner_id'] = self.partner_id.id
aml_dict['statement_line_id'] = self.id
- self._prepare_move_line_for_currency(aml_dict, date)
+ if st_line_currency.id != company_currency.id:
+ aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
+ aml_dict['currency_id'] = st_line_currency.id
+ if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
+ # Statement is in company currency but the transaction is in foreign currency
+ aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
+ aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
+ elif self.currency_id and st_line_currency_rate:
+ # Statement is in foreign currency and the transaction is in another one
+ aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
+ aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
+ else:
+ # Statement is in foreign currency and no extra currency is given for the transaction
+ aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
+ aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
+ elif statement_currency.id != company_currency.id:
+ # Statement is in foreign currency but the transaction is in company currency
+ prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
+ aml_dict['amount_currency'] = prorata_factor * self.amount
+ aml_dict['currency_id'] = statement_currency.id
# Create write-offs
for aml_dict in new_aml_dicts:
@@ -758,36 +727,6 @@ class AccountBankStatementLine(models.Model):
counterpart_moves.assert_balanced()
return counterpart_moves
- @api.multi
- def _prepare_move_line_for_currency(self, aml_dict, date):
- self.ensure_one()
- company_currency = self.journal_id.company_id.currency_id
- statement_currency = self.journal_id.currency_id or company_currency
- st_line_currency = self.currency_id or statement_currency
- st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
- company = self.company_id
-
- if st_line_currency.id != company_currency.id:
- aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
- aml_dict['currency_id'] = st_line_currency.id
- if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
- # Statement is in company currency but the transaction is in foreign currency
- aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
- aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
- elif self.currency_id and st_line_currency_rate:
- # Statement is in foreign currency and the transaction is in another one
- aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
- aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
- else:
- # Statement is in foreign currency and no extra currency is given for the transaction
- aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
- aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
- elif statement_currency.id != company_currency.id:
- # Statement is in foreign currency but the transaction is in company currency
- prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
- aml_dict['amount_currency'] = prorata_factor * self.amount
- aml_dict['currency_id'] = statement_currency.id
-
def _check_invoice_state(self, invoice):
if invoice.state == 'in_payment' and all([payment.state == 'reconciled' for payment in invoice.mapped('payment_move_line_ids.payment_id')]):
invoice.write({'state': 'paid'})
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index e12d36f1e9d..da30e616e85 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -55,14 +55,12 @@ class AccountMove(models.Model):
if line.account_id.user_type_id.type in ('receivable', 'payable'):
amount = abs(line.debit - line.credit)
total_amount += amount
+ for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
+ total_reconciled += partial_line.amount
precision_currency = move.currency_id or move.company_id.currency_id
if float_is_zero(total_amount, precision_rounding=precision_currency.rounding):
move.matched_percentage = 1.0
else:
- for line in move.line_ids:
- if line.account_id.user_type_id.type in ('receivable', 'payable'):
- for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
- total_reconciled += partial_line.amount
move.matched_percentage = total_reconciled / total_amount
@api.multi
@@ -550,7 +548,7 @@ class AccountMoveLine(models.Model):
else:
date = partial_line.credit_move_id.date if partial_line.debit_move_id == line else partial_line.debit_move_id.date
rate = line.currency_id.with_context(date=date).rate
- amount_residual_currency += sign_partial_line * line.currency_id.round(partial_line.amount * rate)
+ amount_residual_currency += sign_partial_line * partial_line.amount * rate
#computing the `reconciled` field.
reconciled = False
@@ -760,21 +758,22 @@ class AccountMoveLine(models.Model):
In case of full reconciliation, all moves belonging to the reconciliation will belong to the same account_full_reconcile object.
"""
# Get first all aml involved
- todo = self.env['account.partial.reconcile'].search_read(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)], ['debit_move_id', 'credit_move_id'])
- amls = set(self.ids)
+ part_recs = self.env['account.partial.reconcile'].search(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)])
+ amls = self
+ todo = set(part_recs)
seen = set()
while todo:
- aml_ids = [rec['debit_move_id'][0] for rec in todo if rec['debit_move_id']] + [rec['credit_move_id'][0] for rec in todo if rec['credit_move_id']]
- amls |= set(aml_ids)
- seen |= set([rec['id'] for rec in todo])
- todo = self.env['account.partial.reconcile'].search_read(['&', '|', ('credit_move_id', 'in', aml_ids), ('debit_move_id', 'in', aml_ids), '!', ('id', 'in', list(seen))], ['debit_move_id', 'credit_move_id'])
-
- partial_rec_ids = list(seen)
+ partial_rec = todo.pop()
+ seen.add(partial_rec)
+ for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]:
+ if aml not in amls:
+ amls += aml
+ for x in aml.matched_debit_ids | aml.matched_credit_ids:
+ if x not in seen:
+ todo.add(x)
+ partial_rec_ids = [x.id for x in seen]
if not amls:
return
- else:
- amls = self.browse(list(amls))
-
# If we have multiple currency, we can only base ourselve on debit-credit to see if it is fully reconciled
currency = set([a.currency_id for a in amls if a.currency_id.id != False])
multiple_currency = False
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index cc9b43efb81..60d708f99d1 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -115,7 +115,7 @@ class account_abstract_payment(models.AbstractModel):
'payment_type': total_amount > 0 and 'inbound' or 'outbound',
'partner_id': False if multi else invoices[0].commercial_partner_id.id,
'partner_type': False if multi else MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type],
- 'communication': ' '.join([ref for ref in invoices.mapped('reference') if ref]),
+ 'communication': ' '.join([ref for ref in invoices.mapped('reference') if ref])[:2000],
'invoice_ids': [(6, 0, invoices.ids)],
'multi': multi,
})
diff --git a/addons/account/models/reconciliation_widget.py b/addons/account/models/reconciliation_widget.py
index 3a20e6ae58f..ee8aa10dff3 100644
--- a/addons/account/models/reconciliation_widget.py
+++ b/addons/account/models/reconciliation_widget.py
@@ -83,28 +83,43 @@ class AccountReconciliation(models.AbstractModel):
@api.model
def _get_bank_statement_line_partners(self, st_lines):
+ params = []
+
+ # Add the res.partner.ban's IR rules. In case partners are not shared between companies,
+ # identical bank accounts may exist in a company we don't have access to.
+ ir_rules_query = self.env['res.partner.bank']._where_calc([])
+ self.env['res.partner.bank']._apply_ir_rules(ir_rules_query, 'read')
+ from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
+ if where_clause:
+ where_bank = ('AND %s' % where_clause).replace('res_partner_bank', 'bank')
+ params += where_clause_params
+ else:
+ where_bank = ''
+
+ # Add the res.partner's IR rules. In case partners are not shared between companies,
+ # identical partners may exist in a company we don't have access to.
+ ir_rules_query = self.env['res.partner']._where_calc([])
+ self.env['res.partner']._apply_ir_rules(ir_rules_query, 'read')
+ from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
+ if where_clause:
+ where_partner = ('AND %s' % where_clause).replace('res_partner', 'p3')
+ params += where_clause_params
+ else:
+ where_partner = ''
+
query = '''
SELECT
st_line.id AS id,
COALESCE(p1.id,p2.id,p3.id) AS partner_id
FROM account_bank_statement_line st_line
- LEFT JOIN res_partner_bank bank ON bank.id = st_line.bank_account_id OR bank.acc_number = st_line.account_number
- LEFT JOIN res_partner p1 ON st_line.partner_id=p1.id
- LEFT JOIN res_partner p2 ON bank.partner_id=p2.id
- LEFT JOIN res_partner p3 ON p3.name ILIKE st_line.partner_name
- WHERE st_line.id IN %s
'''
- params = [tuple(st_lines.ids)]
+ query += 'LEFT JOIN res_partner_bank bank ON bank.id = st_line.bank_account_id OR bank.acc_number = st_line.account_number %s\n' % (where_bank)
+ query += 'LEFT JOIN res_partner p1 ON st_line.partner_id=p1.id \n'
+ query += 'LEFT JOIN res_partner p2 ON bank.partner_id=p2.id \n'
+ query += 'LEFT JOIN res_partner p3 ON p3.name ILIKE st_line.partner_name %s\n' % (where_partner)
+ query += 'WHERE st_line.id IN %s'
- # Add the res.partner's IR rules to the WHERE clause. In case partners are not shared
- # between companies, identical partners may exist in a company we don't have access to.
- ir_rules_query = self.env['res.partner']._where_calc([])
- self.env['res.partner']._apply_ir_rules(ir_rules_query, 'read')
- from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
- where_p3 = (" AND %s" % where_clause).replace('res_partner', 'p3') if where_clause else ''
- if where_p3:
- query += where_p3
- params += where_clause_params
+ params += [tuple(st_lines.ids)]
self._cr.execute(query, params)
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 7dbbd1aa023..dd4b88eedac 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -106,7 +106,8 @@
-
+
+
-
+
diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index a92b0d44eb1..d9f40d24816 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -893,14 +893,23 @@ class Escpos:
self._raw(PAPER_FULL_CUT)
- def cashdraw(self, pin):
- """ Send pulse to kick the cash drawer """
- if pin == 2:
- self._raw(CD_KICK_2)
- elif pin == 5:
- self._raw(CD_KICK_5)
- else:
- raise CashDrawerError()
+ def cashdraw(self, pin, tries=5):
+ """ Send pulse to kick the cash drawer
+
+ With some printers the drawer will not open after one pulse, for this reason we will check
+ the drawer status up to 'tries' times and send a new pulse if the drawer is still closed. If the
+ drawer status is open, we will stop sending pulses.
+ """
+ for i in range(tries):
+ if pin == 2:
+ self._raw(CD_KICK_2)
+ elif pin == 5:
+ self._raw(CD_KICK_5)
+ else:
+ raise CashDrawerError()
+
+ if i != tries - 1 and not self.get_printer_status()['printer']['drawer_pin_high']:
+ break
def hw(self, hw):
diff --git a/addons/l10n_be_hr_payroll_fleet/__manifest__.py b/addons/l10n_be_hr_payroll_fleet/__manifest__.py
index d31865d69b5..3f840a44b74 100644
--- a/addons/l10n_be_hr_payroll_fleet/__manifest__.py
+++ b/addons/l10n_be_hr_payroll_fleet/__manifest__.py
@@ -11,6 +11,7 @@
'views/fleet_views.xml',
'views/res_config_settings_views.xml',
'views/hr_contract_views.xml',
+ 'security/security.xml',
],
'auto_install': True,
}
diff --git a/addons/l10n_be_hr_payroll_fleet/security/security.xml b/addons/l10n_be_hr_payroll_fleet/security/security.xml
new file mode 100644
index 00000000000..3fc21715a4c
--- /dev/null
+++ b/addons/l10n_be_hr_payroll_fleet/security/security.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/addons/l10n_it/data/account.tax.group.csv b/addons/l10n_it/data/account.tax.group.csv
index ec8fffb16bc..c8035a8f072 100644
--- a/addons/l10n_it/data/account.tax.group.csv
+++ b/addons/l10n_it/data/account.tax.group.csv
@@ -1,6 +1,7 @@
id,name
tax_group_iva_2,IVA 2%
tax_group_iva_4,IVA 4%
+tax_group_iva_5,IVA 5%
tax_group_iva_10,IVA 10%
tax_group_iva_12,IVA 12%
tax_group_iva_21,IVA 21%
diff --git a/addons/l10n_it/data/account.tax.template.csv b/addons/l10n_it/data/account.tax.template.csv
index fefd1aa22f8..032b5aff32f 100644
--- a/addons/l10n_it/data/account.tax.template.csv
+++ b/addons/l10n_it/data/account.tax.template.csv
@@ -5,6 +5,8 @@ id,description,chart_template_id/id,name,sequence,amount,amount_type,type_tax_us
21a,21a,l10n_it_chart_template_generic,Iva al 21% (credito),4,21,percent,purchase,False,1601,1601,tax_tag_10,,tax_group_iva_21
20v,20v,l10n_it_chart_template_generic,Iva al 20% (debito),3,20,percent,sale,False,2601,2601,tax_tag_35,,tax_group_iva_20
20a,20a,l10n_it_chart_template_generic,Iva al 20% (credito),4,20,percent,purchase,False,1601,1601,tax_tag_05,,tax_group_iva_20
+5v,5v,l10n_it_chart_template_generic,Iva al 5% (debito),5,5,percent,sale,False,2601,2601,tax_tag_5v,,tax_group_iva_5
+5a,5a,l10n_it_chart_template_generic,Iva al 5% (credito),6,5,percent,purchase,False,1601,1601,tax_tag_5a,,tax_group_iva_5
10v,10v,l10n_it_chart_template_generic,Iva al 10% (debito),5,10,percent,sale,False,2601,2601,tax_tag_29,,tax_group_iva_10
10a,10a,l10n_it_chart_template_generic,Iva al 10% (credito),6,10,percent,purchase,False,1601,1601,tax_tag_01,,tax_group_iva_10
10AOb,10AOb,l10n_it_chart_template_generic,Iva al 10% indetraibile (D),200,0,percent,none,False,1601,1601,,,tax_group_iva_10
diff --git a/addons/l10n_it/data/l10n_it_chart_data.xml b/addons/l10n_it/data/l10n_it_chart_data.xml
index d05183c2215..bcc8636d527 100644
--- a/addons/l10n_it/data/l10n_it_chart_data.xml
+++ b/addons/l10n_it/data/l10n_it_chart_data.xml
@@ -190,4 +190,12 @@
00art15vtaxes
+
+ 5a
+ taxes
+
+
+ 5v
+ taxes
+
diff --git a/addons/l10n_mx/data/account_tag_data.xml b/addons/l10n_mx/data/account_tag_data.xml
index 244c9b65ff3..1e8b23dacda 100644
--- a/addons/l10n_mx/data/account_tag_data.xml
+++ b/addons/l10n_mx/data/account_tag_data.xml
@@ -479,25 +479,25 @@
119.01 IVA pendiente de pago4accounts
- A
+ D119.02 IVA de importación pendiente de pago4accounts
- A
+ D119.03 IEPS pendiente de pago4accounts
- A
+ D119.04 IEPS pendiente de pago en importación4accounts
- A
+ D120.01 Anticipo a proveedores nacional
@@ -917,7 +917,7 @@
180.01 Crédito mercantil4accounts
- A
+ D181.01 Gastos de instalación
@@ -2423,7 +2423,7 @@
503 Devoluciones, descuentos o bonificaciones sobre compras1accounts
- D
+ A503.01 Devoluciones, descuentos o bonificaciones sobre compras
@@ -2585,13 +2585,13 @@
505.01 Costo por venta de activo fijo4accounts
- A
+ D505.02 Costo por baja de activo fijo4accounts
- A
+ D601.01 Sueldos y salarios
@@ -4781,7 +4781,7 @@
607.01 Participación de los trabajadores en las utilidades4accounts
- A
+ D608.01 Participación en resultados de subsidiarias
@@ -4799,7 +4799,7 @@
610.01 Participación de los trabajadores en las utilidades diferida4accounts
- A
+ D611.01 Impuesto Sobre la renta
@@ -4817,7 +4817,7 @@
612.01 Gastos no deducibles para CUFIN4accounts
- A
+ D613.01 Depreciación de edificios
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py
index f5bfd7c11cf..4acce13d381 100644
--- a/addons/mail/models/mail_activity.py
+++ b/addons/mail/models/mail_activity.py
@@ -147,7 +147,7 @@ class MailActivity(models.Model):
('today', 'Today'),
('planned', 'Planned')], 'State',
compute='_compute_state')
- recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type", readonly=True)
+ recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type")
previous_activity_type_id = fields.Many2one('mail.activity.type', string='Previous Activity Type', readonly=True)
has_recommended_activities = fields.Boolean(
'Next activities available',
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 46ec87060cf..75b867d3ad5 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -372,6 +372,7 @@ class MailComposer(models.TransientModel):
- normal mode: return rendered values
/!\ for x2many field, this onchange return command instead of ids
"""
+ attachment_ids = []
if template_id and composition_mode == 'mass_mail':
template = self.env['mail.template'].browse(template_id)
fields = ['subject', 'body_html', 'email_from', 'reply_to', 'mail_server_id']
@@ -397,7 +398,7 @@ class MailComposer(models.TransientModel):
'res_id': 0,
'type': 'binary', # override default_type from context, possibly meant for another model!
}
- values.setdefault('attachment_ids', list()).append(Attachment.create(data_attach).id)
+ attachment_ids.append(Attachment.create(data_attach).id)
else:
default_values = self.with_context(default_composition_mode=composition_mode, default_model=model, default_res_id=res_id).default_get(['composition_mode', 'model', 'res_id', 'parent_id', 'partner_ids', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'])
values = dict((key, default_values[key]) for key in ['subject', 'body', 'partner_ids', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values)
@@ -410,6 +411,8 @@ class MailComposer(models.TransientModel):
# this force the complete replacement of x2many field with
# command and is compatible with onchange api.v7
values = self._convert_to_write(values)
+ if attachment_ids:
+ values.update(attachment_ids=[(6, 0, attachment_ids)])
return {'value': values}
diff --git a/addons/mrp_byproduct/models/mrp_production.py b/addons/mrp_byproduct/models/mrp_production.py
index fd7613f323d..80153d3127c 100644
--- a/addons/mrp_byproduct/models/mrp_production.py
+++ b/addons/mrp_byproduct/models/mrp_production.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, models
+from odoo import api, models, _
from odoo.tools import float_round
+from odoo.exceptions import UserError
class MrpProduction(models.Model):
@@ -42,6 +43,9 @@ class MrpProduction(models.Model):
""" Generates moves and work orders
@return: Newly generated picking Id.
"""
+ for production in self:
+ if production.product_id in production.bom_id.sub_products.mapped('product_id'):
+ raise UserError(_("You cannot have %s as the finished product and in the Byproducts") % production.product_id.name)
res = super(MrpProduction, self)._generate_finished_moves()
for production in self.filtered(lambda production: production.bom_id):
for sub_product in production.bom_id.sub_products:
diff --git a/addons/mrp_byproduct/views/mrp_bom_views.xml b/addons/mrp_byproduct/views/mrp_bom_views.xml
index 9132209d75c..72599f994c2 100644
--- a/addons/mrp_byproduct/views/mrp_bom_views.xml
+++ b/addons/mrp_byproduct/views/mrp_bom_views.xml
@@ -10,12 +10,12 @@
-
+
diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js
index b1d99e2e199..0439c11cb7d 100644
--- a/addons/point_of_sale/static/src/js/gui.js
+++ b/addons/point_of_sale/static/src/js/gui.js
@@ -404,7 +404,7 @@ var Gui = core.Class.extend({
}
} else if (input === '-') {
if (options.firstinput) {
- newbuf = '-';
+ newbuf = '-0';
} else if ( newbuf[0] === '-' ) {
newbuf = newbuf.substring(1,newbuf.length);
} else {
@@ -419,6 +419,9 @@ var Gui = core.Class.extend({
newbuf += input;
}
}
+ if (newbuf === "-") {
+ newbuf = "";
+ }
// End of input buffer at 12 characters.
if (newbuf.length > buffer.length && newbuf.length > 12) {
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 26d315631a8..17c47b154ad 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -990,7 +990,7 @@
-
+
diff --git a/addons/point_of_sale/views/point_of_sale_dashboard.xml b/addons/point_of_sale/views/point_of_sale_dashboard.xml
index c45bc2e19cf..b630f2e4e12 100644
--- a/addons/point_of_sale/views/point_of_sale_dashboard.xml
+++ b/addons/point_of_sale/views/point_of_sale_dashboard.xml
@@ -47,7 +47,6 @@
-
diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py
index 8d1378218a1..fbc1a1a96cc 100644
--- a/addons/portal/controllers/mail.py
+++ b/addons/portal/controllers/mail.py
@@ -137,7 +137,7 @@ class PortalChatter(http.Controller):
raise Forbidden()
# Non-employee see only messages with not internal subtype (aka, no internal logs)
if not request.env['res.users'].has_group('base.group_user'):
- domain = expression.AND([['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False), ('website_published', '=', True)], domain])
+ domain = expression.AND([Message._non_employee_message_domain(), domain])
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(),
diff --git a/addons/portal/models/mail_message.py b/addons/portal/models/mail_message.py
index 4a5d1f804d9..afe97f1b8ca 100644
--- a/addons/portal/models/mail_message.py
+++ b/addons/portal/models/mail_message.py
@@ -21,3 +21,7 @@ class MailMessage(models.Model):
message_tree = dict((m.id, m) for m in self.sudo())
self._message_read_dict_postprocess(message_values, message_tree)
return message_values
+
+ @api.model
+ def _non_employee_message_domain(self):
+ return ['&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)]
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 4d5150ab47a..f2e01324a16 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -1266,7 +1266,7 @@ class SaleOrderLine(models.Model):
result.setdefault(so_line_id, 0.0)
uom = product_uom_map.get(item['product_uom_id'][0])
if so_line.product_uom.category_id == uom.category_id:
- qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom)
+ qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom, rounding_method='HALF-UP')
else:
qty = item['unit_amount']
result[so_line_id] += qty
diff --git a/addons/sale/report/report_all_channels_sales.py b/addons/sale/report/report_all_channels_sales.py
index 1638c087d05..473ab1f1362 100644
--- a/addons/sale/report/report_all_channels_sales.py
+++ b/addons/sale/report/report_all_channels_sales.py
@@ -52,7 +52,7 @@ class PosSaleReport(models.Model):
LEFT JOIN product_pricelist pp ON (so.pricelist_id = pp.id)
LEFT JOIN uom_uom u on (u.id=sol.product_uom)
LEFT JOIN uom_uom u2 on (u2.id=pt.uom_id)
- WHERE so.state != 'cancel'
+ WHERE so.state in ('sale','done')
"""
return so_str
diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py
index c76798bce10..1e1df8677a7 100644
--- a/addons/sale_timesheet/models/sale_order.py
+++ b/addons/sale_timesheet/models/sale_order.py
@@ -236,6 +236,10 @@ class SaleOrderLine(models.Model):
'partner_id': self.order_id.partner_id.id,
'email_from': self.order_id.partner_id.email,
})
+ # duplicating a project doesn't set the SO on sub-tasks
+ project.tasks.filtered(lambda task: task.parent_id != False).write({
+ 'sale_line_id': self.id,
+ })
else:
project = self.env['project.project'].create(values)
# link project as generated by current so line
diff --git a/addons/sales_team/tests/__init__.py b/addons/sales_team/tests/__init__.py
new file mode 100644
index 00000000000..7bb8a11fc5e
--- /dev/null
+++ b/addons/sales_team/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_default_team
diff --git a/addons/sales_team/tests/test_default_team.py b/addons/sales_team/tests/test_default_team.py
new file mode 100644
index 00000000000..9905272b3d9
--- /dev/null
+++ b/addons/sales_team/tests/test_default_team.py
@@ -0,0 +1,48 @@
+from odoo.tests import common
+
+
+class TestDefaultTeam(common.SavepointCase):
+ """Tests to check if correct default team is found."""
+
+ @classmethod
+ def setUpClass(cls):
+ """Set up data for default team tests."""
+ super(TestDefaultTeam, cls).setUpClass()
+ cls.CrmTeam = cls.env['crm.team']
+ ResUsers = cls.env['res.users'].with_context(
+ {'no_reset_password': True})
+ group_sale_manager = cls.env.ref('sales_team.group_sale_manager')
+ cls.user = ResUsers.create({
+ 'name': 'Team User',
+ 'login': 'sales_team_user',
+ 'email': 'sales.team.user@example.com',
+ 'groups_id': [(6, 0, [group_sale_manager.id])]
+ })
+ cls.team_1 = cls.env['crm.team'].create({
+ 'name': 'Test Team',
+ 'member_ids': [(4, cls.user.id)],
+ 'company_id': False
+ })
+ # Europe Team (fall back team)
+ cls.team_2 = cls.env.ref('sales_team.team_sales_department')
+
+ def test_01_user_team(self):
+ """Get default team, when user belongs to one."""
+ team = self.CrmTeam.sudo(self.user)._get_default_team_id()
+ self.assertEqual(team, self.team_1)
+
+ def test_02_fallback_team(self):
+ """Get default team when user does not belong to any team.
+
+ Case 1: fall back default team (from XML ref) is active.
+ Case 2: fall back default team is not active.
+ """
+ # Clear users from team.
+ self.team_1.member_ids = [(5,)]
+ # Case 1.
+ team = self.CrmTeam.sudo(self.user)._get_default_team_id()
+ self.assertEqual(team, self.team_2)
+ # Case 2.
+ self.team_2.active = False
+ team = self.CrmTeam.sudo(self.user)._get_default_team_id()
+ self.assertEqual(team, self.CrmTeam)
diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py
index 707d4618bda..a11616c850a 100644
--- a/addons/stock_account/models/__init__.py
+++ b/addons/stock_account/models/__init__.py
@@ -4,6 +4,7 @@
from . import account_chart_template
from . import account_invoice
from . import account_move
+from . import account_reconciliation_widget
from . import product
from . import stock
from . import res_config_settings
diff --git a/addons/stock_account/models/account_reconciliation_widget.py b/addons/stock_account/models/account_reconciliation_widget.py
new file mode 100644
index 00000000000..44110053899
--- /dev/null
+++ b/addons/stock_account/models/account_reconciliation_widget.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, models
+from odoo.osv import expression
+
+
+class AccountReconciliation(models.AbstractModel):
+ _inherit = "account.reconciliation.widget"
+
+ @api.model
+ def _domain_move_lines_for_reconciliation(self, st_line, aml_accounts, partner_id, excluded_ids=None, search_str=False):
+ def to_int(val):
+ try:
+ return int(val)
+ except (ValueError, TypeError):
+ return None
+
+ domain = super()._domain_move_lines_for_reconciliation(
+ st_line, aml_accounts, partner_id, excluded_ids=excluded_ids, search_str=search_str
+ )
+ acc_props = (
+ "property_stock_account_input",
+ "property_stock_account_output",
+ "property_stock_account_input_categ_id",
+ "property_stock_account_output_categ_id",
+ )
+ acc_ids = [
+ (acc["value_reference"] or "").split(",")[-1]
+ for acc in self.env["ir.property"]
+ .sudo()
+ .search([("name", "in", acc_props), ("value_reference", "!=", False)])
+ .read(["value_reference"])
+ if to_int((acc["value_reference"] or "").split(",")[-1])
+ ]
+ if acc_ids:
+ domain = expression.AND([domain, [("account_id.id", "not in", acc_ids)]])
+ return domain
diff --git a/addons/survey/views/assets.xml b/addons/survey/views/assets.xml
index fa42fde1860..c305b7575b5 100644
--- a/addons/survey/views/assets.xml
+++ b/addons/survey/views/assets.xml
@@ -40,6 +40,12 @@
+
+
+
+
+
+
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js
index 2516b0a7230..2a8e8151560 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -491,7 +491,11 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
new RainbowMan(data).appendTo(this.$el);
} else {
// For instance keep title blank, as we don't have title in data
- this.notification_manager.notify('', data.message, true);
+ this.call('notification', 'notify', {
+ title: "",
+ message: data.message,
+ sticky: false
+ });
}
} else {
throw new Error('Unknown effect type: ' + type);
diff --git a/addons/web/static/src/js/tools/test_menus.js b/addons/web/static/src/js/tools/test_menus.js
index 4e31b2d4467..040901f549e 100644
--- a/addons/web/static/src/js/tools/test_menus.js
+++ b/addons/web/static/src/js/tools/test_menus.js
@@ -44,8 +44,12 @@
}
}
- // Main function that starts orchestration of tests
function clickEverywhere(){
+ setTimeout(_clickEverywhere, 1000);
+ }
+
+ // Main function that starts orchestration of tests
+ function _clickEverywhere(){
console.log("Starting ClickEverywhere test");
var startTime = performance.now();
createWebClientHooks();
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 57c935905e5..df9601b3a04 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -13,6 +13,7 @@ var DateWidget = Widget.extend({
type_of_date: "date",
events: {
'change.datetimepicker': 'changeDatetime',
+ 'error.datetimepicker': 'errorDatetime',
'change .o_datepicker_input': 'changeDatetime',
'input input': '_onInput',
'keydown': '_onKeydown',
@@ -103,6 +104,12 @@ var DateWidget = Widget.extend({
}
}
},
+ /**
+ * Library clears the wrong date format so just ignore error
+ */
+ errorDatetime: function (e) {
+ return false;
+ },
/**
* Focuses the datepicker input. This function must be called in order to
* prevent 'input' events triggered by the lib to bubble up, and to cause
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index 4438cfa004c..8762caa548f 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -2661,6 +2661,25 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('date field should remove the date if the date is not valid', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '',
+ res_id: 4,
+ });
+ // switch to edit mode
+ form.$buttons.find('.o_form_button_edit').click();
+ // set an invalid date
+ var $input = form.$('.o_field_widget[name=date] input');
+ $input.val('mmmh').trigger('change');
+ assert.strictEqual($input.text(), "", "The date field should be empty");
+ form.destroy();
+ });
+
QUnit.test('date field value should not set on first click', function (assert) {
assert.expect(2);
diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js
index e622d3929d7..59c40fa9c01 100644
--- a/addons/web_tour/static/src/js/tour_manager.js
+++ b/addons/web_tour/static/src/js/tour_manager.js
@@ -193,6 +193,9 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
* @returns {boolean} true if a tip was found and activated/updated
*/
_check_for_tooltip: function (tip, tour_name) {
+ if (tip === undefined) {
+ return true;
+ }
if ($('body').hasClass('o_ui_blocked')) {
this._deactivate_tip(tip);
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 0261b1fc616..b9385747287 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -138,12 +138,15 @@ class Website(models.Model):
@api.multi
def write(self, values):
+ public_user_to_change_websites = self.env['website']
self._get_languages.clear_cache(self)
if 'company_id' in values and 'user_id' not in values:
- company = self.env['res.company'].browse(values['company_id'])
- values['user_id'] = company._get_public_user().id
+ public_user_to_change_websites = self.filtered(lambda w: w.user_id.company_id.id != values['company_id'])
+ if public_user_to_change_websites:
+ company = self.env['res.company'].browse(values['company_id'])
+ super(Website, public_user_to_change_websites).write(dict(values, user_id=company._get_public_user().id))
- result = super(Website, self).write(values)
+ result = super(Website, self - public_user_to_change_websites).write(values)
if 'cdn_activated' in values or 'cdn_url' in values or 'cdn_filters' in values:
# invalidate the caches from static node at compile time
self.env['ir.qweb'].clear_caches()
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 7c79b2382ca..f197c48491a 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -947,6 +947,10 @@ registry.gallerySlider = Animation.extend({
destroy: function () {
this._super.apply(this, arguments);
+ if (!this.$indicator) {
+ return;
+ }
+
this.$prev.prependTo(this.$indicator);
this.$next.appendTo(this.$indicator);
this.$carousel.off('.gallery_slider');
diff --git a/addons/website/static/src/js/tours/rte.js b/addons/website/static/src/js/tours/rte.js
index b62999ef2a7..157de002abd 100644
--- a/addons/website/static/src/js/tours/rte.js
+++ b/addons/website/static/src/js/tours/rte.js
@@ -12,6 +12,7 @@ var ready = $.when(domReady, session.is_bound, ajax.loadXML());
tour.register('rte_translator', {
test: true,
+ url: '/',
wait_for: ready,
}, [{
content: "click on Add a language",
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 4e6f94bd977..16b26d9d99b 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -420,7 +420,7 @@