From 522e5299f1d5da1e8fdfe17c374bdafbf640d41c Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 20 Mar 2015 17:56:57 +0100 Subject: [PATCH] Add module procurement_suggest --- procurement_suggest/__init__.py | 24 ++ procurement_suggest/__openerp__.py | 49 ++++ procurement_suggest/stock.py | 29 +++ procurement_suggest/stock_view.xml | 48 ++++ procurement_suggest/wizard/__init__.py | 23 ++ .../wizard/procurement_suggest.py | 221 ++++++++++++++++++ .../wizard/procurement_suggest_view.xml | 109 +++++++++ 7 files changed, 503 insertions(+) create mode 100644 procurement_suggest/__init__.py create mode 100644 procurement_suggest/__openerp__.py create mode 100644 procurement_suggest/stock.py create mode 100644 procurement_suggest/stock_view.xml create mode 100644 procurement_suggest/wizard/__init__.py create mode 100644 procurement_suggest/wizard/procurement_suggest.py create mode 100644 procurement_suggest/wizard/procurement_suggest_view.xml diff --git a/procurement_suggest/__init__.py b/procurement_suggest/__init__.py new file mode 100644 index 0000000..80fe409 --- /dev/null +++ b/procurement_suggest/__init__.py @@ -0,0 +1,24 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Procurement Suggest module for Odoo +# Copyright (C) 2015 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from . import stock +from . import wizard diff --git a/procurement_suggest/__openerp__.py b/procurement_suggest/__openerp__.py new file mode 100644 index 0000000..fa4dea0 --- /dev/null +++ b/procurement_suggest/__openerp__.py @@ -0,0 +1,49 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Procurement Suggest module for Odoo +# Copyright (C) 2015 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'Procurement Suggest', + 'version': '0.1', + 'category': 'Procurements', + 'license': 'AGPL-3', + 'summary': 'Suggest POs/MOs from special suggest orderpoints', + 'description': """ +Procurement Suggest +=================== + +TODO + +Roadmap : split the module in 2 and move the purchase-specific part in a module +that has a dependancy on this module and purchase. This module will then not have a depandancy on purchase any more. + +This module has been written by Alexis de Lattre from Akretion . + """, + 'author': 'Akretion', + 'website': 'http://www.akretion.com', + 'depends': ['stock', 'purchase'], + 'data': [ + 'stock_view.xml', + 'wizard/procurement_suggest_view.xml', + ], + 'installable': True, +} diff --git a/procurement_suggest/stock.py b/procurement_suggest/stock.py new file mode 100644 index 0000000..fe2e41f --- /dev/null +++ b/procurement_suggest/stock.py @@ -0,0 +1,29 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Procurement Suggest module for Odoo +# Copyright (C) 2015 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import models, fields + + +class StockWarehouseOrderpoint(models.Model): + _inherit = 'stock.warehouse.orderpoint' + + suggest = fields.Boolean(string='Suggest', default=True) diff --git a/procurement_suggest/stock_view.xml b/procurement_suggest/stock_view.xml new file mode 100644 index 0000000..d9ccc79 --- /dev/null +++ b/procurement_suggest/stock_view.xml @@ -0,0 +1,48 @@ + + + + + + + + + procurement_suggest.orderpoint.form + stock.warehouse.orderpoint + + + + + + + + + + procurement_suggest.orderpoint.tree + stock.warehouse.orderpoint + + + + + + + + + + procurement_suggest.orderpoint.search + stock.warehouse.orderpoint + + + + + + + + + + + + diff --git a/procurement_suggest/wizard/__init__.py b/procurement_suggest/wizard/__init__.py new file mode 100644 index 0000000..3d2b6de --- /dev/null +++ b/procurement_suggest/wizard/__init__.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Procurement Suggest module for Odoo +# Copyright (C) 2015 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from . import procurement_suggest diff --git a/procurement_suggest/wizard/procurement_suggest.py b/procurement_suggest/wizard/procurement_suggest.py new file mode 100644 index 0000000..ecc4c32 --- /dev/null +++ b/procurement_suggest/wizard/procurement_suggest.py @@ -0,0 +1,221 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Procurement Suggest module for Odoo +# Copyright (C) 2015 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import models, fields, api, _ +import openerp.addons.decimal_precision as dp + + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + + date_order = fields.Datetime( + related='order_id.date_order', string="PO Date", store=True) + + +class ProcurementSuggestionGenerate(models.TransientModel): + _name = 'procurement.suggest.generate' + _description = 'Start to generate the procurement suggestions' + + categ_id = fields.Many2one( + 'product.category', string='Product Category') + seller_id = fields.Many2one( + 'res.partner', string='Supplier', + domain=[('supplier', '=', True), ('is_company', '=', True)]) + location_id = fields.Many2one( + 'stock.location', string='Stock Location', required=True, + default=lambda self: self.env.ref('stock.stock_location_stock')) + + @api.multi + def run(self): + op_domain = [ + ('suggest', '=', True), + ('company_id', '=', self.env.user.company_id.id), + ('location_id', 'child_of', self.location_id.id), + ] + if self.categ_id or self.seller_id: + product_domain = [] + if self.categ_id: + product_domain.append( + ('categ_id', 'child_of', self.categ_id.id)) + if self.seller_id: + product_domain.append( + ('seller_id', '=', self.seller_id.id)) + products = self.env['product.product'].search(product_domain) + op_domain.append(('product_id', 'in', products.ids)) + self = self.with_context(location_id=self.location_id.id) + ops = self.env['stock.warehouse.orderpoint'].search(op_domain) + pso = self.env['procurement.suggest'] + polo = self.env['purchase.order.line'] + p_suggest_lines = [] + lines = {} # key = product_id ; value = {'min_qty', ...} + for op in ops: + if op.product_id.virtual_available < op.product_min_qty: + if op.product_id.id in lines: + raise Warning( + _("There are 2 orderpoints (%s and %s) for the same " + "product on stock location %s or its " + "children.") % ( + lines[op.product_id.id]['orderpoint'].name, + op.name, + self.location_id.complete_name)) + porderline_id = False + if op.product_id.seller_id: + porderlines = polo.search([ + ('state', 'not in', ('draft', 'cancel')), + ('product_id', '=', op.product_id.id)], + order='date_order desc', limit=1) + porderline_id = porderlines and porderlines[0].id or False + + p_suggest_lines.append({ + 'product_id': op.product_id.id, + 'seller_id': op.product_id.seller_id.id or False, + 'qty_available': op.product_id.qty_available, + 'incoming_qty': op.product_id.incoming_qty, + 'outgoing_qty': op.product_id.outgoing_qty, + 'orderpoint_id': op.id, + 'last_po_line_id': porderline_id, + }) + + p_suggest_lines_sorted = sorted( + p_suggest_lines, key=lambda to_sort: to_sort['seller_id']) + if p_suggest_lines_sorted: + p_suggest_ids = [] + for p_suggest_line in p_suggest_lines_sorted: + p_suggest = pso.create(p_suggest_line) + p_suggest_ids.append(p_suggest.id) + action = self.env['ir.actions.act_window'].for_xml_id( + 'procurement_suggest', 'procurement_suggest_action') + action.update({ + 'target': 'current', + 'domain': [('id', 'in', p_suggest_ids)], + }) + print "action=", action + return action + else: + return True + + +class ProcurementSuggest(models.TransientModel): + _name = 'procurement.suggest' + _description = 'Procurement Suggestions' + _rec_name = 'product_id' + + product_id = fields.Many2one( + 'product.product', string='Product', required=True, readonly=True) + seller_id = fields.Many2one('res.partner', string='Supplier', readonly=True) + qty_available = fields.Float( + string='Quantity On Hand', readonly=True, + digits=dp.get_precision('Product Unit of Measure')) + incoming_qty = fields.Float( + string='Incoming Quantity', readonly=True, + digits=dp.get_precision('Product Unit of Measure')) + outgoing_qty = fields.Float( + string='Outgoing Quantity', readonly=True, + digits=dp.get_precision('Product Unit of Measure')) + last_po_line_id = fields.Many2one( + 'purchase.order.line', string='Last Purchase Order Line', + readonly=True) + last_po_date = fields.Datetime( + related='last_po_line_id.order_id.date_order', + string='Date of the last PO', readonly=True) + last_po_qty = fields.Float( + related='last_po_line_id.product_qty', readonly=True, + string='Quantity of the Last Order') + orderpoint_id = fields.Many2one( + 'stock.warehouse.orderpoint', string='Re-ordering Rule', + readonly=True) + min_qty = fields.Float( + string="Min Quantity", readonly=True, + related='orderpoint_id.product_min_qty', + digits=dp.get_precision('Product Unit of Measure')) + qty_to_order = fields.Float( + string='Quantity to Order', + digits=dp.get_precision('Product Unit of Measure')) + +class ProcurementSuggestPoCreate(models.TransientModel): + _name = 'procurement.suggest.po.create' + _description = 'ProcurementSuggestPoCreate' + + @api.model + def _prepare_purchase_order_vals(self, partner, po_lines): + polo = self.pool['purchase.order.line'] + ponull = self.env['purchase.order'].browse(False) + po_vals = {'partner_id': partner.id} + partner_change_dict = ponull.onchange_partner_id(partner.id) + po_vals.update(partner_change_dict['value']) + picking_type_id = self.env['purchase.order']._get_picking_in() + picking_type_dict = ponull.onchange_picking_type_id(picking_type_id) + po_vals.update(picking_type_dict['value']) + order_lines = [] + for product, qty_to_order in po_lines: + product_change_res = polo.onchange_product_id( + self._cr, self._uid, [], + partner.property_product_pricelist_purchase.id, + product.id, qty_to_order, False, partner.id, + fiscal_position_id=partner.property_account_position.id, + context=self.env.context) + product_change_vals = product_change_res['value'] + taxes_id_vals = [] + if product_change_vals.get('taxes_id'): + for tax_id in product_change_vals['taxes_id']: + taxes_id_vals.append((4, tax_id)) + product_change_vals['taxes_id'] = taxes_id_vals + order_lines.append( + [0, 0, dict(product_change_vals, product_id=product.id)]) + po_vals['order_line'] = order_lines + return po_vals + + @api.multi + def create_po(self): + self.ensure_one() + # group by supplier + po_to_create = {} # key = seller_id, value = [(product, qty)] + for line in self.env['procurement.suggest'].browse(self.env.context.get('active_ids')): + if not line.qty_to_order: + continue + if line.seller_id in po_to_create: + po_to_create[line.seller_id].append( + (line.product_id, line.qty_to_order)) + else: + po_to_create[line.seller_id] = [ + (line.product_id, line.qty_to_order)] + new_po_ids = [] + for seller, po_lines in po_to_create.iteritems(): + po_vals = self._prepare_purchase_order_vals( + seller, po_lines) + new_po = self.env['purchase.order'].create(po_vals) + new_po_ids.append(new_po.id) + + if not new_po_ids: + raise Warning(_('No purchase orders created')) + action = self.env['ir.actions.act_window'].for_xml_id( + 'purchase', 'purchase_rfq') + action.update({ + 'nodestroy': False, + 'target': 'current', + 'domain': [('id', 'in', new_po_ids)], + }) + print "action=", action + return action + + + diff --git a/procurement_suggest/wizard/procurement_suggest_view.xml b/procurement_suggest/wizard/procurement_suggest_view.xml new file mode 100644 index 0000000..a00dce4 --- /dev/null +++ b/procurement_suggest/wizard/procurement_suggest_view.xml @@ -0,0 +1,109 @@ + + + + + + + + + procurement_suggest_generate.form + procurement.suggest.generate + +
+ + + + + +
+
+
+
+
+ + + Procurement Suggestions + procurement.suggest.generate + form + new + + + + + + procurement_suggest.tree + procurement.suggest + + + + + + + + + + + + + + + + + procurement_suggest.search + procurement.suggest + + + + + + + + + + + + + + Procurement Suggestions + procurement.suggest + tree + new + + + + procurement_suggest_po_create.form + procurement.suggest.po.create + +
+

+ This wizard will create Purchase Orders. +

+
+
+
+
+
+ + + + +
+