Browse Source

[REF] cpo: refactor

[REF] cpo: blacken

wip
pull/145/head
robin.keunen 5 years ago
parent
commit
4e58913059
  1. 7
      compute_purchase_order/__manifest__.py
  2. 299
      compute_purchase_order/models/computed_purchase_order.py
  3. 242
      compute_purchase_order/models/computed_purchase_order_line.py
  4. 26
      compute_purchase_order/models/product_template.py
  5. 42
      compute_purchase_order/models/purchase_order.py
  6. 1
      compute_purchase_order/tests/__init__.py
  7. 84
      compute_purchase_order/tests/test_cpo.py
  8. 33
      compute_purchase_order/views/computed_purchase_order.xml

7
compute_purchase_order/__manifest__.py

@ -10,12 +10,7 @@
"author": "Coop IT Easy", "author": "Coop IT Easy",
"website": "https://github.com/coopiteasy/procurement-addons", "website": "https://github.com/coopiteasy/procurement-addons",
"license": "AGPL-3", "license": "AGPL-3",
"depends": [
"product",
"purchase",
"stock",
"beesdoo_stock_coverage",
], # todo simplify
"depends": ["purchase", "beesdoo_stock_coverage"],
"data": [ "data": [
"security/ir.model.access.csv", "security/ir.model.access.csv",
"views/computed_purchase_order.xml", "views/computed_purchase_order.xml",

299
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 <robin@coopiteasy.be>
# Vincent Van Rossem <vincent@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class ComputedPurchaseOrder(models.Model): 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( order_date = fields.Datetime(
string='Purchase Order Date',
string="Purchase Order Date",
default=fields.Datetime.now, 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( date_planned = fields.Datetime(
string='Date Planned'
string="Date Planned",
default=fields.Datetime.now, # default=lambda _: fields.Datetime.now()
) )
supplier_id = fields.Many2one( supplier_id = fields.Many2one(
'res.partner',
string='Supplier',
comodel_name="res.partner",
string="Supplier",
readonly=True, 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( 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( 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( 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 @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 quaujourdhui 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: elif len(suppliers) == 1:
return suppliers.pop()
return suppliers
else: else:
raise ValidationError( 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 = { 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 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 @api.multi
def create_purchase_order(self): def create_purchase_order(self):
self.ensure_one() 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')
PurchaseOrder = self.env['purchase.order']
PurchaseOrderLine = self.env['purchase.order.line']
if sum(self.cpo_line_ids.mapped("purchase_quantity")) == 0:
raise ValidationError(
"You need at least a product to generate " "a Purchase Order"
)
po_values = {
'name': 'New',
'date_order': self.order_date,
'partner_id': self.supplier_id.id,
'date_planned': self.date_planned,
purchase_order = self.env["purchase.order"].create(
{
"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.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 = 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 = PurchaseOrderLine.create(pol_values)
)
pol.compute_taxes_id() pol.compute_taxes_id()
self.generated_purchase_order_ids += purchase_order self.generated_purchase_order_ids += purchase_order
action = { 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 return action
class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line'
@api.multi @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

242
compute_purchase_order/models/computed_purchase_order_line.py

@ -1,132 +1,128 @@
# Copyright 2020 Coop IT Easy SCRL fs
# Robin Keunen <robin@coopiteasy.be>
# Vincent Van Rossem <vincent@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import logging import logging
from odoo import models, fields, api
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class ComputedPurchaseOrderLine(models.Model): 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_id = fields.Many2one(
'product.template',
string='Linked Product Template',
comodel_name="product.template",
string="Linked Product Template",
required=True, 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( 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_id = fields.Many2one(
'uom.uom',
string='Unit of Measure',
comodel_name="uom.uom",
string="Unit of Measure",
read_only=True, 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( 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, 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( 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, 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( 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, 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_po_id = fields.Many2one(
'uom.uom',
string='Purchase Unit of Measure',
comodel_name="uom.uom",
string="Purchase Unit of Measure",
read_only=True, 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( 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( 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( 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.multi
@api.depends('product_template_id')
def _depends_on_product_template(self):
@api.depends("supplierinfo_id")
def _compute_name(self):
for cpol in 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.multi
@api.onchange('product_template_id')
@api.onchange("product_template_id")
def _onchange_purchase_quantity(self): def _onchange_purchase_quantity(self):
for cpol in 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 @api.multi
def _depends_on_purchase_quantity(self):
@api.depends("purchase_quantity")
def _compute_coverage_and_subtotal(self):
for cpol in self: for cpol in self:
cpol.subtotal = cpol.product_price * cpol.purchase_quantity cpol.subtotal = cpol.product_price * cpol.purchase_quantity
avg = cpol.daily_sales avg = cpol.daily_sales
if avg > 0: 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 cpol.virtual_coverage = qty / avg
else: else:
# todo what would be a good default value? (not float(inf)) # todo what would be a good default value? (not float(inf))
@ -135,61 +131,51 @@ class ComputedPurchaseOrderLine(models.Model):
return True return True
@api.multi @api.multi
@api.depends('product_template_id')
@api.onchange('product_template_id')
@api.depends("product_template_id")
def _compute_supplierinfo(self): def _compute_supplierinfo(self):
for cpol in self: for cpol in self:
if not cpol.product_template_id: 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
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: if len(si) == 0:
raise ValidationError( raise ValidationError(
'No supplier information set for {name}'
.format(name=cpol.product_template_id.name))
elif len(si) == 1:
cpol.supplierinfo_id = si
else:
_("CPO supplier does not sell product {name}").format(
name=cpol.product_template_id.name
)
)
elif len(si) > 1:
_logger.warning( _logger.warning(
'product {name} has several suppliers, chose last'
.format(name=cpol.product_template_id.name)
"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) si = si.sorted(key=lambda r: r.create_date, reverse=True)
cpol.supplierinfo_id = si[0] cpol.supplierinfo_id = si[0]
@api.constrains('purchase_quantity')
@api.constrains("purchase_quantity")
def _check_minimum_purchase_quantity(self): def _check_minimum_purchase_quantity(self):
for cpol in self: for cpol in self:
if cpol.purchase_quantity < 0: 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))
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
raise ValidationError(
_(
"Purchase quantity for {product_name} "
"must be greater than 0"
).format(product_name=cpol.product_template_id.name)
) )
if products:
return products[0]
else:
elif 0 < cpol.purchase_quantity < cpol.minimum_purchase_qty:
raise ValidationError( raise ValidationError(
'%s:%s template has no variant set'
% (self.product_template_id.id, self.product_template_id.name)
_(
"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,
)
) )

26
compute_purchase_order/models/product_template.py

@ -1,3 +1,8 @@
# Copyright 2020 Coop IT Easy SCRL fs
# Robin Keunen <robin@coopiteasy.be>
# Vincent Van Rossem <vincent@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api from odoo import models, fields, api
@ -5,21 +10,22 @@ class ProductTemplate(models.Model):
_inherit = "product.template" _inherit = "product.template"
main_supplier_id = fields.Many2one( 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): def _get_sorted_supplierinfo(self):
return self.seller_ids.sorted( return self.seller_ids.sorted(
key=lambda seller: seller.date_start,
reverse=True)
key=lambda seller: seller.date_start, reverse=True
)
@api.multi @api.multi
@api.depends('seller_ids', 'seller_ids.date_start')
@api.depends("seller_ids", "seller_ids.date_start")
def _compute_main_supplier_id(self): 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: 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

42
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 <robin@coopiteasy.be>
# Vincent Van Rossem <vincent@coopiteasy.be>
# 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): class PurchaseOrder(models.Model):
_inherit = "purchase.order" _inherit = "purchase.order"
original_cpo_id = fields.Many2one( 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

1
compute_purchase_order/tests/__init__.py

@ -0,0 +1 @@
from . import test_cpo

84
compute_purchase_order/tests/test_cpo.py

@ -0,0 +1,84 @@
# Copyright (C) 2019 - Today: GRAP (http://www.grap.coop)
# @author: Robin Keunen <robin@coopiteasy.be>
# 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)

33
compute_purchase_order/views/computed_purchase_order.xml

@ -37,7 +37,8 @@
<field name="order_date"/> <field name="order_date"/>
<field name="date_planned"/> <field name="date_planned"/>
<field name="total_amount"/> <field name="total_amount"/>
<field name="generated_purchase_order_ids" invisible='1'/>
<field name="generated_purchase_order_ids"
invisible='1'/>
</group> </group>
<div name="buttons" class="oe_right oe_button_box"> <div name="buttons" class="oe_right oe_button_box">
@ -54,8 +55,9 @@
</group> </group>
<field name="order_line_ids"> <!--context="{'cpo_seller_id': supplier_id}"> -->
<tree name="order_lines" string="Order Lines" editable='bottom'>
<field name="cpo_line_ids"> <!--context="{'cpo_seller_id': supplier_id}"> -->
<tree name="order_lines" string="Order Lines"
editable='bottom'>
<field name="product_template_id"/> <!--domain="[('main_seller_id', '=', cpo_seller_id)]"/> --> <field name="product_template_id"/> <!--domain="[('main_seller_id', '=', cpo_seller_id)]"/> -->
<field name="qty_available" readonly='1'/> <field name="qty_available" readonly='1'/>
<field name="virtual_available" readonly='1'/> <field name="virtual_available" readonly='1'/>
@ -90,10 +92,7 @@
<record id="action_computed_purchase_order" model="ir.actions.act_window"> <record id="action_computed_purchase_order" model="ir.actions.act_window">
<field name="name">Computed Purchase Orders</field> <field name="name">Computed Purchase Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">computed.purchase.order</field> <field name="res_model">computed.purchase.order</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="computed_purchase_order_tree"/>
</record> </record>
<menuitem id="computed_purchase_order" <menuitem id="computed_purchase_order"
@ -102,14 +101,18 @@
<!-- Actions --> <!-- Actions -->
<act_window id="action_view_form_computed_purchase_order"
name="Compute Purchase Order"
src_model="product.template"
res_model="computed.purchase.order"
view_mode="form"
view_id="computed_purchase_order_form"
target="current"
key2="client_action_multi"
/>
<record id="action_generate_cpo" model="ir.actions.server">
<field name="name">Compute Purchase Order</field>
<field name="model_id" ref="model_computed_purchase_order"/>
<field name="binding_model_id" ref="product.model_product_template"/>
<field name="state">code</field>
<field name="code">
model.generate_cpo()
</field>
</record>
<menuitem id='menu_testing' name='Testing' sequence="100"
parent="purchase.menu_procurement_management"
action="action_generate_cpo"/>
</odoo> </odoo>
Loading…
Cancel
Save