Skip to content

Commit 93f8946

Browse files
digaivs-cetmix
diga
authored andcommitted
[ADD] hr_timesheet_purchase_order: Add new module to create purchase order from timesheets
1 parent 18ef68b commit 93f8946

24 files changed

+553
-0
lines changed
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
**This file is going to be generated by oca-gen-addon-readme.**
2+
3+
*Manual changes will be overwritten.*
4+
5+
Please provide content in the ``readme`` directory:
6+
7+
* **DESCRIPTION.rst** (required)
8+
* INSTALL.rst (optional)
9+
* CONFIGURE.rst (optional)
10+
* **USAGE.rst** (optional, highly recommended)
11+
* DEVELOP.rst (optional)
12+
* ROADMAP.rst (optional)
13+
* HISTORY.rst (optional, recommended)
14+
* **CONTRIBUTORS.rst** (optional, highly recommended)
15+
* CREDITS.rst (optional)
16+
17+
Content of this README will also be drawn from the addon manifest,
18+
from keys such as name, authors, maintainers, development_status,
19+
and license.
20+
21+
A good, one sentence summary in the manifest is also highly recommended.
22+
23+
24+
Automatic changelog generation
25+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26+
27+
`HISTORY.rst` can be auto generated using `towncrier <https://pypi.org/project/towncrier>`_.
28+
29+
Just put towncrier compatible changelog fragments into `readme/newsfragments`
30+
and the changelog file will be automatically generated and updated when a new fragment is added.
31+
32+
Please refer to `towncrier` documentation to know more.
33+
34+
NOTE: the changelog will be automatically generated when using `/ocabot merge $option`.
35+
If you need to run it manually, refer to `OCA/maintainer-tools README <https://github.com/OCA/maintainer-tools>`_.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "HR Timesheet Purchase Order",
3+
"version": "14.0.1.0.0",
4+
"summary": "HR Timesheet Purchase Order",
5+
"author": "Ooops, Cetmix, Odoo Community Association (OCA)",
6+
"license": "AGPL-3",
7+
"category": "Human Resources",
8+
"website": "https://github.com/OCA/timesheet",
9+
"depends": [
10+
"hr_timesheet_sheet",
11+
"purchase",
12+
],
13+
"external_dependencies": {},
14+
"demo": [],
15+
"data": [
16+
"data/ir_actions_server.xml",
17+
"views/hr_employee_view.xml",
18+
"views/hr_timesheet_sheet_view.xml",
19+
"views/purchase_order_view.xml",
20+
"views/res_config_settings_view.xml",
21+
],
22+
"qweb": [],
23+
"installable": True,
24+
"application": False,
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<odoo>
3+
4+
<record
5+
id="ir_actions_server_action_create_purchase_order"
6+
model="ir.actions.server"
7+
>
8+
<field name="name">Create Purchase Order</field>
9+
<field name="model_id" ref="model_hr_timesheet_sheet" />
10+
<field name="binding_model_id" ref="model_hr_timesheet_sheet" />
11+
<field name="binding_view_types">list</field>
12+
<field name="state">code</field>
13+
<field name="code">
14+
action = records.action_create_purchase_order()
15+
</field>
16+
</record>
17+
18+
</odoo>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from . import hr_employee_base
2+
from . import hr_timesheet_sheet
3+
from . import res_config_settings
4+
from . import res_company
5+
from . import purchase_order
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from odoo import fields, models
2+
3+
4+
class HrEmployeeBase(models.AbstractModel):
5+
_inherit = "hr.employee.base"
6+
7+
allow_generate_purchase_order = fields.Boolean(
8+
string="Generate POs from Timesheet", default=False
9+
)
10+
billing_partner_id = fields.Many2one("res.partner")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from odoo import _, fields, models
2+
from odoo.exceptions import UserError
3+
4+
5+
class HrTimesheetSheet(models.Model):
6+
_inherit = "hr_timesheet.sheet"
7+
8+
purchase_order_id = fields.Many2one("purchase.order", readonly=True)
9+
allow_generate_purchase_order = fields.Boolean(
10+
related="employee_id.allow_generate_purchase_order"
11+
)
12+
13+
def action_create_purchase_order(self):
14+
"""
15+
Create Purchase Order
16+
"""
17+
purchase_order_obj = self.env["purchase.order"].sudo()
18+
order_count = 0
19+
group_by_employee = {}
20+
for record in self:
21+
group_by_employee.setdefault(record.employee_id, []).append(record)
22+
for employee, timesheets in group_by_employee.items():
23+
if any([timesheet.purchase_order_id for timesheet in timesheets]):
24+
raise UserError(
25+
_(
26+
"One of the Timesheet Sheets selected for employee {} "
27+
"is already related to a PO.",
28+
).format(
29+
employee.name,
30+
)
31+
)
32+
if not employee.allow_generate_purchase_order:
33+
raise UserError(
34+
_(
35+
"Employee {} is not enabled for PO creation from Timesheet Sheets."
36+
).format(
37+
employee.name,
38+
)
39+
)
40+
if not employee.billing_partner_id:
41+
raise UserError(
42+
_("Not specified billing partner for the employee: {}.").format(
43+
employee.name,
44+
)
45+
)
46+
if not all([timesheet.state == "done" for timesheet in timesheets]):
47+
raise UserError(
48+
_("Timesheet Sheets must be approved to create a PO from them."),
49+
)
50+
51+
order = purchase_order_obj.create(
52+
{
53+
"partner_id": employee.billing_partner_id.id,
54+
"order_line": [
55+
(
56+
0,
57+
0,
58+
{
59+
"product_id": employee.company_id.timesheet_product_id.id,
60+
"product_qty": sum(
61+
[timesheet.total_time for timesheet in timesheets]
62+
),
63+
"price_unit": employee.timesheet_cost,
64+
},
65+
)
66+
],
67+
}
68+
)
69+
order_count += 1
70+
for timesheet in timesheets:
71+
timesheet.purchase_order_id = order.id
72+
return {
73+
"type": "ir.actions.client",
74+
"tag": "display_notification",
75+
"params": {
76+
"type": "success",
77+
"message": _(
78+
"{} POs created from timesheet sheet selected "
79+
"for the following employees: {}",
80+
).format(
81+
order_count,
82+
", ".join([employee.name for employee in group_by_employee.keys()]),
83+
),
84+
"next": {
85+
"type": "ir.actions.act_window_close",
86+
},
87+
},
88+
}
89+
90+
def action_open_purchase_order(self):
91+
"""
92+
Return action to open related Purchase Order
93+
"""
94+
self.ensure_one()
95+
if self.purchase_order_id:
96+
action = self.env["ir.actions.act_window"]._for_xml_id(
97+
"purchase.action_rfq_form"
98+
)
99+
action["res_id"] = self.purchase_order_id.id
100+
action["target"] = "current"
101+
return action
102+
103+
def action_confirm_purchase_order(self):
104+
"""
105+
Confirm purchase orders
106+
"""
107+
self.filtered(lambda rec: rec.purchase_order_id).mapped(
108+
"purchase_order_id"
109+
).sudo().button_confirm()
110+
111+
def action_timesheet_draft(self):
112+
sheets_with_po = self.filtered(lambda sheet: sheet.purchase_order_id)
113+
if sheets_with_po:
114+
raise UserError(
115+
_(
116+
"Cannot set to draft a Timesheet Sheet from which a PO has been created. "
117+
"Please delete the related PO first.",
118+
),
119+
)
120+
super().action_timesheet_draft()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from odoo import fields, models
2+
3+
4+
class PurchaseOrder(models.Model):
5+
_inherit = "purchase.order"
6+
7+
def _compute_timesheet_sheet_count(self):
8+
"""
9+
Compute total timesheet sheets
10+
"""
11+
for order in self:
12+
order.timesheet_sheet_count = len(order.timesheet_sheet_ids)
13+
14+
timesheet_sheet_ids = fields.One2many(
15+
"hr_timesheet.sheet", "purchase_order_id", string="Timesheet Sheets"
16+
)
17+
timesheet_sheet_count = fields.Integer(compute="_compute_timesheet_sheet_count")
18+
19+
def action_open_timesheet_sheet(self):
20+
"""
21+
Open related timesheet sheets
22+
"""
23+
tree_view_ref = self.env.ref(
24+
"hr_timesheet_sheet.hr_timesheet_sheet_tree", raise_if_not_found=False
25+
)
26+
form_view_ref = self.env.ref(
27+
"hr_timesheet_sheet.hr_timesheet_sheet_form", raise_if_not_found=False
28+
)
29+
action = {
30+
"name": "Timesheet Sheets",
31+
"type": "ir.actions.act_window",
32+
"res_model": "hr_timesheet.sheet",
33+
"views": [(tree_view_ref.id, "tree"), (form_view_ref.id, "form")],
34+
"domain": [("id", "in", self.timesheet_sheet_ids.ids)],
35+
"target": "current",
36+
}
37+
return action
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from odoo import fields, models
2+
3+
4+
class ResCompany(models.Model):
5+
_inherit = "res.company"
6+
7+
timesheet_product_id = fields.Many2one(
8+
"product.product",
9+
string="Purchase Timesheet Product",
10+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from odoo import fields, models
2+
3+
4+
class ResConfigSettings(models.TransientModel):
5+
_inherit = "res.config.settings"
6+
7+
timesheet_product_id = fields.Many2one(
8+
"product.product",
9+
related="company_id.timesheet_product_id",
10+
string="Purchase Timesheet Product",
11+
readonly=False,
12+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Select a product that will be used for PO lines *General Settings -> Timesheet -> Purchase Timesheet Product*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* Ooops404 <https://ooops404.com>
2+
* Cetmix <https://cetmix.com>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
============================================
2+
Create purchase orders from timesheet sheets
3+
============================================
4+
5+
This module allows you to create Purchase Orders based on the employee timesheet sheet.
6+
This might be usefull for subcontrating and outsourcing.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
* Go to Employees app > select an employee > go to HR Settings tab and enable the "Generate Purchase Order from timesheet sheet" checkbox
2+
* Select the Billing partner for which the PO will be created
3+
* Click the *Create Purchase Order* in the *Timesheet Sheet* form to create a new RFQ
4+
5+
A server action to create POs is also available in tree view.
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_create_timesheet_purchase_order

0 commit comments

Comments
 (0)