Add Project Status Report to project module for tracking project progress

This commit is contained in:
younes 2025-04-23 13:50:34 +01:00
parent e0e4ad45a1
commit aa68fd6c3c
7 changed files with 940 additions and 301 deletions

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import models from . import models
from . import wizard from . import wizard
from .import report

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import project_status_report

View File

@ -1,167 +1,454 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<odoo> <odoo>
<template id="project_detail_qweb_report" name="project_detail_qweb_report"> <template id="project_detail_qweb_report" name="project_detail_qweb_report">
<t t-call="web.html_container"> <t t-call="web.html_container">
<t t-foreach="docs" t-as="e"> <t t-foreach="docs" t-as="e">
<t t-call="web.external_layout"> <t t-call="web.external_layout">
<div class="page"> <div class="page">
<form> <form>
<div class="container"> <div class="container">
<tr class="text-center"> <tr class="text-center">
<th> <th>
<t t-if="e.name"> <t t-if="e.name">
<h2 class="text-center"> <h2 class="text-center">
<b> <b>
<span t-field="e.project_no"/> <span t-field="e.project_no"/>
</b> </b>
- -
<b> <b>
<span t-field="e.name"/> <span t-field="e.name"/>
</b> </b>
</h2> </h2>
</t>
</th>
</tr>
<table class="table table-borderless">
<tr>
<td colspan="4" class="text-muted"><h4>Project Info</h4></td>
</tr>
<tr>
<td>
<b>Sale Order</b>
</td>
<td>
<span t-field="e.sale_order_id.name"/>
</td>
<td>
<b>Project Manager</b>
</td>
<td>
<span t-field="e.user_id.name"/>
</td>
</tr>
<tr>
<td>
<b>Customer</b>
</td>
<td>
<div t-field="e.partner_id" t-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: True}"/>
</td>
</tr>
<tr>
<h2>
<b>
<td colspan="4" class="text-muted"><h4>Project Location Info</h4></td>
</b>
</h2>
</tr>
<tr>
<td>
<b>Country</b>
</td>
<td>
<span t-field="e.country_id.name"/>
</td>
</tr>
<tr>
<td>
<b>Project Category</b>
</td>
<td>
<t t-if="e.category_id">
<span t-field="e.category_id.name"/>
</t> </t>
</td> </th>
</tr> </tr>
<tr> <table class="table table-borderless">
<h2>
<b>
<td colspan="4" class="text-muted"><h4>Project Schedule Duration</h4></td>
</b>
</h2>
</tr>
<tr>
<td>
<b>Start Date</b>
</td>
<td>
<span t-field="e.start"/>
</td>
</tr>
<tr>
<td>
<b>Finish Date</b>
</td>
<td>
<span t-field="e.date"/>
</td>
</tr>
<tr>
<td>
<b>Kick-Off Date</b>
</td>
<td>
<span t-field="e.launch_date"/>
</td>
<td>
</td>
<td>
</td>
</tr>
</table>
<p style="page-break-before:always;"> </p>
<table class="table table-bordered">
<tr>
<h2>
<b>
<td colspan="4" class="text-muted"><h4>Project Stages</h4></td>
</b>
</h2>
</tr>
<tr>
<td colspan="2">
<b>#</b>
</td>
<td colspan="2">
<b>stage</b>
</td>
</tr>
<t t-set="counter" t-value="0"/>
<t t-foreach="e.project_phase_ids" t-as="p">
<t t-set="counter" t-value="counter + 1"/>
<tr> <tr>
<td colspan="2"> <td colspan="4" class="text-muted">
<span t-esc="counter"/> <h4>Project Info</h4>
</td>
<td colspan="2">
<span t-esc="p.name"/>
</td> </td>
</tr> </tr>
<tr>
<td>
<b>Sale Order</b>
</td>
<td>
<span t-field="e.sale_order_id.name"/>
</td>
<td>
<b>Project Manager</b>
</td>
<td>
<span t-field="e.user_id.name"/>
</td>
</tr>
<tr>
<td>
<b>Customer</b>
</td>
<td>
<div t-field="e.partner_id"
t-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: True}"/>
</td>
</tr>
<tr>
<h2>
<b>
<td colspan="4" class="text-muted">
<h4>Project Location Info</h4>
</td>
</b>
</h2>
</tr>
<tr>
<td>
<b>Country</b>
</td>
<td>
<span t-field="e.country_id.name"/>
</td>
</tr>
<tr>
<td>
<b>Project Category</b>
</td>
<td>
<t t-if="e.category_id">
<span t-field="e.category_id.name"/>
</t>
</td>
</tr>
<tr>
<h2>
<b>
<td colspan="4" class="text-muted">
<h4>Project Schedule Duration</h4>
</td>
</b>
</h2>
</tr>
<tr>
<td>
<b>Start Date</b>
</td>
<td>
<span t-field="e.start"/>
</td>
</tr>
<tr>
<td>
<b>Finish Date</b>
</td>
<td>
<span t-field="e.date"/>
</td>
</tr>
<tr>
<td>
<b>Kick-Off Date</b>
</td>
<td>
<span t-field="e.launch_date"/>
</td>
<td>
</td>
<td>
</td>
</tr>
</table>
<p style="page-break-before:always;"></p>
<table class="table table-bordered">
<tr>
<h2>
<b>
<td colspan="4" class="text-muted">
<h4>Project Stages</h4>
</td>
</b>
</h2>
</tr>
<tr>
<td colspan="2">
<b>#</b>
</td>
<td colspan="2">
<b>stage</b>
</td>
</tr>
<t t-set="counter" t-value="0"/>
<t t-foreach="e.project_phase_ids" t-as="p">
<t t-set="counter" t-value="counter + 1"/>
<tr>
<td colspan="2">
<span t-esc="counter"/>
</td>
<td colspan="2">
<span t-esc="p.name"/>
</td>
</tr>
</t>
</table>
<table class="table table-borderless">
<tr>
<h2>
<b>
<td colspan="4" class="text-muted">
<h4>Description</h4>
</td>
</b>
</h2>
</tr>
<tr rowspan="4">
<td colspan="4">
<span t-field="e.description"/>
</td>
</tr>
</table>
</div>
</form>
</div>
</t>
</t>
</t>
</template>
<template id="project_status_qweb_report" name="project_status_qweb_report">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="e">
<t t-call="web.external_layout">
<t t-set="e" t-value="e.with_context(lang= e.env.lang)"/>
<style type="text/css">
.report-section {
width: 100%;
padding-right: 15px;
margin-top: 20px;
}
</style>
<div class="page" style="font-family:'Sakkal Majalla'!important;font-size:15pt">
<h2>
<strong>Project Status Report:
<span t-field="e.project_no"/>
</strong>
</h2>
<div class="row" style="display: flex; margin-top: 20px;">
<div style="width: 55%; padding-right: 15px;">
<table class="table table-condensed table-bordered"
style="width: 100%; margin-top: 20px;page-break-inside: avoid;border: 2px solid black; border-collapse: collapse;">
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Project Name</span>
</td>
<td style="border: 1px solid black;">
<span t-field="e.name"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Project Category</span>
</td>
<td style="border: 1px solid black;">
<span t-field="e.category_id.name"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Department</span>
</td>
<td style="border: 1px solid black;">
<span t-field="e.department_id.display_name"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Project Manager</span>
</td>
<td style="border: 1px solid black;">
<span t-field="e.user_id.name"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Executive Department</span>
</td>
<td style="border: 1px solid black;">
<span t-field="e.department_execute_id.display_name"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Project Start Date</span>
</td>
<td style="border: 1px solid black;">
<span t-field="e.start"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Project End Date</span>
</td>
<td style="border: 1px solid black;">
<span t-field="e.date"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Completion Percentage</span>
</td>
<td style="border: 1px solid black;">
<t t-esc="e.progress"/>%
</td>
</tr>
</table>
</div>
<div style="width: 45%; display: flex; align-items: center; justify-content: center; flex-direction: column;">
<t t-if="not chart_map.get(e.id)">
<p style="color: red;">No chart image available</p>
</t> </t>
<img t-if="chart_map.get(e.id)"
t-att-src="'data:image/png;base64,%s' % to_text(chart_map.get(e.id))"
style="max-width: 400px;"/>
</div>
</div>
<div class="report-section">
<h4>
<strong>Project Stages</strong>
</h4>
<table class="table table-condensed table-bordered"
style="width: 100%; margin-top: 20px;page-break-inside: avoid;border: 2px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Stage</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Start Date</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">End Date</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Weight</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Completion</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Task</span>
</th>
</tr>
</thead>
<tbody>
<t t-foreach="e.project_phase_ids" t-as="phase">
<tr>
<td style="border: 1px solid black;">
<span t-esc="phase.phase_id.name"/>
</td>
<td style="border: 1px solid black;">
<span t-esc="phase.start_date"/>
</td>
<td style="border: 1px solid black;">
<span t-esc="phase.end_date"/>
</td>
<td style="border: 1px solid black;">
<span t-esc="phase.weight"/>
</td>
<td style="border: 1px solid black;">
<span><t t-esc="phase.progress"/>%
</span>
</td>
<td style="border: 1px solid black;">
<span t-esc="phase.task_count"/>
</td>
</tr>
</t>
</tbody>
</table> </table>
<table class="table table-borderless"> </div>
<div class="report-section">
<h4>
<strong>Project Payments</strong>
</h4>
<table class="table table-condensed table-bordered"
style="width: 100%; margin-top: 20px;page-break-inside: avoid;border: 2px solid black; border-collapse: collapse;"
>
<tr> <tr>
<h2> <td style="border: 1px solid black;">
<b> <span style="font-weight: bold;">Contract Amount</span>
<td colspan="4" class="text-muted"><h4>Description</h4></td> </td>
</b> <td style="border: 1px solid black;">
</h2> <span t-esc="e.contract_value_untaxed"/>
</tr> </td>
<tr rowspan="4"> <td style="border: 1px solid black;">
<td colspan="4"> <span style="font-weight: bold;">Consultant Fees</span>
<span t-field="e.description"/> </td>
<td style="border: 1px solid black;">
<span t-esc="e.consultant_cost"/>
</td> </td>
</tr> </tr>
</table> <tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Tax amount</span>
</td>
<td style="border: 1px solid black;">
<span t-esc="e.tax_amount"/>
</td>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Total</span>
</td>
<td style="border: 1px solid black;">
<span t-esc="e.contract_value"/>
</td>
</tr>
<tr>
<td style="border: 1px solid black;">
<span style="font-weight: bold;">Additional Work Amount</span>
</td>
<td style="border: 1px solid black;">
<!-- <span t-esc="(e.total_invoiced_amount or 0) - (e.contract_value or 0) + (e.contract_value or 0)"/>-->
<t t-set="invoiced" t-value="e.total_invoiced_amount - e.contract_value"/>
<t t-set="additional_work" t-value="abs(invoiced + e.consultant_cost)"/>
<span t-esc="additional_work"/>
</td>
<td style="border: 1px solid black;"/>
<td style="border: 1px solid black;"/>
</tr>
<tr/>
</table>
<table class="table table-condensed table-bordered"
style="width: 100%; margin-top: 20px;page-break-inside: avoid;border: 2px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Payment</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Due Date</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Amount</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Tax</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Total</span>
</th>
<th style="border: 1px solid black;">
<span style="font-weight: bold;">Payment Status</span>
</th>
</tr>
</thead>
<tbody>
<t t-foreach="e.invoice_ids" t-as="invoice">
<tr>
<td style="border: 1px solid black;">
<span t-esc="invoice.name"/>
</td>
<td style="border: 1px solid black;">
<span t-esc="invoice.plan_date"/>
</td>
<td style="border: 1px solid black;">
<t t-set="subtotal" t-value="0"/>
<t t-foreach="invoice.project_invline_ids" t-as="line">
<t t-set="subtotal" t-value="subtotal + line.price_subtotal"/>
</t>
<span t-esc="subtotal"/>
</td>
<td style="border: 1px solid black;">
<t t-set="tax_total" t-value="0"/>
<t t-foreach="invoice.project_invline_ids" t-as="line">
<t t-set="tax_total" t-value="tax_total + line.price_tax"/>
</t>
<span t-esc="tax_total"/>
</td>
<td style="border: 1px solid black;">
<span t-esc="invoice.amount"/>
</td>
<td style="border: 1px solid black;">
<span t-esc="invoice.payment_state"/>
</td>
</tr>
</t>
</tbody>
</table>
</div>
<div class="report-section">
<h4>
<strong>Project Details</strong>
</h4>
<span t-field="e.description"/>
</div>
</div> </div>
</form> </t>
</div> </t>
</t> </t>
</t> </template>
</t>
</template>
</odoo> </odoo>

View File

@ -8,6 +8,13 @@
file="project_base.project_detail_qweb_report" file="project_base.project_detail_qweb_report"
string="Project Charter"/> string="Project Charter"/>
<report id="report_project_status"
model="project.project"
report_type="qweb-pdf"
name="project_base.project_status_qweb_report"
file="project_base.project_status_qweb_report"
string="Project Status Report"/>
<record id="paperformat_project_invoice" model="report.paperformat"> <record id="paperformat_project_invoice" model="report.paperformat">
<field name="name">A4 for E-Invoice</field> <field name="name">A4 for E-Invoice</field>
<field name="default" eval="False"/> <field name="default" eval="False"/>

View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError, Warning
import matplotlib.pyplot as plt
import io
import base64
import matplotlib
matplotlib.use('Agg')
import logging
import os
import arabic_reshaper
from bidi.algorithm import get_display
from odoo.modules.module import get_module_resource
_logger = logging.getLogger(__name__)
class ReportProjectStatus(models.AbstractModel):
_name = 'report.project_base.project_status_qweb_report'
_description = 'Project Status QWeb Report'
@api.model
def _get_report_values(self, docids, data=None):
projects = self.env['project.project'].browse(docids)
chart_map = {}
for project in projects:
try:
chart = self._get_chart_image(project)
except Exception as e:
_logger.error(f"Chart error for project {project.id}: {str(e)}")
chart = False
chart_map[project.id] = chart
return {
'doc_ids': docids,
'doc_model': 'project.project',
'docs': projects,
'chart_map': chart_map,
}
def _get_chart_image(self, project):
_logger.info(
f"Task counts - New: {project.task_count_new}, In Progress: {project.task_count_inprogress}, Done: {project.task_count_finished}")
task_data = {
'new': project.task_count_new or 0,
'in_progress': project.task_count_inprogress or 0,
'done': project.task_count_finished or 0,
}
if sum(task_data.values()) == 0:
_logger.warning("All task counts are zero, using dummy data")
task_data = {'new': 1, 'in_progress': 1, 'done': 1}
font_path = os.path.join(os.path.dirname(__file__), 'img', 'amiri-regular.ttf')
if not os.path.exists(font_path):
font_path = get_module_resource('project_base', 'static/fonts', 'amiri-regular.ttf')
if not font_path:
_logger.warning("Arabic font not found. Using default font.")
font_path = None
if font_path:
prop = matplotlib.font_manager.FontProperties(fname=font_path)
else:
prop = None
fig = plt.figure(figsize=(5, 4))
def format_arabic(text):
reshaped_text = arabic_reshaper.reshape(text)
bidi_text = get_display(reshaped_text)
return bidi_text
arabicLabels = ['جديد', 'قيد التنفيذ', 'تم']
labels = [format_arabic(label) for label in arabicLabels]
sizes = [task_data['new'], task_data['in_progress'], task_data['done']]
colors = ['#f0312e', '#add8e6', '#90ee90']
plt.pie(sizes, labels=labels, autopct='%1.1f%%', colors=colors, startangle=90,
textprops={'fontproperties': prop, 'fontsize': 16} if prop else {'fontsize': 16})
plt.axis('equal')
plt.rcParams['font.size'] = 18
title_text = format_arabic('احصائيات المهام')
plt.title(title_text, fontproperties=prop if prop else None)
buffer = io.BytesIO()
plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
plt.close('all')
chart_image = base64.b64encode(buffer.getvalue()).decode('utf-8')
buffer.close()
_logger.info(f"Generated chart image with base64 length: {len(chart_image)}")
return chart_image