hunk.code_window:                 'invoice_count': len(set(invoice_ids.ids + refund_ids.ids)),
                   'invoice_ids': invoice_ids.ids + refund_ids.ids,
                   'invoice_status': invoice_status
               })
       @api.model
       def _default_note(self):
           return self.env.user.company_id.sale_note
       @api.model
  
hunk.labels: keep keep keep keep add keep keep keep keep keep
hunk.after_edit:     @api.model
       def get_empty_list_help(self, help):
           if help:
               return '<p class=''oe_view_nocontent_create''">%s</p>' % (help)
           return super(SaleOrder, self).get_empty_list_help(help)
  
hunk.file_path: addons/sale/models/sale.py
hunk.type: add
file: # -*- coding: utf-8 -*-
  # Part of Odoo. See LICENSE file for full copyright and licensing details.
  
  from itertools import groupby
  from datetime import datetime, timedelta
  
  from odoo import api, fields, models, _
  from odoo.exceptions import UserError
  from odoo.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT, pycompat
  from odoo.tools.misc import formatLang
  
  from odoo.addons import decimal_precision as dp
  
  
  class SaleOrder(models.Model):
      _name = "sale.order"
      _inherit = ['mail.thread', 'mail.activity.mixin']
      _description = "Sales Order"
      _order = 'date_order desc, id desc'
  
      @api.depends('order_line.price_total')
      def _amount_all(self):
          """
          Compute the total amounts of the SO.
          """
          for order in self:
              amount_untaxed = amount_tax = 0.0
              for line in order.order_line:
                  amount_untaxed += line.price_subtotal
                  amount_tax += line.price_tax
              order.update({
                  'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed),
                  'amount_tax': order.pricelist_id.currency_id.round(amount_tax),
                  'amount_total': amount_untaxed + amount_tax,
              })
  
      @api.depends('state', 'order_line.invoice_status')
      def _get_invoiced(self):
          """
          Compute the invoice status of a SO. Possible statuses:
          - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
            invoice. This is also hte default value if the conditions of no other status is met.
          - to invoice: if any SO line is 'to invoice', the whole SO is 'to invoice'
          - invoiced: if all SO lines are invoiced, the SO is invoiced.
          - upselling: if all SO lines are invoiced or upselling, the status is upselling.
  
          The invoice_ids are obtained thanks to the invoice lines of the SO lines, and we also search
          for possible refunds created directly from existing invoices. This is necessary since such a
          refund is not directly linked to the SO.
          """
          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)])
              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]
  
              if order.state not in ('sale', 'done'):
                  invoice_status = 'no'
              elif any(invoice_status == 'to invoice' for invoice_status in line_invoice_status):
                  invoice_status = 'to invoice'
              elif all(invoice_status == 'invoiced' for invoice_status in line_invoice_status):
                  invoice_status = 'invoiced'
              elif all(invoice_status in ['invoiced', 'upselling'] for invoice_status in line_invoice_status):
                  invoice_status = 'upselling'
              else:
                  invoice_status = 'no'
  
              order.update({
                  'invoice_count': len(set(invoice_ids.ids + refund_ids.ids)),
                  'invoice_ids': invoice_ids.ids + refund_ids.ids,
                  'invoice_status': invoice_status
              })
  
      @api.model
      def _default_note(self):
          return self.env.user.company_id.sale_note
  
      @api.model
      def _get_default_team(self):
          return self.env['crm.team']._get_default_team_id()
  
      @api.onchange('fiscal_position_id')
      def _compute_tax_id(self):
          """
          Trigger the recompute of the taxes if the fiscal position is changed on the SO.
          """
          for order in self:
              order.order_line._compute_tax_id()
  
      def _inverse_project_id(self):
          self.project_id = self.related_project_id
  
      name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, states={'draft': [('readonly', False)]}, index=True, default=lambda self: _('New'))
      origin = fields.Char(string='Source Document', help="Reference of the document that generated this sales order request.")
      client_order_ref = fields.Char(string='Customer Reference', copy=False)
  
      state = fields.Selection([
          ('draft', 'Quotation'),
          ('sent', 'Quotation Sent'),
          ('sale', 'Sales Order'),
          ('done', 'Locked'),
          ('cancel', 'Cancelled'),
          ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft')
      date_order = fields.Datetime(string='Order Date', required=True, readonly=True, index=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, copy=False, default=fields.Datetime.now)
      validity_date = fields.Date(string='Expiration Date', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
          help="Manually set the expiration date of your quotation (offer), or it will set the date automatically based on the template if online quotation is installed.")
      create_date = fields.Datetime(string='Creation Date', readonly=True, index=True, help="Date on which sales order is created.")
      confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sales order is confirmed.", oldname="date_confirm")
      user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', default=lambda self: self.env.user)
      partner_id = fields.Many2one('res.partner', string='Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, index=True, track_visibility='always')
      partner_invoice_id = fields.Many2one('res.partner', string='Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Invoice address for current sales order.")
      partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Delivery address for current sales order.")
  
      pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current sales order.")
      currency_id = fields.Many2one("res.currency", related='pricelist_id.currency_id', string="Currency", readonly=True, required=True)
      project_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order.", copy=False)
      related_project_id = fields.Many2one('account.analytic.account', inverse='_inverse_project_id', related='project_id', string='Analytic Account', help="The analytic account related to a sales order.")
  
      order_line = fields.One2many('sale.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True, auto_join=True)
  
      invoice_count = fields.Integer(string='# of Invoices', compute='_get_invoiced', readonly=True)
      invoice_ids = fields.Many2many("account.invoice", string='Invoices', compute="_get_invoiced", readonly=True, copy=False)
      invoice_status = fields.Selection([
          ('upselling', 'Upselling Opportunity'),
          ('invoiced', 'Fully Invoiced'),
          ('to invoice', 'To Invoice'),
          ('no', 'Nothing to Invoice')
          ], string='Invoice Status', compute='_get_invoiced', store=True, readonly=True)
  
      note = fields.Text('Terms and conditions', default=_default_note)
  
      amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
      amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all', track_visibility='always')
      amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all', track_visibility='always')
  
      payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', oldname='payment_term')
      fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position')
      company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('sale.order'))
      team_id = fields.Many2one('crm.team', 'Sales Channel', change_default=True, default=_get_default_team, oldname='section_id')
      procurement_group_id = fields.Many2one('procurement.group', 'Procurement Group', copy=False)
  
      product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
  
      @api.model
      def _get_customer_lead(self, product_tmpl_id):
          return False
  
      @api.multi
      def button_dummy(self):
          return True
  
      @api.multi
      def unlink(self):
          for order in self:
              if order.state not in ('draft', 'cancel'):
                  raise UserError(_('You can not delete a sent quotation or a sales order! Try to cancel it before.'))
          return super(SaleOrder, self).unlink()
  
      @api.multi
      def _track_subtype(self, init_values):
          self.ensure_one()
          if 'state' in init_values and self.state == 'sale':
              return 'sale.mt_order_confirmed'
          elif 'state' in init_values and self.state == 'sent':
              return 'sale.mt_order_sent'
          return super(SaleOrder, self)._track_subtype(init_values)
  
      @api.multi
      @api.onchange('partner_shipping_id', 'partner_id')
      def onchange_partner_shipping_id(self):
          """
          Trigger the change of fiscal position when the shipping address is modified.
          """
          self.fiscal_position_id = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, self.partner_shipping_id.id)
          return {}
  
      @api.multi
      @api.onchange('partner_id')
      def onchange_partner_id(self):
          """
          Update the following fields when the partner is changed:
          - Pricelist
          - Payment terms
          - Invoice address
          - Delivery address
          """
          if not self.partner_id:
              self.update({
                  'partner_invoice_id': False,
                  'partner_shipping_id': False,
                  'payment_term_id': False,
                  'fiscal_position_id': False,
              })
              return
  
          addr = self.partner_id.address_get(['delivery', 'invoice'])
          values = {
              'pricelist_id': self.partner_id.property_product_pricelist and self.partner_id.property_product_pricelist.id or False,
              'payment_term_id': self.partner_id.property_payment_term_id and self.partner_id.property_payment_term_id.id or False,
              'partner_invoice_id': addr['invoice'],
              'partner_shipping_id': addr['delivery'],
          }
          if self.env.user.company_id.sale_note:
              values['note'] = self.with_context(lang=self.partner_id.lang).env.user.company_id.sale_note
  
          if self.partner_id.user_id:
              values['user_id'] = self.partner_id.user_id.id
          if self.partner_id.team_id:
              values['team_id'] = self.partner_id.team_id.id
          self.update(values)
  
      @api.onchange('partner_id')
      def onchange_partner_id_warning(self):
          if not self.partner_id:
              return
          warning = {}
          title = False
          message = False
          partner = self.partner_id
  
          # If partner has no warning, check its company
          if partner.sale_warn == 'no-message' and partner.parent_id:
              partner = partner.parent_id
  
          if partner.sale_warn != 'no-message':
              # Block if partner only has warning but parent company is blocked
              if partner.sale_warn != 'block' and partner.parent_id and partner.parent_id.sale_warn == 'block':
                  partner = partner.parent_id
              title = ("Warning for %s") % partner.name
              message = partner.sale_warn_msg
              warning = {
                      'title': title,
                      'message': message,
              }
              if partner.sale_warn == 'block':
                  self.update({'partner_id': False, 'partner_invoice_id': False, 'partner_shipping_id': False, 'pricelist_id': False})
                  return {'warning': warning}
  
          if warning:
              return {'warning': warning}
  
      @api.model
      def create(self, vals):
          if vals.get('name', _('New')) == _('New'):
              vals['name'] = self.env['ir.sequence'].next_by_code('sale.order') or _('New')
  
          # Makes sure partner_invoice_id', 'partner_shipping_id' and 'pricelist_id' are defined
          if any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
              partner = self.env['res.partner'].browse(vals.get('partner_id'))
              addr = partner.address_get(['delivery', 'invoice'])
              vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice'])
              vals['partner_shipping_id'] = vals.setdefault('partner_shipping_id', addr['delivery'])
              vals['pricelist_id'] = vals.setdefault('pricelist_id', partner.property_product_pricelist and partner.property_product_pricelist.id)
          result = super(SaleOrder, self).create(vals)
          return result
  
      @api.multi
      def copy_data(self, default=None):
          if default is None:
              default = {}
          if 'order_line' not in default:
              default['order_line'] = [(0, 0, line.copy_data()[0]) for line in self.order_line.filtered(lambda l: not l.is_downpayment)]
          return super(SaleOrder, self).copy_data(default)
  
      @api.multi
      def _prepare_invoice(self):
          """
          Prepare the dict of values to create the new invoice for a sales order. This method may be
          overridden to implement custom invoice generation (making sure to call super() to establish
          a clean extension chain).
          """
          self.ensure_one()
          journal_id = self.env['account.invoice'].default_get(['journal_id'])['journal_id']
          if not journal_id:
              raise UserError(_('Please define an accounting sales journal for this company.'))
          invoice_vals = {
              'name': self.client_order_ref or '',
              'origin': self.name,
              'type': 'out_invoice',
              'account_id': self.partner_invoice_id.property_account_receivable_id.id,
              'partner_id': self.partner_invoice_id.id,
              'partner_shipping_id': self.partner_shipping_id.id,
              'journal_id': journal_id,
              'currency_id': self.pricelist_id.currency_id.id,
              'comment': self.note,
              'payment_term_id': self.payment_term_id.id,
              'fiscal_position_id': self.fiscal_position_id.id or self.partner_invoice_id.property_account_position_id.id,
              'company_id': self.company_id.id,
              'user_id': self.user_id and self.user_id.id,
              'team_id': self.team_id.id
          }
          return invoice_vals
  
      @api.multi
      def print_quotation(self):
          self.filtered(lambda s: s.state == 'draft').write({'state': 'sent'})
          return self.env.ref('sale.action_report_saleorder').report_action(self)
  
      @api.multi
      def action_view_invoice(self):
          invoices = self.mapped('invoice_ids')
          action = self.env.ref('account.action_invoice_tree1').read()[0]
          if len(invoices) > 1:
              action['domain'] = [('id', 'in', invoices.ids)]
          elif len(invoices) == 1:
              action['views'] = [(self.env.ref('account.invoice_form').id, 'form')]
              action['res_id'] = invoices.ids[0]
          else:
              action = {'type': 'ir.actions.act_window_close'}
          return action
  
      @api.multi
      def action_invoice_create(self, grouped=False, final=False):
          """
          Create the invoice associated to the SO.
          :param grouped: if True, invoices are grouped by SO id. If False, invoices are grouped by
                          (partner_invoice_id, currency)
          :param final: if True, refunds will be generated if necessary
          :returns: list of created invoices
          """
          inv_obj = self.env['account.invoice']
          precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
          invoices = {}
          references = {}
          for order in self:
              group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id)
              for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0):
                  if float_is_zero(line.qty_to_invoice, precision_digits=precision):
                      continue
                  if group_key not in invoices:
                      inv_data = order._prepare_invoice()
                      invoice = inv_obj.create(inv_data)
                      references[invoice] = order
                      invoices[group_key] = invoice
                  elif group_key in invoices:
                      vals = {}
                      if order.name not in invoices[group_key].origin.split(', '):
                          vals['origin'] = invoices[group_key].origin + ', ' + order.name
                      if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', ') and order.client_order_ref != invoices[group_key].name:
                          vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref
                      invoices[group_key].write(vals)
                  if line.qty_to_invoice > 0:
                      line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
                  elif line.qty_to_invoice < 0 and final:
                      line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
  
              if references.get(invoices.get(group_key)):
                  if order not in references[invoices[group_key]]:
                      references[invoice] = references[invoice] | order
  
          if not invoices:
              raise UserError(_('There is no invoicable line.'))
  
          for invoice in pycompat.values(invoices):
              if not invoice.invoice_line_ids:
                  raise UserError(_('There is no invoicable line.'))
              # If invoice is negative, do a refund invoice instead
              if invoice.amount_untaxed < 0:
                  invoice.type = 'out_refund'
                  for line in invoice.invoice_line_ids:
                      line.quantity = -line.quantity
              # Use additional field helper function (for account extensions)
              for line in invoice.invoice_line_ids:
                  line._set_additional_fields(invoice)
              # Necessary to force computation of taxes. In account_invoice, they are triggered
              # by onchanges, which are not triggered when doing a create.
              invoice.compute_taxes()
              invoice.message_post_with_view('mail.message_origin_link',
                  values={'self': invoice, 'origin': references[invoice]},
                  subtype_id=self.env.ref('mail.mt_note').id)
          return [inv.id for inv in pycompat.values(invoices)]
  
      @api.multi
      def action_draft(self):
          orders = self.filtered(lambda s: s.state in ['cancel', 'sent'])
          orders.write({
              'state': 'draft',
              'procurement_group_id': False,
          })
          orders.mapped('order_line').mapped('procurement_ids').write({'sale_line_id': False})
  
      @api.multi
      def action_cancel(self):
          self.write({'state': 'cancel'})
  
      @api.multi
      def action_quotation_send(self):
          '''
          This function opens a window to compose an email, with the edi sale template message loaded by default
          '''
          self.ensure_one()
          ir_model_data = self.env['ir.model.data']
          try:
              template_id = ir_model_data.get_object_reference('sale', 'email_template_edi_sale')[1]
          except ValueError:
              template_id = False
          try:
              compose_form_id = ir_model_data.get_object_reference('mail', 'email_compose_message_wizard_form')[1]
          except ValueError:
              compose_form_id = False
          ctx = {
              'default_model': 'sale.order',
              'default_res_id': self.ids[0],
              'default_use_template': bool(template_id),
              'default_template_id': template_id,
              'default_composition_mode': 'comment',
              'mark_so_as_sent': True,
              'custom_layout': "sale.mail_template_data_notification_email_sale_order",
              'proforma': self.env.context.get('proforma', False)
          }
          return {
              'type': 'ir.actions.act_window',
              'view_type': 'form',
              'view_mode': 'form',
              'res_model': 'mail.compose.message',
              'views': [(compose_form_id, 'form')],
              'view_id': compose_form_id,
              'target': 'new',
              'context': ctx,
          }
  
      @api.multi
      def force_quotation_send(self):
          for order in self:
              email_act = order.action_quotation_send()
              if email_act and email_act.get('context'):
                  email_ctx = email_act['context']
                  email_ctx.update(default_email_from=order.company_id.email)
                  order.with_context(email_ctx).message_post_with_template(email_ctx.get('default_template_id'))
          return True
  
      @api.multi
      def action_done(self):
          self.write({'state': 'done'})
  
      @api.multi
      def action_unlock(self):
          self.write({'state': 'sale'})
  
      def _prepare_procurement_group(self):
          return {'name': self.name}
  
      @api.multi
      def action_confirm(self):
          for order in self.filtered(lambda order: order.partner_id not in order.message_partner_ids):
              order.message_subscribe([order.partner_id.id])
          for order in self:
              order.state = 'sale'
              order.confirmation_date = fields.Datetime.now()
              if self.env.context.get('send_email'):
                  self.force_quotation_send()
              order.order_line._action_procurement_create()
          if self.env['ir.values'].get_default('sale.config.settings', 'auto_done_setting'):
              self.action_done()
          return True
  
      @api.multi
      def _create_analytic_account(self, prefix=None):
          for order in self:
              name = order.name
              if prefix:
                  name = prefix + ": " + order.name
              analytic = self.env['account.analytic.account'].create({
                  'name': name,
                  'code': order.client_order_ref,
                  'company_id': order.company_id.id,
                  'partner_id': order.partner_id.id
              })
              order.project_id = analytic
  
      @api.multi
      def order_lines_layouted(self):
          """
          Returns this order lines classified by sale_layout_category and separated in
          pages according to the category pagebreaks. Used to render the report.
          """
          self.ensure_one()
          report_pages = [[]]
          for category, lines in groupby(self.order_line, lambda l: l.layout_category_id):
              # If last added category induced a pagebreak, this one will be on a new page
              if report_pages[-1] and report_pages[-1][-1]['pagebreak']:
                  report_pages.append([])
              # Append category to current report page
              report_pages[-1].append({
                  'name': category and category.name or 'Uncategorized',
                  'subtotal': category and category.subtotal,
                  'pagebreak': category and category.pagebreak,
                  'lines': list(lines)
              })
  
          return report_pages
  
      @api.multi
      def _get_tax_amount_by_group(self):
          self.ensure_one()
          res = {}
          currency = self.currency_id or self.company_id.currency_id
          for line in self.order_line:
              base_tax = 0
              for tax in line.tax_id:
                  group = tax.tax_group_id
                  res.setdefault(group, 0.0)
                  amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty,
                                           product=line.product_id, partner=self.partner_shipping_id)['taxes'][0]['amount']
                  res[group] += amount
                  if tax.include_base_amount:
                      base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id,
                                                  partner=self.partner_shipping_id)['taxes'][0]['amount']
          res = sorted(pycompat.items(res), key=lambda l: l[0].sequence)
          res = [(l[0].name, l[1]) for l in res]
          return res
  
  
  class SaleOrderLine(models.Model):
      _name = 'sale.order.line'
      _description = 'Sales Order Line'
      _order = 'order_id, layout_category_id, sequence, id'
  
      @api.depends('state', 'product_uom_qty', 'qty_delivered', 'qty_to_invoice', 'qty_invoiced')
      def _compute_invoice_status(self):
          """
          Compute the invoice status of a SO line. Possible statuses:
          - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
            invoice. This is also hte default value if the conditions of no other status is met.
          - to invoice: we refer to the quantity to invoice of the line. Refer to method
            `_get_to_invoice_qty()` for more information on how this quantity is calculated.
          - upselling: this is possible only for a product invoiced on ordered quantities for which
            we delivered more than expected. The could arise if, for example, a project took more
            time than expected but we decided not to invoice the extra cost to the client. This
            occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity
            is removed from the list.
          - invoiced: the quantity invoiced is larger or equal to the quantity ordered.
          """
          precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
          for line in self:
              if line.state not in ('sale', 'done'):
                  line.invoice_status = 'no'
              elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
                  line.invoice_status = 'to invoice'
              elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\
                      float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1:
                  line.invoice_status = 'upselling'
              elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0:
                  line.invoice_status = 'invoiced'
              else:
                  line.invoice_status = 'no'
  
      @api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
      def _compute_amount(self):
          """
          Compute the amounts of the SO line.
          """
          for line in self:
              price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
              taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_shipping_id)
              line.update({
                  'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])),
                  'price_total': taxes['total_included'],
                  'price_subtotal': taxes['total_excluded'],
              })
  
      @api.depends('product_id.invoice_policy', 'order_id.state')
      def _compute_qty_delivered_updateable(self):
          for line in self:
              line.qty_delivered_updateable = (line.order_id.state == 'sale') and (line.product_id.track_service == 'manual') and (line.product_id.expense_policy == 'no')
  
      @api.depends('qty_invoiced', 'qty_delivered', 'product_uom_qty', 'order_id.state')
      def _get_to_invoice_qty(self):
          """
          Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is
          calculated from the ordered quantity. Otherwise, the quantity delivered is used.
          """
          for line in self:
              if line.order_id.state in ['sale', 'done']:
                  if line.product_id.invoice_policy == 'order':
                      line.qty_to_invoice = line.product_uom_qty - line.qty_invoiced
                  else:
                      line.qty_to_invoice = line.qty_delivered - line.qty_invoiced
              else:
                  line.qty_to_invoice = 0
  
      @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity')
      def _get_invoice_qty(self):
          """
          Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. Note
          that this is the case only if the refund is generated from the SO and that is intentional: if
          a refund made would automatically decrease the invoiced quantity, then there is a risk of reinvoicing
          it automatically, which may not be wanted at all. That's why the refund has to be created from the SO
          """
          for line in self:
              qty_invoiced = 0.0
              for invoice_line in line.invoice_lines:
                  if invoice_line.invoice_id.state != 'cancel':
                      if invoice_line.invoice_id.type == 'out_invoice':
                          qty_invoiced += invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_uom)
                      elif invoice_line.invoice_id.type == 'out_refund':
                          qty_invoiced -= invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_uom)
              line.qty_invoiced = qty_invoiced
  
      @api.depends('price_unit', 'discount')
      def _get_price_reduce(self):
          for line in self:
              line.price_reduce = line.price_unit * (1.0 - line.discount / 100.0)
  
      @api.depends('price_total', 'product_uom_qty')
      def _get_price_reduce_tax(self):
          for line in self:
              line.price_reduce_taxinc = line.price_total / line.product_uom_qty if line.product_uom_qty else 0.0
  
      @api.depends('price_subtotal', 'product_uom_qty')
      def _get_price_reduce_notax(self):
          for line in self:
              line.price_reduce_taxexcl = line.price_subtotal / line.product_uom_qty if line.product_uom_qty else 0.0
  
      @api.multi
      def _compute_tax_id(self):
          for line in self:
              fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id
              # If company_id is set, always filter taxes by the company
              taxes = line.product_id.taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
              line.tax_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_shipping_id) if fpos else taxes
  
      @api.multi
      def _prepare_order_line_procurement(self, group_id=False):
          self.ensure_one()
          return {
              'name': self.name,
              'origin': self.order_id.name,
              'date_planned': datetime.strptime(self.order_id.confirmation_date, DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(days=self.customer_lead),
              'product_id': self.product_id.id,
              'product_qty': self.product_uom_qty,
              'product_uom': self.product_uom.id,
              'company_id': self.order_id.company_id.id,
              'group_id': group_id,
              'sale_line_id': self.id
          }
  
      @api.multi
      def _action_procurement_create(self):
          """
          Create procurements based on quantity ordered. If the quantity is increased, new
          procurements are created. If the quantity is decreased, no automated action is taken.
          """
          precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
          new_procs = self.env['procurement.order']  # Empty recordset
          for line in self:
              if line.state != 'sale' or not line.product_id._need_procurement():
                  continue
              qty = 0.0
              for proc in line.procurement_ids:
                  qty += proc.product_qty
              if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0:
                  continue
  
              if not line.order_id.procurement_group_id:
                  vals = line.order_id._prepare_procurement_group()
                  line.order_id.procurement_group_id = self.env["procurement.group"].create(vals)
  
              vals = line._prepare_order_line_procurement(group_id=line.order_id.procurement_group_id.id)
              vals['product_qty'] = line.product_uom_qty - qty
              new_proc = self.env["procurement.order"].with_context(procurement_autorun_defer=True).create(vals)
              new_proc.message_post_with_view('mail.message_origin_link',
                  values={'self': new_proc, 'origin': line.order_id},
                  subtype_id=self.env.ref('mail.mt_note').id)
              new_procs += new_proc
          new_procs.run()
          return new_procs
  
      @api.model
      def _get_purchase_price(self, pricelist, product, product_uom, date):
          return {}
  
      @api.model
      def create(self, values):
          onchange_fields = ['name', 'price_unit', 'product_uom', 'tax_id']
          if values.get('order_id') and values.get('product_id') and any(f not in values for f in onchange_fields):
              line = self.new(values)
              line.product_id_change()
              for field in onchange_fields:
                  if field not in values:
                      values[field] = line._fields[field].convert_to_write(line[field], line)
          line = super(SaleOrderLine, self).create(values)
          if line.state == 'sale':
              line._action_procurement_create()
              msg = _("Extra line with %s ") % (line.product_id.display_name,)
              line.order_id.message_post(body=msg)
  
          return line
  
      @api.multi
      def write(self, values):
          lines = False
          changed_lines = False
          if 'product_uom_qty' in values:
              precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
              lines = self.filtered(
                  lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
              changed_lines = self.filtered(
                  lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) != 0)
              if changed_lines:
                  orders = self.mapped('order_id')
                  for order in orders:
                      order_lines = changed_lines.filtered(lambda x: x.order_id == order)
                      msg = ""
                      if any([values['product_uom_qty'] < x.product_uom_qty for x in order_lines]):
                          msg += "<b>" + _('The ordered quantity has been decreased. Do not forget to take it into account on your invoices and delivery orders.') + '</b>'
                      msg += "<ul>"
                      for line in order_lines:
                          msg += "<li> %s:" % (line.product_id.display_name,)
                          msg += "<br/>" + _("Ordered Quantity") + ": %s -> %s <br/>" % (line.product_uom_qty, float(values['product_uom_qty']),)
                          if line.product_id.type in ('consu', 'product'):
                              msg += _("Delivered Quantity") + ": %s <br/>" % (line.qty_delivered,)
                          msg += _("Invoiced Quantity") + ": %s <br/>" % (line.qty_invoiced,)
                      msg += "</ul>"
                      order.message_post(body=msg)
          result = super(SaleOrderLine, self).write(values)
          if lines:
              lines._action_procurement_create()
          return result
  
      order_id = fields.Many2one('sale.order', string='Order Reference', required=True, ondelete='cascade', index=True, copy=False)
      name = fields.Text(string='Description', required=True)
      sequence = fields.Integer(string='Sequence', default=10)
  
      invoice_lines = fields.Many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_line_id', string='Invoice Lines', copy=False)
      invoice_status = fields.Selection([
          ('upselling', 'Upselling Opportunity'),
          ('invoiced', 'Fully Invoiced'),
          ('to invoice', 'To Invoice'),
          ('no', 'Nothing to Invoice')
          ], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
      price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0)
  
      price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', readonly=True, store=True)
      price_tax = fields.Float(compute='_compute_amount', string='Taxes', readonly=True, store=True)
      price_total = fields.Monetary(compute='_compute_amount', string='Total', readonly=True, store=True)
  
      price_reduce = fields.Monetary(compute='_get_price_reduce', string='Price Reduce', readonly=True, store=True)
      tax_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
      price_reduce_taxinc = fields.Monetary(compute='_get_price_reduce_tax', string='Price Reduce Tax inc', readonly=True, store=True)
      price_reduce_taxexcl = fields.Monetary(compute='_get_price_reduce_notax', string='Price Reduce Tax excl', readonly=True, store=True)
  
      discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
  
      product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], change_default=True, ondelete='restrict', required=True)
      product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
      product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True)
  
      qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
      qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0)
      qty_to_invoice = fields.Float(
          compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True,
          digits=dp.get_precision('Product Unit of Measure'))
      qty_invoiced = fields.Float(
          compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
          digits=dp.get_precision('Product Unit of Measure'))
  
      salesman_id = fields.Many2one(related='order_id.user_id', store=True, string='Salesperson', readonly=True)
      currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True)
      company_id = fields.Many2one(related='order_id.company_id', string='Company', store=True, readonly=True)
      order_partner_id = fields.Many2one(related='order_id.partner_id', store=True, string='Customer')
      analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
      is_downpayment = fields.Boolean(
          string="Is a down payment", help="Down payments are made when creating invoices from a sales order."
          " They are not copied when duplicating a sales order.")
  
      state = fields.Selection([
          ('draft', 'Quotation'),
          ('sent', 'Quotation Sent'),
          ('sale', 'Sales Order'),
          ('done', 'Done'),
          ('cancel', 'Cancelled'),
      ], related='order_id.state', string='Order Status', readonly=True, copy=False, store=True, default='draft')
  
      customer_lead = fields.Float(
          'Delivery Lead Time', required=True, default=0.0,
          help="Number of days between the order confirmation and the shipping of the products to the customer", oldname="delay")
      procurement_ids = fields.One2many('procurement.order', 'sale_line_id', string='Procurements')
  
      layout_category_id = fields.Many2one('sale.layout_category', string='Section')
      layout_category_sequence = fields.Integer(related='layout_category_id.sequence', string='Layout Sequence', store=True)
      #  Store is intentionally set in order to keep the "historic" order.
  
      @api.multi
      def _prepare_invoice_line(self, qty):
          """
          Prepare the dict of values to create the new invoice line for a sales order line.
  
          :param qty: float quantity to invoice
          """
          self.ensure_one()
          res = {}
          account = self.product_id.property_account_income_id or self.product_id.categ_id.property_account_income_categ_id
          if not account:
              raise UserError(_('Please define income account for this product: "%s" (id:%d) - or for its category: "%s".') %
                  (self.product_id.name, self.product_id.id, self.product_id.categ_id.name))
  
          fpos = self.order_id.fiscal_position_id or self.order_id.partner_id.property_account_position_id
          if fpos:
              account = fpos.map_account(account)
  
          res = {
              'name': self.name,
              'sequence': self.sequence,
              'origin': self.order_id.name,
              'account_id': account.id,
              'price_unit': self.price_unit,
              'quantity': qty,
              'discount': self.discount,
              'uom_id': self.product_uom.id,
              'product_id': self.product_id.id or False,
              'layout_category_id': self.layout_category_id and self.layout_category_id.id or False,
              'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)],
              'account_analytic_id': self.order_id.project_id.id,
              'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
          }
          return res
  
      @api.multi
      def invoice_line_create(self, invoice_id, qty):
          """ Create an invoice line. The quantity to invoice can be positive (invoice) or negative (refund).
              :param invoice_id: integer
              :param qty: float quantity to invoice
              :returns recordset of account.invoice.line created
          """
          invoice_lines = self.env['account.invoice.line']
          precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
          for line in self:
              if not float_is_zero(qty, precision_digits=precision):
                  vals = line._prepare_invoice_line(qty=qty)
                  vals.update({'invoice_id': invoice_id, 'sale_line_ids': [(6, 0, [line.id])]})
                  invoice_lines |= self.env['account.invoice.line'].create(vals)
          return invoice_lines
  
      @api.multi
      def _get_display_price(self, product):
          # TO DO: move me in master/saas-16 on sale.order
          if self.order_id.pricelist_id.discount_policy == 'with_discount':
              return product.with_context(pricelist=self.order_id.pricelist_id.id).price
          price, rule_id = self.order_id.pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
          pricelist_item = self.env['product.pricelist.item'].browse(rule_id)
          if (pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id.discount_policy == 'with_discount'):
              price, rule_id = pricelist_item.base_pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
              return price
          else:
              from_currency = self.order_id.company_id.currency_id
              return from_currency.compute(product.lst_price, self.order_id.pricelist_id.currency_id)
  
      @api.multi
      @api.onchange('product_id')
      def product_id_change(self):
          if not self.product_id:
              return {'domain': {'product_uom': []}}
  
          vals = {}
          domain = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
          if not self.product_uom or (self.product_id.uom_id.id != self.product_uom.id):
              vals['product_uom'] = self.product_id.uom_id
              vals['product_uom_qty'] = 1.0
  
          product = self.product_id.with_context(
              lang=self.order_id.partner_id.lang,
              partner=self.order_id.partner_id.id,
              quantity=vals.get('product_uom_qty') or self.product_uom_qty,
              date=self.order_id.date_order,
              pricelist=self.order_id.pricelist_id.id,
              uom=self.product_uom.id
          )
  
          name = product.name_get()[0][1]
          if product.description_sale:
              name += '\n' + product.description_sale
          vals['name'] = name
  
          self._compute_tax_id()
  
          if self.order_id.pricelist_id and self.order_id.partner_id:
              vals['price_unit'] = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id)
          self.update(vals)
  
          title = False
          message = False
          warning = {}
          if product.sale_line_warn != 'no-message':
              title = _("Warning for %s") % product.name
              message = product.sale_line_warn_msg
              warning['title'] = title
              warning['message'] = message
              if product.sale_line_warn == 'block':
                  self.product_id = False
              return {'warning': warning}
          return {'domain': domain}
  
      @api.onchange('product_uom', 'product_uom_qty')
      def product_uom_change(self):
          if not self.product_uom:
              self.price_unit = 0.0
              return
          if self.order_id.pricelist_id and self.order_id.partner_id:
              product = self.product_id.with_context(
                  lang=self.order_id.partner_id.lang,
                  partner=self.order_id.partner_id.id,
                  quantity=self.product_uom_qty,
                  date_order=self.order_id.date_order,
                  pricelist=self.order_id.pricelist_id.id,
                  uom=self.product_uom.id,
                  fiscal_position=self.env.context.get('fiscal_position')
              )
              self.price_unit = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id)
  
      @api.multi
      def unlink(self):
          if self.filtered(lambda x: x.state in ('sale', 'done')):
              raise UserError(_('You can not remove a sales order line.\nDiscard changes and try setting the quantity to 0.'))
          return super(SaleOrderLine, self).unlink()
  
      @api.multi
      def _get_delivered_qty(self):
          '''
          Intended to be overridden in sale_stock and sale_mrp
          :return: the quantity delivered
          :rtype: float
          '''
          return 0.0
  
      def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id):
          """Retrieve the price before applying the pricelist
              :param obj product: object of current product record
              :parem float qty: total quentity of product
              :param tuple price_and_rule: tuple(price, suitable_rule) coming from pricelist computation
              :param obj uom: unit of measure of current order line
              :param integer pricelist_id: pricelist id of sales order"""
          PricelistItem = self.env['product.pricelist.item']
          field_name = 'lst_price'
          currency_id = None
          product_currency = None
          if rule_id:
              pricelist_item = PricelistItem.browse(rule_id)
              if pricelist_item.pricelist_id.discount_policy == 'without_discount':
                  while pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id and pricelist_item.base_pricelist_id.discount_policy == 'without_discount':
                      price, rule_id = pricelist_item.base_pricelist_id.with_context(uom=uom.id).get_product_price_rule(product, qty, self.order_id.partner_id)
                      pricelist_item = PricelistItem.browse(rule_id)
  
              if pricelist_item.base == 'standard_price':
                  field_name = 'standard_price'
              if pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id:
                  field_name = 'price'
                  product = product.with_context(pricelist=pricelist_item.base_pricelist_id.id)
                  product_currency = pricelist_item.base_pricelist_id.currency_id
              currency_id = pricelist_item.pricelist_id.currency_id
  
          product_currency = product_currency or(product.company_id and product.company_id.currency_id) or self.env.user.company_id.currency_id
          if not currency_id:
              currency_id = product_currency
              cur_factor = 1.0
          else:
              if currency_id.id == product_currency.id:
                  cur_factor = 1.0
              else:
                  cur_factor = currency_id._get_conversion_rate(product_currency, currency_id)
  
          product_uom = self.env.context.get('uom') or product.uom_id.id
          if uom and uom.id != product_uom:
              # the unit price is in a different uom
              uom_factor = uom._compute_price(1.0, product.uom_id)
          else:
              uom_factor = 1.0
  
          return product[field_name] * uom_factor * cur_factor, currency_id.id
  
      @api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id')
      def _onchange_discount(self):
          self.discount = 0.0
          if not (self.product_id and self.product_uom and
                  self.order_id.partner_id and self.order_id.pricelist_id and
                  self.order_id.pricelist_id.discount_policy == 'without_discount' and
                  self.env.user.has_group('sale.group_discount_per_so_line')):
              return
  
          context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order)
          pricelist_context = dict(context_partner, uom=self.product_uom.id)
  
          price, rule_id = self.order_id.pricelist_id.with_context(pricelist_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
          new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id)
          new_list_price = self.env['account.tax']._fix_tax_included_price(new_list_price, self.product_id.taxes_id, self.tax_id)
  
          if new_list_price != 0:
              if self.product_id.company_id and self.order_id.pricelist_id.currency_id != self.product_id.company_id.currency_id:
                  # new_list_price is in company's currency while price in pricelist currency
                  new_list_price = self.env['res.currency'].browse(currency_id).with_context(context_partner).compute(new_list_price, self.order_id.pricelist_id.currency_id)
              discount = (new_list_price - price) / new_list_price * 100
              if discount > 0:
                  self.discount = discount
  