Browse Source

compute purchase order, performance fix

pull/145/head
robinkeunen 7 years ago
committed by robin.keunen
parent
commit
393be1aa63
  1. 1
      compute_purchase_order/__init__.py
  2. 21
      compute_purchase_order/__openerp__.py
  3. 4
      compute_purchase_order/models/__init__.py
  4. 194
      compute_purchase_order/models/computed_purchase_order.py
  5. 196
      compute_purchase_order/models/computed_purchase_order_line.py
  6. 26
      compute_purchase_order/models/product_template.py
  7. 12
      compute_purchase_order/models/purchase_order.py
  8. 12
      compute_purchase_order/security/ir.model.access.csv
  9. 115
      compute_purchase_order/views/computed_purchase_order.xml
  10. 24
      compute_purchase_order/views/product_template.xml
  11. 13
      compute_purchase_order/views/purchase_order.xml

1
compute_purchase_order/__init__.py

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

21
compute_purchase_order/__openerp__.py

@ -0,0 +1,21 @@
# -*- encoding: utf-8 -*-
{
'name': 'Computed Purchase Order',
'version': '9.0.1',
'category': 'Purchase Order',
'description': """ todo """,
'author': 'Coop IT Easy',
'website': 'https://github.com/coopiteasy/procurement-addons',
'license': 'AGPL-3',
'depends': [
'product',
'purchase',
'stock',
'stock_coverage',
],
'data': [
'security/ir.model.access.csv',
'views/computed_purchase_order.xml',
'views/purchase_order.xml',
],
}

4
compute_purchase_order/models/__init__.py

@ -0,0 +1,4 @@
from . import purchase_order
from . import computed_purchase_order
from . import computed_purchase_order_line
from . import product_template

194
compute_purchase_order/models/computed_purchase_order.py

@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
from openerp import models, fields, api
from openerp.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')
order_date = fields.Datetime(
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
date_planned = fields.Datetime(
string='Date Planned'
)
supplier_id = fields.Many2one(
'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',
)
total_amount = fields.Float(
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',
)
generated_po_count = fields.Integer(
string='Generated Purchase Order count',
compute='_compute_generated_po_count',
)
@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(u'No supplier is set for selected articles.')
elif len(suppliers) == 1:
return suppliers.pop()
else:
raise ValidationError(
u'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,
}
)
# 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 = u'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()
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
# @api.onchange(order_line_ids) # fixme
@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(u'You need at least a product to generate '
u'a Purchase Order')
PurchaseOrder = self.env['purchase.order']
PurchaseOrderLine = self.env['purchase.order.line']
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:
if cpo_line.purchase_quantity > 0:
pol_values = {
'name': cpo_line.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,
}
PurchaseOrderLine.create(pol_values)
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',
}
return action

196
compute_purchase_order/models/computed_purchase_order_line.py

