diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index fb44df3dbed..66019eaa59e 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -263,9 +263,6 @@ class AccountMove(models.Model): journal_id=journal_id, auto=auto) reversed_moves |= reversed_move - #unreconcile all lines reversed - aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity') - aml.remove_move_reconcile() #reconcile together the reconcilable (or the liquidity aml) and their newly created counterpart for account in set([x.account_id for x in aml]): to_rec = aml.filtered(lambda y: y.account_id == account) @@ -283,6 +280,18 @@ class AccountMove(models.Model): def open_reconcile_view(self): return self.line_ids.open_reconcile_view() + # FIXME: Clarify me and change me in master + @api.multi + def action_duplicate(self): + self.ensure_one() + action = self.env.ref('account.action_move_journal_line').read()[0] + action['target'] = 'inline' + action['context'] = dict(self.env.context) + action['context']['view_no_maturity'] = False + action['views'] = [(self.env.ref('account.view_move_form').id, 'form')] + action['res_id'] = self.copy().id + return action + @api.model def _run_reverses_entries(self): ''' This method is called from a cron job. ''' @@ -467,7 +476,7 @@ class AccountMoveLine(models.Model): index=True, store=True, copy=False) # related is required blocked = fields.Boolean(string='No Follow-up', default=False, help="You can check this box to mark this journal item as a litigation with the associated partner") - date_maturity = fields.Date(string='Due date', index=True, required=True, + date_maturity = fields.Date(string='Due date', index=True, required=True, copy=False, help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.") date = fields.Date(related='move_id.date', string='Date', index=True, store=True, copy=False) # related is required analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines") diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index 0173c8bafee..b1bcb28e12e 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -204,7 +204,7 @@ class ResPartner(models.Model): @api.multi def _credit_debit_get(self): - tables, where_clause, where_params = self.env['account.move.line']._query_get() + tables, where_clause, where_params = self.env['account.move.line'].with_context(company_id=self.env.user.company_id.id)._query_get() where_params = [tuple(self.ids)] + where_params if where_clause: where_clause = 'AND ' + where_clause diff --git a/addons/account/static/src/js/account_payment_field.js b/addons/account/static/src/js/account_payment_field.js index 1e5171e1f44..7d074a685c5 100644 --- a/addons/account/static/src/js/account_payment_field.js +++ b/addons/account/static/src/js/account_payment_field.js @@ -159,4 +159,8 @@ var ShowPaymentLineWidget = AbstractField.extend({ field_registry.add('payment', ShowPaymentLineWidget); +return { + ShowPaymentLineWidget: ShowPaymentLineWidget +}; + }); diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 03ebd6038b8..6e453e1eb32 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1502,10 +1502,11 @@ account.move.form account.move -
+
diff --git a/addons/bus/models/res_partner.py b/addons/bus/models/res_partner.py index b0b4c23d74f..a476e0c08dc 100644 --- a/addons/bus/models/res_partner.py +++ b/addons/bus/models/res_partner.py @@ -15,13 +15,14 @@ class ResPartner(models.Model): self.env.cr.execute(""" SELECT U.partner_id as id, - CASE WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline' - WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away' + CASE WHEN age(now() AT TIME ZONE 'UTC', max(B.last_poll)) > interval %s THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', max(B.last_presence)) > interval %s THEN 'away' ELSE 'online' END as status FROM bus_presence B JOIN res_users U ON B.user_id = U.id WHERE U.partner_id IN %s AND U.active = 't' + GROUP BY U.partner_id """, ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids))) res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall())) for partner in self: diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index 850c90d010b..322278cc4d7 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -70,7 +70,7 @@ class DeliveryCarrier(models.Model): def install_more_provider(self): return { 'name': 'New Providers', - 'view_mode': 'kanban', + 'view_mode': 'kanban,form', 'res_model': 'ir.module.module', 'domain': [['name', 'ilike', 'delivery_']], 'type': 'ir.actions.act_window', diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py index b7c6e04f713..dfea17c3e70 100644 --- a/addons/l10n_be_intrastat/wizard/xml_decl.py +++ b/addons/l10n_be_intrastat/wizard/xml_decl.py @@ -103,35 +103,140 @@ class XmlDeclaration(models.TransientModel): 'res_id': self.id, } + def _build_intrastat_line(self, numlgn, item, linekey, amounts, dispatchmode, extendedmode): + self._set_Dim(item, 'EXSEQCODE', text_type(numlgn)) + self._set_Dim(item, 'EXTRF', text_type(linekey.EXTRF)) + self._set_Dim(item, 'EXCNT', text_type(linekey.EXCNT)) + self._set_Dim(item, 'EXTTA', text_type(linekey.EXTTA)) + self._set_Dim(item, 'EXREG', text_type(linekey.EXREG)) + self._set_Dim(item, 'EXTGO', text_type(linekey.EXGO)) + if extendedmode: + self._set_Dim(item, 'EXTPC', text_type(linekey.EXTPC)) + self._set_Dim(item, 'EXDELTRM', text_type(linekey.EXDELTRM)) + self._set_Dim(item, 'EXTXVAL', text_type(round(amounts[0], 0)).replace(".", ",")) + self._set_Dim(item, 'EXWEIGHT', text_type(round(amounts[1], 0)).replace(".", ",")) + self._set_Dim(item, 'EXUNITS', text_type(round(amounts[2], 0)).replace(".", ",")) + + def _get_intrastat_linekey(self, declcode, inv_line, dispatchmode, extendedmode): + IntrastatRegion = self.env['l10n_be_intrastat.region'] + company = self.company_id + + #Check type of transaction + if inv_line.intrastat_transaction_id: + extta = inv_line.intrastat_transaction_id.code + else: + extta = "1" + #Check country + if inv_line.invoice_id.intrastat_country_id: + excnt = inv_line.invoice_id.intrastat_country_id.code + else: + excnt = inv_line.invoice_id.partner_shipping_id.country_id.code or inv_line.invoice_id.partner_id.country_id.code + + #Check region + #If purchase, comes from purchase order, linked to a location, + #which is linked to the warehouse + #if sales, the sales order is linked to the warehouse + #if sales, from a delivery order, linked to a location, + #which is linked to the warehouse + #If none found, get the company one. + exreg = None + if inv_line.invoice_id.type in ('in_invoice', 'in_refund'): + #comes from purchase + po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) + if po_lines: + if self._is_situation_triangular(company, po_line=po_lines): + return + location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location()) + region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location) + if region_id: + exreg = IntrastatRegion.browse(region_id).code + elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'): + #comes from sales + so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) + if so_lines: + if self._is_situation_triangular(company, so_line=so_lines): + return + saleorder = so_lines.order_id + if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id: + exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code + + if not exreg: + if company.region_id: + exreg = company.region_id.code + else: + self._company_warning(_('The Intrastat Region of the selected company is not set, ' + 'please make sure to configure it first.')) + + #Check commodity codes + intrastat_id = inv_line.product_id.get_intrastat_recursively() + if intrastat_id: + exgo = self.env['report.intrastat.code'].browse(intrastat_id).name + else: + raise exceptions.Warning( + _('Product "%s" has no intrastat code, please configure it') % inv_line.product_id.display_name) + + #In extended mode, 2 more fields required + if extendedmode: + #Check means of transport + if inv_line.invoice_id.transport_mode_id: + extpc = inv_line.invoice_id.transport_mode_id.code + elif company.transport_mode_id: + extpc = company.transport_mode_id.code + else: + self._company_warning(_('The default Intrastat transport mode of your company ' + 'is not set, please make sure to configure it first.')) + + #Check incoterm + if inv_line.invoice_id.incoterm_id: + exdeltrm = inv_line.invoice_id.incoterm_id.code + elif company.incoterm_id: + exdeltrm = company.incoterm_id.code + else: + self._company_warning(_('The default Incoterm of your company is not set, ' + 'please make sure to configure it first.')) + else: + extpc = "" + exdeltrm = "" + intrastatkey = namedtuple("intrastatkey", + ['EXTRF', 'EXCNT', 'EXTTA', 'EXREG', + 'EXGO', 'EXTPC', 'EXDELTRM']) + return intrastatkey(EXTRF=declcode, EXCNT=excnt, + EXTTA=extta, EXREG=exreg, EXGO=exgo, + EXTPC=extpc, EXDELTRM=exdeltrm) + + def _get_reception_code(self, extended): + return 'EX19E' if extended else 'EX19S' + + def _get_reception_form(self, extended): + return 'EXF19E' if extended else 'EXF19S' + + def _get_expedition_code(self, extended): + return 'EX29E' if extended else 'EX29S' + + def _get_expedition_form(self, extended): + return 'EXF29E' if extended else 'EXF29S' + @api.multi def _get_lines(self, dispatchmode=False, extendedmode=False): company = self.company_id - IntrastatRegion = self.env['l10n_be_intrastat.region'] if dispatchmode: mode1 = 'out_invoice' mode2 = 'in_refund' - declcode = "29" + declcode = self._get_expedition_code(extendedmode) + declform = self._get_expedition_form(extendedmode) else: mode1 = 'in_invoice' mode2 = 'out_refund' - declcode = "19" + declcode = self._get_reception_code(extendedmode) + declform = self._get_reception_form(extendedmode) decl = ET.Element('Report') - if not extendedmode: - decl.set('code', 'EX%sS' % declcode) - else: - decl.set('code', 'EX%sE' % declcode) + decl.set('code', declcode) decl.set('date', '%s-%s' % (self.year, self.month)) datas = ET.SubElement(decl, 'Data') - if not extendedmode: - datas.set('form', 'EXF%sS' % declcode) - else: - datas.set('form', 'EXF%sE' % declcode) + datas.set('form', declform) datas.set('close', 'true') - intrastatkey = namedtuple("intrastatkey", - ['EXTRF', 'EXCNT', 'EXTTA', 'EXREG', - 'EXGO', 'EXTPC', 'EXDELTRM']) entries = {} query = """ @@ -167,86 +272,10 @@ class XmlDeclaration(models.TransientModel): invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids) for inv_line in invoicelines: + linekey = self._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode) + if linekey is None: + continue - #Check type of transaction - if inv_line.intrastat_transaction_id: - extta = inv_line.intrastat_transaction_id.code - else: - extta = "1" - #Check country - if inv_line.invoice_id.intrastat_country_id: - excnt = inv_line.invoice_id.intrastat_country_id.code - else: - excnt = inv_line.invoice_id.partner_shipping_id.country_id.code or inv_line.invoice_id.partner_id.country_id.code - - #Check region - #If purchase, comes from purchase order, linked to a location, - #which is linked to the warehouse - #if sales, the sales order is linked to the warehouse - #if sales, from a delivery order, linked to a location, - #which is linked to the warehouse - #If none found, get the company one. - exreg = None - if inv_line.invoice_id.type in ('in_invoice', 'in_refund'): - #comes from purchase - po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) - if po_lines: - if self._is_situation_triangular(company, po_line=po_lines): - continue - location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location()) - region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location) - if region_id: - exreg = IntrastatRegion.browse(region_id).code - elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'): - #comes from sales - so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) - if so_lines: - if self._is_situation_triangular(company, so_line=so_lines): - continue - saleorder = so_lines.order_id - if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id: - exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code - - if not exreg: - if company.region_id: - exreg = company.region_id.code - else: - self._company_warning(_('The Intrastat Region of the selected company is not set, ' - 'please make sure to configure it first.')) - - #Check commodity codes - intrastat_id = inv_line.product_id.get_intrastat_recursively() - if intrastat_id: - exgo = self.env['report.intrastat.code'].browse(intrastat_id).name - else: - raise exceptions.Warning( - _('Product "%s" has no intrastat code, please configure it') % inv_line.product_id.display_name) - - #In extended mode, 2 more fields required - if extendedmode: - #Check means of transport - if inv_line.invoice_id.transport_mode_id: - extpc = inv_line.invoice_id.transport_mode_id.code - elif company.transport_mode_id: - extpc = company.transport_mode_id.code - else: - self._company_warning(_('The default Intrastat transport mode of your company ' - 'is not set, please make sure to configure it first.')) - - #Check incoterm - if inv_line.invoice_id.incoterm_id: - exdeltrm = inv_line.invoice_id.incoterm_id.code - elif company.incoterm_id: - exdeltrm = company.incoterm_id.code - else: - self._company_warning(_('The default Incoterm of your company is not set, ' - 'please make sure to configure it first.')) - else: - extpc = "" - exdeltrm = "" - linekey = intrastatkey(EXTRF=declcode, EXCNT=excnt, - EXTTA=extta, EXREG=exreg, EXGO=exgo, - EXTPC=extpc, EXDELTRM=exdeltrm) #We have the key #calculate amounts if inv_line.price_unit and inv_line.quantity: @@ -270,18 +299,7 @@ class XmlDeclaration(models.TransientModel): continue numlgn += 1 item = ET.SubElement(datas, 'Item') - self._set_Dim(item, 'EXSEQCODE', text_type(numlgn)) - self._set_Dim(item, 'EXTRF', text_type(linekey.EXTRF)) - self._set_Dim(item, 'EXCNT', text_type(linekey.EXCNT)) - self._set_Dim(item, 'EXTTA', text_type(linekey.EXTTA)) - self._set_Dim(item, 'EXREG', text_type(linekey.EXREG)) - self._set_Dim(item, 'EXTGO', text_type(linekey.EXGO)) - if extendedmode: - self._set_Dim(item, 'EXTPC', text_type(linekey.EXTPC)) - self._set_Dim(item, 'EXDELTRM', text_type(linekey.EXDELTRM)) - self._set_Dim(item, 'EXTXVAL', text_type(round(amounts[0], 0)).replace(".", ",")) - self._set_Dim(item, 'EXWEIGHT', text_type(round(amounts[1], 0)).replace(".", ",")) - self._set_Dim(item, 'EXUNITS', text_type(round(amounts[2], 0)).replace(".", ",")) + self._build_intrastat_line(numlgn, item, linekey, amounts, dispatchmode, extendedmode) if numlgn == 0: #no datas diff --git a/addons/l10n_be_intrastat_2019/__init__.py b/addons/l10n_be_intrastat_2019/__init__.py new file mode 100644 index 00000000000..dc5e6b693d1 --- /dev/null +++ b/addons/l10n_be_intrastat_2019/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/l10n_be_intrastat_2019/__manifest__.py b/addons/l10n_be_intrastat_2019/__manifest__.py new file mode 100644 index 00000000000..b3de01cd46b --- /dev/null +++ b/addons/l10n_be_intrastat_2019/__manifest__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Belgian Intrastat Declaration - Complement for 2019', + 'category': 'Accounting', + 'description': """ +Adds the possibility to specify the origin country of goods and the partner VAT in the Intrastat XML report. + """, + 'depends': ['l10n_be_intrastat'], + 'data': [ + 'views/account_invoice_line_view.xml', + ], + 'auto_install': True, +} diff --git a/addons/l10n_be_intrastat_2019/i18n/l10n_be_intrastat_2019.pot b/addons/l10n_be_intrastat_2019/i18n/l10n_be_intrastat_2019.pot new file mode 100644 index 00000000000..5953a7be43c --- /dev/null +++ b/addons/l10n_be_intrastat_2019/i18n/l10n_be_intrastat_2019.pot @@ -0,0 +1,32 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_be_intrastat_2019 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-01-21 11:04+0000\n" +"PO-Revision-Date: 2019-01-21 11:04+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_be_intrastat_2019 +#: model:ir.model,name:l10n_be_intrastat_2019.model_l10n_be_intrastat_xml_xml_decl +msgid "Intrastat XML Declaration" +msgstr "" + +#. module: l10n_be_intrastat_2019 +#: model:ir.model,name:l10n_be_intrastat_2019.model_account_invoice_line +msgid "Invoice Line" +msgstr "" + +#. module: l10n_be_intrastat_2019 +#: model:ir.model.fields,field_description:l10n_be_intrastat_2019.field_account_invoice_line_intrastat_product_origin_country_id +msgid "Origin Country of Product" +msgstr "" + diff --git a/addons/l10n_be_intrastat_2019/models/__init__.py b/addons/l10n_be_intrastat_2019/models/__init__.py new file mode 100644 index 00000000000..9115b4d66b6 --- /dev/null +++ b/addons/l10n_be_intrastat_2019/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import account_intrastat_report +from . import account_invoice_line diff --git a/addons/l10n_be_intrastat_2019/models/account_intrastat_report.py b/addons/l10n_be_intrastat_2019/models/account_intrastat_report.py new file mode 100644 index 00000000000..51cf8c4ae56 --- /dev/null +++ b/addons/l10n_be_intrastat_2019/models/account_intrastat_report.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models +from odoo.tools.pycompat import text_type +from collections import namedtuple + + +class XmlDeclaration(models.TransientModel): + """ + Intrastat XML Declaration + """ + _inherit = "l10n_be_intrastat_xml.xml_decl" + + def _build_intrastat_line(self, numlgn, item, linekey, amounts, dispatchmode, extendedmode): + super(XmlDeclaration, self)._build_intrastat_line(numlgn, item, linekey, amounts, dispatchmode, extendedmode) + if dispatchmode: + self._set_Dim(item, 'EXCNTORI', text_type(linekey.EXCNTORI)) + self._set_Dim(item, 'PARTNERID', text_type(linekey.PARTNERID)) + + def _get_intrastat_linekey(self, declcode, inv_line, dispatchmode, extendedmode): + res = super(XmlDeclaration, self)._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode) + if res and dispatchmode: + res_dict = res._asdict() + res_dict['EXCNTORI'] = inv_line.intrastat_product_origin_country_id.code or 'QU' + res_dict['PARTNERID'] = inv_line.invoice_id.partner_id.vat or 'QV999999999999' + return namedtuple('intrastatkey', res_dict.keys())(**res_dict) + return res + + def _get_expedition_code(self, extended): + return 'INTRASTAT_X_E' if extended else 'INTRASTAT_X_S' + + def _get_expedition_form(self, extended): + return 'INTRASTAT_X_EF' if extended else 'INTRASTAT_X_SF' diff --git a/addons/l10n_be_intrastat_2019/models/account_invoice_line.py b/addons/l10n_be_intrastat_2019/models/account_invoice_line.py new file mode 100644 index 00000000000..01199b0174a --- /dev/null +++ b/addons/l10n_be_intrastat_2019/models/account_invoice_line.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + +class AccountInvoiceLine(models.Model): + _inherit = 'account.invoice.line' + + intrastat_product_origin_country_id = fields.Many2one('res.country', string='Origin Country of Product') diff --git a/addons/l10n_be_intrastat_2019/views/account_invoice_line_view.xml b/addons/l10n_be_intrastat_2019/views/account_invoice_line_view.xml new file mode 100644 index 00000000000..187e32b161e --- /dev/null +++ b/addons/l10n_be_intrastat_2019/views/account_invoice_line_view.xml @@ -0,0 +1,13 @@ + + + + account.invoice.form.inherit.account.be.intrastat + account.invoice + + + + + + + + diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index e691ec98c73..f81b4c496bf 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -230,6 +230,7 @@ var Chatter = Widget.extend({ record_name: this.record_name, default_body: old_composer && old_composer.$input && old_composer.$input.val(), default_mention_selections: old_composer && old_composer.mention_get_listener_selections(), + attachment_ids: (old_composer && old_composer.get('attachment_ids')) || [], }); this.composer.on('input_focused', this, function () { this.composer.mention_set_prefetched_partners(this.mentionSuggestions || []); diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index 64cc48285e5..833e8994b80 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -391,7 +391,7 @@ var BasicComposer = Widget.extend({ // Attachments this.AttachmentDataSet = new data.DataSetSearch(this, 'ir.attachment', this.context); this.fileupload_id = _.uniqueId('o_chat_fileupload'); - this.set('attachment_ids', []); + this.set('attachment_ids', options.attachment_ids || []); // Mention this.mention_manager = new MentionManager(this); @@ -451,6 +451,7 @@ var BasicComposer = Widget.extend({ dom.autoresize(this.$input, {parent: this, min_height: this.options.input_min_height}); // Attachments + this.render_attachments(); $(window).on(this.fileupload_id, this.on_attachment_loaded); this.on("change:attachment_ids", this, this.render_attachments); diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index a10b8c97678..b0d1080296b 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -53,22 +53,31 @@ class SaleOrder(models.Model): for possible refunds created directly from existing invoices. This is necessary since such a refund is not directly linked to the SO. """ + # Ignore the status of the deposit product + deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id() + line_invoice_status_all = [(d['order_id'][0], d['invoice_status']) for d in self.env['sale.order.line'].read_group([('order_id', 'in', self.ids), ('product_id', '!=', deposit_product_id.id)], ['order_id', 'invoice_status'], ['order_id', 'invoice_status'], lazy=False)] for order in self: invoice_ids = order.order_line.mapped('invoice_lines').mapped('invoice_id').filtered(lambda r: r.type in ['out_invoice', 'out_refund']) # Search for invoices which have been 'cancelled' (filter_refund = 'modify' in # 'account.invoice.refund') # use like as origin may contains multiple references (e.g. 'SO01, SO02') - refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund']) + refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id), ('type', 'in', ('out_invoice', 'out_refund'))]) invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')]) - # Search for refunds as well - refund_ids = self.env['account.invoice'].browse() - if invoice_ids: - for inv in invoice_ids: - refund_ids += refund_ids.search([('type', '=', 'out_refund'), ('origin', '=', inv.number), ('origin', '!=', False), ('journal_id', '=', inv.journal_id.id)]) - # Ignore the status of the deposit product - deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id() - line_invoice_status = [line.invoice_status for line in order.order_line if line.product_id != deposit_product_id] + # Search for refunds as well + domain_inv = expression.OR([ + ['&', ('origin', '=', inv.number), ('journal_id', '=', inv.journal_id.id)] + for inv in invoice_ids if inv.number + ]) + if domain_inv: + refund_ids = self.env['account.invoice'].search(expression.AND([ + ['&', ('type', '=', 'out_refund'), ('origin', '!=', False)], + domain_inv + ])) + else: + refund_ids = self.env['account.invoice'].browse() + + line_invoice_status = [d[1] for d in line_invoice_status_all if d[0] == order.id] if order.state not in ('sale', 'done'): invoice_status = 'no' diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index d9e073d3bd5..d6d1426d626 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -262,6 +262,7 @@ context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'uom_qty_change':True, 'company_id': parent.company_id}" name="product_uom_qty" class="oe_inline"/>