Initial commit: OCA Workflow Process packages (456 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:00 +02:00
commit d366e42934
18799 changed files with 1284507 additions and 0 deletions

View file

@ -0,0 +1,46 @@
# Purchase Request
Odoo addon: purchase_request
## Installation
```bash
pip install odoo-bringout-oca-purchase-workflow-purchase_request
```
## Dependencies
This addon depends on:
- purchase_stock
## Manifest Information
- **Name**: Purchase Request
- **Version**: 16.0.2.4.0
- **Category**: Purchase Management
- **License**: LGPL-3
- **Installable**: True
## Source
Based on [OCA/purchase-workflow](https://github.com/OCA/purchase-workflow) branch 16.0, addon `purchase_request`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Purchase_request Module - purchase_request
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for purchase_request. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [purchase_stock](../../odoo-bringout-oca-ocb-purchase_stock)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon purchase_request or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-purchase-workflow-purchase_request"
# or
uv pip install odoo-bringout-oca-purchase-workflow-purchase_request"
```

View file

@ -0,0 +1,21 @@
# Models
Detected core models and extensions in purchase_request.
```mermaid
classDiagram
class purchase_request
class purchase_request_allocation
class purchase_request_line
class product_template
class purchase_order
class purchase_order_line
class stock_move
class stock_move_line
class stock_rule
class stock_warehouse_orderpoint
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: purchase_request. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon purchase_request
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,42 @@
# Security
Access control and security definitions in purchase_request.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../purchase_request/security/ir.model.access.csv)**
- 17 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[purchase_request.xml](../purchase_request/security/purchase_request.xml)**
- 2 security groups defined
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../purchase_request/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
- **[purchase_request.xml](../purchase_request/security/purchase_request.xml)**
- Security groups, categories, and XML-based rules
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon purchase_request
```

View file

@ -0,0 +1,9 @@
# Wizards
Transient models exposed as UI wizards in purchase_request.
```mermaid
classDiagram
class PurchaseRequestLineMakePurchaseOrder
class PurchaseRequestLineMakePurchaseOrderItem
```

View file

@ -0,0 +1,164 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
================
Purchase Request
================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:5b19c693955170a23b931af36c7918760373e06299b80106eaa28e8c188eafbd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/purchase-workflow/tree/16.0/purchase_request
:alt: OCA/purchase-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/purchase-workflow-16-0/purchase-workflow-16-0-purchase_request
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/purchase-workflow&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
You use this module if you wish to give notification of requirements of
materials and/or external services and keep track of such requirements.
Requests can be created either directly or indirectly. "Directly" means
that someone from the requesting department enters a purchase request
manually.
The person creating the request determines what and how much to order,
and the requested date.
"Indirectly" means that the purchase request initiated by the
application automatically, for example, from procurement orders (MO,
SO).
A purchase request is an instruction to Purchasing to procure a certain
quantity of materials services, so that they are available at a certain
point in time.
A line of a request contains the quantity and requested date of the
material to be supplied or the quantity of the service to be performed.
You can indicate the service specifications if needed.
Once request is approved go to the Purchase Request Lines from the menu
entry 'Purchase Requests', and also from the 'Purchase' menu.
Select the lines that you wish to initiate the RFQ for, then go to
'More' and press 'Create RFQ'.
You can choose to select an existing RFQ or create a new one. In the
later, you have to choose a supplier.
In case that you chose to select an existing RFQ, the application will
search for existing lines matching the request line, and will add the
extra quantity to them, recalculating the minimum order quantity, if it
exists for the supplier of that RFQ.
In case that you create a new RFQ, the request lines will also be
consolidated into as few as possible lines in the RFQ.
**Table of contents**
.. contents::
:local:
Configuration
=============
To configure the product follow this steps:
1. Go to a product form.
2. Go to *Inventory* tab.
3. Check the box *Purchase Request* along with the route *Buy*.
With this configuration, whenever a procurement order is created and the
supply rule selected is 'Buy' the application will create a Purchase
Request instead of a Purchase Order.
Usage
=====
Purchase requests are accessible through a new menu entry 'Purchase
Requests', and also from the 'Purchase' menu.
Users can access the list of Purchase Requests or Purchase Request
Lines.
It is possible to filter requests by its approval status.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/purchase-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/purchase-workflow/issues/new?body=module:%20purchase_request%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* ForgeFlow
Contributors
------------
- Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
- Jonathan Nemry <jonathan.nemry@acsone.eu>
- Aaron Henriquez <ahenriquez@forgeflow.com>
- Adrien Peiffer <adrien.peiffer@acsone.eu>
- Lois Rilo <lois.rilo@forgeflow.com>
- Héctor Villarreal <hector.villarreal@forgeflow.com>
- Ben Cai <ben.cai@elico-corp.com>
- Rattapong Chokmasermkul <rattapongc@ecosoft.co.th>
- Stefan Rijnhart <stefan@opener.amsterdam>
Other credits
-------------
The development of this module has been financially supported by:
|Aleph Objects, Inc|
Images
~~~~~~
- Enric Tobella (logo)
.. |Aleph Objects, Inc| image:: https://upload.wikimedia.org/wikipedia/en/3/3b/Aleph_Objects_Logo.png
:target: https://www.alephobjects.com
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/purchase-workflow <https://github.com/OCA/purchase-workflow/tree/16.0/purchase_request>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

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

View file

@ -0,0 +1,33 @@
# Copyright 2018-2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0).
{
"name": "Purchase Request",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"version": "16.0.2.4.0",
"summary": "Use this module to have notification of requirements of "
"materials and/or external services and keep track of such "
"requirements.",
"website": "https://github.com/OCA/purchase-workflow",
"category": "Purchase Management",
"depends": ["purchase_stock"],
"data": [
"security/purchase_request.xml",
"security/ir.model.access.csv",
"data/purchase_request_sequence.xml",
"data/purchase_request_data.xml",
"reports/report_purchase_request.xml",
"wizard/purchase_request_line_make_purchase_order_view.xml",
"views/purchase_request_view.xml",
"views/purchase_request_line_view.xml",
"views/purchase_request_report.xml",
"views/product_template.xml",
"views/purchase_order_view.xml",
"views/stock_move_views.xml",
"views/stock_picking_views.xml",
],
"demo": ["demo/purchase_request_demo.xml"],
"license": "LGPL-3",
"installable": True,
"application": True,
}

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018-2019 ForgeFlow, S.L.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo noupdate="1">
<!-- Request-related subtypes for messaging / Chatter -->
<record id="mt_request_to_approve" model="mail.message.subtype">
<field name="name">Purchase Request to be approved</field>
<field name="res_model">purchase.request</field>
<field name="default" eval="True" />
<field name="description">Purchase Request to be approved</field>
</record>
<record id="mt_request_approved" model="mail.message.subtype">
<field name="name">Purchase Request approved</field>
<field name="res_model">purchase.request</field>
<field name="default" eval="True" />
<field name="description">Purchase Request approved</field>
</record>
<record id="mt_request_rejected" model="mail.message.subtype">
<field name="name">Purchase Request rejected</field>
<field name="res_model">purchase.request</field>
<field name="default" eval="True" />
<field name="description">Purchase Request rejected</field>
</record>
<record id="mt_request_done" model="mail.message.subtype">
<field name="name">Purchase Request done</field>
<field name="res_model">purchase.request</field>
<field name="default" eval="True" />
<field name="description">Purchase Request is done</field>
</record>
<record id="mt_request_po_confirmed" model="mail.message.subtype">
<field name="name">Purchase Order confirmation</field>
<field name="res_model">purchase.request</field>
<field name="default" eval="True" />
<field name="internal" eval="True" />
<field name="description">Purchase Order is confirmed</field>
</record>
<record id="mt_request_picking_done" model="mail.message.subtype">
<field name="name">Purchase receipt confirmation</field>
<field name="res_model">purchase.request</field>
<field name="default" eval="True" />
<field name="internal" eval="True" />
<field name="description">Receipt is done</field>
</record>
</odoo>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018-2019 ForgeFlow, S.L.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo noupdate="1">
<record id="seq_purchase_request" model="ir.sequence">
<field name="name">Purchase Request</field>
<field name="code">purchase.request</field>
<field name="prefix">PR</field>
<field name="padding">5</field>
<field name="company_id" />
</record>
</odoo>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018-2019 ForgeFlow, S.L.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo noupdate="1">
<record id="base.user_demo" model="res.users">
<field eval="[(4, ref('group_purchase_request_user'))]" name="groups_id" />
</record>
<record id="base.user_admin" model="res.users">
<field eval="[(4, ref('group_purchase_request_manager'))]" name="groups_id" />
</record>
</odoo>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
# Copyright 2025 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openupgradelib import openupgrade
_noupdate_xmlids = [
"mt_request_to_approve",
"mt_request_approved",
"mt_request_rejected",
"mt_request_done",
]
@openupgrade.migrate()
def migrate(env, version):
openupgrade.set_xml_ids_noupdate_value(
env, "purchase_request", _noupdate_xmlids, True
)

View file

@ -0,0 +1,11 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from . import purchase_request_allocation
from . import orderpoint
from . import purchase_request
from . import purchase_request_line
from . import stock_rule
from . import product_template
from . import purchase_order
from . import stock_move
from . import stock_move_line

View file

@ -0,0 +1,26 @@
# Copyright 2018-2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import models
class Orderpoint(models.Model):
_inherit = "stock.warehouse.orderpoint"
def _quantity_in_progress(self):
res = super(Orderpoint, self)._quantity_in_progress()
for prline in self.env["purchase.request.line"].search(
[
(
"request_id.state",
"in",
("draft", "approved", "to_approve", "in_progress"),
),
("orderpoint_id", "in", self.ids),
("purchase_state", "=", False),
]
):
res[prline.orderpoint_id.id] += prline.product_uom_id._compute_quantity(
prline.product_qty, prline.orderpoint_id.product_uom, round=False
)
return res

View file

@ -0,0 +1,14 @@
# Copyright 2018-2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = "product.template"
purchase_request = fields.Boolean(
help="Check this box to generate Purchase Request instead of "
"generating Requests For Quotation from procurement.",
company_dependent=True,
)

View file

@ -0,0 +1,240 @@
# Copyright 2018-2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import _, api, exceptions, fields, models
class PurchaseOrder(models.Model):
_inherit = "purchase.order"
def _purchase_request_confirm_message_content(self, request, request_dict=None):
self.ensure_one()
if not request_dict:
request_dict = {}
title = _("Order confirmation %(po_name)s for your Request %(pr_name)s") % {
"po_name": self.name,
"pr_name": request.name,
}
message = "<h3>%s</h3><ul>" % title
message += _(
"The following requested items from Purchase Request %(pr_name)s "
"have now been confirmed in Purchase Order %(po_name)s:"
) % {
"po_name": self.name,
"pr_name": request.name,
}
for line in request_dict.values():
message += _(
"<li><b>%(prl_name)s</b>: Ordered quantity %(prl_qty)s %(prl_uom)s, "
"Planned date %(prl_date_planned)s</li>"
) % {
"prl_name": line["name"],
"prl_qty": line["product_qty"],
"prl_uom": line["product_uom"],
"prl_date_planned": line["date_planned"],
}
message += "</ul>"
return message
def _purchase_request_confirm_message(self):
request_obj = self.env["purchase.request"]
for po in self:
requests_dict = {}
for line in po.order_line:
for request_line in line.sudo().purchase_request_lines:
request_id = request_line.request_id.id
if request_id not in requests_dict:
requests_dict[request_id] = {}
date_planned = "%s" % line.date_planned
data = {
"name": request_line.name,
"product_qty": line.product_qty,
"product_uom": line.product_uom.name,
"date_planned": date_planned,
}
requests_dict[request_id][request_line.id] = data
for request_id in requests_dict:
request = request_obj.sudo().browse(request_id)
message = po._purchase_request_confirm_message_content(
request, requests_dict[request_id]
)
request.message_post(
body=message,
subtype_id=self.env.ref(
"purchase_request.mt_request_po_confirmed"
).id,
)
return True
def _purchase_request_line_check(self):
for po in self:
for line in po.order_line:
for request_line in line.purchase_request_lines:
if request_line.sudo().purchase_state == "done":
raise exceptions.UserError(
_("Purchase Request %s has already been completed")
% (request_line.request_id.name)
)
return True
def button_confirm(self):
self._purchase_request_line_check()
res = super(PurchaseOrder, self).button_confirm()
self._purchase_request_confirm_message()
return res
def unlink(self):
alloc_to_unlink = self.env["purchase.request.allocation"]
for rec in self:
for alloc in (
rec.order_line.mapped("purchase_request_lines")
.mapped("purchase_request_allocation_ids")
.filtered(
lambda alloc, rec=rec: alloc.purchase_line_id.order_id.id == rec.id
)
):
alloc_to_unlink += alloc
res = super().unlink()
alloc_to_unlink.unlink()
return res
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
purchase_request_lines = fields.Many2many(
comodel_name="purchase.request.line",
relation="purchase_request_purchase_order_line_rel",
column1="purchase_order_line_id",
column2="purchase_request_line_id",
readonly=True,
copy=False,
)
purchase_request_allocation_ids = fields.One2many(
comodel_name="purchase.request.allocation",
inverse_name="purchase_line_id",
string="Purchase Request Allocation",
copy=False,
)
def action_open_request_line_tree_view(self):
"""
:return dict: dictionary value for created view
"""
request_line_ids = []
for line in self:
request_line_ids += line.purchase_request_lines.ids
domain = [("id", "in", request_line_ids)]
return {
"name": _("Purchase Request Lines"),
"type": "ir.actions.act_window",
"res_model": "purchase.request.line",
"view_mode": "tree,form",
"domain": domain,
}
def _prepare_stock_moves(self, picking):
self.ensure_one()
val = super(PurchaseOrderLine, self)._prepare_stock_moves(picking)
all_list = []
for v in val:
all_ids = self.env["purchase.request.allocation"].search(
[("purchase_line_id", "=", v["purchase_line_id"])]
)
for all_id in all_ids:
all_list.append((4, all_id.id))
v["purchase_request_allocation_ids"] = all_list
return val
def update_service_allocations(self, prev_qty_received):
for rec in self:
allocation = self.env["purchase.request.allocation"].search(
[
("purchase_line_id", "=", rec.id),
("purchase_line_id.product_id.type", "=", "service"),
]
)
if not allocation:
return
qty_left = rec.qty_received - prev_qty_received
for alloc in allocation:
allocated_product_qty = alloc.allocated_product_qty
if not qty_left:
alloc.purchase_request_line_id._compute_qty()
break
if alloc.open_product_qty <= qty_left:
allocated_product_qty += alloc.open_product_qty
qty_left -= alloc.open_product_qty
alloc._notify_allocation(alloc.open_product_qty)
else:
allocated_product_qty += qty_left
alloc._notify_allocation(qty_left)
qty_left = 0
alloc.write({"allocated_product_qty": allocated_product_qty})
message_data = self._prepare_request_message_data(
alloc, alloc.purchase_request_line_id, allocated_product_qty
)
message = self._purchase_request_confirm_done_message_content(
message_data
)
if message:
alloc.purchase_request_line_id.request_id.message_post(
body=message, subtype_id=self.env.ref("mail.mt_note").id
)
alloc.purchase_request_line_id._compute_qty()
return True
@api.model
def _purchase_request_confirm_done_message_content(self, message_data):
title = _("Service confirmation for Request %s") % (
message_data["request_name"]
)
message = "<h3>%s</h3>" % title
message += _(
"The following requested services from Purchase"
" Request %(request_name)s requested by %(requestor)s "
"have now been received:"
) % {
"request_name": message_data["request_name"],
"requestor": message_data["requestor"],
}
message += "<ul>"
message += _(
"<li><b>%(product_name)s</b>: "
"Received quantity %(product_qty)s %(product_uom)s</li>"
) % {
"product_name": message_data["product_name"],
"product_qty": message_data["product_qty"],
"product_uom": message_data["product_uom"],
}
message += "</ul>"
return message
def _prepare_request_message_data(self, alloc, request_line, allocated_qty):
return {
"request_name": request_line.request_id.name,
"product_name": request_line.product_id.name_get()[0][1],
"product_qty": allocated_qty,
"product_uom": alloc.product_uom_id.name,
"requestor": request_line.request_id.requested_by.partner_id.name,
}
def write(self, vals):
# As services do not generate stock move this tweak is required
# to allocate them.
prev_qty_received = {}
if vals.get("qty_received", False):
service_lines = self.filtered(lambda l: l.product_id.type == "service")
for line in service_lines:
prev_qty_received[line.id] = line.qty_received
res = super(PurchaseOrderLine, self).write(vals)
if prev_qty_received:
for line in service_lines:
line.update_service_allocations(prev_qty_received[line.id])
return res

View file

@ -0,0 +1,317 @@
# Copyright 2018-2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import _, api, fields, models
from odoo.exceptions import UserError
_STATES = [
("draft", "Draft"),
("to_approve", "To be approved"),
("approved", "Approved"),
("in_progress", "In progress"),
("done", "Done"),
("rejected", "Rejected"),
]
class PurchaseRequest(models.Model):
_name = "purchase.request"
_description = "Purchase Request"
_mail_post_access = "read"
_inherit = ["mail.thread", "mail.activity.mixin"]
_order = "id desc"
@api.model
def _company_get(self):
return self.env["res.company"].browse(self.env.company.id)
@api.model
def _get_default_requested_by(self):
return self.env["res.users"].browse(self.env.uid)
@api.model
def _get_default_name(self):
return self.env["ir.sequence"].next_by_code("purchase.request")
@api.model
def _default_picking_type(self):
type_obj = self.env["stock.picking.type"]
company_id = self.env.context.get("company_id") or self.env.company.id
types = type_obj.search(
[("code", "=", "incoming"), ("warehouse_id.company_id", "=", company_id)]
)
if not types:
types = type_obj.search(
[("code", "=", "incoming"), ("warehouse_id", "=", False)]
)
return types[:1]
@api.depends("state")
def _compute_is_editable(self):
for rec in self:
if rec.state in (
"to_approve",
"approved",
"rejected",
"in_progress",
"done",
):
rec.is_editable = False
else:
rec.is_editable = True
name = fields.Char(
string="Request Reference",
required=True,
default=lambda self: _("New"),
tracking=True,
)
is_name_editable = fields.Boolean(
default=lambda self: self.env.user.has_group("base.group_no_one"),
)
origin = fields.Char(string="Source Document")
date_start = fields.Date(
string="Creation date",
help="Date when the user initiated the request.",
default=fields.Date.context_today,
tracking=True,
)
requested_by = fields.Many2one(
comodel_name="res.users",
required=True,
copy=False,
tracking=True,
default=_get_default_requested_by,
index=True,
)
assigned_to = fields.Many2one(
comodel_name="res.users",
string="Approver",
tracking=True,
domain=lambda self: [
(
"groups_id",
"in",
self.env.ref("purchase_request.group_purchase_request_manager").id,
)
],
index=True,
)
description = fields.Text()
company_id = fields.Many2one(
comodel_name="res.company",
required=False,
default=_company_get,
tracking=True,
)
line_ids = fields.One2many(
comodel_name="purchase.request.line",
inverse_name="request_id",
string="Products to Purchase",
readonly=True,
copy=True,
tracking=True,
states={"draft": [("readonly", False)]},
)
product_id = fields.Many2one(
comodel_name="product.product",
related="line_ids.product_id",
string="Product",
readonly=True,
)
state = fields.Selection(
selection=_STATES,
string="Status",
index=True,
tracking=True,
required=True,
copy=False,
default="draft",
)
is_editable = fields.Boolean(compute="_compute_is_editable", readonly=True)
to_approve_allowed = fields.Boolean(compute="_compute_to_approve_allowed")
picking_type_id = fields.Many2one(
comodel_name="stock.picking.type",
string="Picking Type",
required=True,
default=_default_picking_type,
)
group_id = fields.Many2one(
comodel_name="procurement.group",
string="Procurement Group",
copy=False,
index=True,
)
line_count = fields.Integer(
string="Purchase Request Line count",
compute="_compute_line_count",
readonly=True,
)
move_count = fields.Integer(
string="Stock Move count", compute="_compute_move_count", readonly=True
)
purchase_count = fields.Integer(
string="Purchases count", compute="_compute_purchase_count", readonly=True
)
currency_id = fields.Many2one(related="company_id.currency_id", readonly=True)
estimated_cost = fields.Monetary(
compute="_compute_estimated_cost",
string="Total Estimated Cost",
store=True,
)
@api.depends("line_ids", "line_ids.estimated_cost")
def _compute_estimated_cost(self):
for rec in self:
rec.estimated_cost = sum(rec.line_ids.mapped("estimated_cost"))
@api.depends("line_ids")
def _compute_purchase_count(self):
for rec in self:
rec.purchase_count = len(rec.mapped("line_ids.purchase_lines.order_id"))
def action_view_purchase_order(self):
action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
lines = self.mapped("line_ids.purchase_lines.order_id")
if len(lines) > 1:
action["domain"] = [("id", "in", lines.ids)]
elif lines:
action["views"] = [
(self.env.ref("purchase.purchase_order_form").id, "form")
]
action["res_id"] = lines.id
return action
@api.depends("line_ids")
def _compute_move_count(self):
for rec in self:
rec.move_count = len(
rec.mapped("line_ids.purchase_request_allocation_ids.stock_move_id")
)
def action_view_stock_picking(self):
action = self.env["ir.actions.actions"]._for_xml_id(
"stock.action_picking_tree_all"
)
# remove default filters
action["context"] = {}
lines = self.mapped(
"line_ids.purchase_request_allocation_ids.stock_move_id.picking_id"
)
if len(lines) > 1:
action["domain"] = [("id", "in", lines.ids)]
elif lines:
action["views"] = [(self.env.ref("stock.view_picking_form").id, "form")]
action["res_id"] = lines.id
return action
@api.depends("line_ids")
def _compute_line_count(self):
for rec in self:
rec.line_count = len(rec.mapped("line_ids"))
def action_view_purchase_request_line(self):
action = (
self.env.ref("purchase_request.purchase_request_line_form_action")
.sudo()
.read()[0]
)
lines = self.mapped("line_ids")
if len(lines) > 1:
action["domain"] = [("id", "in", lines.ids)]
elif lines:
action["views"] = [
(self.env.ref("purchase_request.purchase_request_line_form").id, "form")
]
action["res_id"] = lines.ids[0]
return action
@api.depends("state", "line_ids.product_qty", "line_ids.cancelled")
def _compute_to_approve_allowed(self):
for rec in self:
rec.to_approve_allowed = rec.state == "draft" and any(
not line.cancelled and line.product_qty for line in rec.line_ids
)
def copy(self, default=None):
default = dict(default or {})
self.ensure_one()
default.update({"state": "draft", "name": self._get_default_name()})
return super(PurchaseRequest, self).copy(default)
@api.model
def _get_partner_id(self, request):
user_id = request.assigned_to or self.env.user
return user_id.partner_id.id
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get("name", _("New")) == _("New"):
vals["name"] = self._get_default_name()
requests = super(PurchaseRequest, self).create(vals_list)
for vals, request in zip(vals_list, requests):
if vals.get("assigned_to"):
partner_id = self._get_partner_id(request)
request.message_subscribe(partner_ids=[partner_id])
return requests
def write(self, vals):
res = super(PurchaseRequest, self).write(vals)
for request in self:
if vals.get("assigned_to"):
partner_id = self._get_partner_id(request)
request.message_subscribe(partner_ids=[partner_id])
return res
def _can_be_deleted(self):
self.ensure_one()
return self.state == "draft"
def unlink(self):
for request in self:
if not request._can_be_deleted():
raise UserError(
_("You cannot delete a purchase request which is not draft.")
)
return super(PurchaseRequest, self).unlink()
def button_draft(self):
self.mapped("line_ids").do_uncancel()
return self.write({"state": "draft"})
def button_to_approve(self):
self.to_approve_allowed_check()
return self.write({"state": "to_approve"})
def button_approved(self):
return self.write({"state": "approved"})
def button_rejected(self):
self.mapped("line_ids").do_cancel()
return self.write({"state": "rejected"})
def button_in_progress(self):
return self.write({"state": "in_progress"})
def button_done(self):
return self.write({"state": "done"})
def check_auto_reject(self):
"""When all lines are cancelled the purchase request should be
auto-rejected."""
for pr in self:
if not pr.line_ids.filtered(lambda l: l.cancelled is False):
pr.write({"state": "rejected"})
def to_approve_allowed_check(self):
for rec in self:
if not rec.to_approve_allowed:
raise UserError(
_(
"You can't request an approval for a purchase request "
"which is empty. (%s)"
)
% rec.name
)

View file

@ -0,0 +1,132 @@
# Copyright 2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import _, api, fields, models
class PurchaseRequestAllocation(models.Model):
_name = "purchase.request.allocation"
_description = "Purchase Request Allocation"
purchase_request_line_id = fields.Many2one(
string="Purchase Request Line",
comodel_name="purchase.request.line",
required=True,
ondelete="cascade",
copy=True,
index=True,
)
company_id = fields.Many2one(
string="Company",
comodel_name="res.company",
readonly=True,
related="purchase_request_line_id.request_id.company_id",
store=True,
index=True,
)
stock_move_id = fields.Many2one(
string="Stock Move",
comodel_name="stock.move",
ondelete="cascade",
index=True,
)
purchase_line_id = fields.Many2one(
string="Purchase Line",
comodel_name="purchase.order.line",
copy=True,
ondelete="cascade",
help="Service Purchase Order Line",
index=True,
)
product_id = fields.Many2one(
string="Product",
comodel_name="product.product",
related="purchase_request_line_id.product_id",
readonly=True,
)
product_uom_id = fields.Many2one(
string="UoM",
comodel_name="uom.uom",
related="purchase_request_line_id.product_uom_id",
readonly=True,
required=True,
)
requested_product_uom_qty = fields.Float(
string="Requested Quantity",
help="Quantity of the purchase request line allocated to the"
"stock move, in the UoM of the Purchase Request Line",
)
allocated_product_qty = fields.Float(
string="Allocated Quantity",
copy=False,
help="Quantity of the purchase request line allocated to the stock"
"move, in the default UoM of the product",
)
open_product_qty = fields.Float(
string="Open Quantity", compute="_compute_open_product_qty"
)
purchase_state = fields.Selection(related="purchase_line_id.state")
@api.depends(
"requested_product_uom_qty",
"allocated_product_qty",
"stock_move_id",
"stock_move_id.state",
"stock_move_id.product_uom_qty",
"stock_move_id.move_line_ids.qty_done",
"purchase_line_id",
"purchase_line_id.qty_received",
"purchase_state",
)
def _compute_open_product_qty(self):
for rec in self:
if rec.purchase_state in ["cancel", "done"]:
rec.open_product_qty = 0.0
else:
rec.open_product_qty = (
rec.requested_product_uom_qty - rec.allocated_product_qty
)
if rec.open_product_qty < 0.0:
rec.open_product_qty = 0.0
@api.model
def _purchase_request_confirm_done_message_content(self, message_data):
message = ""
message += _(
"From last reception this quantity has been "
"allocated to this purchase request"
)
message += "<ul>"
message += _(
"<li><b>%(product_name)s</b>: "
"Received quantity %(product_qty)s %(product_uom)s</li>"
) % {
"product_name": message_data["product_name"],
"product_qty": message_data["product_qty"],
"product_uom": message_data["product_uom"],
}
message += "</ul>"
return message
def _prepare_message_data(self, po_line, request, allocated_qty):
return {
"request_name": request.name,
"po_name": po_line.order_id.name,
"product_name": po_line.product_id.name_get()[0][1],
"product_qty": allocated_qty,
"product_uom": po_line.product_uom.name,
}
def _notify_allocation(self, allocated_qty):
if not allocated_qty:
return
for allocation in self:
request = allocation.purchase_request_line_id.request_id
po_line = allocation.purchase_line_id
message_data = self._prepare_message_data(po_line, request, allocated_qty)
message = self._purchase_request_confirm_done_message_content(message_data)
request.message_post(
body=message, subtype_id=self.env.ref("mail.mt_note").id
)

View file

@ -0,0 +1,433 @@
# Copyright 2018-2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import _, api, fields, models
from odoo.exceptions import UserError
_STATES = [
("draft", "Draft"),
("to_approve", "To be approved"),
("approved", "Approved"),
("in_progress", "In progress"),
("done", "Done"),
("rejected", "Rejected"),
]
class PurchaseRequestLine(models.Model):
_name = "purchase.request.line"
_description = "Purchase Request Line"
_inherit = ["mail.thread", "mail.activity.mixin", "analytic.mixin"]
_order = "id desc"
name = fields.Char(string="Description", tracking=True)
product_uom_id = fields.Many2one(
comodel_name="uom.uom",
string="UoM",
tracking=True,
domain="[('category_id', '=', product_uom_category_id)]",
)
product_uom_category_id = fields.Many2one(related="product_id.uom_id.category_id")
product_qty = fields.Float(
string="Quantity", tracking=True, digits="Product Unit of Measure"
)
request_id = fields.Many2one(
comodel_name="purchase.request",
string="Purchase Request",
ondelete="cascade",
readonly=True,
index=True,
auto_join=True,
)
company_id = fields.Many2one(
comodel_name="res.company",
related="request_id.company_id",
string="Company",
store=True,
index=True,
)
requested_by = fields.Many2one(
comodel_name="res.users",
related="request_id.requested_by",
string="Requested by",
store=True,
)
assigned_to = fields.Many2one(
comodel_name="res.users",
related="request_id.assigned_to",
string="Assigned to",
store=True,
)
date_start = fields.Date(related="request_id.date_start", store=True)
description = fields.Text(
related="request_id.description",
string="PR Description",
store=True,
readonly=False,
)
origin = fields.Char(
related="request_id.origin", string="Source Document", store=True
)
date_required = fields.Date(
string="Request Date",
required=True,
tracking=True,
default=fields.Date.context_today,
)
is_editable = fields.Boolean(compute="_compute_is_editable", readonly=True)
specifications = fields.Text()
request_state = fields.Selection(
string="Request state",
related="request_id.state",
store=True,
)
supplier_id = fields.Many2one(
comodel_name="res.partner",
string="Preferred supplier",
compute="_compute_supplier_id",
compute_sudo=True,
store=True,
)
cancelled = fields.Boolean(readonly=True, default=False, copy=False)
purchased_qty = fields.Float(
string="RFQ/PO Qty",
digits="Product Unit of Measure",
compute="_compute_purchased_qty",
)
purchase_lines = fields.Many2many(
comodel_name="purchase.order.line",
relation="purchase_request_purchase_order_line_rel",
column1="purchase_request_line_id",
column2="purchase_order_line_id",
string="Purchase Order Lines",
readonly=True,
copy=False,
)
purchase_state = fields.Selection(
compute="_compute_purchase_state",
string="Purchase Status",
selection=lambda self: self.env["purchase.order"]
._fields["state"]
._description_selection(self.env),
store=True,
)
move_dest_ids = fields.One2many(
comodel_name="stock.move",
inverse_name="created_purchase_request_line_id",
string="Downstream Moves",
)
orderpoint_id = fields.Many2one(
comodel_name="stock.warehouse.orderpoint", string="Orderpoint"
)
purchase_request_allocation_ids = fields.One2many(
comodel_name="purchase.request.allocation",
inverse_name="purchase_request_line_id",
string="Purchase Request Allocation",
)
qty_in_progress = fields.Float(
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty",
store=True,
help="Quantity in progress.",
)
qty_done = fields.Float(
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty",
store=True,
help="Quantity completed",
)
qty_cancelled = fields.Float(
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty_cancelled",
store=True,
help="Quantity cancelled",
)
qty_to_buy = fields.Boolean(
compute="_compute_qty_to_buy",
string="There is some pending qty to buy",
store=True,
)
pending_qty_to_receive = fields.Float(
compute="_compute_qty_to_buy",
digits="Product Unit of Measure",
copy=False,
string="Pending Qty to Receive",
store=True,
)
estimated_cost = fields.Monetary(
currency_field="currency_id",
default=0.0,
help="Estimated cost of Purchase Request Line, not propagated to PO.",
)
currency_id = fields.Many2one(related="company_id.currency_id", readonly=True)
product_id = fields.Many2one(
comodel_name="product.product",
string="Product",
domain=[("purchase_ok", "=", True)],
tracking=True,
)
@api.depends(
"purchase_request_allocation_ids",
"purchase_request_allocation_ids.stock_move_id.state",
"purchase_request_allocation_ids.stock_move_id",
"purchase_request_allocation_ids.purchase_line_id",
"purchase_request_allocation_ids.purchase_line_id.state",
"request_id.state",
"product_qty",
)
def _compute_qty_to_buy(self):
for pr in self:
qty_to_buy = sum(pr.mapped("product_qty")) - sum(pr.mapped("qty_done"))
pr.qty_to_buy = qty_to_buy > 0.0
pr.pending_qty_to_receive = qty_to_buy
@api.depends(
"purchase_request_allocation_ids",
"purchase_request_allocation_ids.stock_move_id.state",
"purchase_request_allocation_ids.stock_move_id",
"purchase_request_allocation_ids.purchase_line_id.state",
"purchase_request_allocation_ids.purchase_line_id",
)
def _compute_qty(self):
for request in self:
done_qty = sum(
request.purchase_request_allocation_ids.mapped("allocated_product_qty")
)
open_qty = sum(
request.purchase_request_allocation_ids.mapped("open_product_qty")
)
request.qty_done = done_qty
request.qty_in_progress = open_qty
@api.depends(
"purchase_request_allocation_ids",
"purchase_request_allocation_ids.stock_move_id.state",
"purchase_request_allocation_ids.stock_move_id",
"purchase_request_allocation_ids.purchase_line_id.order_id.state",
"purchase_request_allocation_ids.purchase_line_id",
)
def _compute_qty_cancelled(self):
for request in self:
if request.product_id.type != "service":
qty_cancelled = sum(
request.mapped("purchase_request_allocation_ids.stock_move_id")
.filtered(lambda sm: sm.state == "cancel")
.mapped("product_qty")
)
else:
qty_cancelled = sum(
request.mapped("purchase_request_allocation_ids.purchase_line_id")
.filtered(lambda sm: sm.state == "cancel")
.mapped("product_qty")
)
# done this way as i cannot track what was received before
# cancelled the purchase order
qty_cancelled -= request.qty_done
if request.product_uom_id:
request.qty_cancelled = (
max(
0,
request.product_id.uom_id._compute_quantity(
qty_cancelled, request.product_uom_id
),
)
if request.purchase_request_allocation_ids
else 0
)
else:
request.qty_cancelled = qty_cancelled
@api.depends(
"purchase_lines",
"request_id.state",
)
def _compute_is_editable(self):
for rec in self:
if rec.request_id.state in (
"to_approve",
"approved",
"rejected",
"in_progress",
"done",
):
rec.is_editable = False
else:
rec.is_editable = True
for rec in self.filtered(lambda p: p.purchase_lines):
rec.is_editable = False
@api.depends("product_id", "product_id.seller_ids")
def _compute_supplier_id(self):
for rec in self:
sellers = rec.product_id.seller_ids.filtered(
lambda si, rec=rec: not si.company_id or si.company_id == rec.company_id
)
rec.supplier_id = sellers[0].partner_id if sellers else False
@api.onchange("product_id")
def onchange_product_id(self):
if self.product_id:
name = self.product_id.name
if self.product_id.code:
name = "[{}] {}".format(self.product_id.code, name)
if self.product_id.description_purchase:
name += "\n" + self.product_id.description_purchase
self.product_uom_id = self.product_id.uom_id.id
self.product_qty = 1
self.name = name
def do_cancel(self):
"""Actions to perform when cancelling a purchase request line."""
self.write({"cancelled": True})
def do_uncancel(self):
"""Actions to perform when uncancelling a purchase request line."""
self.write({"cancelled": False})
def write(self, vals):
res = super(PurchaseRequestLine, self).write(vals)
if vals.get("cancelled"):
requests = self.mapped("request_id")
requests.check_auto_reject()
return res
def _compute_purchased_qty(self):
for rec in self:
rec.purchased_qty = 0.0
for line in rec.purchase_lines.filtered(lambda x: x.state != "cancel"):
if rec.product_uom_id and line.product_uom != rec.product_uom_id:
rec.purchased_qty += line.product_uom._compute_quantity(
line.product_qty, rec.product_uom_id
)
else:
rec.purchased_qty += line.product_qty
@api.depends("purchase_lines.state", "purchase_lines.order_id.state")
def _compute_purchase_state(self):
for rec in self:
temp_purchase_state = False
if rec.purchase_lines:
if any(po_line.state == "done" for po_line in rec.purchase_lines):
temp_purchase_state = "done"
elif all(po_line.state == "cancel" for po_line in rec.purchase_lines):
temp_purchase_state = "cancel"
elif any(po_line.state == "purchase" for po_line in rec.purchase_lines):
temp_purchase_state = "purchase"
elif any(
po_line.state == "to approve" for po_line in rec.purchase_lines
):
temp_purchase_state = "to approve"
elif any(po_line.state == "sent" for po_line in rec.purchase_lines):
temp_purchase_state = "sent"
elif all(
po_line.state in ("draft", "cancel")
for po_line in rec.purchase_lines
):
temp_purchase_state = "draft"
rec.purchase_state = temp_purchase_state
@api.model
def _get_supplier_min_qty(self, product, partner_id=False):
seller_min_qty = 0.0
if partner_id:
seller = product.seller_ids.filtered(
lambda r: r.partner_id == partner_id
).sorted(key=lambda r: r.min_qty)
else:
seller = product.seller_ids.sorted(key=lambda r: r.min_qty)
if seller:
seller_min_qty = seller[0].min_qty
return seller_min_qty
@api.model
def _calc_new_qty(self, request_line, po_line=None, new_pr_line=False):
purchase_uom = po_line.product_uom or request_line.product_id.uom_po_id
# TODO: Not implemented yet.
# Make sure we use the minimum quantity of the partner corresponding
# to the PO. This does not apply in case of dropshipping
supplierinfo_min_qty = 0.0
if not po_line.order_id.dest_address_id:
supplierinfo_min_qty = self._get_supplier_min_qty(
po_line.product_id, po_line.order_id.partner_id
)
rl_qty = 0.0
# Recompute quantity by adding existing running procurements.
if new_pr_line:
rl_qty = po_line.product_uom_qty
else:
for prl in po_line.purchase_request_lines:
for alloc in prl.purchase_request_allocation_ids:
rl_qty += alloc.product_uom_id._compute_quantity(
alloc.requested_product_uom_qty, purchase_uom
)
qty = max(rl_qty, supplierinfo_min_qty)
return qty
def _can_be_deleted(self):
self.ensure_one()
return self.request_state == "draft"
def unlink(self):
if self.mapped("purchase_lines"):
raise UserError(
_("You cannot delete a record that refers to purchase lines!")
)
for line in self:
if not line._can_be_deleted():
raise UserError(
_(
"You can only delete a purchase request line "
"if the purchase request is in draft state."
)
)
return super(PurchaseRequestLine, self).unlink()
def action_show_details(self):
self.ensure_one()
view = self.env.ref("purchase_request.view_purchase_request_line_details")
return {
"name": _("Detailed Line"),
"type": "ir.actions.act_window",
"view_mode": "form",
"res_model": "purchase.request.line",
"views": [(view.id, "form")],
"view_id": view.id,
"target": "new",
"res_id": self.id,
"context": dict(
self.env.context,
),
}
@api.model
def _get_analytic_name(self):
return (
[
"%(name)s (%(value)s)"
% {
"name": self.env["account.analytic.account"]
.browse(int(key))
.display_name,
"value": value,
}
for key, value in self.analytic_distribution.items()
]
if self.analytic_distribution
else [""]
)
@api.model
def _get_analytic_distribution(self):
self.ensure_one()
name = ", ".join(filter(None, self._get_analytic_name()))
return name

Some files were not shown because too many files have changed in this diff Show more