diff --git a/addons/purchase/__manifest__.py b/addons/purchase/__manifest__.py index b6d47abdbac..bac5c28fb44 100644 --- a/addons/purchase/__manifest__.py +++ b/addons/purchase/__manifest__.py @@ -9,7 +9,7 @@ 'summary': 'Purchase Orders, Receipts, Vendor Bills', 'description': "", 'website': 'https://www.odoo.com/page/purchase', - 'depends': ['stock_account'], + 'depends': ['account'], 'data': [ 'security/purchase_security.xml', 'security/ir.model.access.csv', diff --git a/addons/purchase/data/purchase_data.xml b/addons/purchase/data/purchase_data.xml index cc4c8e2b7a8..a7cae1b0012 100644 --- a/addons/purchase/data/purchase_data.xml +++ b/addons/purchase/data/purchase_data.xml @@ -18,17 +18,7 @@ purchase.order - - - - Buy - - 5 - - - + Purchase Order purchase.order @@ -37,9 +27,5 @@ - - - - diff --git a/addons/purchase/data/purchase_demo.xml b/addons/purchase/data/purchase_demo.xml index 173ee4855fc..4801fe15634 100644 --- a/addons/purchase/data/purchase_demo.xml +++ b/addons/purchase/data/purchase_demo.xml @@ -6,21 +6,6 @@ - - - - - - - - - - - - - - - @@ -184,14 +169,5 @@ ]"/> - - - - - - - - - diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py index 9e220d23704..ba395fd25cc 100644 --- a/addons/purchase/models/account_invoice.py +++ b/addons/purchase/models/account_invoice.py @@ -44,7 +44,7 @@ class AccountInvoice(models.Model): ('invoice_status', 'in', ['to invoice', 'no']), ('partner_id', 'child_of', self.partner_id.id), ('id', 'not in', purchase_ids.ids), - ]} + ]} return result def _prepare_invoice_line_from_po_line(self, line): @@ -146,110 +146,6 @@ class AccountInvoice(models.Model): self.currency_id = self.env.context['default_currency_id'] return res - @api.model - def invoice_line_move_line_get(self): - res = super(AccountInvoice, self).invoice_line_move_line_get() - - if self.env.user.company_id.anglo_saxon_accounting: - if self.type in ['in_invoice', 'in_refund']: - for i_line in self.invoice_line_ids: - res.extend(self._anglo_saxon_purchase_move_lines(i_line, res)) - return res - - @api.model - def _anglo_saxon_purchase_move_lines(self, i_line, res): - """Return the additional move lines for purchase invoices and refunds. - - i_line: An account.invoice.line object. - res: The move line entries produced so far by the parent move_line_get. - """ - inv = i_line.invoice_id - company_currency = inv.company_id.currency_id - if i_line.product_id and i_line.product_id.valuation == 'real_time' and i_line.product_id.type == 'product': - # get the fiscal position - fpos = i_line.invoice_id.fiscal_position_id - # get the price difference account at the product - acc = i_line.product_id.property_account_creditor_price_difference - if not acc: - # if not found on the product get the price difference account at the category - acc = i_line.product_id.categ_id.property_account_creditor_price_difference_categ - acc = fpos.map_account(acc).id - # reference_account_id is the stock input account - reference_account_id = i_line.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=fpos)['stock_input'].id - diff_res = [] - # calculate and write down the possible price difference between invoice price and product price - for line in res: - if line.get('invl_id', 0) == i_line.id and reference_account_id == line['account_id']: - valuation_price_unit = i_line.product_id.uom_id._compute_price(i_line.product_id.standard_price, i_line.uom_id) - line_quantity = line['quantity'] - - if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id: - #for average/fifo/lifo costing method, fetch real cost price from incomming moves - valuation_price_unit = i_line.purchase_line_id.product_uom._compute_price(i_line.purchase_line_id.price_unit, i_line.uom_id) - stock_move_obj = self.env['stock.move'] - valuation_stock_move = stock_move_obj.search([('purchase_line_id', '=', i_line.purchase_line_id.id), ('state', '=', 'done')]) - - if valuation_stock_move: - valuation_price_unit_total = 0 - valuation_total_qty = 0 - for val_stock_move in valuation_stock_move: - valuation_price_unit_total += abs(val_stock_move.price_unit) * val_stock_move.product_qty - valuation_total_qty += val_stock_move.product_qty - valuation_price_unit = valuation_price_unit_total / valuation_total_qty - valuation_price_unit = i_line.product_id.uom_id._compute_price(valuation_price_unit, i_line.uom_id) - line_quantity = valuation_total_qty - - elif i_line.product_id.cost_method == 'real': - # In this condition, we have a real price-valuated product which has not yet been received - valuation_price_unit = i_line.purchase_line_id.price_unit - - interim_account_price = valuation_price_unit * line_quantity - if inv.currency_id.id != company_currency.id: - # We express everyhting in the invoice currency - valuation_price_unit = company_currency._convert(valuation_price_unit, inv.currency_id, inv.company_id, inv.date_invoice or fields.Date.today(), round=False) - interim_account_price = company_currency._convert(interim_account_price, inv.currency_id, inv.company_id, inv.date_invoice or fields.Date.today(), round=False) - - invoice_cur_prec = inv.currency_id.decimal_places - - if float_compare(valuation_price_unit, i_line.price_unit, precision_digits=invoice_cur_prec) != 0 and float_compare(line['price_unit'], i_line.price_unit, precision_digits=invoice_cur_prec) == 0: - - # price with discount and without tax included - price_unit = i_line.price_unit * (1 - (i_line.discount or 0.0) / 100.0) - tax_ids = [] - if line['tax_ids']: - #line['tax_ids'] is like [(4, tax_id, None), (4, tax_id2, None)...] - taxes = self.env['account.tax'].browse([x[1] for x in line['tax_ids']]) - price_unit = taxes.compute_all(price_unit, currency=inv.currency_id, quantity=1.0)['total_excluded'] - for tax in taxes: - tax_ids.append((4, tax.id, None)) - for child in tax.children_tax_ids: - if child.type_tax_use != 'none': - tax_ids.append((4, child.id, None)) - - price_before = line.get('price', 0.0) - price_unit_val_dif = price_unit - valuation_price_unit - - price_val_dif = price_before - interim_account_price - if inv.currency_id.compare_amounts(i_line.price_unit, i_line.purchase_line_id.price_unit) != 0 and acc: - # If the unit prices have not changed and we have a - # valuation difference, it means this difference is due to exchange rates, - # so we don't create anything, the exchange rate entries will - # be processed automatically by the rest of the code. - diff_res.append({ - 'type': 'src', - 'name': i_line.name[:64], - 'price_unit': inv.currency_id.round(price_unit_val_dif), - 'quantity': line_quantity, - 'price': inv.currency_id.round(price_val_dif), - 'account_id': acc, - 'product_id': line['product_id'], - 'uom_id': line['uom_id'], - 'account_analytic_id': line['account_analytic_id'], - 'tax_ids': tax_ids, - }) - return diff_res - return [] - @api.model def create(self, vals): invoice = super(AccountInvoice, self).create(vals) @@ -273,16 +169,6 @@ class AccountInvoice(models.Model): invoice.message_post(body=message) return result - def _get_last_step_stock_moves(self): - """ Overridden from stock_account. - Returns the stock moves associated to this invoice.""" - rslt = super(AccountInvoice, self)._get_last_step_stock_moves() - for invoice in self.filtered(lambda x: x.type == 'in_invoice'): - rslt += invoice.mapped('invoice_line_ids.purchase_line_id.move_ids').filtered(lambda x: x.state == 'done' and x.location_id.usage == 'supplier') - for invoice in self.filtered(lambda x: x.type == 'in_refund'): - rslt += invoice.mapped('invoice_line_ids.purchase_line_id.move_ids').filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'supplier') - return rslt - class AccountInvoiceLine(models.Model): """ Override AccountInvoice_line to add the link to the purchase order line it is related to""" diff --git a/addons/purchase/models/product.py b/addons/purchase/models/product.py index 00bee1d5fca..3cd911b3925 100644 --- a/addons/purchase/models/product.py +++ b/addons/purchase/models/product.py @@ -9,13 +9,6 @@ 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: @@ -31,7 +24,6 @@ class ProductTemplate(models.Model): ('receive', 'On received quantities'), ], string="Control Policy", 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') diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 01827138000..6053f5c0b8f 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -6,10 +6,9 @@ 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.tools.float_utils import float_compare from odoo.exceptions import UserError, AccessError from odoo.tools.misc import formatLang -from odoo.addons.base.models.res_partner import WARNING_MESSAGE, WARNING_HELP from odoo.addons import decimal_precision as dp @@ -68,35 +67,6 @@ class PurchaseOrder(models.Model): 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.returned_move_ids', - 'order_line.move_ids.state', - 'order_line.move_ids.picking_id') - 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') - 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)], @@ -104,10 +74,10 @@ class PurchaseOrder(models.Model): } name = fields.Char('Order Reference', required=True, index=True, copy=False, default='New') - origin = fields.Char('Source Document', copy=False,\ + origin = fields.Char('Source Document', copy=False, help="Reference of the document that generated this purchase order " "request (e.g. a sales order)") - partner_ref = fields.Char('Vendor Reference', copy=False,\ + 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 " @@ -116,10 +86,10 @@ class PurchaseOrder(models.Model): 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. "\ + 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,\ + 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'), @@ -128,7 +98,7 @@ class PurchaseOrder(models.Model): ('purchase', 'Purchase Order'), ('done', 'Locked'), ('cancel', 'Cancelled') - ], string='Status', readonly=True, index=True, copy=False, default='draft', track_visibility='onchange') + ], 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') @@ -138,10 +108,7 @@ class PurchaseOrder(models.Model): ('no', 'Nothing to Bill'), ('to invoice', 'Waiting Bills'), ('invoiced', 'No Bill to Receive'), - ], string='Billing Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no') - - picking_count = fields.Integer(compute='_compute_picking', string='Picking count', default=0, store=True) - picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False, store=True) + ], string='Billing Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no') # 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) @@ -152,19 +119,11 @@ class PurchaseOrder(models.Model): 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') user_id = fields.Many2one('res.users', string='Purchase Representative', index=True, track_visibility='onchange', default=lambda self: self.env.user) 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 operation 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") - website_url = fields.Char( 'Website URL', compute='_website_url', help='The full URL to access the document through the website.') @@ -189,7 +148,7 @@ class PurchaseOrder(models.Model): for po in self: name = po.name if po.partner_ref: - name += ' ('+po.partner_ref+')' + name += ' (' + po.partner_ref + ')' if self.env.context.get('show_total_amount') and po.amount_total: name += ': ' + formatLang(self.env, po.amount_total, currency_obj=po.currency_id) result.append((po.id, name)) @@ -201,21 +160,6 @@ class PurchaseOrder(models.Model): vals['name'] = self.env['ir.sequence'].next_by_code('purchase.order') or '/' return super(PurchaseOrder, self).create(vals) - def write(self, vals): - if vals.get('order_line') and self.state == 'purchase': - for order in self: - pre_order_line_qty = {order_line: order_line.product_qty for order_line in order.mapped('order_line')} - res = super(PurchaseOrder, self).write(vals) - if vals.get('order_line') and self.state == 'purchase': - for order in self: - to_log = {} - for order_line in order.order_line: - if pre_order_line_qty.get(order_line, False) and float_compare(pre_order_line_qty[order_line], order_line.product_qty, precision_rounding=order_line.product_uom.rounding) > 0: - to_log[order_line] = (order_line.product_qty, pre_order_line_qty[order_line]) - if to_log: - order._log_decrease_ordered_quantity(to_log) - return res - @api.multi def unlink(self): for order in self: @@ -224,7 +168,6 @@ class PurchaseOrder(models.Model): return super(PurchaseOrder, self).unlink() @api.multi - @api.returns('self', lambda value: value.id) def copy(self, default=None): new_po = super(PurchaseOrder, self).copy(default=default) for line in new_po.order_line: @@ -254,8 +197,7 @@ class PurchaseOrder(models.Model): 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 - if not self.currency_id: - self.currency_id = self.partner_id.property_purchase_currency_id.id or self.env.user.company_id.currency_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') @@ -289,17 +231,12 @@ class PurchaseOrder(models.Model): 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): ''' @@ -325,7 +262,7 @@ class PurchaseOrder(models.Model): 'default_use_template': bool(template_id), 'default_template_id': template_id, 'default_composition_mode': 'comment', - 'custom_layout': "mail.mail_notification_borders", + 'custom_layout': "purchase.mail_template_data_notification_email_purchase_order", 'force_email': True, 'mark_rfq_as_sent': True, }) @@ -355,9 +292,7 @@ class PurchaseOrder(models.Model): @api.multi def button_approve(self, force=False): self.write({'state': 'purchase', 'date_approve': fields.Date.context_today(self)}) - self._create_picking() - self.filtered( - lambda p: p.company_id.po_lock == 'lock').write({'state': 'done'}) + self.filtered(lambda p: p.company_id.po_lock == 'lock').write({'state': 'done'}) return {} @api.multi @@ -385,26 +320,9 @@ class PurchaseOrder(models.Model): @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.")) - - # If the product is MTO, change the procure_method of the the closest move to purchase to MTS. - # The purpose is to link the po that the user will manually generate to the existing moves's chain. - if order.state in ('draft', 'sent', 'to approve'): - for order_line in order.order_line: - if order_line.move_dest_ids: - siblings_states = (order_line.move_dest_ids.mapped('move_orig_ids')).mapped('state') - if all(state in ('done', 'cancel') for state in siblings_states): - order_line.move_dest_ids.write({'procure_method': 'make_to_stock'}) - order_line.move_dest_ids._recompute_state() - - for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'): - pick.action_cancel() - self.write({'state': 'cancel'}) @api.multi @@ -415,55 +333,6 @@ class PurchaseOrder(models.Model): 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._action_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 @@ -491,62 +360,6 @@ class PurchaseOrder(models.Model): except AccessError: # no write access rights -> just ignore break - def _log_decrease_ordered_quantity(self, purchase_order_lines_quantities): - - def _keys_in_sorted(move): - """ sort by picking and the responsible for the product the - move. - """ - return (move.picking_id.id, move.product_id.responsible_id.id) - - def _keys_in_groupby(move): - """ group by picking and the responsible for the product the - move. - """ - return (move.picking_id, move.product_id.responsible_id) - - def _render_note_exception_quantity_po(order_exceptions): - order_line_ids = self.env['purchase.order.line'].browse([order_line.id for order in order_exceptions.values() for order_line in order[0]]) - purchase_order_ids = order_line_ids.mapped('order_id') - move_ids = self.env['stock.move'].concat(*rendering_context.keys()) - impacted_pickings = move_ids.mapped('picking_id')._get_impacted_pickings(move_ids) - move_ids.mapped('picking_id') - values = { - 'purchase_order_ids': purchase_order_ids, - 'order_exceptions': order_exceptions.values(), - 'impacted_pickings': impacted_pickings, - } - return self.env.ref('purchase.exception_on_po').render(values=values) - - documents = self.env['stock.picking']._log_activity_get_documents(purchase_order_lines_quantities, 'move_ids', 'DOWN', _keys_in_sorted, _keys_in_groupby) - filtered_documents = {} - for (parent, responsible), rendering_context in documents.items(): - if parent._name == 'stock.picking': - if parent.state == 'cancel': - continue - filtered_documents[(parent, responsible)] = rendering_context - self.env['stock.picking']._log_activity(_render_note_exception_quantity_po, filtered_documents) - - @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 operation type - result['context'] = {} - pick_ids = self.mapped('picking_ids') - #choose the view_mode accordingly - if len(pick_ids) > 1: - result['domain'] = "[('id','in',%s)]" % (pick_ids.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.id - return result - @api.multi def action_view_invoice(self): ''' @@ -556,15 +369,15 @@ class PurchaseOrder(models.Model): 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', + # override the context to get rid of the default filtering + result['context'] = { + 'type': 'in_invoice', 'default_purchase_id': self.id, 'default_currency_id': self.currency_id.id, 'default_company_id': self.company_id.id, 'company_id': self.company_id.id } - - #choose the view_mode accordingly + # 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: @@ -614,33 +427,6 @@ class PurchaseOrderLine(models.Model): qty -= inv_line.uom_id._compute_quantity(inv_line.quantity, line.product_uom) line.qty_invoiced = qty - @api.model - def create(self, values): - line = super(PurchaseOrderLine, self).create(values) - if line.order_id.state == 'purchase': - line._create_or_update_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): - if 'product_qty' in values: - for line in self: - if line.order_id.state == 'purchase': - line.order_id.message_post_with_view('purchase.track_po_line_template', - values={'line': line, 'product_qty': values['product_qty']}, - subtype_id=self.env.ref('mail.mt_note').id) - result = super(PurchaseOrderLine, self).write(values) - # Update expected 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']}) - if 'product_qty' in values: - self.filtered(lambda l: l.order_id.state == 'purchase')._create_or_update_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) @@ -652,7 +438,6 @@ class PurchaseOrderLine(models.Model): 'Product Image', related="product_id.image", help="Non-stored related field to allow portal user to see the image of the product he has ordered") product_type = fields.Selection(related='product_id.type', readonly=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) @@ -675,132 +460,29 @@ class PurchaseOrderLine(models.Model): 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) - orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint') - move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves') - - def _update_received_qty(self): - for line in self: - total = 0.0 - for move in line.move_ids: - if move.state == 'done': - if move.location_dest_id.usage == "supplier": - if move.to_refund: - total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) - else: - total += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) - line.qty_received = total + @api.model + def create(self, values): + line = super(PurchaseOrderLine, self).create(values) + if line.order_id.state == 'purchase': + msg = _("Extra line with %s ") % (line.product_id.display_name,) + line.order_id.message_post(body=msg) + return line @api.multi - def _create_or_update_picking(self): - for line in self: - if line.product_id.type in ('product', 'consu'): - # Prevent decreasing below received quantity - if float_compare(line.product_qty, line.qty_received, line.product_uom.rounding) < 0: - raise UserError(_('You cannot decrease the ordered quantity below the received quantity.\n' - 'Create a return first.')) - - if float_compare(line.product_qty, line.qty_invoiced, line.product_uom.rounding) == -1: - # If the quantity is now below the invoiced quantity, create an activity on the vendor bill - # inviting the user to create a refund. - activity = self.env['mail.activity'].sudo().create({ - 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, - 'note': _('The quantities on your purchase order indicate less than billed. You should ask for a refund. '), - 'res_id': line.invoice_lines[0].invoice_id.id, - 'res_model_id': self.env.ref('account.model_account_invoice').id, - }) - activity._onchange_activity_type_id() - - # If the user increased quantity of existing line or created a new line - pickings = line.order_id.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.location_dest_id.usage in ('internal', 'transit')) - picking = pickings and pickings[0] or False - if not picking: - res = line.order_id._prepare_picking() - picking = self.env['stock.picking'].create(res) - move_vals = line._prepare_stock_moves(picking) - for move_val in move_vals: - self.env['stock.move']\ - .create(move_val)\ - ._action_confirm()\ - ._action_assign() - - @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._convert( - price_unit, order.company_id.currency_id, self.company_id, self.date_order or fields.Date.today(), 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' and not x.location_dest_id.usage == "supplier"): - qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') - 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_ids': [(4, x) for x in self.move_dest_ids.ids], - '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, - '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, - } - diff_quantity = self.product_qty - qty - if float_compare(diff_quantity, 0.0, precision_rounding=self.product_uom.rounding) > 0: - quant_uom = self.product_id.uom_id - get_param = self.env['ir.config_parameter'].sudo().get_param - if self.product_uom.id != quant_uom.id and get_param('stock.propagate_uom') != '1': - product_qty = self.product_uom._compute_quantity(diff_quantity, quant_uom, rounding_method='HALF-UP') - template['product_uom'] = quant_uom.id - template['product_uom_qty'] = product_qty - else: - 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 + def write(self, values): + if 'product_qty' in values: + for line in self: + if line.order_id.state == 'purchase': + line.order_id.message_post_with_view('purchase.track_po_line_template', + values={'line': line, 'product_qty': values['product_qty']}, + subtype_id=self.env.ref('mail.mt_note').id) + return super(PurchaseOrderLine, self).write(values) @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,)) + raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') % (line.state,)) return super(PurchaseOrderLine, self).unlink() @api.model @@ -822,12 +504,6 @@ class PurchaseOrderLine(models.Model): else: return datetime.today() + relativedelta(days=seller.delay if seller else 0) - def _merge_in_existing_line(self, product_id, product_qty, product_uom, location_id, name, origin, values): - """ This function purpose is to be override with the purpose to forbide _run_buy method - to merge a new po line in an existing one. - """ - return True - @api.onchange('product_id') def onchange_product_id(self): result = {} diff --git a/addons/purchase/models/res_config_settings.py b/addons/purchase/models/res_config_settings.py index 3ded07dba83..46ad2744143 100644 --- a/addons/purchase/models/res_config_settings.py +++ b/addons/purchase/models/res_config_settings.py @@ -20,13 +20,10 @@ class ResConfigSettings(models.TransientModel): ], string="Bill Control", default_model="product.template", help="This default value is applied to any new product created. " "This can be changed in the product detail form.", default="receive") - module_purchase_requisition = fields.Boolean("Purchase Agreements") group_warning_purchase = fields.Boolean("Purchase Warnings", implied_group='purchase.group_warning_purchase') - module_stock_dropshipping = fields.Boolean("Dropshipping") group_manage_vendor_price = fields.Boolean("Vendor Pricelists", implied_group="purchase.group_manage_vendor_price") module_account_3way_match = fields.Boolean("3-way matching: purchases, receptions and bills") - is_installed_sale = fields.Boolean(string="Is the Sale Module Installed") po_lead = fields.Float(related='company_id.po_lead') use_po_lead = fields.Boolean( string="Security Lead Time for Purchase", @@ -39,14 +36,6 @@ class ResConfigSettings(models.TransientModel): if not self.use_po_lead: self.po_lead = 0.0 - @api.multi - def get_values(self): - res = super(ResConfigSettings, self).get_values() - res.update( - is_installed_sale=self.env['ir.module.module'].search([('name', '=', 'sale'), ('state', '=', 'installed')]).id, - ) - return res - def set_values(self): super(ResConfigSettings, self).set_values() self.po_lock = 'lock' if self.lock_confirmed_po else 'edit' diff --git a/addons/purchase/report/purchase_order_templates.xml b/addons/purchase/report/purchase_order_templates.xml index c374a72ff1b..4adf82c3bc4 100644 --- a/addons/purchase/report/purchase_order_templates.xml +++ b/addons/purchase/report/purchase_order_templates.xml @@ -6,18 +6,12 @@
-
+
Shipping address:
- -
- -
-
:

+

Request for Quotation #

Purchase Order #

Purchase Order Confirmation #

diff --git a/addons/purchase/report/purchase_quotation_templates.xml b/addons/purchase/report/purchase_quotation_templates.xml index 4a8d86edd85..b63329ccb5a 100644 --- a/addons/purchase/report/purchase_quotation_templates.xml +++ b/addons/purchase/report/purchase_quotation_templates.xml @@ -7,17 +7,12 @@
-
+
Shipping address:
-
- -
-
- diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py index 8ab87b64da1..f54f63b9035 100644 --- a/addons/purchase/report/purchase_report.py +++ b/addons/purchase/report/purchase_report.py @@ -24,7 +24,6 @@ class PurchaseReport(models.Model): ('cancel', 'Cancelled') ], 'Order Status', readonly=True) product_id = fields.Many2one('product.product', 'Product', readonly=True) - picking_type_id = fields.Many2one('stock.warehouse', 'Warehouse', readonly=True) partner_id = fields.Many2one('res.partner', 'Vendor', readonly=True) date_approve = fields.Date('Date Approved', readonly=True) product_uom = fields.Many2one('uom.uom', 'Reference Unit of Measure', required=True) @@ -88,8 +87,7 @@ class PurchaseReport(models.Model): partner.commercial_partner_id as commercial_partner_id, analytic_account.id as account_analytic_id, sum(p.weight * l.product_qty/u.factor*u2.factor) as weight, - sum(p.volume * l.product_qty/u.factor*u2.factor) as volume, - spt.warehouse_id as picking_type_id + sum(p.volume * l.product_qty/u.factor*u2.factor) as volume """ % self.env['res.currency']._select_companies_rates() return select_str @@ -108,7 +106,6 @@ class PurchaseReport(models.Model): cr.company_id = s.company_id and cr.date_start <= coalesce(s.date_order, now()) and (cr.date_end is null or cr.date_end > coalesce(s.date_order, now()))) - left join stock_picking_type spt on (spt.id=s.picking_type_id) """ return from_str @@ -138,7 +135,6 @@ class PurchaseReport(models.Model): u2.factor, partner.country_id, partner.commercial_partner_id, - analytic_account.id, - spt.warehouse_id + analytic_account.id """ return group_by_str diff --git a/addons/purchase/report/purchase_report_views.xml b/addons/purchase/report/purchase_report_views.xml index 89892f4a6ee..5b65a546b88 100644 --- a/addons/purchase/report/purchase_report_views.xml +++ b/addons/purchase/report/purchase_report_views.xml @@ -60,7 +60,6 @@ - @@ -91,7 +90,7 @@ Purchase Analysis allows you to easily check and analyse your company purchase history and performance. From this menu you can track your negotiation performance, the delivery performance of your vendors, etc. - diff --git a/addons/purchase/security/ir.model.access.csv b/addons/purchase/security/ir.model.access.csv index 3646eeccffe..e2c47c7dc41 100644 --- a/addons/purchase/security/ir.model.access.csv +++ b/addons/purchase/security/ir.model.access.csv @@ -1,25 +1,13 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_purchase_order,purchase.order,model_purchase_order,group_purchase_user,1,1,1,1 access_purchase_order_manager,purchase.order,model_purchase_order,group_purchase_manager,1,1,1,1 -access_purchase_order_stock_worker,purchase.order,model_purchase_order,stock.group_stock_user,1,0,0,0 access_purchase_order_invoicing_payments,purchase.order,model_purchase_order,account.group_account_invoice,1,1,0,0 access_purchase_order_portal,purchase.order.portal,purchase.model_purchase_order,base.group_portal,1,0,0,0 access_purchase_order_line,purchase.order.line user,model_purchase_order_line,group_purchase_user,1,1,1,1 access_purchase_order_line_manager,purchase.order.line,model_purchase_order_line,group_purchase_manager,1,1,1,1 -access_purchase_order_line_stock_worker,purchase.order.line,model_purchase_order_line,stock.group_stock_user,1,0,0,0 access_purchase_order_line_invoicing_payments,purchase.order.line,model_purchase_order_line,account.group_account_invoice,1,1,0,0 access_purchase_order_line_portal,purchase.order.line.portal,purchase.model_purchase_order_line,base.group_portal,1,0,0,0 -access_stock_location_purchase_user,stock.location,stock.model_stock_location,group_purchase_user,1,0,0,0 -access_stock_warehouse_purchase_user,stock.warehouse,stock.model_stock_warehouse,group_purchase_user,1,0,0,0 -access_stock_picking_purchase_user,stock.picking,stock.model_stock_picking,group_purchase_user,1,1,1,1 -access_stock_move_purchase_user,stock.move,stock.model_stock_move,group_purchase_user,1,1,1,0 access_account_tax_purchase_user,account.tax,account.model_account_tax,group_purchase_user,1,0,0,0 -access_report_purchase_order,purchase.report,model_purchase_report,group_purchase_manager,1,0,0,0 -access_report_purchase_order_user,purchase.report user,model_purchase_report,group_purchase_user,1,0,0,0 -access_stock_location_purchase_user_manager,stock.location,stock.model_stock_location,group_purchase_manager,1,0,0,0 -access_stock_warehouse_purchase_user_manager,stock.warehouse,stock.model_stock_warehouse,group_purchase_manager,1,0,0,0 -access_stock_picking_purchase_user_manager,stock.picking,stock.model_stock_picking,group_purchase_manager,1,1,1,1 -access_stock_move_purchase_user_manager,stock.move,stock.model_stock_move,group_purchase_manager,1,1,1,1 access_account_tax_purchase_user_manager,account.tax,account.model_account_tax,group_purchase_manager,1,0,0,0 access_product_product_purchase_user,product.product.purchase.user,product.model_product_product,group_purchase_user,1,0,0,0 access_product_product_purchase_manager,product.product purchase_manager,product.model_product_product,purchase.group_purchase_manager,1,1,1,1 @@ -47,9 +35,6 @@ access_product_group_res_partner_purchase_manager,res_partner group_purchase_man access_product_pricelist_item_purchase_manager,product.pricelist.item purchase_manager,product.model_product_pricelist_item,purchase.group_purchase_manager,1,1,1,1 access_account_account_purchase_manager,account.account purchase manager,account.model_account_account,purchase.group_purchase_manager,1,0,0,0 access_account_journal_purchase_manager,account.journal purchase manager,account.model_account_journal,purchase.group_purchase_manager,1,0,0,0 -access_stock_location_purchase_manager,stock.location purchase manager,stock.model_stock_location,purchase.group_purchase_manager,1,0,0,0 -access_stock_warehouse_orderpoint_manager,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,purchase.group_purchase_manager,1,0,0,0 -access_stock_warehouse_orderpoint_user,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,group_purchase_user,1,0,0,0 access_product_price_history_purchase_user,prices.history purchase user,product.model_product_price_history,purchase.group_purchase_user,1,0,0,0 access_product_price_history_purchase_manager,prices.history purchase manager,product.model_product_price_history,purchase.group_purchase_manager,1,1,1,1 access_purchase_bill_union,access_purchase_bill_union,model_purchase_bill_union,purchase.group_purchase_user,1,0,0,0 diff --git a/addons/purchase/security/purchase_security.xml b/addons/purchase/security/purchase_security.xml index b2d8e241ba7..5633528a273 100644 --- a/addons/purchase/security/purchase_security.xml +++ b/addons/purchase/security/purchase_security.xml @@ -43,13 +43,6 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - - Purchase Order Report multi-company - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - - Portal Purchase Orders diff --git a/addons/purchase/views/purchase_template.xml b/addons/purchase/views/purchase_template.xml index 71d77830d6c..9c5d5c5975c 100644 --- a/addons/purchase/views/purchase_template.xml +++ b/addons/purchase/views/purchase_template.xml @@ -15,31 +15,4 @@
- diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml index ba3d2f73e36..2ce4d2a19a7 100644 --- a/addons/purchase/views/purchase_views.xml +++ b/addons/purchase/views/purchase_views.xml @@ -42,10 +42,6 @@ - - - + + + + + + + + + + + + + + + + + + + + + purchase.order.line.form.inherit + purchase.order.line + + + + + + + + + + diff --git a/addons/purchase_stock/views/res_config_settings_views.xml b/addons/purchase_stock/views/res_config_settings_views.xml new file mode 100644 index 00000000000..2624b5c1a04 --- /dev/null +++ b/addons/purchase_stock/views/res_config_settings_views.xml @@ -0,0 +1,73 @@ + + + + + res.config.settings.view.form.inherit.purchase + res.config.settings + + + + + +