@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
import logging
from openerp import models, fields, api
from openerp.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class ComputedPurchaseOrderLine(models.Model):
_description = 'Computed Purchase Order Line'
_name = 'computed.purchase.order.line'
computed_purchase_order_id = fields.Many2one(
'computed.purchase.order',
string='Computed Purchase Order',
)
product_template_id = fields.Many2one(
'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,
)
category_id = fields.Many2one(
'product.category',
string='Internal Category',
related='product_template_id.categ_id',
read_only=True)
uom_id = fields.Many2one(
'product.uom',
string='Unit of Measure',
read_only=True,
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',
read_only=True,
help='Quantity currently in stock. Does not take '
'into account incoming orders.')
virtual_available = fields.Float(
string='Stock Quantity',
related='product_template_id.virtual_available',
read_only=True,
help='Virtual quantity taking into account current stock, incoming '
'orders and outgoing sales.')
average_consumption = fields.Float(
string='Average Consumption',
related='product_template_id.average_consumption',
read_only=True)
stock_coverage = fields.Float(
string='Stock Coverage',
related='product_template_id.estimated_stock_coverage',
read_only=True,
)
minimum_purchase_qty = fields.Float(
string='Minimum Purchase Quantity',
compute='_depends_on_product_template',
)
purchase_quantity = fields.Float(
string='Purchase Quantity',
required=True,
default=0.)
uom_po_id = fields.Many2one(
'product.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.")
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')
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
subtotal = fields.Float(
string='Subtotal (w/o VAT)',
compute='_depends_on_purchase_quantity')
@api.multi
@api.depends('product_template_id')
def _depends_on_product_template(self):
for cpol in self:
# get supplier info
cpol.minimum_purchase_qty = cpol.supplierinfo_id.min_qty
cpol.product_price = cpol.supplierinfo_id.price
@api.multi
@api.onchange('product_template_id')
def _onchange_purchase_quantity(self):
for cpol in self:
cpol.purchase_quantity = cpol.supplierinfo_id.min_qty
@api.depends('purchase_quantity')
@api.multi
def _depends_on_purchase_quantity(self):
for cpol in self:
cpol.subtotal = cpol.product_price * cpol.purchase_quantity
avg = cpol.average_consumption
if avg > 0:
qty = cpol.virtual_available + cpol.purchase_quantity
cpol.virtual_coverage = qty / avg
else:
# todo what would be a good default value? (not float(inf))
cpol.virtual_coverage = 9999
return True
@api.multi
@api.depends('product_template_id')
@api.onchange('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
])
if len(si) == 0:
raise ValidationError(
u'No supplier information set for {name}'
.format(name=cpol.product_template_id.name))
elif len(si) == 1:
cpol.supplierinfo_id = si
else:
_logger.warning(
u'product {name} has several suppliers, 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]
@api.constrains('purchase_quantity')
def _check_minimum_purchase_quantity(self):
for cpol in self:
if cpol.purchase_quantity < 0:
raise ValidationError(u'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(u'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(
u'%s:%s template has no variant set'
% (self.product_template_id.id, self.product_template_id.name)
)

26
compute_purchase_order/models/product_template.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from openerp import models, fields, api
class ProductTemplate(models.Model):
_inherit = "product.template"
main_supplier_id = fields.Many2one(
'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)
@api.multi
@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

12
compute_purchase_order/models/purchase_order.py

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from openerp import models, fields
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'
)

12
compute_purchase_order/security/ir.model.access.csv

@ -0,0 +1,12 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_purchase_order,purchase.order,model_computed_purchase_order,purchase.group_purchase_user,1,1,1,1
access_purchase_order_manager,purchase.order,model_computed_purchase_order,purchase.group_purchase_manager,1,1,1,1
access_purchase_order_stock_worker,purchase.order,model_computed_purchase_order,stock.group_stock_user,1,0,0,0
access_purchase_order_invoicing_payments,purchase.order,model_computed_purchase_order,account.group_account_invoice,1,1,0,0
access_purchase_order_line,purchase.order.line user,model_computed_purchase_order_line,purchase.group_purchase_user,1,1,1,1
access_purchase_order_line_manager,purchase.order.line manager,model_computed_purchase_order_line,purchase.group_purchase_manager,1,0,0,0
access_purchase_order_line_stock_worker,purchase.order.line,model_computed_purchase_order_line,stock.group_stock_user,1,0,0,0
access_purchase_order_line_manager,purchase.order.line,model_computed_purchase_order_line,purchase.group_purchase_manager,1,1,1,1
access_purchase_order_line_invoicing_payments,purchase.order.line,model_computed_purchase_order_line,account.group_account_invoice,1,1,0,0

115
compute_purchase_order/views/computed_purchase_order.xml

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- views-->
<!--tree-->
<record id="computed_purchase_order_tree" model="ir.ui.view">
<field name="name">computed.purchase.order.tree</field>
<field name="model">computed.purchase.order</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="supplier_id"/>
<field name="order_date"/>
<field name="date_planned"/>
<field name="total_amount"/>
</tree>
</field>
</record>
<!-- form -->
<record id="computed_purchase_order_form" model="ir.ui.view">
<field name="name">computed.purchase.order.form</field>
<field name="model">computed.purchase.order</field>
<field name="arch" type="xml">
<form>
<header>
<button type="object"
name="create_purchase_order"
string="Create Purchase Order"
class="oe_highlight"/>
</header>
<sheet>
<group>
<group class="oe_left">
<field name="supplier_id" />
<field name="order_date" />
<field name="date_planned"/>
<field name="total_amount"/>
<field name="generated_purchase_order_ids" invisible='1'/>
</group>
<div name="buttons" class="oe_right oe_button_box">
<button class="oe_inline oe_stat_button"
type="object"
icon="fa-shopping-cart"
name="get_generated_po_action"
help="Generated Purchase Orders" >
<field string="Purchase orders"
name="generated_po_count"
widget="statinfo"/>
</button>
</div>
</group>
<field name="order_line_ids" context="{'cpo_seller_id': supplier_id}">
<tree string="Order Lines" editable='bottom'>
<field name="product_template_id" domain="[('main_seller_id', '=', cpo_seller_id)]"/>
<field name="category_id" readonly='1'/>
<field name="qty_available" readonly='1'/>
<field name="uom_id" readonly='1'/>
<field name="average_consumption" readonly='1'/>
<field name="stock_coverage" readonly='1'/>
<field name="virtual_coverage" readonly='1'/>
<field name="product_price" readonly='1'/>
<field name="uom_po_id" readonly='1'/>
<field name="purchase_quantity"/>
<field name="subtotal" readonly='1'/>
</tree>
</field>
</sheet>
</form>
</field>
</record>
<!-- filters-->
<record id="computed_purchase_order_filter" model="ir.ui.view">
<field name="model">computed.purchase.order</field>
<field name="arch" type="xml">
<search>
<field name="supplier_id"/>
</search>
</field>
</record>
<!-- Menu item -->
<record id="action_computed_purchase_order" model="ir.actions.act_window">
<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="view_mode">tree,form</field>
<field name="view_id" eval="computed_purchase_order_tree"/>
</record>
<menuitem id="computed_purchase_order"
parent="purchase.menu_procurement_management"
action="action_computed_purchase_order"/>
<!-- 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"
/>
</odoo>

24
compute_purchase_order/views/product_template.xml

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="product_template_tree" model="ir.ui.view">
<field name="name">product.template.tree</field>
<field name="model">product.template</field>
<field eval="6" name="priority"/>
<!--<field name="inherit_id" ref="product.product_template_tree_view"/>-->
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="ref interne"/>
<field name="supplier"/>
<field name="estimated_stock_coverage"/>
<field name="virtual_coverage"/>
<field name="categ_id"/>
<field name="qty_available"/>
<field name="expected quantity"/>
</tree>
</field>
</record>
</odoo>

13
compute_purchase_order/views/purchase_order.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_purchase_order_form_inherit" model="ir.ui.view">
<field name="name">purchase.order.form.inherit</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='date_order']" position="after">
<field name="original_cpo_id" attrs="{'invisible': [('original_cpo_id','=',False)]}"/>
</xpath>
</field>
</record>
</odoo>
Loading…
Cancel
Save