diff --git a/koalixcrm/crm/documents/calculations.py b/koalixcrm/crm/documents/calculations.py index 89306bb1..9e55133a 100644 --- a/koalixcrm/crm/documents/calculations.py +++ b/koalixcrm/crm/documents/calculations.py @@ -31,7 +31,10 @@ def calculate_document_price(document, pricing_date): if positions.exists(): for position in positions: - price += Calculations.calculate_position_price(position, pricing_date, contact_for_price_calculation, document.currency) + price += Calculations.calculate_position_price(position, + pricing_date, + contact_for_price_calculation, + document.currency) tax += Calculations.calculate_position_tax(position, document.currency) if calculate_with_document_discount: @@ -64,7 +67,10 @@ def calculate_position_price(position, pricing_date, contact, currency): Can trow Product.NoPriceFound when Product Price could not be found""" if not position.overwrite_product_price: - position.position_price_per_unit = position.product.get_price(pricing_date, position.unit, contact, currency) + position.position_price_per_unit = position.product.get_price(pricing_date, + position.unit, + contact, + currency) if isinstance(position.discount, Decimal): position.last_calculated_price = int(position.position_price_per_unit * position.quantity * ( 1 - position.discount / 100) / currency.rounding) * currency.rounding diff --git a/koalixcrm/crm/factories/factory_customer_group.py b/koalixcrm/crm/factories/factory_customer_group.py index bf5a7c16..2b879a48 100644 --- a/koalixcrm/crm/factories/factory_customer_group.py +++ b/koalixcrm/crm/factories/factory_customer_group.py @@ -12,3 +12,9 @@ class Meta: name = factory.Sequence(lambda n: "Customer Group #%s" % n) +class AdvancedCustomerGroupFactory(factory.django.DjangoModelFactory): + class Meta: + model = CustomerGroup + django_get_or_create = ('name',) + + name = factory.Sequence(lambda n: "Customer Group #%s" % n) diff --git a/koalixcrm/crm/factories/factory_customer_group_transformk.py b/koalixcrm/crm/factories/factory_customer_group_transformk.py new file mode 100644 index 00000000..2555b107 --- /dev/null +++ b/koalixcrm/crm/factories/factory_customer_group_transformk.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +import factory +from koalixcrm.crm.product.customer_group_transform import CustomerGroupTransform +from koalixcrm.crm.factories.factory_product import StandardProductFactory +from koalixcrm.crm.factories.factory_customer_group import AdvancedCustomerGroupFactory +from koalixcrm.crm.factories.factory_customer_group import StandardCustomerGroupFactory + + +class StandardCustomerGroupTransformFactory(factory.django.DjangoModelFactory): + class Meta: + model = CustomerGroupTransform + from_customer_group = factory.SubFactory(AdvancedCustomerGroupFactory) + to_customer_group = factory.SubFactory(StandardCustomerGroupFactory) + product = factory.SubFactory(StandardProductFactory) + factor = "10" diff --git a/koalixcrm/crm/factories/factory_product.py b/koalixcrm/crm/factories/factory_product.py new file mode 100644 index 00000000..0f7b563d --- /dev/null +++ b/koalixcrm/crm/factories/factory_product.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +import factory +from koalixcrm.crm.models import Project +from koalixcrm.crm.factories.factory_unit import StandardUnitFactory +from koalixcrm.djangoUserExtension.factories.factory_template_set import StandardTemplateSetFactory +from koalixcrm.crm.factories.factory_user import StaffUserFactory + + +class StandardProductFactory(factory.django.DjangoModelFactory): + class Meta: + model = Project + django_get_or_create = ('project_name',) + + description = models.TextField(verbose_name=_("Description"), + null=True, + blank=True) + title = "This is a test Product" + product_number = "123456" + default_unit = factory.SubFactory(StandardUnitFactory) + date_of_creation = make_date_utc(datetime.datetime(2018, 6, 15, 00)) + last_modification = make_date_utc(datetime.datetime(2018, 6, 15, 00)) + last_modified_by = factory.SubFactory(StaffUserFactory) + tax = factory.SubFactory(StandardUnitFactory) + accounting_product_categorie = models.ForeignKey('accounting.ProductCategorie', + verbose_name=_("Accounting Product Categorie"), + null=True, + blank="True") + diff --git a/koalixcrm/crm/models.py b/koalixcrm/crm/models.py index dfc3a952..d534f318 100644 --- a/koalixcrm/crm/models.py +++ b/koalixcrm/crm/models.py @@ -25,10 +25,12 @@ from koalixcrm.crm.product.currency import * from koalixcrm.crm.product.price import * from koalixcrm.crm.product.product import * +from koalixcrm.crm.product.customer_group_transform import * +from koalixcrm.crm.product.unit_transform import * from koalixcrm.crm.product.tax import * from koalixcrm.crm.product.unit import * -from koalixcrm.crm.reporting.employee_assignment_to_task import * +from koalixcrm.crm.reporting.estimation_of_resource_consumption import * from koalixcrm.crm.reporting.generic_task_link import * from koalixcrm.crm.reporting.task import * from koalixcrm.crm.reporting.task_link_type import * diff --git a/koalixcrm/crm/product/customer_group_transform.py b/koalixcrm/crm/product/customer_group_transform.py new file mode 100644 index 00000000..b8c09b09 --- /dev/null +++ b/koalixcrm/crm/product/customer_group_transform.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.utils.translation import ugettext as _ + + +class CustomerGroupTransform(models.Model): + from_customer_group = models.ForeignKey('CustomerGroup', + verbose_name=_("From Unit"), + related_name="db_reltransfromfromcustomergroup", + blank=False, + null=False) + to_customer_group = models.ForeignKey('CustomerGroup', + verbose_name=_("To Unit"), + related_name="db_reltransfromtocustomergroup", + blank=False, + null=False) + product = models.ForeignKey('Product', + verbose_name=_("Product"), + blank=False, + null=False) + factor = models.IntegerField(verbose_name=_("Factor between From and To Customer Group"), + blank=True, + null=True) + + def transform(self, customer_group): + """The transform function verifies whether the provided argument customer_group + is corresponding with the "from_customer_group" variable of the CustomerGroupTransform class + When this is ok, the function returns the "to_customer_group". When the provided customer_group + argument is not corresponding, the function returns a "None" + + Args: + customer_group: CustomerGroup object + + Returns: + CustomerGroup object or None + + Raises: + No exceptions planned""" + if self.from_customer_group == customer_group: + return self.to_customer_group + else: + return None + + def __str__(self): + return "From " + self.from_customer_group.name + " to " + self.to_customer_group.name + + class Meta: + app_label = "crm" + verbose_name = _('Customer Group Price Transform') + verbose_name_plural = _('Customer Group Price Transforms') diff --git a/koalixcrm/crm/product/price.py b/koalixcrm/crm/product/price.py index 66cf7419..0b530fdd 100644 --- a/koalixcrm/crm/product/price.py +++ b/koalixcrm/crm/product/price.py @@ -8,58 +8,49 @@ from koalixcrm.crm.contact.customer_group import CustomerGroup -class CustomerGroupTransform(models.Model): - from_customer_group = models.ForeignKey('CustomerGroup', verbose_name=_("From Unit"), - related_name="db_reltransfromfromcustomergroup") - to_customer_group = models.ForeignKey('CustomerGroup', verbose_name=_("To Unit"), - related_name="db_reltransfromtocustomergroup") - product = models.ForeignKey('Product', verbose_name=_("Product")) - factor = models.IntegerField(verbose_name=_("Factor between From and To Customer Group"), blank=True, null=True) - - def transform(self, customer_group): - if (self.from_customer_group == customer_group): - return self.to_customer_group - else: - return unit - - def __str__(self): - return "From " + self.from_customer_group.name + " to " + self.to_customer_group.name - - class Meta: - app_label = "crm" - verbose_name = _('Customer Group Price Transfrom') - verbose_name_plural = _('Customer Group Price Transfroms') - - class Price(models.Model): - product = models.ForeignKey("Product", verbose_name=_("Product")) - unit = models.ForeignKey(Unit, blank=False, verbose_name=_("Unit")) - currency = models.ForeignKey(Currency, blank=False, null=False, verbose_name=('Currency')) - customer_group = models.ForeignKey(CustomerGroup, blank=True, null=True, verbose_name=_("Customer Group")) - price = models.DecimalField(max_digits=17, decimal_places=2, verbose_name=_("Price Per Unit")) - valid_from = models.DateField(verbose_name=_("Valid from"), blank=True, null=True) - valid_until = models.DateField(verbose_name=_("Valid until"), blank=True, null=True) + product = models.ForeignKey("Product", + verbose_name=_("Product")) + unit = models.ForeignKey(Unit, + blank=False, + verbose_name=_("Unit")) + currency = models.ForeignKey(Currency, + verbose_name='Currency', + blank=False, + null=False) + customer_group = models.ForeignKey(CustomerGroup, + verbose_name=_("Customer Group"), + blank=True, + null=True) + price = models.DecimalField(max_digits=17, + decimal_places=2, verbose_name=_("Price Per Unit")) + valid_from = models.DateField(verbose_name=_("Valid from"), + blank=True, + null=True) + valid_until = models.DateField(verbose_name=_("Valid until"), + blank=True, + null=True) def is_valid_from_criteria_fulfilled(self, date): - if self.valid_from == None: - return True; + if not self.valid_from: + return True elif (self.valid_from - date).days <= 0: - return True; + return True else: - return False; + return False def is_valid_until_criteria_fulfilled(self, date): - if self.valid_until == None: + if not self.valid_until: return True elif (date - self.valid_until).days <= 0: return True else: return False - def is_customer_group_criteria_fulfilled(self, customerGroup): - if self.customer_group == None: + def is_customer_group_criteria_fulfilled(self, customer_group): + if not self.customer_group: return True - elif self.customer_group == customerGroup: + elif self.customer_group == customer_group: return True else: return False @@ -76,7 +67,7 @@ def is_unit_criteria_fulfilled(self, unit): else: return False - def matchesDateUnitCustomerGroupCurrency(self, date, unit, customer_group, currency): + def matches_date_unit_customer_group_currency(self, date, unit, customer_group, currency): if (self.is_unit_criteria_fulfilled(unit) & self.is_currency_criteria_fulfilled(currency) & self.is_customer_group_criteria_fulfilled(customer_group) & @@ -98,7 +89,12 @@ class ProductPrice(admin.TabularInline): classes = ['collapse'] fieldsets = ( ('', { - 'fields': ('price', 'currency', 'unit', 'valid_from', 'valid_until', 'customer_group') + 'fields': ('price', + 'currency', + 'unit', + 'valid_from', + 'valid_until', + 'customer_group') }), ) - allow_add = True \ No newline at end of file + allow_add = True diff --git a/koalixcrm/crm/product/product.py b/koalixcrm/crm/product/product.py index 223ffe48..aafa197c 100644 --- a/koalixcrm/crm/product/product.py +++ b/koalixcrm/crm/product/product.py @@ -40,23 +40,38 @@ def get_price(self, date, unit, customer, currency): customer_group_transforms = koalixcrm.crm.product.price.CustomerGroupTransform.objects.filter(product=self.id) valid_prices = list() for price in list(prices): + if price.direct_fits(date, + unit, + customerGroup, + currency): + valid_prices.append(price.price) + elif price.fits_trough_customer_group_transform(): + valid_prices.append(price.price) + elif price.fits_through_currency_transform(): + price.transform() + elif price.fits_through_price_and_group_transform(): + for customerGroup in CustomerGroup.objects.filter(customer=customer): - if price.matchesDateUnitCustomerGroupCurrency(date, unit, customerGroup, currency): - valid_prices.append(price.price) + if price.matches_date_unit_customer_group_currency(date, + unit, + customerGroup, + currency): else: for customerGroupTransform in customer_group_transforms: - if price.matchesDateUnitCustomerGroupCurrency(date, - unit, - customerGroupTransform.transform(customerGroup), - currency): + if price.matches_date_unit_customer_group_currency(date, + unit, + customerGroupTransform.transform( + customerGroup), + currency): valid_prices.append(price.price * customerGroup.factor); else: for unitTransform in list(unit_transforms): - if price.matchesDateUnitCustomerGroupCurrency(date, - unitTransform.transfrom(unit).transform( - unitTransform), - customerGroupTransform.transform( - customerGroup), currency): + if price.matches_date_unit_customer_group_currency(date, + unitTransform.transfrom(unit).transform( + unitTransform), + customerGroupTransform.transform( + customerGroup), + currency): valid_prices.append( price.price * customerGroupTransform.factor * unitTransform.factor); if len(valid_prices) > 0: @@ -96,10 +111,19 @@ def __str__(self): class OptionProduct(admin.ModelAdmin): - list_display = ('product_number', 'title', 'default_unit', 'tax', 'accounting_product_categorie') + list_display = ('product_number', + 'title', + 'default_unit', + 'tax', + 'accounting_product_categorie') list_display_links = ('product_number',) fieldsets = ( (_('Basics'), { - 'fields': ('product_number', 'title', 'description', 'default_unit', 'tax', 'accounting_product_categorie') + 'fields': ('product_number', + 'title', + 'description', + 'default_unit', + 'tax', + 'accounting_product_categorie') }),) inlines = [ProductPrice, ProductUnitTransform] diff --git a/koalixcrm/crm/product/unit.py b/koalixcrm/crm/product/unit.py index b473c398..6040d5e2 100644 --- a/koalixcrm/crm/product/unit.py +++ b/koalixcrm/crm/product/unit.py @@ -30,39 +30,14 @@ class Meta: class OptionUnit(admin.ModelAdmin): - list_display = ('id', 'description', 'short_name', 'is_a_fraction_of', 'fraction_factor_to_next_higher_unit') - fieldsets = (('', {'fields': ('description', 'short_name', 'is_a_fraction_of', 'fraction_factor_to_next_higher_unit')}),) + list_display = ('id', + 'description', + 'short_name', + 'is_a_fraction_of', + 'fraction_factor_to_next_higher_unit') + fieldsets = (('', {'fields': ('description', + 'short_name', + 'is_a_fraction_of', + 'fraction_factor_to_next_higher_unit')}),) allow_add = True - -class UnitTransform(models.Model): - from_unit = models.ForeignKey('Unit', verbose_name=_("From Unit"), related_name="db_reltransfromfromunit") - to_unit = models.ForeignKey('Unit', verbose_name=_("To Unit"), related_name="db_reltransfromtounit") - product = models.ForeignKey('Product', verbose_name=_("Product")) - factor = models.IntegerField(verbose_name=_("Factor between From and To Unit"), blank=True, null=True) - - def transform(self, unit): - if (self.from_unit == unit): - return self.to_unit - else: - return unit - - def __str__(self): - return "From " + self.from_unit.short_name + " to " + self.to_unit.short_name - - class Meta: - app_label = "crm" - verbose_name = _('Unit Transfrom') - verbose_name_plural = _('Unit Transfroms') - - -class ProductUnitTransform(admin.TabularInline): - model = UnitTransform - extra = 1 - classes = ['collapse'] - fieldsets = ( - ('', { - 'fields': ('from_unit', 'to_unit', 'factor',) - }), - ) - allow_add = True diff --git a/koalixcrm/crm/product/unit_transform.py b/koalixcrm/crm/product/unit_transform.py new file mode 100644 index 00000000..a61a8725 --- /dev/null +++ b/koalixcrm/crm/product/unit_transform.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.contrib import admin +from django.utils.translation import ugettext as _ + + +class UnitTransform(models.Model): + from_unit = models.ForeignKey('Unit', + verbose_name=_("From Unit"), + related_name="db_reltransfromfromunit") + to_unit = models.ForeignKey('Unit', + verbose_name=_("To Unit"), + related_name="db_reltransfromtounit") + product = models.ForeignKey('Product', + verbose_name=_("Product")) + factor = models.IntegerField(verbose_name=_("Factor between From and To Unit"), + blank=True, + null=True) + + def transform(self, unit): + if self.from_unit == unit: + return self.to_unit + else: + return None + + def __str__(self): + return "From " + self.from_unit.short_name + " to " + self.to_unit.short_name + + class Meta: + app_label = "crm" + verbose_name = _('Unit Transform') + verbose_name_plural = _('Unit Transforms') + + +class ProductUnitTransform(admin.TabularInline): + model = UnitTransform + extra = 1 + classes = ['collapse'] + fieldsets = ( + ('', { + 'fields': ('from_unit', + 'to_unit', + 'factor',) + }), + ) + allow_add = True diff --git a/koalixcrm/crm/reporting/employee_assignment_to_task.py b/koalixcrm/crm/reporting/employee_assignment_to_task.py deleted file mode 100644 index bc53da20..00000000 --- a/koalixcrm/crm/reporting/employee_assignment_to_task.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.db import models -from django.utils.translation import ugettext as _ -from django.contrib import admin - - -class EmployeeAssignmentToTask(models.Model): - employee = models.ForeignKey("djangoUserExtension.UserExtension") - planned_effort = models.DecimalField(verbose_name=_("Effort"), max_digits=10, decimal_places=2) - task = models.ForeignKey("Task", verbose_name=_('Task'), blank=False, null=False) - - def __str__(self): - return _("Employee Assignment") + ": " + str(self.employee.user.first_name) - - class Meta: - app_label = "crm" - verbose_name = _('Employee Assignment') - verbose_name_plural = _('Employee Assignments') - - -class InlineEmployeeAssignmentToTask(admin.TabularInline): - model = EmployeeAssignmentToTask - fieldsets = ( - (_('Work'), { - 'fields': ('employee', - 'planned_effort',) - }), - ) - extra = 1 \ No newline at end of file diff --git a/koalixcrm/crm/reporting/estimation_of_resource_consumption.py b/koalixcrm/crm/reporting/estimation_of_resource_consumption.py new file mode 100644 index 00000000..924aeebe --- /dev/null +++ b/koalixcrm/crm/reporting/estimation_of_resource_consumption.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.utils.translation import ugettext as _ +from django.contrib import admin + + +class EstimationOfResourceConsumption(models.Model): + task = models.ForeignKey("Task", + verbose_name=_('Task'), + blank=False, + null=False) + resource_type = models.ForeignKey("Product", + verbose_name="Resource Type", + blank=False, + null=False) + reporting_period = models.ForeignKey("ReportingPeriod", + verbose_name="Reporting Period", + blank=False, + null=False) + amount = models.DecimalField(verbose_name=_("Estimated Amount"), + max_digits=10, + decimal_places=2, + blank=True, + null=True) + start_date = models.DateField(verbose_name=_("Estimated Start"), + blank=True, + null=True) + end_date = models.DateField(verbose_name=_("Estimated End"), + blank=True, + null=True) + + def calculated_costs(self): + res + + def __str__(self): + return _("Estimation of Resource Consumption") + ": " + str(self.id) + + class Meta: + app_label = "crm" + verbose_name = _('Estimation of Resource Consumption') + verbose_name_plural = _('Estimation of Resource Consumptions') + + +class InlineEstimationOfResourceConsumption(admin.TabularInline): + model = EstimationOfResourceConsumption + fieldsets = ( + (_('Work'), { + 'fields': ('task', + 'resource_type', + 'amount', + 'start_date', + 'end_date') + }), + ) + extra = 1 diff --git a/koalixcrm/crm/reporting/project.py b/koalixcrm/crm/reporting/project.py index 99163bb4..c896e8c4 100644 --- a/koalixcrm/crm/reporting/project.py +++ b/koalixcrm/crm/reporting/project.py @@ -130,7 +130,7 @@ def effective_effort(self, reporting_period): def planned_effort(self): planned_effort_accumulated = 0 for task in Task.objects.filter(project=self.id): - planned_effort_accumulated += task.planned_effort() + planned_effort_accumulated += task.planned_costs() return planned_effort_accumulated planned_effort.short_description = _("Planned Effort [hrs]") planned_effort.tags = True diff --git a/koalixcrm/crm/reporting/task.py b/koalixcrm/crm/reporting/task.py index f7563370..3c1034da 100644 --- a/koalixcrm/crm/reporting/task.py +++ b/koalixcrm/crm/reporting/task.py @@ -4,9 +4,11 @@ from django.utils.translation import ugettext as _ from django.contrib import admin from django.utils.html import format_html -from koalixcrm.crm.reporting.employee_assignment_to_task import EmployeeAssignmentToTask, InlineEmployeeAssignmentToTask +from koalixcrm.crm.reporting.estimation_of_resource_consumption import EstimationOfResourceConsumption +from koalixcrm.crm.reporting.estimation_of_resource_consumption import InlineEstimationOfResourceConsumption from koalixcrm.crm.reporting.generic_task_link import InlineGenericTaskLink from koalixcrm.crm.reporting.work import InlineWork, Work +from koalixcrm.crm.reporting.reporting_period import ReportingPeriod from koalixcrm.crm.documents.pdf_export import PDFExport from rest_framework import serializers from koalixcrm import global_support_functions @@ -17,12 +19,6 @@ class Task(models.Model): max_length=100, blank=True, null=True) - planned_start_date = models.DateField(verbose_name=_("Planned Start"), - blank=True, - null=True) - planned_end_date = models.DateField(verbose_name=_("Planned End"), - blank=True, - null=True) project = models.ForeignKey("Project", verbose_name=_('Project'), related_name='tasks', @@ -69,25 +65,32 @@ def planned_duration(self): planned_duration.short_description = _("Planned Duration [dys]") planned_duration.tags = True - def planned_effort(self): - """The function return the planned effort of all employees which have been assigned to - this task + def planned_costs(self, reporting_period): + """The function return the planned costs of resources which have been estimated for this task + at a specific reporting period. When no reporting_period is provided, the last reporting period + is selected Args: no arguments Returns: - planned_effort [hrs] (Decimal), 0 if when no assignments are present + planned costs (Decimal), 0 if when no estimations are present Raises: No exceptions planned""" - assignments_to_this_task = EmployeeAssignmentToTask.objects.filter(task=self.id) - sum_effort = 0 - for assignment_to_this_task in assignments_to_this_task: - sum_effort += assignment_to_this_task.planned_effort - return sum_effort - planned_effort.short_description = _("Planned Effort [hrs]") - planned_effort.tags = True + if not reporting_period: + reporting_period_internal = ReportingPeriod.get_reporting_period(self.project, + global_support_functions.get_today_date()) + else: + reporting_period_internal = reporting_period + estimations_to_this_task = EstimationOfResourceConsumption.objects.filter(task=self.id, + reporting_period=reporting_period_internal) + sum_costs = 0 + for estimation_to_this_task in estimations_to_this_task: + sum_costs += estimation_to_this_task.calculated_costs + return sum_costs + 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. The @@ -216,7 +219,7 @@ def serialize_to_xml(self, reporting_period): main_xml = PDFExport.append_element_to_pattern(main_xml, "object/[@model='crm.task']", "Planned_Effort", - self.planned_effort()) + self.planned_costs()) main_xml = PDFExport.append_element_to_pattern(main_xml, "object/[@model='crm.task']", "Effective_Duration", @@ -233,7 +236,7 @@ def effective_effort_overall(self): effective_effort_overall.tags = True def effective_effort(self, reporting_period): - """ effective effort returns the effective effort on a task + """ Effective effort returns the effective effort on a task when reporting_period is None, the effective effort overall is calculated when reporting_period is specified, the effective effort in this period is calculated""" if reporting_period: @@ -247,6 +250,25 @@ def effective_effort(self, reporting_period): sum_effort_in_hours = sum_effort / 3600 return sum_effort_in_hours + def effective_costs(self, reporting_period): + """ Effective effort returns the effective costs on a task + when reporting_period is None, the effective effort overall is calculated + when reporting_period is specified, the effective effort in this period is calculated""" + if reporting_period: + work_objects = Work.objects.filter(task=self.id, + reporting_period=reporting_period) + expense_objects = Expense.objects.filter(task=self.id, + reporting_period=reporting_period) + else: + work_objects = Work.objects.filter(task=self.id) + expense_objects = Expense.objects.filter(task=self.id) + sum_costs = 0 + for work_object in work_objects: + sum_costs += work_object.costs() + for expense_object in expense_objects: + sum_costs += expense_object.costs() + return sum_costs + def is_reporting_allowed(self): """Returns True when the task is available for reporting, Returns False when the task is not available for reporting, @@ -306,15 +328,13 @@ class OptionTask(admin.ModelAdmin): fieldsets = ( (_('Work'), { 'fields': ('title', - 'planned_start_date', - 'planned_end_date', 'project', 'description', 'status') }), ) save_as = True - inlines = [InlineEmployeeAssignmentToTask, + inlines = [InlineEstimationOfResourceConsumption, InlineGenericTaskLink, InlineWork] @@ -323,6 +343,8 @@ class InlineTasks(admin.TabularInline): model = Task readonly_fields = ('link_to_task', 'last_status_change', + 'planned_start_date', + 'planned_end_date', 'planned_duration', 'planned_effort', 'effective_duration', diff --git a/koalixcrm/crm/tests/test_task_constructor.py b/koalixcrm/crm/tests/test_task_constructor.py new file mode 100644 index 00000000..d22aac72 --- /dev/null +++ b/koalixcrm/crm/tests/test_task_constructor.py @@ -0,0 +1,79 @@ +import datetime +import pytest +from django.test import TestCase +from koalixcrm.crm.factories.factory_user import AdminUserFactory +from koalixcrm.crm.factories.factory_customer_billing_cycle import StandardCustomerBillingCycleFactory +from koalixcrm.crm.factories.factory_customer import StandardCustomerFactory +from koalixcrm.crm.factories.factory_customer_group import StandardCustomerGroupFactory +from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory +from koalixcrm.crm.factories.factory_reporting_period import StandardReportingPeriodFactory +from koalixcrm.djangoUserExtension.factories.factory_user_extension import StandardUserExtensionFactory +from koalixcrm.crm.reporting.task import Task + + +@pytest.fixture() +def freeze(monkeypatch): + """ Now() manager patches date return a fixed, settable, value + (freezes date) + """ + import datetime + original = datetime.date + + class FreezeMeta(type): + def __instancecheck__(self, instance): + if type(instance) == original or type(instance) == Freeze: + return True + + class Freeze(datetime.datetime): + __metaclass__ = FreezeMeta + + @classmethod + def freeze(cls, val): + cls.frozen = val + + @classmethod + def today(cls): + return cls.frozen + + @classmethod + def delta(cls, timedelta=None, **kwargs): + """ Moves time fwd/bwd by the delta""" + from datetime import timedelta as td + if not timedelta: + timedelta = td(**kwargs) + cls.frozen += timedelta + + monkeypatch.setattr(datetime, 'date', Freeze) + Freeze.freeze(original.today()) + return Freeze + + +class TaskConstructorTest(TestCase): + + @pytest.fixture(autouse=True) + def freeze_time(self, freeze): + self._freeze = freeze + + def setUp(self): + + self.test_billing_cycle = StandardCustomerBillingCycleFactory.create() + self.test_user = AdminUserFactory.create() + self.test_customer_group = StandardCustomerGroupFactory.create() + self.test_customer = StandardCustomerFactory.create(is_member_of=(self.test_customer_group,)) + self.test_currency = StandardCurrencyFactory.create() + self.test_user_extension = StandardUserExtensionFactory.create(user=self.test_user) + self.test_reporting_period = StandardReportingPeriodFactory.create() + + def test_task_constructor(self): + self._freeze.freeze(datetime.date(2024, 6, 2)) + task_minimal_1 = Task.objects.create( + project=self.test_reporting_period.project, + ) + task_minimal_1.save() + self.assertEquals(task_minimal_1.last_status_change, datetime.date(2024, 6, 2)) + self._freeze.freeze(datetime.date(2018, 6, 15)) + task_minimal_2 = Task.objects.create( + project=self.test_reporting_period.project, + ) + task_minimal_2.save() + self.assertEquals(task_minimal_2.last_status_change, datetime.date(2018, 6, 15)) diff --git a/koalixcrm/crm/tests/test_task_effective_effort.py b/koalixcrm/crm/tests/test_task_effective_effort.py index 744c32bf..7b1cbafb 100644 --- a/koalixcrm/crm/tests/test_task_effective_effort.py +++ b/koalixcrm/crm/tests/test_task_effective_effort.py @@ -47,11 +47,11 @@ def test_effective_effort(self): self.assertEqual( (self.test_1st_task.planned_duration()).__str__(), "60") self.assertEqual( - (self.test_1st_task.planned_effort()).__str__(), "0") + (self.test_1st_task.planned_costs()).__str__(), "0") self.assertEqual( (self.test_2nd_task.planned_duration()).__str__(), "90") self.assertEqual( - (self.test_2nd_task.planned_effort()).__str__(), "0") + (self.test_2nd_task.planned_costs()).__str__(), "0") StandardWorkFactory.create( employee=self.test_user_extension, date=date_now, diff --git a/koalixcrm/crm/tests/test_task_planned_duration.py b/koalixcrm/crm/tests/test_task_planned_duration.py index f8d332d5..90cbe314 100644 --- a/koalixcrm/crm/tests/test_task_planned_duration.py +++ b/koalixcrm/crm/tests/test_task_planned_duration.py @@ -40,8 +40,8 @@ def test_planned_duration(self): self.assertEqual( (self.test_1st_task.planned_duration()).__str__(), "60") self.assertEqual( - (self.test_1st_task.planned_effort()).__str__(), "0") + (self.test_1st_task.planned_costs()).__str__(), "0") self.assertEqual( (self.test_2nd_task.planned_duration()).__str__(), "90") self.assertEqual( - (self.test_2nd_task.planned_effort()).__str__(), "0") + (self.test_2nd_task.planned_costs()).__str__(), "0") diff --git a/koalixcrm/crm/tests/test_task_planned_effort.py b/koalixcrm/crm/tests/test_task_planned_effort.py index 6d4f999d..4c7765c7 100644 --- a/koalixcrm/crm/tests/test_task_planned_effort.py +++ b/koalixcrm/crm/tests/test_task_planned_effort.py @@ -44,11 +44,11 @@ def test_planned_effort(self): self.assertEqual( (self.test_1st_task.planned_duration()).__str__(), "60") self.assertEqual( - (self.test_1st_task.planned_effort()).__str__(), "0") + (self.test_1st_task.planned_costs()).__str__(), "0") self.assertEqual( (self.test_2nd_task.planned_duration()).__str__(), "90") self.assertEqual( - (self.test_2nd_task.planned_effort()).__str__(), "0") + (self.test_2nd_task.planned_costs()).__str__(), "0") StandardEmployeeAssignmentToTaskFactory.create(employee=self.test_user_extension, planned_effort="2.00", task=self.test_1st_task) @@ -62,10 +62,10 @@ def test_planned_effort(self): planned_effort="3.25", task=self.test_2nd_task) self.assertEqual( - (self.test_1st_task.planned_effort()).__str__(), "3.50") + (self.test_1st_task.planned_costs()).__str__(), "3.50") self.assertEqual( (self.test_1st_task.effective_effort(reporting_period=None)).__str__(), "0.0") self.assertEqual( - (self.test_2nd_task.planned_effort()).__str__(), "8.00") + (self.test_2nd_task.planned_costs()).__str__(), "8.00") self.assertEqual( (self.test_2nd_task.effective_effort(reporting_period=None)).__str__(), "0.0") diff --git a/koalixcrm/crm/tests/test_task_update_last_status_update.py b/koalixcrm/crm/tests/test_task_update_last_status_update.py index 67384a7b..fa206f00 100644 --- a/koalixcrm/crm/tests/test_task_update_last_status_update.py +++ b/koalixcrm/crm/tests/test_task_update_last_status_update.py @@ -87,9 +87,8 @@ def test_last_status_update(self): previous_last_status_change = self.test_1st_task.last_status_change new_status = DoneTaskStatusFactory.create() self._freeze.freeze(datetime.date(2024, 6, 2)) - print(get_today_date()) - print(datetime.date.today()) self.test_1st_task.status = new_status self.test_1st_task.save() self.assertEquals(previous_last_status_change, datetime.date(2024, 6, 15)) self.assertEqual(self.test_1st_task.last_status_change, datetime.date(2024, 6, 2)) +