Logistics

+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+ + + + + + res.config.settings.view.form.inherit.purchase.stock + res.config.settings + + +
+
+
+ +
+
+
+
+
+
+
+ + diff --git a/addons/purchase_stock/views/stock_views.xml b/addons/purchase_stock/views/stock_views.xml index 4da956ce2e5..70439d1092a 100644 --- a/addons/purchase_stock/views/stock_views.xml +++ b/addons/purchase_stock/views/stock_views.xml @@ -29,4 +29,11 @@ tree,form [('purchase_id', '=', active_id)] + + + diff --git a/addons/stock_dropshipping/__manifest__.py b/addons/stock_dropshipping/__manifest__.py index 174df2b7ef2..ae1af48e2cc 100644 --- a/addons/stock_dropshipping/__manifest__.py +++ b/addons/stock_dropshipping/__manifest__.py @@ -21,7 +21,7 @@ internal transfer document is needed. """, 'website': 'https://www.odoo.com/page/warehouse', - 'depends': ['purchase', 'sale_stock'], + 'depends': ['purchase_stock', 'sale_stock'], 'data': ['data/stock_data.xml'], 'installable': True, 'auto_install': False, diff --git a/addons/stock_landed_costs/__manifest__.py b/addons/stock_landed_costs/__manifest__.py index e181f6d9bb4..aeee0e7ae24 100644 --- a/addons/stock_landed_costs/__manifest__.py +++ b/addons/stock_landed_costs/__manifest__.py @@ -11,7 +11,7 @@ Landed Costs Management This module allows you to easily add extra costs on pickings and decide the split of these costs among their stock moves in order to take them into account in your stock valuation. """, 'website': 'https://www.odoo.com/page/warehouse', - 'depends': ['stock_account', 'purchase'], + 'depends': ['stock_account', 'purchase_stock'], 'category': 'Warehouse', 'sequence': 16, 'demo': [ diff --git a/odoo/addons/test_main_flows/__manifest__.py b/odoo/addons/test_main_flows/__manifest__.py index f0c471a0566..cc88c62cf50 100644 --- a/odoo/addons/test_main_flows/__manifest__.py +++ b/odoo/addons/test_main_flows/__manifest__.py @@ -8,9 +8,9 @@ This module will test the main workflow of Odoo. It will install some main apps and will try to execute the most important actions. """, - 'depends': ['web_tour', 'crm', 'sale_timesheet', 'purchase', 'mrp', 'account'], + 'depends': ['web_tour', 'crm', 'sale_timesheet', 'purchase_stock', 'mrp', 'account'], 'data': [ 'views/templates.xml', - ], + ], 'installable': True, }