mirror of
https://github.com/bringout/oca-workflow-process.git
synced 2026-04-19 15:12:03 +02:00
Initial commit: OCA Workflow Process packages (456 packages)
This commit is contained in:
commit
d366e42934
18799 changed files with 1284507 additions and 0 deletions
|
|
@ -0,0 +1,3 @@
|
|||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
|
||||
|
||||
from . import purchase_request_line_make_purchase_order
|
||||
|
|
@ -0,0 +1,398 @@
|
|||
# Copyright 2018-2019 ForgeFlow, S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import get_lang
|
||||
|
||||
|
||||
class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
|
||||
_name = "purchase.request.line.make.purchase.order"
|
||||
_description = "Purchase Request Line Make Purchase Order"
|
||||
|
||||
supplier_id = fields.Many2one(
|
||||
comodel_name="res.partner",
|
||||
string="Supplier",
|
||||
required=True,
|
||||
context={"res_partner_search_mode": "supplier"},
|
||||
)
|
||||
item_ids = fields.One2many(
|
||||
comodel_name="purchase.request.line.make.purchase.order.item",
|
||||
inverse_name="wiz_id",
|
||||
string="Items",
|
||||
)
|
||||
purchase_order_id = fields.Many2one(
|
||||
comodel_name="purchase.order",
|
||||
string="Purchase Order",
|
||||
domain=[("state", "=", "draft")],
|
||||
)
|
||||
sync_data_planned = fields.Boolean(
|
||||
string="Match existing PO lines by Scheduled Date",
|
||||
help=(
|
||||
"When checked, PO lines on the selected purchase order are only reused "
|
||||
"if the scheduled date matches as well."
|
||||
),
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _prepare_item(self, line):
|
||||
return {
|
||||
"line_id": line.id,
|
||||
"request_id": line.request_id.id,
|
||||
"product_id": line.product_id.id,
|
||||
"name": line.name or line.product_id.name,
|
||||
"product_qty": line.pending_qty_to_receive,
|
||||
"product_uom_id": line.product_uom_id.id,
|
||||
"estimated_cost": line.estimated_cost,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _check_valid_request_line(self, request_line_ids):
|
||||
picking_type = False
|
||||
company_id = False
|
||||
|
||||
for line in self.env["purchase.request.line"].browse(request_line_ids):
|
||||
if line.request_id.state == "done":
|
||||
raise UserError(_("The purchase has already been completed."))
|
||||
if line.request_id.state not in ["approved", "in_progress"]:
|
||||
raise UserError(
|
||||
_("Purchase Request %s is not approved or in progress")
|
||||
% line.request_id.name
|
||||
)
|
||||
|
||||
if line.purchase_state == "done":
|
||||
raise UserError(_("The purchase has already been completed."))
|
||||
|
||||
line_company_id = line.company_id and line.company_id.id or False
|
||||
if company_id is not False and line_company_id != company_id:
|
||||
raise UserError(_("You have to select lines from the same company."))
|
||||
else:
|
||||
company_id = line_company_id
|
||||
|
||||
line_picking_type = line.request_id.picking_type_id or False
|
||||
if not line_picking_type:
|
||||
raise UserError(_("You have to enter a Picking Type."))
|
||||
if picking_type is not False and line_picking_type != picking_type:
|
||||
raise UserError(
|
||||
_("You have to select lines from the same Picking Type.")
|
||||
)
|
||||
else:
|
||||
picking_type = line_picking_type
|
||||
|
||||
@api.model
|
||||
def check_group(self, request_lines):
|
||||
if len(list(set(request_lines.mapped("request_id.group_id")))) > 1:
|
||||
raise UserError(
|
||||
_(
|
||||
"You cannot create a single purchase order from "
|
||||
"purchase requests that have different procurement group."
|
||||
)
|
||||
)
|
||||
|
||||
@api.model
|
||||
def get_items(self, request_line_ids):
|
||||
request_line_obj = self.env["purchase.request.line"]
|
||||
items = []
|
||||
request_lines = request_line_obj.browse(request_line_ids)
|
||||
self._check_valid_request_line(request_line_ids)
|
||||
self.check_group(request_lines)
|
||||
for line in request_lines:
|
||||
items.append([0, 0, self._prepare_item(line)])
|
||||
return items
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super().default_get(fields)
|
||||
active_model = self.env.context.get("active_model", False)
|
||||
request_line_ids = []
|
||||
if active_model == "purchase.request.line":
|
||||
request_line_ids += self.env.context.get("active_ids", [])
|
||||
elif active_model == "purchase.request":
|
||||
request_ids = self.env.context.get("active_ids", False)
|
||||
request_line_ids += (
|
||||
self.env[active_model].browse(request_ids).mapped("line_ids.id")
|
||||
)
|
||||
if not request_line_ids:
|
||||
return res
|
||||
res["item_ids"] = self.get_items(request_line_ids)
|
||||
request_lines = self.env["purchase.request.line"].browse(request_line_ids)
|
||||
supplier_ids = request_lines.mapped("supplier_id").ids
|
||||
if len(supplier_ids) == 1:
|
||||
res["supplier_id"] = supplier_ids[0]
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _prepare_purchase_order(self, picking_type, group_id, company, origin):
|
||||
if not self.supplier_id:
|
||||
raise UserError(_("Enter a supplier."))
|
||||
supplier = self.supplier_id
|
||||
data = {
|
||||
"origin": origin,
|
||||
"partner_id": self.supplier_id.id,
|
||||
"payment_term_id": self.supplier_id.property_supplier_payment_term_id.id,
|
||||
"fiscal_position_id": supplier.property_account_position_id
|
||||
and supplier.property_account_position_id.id
|
||||
or False,
|
||||
"picking_type_id": picking_type.id,
|
||||
"company_id": company.id,
|
||||
"group_id": group_id.id,
|
||||
}
|
||||
return data
|
||||
|
||||
def create_allocation(self, po_line, pr_line, new_qty, alloc_uom):
|
||||
vals = {
|
||||
"requested_product_uom_qty": new_qty,
|
||||
"product_uom_id": alloc_uom.id,
|
||||
"purchase_request_line_id": pr_line.id,
|
||||
"purchase_line_id": po_line.id,
|
||||
}
|
||||
return self.env["purchase.request.allocation"].create(vals)
|
||||
|
||||
@api.model
|
||||
def _prepare_purchase_order_line(self, po, item):
|
||||
if not item.product_id:
|
||||
raise UserError(_("Please select a product for all lines"))
|
||||
product = item.product_id
|
||||
|
||||
# Keep the standard product UOM for purchase order so we should
|
||||
# convert the product quantity to this UOM
|
||||
qty = item.product_uom_id._compute_quantity(
|
||||
item.product_qty, product.uom_po_id or product.uom_id
|
||||
)
|
||||
# Suggest the supplier min qty as it's done in Odoo core
|
||||
min_qty = item.line_id._get_supplier_min_qty(product, po.partner_id)
|
||||
qty = max(qty, min_qty)
|
||||
date_required = item.line_id.date_required
|
||||
return {
|
||||
"order_id": po.id,
|
||||
"product_id": product.id,
|
||||
"product_uom": product.uom_po_id.id or product.uom_id.id,
|
||||
"price_unit": 0.0,
|
||||
"product_qty": qty,
|
||||
"analytic_distribution": item.line_id.analytic_distribution,
|
||||
"purchase_request_lines": [(4, item.line_id.id)],
|
||||
"date_planned": datetime(
|
||||
date_required.year, date_required.month, date_required.day
|
||||
),
|
||||
"move_dest_ids": [(4, x.id) for x in item.line_id.move_dest_ids],
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _get_purchase_line_name(self, order, line):
|
||||
"""Fetch the product name as per supplier settings"""
|
||||
product_lang = line.product_id.with_context(
|
||||
lang=get_lang(self.env, self.supplier_id.lang).code,
|
||||
partner_id=self.supplier_id.id,
|
||||
company_id=order.company_id.id,
|
||||
)
|
||||
name = product_lang.display_name
|
||||
if product_lang.description_purchase:
|
||||
name += "\n" + product_lang.description_purchase
|
||||
return name
|
||||
|
||||
@api.model
|
||||
def _get_order_line_search_domain(self, order, item):
|
||||
vals = self._prepare_purchase_order_line(order, item)
|
||||
name = self._get_purchase_line_name(order, item)
|
||||
order_line_data = [
|
||||
("order_id", "=", order.id),
|
||||
("name", "=", name),
|
||||
("product_id", "=", item.product_id.id),
|
||||
("product_uom", "=", vals["product_uom"]),
|
||||
("analytic_distribution", "=?", item.line_id.analytic_distribution),
|
||||
]
|
||||
if self.sync_data_planned:
|
||||
date_required = item.line_id.date_required
|
||||
order_line_data += [
|
||||
(
|
||||
"date_planned",
|
||||
"=",
|
||||
datetime(
|
||||
date_required.year, date_required.month, date_required.day
|
||||
),
|
||||
)
|
||||
]
|
||||
if not item.product_id:
|
||||
order_line_data.append(("name", "=", item.name))
|
||||
return order_line_data
|
||||
|
||||
def make_purchase_order(self):
|
||||
res = []
|
||||
purchase_obj = self.env["purchase.order"]
|
||||
po_line_obj = self.env["purchase.order.line"]
|
||||
purchase = False
|
||||
|
||||
for item in self.item_ids:
|
||||
line = item.line_id
|
||||
if item.product_qty <= 0.0:
|
||||
raise UserError(_("Enter a positive quantity."))
|
||||
if self.purchase_order_id:
|
||||
purchase = self.purchase_order_id
|
||||
if not purchase:
|
||||
po_data = self._prepare_purchase_order(
|
||||
line.request_id.picking_type_id,
|
||||
line.request_id.group_id,
|
||||
line.company_id,
|
||||
line.origin,
|
||||
)
|
||||
purchase = purchase_obj.create(po_data)
|
||||
|
||||
# Look for any other PO line in the selected PO with same
|
||||
# product and UoM to sum quantities instead of creating a new
|
||||
# po line
|
||||
domain = self._get_order_line_search_domain(purchase, item)
|
||||
available_po_lines = po_line_obj.search(domain)
|
||||
new_pr_line = True
|
||||
# If Unit of Measure is not set, update from wizard.
|
||||
if not line.product_uom_id:
|
||||
line.product_uom_id = item.product_uom_id
|
||||
# Allocation UoM has to be the same as PR line UoM
|
||||
alloc_uom = line.product_uom_id
|
||||
wizard_uom = item.product_uom_id
|
||||
if (
|
||||
available_po_lines
|
||||
and not item.keep_description
|
||||
and not item.keep_estimated_cost
|
||||
):
|
||||
new_pr_line = False
|
||||
po_line = available_po_lines[0]
|
||||
po_line.purchase_request_lines = [(4, line.id)]
|
||||
po_line.move_dest_ids |= line.move_dest_ids
|
||||
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
|
||||
po_line.product_uom_qty, alloc_uom
|
||||
)
|
||||
wizard_product_uom_qty = wizard_uom._compute_quantity(
|
||||
item.product_qty, alloc_uom
|
||||
)
|
||||
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
|
||||
self.create_allocation(po_line, line, all_qty, alloc_uom)
|
||||
else:
|
||||
po_line_data = self._prepare_purchase_order_line(purchase, item)
|
||||
if item.keep_description:
|
||||
po_line_data["name"] = item.name
|
||||
po_line = po_line_obj.create(po_line_data)
|
||||
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
|
||||
po_line.product_uom_qty, alloc_uom
|
||||
)
|
||||
wizard_product_uom_qty = wizard_uom._compute_quantity(
|
||||
item.product_qty, alloc_uom
|
||||
)
|
||||
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
|
||||
self.create_allocation(po_line, line, all_qty, alloc_uom)
|
||||
self._post_process_po_line(item, po_line, new_pr_line)
|
||||
res.append(purchase.id)
|
||||
|
||||
purchase_requests = self.item_ids.mapped("request_id")
|
||||
purchase_requests.button_in_progress()
|
||||
return {
|
||||
"domain": [("id", "in", res)],
|
||||
"name": _("RFQ"),
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "purchase.order",
|
||||
"view_id": False,
|
||||
"context": False,
|
||||
"type": "ir.actions.act_window",
|
||||
}
|
||||
|
||||
def _post_process_po_line(self, item, po_line, new_pr_line):
|
||||
self.ensure_one()
|
||||
line = item.line_id
|
||||
# TODO: Check propagate_uom compatibility:
|
||||
price_unit = item.estimated_cost / item.product_qty
|
||||
new_qty = self.env["purchase.request.line"]._calc_new_qty(
|
||||
line, po_line=po_line, new_pr_line=new_pr_line
|
||||
)
|
||||
po_line.product_qty = new_qty
|
||||
if item.keep_estimated_cost:
|
||||
po_line.price_unit = price_unit
|
||||
po_line._compute_amount()
|
||||
# The quantity update triggers a compute method that alters the
|
||||
# unit price (which is what we want, to honor graduate pricing)
|
||||
# but also the scheduled date which is what we don't want.
|
||||
date_required = line.date_required
|
||||
po_line.date_planned = datetime(
|
||||
date_required.year, date_required.month, date_required.day
|
||||
)
|
||||
|
||||
|
||||
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):
|
||||
_name = "purchase.request.line.make.purchase.order.item"
|
||||
_description = "Purchase Request Line Make Purchase Order Item"
|
||||
|
||||
wiz_id = fields.Many2one(
|
||||
comodel_name="purchase.request.line.make.purchase.order",
|
||||
string="Wizard",
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
readonly=True,
|
||||
)
|
||||
line_id = fields.Many2one(
|
||||
comodel_name="purchase.request.line", string="Purchase Request Line"
|
||||
)
|
||||
request_id = fields.Many2one(
|
||||
comodel_name="purchase.request",
|
||||
related="line_id.request_id",
|
||||
string="Purchase Request",
|
||||
readonly=False,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
string="Product",
|
||||
related="line_id.product_id",
|
||||
readonly=False,
|
||||
)
|
||||
name = fields.Char(string="Description", required=True)
|
||||
product_qty = fields.Float(
|
||||
string="Quantity to purchase", digits="Product Unit of Measure"
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
comodel_name="uom.uom", string="UoM", required=True
|
||||
)
|
||||
estimated_cost = fields.Monetary(currency_field="currency_id")
|
||||
currency_id = fields.Many2one(
|
||||
"res.currency", string="Currency", related="line_id.currency_id", readonly=True
|
||||
)
|
||||
keep_description = fields.Boolean(
|
||||
string="Copy descriptions to new PO",
|
||||
help="Set true if you want to keep the "
|
||||
"descriptions provided in the "
|
||||
"wizard in the new PO.",
|
||||
)
|
||||
keep_estimated_cost = fields.Boolean(
|
||||
string="Copy estimative cost to new PO",
|
||||
help="Set true if you want to keep the "
|
||||
"estimated cost provided in the "
|
||||
"wizard in the new PO.",
|
||||
)
|
||||
|
||||
@api.onchange("product_id")
|
||||
def onchange_product_id(self):
|
||||
if self.product_id:
|
||||
if not self.keep_description:
|
||||
name = self.product_id.name
|
||||
code = self.product_id.code
|
||||
sup_info_id = self.env["product.supplierinfo"].search(
|
||||
[
|
||||
"|",
|
||||
("product_id", "=", self.product_id.id),
|
||||
("product_tmpl_id", "=", self.product_id.product_tmpl_id.id),
|
||||
("partner_id", "=", self.wiz_id.supplier_id.id),
|
||||
]
|
||||
)
|
||||
if sup_info_id:
|
||||
p_code = sup_info_id[0].product_code
|
||||
p_name = sup_info_id[0].product_name
|
||||
name = "[{}] {}".format(
|
||||
p_code if p_code else code, p_name if p_name else name
|
||||
)
|
||||
else:
|
||||
if code:
|
||||
name = "[{}] {}".format(
|
||||
code, self.name if self.keep_description else name
|
||||
)
|
||||
if self.product_id.description_purchase and not self.keep_description:
|
||||
name += "\n" + self.product_id.description_purchase
|
||||
self.product_uom_id = self.product_id.uom_id.id
|
||||
if name:
|
||||
self.name = name
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2018-2019 ForgeFlow, S.L.
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) -->
|
||||
<odoo>
|
||||
<record id="view_purchase_request_line_make_purchase_order" model="ir.ui.view">
|
||||
<field name="name">Purchase Request Line Make Purchase Order</field>
|
||||
<field name="model">purchase.request.line.make.purchase.order</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create RFQ">
|
||||
<separator string="Existing RFQ to update:" />
|
||||
<newline />
|
||||
<group>
|
||||
<field
|
||||
name="purchase_order_id"
|
||||
domain="[('partner_id', '=', supplier_id)]"
|
||||
/>
|
||||
<field name="sync_data_planned" />
|
||||
</group>
|
||||
<newline />
|
||||
<separator string="New PO details:" />
|
||||
<newline />
|
||||
<group>
|
||||
<field name="supplier_id" />
|
||||
</group>
|
||||
<newline />
|
||||
<group>
|
||||
<field name="item_ids" nolabel="1" colspan="2">
|
||||
<tree name="Details" create="false" editable="bottom">
|
||||
<field
|
||||
name="line_id"
|
||||
options="{'no_open': true}"
|
||||
invisible="1"
|
||||
/>
|
||||
<field name="request_id" attrs="{'readonly': True}" />
|
||||
<field name="product_id" />
|
||||
<field name="name" />
|
||||
<field name="estimated_cost" />
|
||||
<field name="product_qty" />
|
||||
<field
|
||||
name="product_uom_id"
|
||||
invisible="1"
|
||||
groups="!uom.group_uom"
|
||||
/>
|
||||
<field name="product_uom_id" groups="uom.group_uom" />
|
||||
<field
|
||||
name="keep_description"
|
||||
widget="boolean_toggle"
|
||||
options="{'autosave': False}"
|
||||
/>
|
||||
<field
|
||||
name="keep_estimated_cost"
|
||||
widget="boolean_toggle"
|
||||
options="{'autosave': False}"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<newline />
|
||||
<footer>
|
||||
<button
|
||||
name="make_purchase_order"
|
||||
string="Create RFQ"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
<button special="cancel" string="Cancel" class="oe_link" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record
|
||||
id="action_purchase_request_line_make_purchase_order"
|
||||
model="ir.actions.act_window"
|
||||
>
|
||||
<field name="name">Create RFQ</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">purchase.request.line.make.purchase.order</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_purchase_request_line_make_purchase_order" />
|
||||
<field name="target">new</field>
|
||||
<field
|
||||
name="binding_model_id"
|
||||
ref="purchase_request.model_purchase_request_line"
|
||||
/>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue