From 4d7b749e9a16704f75b4f79f136421a537878629 Mon Sep 17 00:00:00 2001 From: Aaron Riedener Date: Fri, 14 Sep 2018 20:19:28 +0200 Subject: [PATCH] [Issue #174] several adjustment to fix issues created by the massive change in the models. started to create the calculations for the final presentation of the planned vs effective chart --- koalixcrm/crm/admin.py | 4 + koalixcrm/crm/reporting/agreement.py | 36 +++++---- koalixcrm/crm/reporting/agreement_status.py | 2 +- koalixcrm/crm/reporting/estimation.py | 40 ++++++---- koalixcrm/crm/reporting/estimation_status.py | 49 ++++++++++++ .../crm/reporting/generic_project_link.py | 2 +- koalixcrm/crm/reporting/human_resource.py | 3 + koalixcrm/crm/reporting/project.py | 80 ++++++++++++++----- koalixcrm/crm/reporting/reporting_period.py | 3 +- koalixcrm/crm/reporting/resource.py | 9 +++ koalixcrm/crm/reporting/task.py | 12 +-- koalixcrm/crm/reporting/work.py | 31 +++++-- koalixcrm/crm/views/pdfexport.py | 7 +- 13 files changed, 210 insertions(+), 68 deletions(-) create mode 100644 koalixcrm/crm/reporting/estimation_status.py diff --git a/koalixcrm/crm/admin.py b/koalixcrm/crm/admin.py index 93c61d02..2c73d0a8 100644 --- a/koalixcrm/crm/admin.py +++ b/koalixcrm/crm/admin.py @@ -22,6 +22,8 @@ from koalixcrm.crm.reporting.task import Task, TaskAdminView from koalixcrm.crm.reporting.task_link_type import TaskLinkType, OptionTaskLinkType from koalixcrm.crm.reporting.task_status import TaskStatus, OptionTaskStatus +from koalixcrm.crm.reporting.estimation_status import EstimationStatus, EstimationStatusAdminView +from koalixcrm.crm.reporting.agreement_status import AgreementStatus, AgreementStatusAdminView from koalixcrm.crm.reporting.resource_type import ResourceType, ResourceTypeAdminView from koalixcrm.crm.reporting.human_resource import HumanResource, HumanResourceAdminView from koalixcrm.crm.reporting.resource_manager import ResourceManager, ResourceManagerAdminView @@ -57,6 +59,8 @@ admin.site.register(Task, TaskAdminView) admin.site.register(TaskLinkType, OptionTaskLinkType) admin.site.register(TaskStatus, OptionTaskStatus) +admin.site.register(EstimationStatus, EstimationStatusAdminView) +admin.site.register(AgreementStatus, AgreementStatusAdminView) admin.site.register(Work, WorkAdminView) admin.site.register(HumanResource, HumanResourceAdminView) admin.site.register(ResourceType, ResourceTypeAdminView) diff --git a/koalixcrm/crm/reporting/agreement.py b/koalixcrm/crm/reporting/agreement.py index 9bbfc2aa..1c8b50f3 100644 --- a/koalixcrm/crm/reporting/agreement.py +++ b/koalixcrm/crm/reporting/agreement.py @@ -14,19 +14,19 @@ class Agreement(models.Model): resource = models.ForeignKey("Resource") unit = models.ForeignKey("Unit") costs = models.ForeignKey(ResourcePrice) - agreement_type = models.ForeignKey("AgreementType") - agreement_status = models.ForeignKey("AgreementStatus") - agreement_from = models.DateField(verbose_name=_("Agreement From"), - blank=False, - null=False) - agreement_to = models.DateField(verbose_name=_("Agreement To"), - blank=False, - null=False) - agreement_amount = models.DecimalField(verbose_name=_("Amount"), - max_digits=5, - decimal_places=2, - blank=True, - null=True) + type = models.ForeignKey("AgreementType") + status = models.ForeignKey("AgreementStatus") + date_from = models.DateField(verbose_name=_("Agreement From"), + blank=False, + null=False) + date_until = models.DateField(verbose_name=_("Agreement To"), + blank=False, + null=False) + amount = models.DecimalField(verbose_name=_("Amount"), + max_digits=5, + decimal_places=2, + blank=True, + null=True) def calculated_costs(self): currency = self.task.project.default_currency @@ -35,7 +35,7 @@ def calculated_costs(self): self.product.get_costs(self, date, unit, currency) def __str__(self): - return _("Estimation of Resource Consumption") + ": " + str(self.id) + return _("Agreement of Resource Consumption") + ": " + str(self.id) class Meta: app_label = "crm" @@ -49,9 +49,11 @@ class AgreementInlineAdminView(admin.TabularInline): (_('Work'), { 'fields': ('task', 'resource', - 'agreement_amount', - 'agreement_from', - 'agreement_to') + 'amount', + 'date_from', + 'date_until', + 'type', + 'status') }), ) extra = 1 diff --git a/koalixcrm/crm/reporting/agreement_status.py b/koalixcrm/crm/reporting/agreement_status.py index 3989e0de..f22262ad 100644 --- a/koalixcrm/crm/reporting/agreement_status.py +++ b/koalixcrm/crm/reporting/agreement_status.py @@ -25,7 +25,7 @@ def __str__(self): return str(self.id) + " " + str(self.title) -class OptionProjectStatus(admin.ModelAdmin): +class AgreementStatusAdminView(admin.ModelAdmin): list_display = ('id', 'title', 'description', diff --git a/koalixcrm/crm/reporting/estimation.py b/koalixcrm/crm/reporting/estimation.py index d84fb466..5d2d553b 100644 --- a/koalixcrm/crm/reporting/estimation.py +++ b/koalixcrm/crm/reporting/estimation.py @@ -12,22 +12,30 @@ class Estimation(models.Model): blank=False, null=False) resource = models.ForeignKey("Resource") - estimation_from = models.DateField(verbose_name=_("Estimation From"), - blank=False, - null=False) - estimation_to = models.DateField(verbose_name=_("Estimation To"), - blank=False, - null=False) - estimation_amount = models.DecimalField(verbose_name=_("Amount"), - max_digits=5, - decimal_places=2, - blank=True, - null=True) + date_from = models.DateField(verbose_name=_("Estimation From"), + blank=False, + null=False) + date_until = models.DateField(verbose_name=_("Estimation To"), + blank=False, + null=False) + amount = models.DecimalField(verbose_name=_("Amount"), + max_digits=5, + decimal_places=2, + blank=True, + null=True) + status = models.ForeignKey("EstimationStatus", + verbose_name=_('Status of the estimation'), + blank=False, + null=False) + reporting_period = models.ForeignKey("ReportingPeriod", + verbose_name=_('Reporting Period based on which the estimation was done'), + blank=False, + null=False) def calculated_costs(self): currency = self.task.project.default_currency unit = Unit.objects.filter(short_name="hrs") - date = self.estimation_from + date = self.date_from self.product.get_costs(self, unit, date, currency) @@ -46,9 +54,11 @@ class EstimationInlineAdminView(admin.TabularInline): (_('Work'), { 'fields': ('task', 'resource', - 'estimation_amount', - 'estimation_from', - 'estimation_to') + 'amount', + 'date_from', + 'date_until', + 'status', + 'reporting_period') }), ) extra = 1 diff --git a/koalixcrm/crm/reporting/estimation_status.py b/koalixcrm/crm/reporting/estimation_status.py new file mode 100644 index 00000000..bf5dbc27 --- /dev/null +++ b/koalixcrm/crm/reporting/estimation_status.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.utils.translation import ugettext as _ +from django.contrib import admin +from rest_framework import serializers + + +class EstimationStatus(models.Model): + title = models.CharField(verbose_name=_("Title"), + max_length=250, + blank=False, + null=False) + description = models.TextField(verbose_name=_("Text"), + blank=True, + null=True) + is_obsolete = models.BooleanField(verbose_name=_("Status represents estimation is obsolete"),) + + class Meta: + app_label = "crm" + verbose_name = _('Estimation Status') + verbose_name_plural = _('Estimation Status') + + def __str__(self): + return str(self.id) + " " + str(self.title) + + +class EstimationStatusAdminView(admin.ModelAdmin): + list_display = ('id', + 'title', + 'description', + 'is_obsolete') + + fieldsets = ( + (_('Agreement Status'), { + 'fields': ('title', + 'description', + 'is_obsolete') + }), + ) + save_as = True + + +class EstimationStatusJSONSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = EstimationStatus + fields = ('id', + 'title', + 'description',) diff --git a/koalixcrm/crm/reporting/generic_project_link.py b/koalixcrm/crm/reporting/generic_project_link.py index a9a1a5ed..d1fd18ed 100644 --- a/koalixcrm/crm/reporting/generic_project_link.py +++ b/koalixcrm/crm/reporting/generic_project_link.py @@ -37,7 +37,7 @@ class Meta: verbose_name_plural = _('Project Links') -class InlineGenericLinks(admin.TabularInline): +class GenericLinkInlineAdminView(admin.TabularInline): model = GenericProjectLink readonly_fields = ('project_link_type', 'content_type', diff --git a/koalixcrm/crm/reporting/human_resource.py b/koalixcrm/crm/reporting/human_resource.py index 2ba8bddd..926b2eea 100644 --- a/koalixcrm/crm/reporting/human_resource.py +++ b/koalixcrm/crm/reporting/human_resource.py @@ -15,6 +15,9 @@ class HumanResource(Resource): user = models.ForeignKey(UserExtension, verbose_name=_("User")) + def __str__(self): + return self.user.__str__() + def serialize_to_xml(self, **kwargs): date_from = kwargs.get('date_from', datetime.date.today()-datetime.timedelta(days=60)) date_to = kwargs.get('date_to', datetime.date.today()) diff --git a/koalixcrm/crm/reporting/project.py b/koalixcrm/crm/reporting/project.py index a893f4b7..34e0250c 100644 --- a/koalixcrm/crm/reporting/project.py +++ b/koalixcrm/crm/reporting/project.py @@ -5,7 +5,8 @@ from django.contrib import admin from django.utils.translation import ugettext as _ from django.utils.html import format_html -from koalixcrm.crm.reporting.generic_project_link import InlineGenericLinks +from koalixcrm.crm.reporting.generic_project_link import GenericLinkInlineAdminView +from koalixcrm.crm.reporting.reporting_period import ReportingPeriodInlineAdminView from koalixcrm.crm.reporting.task import TaskInlineAdminView from koalixcrm.crm.documents.pdf_export import PDFExport from koalixcrm.crm.exceptions import TemplateSetMissingInContract @@ -75,7 +76,7 @@ def get_xsl_file(self, template_set): def get_reporting_period(self, search_date): from koalixcrm.crm.reporting.reporting_period import ReportingPeriod - """Returns the reporting period that is currently valid. Valid is a reporting period when the provided date + """Returns the reporting period that is valid. Valid is a reporting period when the provided date lies between begin and end of the reporting period Args: @@ -109,7 +110,7 @@ def serialize_to_xml(self, **kwargs): main_xml = PDFExport.append_element_to_pattern(main_xml, "object/[@model='crm.project']", "Planned_Effort", - self.planned_effort()) + self.planned_costs()) main_xml = PDFExport.append_element_to_pattern(main_xml, "object/[@model='crm.project']", "Effective_Duration", @@ -120,10 +121,13 @@ def serialize_to_xml(self, **kwargs): self.planned_duration()) return main_xml - def effective_effort_overall(self): - return self.effective_effort(reporting_period=None) - effective_effort_overall.short_description = _("Effective Effort [hrs]") - effective_effort_overall.tags = True + def effective_accumulated_costs(self, reporting_period=None): + effective_effort_accumulated = 0 + for task in Task.objects.filter(project=self.id): + effective_effort_accumulated += task.effective_acucumulated_costs(reporting_period=reporting_period) + return effective_effort_accumulated + effective_accumulated_costs.short_description = _("Effective Effort [hrs]") + effective_accumulated_costs.tags = True def effective_effort(self, reporting_period): effective_effort_accumulated = 0 @@ -131,13 +135,25 @@ def effective_effort(self, reporting_period): effective_effort_accumulated += task.effective_effort(reporting_period=reporting_period) return effective_effort_accumulated - def planned_effort(self): - planned_effort_accumulated = 0 - for task in Task.objects.filter(project=self.id): - planned_effort_accumulated += task.planned_costs() + def planned_costs(self, reporting_period=None): + """The function return the planned overall costs + + Args: + no arguments + + Returns: + planned costs (String) + + Raises: + No exceptions planned""" + planned_effort_accumulated = "0" + all_project_tasks = Task.objects.filter(project=self.id) + if all_project_tasks: + for task in all_project_tasks: + planned_effort_accumulated += task.planned_costs(reporting_period) return planned_effort_accumulated - planned_effort.short_description = _("Planned Effort [hrs]") - planned_effort.tags = True + planned_costs.short_description = _("Planned Costs") + planned_costs.tags = True def effective_start(self): """The function return the effective start of a project as a date @@ -236,10 +252,23 @@ def effective_duration(self): effective_duration.tags = True def planned_start(self): + """ The function return planned overall start of a project as a date + the function finds all tasks within this project and finds the earliest start date. + when no task is attached the task has which are attached have no start_date set, the + function returns a None value + + Args: + no arguments + + Returns: + planned_end (datetime.Date) or None + + Raises: + No exceptions planned""" tasks = Task.objects.filter(project=self.id) if tasks: i = 0 - project_start = datetime.today() + project_start = None for task in tasks: if task.planned_start(): if i == 0: @@ -252,10 +281,23 @@ def planned_start(self): return None def planned_end(self): + """T he function return planned overall end of a project as a date + the function finds all tasks within this project and finds the latest start end_date. + when no task is attached the task has which are attached have no end_date set, the + function returns a None value + + Args: + no arguments + + Returns: + planned_end (datetime.Date) + + Raises: + No exceptions planned""" tasks = Task.objects.filter(project=self.id) if tasks: i = 0 - project_end = datetime.today() + project_end = None for task in tasks: if task.planned_end(): if i == 0: @@ -345,7 +387,7 @@ class ProjectAdminView(admin.ModelAdmin): 'default_currency', 'effective_effort_overall', 'planned_duration', - 'effective_duration') + 'effective_duration',) list_display_links = ('id',) ordering = ('-id',) @@ -357,11 +399,13 @@ class ProjectAdminView(admin.ModelAdmin): 'project_name', 'description', 'default_currency', - 'default_template_set') + 'default_template_set',) }), ) - inlines = [TaskInlineAdminView, InlineGenericLinks] + inlines = [TaskInlineAdminView, + GenericLinkInlineAdminView, + ReportingPeriodInlineAdminView] actions = ['create_report_pdf', ] def save_model(self, request, obj, form, change): diff --git a/koalixcrm/crm/reporting/reporting_period.py b/koalixcrm/crm/reporting/reporting_period.py index 136c9856..8c29d4b1 100644 --- a/koalixcrm/crm/reporting/reporting_period.py +++ b/koalixcrm/crm/reporting/reporting_period.py @@ -195,7 +195,7 @@ def create_report_pdf(self, request, queryset): create_report_pdf.short_description = _("Create Report PDF") -class InlineReportingPeriod(admin.TabularInline): +class ReportingPeriodInlineAdminView(admin.TabularInline): model = ReportingPeriod fieldsets = ( (_('ReportingPeriod'), { @@ -206,7 +206,6 @@ class InlineReportingPeriod(admin.TabularInline): 'status') }), ) - extra = 0 def has_add_permission(self, request): return False diff --git a/koalixcrm/crm/reporting/resource.py b/koalixcrm/crm/reporting/resource.py index a7b4f932..2093528b 100644 --- a/koalixcrm/crm/reporting/resource.py +++ b/koalixcrm/crm/reporting/resource.py @@ -13,3 +13,12 @@ class Resource(models.Model): verbose_name=_("Resource Type"), blank=True, null=True) + + def __str__(self): + from koalixcrm.crm.reporting.human_resource import HumanResource + human_resource = HumanResource.objects.get(id=self.id) + if human_resource: + return human_resource.__str__() + else: + return "Resource" + diff --git a/koalixcrm/crm/reporting/task.py b/koalixcrm/crm/reporting/task.py index 77cd2da7..85020d4a 100644 --- a/koalixcrm/crm/reporting/task.py +++ b/koalixcrm/crm/reporting/task.py @@ -87,9 +87,9 @@ def planned_start(self): else: for estimation in all_task_estimations: if not planned_task_start: - planned_task_start = estimation.estimation_from - elif estimation.estimation_from < planned_task_start: - planned_task_start = estimation.estimation_from + planned_task_start = estimation.date_from + elif estimation.date_from < planned_task_start: + planned_task_start = estimation.date_from return planned_task_start planned_start.short_description = _("Planned Start") planned_start.tags = True @@ -113,9 +113,9 @@ def planned_end(self): else: for estimation in all_task_estimations: if not planned_task_end: - planned_task_end = estimation.estimation_to - elif estimation.estimation_to < planned_task_end: - planned_task_end = estimation.estimation_to + planned_task_end = estimation.date_until + elif estimation.date_until < planned_task_end: + planned_task_end = estimation.date_until return planned_task_end planned_start.short_description = _("Planned End") planned_start.tags = True diff --git a/koalixcrm/crm/reporting/work.py b/koalixcrm/crm/reporting/work.py index ac2d79e5..84a334e7 100644 --- a/koalixcrm/crm/reporting/work.py +++ b/koalixcrm/crm/reporting/work.py @@ -13,18 +13,35 @@ class Work(models.Model): human_resource = models.ForeignKey("HumanResource") - date = models.DateField(verbose_name=_("Date"), blank=False, null=False) - start_time = models.DateTimeField(verbose_name=_("Start Time"), blank=True, null=True) - stop_time = models.DateTimeField(verbose_name=_("Stop Time"), blank=True, null=True) + date = models.DateField(verbose_name=_("Date"), + blank=False, + null=False) + start_time = models.DateTimeField(verbose_name=_("Start Time"), + blank=True, + null=True) + stop_time = models.DateTimeField(verbose_name=_("Stop Time"), + blank=True, + null=True) worked_hours = models.DecimalField(verbose_name=_("Worked Hours"), max_digits=5, decimal_places=2, blank=True, null=True) - short_description = models.CharField(verbose_name=_("Short Description"), max_length=300, blank=False, null=False) - description = models.TextField(verbose_name=_("Text"), blank=True, null=True) - task = models.ForeignKey("Task", verbose_name=_('Task'), blank=False, null=False) - reporting_period = models.ForeignKey("ReportingPeriod", verbose_name=_('Reporting Period'), blank=False, null=False) + short_description = models.CharField(verbose_name=_("Short Description"), + max_length=300, + blank=False, + null=False) + description = models.TextField(verbose_name=_("Text"), + blank=True, + null=True) + task = models.ForeignKey("Task", + verbose_name=_('Task'), + blank=False, + null=False) + reporting_period = models.ForeignKey("ReportingPeriod", + verbose_name=_('Reporting Period'), + blank=False, + null=False) def link_to_work(self): if self.id: diff --git a/koalixcrm/crm/views/pdfexport.py b/koalixcrm/crm/views/pdfexport.py index 779a6bd5..1aafdf8e 100644 --- a/koalixcrm/crm/views/pdfexport.py +++ b/koalixcrm/crm/views/pdfexport.py @@ -37,6 +37,7 @@ def export_pdf(calling_model_admin, request, source, redirect_to, template_to_us response = HttpResponse(FileWrapper(open(pdf, 'rb')), content_type='application/pdf') response['Content-Length'] = path.getsize(pdf) except (TemplateSetMissing, + TemplateSetMissingInContract, UserExtensionMissing, CalledProcessError, UserExtensionEmailAddressMissing, @@ -55,7 +56,11 @@ def export_pdf(calling_model_admin, request, source, redirect_to, template_to_us level=messages.ERROR) elif isinstance(e, TemplateSetMissing): response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, _("Templateset Missing"), + calling_model_admin.message_user(request, _("Template-set Missing"), + level=messages.ERROR) + elif isinstance(e, TemplateSetMissingInContract): + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, _("Template-set Missing"), level=messages.ERROR) elif isinstance(e, TemplateFOPConfigFileMissing): response = HttpResponseRedirect(redirect_to)