Skip to content

Commit c28cafa

Browse files
[14.0][UPD] deltatech_business_process
1 parent e3ef613 commit c28cafa

13 files changed

Lines changed: 225 additions & 46 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ addon | version | maintainers | summary | price
2222
[deltatech_auto_reorder_rule](deltatech_auto_reorder_rule/) | 14.0.0.0.3 | | Auto create reorder rule | Free
2323
[deltatech_average_payment_period](deltatech_average_payment_period/) | 14.0.1.0.4 | [![dhongu](https://github.com/dhongu.png?size=30px)](https://github.com/dhongu) | Computes average duration of cash accounting | Free
2424
[deltatech_batch_transfer](deltatech_batch_transfer/) | 14.0.0.0.2 | [![danila12](https://github.com/danila12.png?size=30px)](https://github.com/danila12) | Batch transfer improvements | Free
25-
[deltatech_business_process](deltatech_business_process/) | 14.0.1.3.0 | [![dhongu](https://github.com/dhongu.png?size=30px)](https://github.com/dhongu) | Business process | Free
25+
[deltatech_business_process](deltatech_business_process/) | 14.0.1.3.7 | [![dhongu](https://github.com/dhongu.png?size=30px)](https://github.com/dhongu) | Business process | Free
2626
[deltatech_cash](deltatech_cash/) | 14.0.1.0.0 | [![dhongu](https://github.com/dhongu.png?size=30px)](https://github.com/dhongu) | Cash In / Out | Free
2727
[deltatech_cash_statement](deltatech_cash_statement/) | 14.0.3.0.1 | [![dhongu](https://github.com/dhongu.png?size=30px)](https://github.com/dhongu) | Update cash balance | Free
2828
[deltatech_category_group](deltatech_category_group/) | 14.0.0.0.2 | [![danila12](https://github.com/danila12.png?size=30px)](https://github.com/danila12) | Groups for internal categories | Free

deltatech_business_process/README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Business process
77
!! This file is generated by oca-gen-addon-readme !!
88
!! changes will be overwritten. !!
99
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10-
!! source digest: sha256:ac5ff79483e139ab8d219ad62a3d4978cea24a785d19016d1da940bdefb63162
10+
!! source digest: sha256:46aa3d219febd0c9ce97a7225babf101f73c16ce8d2456a3e90d985d7d457790
1111
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1212
1313
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png

deltatech_business_process/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
{
66
"name": "Business process",
77
"summary": "Business process",
8-
"version": "14.0.1.3.0",
8+
"version": "14.0.1.3.7",
99
"author": "Terrabit, Dorin Hongu",
1010
"website": "https://www.terrabit.ro",
1111
"license": "OPL-1",

deltatech_business_process/models/business_development.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ class BusinessDevelopment(models.Model):
1919

2020
project_id = fields.Many2one(string="Project", comodel_name="business.project")
2121
approved = fields.Selection(
22-
[("draft", "Draft"), ("approved", "Approved"), ("rejected", "Rejected"), ("pending", "Pending")],
22+
[
23+
("draft", "Draft"),
24+
("approved", "Approved"),
25+
("rejected", "Rejected"),
26+
("pending", "Pending"),
27+
("awaiting_approval", "Awaiting Approval"),
28+
],
2329
string="Approved",
2430
default="draft",
2531
tracking=True,
@@ -74,13 +80,13 @@ class BusinessDevelopment(models.Model):
7480
development_duration = fields.Float(string="Development duration")
7581
note = fields.Html(string="Note")
7682

77-
@api.model
78-
def create(self, vals):
79-
if not vals.get("code", False):
80-
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
81-
result = super().create(vals)
83+
@api.model_create_multi
84+
def create(self, vals_list):
85+
for vals in vals_list:
86+
if not vals.get("code", False):
87+
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
8288

83-
return result
89+
return super().create(vals_list)
8490

8591
def write(self, vals):
8692
result = super().write(vals)

deltatech_business_process/models/business_issue.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,21 @@ class BusinessIssue(models.Model):
133133
string="Closed by", comodel_name="res.partner", readonly=True, states={"in_test": [("readonly", False)]}
134134
)
135135

136-
@api.model
137-
def create(self, vals):
138-
if not vals.get("code", False):
139-
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
140-
result = super().create(vals)
141-
result.send_mail()
142-
return result
136+
@api.model_create_multi
137+
def create(self, vals_list):
138+
for vals in vals_list:
139+
if not vals.get("code", False):
140+
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
141+
res = super().create(vals_list)
142+
res.send_mail()
143+
return res
143144

144145
def send_mail(self):
145-
today = date.today().strftime("%Y-%m-%d")
146-
self.sudo().message_post(body=f"Date of approval: {today}")
147-
template = self.env.ref("deltatech_business_process.email_template_issue_submitted")
148-
self.env["mail.template"].browse(template.id).send_mail(self.id, force_send=True)
146+
for item in self:
147+
today = date.today().strftime("%Y-%m-%d")
148+
item.sudo().message_post(body=f"Date of approval: {today}")
149+
template = self.env.ref("deltatech_business_process.email_template_issue_submitted")
150+
self.env["mail.template"].browse(template.id).send_mail(item.id, force_send=True)
149151

150152
def name_get(self):
151153
self.browse(self.ids).read(["name", "code"])

deltatech_business_process/models/business_process.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,14 @@ class BusinessProcess(models.Model):
7878
states={"draft": [("readonly", False)], "design": [("readonly", False)]},
7979
)
8080
state = fields.Selection(
81-
[("draft", "Draft"), ("design", "Design"), ("test", "Test"), ("ready", "Ready"), ("production", "Production")],
81+
[
82+
("draft", "Draft"),
83+
("design", "Design"),
84+
("test", "Test"),
85+
("ready", "Ready"),
86+
("production", "Production"),
87+
("abandoned", "Abandoned"),
88+
],
8289
string="State",
8390
default="draft",
8491
tracking=True,
@@ -169,14 +176,16 @@ class BusinessProcess(models.Model):
169176
[("standard", "Standard"), ("custom", "Custom"), ("implementor", "Implementor")], string="Module type"
170177
)
171178

172-
@api.model
173-
def create(self, vals):
174-
if not vals.get("code", False):
175-
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
176-
result = super().create(vals)
177-
if result.area_id.responsible_id and not result.responsible_id:
178-
result.responsible_id = result.area_id.responsible_id
179-
return result
179+
@api.model_create_multi
180+
def create(self, vals_list):
181+
for vals in vals_list:
182+
if not vals.get("code", False):
183+
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
184+
results = super().create(vals_list)
185+
for result in results:
186+
if result.area_id.responsible_id and not result.responsible_id:
187+
result.responsible_id = result.area_id.responsible_id
188+
return results
180189

181190
def name_get(self):
182191
self.browse(self.ids).read(["name", "code"])
@@ -228,6 +237,7 @@ def action_view_tests(self):
228237
domain = [("process_id", "=", self.id)]
229238
context = {
230239
"default_process_id": self.id,
240+
"default_scope": "internal",
231241
}
232242
action = self.env["ir.actions.actions"]._for_xml_id("deltatech_business_process.action_business_process_test")
233243
action.update({"domain": domain, "context": context})
@@ -239,6 +249,7 @@ def action_view_acceptance_tests(self):
239249
domain = [("process_id", "=", self.id), ("scope", "=", "user_acceptance")]
240250
context = {
241251
"default_process_id": self.id,
252+
"default_scope": "user_acceptance",
242253
}
243254
tests = self.env["business.process.test"].search(domain)
244255
if len(tests) == 1:
@@ -395,6 +406,9 @@ def button_go_live(self):
395406
def button_draft(self):
396407
self.write({"state": "draft"})
397408

409+
def button_abandon(self):
410+
self.write({"state": "abandoned"})
411+
398412
def start_internal_test(self):
399413
self._start_test("internal")
400414

deltatech_business_process/models/business_process_step.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ class BusinessProcessStep(models.Model):
7979

8080
details = fields.Html()
8181

82-
@api.model
83-
def create(self, vals):
84-
if not vals.get("code", False):
85-
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
86-
result = super().create(vals)
87-
return result
82+
@api.model_create_multi
83+
def create(self, vals_list):
84+
for vals in vals_list:
85+
if not vals.get("code", False):
86+
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
87+
return super().create(vals_list)
8888

8989
def name_get(self):
9090
self.browse(self.ids).read(["name", "code"])

deltatech_business_process/models/business_process_test.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class BusinessProcessTest(models.Model):
2020
string="Tester",
2121
comodel_name="res.partner",
2222
domain="[('is_company', '=', False)]",
23-
states={"done": [("readonly", True)]},
2423
)
2524
date_start = fields.Date(string="Date start", states={"done": [("readonly", True)]}, default=fields.Date.today)
2625
date_end = fields.Date(string="Date end", states={"done": [("readonly", True)]})

deltatech_business_process/models/business_project.py

Lines changed: 155 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
# © 2023 Deltatech
22
# See README.rst file on addons root folder for license details
3+
import base64
4+
import io
5+
6+
import xlsxwriter
37

48
from odoo import _, api, fields, models
9+
from odoo.exceptions import UserError
10+
11+
# from odoo.tools import date_utils
512

613

714
class BusinessProject(models.Model):
@@ -52,12 +59,44 @@ class BusinessProject(models.Model):
5259
)
5360
project_type = fields.Selection([("remote", "Remote"), ("local", "Local")], string="Project Type", default="remote")
5461

62+
attachment_ids = fields.One2many(
63+
"ir.attachment",
64+
compute="_compute_attachment_ids",
65+
string="Main Attachments",
66+
help="Attachments that don't come from a message.",
67+
)
68+
5569
@api.model
56-
def create(self, vals):
57-
if not vals.get("code", False):
58-
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
59-
result = super().create(vals)
60-
return result
70+
def _get_attachments_search_domain(self, model, res_ids):
71+
return [("res_id", "in", res_ids), ("res_model", "=", model)]
72+
73+
def _compute_attachment_ids(self):
74+
for project in self:
75+
domain = project._get_attachments_search_domain(project._name, project.ids)
76+
attachments = self.env["ir.attachment"].search(domain)
77+
attachments |= project.mapped("message_ids.attachment_ids")
78+
field_name = [
79+
"process_ids",
80+
"process_ids.step_ids",
81+
"process_ids.test_ids",
82+
"process_ids.development_ids",
83+
"process_ids.step_ids.development_ids",
84+
"process_ids.test_ids.test_step_ids",
85+
"process_ids.test_ids.test_step_ids.issue_ids",
86+
]
87+
for field in field_name:
88+
if "attachment_ids" in project.mapped(field):
89+
attachments |= project.mapped(field).mapped("attachment_ids")
90+
if "message_ids" in project.mapped(field):
91+
attachments |= project.mapped(field).mapped("message_ids.attachment_ids")
92+
project.attachment_ids = attachments
93+
94+
@api.model_create_multi
95+
def create(self, vals_list):
96+
for vals in vals_list:
97+
if not vals.get("code", False):
98+
vals["code"] = self.env["ir.sequence"].next_by_code(self._name)
99+
return super().create(vals)
61100

62101
def name_get(self):
63102
self.browse(self.ids).read(["name", "code"])
@@ -111,7 +150,7 @@ def get_attachment_domain(self):
111150
def _compute_attached_docs_count(self):
112151
for order in self:
113152
domain = order.get_attachment_domain()
114-
order.doc_count = self.env["ir.attachment"].search_count(domain)
153+
order.doc_count = self.env["ir.attachment"].sudo().search_count(domain)
115154

116155
def attachment_tree_view(self):
117156
domain = self.get_attachment_domain()
@@ -158,3 +197,113 @@ def calculate_total_project_duration(self):
158197
[("project_id", "=", project.id), ("approved", "not in", ("draft", "rejected"))]
159198
):
160199
project.total_project_duration += development.development_duration
200+
201+
def float_to_time(self, float_hours):
202+
hours = int(float_hours)
203+
minutes = int((float_hours - hours) * 60)
204+
if minutes % 5 != 0:
205+
minutes = round(minutes / 5) * 5
206+
if minutes < 10:
207+
minutes = f"0{minutes}"
208+
if hours < 10:
209+
hours = f"0{hours}"
210+
return f"{hours}:{minutes}"
211+
212+
def generate_excel_report(self):
213+
output = io.BytesIO()
214+
workbook = xlsxwriter.Workbook(output, {"in_memory": True})
215+
worksheet = workbook.add_worksheet()
216+
header_format = workbook.add_format({"bg_color": "#D0F0C0", "bold": True})
217+
red_text_format = workbook.add_format({"font_color": "red"})
218+
219+
# Add headers
220+
headers = [
221+
"Code",
222+
"Name",
223+
"Configuration Duration",
224+
"Training duration",
225+
"Testing duration",
226+
"Data Migration Duration",
227+
"Total Duration",
228+
]
229+
for col_num, header in enumerate(headers):
230+
worksheet.write(0, col_num, header, header_format)
231+
worksheet.set_column(col_num, col_num, len(header) + 2)
232+
233+
area_processes = {}
234+
for process in self.process_ids:
235+
if process.area_id:
236+
if process.area_id not in area_processes:
237+
area_processes[process.area_id] = []
238+
area_processes[process.area_id].append(process)
239+
area_format = workbook.add_format({"bg_color": "#FFFF99", "bold": True, "align": "center"})
240+
241+
row = 1
242+
configuration_duration = 0
243+
instructing_duration = 0
244+
data_migration_duration = 0
245+
duration_for_completion = 0
246+
duration_for_testing = 0
247+
for area in sorted(area_processes.keys(), key=lambda a: a.name):
248+
processes = area_processes[area]
249+
processes.sort(key=lambda p: p.code)
250+
worksheet.merge_range(row, 0, row, 6, f"{area.name} - {len(processes)}", area_format)
251+
row += 1
252+
for process in processes:
253+
format_to_use = red_text_format if process.duration_for_completion == 0 else None
254+
worksheet.write(row, 0, process.code, format_to_use)
255+
worksheet.write(row, 1, process.name, format_to_use)
256+
worksheet.write(row, 2, self.float_to_time(process.configuration_duration), format_to_use)
257+
configuration_duration += process.configuration_duration
258+
worksheet.write(row, 3, self.float_to_time(process.instructing_duration), format_to_use)
259+
instructing_duration += process.instructing_duration
260+
worksheet.write(row, 4, self.float_to_time(process.data_migration_duration), format_to_use)
261+
data_migration_duration += process.data_migration_duration
262+
worksheet.write(row, 5, self.float_to_time(process.testing_duration), format_to_use)
263+
duration_for_testing += process.testing_duration
264+
worksheet.write(row, 6, self.float_to_time(process.duration_for_completion), format_to_use)
265+
duration_for_completion += process.duration_for_completion
266+
row += 1
267+
worksheet.write(row, 1, "Total", header_format)
268+
worksheet.write(row, 2, self.float_to_time(configuration_duration), header_format)
269+
worksheet.write(row, 3, self.float_to_time(instructing_duration), header_format)
270+
worksheet.write(row, 4, self.float_to_time(data_migration_duration), header_format)
271+
worksheet.write(row, 5, self.float_to_time(duration_for_testing), header_format)
272+
worksheet.write(row, 6, self.float_to_time(duration_for_completion), header_format)
273+
# for project in self:
274+
# worksheet.write(row, 0, project.code)
275+
# worksheet.write(row, 1, project.name)
276+
# worksheet.write(row, 2, project.customer_id.name)
277+
# worksheet.write(row, 3, project.state)
278+
# worksheet.write(row, 4, project.date_start and project.date_start.strftime('%Y-%m-%d') or '')
279+
# worksheet.write(row, 5, project.date_go_live and project.date_go_live.strftime('%Y-%m-%d') or '')
280+
# row += 1
281+
# worksheet.autofit()
282+
283+
workbook.close()
284+
output.seek(0)
285+
return output.read()
286+
287+
def action_download_excel_report(self):
288+
active_id = self.env.context.get("active_id")
289+
if not active_id:
290+
raise UserError(_("No active project found."))
291+
292+
project = self.browse(active_id)
293+
excel_data = project.generate_excel_report()
294+
295+
attachment = self.env["ir.attachment"].create(
296+
{
297+
"name": "Project_Report.xlsx",
298+
"type": "binary",
299+
"datas": base64.b64encode(excel_data),
300+
"store_fname": "Project_Report.xlsx",
301+
"mimetype": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
302+
}
303+
)
304+
305+
return {
306+
"type": "ir.actions.act_url",
307+
"url": f"/web/content/{attachment.id}?download=true",
308+
"target": "self",
309+
}

deltatech_business_process/static/description/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ <h1 class="title">Business process</h1>
368368
!! This file is generated by oca-gen-addon-readme !!
369369
!! changes will be overwritten. !!
370370
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
371-
!! source digest: sha256:ac5ff79483e139ab8d219ad62a3d4978cea24a785d19016d1da940bdefb63162
371+
!! source digest: sha256:46aa3d219febd0c9ce97a7225babf101f73c16ce8d2456a3e90d985d7d457790
372372
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
373373
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="https://www.odoo.com/documentation/master/legal/licenses.html"><img alt="License: OPL-1" src="https://img.shields.io/badge/licence-OPL--1-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/dhongu/deltatech/tree/14.0/deltatech_business_process"><img alt="dhongu/deltatech" src="https://img.shields.io/badge/github-dhongu%2Fdeltatech-lightgray.png?logo=github" /></a></p>
374374
<dl class="docutils">

0 commit comments

Comments
 (0)