text: # -*- coding: utf-8 -*-
  # Part of Odoo. See LICENSE file for full copyright and licensing details.
  
  from datetime import datetime
  from dateutil.relativedelta import relativedelta
  
  from odoo import api, fields, models, SUPERUSER_ID, _
  from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
  from odoo.tools.float_utils import float_is_zero, float_compare
  from odoo.exceptions import UserError, AccessError
  from odoo.tools.misc import formatLang
  from odoo.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP
  import odoo.addons.decimal_precision as dp
  
  
  class PurchaseOrder(models.Model):
      _name = "purchase.order"
      _inherit = ['mail.thread', 'ir.needaction_mixin']
      _description = "Purchase Order"
      _order = 'date_order desc, id desc'
  
      @api.depends('order_line.price_total')
      def _amount_all(self):
          for order in self:
              amount_untaxed = amount_tax = 0.0
              for line in order.order_line:
                  amount_untaxed += line.price_subtotal
                  # FORWARDPORT UP TO 10.0
                  if order.company_id.tax_calculation_rounding_method == 'round_globally':
                      taxes = line.taxes_id.compute_all(line.price_unit, line.order_id.currency_id, line.product_qty, product=line.product_id, partner=line.order_id.partner_id)
                      amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
                  else:
                      amount_tax += line.price_tax
              order.update({
                  'amount_untaxed': order.currency_id.round(amount_untaxed),
                  'amount_tax': order.currency_id.round(amount_tax),
                  'amount_total': amount_untaxed + amount_tax,
              })
  
      @api.depends('order_line.date_planned')
      def _compute_date_planned(self):
          for order in self:
              min_date = False
              for line in order.order_line:
                  if not min_date or line.date_planned < min_date:
                      min_date = line.date_planned
              if min_date:
                  order.date_planned = min_date
  
      @api.depends('state', 'order_line.qty_invoiced', 'order_line.qty_received', 'order_line.product_qty')
      def _get_invoiced(self):
          precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
          for order in self:
              if order.state not in ('purchase', 'done'):
                  order.invoice_status = 'no'
                  continue
  
              if any(float_compare(line.qty_invoiced, line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received, precision_digits=precision) == -1 for line in order.order_line):
                  order.invoice_status = 'to invoice'
              elif all(float_compare(line.qty_invoiced, line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received, precision_digits=precision) >= 0 for line in order.order_line) and order.invoice_ids:
                  order.invoice_status = 'invoiced'
              else:
                  order.invoice_status = 'no'
  
      @api.depends('order_line.invoice_lines.invoice_id.state')
      def _compute_invoice(self):
          for order in self:
              invoices = self.env['account.invoice']
              for line in order.order_line:
                  invoices |= line.invoice_lines.mapped('invoice_id')
              order.invoice_ids = invoices
              order.invoice_count = len(invoices)
  
      @api.model
      def _default_picking_type(self):
          type_obj = self.env['stock.picking.type']
          company_id = self.env.context.get('company_id') or self.env.user.company_id.id
          types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)])
          if not types:
              types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id', '=', False)])
          return types[:1]
  
      @api.depends('order_line.move_ids')
      def _compute_picking(self):
          for order in self:
              pickings = self.env['stock.picking']
              for line in order.order_line:
                  # We keep a limited scope on purpose. Ideally, we should also use move_orig_ids and
                  # do some recursive search, but that could be prohibitive if not done correctly.
                  moves = line.move_ids | line.move_ids.mapped('returned_move_ids')
                  moves = moves.filtered(lambda r: r.state != 'cancel')
                  pickings |= moves.mapped('picking_id')
              order.picking_ids = pickings
              order.picking_count = len(pickings)
  
      @api.depends('picking_ids', 'picking_ids.state')
      def _compute_is_shipped(self):
          for order in self:
              if order.picking_ids and all([x.state == 'done' for x in order.picking_ids]):
                  order.is_shipped = True
  
      READONLY_STATES = {
          'purchase': [('readonly', True)],
          'done': [('readonly', True)],
          'cancel': [('readonly', True)],
      }
  
      name = fields.Char('Order Reference', required=True, index=True, copy=False, default='New')
      origin = fields.Char('Source Document', copy=False,\
          help="Reference of the document that generated this purchase order "
               "request (e.g. a sale order or an internal procurement request)")
      partner_ref = fields.Char('Vendor Reference', copy=False,\
          help="Reference of the sales order or bid sent by the vendor. "
               "It's used to do the matching when you receive the "
               "products as this reference is usually written on the "
               "delivery order sent by your vendor.")
      date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, index=True, copy=False, default=fields.Datetime.now,\
          help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
      date_approve = fields.Date('Approval Date', readonly=1, index=True, copy=False)
      partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
      dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,\
          help="Put an address if you want to deliver directly from the vendor to the customer. "\
               "Otherwise, keep empty to deliver to your own company.")
      currency_id = fields.Many2one('res.currency', 'Currency', required=True, states=READONLY_STATES,\
          default=lambda self: self.env.user.company_id.currency_id.id)
      state = fields.Selection([
          ('draft', 'RFQ'),
          ('sent', 'RFQ Sent'),
          ('to approve', 'To Approve'),
          ('purchase', 'Purchase Order'),
          ('done', 'Locked'),
          ('cancel', 'Cancelled')
          ], string='Status', readonly=True, index=True, copy=False, default='draft', track_visibility='onchange')
      order_line = fields.One2many('purchase.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True)
      notes = fields.Text('Terms and Conditions')
  
      invoice_count = fields.Integer(compute="_compute_invoice", string='# of Bills', copy=False, default=0)
      invoice_ids = fields.Many2many('account.invoice', compute="_compute_invoice", string='Bills', copy=False)
      invoice_status = fields.Selection([
          ('no', 'Nothing to Bill'),
          ('to invoice', 'Waiting Bills'),
          ('invoiced', 'Bills Received'),
          ], string='Billing Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no')
  
      picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0)
      picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False)
  
      # There is no inverse function on purpose since the date may be different on each line
      date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', store=True, index=True)
  
      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')
      amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all')
  
      fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', oldname='fiscal_position')
      payment_term_id = fields.Many2one('account.payment.term', 'Payment Terms')
      incoterm_id = fields.Many2one('stock.incoterms', 'Incoterm', states={'done': [('readonly', True)]}, help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")
  
      product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
      create_uid = fields.Many2one('res.users', 'Responsible')
      company_id = fields.Many2one('res.company', 'Company', required=True, index=True, states=READONLY_STATES, default=lambda self: self.env.user.company_id.id)
  
      picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
          help="This will determine picking type of incoming shipment")
      default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
          help="Technical field used to display the Drop Ship Address", readonly=True)
      group_id = fields.Many2one('procurement.group', string="Procurement Group", copy=False)
      is_shipped = fields.Boolean(compute="_compute_is_shipped")
  
      @api.model
      def name_search(self, name, args=None, operator='ilike', limit=100):
          args = args or []
          domain = []
          if name:
              domain = ['|', ('name', operator, name), ('partner_ref', operator, name)]
          pos = self.search(domain + args, limit=limit)
          return pos.name_get()
  
      @api.multi
      @api.depends('name', 'partner_ref')
      def name_get(self):
          result = []
          for po in self:
              name = po.name
              if po.partner_ref:
                  name += ' ('+po.partner_ref+')'
              if po.amount_total:
                  name += ': ' + formatLang(self.env, po.amount_total, currency_obj=po.currency_id)
              result.append((po.id, name))
          return result
  
      @api.model
      def create(self, vals):
          if vals.get('name', 'New') == 'New':
              vals['name'] = self.env['ir.sequence'].next_by_code('purchase.order') or '/'
          return super(PurchaseOrder, self).create(vals)
  
      @api.multi
      def unlink(self):
          for order in self:
              if not order.state == 'cancel':
                  raise UserError(_('In order to delete a purchase order, you must cancel it first.'))
          return super(PurchaseOrder, self).unlink()
  
      @api.multi
      def copy(self, default=None):
          new_po = super(PurchaseOrder, self).copy(default=default)
          for line in new_po.order_line:
              seller = line.product_id._select_seller(
                  partner_id=line.partner_id, quantity=line.product_qty,
                  date=line.order_id.date_order and line.order_id.date_order[:10], uom_id=line.product_uom)
              line.date_planned = line._get_date_planned(seller)
          return new_po
  
      @api.multi
      def _track_subtype(self, init_values):
          self.ensure_one()
          if 'state' in init_values and self.state == 'purchase':
              return 'purchase.mt_rfq_approved'
          elif 'state' in init_values and self.state == 'to approve':
              return 'purchase.mt_rfq_confirmed'
          elif 'state' in init_values and self.state == 'done':
              return 'purchase.mt_rfq_done'
          return super(PurchaseOrder, self)._track_subtype(init_values)
  
      @api.onchange('partner_id', 'company_id')
      def onchange_partner_id(self):
          if not self.partner_id:
              self.fiscal_position_id = False
              self.payment_term_id = False
              self.currency_id = False
          else:
              self.fiscal_position_id = self.env['account.fiscal.position'].with_context(company_id=self.company_id.id).get_fiscal_position(self.partner_id.id)
              self.payment_term_id = self.partner_id.property_supplier_payment_term_id.id
              self.currency_id = self.partner_id.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id
          return {}
  
      @api.onchange('fiscal_position_id')
      def _compute_tax_id(self):
          """
          Trigger the recompute of the taxes if the fiscal position is changed on the PO.
          """
          for order in self:
              order.order_line._compute_tax_id()
  
      @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.purchase_warn == 'no-message' and partner.parent_id:
              partner = partner.parent_id
  
          if partner.purchase_warn != 'no-message':
              # Block if partner only has warning but parent company is blocked
              if partner.purchase_warn != 'block' and partner.parent_id and partner.parent_id.purchase_warn == 'block':
                  partner = partner.parent_id
              title = _("Warning for %s") % partner.name
              message = partner.purchase_warn_msg
              warning = {
                  'title': title,
                  'message': message
                  }
              if partner.purchase_warn == 'block':
                  self.update({'partner_id': False})
              return {'warning': warning}
          return {}
  
      @api.onchange('picking_type_id')
      def _onchange_picking_type_id(self):
          if self.picking_type_id.default_location_dest_id.usage != 'customer':
              self.dest_address_id = False
  
      @api.multi
      def action_rfq_send(self):
          '''
          This function opens a window to compose an email, with the edi purchase template message loaded by default
          '''
          self.ensure_one()
          ir_model_data = self.env['ir.model.data']
          try:
              if self.env.context.get('send_rfq', False):
                  template_id = ir_model_data.get_object_reference('purchase', 'email_template_edi_purchase')[1]
              else:
                  template_id = ir_model_data.get_object_reference('purchase', 'email_template_edi_purchase_done')[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 = dict(self.env.context or {})
          ctx.update({
              'default_model': 'purchase.order',
              'default_res_id': self.ids[0],
              'default_use_template': bool(template_id),
              'default_template_id': template_id,
              'default_composition_mode': 'comment',
          })
          return {
              'name': _('Compose Email'),
              '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 print_quotation(self):
          self.write({'state': "sent"})
          return self.env['report'].get_action(self, 'purchase.report_purchasequotation')
  
      @api.multi
      def button_approve(self, force=False):
          self.write({'state': 'purchase'})
          self._create_picking()
          if self.company_id.po_lock == 'lock':
              self.write({'state': 'done'})
          return {}
  
      @api.multi
      def button_draft(self):
          self.write({'state': 'draft'})
          return {}
  
      @api.multi
      def button_confirm(self):
          for order in self:
              if order.state not in ['draft', 'sent']:
                  continue
              order._add_supplier_to_product()
              # Deal with double validation process
              if order.company_id.po_double_validation == 'one_step'\
                      or (order.company_id.po_double_validation == 'two_step'\
                          and order.amount_total < self.env.user.company_id.currency_id.compute(order.company_id.po_double_validation_amount, order.currency_id))\
                      or order.user_has_groups('purchase.group_purchase_manager'):
                  order.button_approve()
              else:
                  order.write({'state': 'to approve'})
          return True
  
      @api.multi
      def button_cancel(self):
          for order in self:
              for pick in order.picking_ids:
                  if pick.state == 'done':
                      raise UserError(_('Unable to cancel purchase order %s as some receptions have already been done.') % (order.name))
              for inv in order.invoice_ids:
                  if inv and inv.state not in ('cancel', 'draft'):
                      raise UserError(_("Unable to cancel this purchase order. You must first cancel related vendor bills."))
  
              for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
                  pick.action_cancel()
              # TDE FIXME: I don' think context key is necessary, as actions are not related / called from each other
              if not self.env.context.get('cancel_procurement'):
                  procurements = order.order_line.mapped('procurement_ids')
                  procurements.filtered(lambda r: r.state not in ('cancel', 'exception') and r.rule_id.propagate).write({'state': 'cancel'})
                  procurements.filtered(lambda r: r.state not in ('cancel', 'exception') and not r.rule_id.propagate).write({'state': 'exception'})
                  moves = procurements.filtered(lambda r: r.rule_id.propagate).mapped('move_dest_id')
                  moves.filtered(lambda r: r.state != 'cancel').action_cancel()
  
          self.write({'state': 'cancel'})
  
      @api.multi
      def button_unlock(self):
          self.write({'state': 'purchase'})
  
      @api.multi
      def button_done(self):
          self.write({'state': 'done'})
  
      @api.multi
      def _get_destination_location(self):
          self.ensure_one()
          if self.dest_address_id:
              return self.dest_address_id.property_stock_customer.id
          return self.picking_type_id.default_location_dest_id.id
  
      @api.model
      def _prepare_picking(self):
          if not self.group_id:
              self.group_id = self.group_id.create({
                  'name': self.name,
                  'partner_id': self.partner_id.id
              })
          if not self.partner_id.property_stock_supplier.id:
              raise UserError(_("You must set a Vendor Location for this partner %s") % self.partner_id.name)
          return {
              'picking_type_id': self.picking_type_id.id,
              'partner_id': self.partner_id.id,
              'date': self.date_order,
              'origin': self.name,
              'location_dest_id': self._get_destination_location(),
              'location_id': self.partner_id.property_stock_supplier.id,
              'company_id': self.company_id.id,
          }
  
      @api.multi
      def _create_picking(self):
          StockPicking = self.env['stock.picking']
          for order in self:
              if any([ptype in ['product', 'consu'] for ptype in order.order_line.mapped('product_id.type')]):
                  pickings = order.picking_ids.filtered(lambda x: x.state not in ('done','cancel'))
                  if not pickings:
                      res = order._prepare_picking()
                      picking = StockPicking.create(res)
                  else:
                      picking = pickings[0]
                  moves = order.order_line._create_stock_moves(picking)
                  moves = moves.filtered(lambda x: x.state not in ('done', 'cancel')).action_confirm()
                  seq = 0
                  for move in sorted(moves, key=lambda move: move.date_expected):
                      seq += 5
                      move.sequence = seq
                  moves.force_assign()
                  picking.message_post_with_view('mail.message_origin_link',
                      values={'self': picking, 'origin': order},
                      subtype_id=self.env.ref('mail.mt_note').id)
          return True
  
      @api.multi
      def _add_supplier_to_product(self):
          # Add the partner in the supplier list of the product if the supplier is not registered for
          # this product. We limit to 10 the number of suppliers for a product to avoid the mess that
          # could be caused for some generic products ("Miscellaneous").
          for line in self.order_line:
              # Do not add a contact as a supplier
              partner = self.partner_id if not self.partner_id.parent_id else self.partner_id.parent_id
              if partner not in line.product_id.seller_ids.mapped('name') and len(line.product_id.seller_ids) <= 10:
                  currency = partner.property_purchase_currency_id or self.env.user.company_id.currency_id
                  supplierinfo = {
                      'name': partner.id,
                      'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
                      'product_uom': line.product_uom.id,
                      'min_qty': 0.0,
                      'price': self.currency_id.compute(line.price_unit, currency),
                      'currency_id': currency.id,
                      'delay': 0,
                  }
                  vals = {
                      'seller_ids': [(0, 0, supplierinfo)],
                  }
                  try:
                      line.product_id.write(vals)
                  except AccessError:  # no write access rights -> just ignore
                      break
  
      @api.multi
      def action_view_picking(self):
          '''
          This function returns an action that display existing picking orders of given purchase order ids.
          When only one found, show the picking immediately.
          '''
          action = self.env.ref('stock.action_picking_tree')
          result = action.read()[0]
  
          #override the context to get rid of the default filtering on picking type
          result.pop('id', None)
          result['context'] = {}
          pick_ids = sum([order.picking_ids.ids for order in self], [])
          #choose the view_mode accordingly
          if len(pick_ids) > 1:
              result['domain'] = "[('id','in',[" + ','.join(map(str, pick_ids)) + "])]"
          elif len(pick_ids) == 1:
              res = self.env.ref('stock.view_picking_form', False)
              result['views'] = [(res and res.id or False, 'form')]
              result['res_id'] = pick_ids and pick_ids[0] or False
          return result
  
      @api.multi
      def action_view_invoice(self):
          '''
          This function returns an action that display existing vendor bills of given purchase order ids.
          When only one found, show the vendor bill immediately.
          '''
          action = self.env.ref('account.action_invoice_tree2')
          result = action.read()[0]
  
          #override the context to get rid of the default filtering
          result['context'] = {'type': 'in_invoice', 'default_purchase_id': self.id}
  
          if not self.invoice_ids:
              # Choose a default account journal in the same currency in case a new invoice is created
              journal_domain = [
                  ('type', '=', 'purchase'),
                  ('company_id', '=', self.company_id.id),
                  ('currency_id', '=', self.currency_id.id),
              ]
              default_journal_id = self.env['account.journal'].search(journal_domain, limit=1)
              if default_journal_id:
                  result['context']['default_journal_id'] = default_journal_id.id
          else:
              # Use the same account journal than a previous invoice
              result['context']['default_journal_id'] = self.invoice_ids[0].journal_id.id
  
          #choose the view_mode accordingly
          if len(self.invoice_ids) != 1:
              result['domain'] = "[('id', 'in', " + str(self.invoice_ids.ids) + ")]"
          elif len(self.invoice_ids) == 1:
              res = self.env.ref('account.invoice_supplier_form', False)
              result['views'] = [(res and res.id or False, 'form')]
              result['res_id'] = self.invoice_ids.id
          return result
  
      @api.multi
      def action_set_date_planned(self):
          for order in self:
              order.order_line.update({'date_planned': order.date_planned})
  
  
  class PurchaseOrderLine(models.Model):
      _name = 'purchase.order.line'
      _description = 'Purchase Order Line'
      _order = 'sequence, id'
  
      @api.depends('product_qty', 'price_unit', 'taxes_id')
      def _compute_amount(self):
          for line in self:
              taxes = line.taxes_id.compute_all(line.price_unit, line.order_id.currency_id, line.product_qty, product=line.product_id, partner=line.order_id.partner_id)
              line.update({
                  'price_tax': taxes['total_included'] - taxes['total_excluded'],
                  'price_total': taxes['total_included'],
                  'price_subtotal': taxes['total_excluded'],
              })
  
      @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.supplier_taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
              line.taxes_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_id) if fpos else taxes
  
      @api.depends('invoice_lines.invoice_id.state')
      def _compute_qty_invoiced(self):
          for line in self:
              qty = 0.0
              for inv_line in line.invoice_lines:
                  if inv_line.invoice_id.state not in ['cancel']:
                      if inv_line.invoice_id.type == 'in_invoice':
                          qty += inv_line.uom_id._compute_quantity(inv_line.quantity, line.product_uom)
                      elif inv_line.invoice_id.type == 'in_refund':
                          qty -= inv_line.uom_id._compute_quantity(inv_line.quantity, line.product_uom)
              line.qty_invoiced = qty
  
      @api.depends('order_id.state', 'move_ids.state')
      def _compute_qty_received(self):
          for line in self:
              if line.order_id.state not in ['purchase', 'done']:
                  line.qty_received = 0.0
                  continue
              if line.product_id.type not in ['consu', 'product']:
                  line.qty_received = line.product_qty
                  continue
              total = 0.0
              for move in line.move_ids:
                  if move.state == 'done':
                      if move.product_uom != line.product_uom:
                          total += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom)
                      else:
                          total += move.product_uom_qty
              line.qty_received = total
  
      @api.model
      def create(self, values):
          line = super(PurchaseOrderLine, self).create(values)
          if line.order_id.state == 'purchase':
              line.order_id._create_picking()
              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):
          orders = False
          if 'product_qty' in values:
              changed_lines = self.filtered(lambda x: x.order_id.state == 'purchase')
              if changed_lines:
                  orders = changed_lines.mapped('order_id')
                  for order in orders:
                      order_lines = changed_lines.filtered(lambda x: x.order_id == order)
                      msg = ""
                      if any([values['product_qty'] < x.product_qty for x in order_lines]):
                          msg += "<b>" + _('The ordered quantity has been decreased. Do not forget to take it into account on your bills and receipts.') + '</b><br/>'
                      msg += "<ul>"
                      for line in order_lines:
                          msg += "<li> %s:" % (line.product_id.display_name,)
                          msg += "<br/>" + _("Ordered Quantity") + ": %s -> %s <br/>" % (line.product_qty, float(values['product_qty']),)
                          if line.product_id.type in ('product', 'consu'):
                              msg += _("Received Quantity") + ": %s <br/>" % (line.qty_received,)
                          msg += _("Billed Quantity") + ": %s <br/></li>" % (line.qty_invoiced,)
                      msg += "</ul>"
                      order.message_post(body=msg)
          # Update expectged date of corresponding moves
          if 'date_planned' in values:
              self.env['stock.move'].search([
                  ('purchase_line_id', 'in', self.ids), ('state', '!=', 'done')
              ]).write({'date_expected': values['date_planned']})
          result = super(PurchaseOrderLine, self).write(values)
          if orders:
              orders._create_picking()
          return result
  
      name = fields.Text(string='Description', required=True)
      sequence = fields.Integer(string='Sequence', default=10)
      product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True)
      date_planned = fields.Datetime(string='Scheduled Date', required=True, index=True)
      taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
      product_uom = fields.Many2one('product.uom', string='Product Unit of Measure', required=True)
      product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True)
      move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, ondelete='set null', copy=False)
      price_unit = fields.Float(string='Unit Price', required=True, digits=dp.get_precision('Product Price'))
  
      price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', store=True)
      price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
      price_tax = fields.Monetary(compute='_compute_amount', string='Tax', store=True)
  
      order_id = fields.Many2one('purchase.order', string='Order Reference', index=True, required=True, ondelete='cascade')
      account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
      analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
      company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True)
      state = fields.Selection(related='order_id.state', store=True)
  
      invoice_lines = fields.One2many('account.invoice.line', 'purchase_line_id', string="Bill Lines", readonly=True, copy=False)
  
      # Replace by invoiced Qty
      qty_invoiced = fields.Float(compute='_compute_qty_invoiced', string="Billed Qty", digits=dp.get_precision('Product Unit of Measure'), store=True)
      qty_received = fields.Float(compute='_compute_qty_received', string="Received Qty", digits=dp.get_precision('Product Unit of Measure'), store=True)
  
      partner_id = fields.Many2one('res.partner', related='order_id.partner_id', string='Partner', readonly=True, store=True)
      currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True)
      date_order = fields.Datetime(related='order_id.date_order', string='Order Date', readonly=True)
      procurement_ids = fields.One2many('procurement.order', 'purchase_line_id', string='Associated Procurements', copy=False)
  
      @api.multi
      def _get_stock_move_price_unit(self):
          self.ensure_one()
          line = self[0]
          order = line.order_id
          price_unit = line.price_unit
          if line.taxes_id:
              price_unit = line.taxes_id.with_context(round=False).compute_all(
                  price_unit, currency=line.order_id.currency_id, quantity=1.0, product=line.product_id, partner=line.order_id.partner_id
              )['total_excluded']
          if line.product_uom.id != line.product_id.uom_id.id:
              price_unit *= line.product_uom.factor / line.product_id.uom_id.factor
          if order.currency_id != order.company_id.currency_id:
              price_unit = order.currency_id.compute(price_unit, order.company_id.currency_id, round=False)
          return price_unit
  
      @api.multi
      def _prepare_stock_moves(self, picking):
          """ Prepare the stock moves data for one order line. This function returns a list of
          dictionary ready to be used in stock.move's create()
          """
          self.ensure_one()
          res = []
          if self.product_id.type not in ['product', 'consu']:
              return res
          qty = 0.0
          price_unit = self._get_stock_move_price_unit()
          for move in self.move_ids.filtered(lambda x: x.state != 'cancel'):
              qty += move.product_qty
          template = {
              'name': self.name or '',
              'product_id': self.product_id.id,
              'product_uom': self.product_uom.id,
              'date': self.order_id.date_order,
              'date_expected': self.date_planned,
              'location_id': self.order_id.partner_id.property_stock_supplier.id,
              'location_dest_id': self.order_id._get_destination_location(),
              'picking_id': picking.id,
              'partner_id': self.order_id.dest_address_id.id,
              'move_dest_id': False,
              'state': 'draft',
              'purchase_line_id': self.id,
              'company_id': self.order_id.company_id.id,
              'price_unit': price_unit,
              'picking_type_id': self.order_id.picking_type_id.id,
              'group_id': self.order_id.group_id.id,
              'procurement_id': False,
              'origin': self.order_id.name,
              'route_ids': self.order_id.picking_type_id.warehouse_id and [(6, 0, [x.id for x in self.order_id.picking_type_id.warehouse_id.route_ids])] or [],
              'warehouse_id': self.order_id.picking_type_id.warehouse_id.id,
          }
          # Fullfill all related procurements with this po line
          diff_quantity = self.product_qty - qty
          for procurement in self.procurement_ids.filtered(lambda p: p.state != 'cancel'):
              # If the procurement has some moves already, we should deduct their quantity
              sum_existing_moves = sum(x.product_qty for x in procurement.move_ids if x.state != 'cancel')
              existing_proc_qty = procurement.product_id.uom_id._compute_quantity(sum_existing_moves, procurement.product_uom)
              procurement_qty = procurement.product_uom._compute_quantity(procurement.product_qty, self.product_uom) - existing_proc_qty
              if float_compare(procurement_qty, 0.0, precision_rounding=procurement.product_uom.rounding) > 0 and float_compare(diff_quantity, 0.0, precision_rounding=self.product_uom.rounding) > 0:
                  tmp = template.copy()
                  tmp.update({
                      'product_uom_qty': min(procurement_qty, diff_quantity),
                      'move_dest_id': procurement.move_dest_id.id,  # move destination is same as procurement destination
                      'procurement_id': procurement.id,
                      'propagate': procurement.rule_id.propagate,
                  })
                  res.append(tmp)
                  diff_quantity -= min(procurement_qty, diff_quantity)
          if float_compare(diff_quantity, 0.0,  precision_rounding=self.product_uom.rounding) > 0:
              template['product_uom_qty'] = diff_quantity
              res.append(template)
          return res
  
      @api.multi
      def _create_stock_moves(self, picking):
          moves = self.env['stock.move']
          done = self.env['stock.move'].browse()
          for line in self:
              for val in line._prepare_stock_moves(picking):
                  done += moves.create(val)
          return done
  
      @api.multi
      def unlink(self):
          for line in self:
              if line.order_id.state in ['purchase', 'done']:
                  raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
              for proc in line.procurement_ids:
                  proc.message_post(body=_('Purchase order line deleted.'))
              line.procurement_ids.filtered(lambda r: r.state != 'cancel').write({'state': 'exception'})
          return super(PurchaseOrderLine, self).unlink()
  
      @api.model
      def _get_date_planned(self, seller, po=False):
          """Return the datetime value to use as Schedule Date (``date_planned``) for
             PO Lines that correspond to the given product.seller_ids,
             when ordered at `date_order_str`.
  
             :param browse_record | False product: product.product, used to
                 determine delivery delay thanks to the selected seller field (if False, default delay = 0)
             :param browse_record | False po: purchase.order, necessary only if
                 the PO line is not yet attached to a PO.
             :rtype: datetime
             :return: desired Schedule Date for the PO line
          """
          date_order = po.date_order if po else self.order_id.date_order
          if date_order:
              return datetime.strptime(date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=seller.delay if seller else 0)
          else:
              return datetime.today() + relativedelta(days=seller.delay if seller else 0)
  
      @api.onchange('product_id')
      def onchange_product_id(self):
          result = {}
          if not self.product_id:
              return result
  
          # Reset date, price and quantity since _onchange_quantity will provide default values
          self.date_planned = datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
          self.price_unit = self.product_qty = 0.0
          self.product_uom = self.product_id.uom_po_id or self.product_id.uom_id
          result['domain'] = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
  
          product_lang = self.product_id.with_context({
              'lang': self.partner_id.lang,
              'partner_id': self.partner_id.id,
          })
          self.name = product_lang.display_name
          if product_lang.description_purchase:
              self.name += '\n' + product_lang.description_purchase
  
          fpos = self.order_id.fiscal_position_id
          if self.env.uid == SUPERUSER_ID:
              company_id = self.env.user.company_id.id
              self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id.id == company_id))
          else:
              self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
  
          self._suggest_quantity()
          self._onchange_quantity()
  
          return result
  
      @api.onchange('product_id')
      def onchange_product_id_warning(self):
          if not self.product_id:
              return
          warning = {}
          title = False
          message = False
  
          product_info = self.product_id
  
          if product_info.purchase_line_warn != 'no-message':
              title = _("Warning for %s") % product_info.name
              message = product_info.purchase_line_warn_msg
              warning['title'] = title
              warning['message'] = message
              if product_info.purchase_line_warn == 'block':
                  self.product_id = False
              return {'warning': warning}
          return {}
  
      @api.onchange('product_qty', 'product_uom')
      def _onchange_quantity(self):
          if not self.product_id:
              return
  
          seller = self.product_id._select_seller(
              partner_id=self.partner_id,
              quantity=self.product_qty,
              date=self.order_id.date_order and self.order_id.date_order[:10],
              uom_id=self.product_uom)
  
          if seller or not self.date_planned:
              self.date_planned = self._get_date_planned(seller).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
  
          if not seller:
              return
  
          price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, self.product_id.supplier_taxes_id, self.taxes_id, self.company_id) if seller else 0.0
          if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id:
              price_unit = seller.currency_id.compute(price_unit, self.order_id.currency_id)
  
          if seller and self.product_uom and seller.product_uom != self.product_uom:
              price_unit = seller.product_uom._compute_price(price_unit, self.product_uom)
  
          self.price_unit = price_unit
  
      @api.onchange('product_qty')
      def _onchange_product_qty(self):
          if (self.state == 'purchase' or self.state == 'to approve') and self.product_id.type in ['product', 'consu'] and self.product_qty < self._origin.product_qty:
              warning_mess = {
                  'title': _('Ordered quantity decreased!'),
                  'message' : _('You are decreasing the ordered quantity!\nYou must update the quantities on the reception and/or bills.'),
              }
              return {'warning': warning_mess}
  
      def _suggest_quantity(self):
          '''
          Suggest a minimal quantity based on the seller
          '''
          if not self.product_id:
              return
  
          seller_min_qty = self.product_id.seller_ids\
              .filtered(lambda r: r.name == self.order_id.partner_id)\
              .sorted(key=lambda r: r.min_qty)
          if seller_min_qty:
              self.product_qty = seller_min_qty[0].min_qty or 1.0
              self.product_uom = seller_min_qty[0].product_uom
          else:
              self.product_qty = 1.0
  
  
  class ProcurementRule(models.Model):
      _inherit = 'procurement.rule'
  
      @api.model
      def _get_action(self):
          return [('buy', _('Buy'))] + super(ProcurementRule, self)._get_action()
  
  
  class ProcurementOrder(models.Model):
      _inherit = 'procurement.order'
  
      purchase_line_id = fields.Many2one('purchase.order.line', string='Purchase Order Line')
      purchase_id = fields.Many2one(related='purchase_line_id.order_id', string='Purchase Order')
  
      @api.multi
      def propagate_cancels(self):
          result = super(ProcurementOrder, self).propagate_cancels()
          for procurement in self:
              if procurement.rule_id.action == 'buy' and procurement.purchase_line_id:
                  if procurement.purchase_line_id.order_id.state not in ('draft', 'cancel', 'sent', 'to validate'):
                      raise UserError(
                          _('Can not cancel a procurement related to a purchase order. Please cancel the purchase order first.'))
              if procurement.purchase_line_id:
                  price_unit = 0.0
                  product_qty = 0.0
                  others_procs = procurement.purchase_line_id.procurement_ids.filtered(lambda r: r != procurement)
                  for other_proc in others_procs:
                      if other_proc.state not in ['cancel', 'draft']:
                          product_qty += other_proc.product_uom._compute_quantity(other_proc.product_qty, procurement.purchase_line_id.product_uom)
  
                  precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
                  if not float_is_zero(product_qty, precision_digits=precision):
                      seller = procurement.product_id._select_seller(
                          partner_id=procurement.purchase_line_id.partner_id,
                          quantity=product_qty,
                          date=procurement.purchase_line_id.order_id.date_order and procurement.purchase_line_id.order_id.date_order[:10],
                          uom_id=procurement.purchase_line_id.product_uom)
  
                      price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, procurement.purchase_line_id.product_id.supplier_taxes_id, procurement.purchase_line_id.taxes_id, procurement.company_id) if seller else 0.0
                      if price_unit and seller and procurement.purchase_line_id.order_id.currency_id and seller.currency_id != procurement.purchase_line_id.order_id.currency_id:
                          price_unit = seller.currency_id.compute(price_unit, procurement.purchase_line_id.order_id.currency_id)
  
                      if seller and seller.product_uom != procurement.purchase_line_id.product_uom:
                          price_unit = seller.product_uom._compute_price(price_unit, procurement.purchase_line_id.product_uom)
  
                      procurement.purchase_line_id.product_qty = product_qty
                      procurement.purchase_line_id.price_unit = price_unit
                  else:
                      procurement.purchase_line_id.unlink()
  
          return result
  
      @api.multi
      def _run(self):
          if self.rule_id and self.rule_id.action == 'buy':
              return self.make_po()
          return super(ProcurementOrder, self)._run()
  
      @api.multi
      def _check(self):
          if self.purchase_line_id:
              if not self.move_ids:
                  return False
              return all(move.state in ('done', 'cancel') for move in self.move_ids) and any(move.state == 'done' for move in self.move_ids)
          return super(ProcurementOrder, self)._check()
  
      def _get_purchase_schedule_date(self):
          """Return the datetime value to use as Schedule Date (``date_planned``) for the
             Purchase Order Lines created to satisfy the given procurement. """
          procurement_date_planned = datetime.strptime(self.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
          schedule_date = (procurement_date_planned - relativedelta(days=self.company_id.po_lead))
          return schedule_date
  
      def _get_purchase_order_date(self, schedule_date):
          """Return the datetime value to use as Order Date (``date_order``) for the
             Purchase Order created to satisfy the given procurement. """
          self.ensure_one()
          seller_delay = int(self.product_id._select_seller(quantity=self.product_qty, uom_id=self.product_uom).delay)
          return schedule_date - relativedelta(days=seller_delay)
  
      @api.multi
      def _prepare_purchase_order_line(self, po, supplier):
          self.ensure_one()
  
          procurement_uom_po_qty = self.product_uom._compute_quantity(self.product_qty, self.product_id.uom_po_id)
          seller = self.product_id._select_seller(
              partner_id=supplier.name,
              quantity=procurement_uom_po_qty,
              date=po.date_order and po.date_order[:10],
              uom_id=self.product_id.uom_po_id)
  
          taxes = self.product_id.supplier_taxes_id
          fpos = po.fiscal_position_id
          taxes_id = fpos.map_tax(taxes) if fpos else taxes
          if taxes_id:
              taxes_id = taxes_id.filtered(lambda x: x.company_id.id == self.company_id.id)
  
          price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, self.product_id.supplier_taxes_id, taxes_id, self.company_id) if seller else 0.0
          if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
              price_unit = seller.currency_id.compute(price_unit, po.currency_id)
  
          product_lang = self.product_id.with_context({
              'lang': supplier.name.lang,
              'partner_id': supplier.name.id,
          })
          name = product_lang.display_name
          if product_lang.description_purchase:
              name += '\n' + product_lang.description_purchase
  
          date_planned = self.env['purchase.order.line']._get_date_planned(seller, po=po).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
  
          return {
              'name': name,
              'product_qty': procurement_uom_po_qty,
              'product_id': self.product_id.id,
              'product_uom': self.product_id.uom_po_id.id,
              'price_unit': price_unit,
              'date_planned': date_planned,
              'taxes_id': [(6, 0, taxes_id.ids)],
              'procurement_ids': [(4, self.id)],
              'order_id': po.id,
          }
  
      @api.multi
      def _prepare_purchase_order(self, partner):
          self.ensure_one()
          schedule_date = self._get_purchase_schedule_date()
          purchase_date = self._get_purchase_order_date(schedule_date)
          fpos = self.env['account.fiscal.position'].with_context(company_id=self.company_id.id).get_fiscal_position(partner.id)
  
          gpo = self.rule_id.group_propagation_option
          group = (gpo == 'fixed' and self.rule_id.group_id.id) or \
                  (gpo == 'propagate' and self.group_id.id) or False
  
          return {
              'partner_id': partner.id,
              'picking_type_id': self.rule_id.picking_type_id.id,
              'company_id': self.company_id.id,
              'currency_id': partner.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
              'dest_address_id': self.partner_dest_id.id,
              'origin': self.origin,
              'payment_term_id': partner.property_supplier_payment_term_id.id,
              'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
              'fiscal_position_id': fpos,
              'group_id': group
          }
  
      def _make_po_select_supplier(self, suppliers):
          """ Method intended to be overridden by customized modules to implement any logic in the
              selection of supplier.
          """
          return suppliers[0]
  
      def _make_po_get_domain(self, partner):
          gpo = self.rule_id.group_propagation_option
          group = (gpo == 'fixed' and self.rule_id.group_id) or \
                  (gpo == 'propagate' and self.group_id) or False
  
          domain = (
              ('partner_id', '=', partner.id),
              ('state', '=', 'draft'),
              ('picking_type_id', '=', self.rule_id.picking_type_id.id),
              ('company_id', '=', self.company_id.id),
              ('dest_address_id', '=', self.partner_dest_id.id))
          if group:
              domain += (('group_id', '=', group.id),)
          return domain
  
      @api.multi
      def make_po(self):
          cache = {}
          res = []
          for procurement in self:
              suppliers = procurement.product_id.seller_ids\
                  .filtered(lambda r: (not r.company_id or r.company_id == procurement.company_id) and (not r.product_id or r.product_id == procurement.product_id))
              if not suppliers:
                  procurement.message_post(body=_('No vendor associated to product %s. Please set one to fix this procurement.') % (procurement.product_id.name))
                  continue
              supplier = procurement._make_po_select_supplier(suppliers)
              partner = supplier.name
  
              domain = procurement._make_po_get_domain(partner)
  
              if domain in cache:
                  po = cache[domain]
              else:
                  po = self.env['purchase.order'].search([dom for dom in domain])
                  po = po[0] if po else False
                  cache[domain] = po
              if not po:
                  vals = procurement._prepare_purchase_order(partner)
                  po = self.env['purchase.order'].create(vals)
                  name = (procurement.group_id and (procurement.group_id.name + ":") or "") + (procurement.name != "/" and procurement.name or procurement.move_dest_id.raw_material_production_id and procurement.move_dest_id.raw_material_production_id.name or "")
                  message = _("This purchase order has been created from: <a href=# data-oe-model=procurement.order data-oe-id=%d>%s</a>") % (procurement.id, name)
                  po.message_post(body=message)
                  cache[domain] = po
              elif not po.origin or procurement.origin not in po.origin.split(', '):
                  # Keep track of all procurements
                  if po.origin:
                      if procurement.origin:
                          po.write({'origin': po.origin + ', ' + procurement.origin})
                      else:
                          po.write({'origin': po.origin})
                  else:
                      po.write({'origin': procurement.origin})
                  name = (self.group_id and (self.group_id.name + ":") or "") + (self.name != "/" and self.name or self.move_dest_id.raw_material_production_id and self.move_dest_id.raw_material_production_id.name or "")
                  message = _("This purchase order has been modified from: <a href=# data-oe-model=procurement.order data-oe-id=%d>%s</a>") % (procurement.id, name)
                  po.message_post(body=message)
              if po:
                  res += [procurement.id]
  
              # Create Line
              po_line = False
              for line in po.order_line:
                  if line.product_id == procurement.product_id and line.product_uom == procurement.product_id.uom_po_id:
                      procurement_uom_po_qty = procurement.product_uom._compute_quantity(procurement.product_qty, procurement.product_id.uom_po_id)
                      seller = procurement.product_id._select_seller(
                          partner_id=partner,
                          quantity=line.product_qty + procurement_uom_po_qty,
                          date=po.date_order and po.date_order[:10],
                          uom_id=procurement.product_id.uom_po_id)
  
                      price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, self.company_id) if seller else 0.0
                      if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
                          price_unit = seller.currency_id.compute(price_unit, po.currency_id)
  
                      po_line = line.write({
                          'product_qty': line.product_qty + procurement_uom_po_qty,
                          'price_unit': price_unit,
                          'procurement_ids': [(4, procurement.id)]
                      })
                      break
              if not po_line:
                  vals = procurement._prepare_purchase_order_line(po, supplier)
                  self.env['purchase.order.line'].create(vals)
          return res
  
      @api.multi
      def open_purchase_order(self):
          action = self.env.ref('purchase.purchase_order_action_generic')
          action_dict = action.read()[0]
          action_dict['res_id'] = self.purchase_id.id
          action_dict['target'] = 'current'
          return action_dict
  
  
  class ProductTemplate(models.Model):
      _name = 'product.template'
      _inherit = 'product.template'
  
      @api.model
      def _get_buy_route(self):
          buy_route = self.env.ref('purchase.route_warehouse0_buy', raise_if_not_found=False)
          if buy_route:
              return buy_route.ids
          return []
  
      @api.multi
      def _purchase_count(self):
          for template in self:
              template.purchase_count = sum([p.purchase_count for p in template.product_variant_ids])
          return True
  
      property_account_creditor_price_difference = fields.Many2one(
          'account.account', string="Price Difference Account", company_dependent=True,
          help="This account will be used to value price difference between purchase price and cost price.")
      purchase_count = fields.Integer(compute='_purchase_count', string='# Purchases')
      purchase_method = fields.Selection([
          ('purchase', 'On ordered quantities'),
          ('receive', 'On received quantities'),
          ], string="Control Purchase Bills",
          help="On ordered quantities: control bills based on ordered quantities.\n"
          "On received quantities: control bills based on received quantity.", default="receive")
      route_ids = fields.Many2many(default=lambda self: self._get_buy_route())
      purchase_line_warn = fields.Selection(WARNING_MESSAGE, 'Purchase Order Line', help=WARNING_HELP, required=True, default="no-message")
      purchase_line_warn_msg = fields.Text('Message for Purchase Order Line')
  
  
  class ProductProduct(models.Model):
      _name = 'product.product'
      _inherit = 'product.product'
  
      @api.multi
      def _purchase_count(self):
          domain = [
              ('state', 'in', ['purchase', 'done']),
              ('product_id', 'in', self.mapped('id')),
          ]
          PurchaseOrderLines = self.env['purchase.order.line'].search(domain)
          for product in self:
              product.purchase_count = len(PurchaseOrderLines.filtered(lambda r: r.product_id == product).mapped('order_id'))
  
      purchase_count = fields.Integer(compute='_purchase_count', string='# Purchases')
  
  
  class ProductCategory(models.Model):
      _inherit = "product.category"
  
      property_account_creditor_price_difference_categ = fields.Many2one(
          'account.account', string="Price Difference Account",
          company_dependent=True,
          help="This account will be used to value price difference between purchase price and accounting cost.")
  
  
  class MailComposeMessage(models.TransientModel):
      _inherit = 'mail.compose.message'
  
      @api.multi
      def send_mail(self, auto_commit=False):
          if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'):
              if not self.filtered('subtype_id.internal'):
                  order = self.env['purchase.order'].browse([self._context['default_res_id']])
                  if order.state == 'draft':
                      order.state = 'sent'
          return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit)
  