From 4e58913059c882beef04184c255f4a02a25cab20 Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Mon, 27 Apr 2020 15:37:13 +0200 Subject: [PATCH] [REF] cpo: refactor [REF] cpo: blacken wip --- compute_purchase_order/__manifest__.py | 7 +- .../models/computed_purchase_order.py | 303 +++++++----------- .../models/computed_purchase_order_line.py | 256 +++++++-------- .../models/product_template.py | 26 +- .../models/purchase_order.py | 42 ++- compute_purchase_order/tests/__init__.py | 1 + compute_purchase_order/tests/test_cpo.py | 84 +++++ .../views/computed_purchase_order.xml | 211 ++++++------ 8 files changed, 489 insertions(+), 441 deletions(-) create mode 100644 compute_purchase_order/tests/__init__.py create mode 100644 compute_purchase_order/tests/test_cpo.py diff --git a/compute_purchase_order/__manifest__.py b/compute_purchase_order/__manifest__.py index e944a98..8a9de1e 100644 --- a/compute_purchase_order/__manifest__.py +++ b/compute_purchase_order/__manifest__.py @@ -10,12 +10,7 @@ "author": "Coop IT Easy", "website": "https://github.com/coopiteasy/procurement-addons", "license": "AGPL-3", - "depends": [ - "product", - "purchase", - "stock", - "beesdoo_stock_coverage", - ], # todo simplify + "depends": ["purchase", "beesdoo_stock_coverage"], "data": [ "security/ir.model.access.csv", "views/computed_purchase_order.xml", diff --git a/compute_purchase_order/models/computed_purchase_order.py b/compute_purchase_order/models/computed_purchase_order.py index 9df5623..9398e0d 100644 --- a/compute_purchase_order/models/computed_purchase_order.py +++ b/compute_purchase_order/models/computed_purchase_order.py @@ -1,228 +1,167 @@ -from odoo import models, fields, api, SUPERUSER_ID +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# Vincent Van Rossem +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models, fields, api, _ from odoo.exceptions import ValidationError class ComputedPurchaseOrder(models.Model): - _description = 'Computed Purchase Order' - _name = 'computed.purchase.order' - _order = 'id desc' - - name = fields.Char( - string='CPO Reference', - size=64, - default='New') + _description = "Computed Purchase Order" + _name = "computed.purchase.order" + _order = "id desc" + name = fields.Char(string="CPO Reference", default=_("New")) order_date = fields.Datetime( - string='Purchase Order Date', + string="Purchase Order Date", default=fields.Datetime.now, - help="Depicts the date where the Quotation should be validated and converted into a purchase order.") # noqa - + help="Date at which the Quotation should be validated and " + "converted into a purchase order.", + ) date_planned = fields.Datetime( - string='Date Planned' + string="Date Planned", + default=fields.Datetime.now, # default=lambda _: fields.Datetime.now() ) - supplier_id = fields.Many2one( - 'res.partner', - string='Supplier', + comodel_name="res.partner", + string="Supplier", readonly=True, - help="Supplier of the purchase order.") - - order_line_ids = fields.One2many( - 'computed.purchase.order.line', - 'computed_purchase_order_id', - string='Order Lines', + help="Supplier of the purchase order.", + ) + cpo_line_ids = fields.One2many( + comodel_name="computed.purchase.order.line", + inverse_name="cpo_id", + string="Order Lines", ) - total_amount = fields.Float( - string='Total Amount (w/o VAT)', - compute='_compute_cpo_total', - store=True, + string="Total Amount (w/o VAT)", compute="_compute_cpo_total" ) - generated_purchase_order_ids = fields.One2many( - 'purchase.order', - 'original_cpo_id', - string='Generated Purchase Orders', + comodel_name="purchase.order", + inverse_name="original_cpo_id", + string="Generated Purchase Orders", ) - generated_po_count = fields.Integer( - string='Generated Purchase Order count', - compute='_compute_generated_po_count', + string="Generated Purchase Order count", + compute="_compute_generated_po_count", ) + @api.multi + @api.depends("cpo_line_ids", "cpo_line_ids.purchase_quantity") + def _compute_cpo_total(self): + for cpo in self: + total_amount = sum(cpol.subtotal for cpol in cpo.cpo_line_ids) + cpo.total_amount = total_amount + @api.model - def default_get(self, fields_list): - record = super(ComputedPurchaseOrder, self).default_get(fields_list) - - record['date_planned'] = self._get_default_date_planned() - record['supplier_id'] = self._get_selected_supplier_id() - record['order_line_ids'] = self._create_order_lines() - record['name'] = self._compute_default_name() - - return record - - def _get_default_date_planned(self): - return fields.Datetime.now() - - def _get_selected_supplier_id(self): - """ - Calcule le vendeur associé qui a la date de début la plus récente et - plus petite qu’aujourd’hui pour chaque article sélectionné. - Will raise an error if more than two sellers are set - """ - if 'active_ids' not in self.env.context: - return False - - product_ids = self.env.context['active_ids'] - products = self.env['product.template'].browse(product_ids) - - suppliers = set() - for product in products: - main_supplier_id = product.main_supplier_id.id - suppliers.add(main_supplier_id) - - if len(suppliers) == 0: - raise ValidationError('No supplier is set for selected articles.') + def _get_selected_supplier(self): + product_ids = self.env.context.get("active_ids", []) + products = self.env["product.template"].browse(product_ids) + suppliers = products.mapped("main_supplier_id") + + if not suppliers: + raise ValidationError("No supplier is set for selected articles.") elif len(suppliers) == 1: - return suppliers.pop() + return suppliers else: raise ValidationError( - 'You must select article from a single supplier.') - - def _create_order_lines(self): - product_tmpl_ids = self._get_selected_products() - cpol_ids = [] - OrderLine = self.env['computed.purchase.order.line'] - for product_id in product_tmpl_ids: - cpol = OrderLine.create( - {'computed_purchase_order_id': self.id, - 'product_template_id': product_id, - } + "You must select article from a single supplier." ) - # should ideally be set in cpol defaults - cpol.purchase_quantity = cpol.minimum_purchase_qty - cpol_ids.append(cpol.id) - return cpol_ids - - def _compute_default_name(self): - supplier_id = self._get_selected_supplier_id() - if supplier_id: - supplier_name = ( - self.env['res.partner'] - .browse(supplier_id) - .name) - - name = 'CPO {} {}'.format( - supplier_name, - fields.Date.today()) - else: - name = 'New' - return name - - def _get_selected_products(self): - if 'active_ids' in self.env.context: - return self.env.context['active_ids'] - else: - return [] - - @api.multi - def _compute_generated_po_count(self): - for cpo in self: - cpo.generated_po_count = len(cpo.generated_purchase_order_ids) - @api.multi - def get_generated_po_action(self): - self.ensure_one() + @api.model + def generate_cpo(self): + order_line_obj = self.env["computed.purchase.order.line"] + product_ids = self.env.context.get("active_ids", []) + + supplier = self._get_selected_supplier() + name = "CPO {} {}".format(supplier.name, fields.Date.today()) + cpo = self.create({"name": name, "supplier_id": supplier.id}) + + for product_id in product_ids: + supplierinfo = self.env["product.supplierinfo"].search( + [ + ("product_tmpl_id", "=", product_id), + ("name", "=", supplier.id), + ] + ) + min_qty = supplierinfo.min_qty if supplierinfo else 0 + order_line_obj.create( + { + "cpo_id": cpo.id, + "product_template_id": product_id, + "purchase_quantity": min_qty, + } + ) action = { - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.order', - 'view_mode': 'tree,form,kanban', - 'target': 'current', - 'domain': [('id', 'in', self.generated_purchase_order_ids.ids)], + "type": "ir.actions.act_window", + "res_model": "computed.purchase.order", + "views": [[False, "form"]], + # "view_mode": "form,tree", + # "view_type": "form", + # "target": "current", + "res_id": cpo.id, } return action - @api.depends('order_line_ids.subtotal') - @api.multi - def _compute_cpo_total(self): - for cpo in self: - total_amount = sum(cpol.subtotal for cpol in cpo.order_line_ids) - cpo.total_amount = total_amount - @api.multi def create_purchase_order(self): self.ensure_one() - if sum(self.order_line_ids.mapped('purchase_quantity')) == 0: - raise ValidationError('You need at least a product to generate ' - 'a Purchase Order') + if sum(self.cpo_line_ids.mapped("purchase_quantity")) == 0: + raise ValidationError( + "You need at least a product to generate " "a Purchase Order" + ) - PurchaseOrder = self.env['purchase.order'] - PurchaseOrderLine = self.env['purchase.order.line'] + purchase_order = self.env["purchase.order"].create( + { + "date_order": self.order_date, + "partner_id": self.supplier_id.id, + "date_planned": self.date_planned, + } + ) - po_values = { - 'name': 'New', - 'date_order': self.order_date, - 'partner_id': self.supplier_id.id, - 'date_planned': self.date_planned, - } - purchase_order = PurchaseOrder.create(po_values) - - for cpo_line in self.order_line_ids: + for cpo_line in self.cpo_line_ids: if cpo_line.purchase_quantity > 0: - if cpo_line.supplierinfo_id.product_code: - pol_name = '[%s] %s' % (cpo_line.supplierinfo_id.product_code, cpo_line.name) - else: - pol_name = cpo_line.name - pol_values = { - 'name': pol_name, - 'product_id': cpo_line.get_default_product_product().id, - 'product_qty': cpo_line.purchase_quantity, - 'price_unit': cpo_line.product_price, - 'product_uom': cpo_line.uom_po_id.id, - 'order_id': purchase_order.id, - 'date_planned': self.date_planned, - } - pol = PurchaseOrderLine.create(pol_values) + pol = self.env["purchase.order.line"].create( + { + "name": cpo_line.name, + "product_id": cpo_line.product_template_id.product_variant_id.id, + "product_qty": cpo_line.purchase_quantity, + "price_unit": cpo_line.product_price, + "product_uom": cpo_line.uom_po_id.id, + "order_id": purchase_order.id, + "date_planned": self.date_planned, + } + ) pol.compute_taxes_id() self.generated_purchase_order_ids += purchase_order action = { - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.order', - 'res_id': purchase_order.id, - 'view_type': 'form', - 'view_mode': 'form', - 'target': 'current', + "type": "ir.actions.act_window", + "res_model": "purchase.order", + "res_id": purchase_order.id, + "view_type": "form", + "view_mode": "form,tree", + "target": "current", } return action - -class PurchaseOrderLine(models.Model): - _inherit = 'purchase.order.line' - @api.multi - def compute_taxes_id(self): - for pol in self: - if self.env.uid == SUPERUSER_ID: - company_id = self.env.user.company_id.id - else: - company_id = self.company_id.id - - fpos_id = ( - self.env['account.fiscal.position'] - .with_context(company_id=company_id) - .get_fiscal_position(pol.partner_id.id) - ) - fpos = self.env['account.fiscal.position'].browse(fpos_id) - pol.order_id.fiscal_position_id = fpos - - taxes = self.product_id.supplier_taxes_id - taxes_id = fpos.map_tax(taxes) if fpos else taxes - - if taxes_id: - taxes_id = taxes_id.filtered( - lambda t: t.company_id.id == company_id) + @api.depends("generated_purchase_order_ids") + def _compute_generated_po_count(self): + for cpo in self: + cpo.generated_po_count = len(cpo.generated_purchase_order_ids) - pol.taxes_id = taxes_id + @api.multi + def get_generated_po_action(self): + self.ensure_one() + action = { + "type": "ir.actions.act_window", + "res_model": "purchase.order", + "view_mode": "tree,form,kanban", + "target": "current", + "domain": [("id", "in", self.generated_purchase_order_ids.ids)], + } + return action diff --git a/compute_purchase_order/models/computed_purchase_order_line.py b/compute_purchase_order/models/computed_purchase_order_line.py index 4bdead9..ab97a90 100644 --- a/compute_purchase_order/models/computed_purchase_order_line.py +++ b/compute_purchase_order/models/computed_purchase_order_line.py @@ -1,132 +1,128 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# Vincent Van Rossem +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). import logging -from odoo import models, fields, api +from odoo import models, fields, api, _ from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) class ComputedPurchaseOrderLine(models.Model): - _description = 'Computed Purchase Order Line' - _name = 'computed.purchase.order.line' + _description = "Computed Purchase Order Line" + _name = "computed.purchase.order.line" - computed_purchase_order_id = fields.Many2one( - 'computed.purchase.order', - string='Computed Purchase Order', + name = fields.Char(string="Product Name", compute="_compute_name") + cpo_id = fields.Many2one( + comodel_name="computed.purchase.order", + string="Computed Purchase Order", ) - product_template_id = fields.Many2one( - 'product.template', - string='Linked Product Template', + comodel_name="product.template", + string="Linked Product Template", required=True, - help='Product') - - name = fields.Char( - string='Product Name', - related='product_template_id.name', - read_only=True) - - supplierinfo_id = fields.Many2one( - 'product.supplierinfo', - string='Supplier information', - compute='_compute_supplierinfo', - store=True, - readonly=True, + help="Product", ) - + purchase_quantity = fields.Float(string="Purchase Quantity", default=0.0) category_id = fields.Many2one( - 'product.category', - string='Internal Category', - related='product_template_id.categ_id', - read_only=True) - + comodel_name="product.category", + string="Internal Category", + related="product_template_id.categ_id", + read_only=True, + ) uom_id = fields.Many2one( - 'uom.uom', - string='Unit of Measure', + comodel_name="uom.uom", + string="Unit of Measure", read_only=True, - related='product_template_id.uom_id', - help="Default Unit of Measure used for all stock operation.") - + related="product_template_id.uom_id", + help="Default Unit of Measure used for all stock operation.", + ) qty_available = fields.Float( - string='Stock Quantity', - related='product_template_id.qty_available', + string="Stock Quantity", + related="product_template_id.qty_available", read_only=True, - help='Quantity currently in stock. Does not take ' - 'into account incoming orders.') - + help="Quantity currently in stock. Does not take " + "into account incoming orders.", + ) virtual_available = fields.Float( - string='Forecast Quantity', - related='product_template_id.virtual_available', + string="Forecast Quantity", + related="product_template_id.virtual_available", read_only=True, - help='Virtual quantity taking into account current stock, incoming ' - 'orders and outgoing sales.') - + help="Virtual quantity taking into account current stock, incoming " + "orders and outgoing sales.", + ) daily_sales = fields.Float( - string='Average Consumption', - related='product_template_id.daily_sales', - read_only=True) - - stock_coverage = fields.Float( - string='Stock Coverage', - related='product_template_id.stock_coverage', + string="Average Consumption", + related="product_template_id.daily_sales", read_only=True, ) - - minimum_purchase_qty = fields.Float( - string='Minimum Purchase Quantity', - compute='_depends_on_product_template', + stock_coverage = fields.Float( + string="Stock Coverage", + related="product_template_id.stock_coverage", + read_only=True, ) - - purchase_quantity = fields.Float( - string='Purchase Quantity', - required=True, - default=0.) - uom_po_id = fields.Many2one( - 'uom.uom', - string='Purchase Unit of Measure', + comodel_name="uom.uom", + string="Purchase Unit of Measure", read_only=True, - related='product_template_id.uom_po_id', - help="Default Unit of Measure used for all stock operation.") - + related="product_template_id.uom_po_id", + help="Default Unit of Measure used for all stock operation.", + ) + supplierinfo_id = fields.Many2one( + comodel_name="product.supplierinfo", + string="Supplier information", + compute="_compute_supplierinfo", + store=True, + readonly=True, + ) + minimum_purchase_qty = fields.Float( + string="Minimum Purchase Quantity", related="supplierinfo_id.min_qty" + ) product_price = fields.Float( - string='Product Price (w/o VAT)', - compute='_depends_on_product_template', - read_only=True, - help='Supplier Product Price by buying unit. Price is without VAT') - + string="Product Price (w/o VAT)", + related="supplierinfo_id.price", + help="Supplier Product Price by buying unit. Price is without VAT", + ) virtual_coverage = fields.Float( - string='Expected Stock Coverage', - compute='_depends_on_purchase_quantity', - help='Expected stock coverage (in days) based on current stocks and average daily consumption') # noqa - + string="Expected Stock Coverage", + compute="_compute_coverage_and_subtotal", + help="Expected stock coverage (in days) based on current stocks and " + "average daily consumption", + ) subtotal = fields.Float( - string='Subtotal (w/o VAT)', - compute='_depends_on_purchase_quantity') + string="Subtotal (w/o VAT)", compute="_compute_coverage_and_subtotal" + ) @api.multi - @api.depends('product_template_id') - def _depends_on_product_template(self): + @api.depends("supplierinfo_id") + def _compute_name(self): for cpol in self: - # get supplier info - cpol.minimum_purchase_qty = cpol.supplierinfo_id.min_qty - cpol.product_price = cpol.supplierinfo_id.price + if cpol.supplierinfo_id and cpol.supplierinfo_id.product_code: + product_code = cpol.supplierinfo_id.product_code + product_name = cpol.product_template_id.name + cpol_name = "[%s] %s" % (product_code, product_name) + else: + cpol_name = cpol.product_template_id.name + cpol.name = cpol_name @api.multi - @api.onchange('product_template_id') + @api.onchange("product_template_id") def _onchange_purchase_quantity(self): for cpol in self: - cpol.purchase_quantity = cpol.supplierinfo_id.min_qty + cpol.purchase_quantity = cpol.minimum_purchase_qty - @api.depends('purchase_quantity') @api.multi - def _depends_on_purchase_quantity(self): + @api.depends("purchase_quantity") + def _compute_coverage_and_subtotal(self): for cpol in self: cpol.subtotal = cpol.product_price * cpol.purchase_quantity avg = cpol.daily_sales if avg > 0: - qty = ((cpol.virtual_available / cpol.uom_id.factor) - + (cpol.purchase_quantity / cpol.uom_po_id.factor)) + qty = (cpol.virtual_available / cpol.uom_id.factor) + ( + cpol.purchase_quantity / cpol.uom_po_id.factor + ) cpol.virtual_coverage = qty / avg else: # todo what would be a good default value? (not float(inf)) @@ -135,61 +131,51 @@ class ComputedPurchaseOrderLine(models.Model): return True @api.multi - @api.depends('product_template_id') - @api.onchange('product_template_id') + @api.depends("product_template_id") def _compute_supplierinfo(self): for cpol in self: if not cpol.product_template_id: - cpol.supplierinfo_id = False - else: - SupplierInfo = self.env['product.supplierinfo'] - si = SupplierInfo.search([ - ('product_tmpl_id', '=', cpol.product_template_id.id), - ('name', '=', cpol.product_template_id.main_supplier_id.id) # noqa - ]) + continue - if len(si) == 0: - raise ValidationError( - 'No supplier information set for {name}' - .format(name=cpol.product_template_id.name)) - elif len(si) == 1: - cpol.supplierinfo_id = si - else: - _logger.warning( - 'product {name} has several suppliers, chose last' - .format(name=cpol.product_template_id.name) + si = self.env["product.supplierinfo"].search( + [ + ("product_tmpl_id", "=", cpol.product_template_id.id), + ("name", "=", cpol.cpo_id.supplier_id.id), + ] + ) + + if len(si) == 0: + raise ValidationError( + _("CPO supplier does not sell product {name}").format( + name=cpol.product_template_id.name + ) + ) + elif len(si) > 1: + _logger.warning( + "product {name} has several supplier info set, chose last".format( + name=cpol.product_template_id.name ) - si = si.sorted(key=lambda r: r.create_date, reverse=True) - cpol.supplierinfo_id = si[0] + ) + si = si.sorted(key=lambda r: r.create_date, reverse=True) + cpol.supplierinfo_id = si[0] - @api.constrains('purchase_quantity') + @api.constrains("purchase_quantity") def _check_minimum_purchase_quantity(self): for cpol in self: if cpol.purchase_quantity < 0: - raise ValidationError('Purchase quantity for {product_name} must be greater than 0' # noqa - .format(product_name=cpol.product_template_id.name)) + raise ValidationError( + _( + "Purchase quantity for {product_name} " + "must be greater than 0" + ).format(product_name=cpol.product_template_id.name) + ) elif 0 < cpol.purchase_quantity < cpol.minimum_purchase_qty: - raise ValidationError('Purchase quantity for {product_name} must be greater than {min_qty}' # noqa - .format(product_name=cpol.product_template_id.name, - min_qty=cpol.minimum_purchase_qty)) - - @api.multi - def get_default_product_product(self): - self.ensure_one() - ProductProduct = self.env['product.product'] - products = ProductProduct.search([ - ('product_tmpl_id', '=', self.product_template_id.id) - ]) - - products = products.sorted( - key=lambda product: product.create_date, - reverse=True - ) - - if products: - return products[0] - else: - raise ValidationError( - '%s:%s template has no variant set' - % (self.product_template_id.id, self.product_template_id.name) - ) + raise ValidationError( + _( + "Purchase quantity for {product_name} " + "must be greater than {min_qty}" + ).format( + product_name=cpol.product_template_id.name, + min_qty=cpol.minimum_purchase_qty, + ) + ) diff --git a/compute_purchase_order/models/product_template.py b/compute_purchase_order/models/product_template.py index 61bf5e9..5cd0545 100644 --- a/compute_purchase_order/models/product_template.py +++ b/compute_purchase_order/models/product_template.py @@ -1,3 +1,8 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# Vincent Van Rossem +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + from odoo import models, fields, api @@ -5,21 +10,22 @@ class ProductTemplate(models.Model): _inherit = "product.template" main_supplier_id = fields.Many2one( - 'res.partner', - compute='_compute_main_supplier_id', - store=True + "res.partner", compute="_compute_main_supplier_id", store=True ) def _get_sorted_supplierinfo(self): return self.seller_ids.sorted( - key=lambda seller: seller.date_start, - reverse=True) + key=lambda seller: seller.date_start, reverse=True + ) @api.multi - @api.depends('seller_ids', 'seller_ids.date_start') + @api.depends("seller_ids", "seller_ids.date_start") def _compute_main_supplier_id(self): - # Calcule le vendeur associé qui a la date de début la plus récente - # et plus petite qu’aujourd’hui for pt in self: - sellers_ids = pt._get_sorted_supplierinfo() - pt.main_supplier_id = sellers_ids and sellers_ids[0].name or False + sellers_ids = pt.seller_ids.sorted( + key=lambda seller: seller.date_start, reverse=True + ) + if sellers_ids: + pt.main_supplier_id = sellers_ids[0].name + else: + pt.main_supplier_id = False diff --git a/compute_purchase_order/models/purchase_order.py b/compute_purchase_order/models/purchase_order.py index 2c57db6..c72b2be 100644 --- a/compute_purchase_order/models/purchase_order.py +++ b/compute_purchase_order/models/purchase_order.py @@ -1,11 +1,45 @@ -from odoo import models, fields +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# Vincent Van Rossem +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import models, fields, api, SUPERUSER_ID class PurchaseOrder(models.Model): _inherit = "purchase.order" original_cpo_id = fields.Many2one( - 'computed.purchase.order', - string='Original CPO', - help='CPO used to generate this Purchase Order' + "computed.purchase.order", + string="Original CPO", + help="CPO used to generate this Purchase Order", ) + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + @api.multi + def compute_taxes_id(self): + for pol in self: + if self.env.uid == SUPERUSER_ID: + company_id = self.env.user.company_id.id + else: + company_id = self.company_id.id + + fpos_id = ( + self.env["account.fiscal.position"] + .with_context(company_id=company_id) + .get_fiscal_position(pol.partner_id.id) + ) + fpos = self.env["account.fiscal.position"].browse(fpos_id) + pol.order_id.fiscal_position_id = fpos + + taxes = self.product_id.supplier_taxes_id + taxes_id = fpos.map_tax(taxes) if fpos else taxes + + if taxes_id: + taxes_id = taxes_id.filtered( + lambda t: t.company_id.id == company_id + ) + + pol.taxes_id = taxes_id diff --git a/compute_purchase_order/tests/__init__.py b/compute_purchase_order/tests/__init__.py new file mode 100644 index 0000000..0246a73 --- /dev/null +++ b/compute_purchase_order/tests/__init__.py @@ -0,0 +1 @@ +from . import test_cpo diff --git a/compute_purchase_order/tests/test_cpo.py b/compute_purchase_order/tests/test_cpo.py new file mode 100644 index 0000000..c50830b --- /dev/null +++ b/compute_purchase_order/tests/test_cpo.py @@ -0,0 +1,84 @@ +# Copyright (C) 2019 - Today: GRAP (http://www.grap.coop) +# @author: Robin Keunen +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase, Form + + +class TestCPO(TransactionCase): + def setUp(self): + super().setUp() + + self.supplier = self.browse_ref("base.res_partner_1") + self.pproduct1 = self.browse_ref("product.product_product_25") + self.ptemplate1 = self.pproduct1.product_tmpl_id + self.pproduct2 = self.browse_ref("product.product_delivery_02") + self.ptemplate2 = self.pproduct2.product_tmpl_id + + def test_generate_cpo(self): + supplierinfo_obj = self.env["product.supplierinfo"] + supplierinfo = supplierinfo_obj.search( + [ + ("name", "=", self.supplier.id), + ("product_tmpl_id", "=", self.ptemplate1.id), + ] + ) + supplierinfo2 = supplierinfo_obj.search( + [ + ("name", "=", self.supplier.id), + ("product_tmpl_id", "=", self.ptemplate2.id), + ] + ) + + cpo_obj = self.env["computed.purchase.order"] + cpo_action = cpo_obj.with_context( + active_ids=[self.ptemplate1.id] + ).generate_cpo() + cpo = cpo_obj.browse(cpo_action["res_id"]) + cpol = cpo.cpo_line_ids # expect one line + + self.assertEquals(cpo.supplier_id, self.supplier) + self.assertEquals(cpol.product_template_id, self.ptemplate1) + self.assertEquals(cpol.product_price, supplierinfo.price) + self.assertEquals(cpol.purchase_quantity, supplierinfo.min_qty) + + # testing triggers + expected_subtotal = supplierinfo.price * supplierinfo.min_qty + self.assertEquals(cpol.subtotal, expected_subtotal) + + cpol.purchase_quantity = 4 + expected_subtotal = supplierinfo.price * 4 + self.assertEquals(cpol.subtotal, expected_subtotal) + + cpo_form = Form(cpo) + with cpo_form.cpo_line_ids.edit(index=0) as line_form: + line_form.product_template_id = self.ptemplate2 + self.assertEquals(line_form.product_template_id, self.ptemplate2) + cpo = cpo_form.save() + cpol = cpo.cpo_line_ids + + expected_subtotal = supplierinfo2.price * supplierinfo2.min_qty + self.assertEquals(cpol.product_price, supplierinfo2.price) + self.assertEquals(cpol.purchase_quantity, supplierinfo2.min_qty) + self.assertEquals(cpol.subtotal, expected_subtotal) + + def test_generate_po(self): + cpo_obj = self.env["computed.purchase.order"] + cpo_action = cpo_obj.with_context( + active_ids=[self.ptemplate1.id, self.ptemplate2.id] + ).generate_cpo() + cpo = cpo_obj.browse(cpo_action["res_id"]) + po_action = cpo.create_purchase_order() + po = self.env["purchase.order"].browse(po_action["res_id"]) + + self.assertEquals(cpo.supplier_id, po.partner_id) + self.assertEquals(len(cpo.cpo_line_ids), len(po.order_line)) + lines = zip( + cpo.cpo_line_ids.sorted(lambda l: l.product_template_id), + po.order_line.sorted(lambda l: l.product_id.product_tmpl_id), + ) + for cpol, pol in lines: + self.assertEquals( + cpol.product_template_id, pol.product_id.product_tmpl_id + ) + self.assertEquals(cpol.purchase_quantity, pol.product_qty) diff --git a/compute_purchase_order/views/computed_purchase_order.xml b/compute_purchase_order/views/computed_purchase_order.xml index bd888a9..f4c9b64 100644 --- a/compute_purchase_order/views/computed_purchase_order.xml +++ b/compute_purchase_order/views/computed_purchase_order.xml @@ -1,115 +1,118 @@ - + - - computed.purchase.order.tree - computed.purchase.order - - - - - - - - - - + + computed.purchase.order.tree + computed.purchase.order + + + + + + + + + + - - computed.purchase.order.form - computed.purchase.order - -
-
-
- - - - - - - - - - - -
- -
- -
- - - - - - - - - - - - - - - - -
- - -
-
-
- - - - computed.purchase.order - - - - - - - - - - - Computed Purchase Orders - ir.actions.act_window - computed.purchase.order - tree,form - - - - + + computed.purchase.order.form + computed.purchase.order + +
+
+
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + computed.purchase.order + + + + + + + + + + + Computed Purchase Orders + computed.purchase.order + + + - + + Compute Purchase Order + + + code + + model.generate_cpo() + + + +