diff --git a/purchase_by_sale_history/__init__.py b/purchase_by_sale_history/__init__.py deleted file mode 100755 index 40272379..00000000 --- a/purchase_by_sale_history/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import wizard diff --git a/purchase_by_sale_history/__manifest__.py b/purchase_by_sale_history/__manifest__.py deleted file mode 100755 index 7a598046..00000000 --- a/purchase_by_sale_history/__manifest__.py +++ /dev/null @@ -1,23 +0,0 @@ -{ - 'name': 'Purchase by Sale History', - 'author': 'Hibou Corp. ', - 'version': '15.0.1.0.0', - 'license': 'LGPL-3', - 'category': 'Purchases', - 'sequence': 95, - 'summary': 'Fill Purchase Orders by Sales History', - 'description': """ -Adds wizard to Purchase Orders that will fill the purchase order with products based on sales history. - """, - 'website': 'https://hibou.io/', - 'depends': [ - 'sale_stock', - 'purchase', - ], - 'data': [ - 'security/ir.model.access.csv', - 'wizard/purchase_by_sale_history_views.xml', - ], - 'installable': True, - 'application': False, -} diff --git a/purchase_by_sale_history/i18n/es.po b/purchase_by_sale_history/i18n/es.po deleted file mode 100644 index e8a15083..00000000 --- a/purchase_by_sale_history/i18n/es.po +++ /dev/null @@ -1,132 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * purchase_by_sale_history -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 15.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-10-12 01:36+0000\n" -"PO-Revision-Date: 2021-10-12 01:36+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: purchase_by_sale_history -#: model_terms:ir.ui.view,arch_db:purchase_by_sale_history.purchase_sale_history_make_form -msgid "Cancel" -msgstr "Cancelar" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__create_uid -msgid "Created by" -msgstr "Creado por" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__create_date -msgid "Created on" -msgstr "Creado el" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__procure_days -msgid "Days to Procure" -msgstr "Días para procurar" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__display_name -msgid "Display Name" -msgstr "Nombre para mostrar" - -#. module: purchase_by_sale_history -#: model:ir.actions.act_window,name:purchase_by_sale_history.purchase_sale_history_make_action -#: model_terms:ir.ui.view,arch_db:purchase_by_sale_history.purchase_sale_history_make_form -msgid "Fill PO From Sales History" -msgstr "Llenar Pedido de Compra desde la Historia de Ventas" - -#. module: purchase_by_sale_history -#: model_terms:ir.ui.view,arch_db:purchase_by_sale_history.purchase_order_form_inherit -msgid "Fill by Sales" -msgstr "Llenar por Ventas" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,help:purchase_by_sale_history.field_purchase_sale_history_make__procure_days -msgid "" -"History will be computed as an average per day, and then multiplied by the " -"days you wish to procure for." -msgstr "La historia sera calculado como el promedio por dia, luego multiplicado por dias donde usted desea procurar" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__id -msgid "ID" -msgstr "ID" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make____last_update -msgid "Last Modified on" -msgstr "Última Modificación el" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__write_uid -msgid "Last Updated by" -msgstr "Última Actualización por" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__write_date -msgid "Last Updated on" -msgstr "Última Actualización el" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__product_count -msgid "Product Count" -msgstr "Recuento de Productos" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,help:purchase_by_sale_history.field_purchase_sale_history_make__product_count -msgid "Products on the PO or that the Vendor provides." -msgstr "Productos en el Pedido de Compras o que el vendedor provee" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__purchase_id -msgid "Purchase Order" -msgstr "Pedido de Compra" - -#. module: purchase_by_sale_history -#: model_terms:ir.ui.view,arch_db:purchase_by_sale_history.purchase_sale_history_make_form -msgid "Run" -msgstr "Ejecutar" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__history_days -msgid "Sales History Days" -msgstr "Historia de Ventas en Dias" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__history_end -msgid "Sales History End" -msgstr "Fin de la Historia de Ventas" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__history_start -msgid "Sales History Start" -msgstr "Comienzo de la Historia de Ventas" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,help:purchase_by_sale_history.field_purchase_sale_history_make__history_warehouse_ids -msgid "" -"Sales are calculated by these warehouses. Current Inventory is summed from " -"these warehouses. If it is left blank then all warehouses and inventory will" -" be considered." -msgstr "Las ventas son calculadas por estos almacenes. El inventario actual es sumado por estos almacenes. Si se deja vacio, entonces todos los almacenes y inventario serán considerados" - -#. module: purchase_by_sale_history -#: model:ir.model.fields,field_description:purchase_by_sale_history.field_purchase_sale_history_make__history_warehouse_ids -msgid "Warehouses" -msgstr "Almacenes" - -#. module: purchase_by_sale_history -#: model:ir.model,name:purchase_by_sale_history.model_purchase_sale_history_make -msgid "purchase.sale.history.make" -msgstr "purchase.sale.history.make" diff --git a/purchase_by_sale_history/security/ir.model.access.csv b/purchase_by_sale_history/security/ir.model.access.csv deleted file mode 100644 index fa0c6fbe..00000000 --- a/purchase_by_sale_history/security/ir.model.access.csv +++ /dev/null @@ -1,2 +0,0 @@ -"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -access_purchase_sale_history_make,access_purchase_sale_history_make,model_purchase_sale_history_make,base.group_user,1,1,1,0 \ No newline at end of file diff --git a/purchase_by_sale_history/tests/__init__.py b/purchase_by_sale_history/tests/__init__.py deleted file mode 100644 index 6cc4addf..00000000 --- a/purchase_by_sale_history/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import test_purchase_by_sale_history diff --git a/purchase_by_sale_history/tests/test_purchase_by_sale_history.py b/purchase_by_sale_history/tests/test_purchase_by_sale_history.py deleted file mode 100644 index 4b4c6d52..00000000 --- a/purchase_by_sale_history/tests/test_purchase_by_sale_history.py +++ /dev/null @@ -1,182 +0,0 @@ -from odoo import fields -from odoo.tests import common -from datetime import datetime, timedelta - - -class TestPurchaseBySaleHistory(common.TransactionCase): - - def test_00_wizard(self): - wh1 = self.env.ref('stock.warehouse0') - wh2 = self.env['stock.warehouse'].create({ - 'name': 'WH2', - 'code': 'twh2', - }) - - sale_partner = self.env.ref('base.res_partner_2') - purchase_partner = self.env['res.partner'].create({ - 'name': 'Purchase Partner', - }) - - product11 = self.env['product.product'].create({ - 'name': 'Product 1', - 'type': 'product', - }) - product_template1 = product11.product_tmpl_id - - color = self.env.ref('product.product_attribute_2') - attribute_line = self.env['product.template.attribute.line'].create({ - 'product_tmpl_id': product_template1.id, - 'attribute_id': color.id, - 'value_ids': [(6, 0, [color.value_ids[0].id])], - }) - attribute_line.write({'value_ids': [(4, color.value_ids[1].id)]}) - self.assertEqual(len(product_template1.product_variant_ids), 2) - product12 = product_template1.product_variant_ids.filtered(lambda p: p != product11) - self.assertTrue(product12) - - product2 = self.env['product.product'].create({ - 'name': 'Product 2', - 'type': 'product', - }) - - po1 = self.env['purchase.order'].create({ - 'partner_id': purchase_partner.id, - }) - - # Create initial wizard, it won't apply to any products because the PO is empty, and the vendor - # doesn't supply any products yet. - wiz = self.env['purchase.sale.history.make'].create({ - 'purchase_id': po1.id, - }) - - self.assertEqual(wiz.product_count, 0.0, 'There shouldn\'t be any products for this vendor yet.') - - # Assign vendor to products created earlier. - self.env['product.supplierinfo'].create({ - 'name': purchase_partner.id, - 'product_tmpl_id': product11.product_tmpl_id.id, - 'product_id': product11.id, - }) - self.env['product.supplierinfo'].create({ - 'name': purchase_partner.id, - 'product_tmpl_id': product2.product_tmpl_id.id, - }) - # New wizard picks up the correct number of products supplied by this vendor. - wiz = self.env['purchase.sale.history.make'].create({ - 'purchase_id': po1.id, - }) - self.assertEqual(wiz.product_count, 2) - - # Make some sales history... - sale_date = fields.Datetime.to_string(datetime.now() - timedelta(days=30)) - self.env['sale.order'].create({ - 'partner_id': sale_partner.id, - 'date_order': sale_date, - 'picking_policy': 'direct', - 'order_line': [ - (0, 0, {'product_id': product11.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product12.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product2.id, 'product_uom_qty': 3.0}), - ], - }).action_confirm() - - days = 60 - history_start = fields.Date.to_string(datetime.now() - timedelta(days=days)) - history_end = fields.Date.today() - wiz.write({ - 'history_start': history_start, - 'history_end': history_end, - 'procure_days': days, - 'history_warehouse_ids': [(4, wh1.id, None)], - }) - self.assertEqual(wiz.history_days, days) - wiz.action_confirm() - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 3.0 + 3.0) # 3 from Sales History, 3 from Demand (from the sale) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product12).product_qty, 0.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product2).product_qty, 3.0 + 3.0) - - # Make additional sales history... - sale_date = fields.Datetime.to_string(datetime.now() - timedelta(days=15)) - self.env['sale.order'].create({ - 'partner_id': sale_partner.id, - 'date_order': sale_date, - 'picking_policy': 'direct', - 'order_line': [ - (0, 0, {'product_id': product11.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product12.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product2.id, 'product_uom_qty': 3.0}), - ], - }).action_confirm() - - wiz.action_confirm() - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 6.0 + 6.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product12).product_qty, 0.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product2).product_qty, 6.0 + 6.0) - - # Make additional sales history in other warehouse - sale_date = fields.Datetime.to_string(datetime.now() - timedelta(days=15)) - self.env['sale.order'].create({ - 'partner_id': sale_partner.id, - 'date_order': sale_date, - 'picking_policy': 'direct', - 'warehouse_id': wh2.id, - 'order_line': [ - (0, 0, {'product_id': product11.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product12.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product2.id, 'product_uom_qty': 3.0}), - ], - }).action_confirm() - - wiz.action_confirm() - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 6.0 + 6.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product12).product_qty, 0.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product2).product_qty, 6.0 + 6.0) - - # Make additional sales history that should NOT be counted... - sale_date = fields.Datetime.to_string(datetime.now() - timedelta(days=61)) - self.env['sale.order'].create({ - 'partner_id': sale_partner.id, - 'date_order': sale_date, - 'picking_policy': 'direct', - 'order_line': [ - (0, 0, {'product_id': product11.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product12.id, 'product_uom_qty': 3.0}), - (0, 0, {'product_id': product2.id, 'product_uom_qty': 3.0}), - ], - }).action_confirm() - - wiz.action_confirm() - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 6.0 + 9.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product12).product_qty, 0.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product2).product_qty, 6.0 + 9.0) - - # Test that the wizard will only use the existing PO line products now that we have lines. - po1.order_line.filtered(lambda l: l.product_id == product2).unlink() - wiz.action_confirm() - # This test is failing due to the unlink call above in version 13.0. - # During debugging, we looked closely into the query call made in the _sale_history method - # and confirmed that the same query was producing different results each time it's run. - # We intend to fix this in version 14.0 - # Still broken in 15, but observed to work correctly in UI. - # self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 6.0 + 9.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product12).product_qty, 0.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product2).product_qty, 0.0) - - # Plan for 1/2 the days of inventory - wiz.procure_days = days / 2.0 - wiz.action_confirm() - # similarly above - # self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 3.0 + 9.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 9.0) - - # Cause Inventory on existing product to make sure we don't order it. - adjust_quant = self.env['stock.quant'].with_context(inventory_mode=True).create({ - 'product_id': product11.id, - 'location_id': wh1.lot_stock_id.id, - 'inventory_quantity': 100.0, - }).action_apply_inventory() - - wiz.action_confirm() - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product11).product_qty, 0.0) # Because we have so much in stock now. - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product12).product_qty, 0.0) - self.assertEqual(po1.order_line.filtered(lambda l: l.product_id == product2).product_qty, 0.0) diff --git a/purchase_by_sale_history/wizard/__init__.py b/purchase_by_sale_history/wizard/__init__.py deleted file mode 100644 index bbdff949..00000000 --- a/purchase_by_sale_history/wizard/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import purchase_by_sale_history diff --git a/purchase_by_sale_history/wizard/purchase_by_sale_history.py b/purchase_by_sale_history/wizard/purchase_by_sale_history.py deleted file mode 100644 index efb86018..00000000 --- a/purchase_by_sale_history/wizard/purchase_by_sale_history.py +++ /dev/null @@ -1,135 +0,0 @@ -from odoo import api, fields, models -from math import ceil -from datetime import timedelta - - -class PurchaseBySaleHistory(models.TransientModel): - _name = 'purchase.sale.history.make' - - purchase_id = fields.Many2one('purchase.order', string='Purchase Order') - history_start = fields.Date(string='Sales History Start', default=lambda o: fields.Date.from_string(fields.Date.today()) - timedelta(days=30)) - history_end = fields.Date(string='Sales History End', default=fields.Date.today) - history_days = fields.Integer(string='Sales History Days', compute='_compute_history_days') - procure_days = fields.Integer(string='Days to Procure', - default=30, - help='History will be computed as an average per day, ' - 'and then multiplied by the days you wish to procure for.') - product_count = fields.Integer(string='Product Count', compute='_compute_product_count', - help='Products on the PO or that the Vendor provides.') - history_warehouse_ids = fields.Many2many('stock.warehouse', string='Warehouses', - help='Sales are calculated by these warehouses. ' - 'Current Inventory is summed from these warehouses. ' - 'If it is left blank then all warehouses and inventory ' - 'will be considered.') - - @api.depends('history_start', 'history_end') - def _compute_history_days(self): - for wiz in self: - if not all((wiz.history_end, wiz.history_start)): - wiz.history_days = 0 - else: - delta = fields.Date.from_string(wiz.history_end) - fields.Date.from_string(wiz.history_start) - wiz.history_days = delta.days - - @api.depends('purchase_id', 'purchase_id.order_line', 'purchase_id.partner_id') - def _compute_product_count(self): - for wiz in self: - if wiz.purchase_id.order_line: - wiz.product_count = len(set(wiz.purchase_id.order_line.mapped('product_id.id'))) - elif wiz.purchase_id.partner_id: - self.env.cr.execute("""SELECT COUNT(DISTINCT(psi.product_id)) + COUNT(DISTINCT(p.id)) - FROM product_supplierinfo psi - LEFT JOIN product_product p ON p.product_tmpl_id = psi.product_tmpl_id AND psi.product_id IS NULL - WHERE psi.name = %d;""" - % (wiz.purchase_id.partner_id.id, )) - wiz.product_count = self.env.cr.fetchall()[0][0] - - def _history_product_ids(self): - if self.purchase_id.order_line: - return self.purchase_id.order_line.mapped('product_id.id') - - self.env.cr.execute("""SELECT DISTINCT(COALESCE(psi.product_id, p.id)) - FROM product_supplierinfo psi - LEFT JOIN product_product p ON p.product_tmpl_id = psi.product_tmpl_id AND psi.product_id IS NULL - WHERE psi.name = %d;""" - % (self.purchase_id.partner_id.id, )) - rows = self.env.cr.fetchall() - return [r[0] for r in rows if r[0]] - - def _sale_history(self, product_ids): - p_ids = tuple(product_ids) - if self.history_warehouse_ids: - wh_ids = tuple(self.history_warehouse_ids.ids) - self.env.cr.execute("""SELECT product_id, sum(product_uom_qty) - FROM sale_report - WHERE date BETWEEN %s AND %s AND product_id IN %s AND warehouse_id IN %s - GROUP BY 1""", - (self.history_start, self.history_end, p_ids, wh_ids)) - else: - self.env.cr.execute("""SELECT product_id, sum(product_uom_qty) - FROM sale_report - WHERE date BETWEEN %s AND %s AND product_id IN %s - GROUP BY 1""", - (self.history_start, self.history_end, p_ids)) - return self.env.cr.fetchall() - - def _apply_history_product(self, product, history): - qty = ceil(history['sold_qty'] * self.procure_days / self.history_days) - history['buy_qty'] = max((0.0, qty - product.virtual_available)) - - def _convert_to_purchase_line_qty(self, line, qty): - # Skip calculation if they are the same UOM - if line.product_id.uom_id != line.product_uom: - return line.product_id.uom_id._compute_quantity(qty, line.product_uom) - return qty - - def _apply_history(self, history, product_ids): - line_model = self.env['purchase.order.line'] - updated_lines = line_model.browse() - - # Collect stock to consider against the sales demand. - product_model = self.env['product.product'] - if self.history_warehouse_ids: - product_model = self.env['product.product']\ - .with_context({'location': [wh.lot_stock_id.id for wh in self.history_warehouse_ids]}) - products = product_model.browse(product_ids) - - #product_available_stock = {p.id: p.virtual_available for p in products} - history_dict = {pid: {'sold_qty': sold_qty} for pid, sold_qty in history} - for p in products: - if p.id not in history_dict: - history_dict[p.id] = {'sold_qty': 0.0} - self._apply_history_product(p, history_dict[p.id]) - - for pid, history in history_dict.items(): - qty = history.get('buy_qty', 0.0) - - # Find line that already exists on PO - line = self.purchase_id.order_line.filtered(lambda l: l.product_id.id == pid) - if line: - line.write({'product_qty': self._convert_to_purchase_line_qty(line, qty)}) - line._onchange_quantity() - else: - # Create new PO line - line = line_model.new({ - 'order_id': self.purchase_id.id, - 'product_id': pid, - 'product_qty': qty, - }) - line.onchange_product_id() - line_vals = line._convert_to_write(line._cache) - line_vals['product_qty'] = self._convert_to_purchase_line_qty(line, qty) - line = line_model.create(line_vals) - updated_lines += line - - # Lines not touched should now not be ordered. - other_lines = self.purchase_id.order_line - updated_lines - other_lines.write({'product_qty': 0.0}) - for line in other_lines: - line._onchange_quantity() - - def action_confirm(self): - self.ensure_one() - history_product_ids = self._history_product_ids() - history = self._sale_history(history_product_ids) - self._apply_history(history, history_product_ids) diff --git a/purchase_by_sale_history/wizard/purchase_by_sale_history_views.xml b/purchase_by_sale_history/wizard/purchase_by_sale_history_views.xml deleted file mode 100644 index f3557826..00000000 --- a/purchase_by_sale_history/wizard/purchase_by_sale_history_views.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - purchase.sale.history.make.form - purchase.sale.history.make - -
- - - - - - - - - - - - - - - - -
-
-
-
-
- - - Fill PO From Sales History - purchase.sale.history.make - form - - new - {'default_purchase_id': active_id} - - - - - purchase.order.form.inherit - purchase.order - - - -