<repo_name>Shikhar10000/koalixcrm<file_sep>/koalixcrm/crm/product/unit.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _


class Unit(models.Model):
    description = models.CharField(verbose_name=_("Description"),
                                   max_length=100)
    short_name = models.CharField(verbose_name=_("Displayed Name After Quantity In The Position"),
                                  max_length=3)
    is_a_fraction_of = models.ForeignKey('self',
                                         blank=True,
                                         null=True,
                                         verbose_name=_("Is A Fraction Of"))
    fraction_factor_to_next_higher_unit = models.DecimalField(verbose_name=_("Factor Between This And Next Higher Unit"),
                                                              max_digits=20,
                                                              decimal_places=10,
                                                              blank=True,
                                                              null=True)

    def __str__(self):
        return self.short_name

    class Meta:
        app_label = "crm"
        verbose_name = _('Unit')
        verbose_name_plural = _('Units')


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')}),)
    allow_add = True

<file_sep>/koalixcrm/crm/views/work_entry_form.py
# -*- coding: utf-8 -*-

import datetime
from django.forms import NumberInput
from koalixcrm.crm.reporting.task import Task
from koalixcrm.crm.reporting.project import Project
from django.contrib.admin.widgets import *
from koalixcrm.global_support_functions import limit_string_length
from koalixcrm.djangoUserExtension.models import UserExtension
from koalixcrm.crm.reporting.reporting_period import ReportingPeriod
from koalixcrm.crm.reporting.human_resource import HumanResource


class WorkEntry(forms.Form):
    """
    Form which allows to fill out a full Work. Instead of only showing the task, it is possible
    to select the task based on the input from the project
    """
    project = forms.ModelChoiceField(queryset=Project.objects.filter(reportingperiod__status__is_done=False).distinct(),
                                     required=True)
    task = forms.ModelChoiceField(queryset=Task.objects.filter(status__is_done=False),
                                  required=True)
    date = forms.DateField(widget=AdminDateWidget,
                           required=True)
    start_time = forms.TimeField(widget=AdminTimeWidget,
                                 required=False)
    stop_time = forms.TimeField(widget=AdminTimeWidget,
                                required=False)
    worked_hours = forms.DecimalField(widget=NumberInput(attrs={'step': 0.1,
                                                                'min': 0,
                                                                'max': 24}),
                                      required=False)
    description = forms.CharField(widget=AdminTextareaWidget,
                                  required=True)
    work_id = forms.IntegerField(widget=forms.HiddenInput(),
                                 required=False)

    def __init__(self, *args, **kwargs):
        self.from_date = kwargs.pop('from_date')
        self.to_date = kwargs.pop('to_date')
        self.original_from_date = self.from_date
        self.original_to_date = self.to_date
        super(WorkEntry, self).__init__(*args, **kwargs)

    @staticmethod
    def check_working_hours(cleaned_data):
        """This method checks that the working hour is correctly proved either using the start_stop pattern
        or by providing the worked_hours in total.

        Args:
          cleaned_data (Dict):  The cleaned_data must contain the values form the form validation.
          The django built in form validation must already have been passed

        Returns:
          True when no ValidationError was raised

        Raises:
          may raise ValidationError exception"""
        if ("start_time" in cleaned_data) & ("stop_time" in cleaned_data) & ("worked_hours" in cleaned_data):
            start_stop_pattern_complete = bool(cleaned_data["start_time"]) & bool(cleaned_data["stop_time"])
            start_stop_pattern_stop_missing = bool(cleaned_data["start_time"]) & (not bool(cleaned_data["stop_time"]))
            start_stop_pattern_start_missing = (not bool(cleaned_data["start_time"])) & bool(cleaned_data["stop_time"])
            worked_hours_pattern = bool(cleaned_data["worked_hours"])
        else:
            raise forms.ValidationError('Programming error', code='invalid')
        if start_stop_pattern_complete & worked_hours_pattern:
            raise forms.ValidationError('Please either set the start, stop time or worked hours (not both)',
                                        code='invalid')
        elif start_stop_pattern_start_missing or start_stop_pattern_stop_missing:
            raise forms.ValidationError('Set start and stop time',
                                        code='invalid')
        elif not start_stop_pattern_complete and not worked_hours_pattern:
            raise forms.ValidationError('Either fill out the start_time and stop_time or the worked_hours',
                                        code='invalid')
        return True

    def clean(self):
        cleaned_data = super(WorkEntry, self).clean()
        if 'date' in cleaned_data:
            date = cleaned_data['date']
            if date < self.from_date:
                raise forms.ValidationError('date is not within the selected range', code='invalid')
            elif self.to_date < date:
                raise forms.ValidationError('date is not within the selected range', code='invalid')
        if not cleaned_data["project"].is_reporting_allowed():
            raise forms.ValidationError('The project is either closed or there is not '
                                        'reporting period available', code='invalid')
        WorkEntry.check_working_hours(cleaned_data)
        return cleaned_data

    def update_work(self, request):
        from koalixcrm.crm.reporting.work import Work
        if self.has_changed():
            if self.cleaned_data['work_id']:
                work = Work.objects.get(id=self.cleaned_data['work_id'])
            else:
                work = Work()
            if self.cleaned_data['DELETE']:
                work.delete()
            else:
                work.task = self.cleaned_data['task']
                work.reporting_period = ReportingPeriod.get_reporting_period(project=self.cleaned_data['task'].project,
                                                                             search_date=self.cleaned_data['date'])
                work.human_resource = HumanResource.objects.get(user=UserExtension.get_user_extension(request.user))
                work.date = self.cleaned_data['date']
                if bool(self.cleaned_data['start_time']) & bool(self.cleaned_data['stop_time']):
                    work.start_time = datetime.datetime.combine(self.cleaned_data['date'],
                                                                self.cleaned_data['start_time'])
                    work.stop_time = datetime.datetime.combine(self.cleaned_data['date'],
                                                               self.cleaned_data['stop_time'])
                else:
                    work.worked_hours = self.cleaned_data['worked_hours']
                work.description = self.cleaned_data['description']
                work.short_description = limit_string_length(work.description, 100)
                work.save()
<file_sep>/koalixcrm/crm/exceptions.py
# -*- coding: utf-8 -*-


class TemplateSetMissing(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class TemplateMissingInTemplateSet(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class TemplateSetMissingInContract(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class TemplateFOPConfigFileMissing(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class TemplateXSLTFileMissing(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class NoSerializationPatternFound(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class OpenInterestAccountMissing(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class IncompleteInvoice(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class InvoiceAlreadyRegistered(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class UserIsNoHumanResource(Exception):
    def __init__(self, value):
        self.value = value
        self.view = "/koalixcrm/crm/reporting/user_is_not_human_resource"

    def __str__(self):
        return repr(self.value)


class ReportingPeriodDoneDeleteNotPossible(Exception):
    def __init__(self, value=None):
        self.value = value
        self.view = "/koalixcrm/crm/reporting/reporting_period_done_delete_not_possible"

    def __str__(self):
        return repr(self.value)


class ReportingPeriodNotFound(Exception):
    def __init__(self, value):
        self.value = value
        self.view = "/koalixcrm/crm/reporting/reporting_period_missing"

    def __str__(self):
        return repr(self.value)
<file_sep>/koalixcrm/crm/migrations/0020_auto_20180116_2056.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-16 20:56
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0019_auto_20180112_2020'),
    ]

    operations = [
        migrations.DeleteModel(
            name='PurchaseOrder',
        ),
        migrations.DeleteModel(
            name='EmailAddressForPurchaseOrder',
        ),
        migrations.DeleteModel(
            name='PhoneAddressForPurchaseOrder',
        ),
        migrations.DeleteModel(
            name='PostalAddressForPurchaseOrder',
        ),
        migrations.DeleteModel(
            name='PurchaseOrderPosition',
        ),
    ]
<file_sep>/koalixcrm/crm/documents/quote.py
# -*- coding: utf-8 -*-

from datetime import *
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from django.utils.html import format_html
from koalixcrm.crm.const.status import *
from koalixcrm.plugin import *
from koalixcrm.crm.documents.sales_document import SalesDocument, OptionSalesDocument
from koalixcrm.global_support_functions import limit_string_length


class Quote(SalesDocument):
    valid_until = models.DateField(verbose_name=_("Valid until"))
    status = models.CharField(max_length=1, choices=QUOTESTATUS, verbose_name=_('Status'))

    def link_to_quote(self):
        if self.id:
            return format_html("<a href='/admin/crm/quote/%s' >%s</a>" % (str(self.id),
                                                                          limit_string_length(str(self.description),
                                                                                              30)))
        else:
            return "Not present"
    link_to_quote.short_description = _("Quote");

    def create_from_reference(self, calling_model):
        self.create_sales_document(calling_model)
        self.status = 'I'
        self.valid_until = date.today().__str__()
        self.date_of_creation = date.today().__str__()
        self.template_set = self.contract.get_template_set(self)
        self.save()
        self.attach_sales_document_positions(calling_model)
        self.attach_text_paragraphs()

    def __str__(self):
        return _("Quote") + ": " + self.id.__str__() + " " + _("from Contract") + ": " + self.contract.id.__str__()

    class Meta:
        app_label = "crm"
        verbose_name = _('Quote')
        verbose_name_plural = _('Quotes')


class OptionQuote(OptionSalesDocument):
    list_display = OptionSalesDocument.list_display + ('valid_until',
                                                       'status',)
    list_filter = OptionSalesDocument.list_filter + ('status',)
    ordering = OptionSalesDocument.ordering
    search_fields = OptionSalesDocument.search_fields
    fieldsets = OptionSalesDocument.fieldsets + (
        (_('Quote specific'), {
            'fields': ('valid_until',
                       'status', )
        }),
    )

    save_as = OptionSalesDocument.save_as
    inlines = OptionSalesDocument.inlines

    actions = ['create_purchase_confirmation',
               'create_invoice',
               'create_delivery_note',
               'create_purchase_order',
               'create_project',
               'create_pdf']

    pluginProcessor = PluginProcessor()
    inlines.extend(pluginProcessor.getPluginAdditions("quoteInlines"))


class InlineQuote(admin.TabularInline):
    model = Quote
    classes = ['collapse']
    show_change_link = True
    can_delete = True
    extra = 1
    readonly_fields = ('link_to_quote',
                       'contract',
                       'customer',
                       'valid_until',
                       'status',
                       'last_pricing_date',
                       'last_calculated_price',
                       'last_calculated_tax',)
    fieldsets = (
        (_('Quote'), {
            'fields': ('link_to_quote',
                       'contract',
                       'customer',
                       'valid_until',
                       'status',
                       'last_pricing_date',
                       'last_calculated_price',
                       'last_calculated_tax',)
        }),
    )
    allow_add = False
<file_sep>/koalixcrm/crm/product/price.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _
from koalixcrm.crm.product.currency import Currency
from koalixcrm.crm.product.unit import Unit
from koalixcrm.crm.contact.customer_group import CustomerGroup
from koalixcrm.crm.product.unit_transform import UnitTransform
from koalixcrm.crm.product.customer_group_transform import CustomerGroupTransform
from koalixcrm.crm.product.currency_transform import CurrencyTransform


class Price(models.Model):
    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 __str__(self):
        return str(self.id) + " " +str(self.price) + " " + str(self.currency.short_name)

    def is_valid_from_criteria_fulfilled(self, date):
        if self.valid_from is None:
            return True
        elif (self.valid_from - date).days <= 0:
            return True
        else:
            return False

    def is_valid_until_criteria_fulfilled(self, date):
        if self.valid_until is None:
            return True
        elif (date - self.valid_until).days <= 0:
            return True
        else:
            return False

    def is_customer_group_criteria_fulfilled(self, customer_group):
        if self.customer_group is None:
            return True
        elif self.customer_group == customer_group:
            return True
        else:
            return False

    def is_currency_criteria_fulfilled(self, currency):
        if self.currency == currency:
            return True
        else:
            return False

    def is_unit_criteria_fulfilled(self, unit):
        if self.unit == unit:
            return True
        else:
            return False

    def is_date_in_range(self, date):
        if (self.valid_from is None) and (self.valid_until is None):
            return True
        elif self.valid_until is None:
            if self.valid_from <= date:
                return True
            else:
                return False
        elif self.valid_from is None:
            if date <= self.valid_until:
                return True
            else:
                return False
        elif (self.valid_from <= date) and (date <= self.valid_until):
            return True
        else:
            return False

    def get_currency_transform_factor(self, currency, product_type):
        """check currency conditions and factor"""
        currency_factor = 0
        if self.currency == currency:
            currency_factor = 1
        else:
            currency_transform = CurrencyTransform.objects.get(from_currency=self.currency,
                                                               to_currency=currency,
                                                               product_type=product_type)
            if currency_transform:
                currency_factor = currency_transform.get_transform_factor()
        return currency_factor

    def get_unit_transform_factor(self, unit, product_type):
        """check unit conditions and factor"""
        unit_factor = 0
        if self.unit == unit:
            unit_factor = 1
        else:
            unit_transform = UnitTransform.objects.get(from_unit=self.unit,
                                                       to_unit=unit,
                                                       product_type=product_type)
            if unit_transform:
                unit_factor = unit_transform.get_transform_factor()
        return unit_factor

    def get_customer_group_transform_factor(self, customer, product_type):
        """The function searches through all customer_groups in which the customer is member of
        from these customer_groups, the function returns the customer_group with the perfect match
        or it returns the factor with the lowest transform factor

        Args:
            koalixcrm.crm.contact.customer customer
            koalixcrm.crm.product.product product

        Returns:
            Decimal factor

        Raises:
            No exceptions planned"""
        customer_group_factor = 0
        if self.customer_group is None:
            customer_group_factor = 1
        elif customer is not None:
            customer_groups = customer.is_member_of.all()
            if customer_groups is not None:
                for customer_group in customer_groups:
                    if self.customer_group == customer_group:
                        customer_group_factor = 1
                        # Stop for loop when a perfect match is found
                        break
                    else:
                        customer_group_transform = CustomerGroupTransform.objects.get(
                            from_customer_group=self.customer_group,
                            to_customer_group=customer_group,
                            product_type=product_type)
                        if customer_group_transform:
                            transform_factor = customer_group_transform.get_transform_factor()
                            if customer_group_factor > transform_factor or customer_group_factor == 0:
                                customer_group_factor = transform_factor
        return customer_group_factor

    class Meta:
        app_label = "crm"
        verbose_name = _('Price')
        verbose_name_plural = _('Prices')
<file_sep>/koalixcrm/crm/migrations/0028_auto_20180611_1835.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-11 18:35
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0027_auto_20180606_2034'),
    ]

    operations = [
        migrations.AlterField(
            model_name='task',
            name='planned_end_date',
            field=models.DateField(blank=True, null=True, verbose_name='Planned End Date'),
        ),
        migrations.AlterField(
            model_name='task',
            name='planned_start_date',
            field=models.DateField(blank=True, null=True, verbose_name='Planned Start Date'),
        ),
        migrations.AlterField(
            model_name='task',
            name='status',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.TaskStatus', verbose_name='Task Status'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_unit_transform.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import UnitTransform
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory, SmallUnitFactory
from koalixcrm.crm.factories.factory_product_type import StandardProductTypeFactory


class StandardUnitTransformFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = UnitTransform
        django_get_or_create = ('from_unit',
                                'to_unit')

    from_unit = factory.SubFactory(StandardUnitFactory)
    to_unit = factory.SubFactory(SmallUnitFactory)
    product_type = factory.SubFactory(StandardProductTypeFactory)
    factor = 1.10
<file_sep>/koalixcrm/crm/migrations/0031_auto_20180612_1924.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-12 19:24
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0030_auto_20180611_1930'),
    ]

    operations = [
        migrations.AlterField(
            model_name='task',
            name='short_description',
            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Short Description'),
        ),
    ]
<file_sep>/koalixcrm/crm/admin.py
# -*- coding: utf-8 -*-

from django.contrib import admin
from koalixcrm.crm.documents.quote import Quote, OptionQuote
from koalixcrm.crm.documents.purchase_confirmation import PurchaseConfirmation, OptionPurchaseConfirmation
from koalixcrm.crm.documents.delivery_note import DeliveryNote, OptionDeliveryNote
from koalixcrm.crm.documents.invoice import Invoice, OptionInvoice
from koalixcrm.crm.documents.payment_reminder import PaymentReminder, OptionPaymentReminder
from koalixcrm.crm.documents.purchase_order import PurchaseOrder, OptionPurchaseOrder
from koalixcrm.crm.documents.contract import Contract, OptionContract
from koalixcrm.crm.product.tax import Tax, OptionTax
from koalixcrm.crm.product.unit import Unit, OptionUnit
from koalixcrm.crm.product.product_type import ProductType, ProductTypeAdminView
from koalixcrm.crm.product.currency import Currency, OptionCurrency
from koalixcrm.crm.contact.customer import Customer, OptionCustomer
from koalixcrm.crm.contact.supplier import Supplier, OptionSupplier
from koalixcrm.crm.contact.customer_group import CustomerGroup, OptionCustomerGroup
from koalixcrm.crm.contact.customer_billing_cycle import CustomerBillingCycle, OptionCustomerBillingCycle
from koalixcrm.crm.contact.person import Person
from koalixcrm.crm.contact.contact import OptionPerson, CallForContact, VisitForContact
from koalixcrm.crm.contact.call import OptionCall, OptionVisit
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.agreement_type import AgreementType, AgreementTypeAdminView
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
from koalixcrm.crm.reporting.project import Project, ProjectAdminView
from koalixcrm.crm.reporting.project_link_type import ProjectLinkType, OptionProjectLinkType
from koalixcrm.crm.reporting.project_status import ProjectStatus, OptionProjectStatus
from koalixcrm.crm.reporting.work import Work, WorkAdminView
from koalixcrm.crm.reporting.reporting_period import ReportingPeriod, ReportingPeriodAdmin
from koalixcrm.crm.reporting.reporting_period_status import ReportingPeriodStatus, OptionReportingPeriodStatus


admin.site.register(Customer, OptionCustomer)
admin.site.register(CustomerGroup, OptionCustomerGroup)
admin.site.register(CustomerBillingCycle, OptionCustomerBillingCycle)
admin.site.register(Supplier, OptionSupplier)
admin.site.register(Person, OptionPerson)
admin.site.register(CallForContact, OptionCall)
admin.site.register(VisitForContact, OptionVisit)

admin.site.register(Contract, OptionContract)
admin.site.register(Quote, OptionQuote)
admin.site.register(PurchaseConfirmation, OptionPurchaseConfirmation)
admin.site.register(DeliveryNote, OptionDeliveryNote)
admin.site.register(Invoice, OptionInvoice)
admin.site.register(PaymentReminder, OptionPaymentReminder)
admin.site.register(PurchaseOrder, OptionPurchaseOrder)

admin.site.register(Unit, OptionUnit)
admin.site.register(Currency, OptionCurrency)
admin.site.register(Tax, OptionTax)
admin.site.register(ProductType, ProductTypeAdminView)

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(AgreementType, AgreementTypeAdminView)
admin.site.register(Work, WorkAdminView)
admin.site.register(HumanResource, HumanResourceAdminView)
admin.site.register(ResourceType, ResourceTypeAdminView)
admin.site.register(ResourceManager, ResourceManagerAdminView)
admin.site.register(Project, ProjectAdminView)
admin.site.register(ProjectLinkType, OptionProjectLinkType)
admin.site.register(ProjectStatus, OptionProjectStatus)
admin.site.register(ReportingPeriod, ReportingPeriodAdmin)
admin.site.register(ReportingPeriodStatus, OptionReportingPeriodStatus)
<file_sep>/koalixcrm/crm/factories/factory_estimation.py
# -*- coding: utf-8 -*-

import factory
import datetime
from koalixcrm.crm.models import Estimation
from koalixcrm.crm.factories.factory_resource import StandardResourceFactory
from koalixcrm.crm.factories.factory_human_resource import StandardHumanResourceFactory
from koalixcrm.crm.factories.factory_reporting_period import StandardReportingPeriodFactory
from koalixcrm.crm.factories.factory_estimation_status import StartedEstimationStatusFactory
from koalixcrm.crm.factories.factory_task import StandardTaskFactory
from koalixcrm.test_support_functions import make_date_utc


class StandardEstimationToTaskFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Estimation

    amount = "112.50"
    task = factory.SubFactory(StandardTaskFactory)
    resource = factory.SubFactory(StandardResourceFactory)
    date_from = make_date_utc(datetime.datetime(2018, 5, 2, 00))
    date_until = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    status = factory.SubFactory(StartedEstimationStatusFactory)
    reporting_period = factory.SubFactory(StandardReportingPeriodFactory)


class StandardHumanResourceEstimationToTaskFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Estimation

    amount = "112.50"
    task = factory.SubFactory(StandardTaskFactory)
    resource = factory.SubFactory(StandardHumanResourceFactory)
    date_from = make_date_utc(datetime.datetime(2018, 5, 2, 00))
    date_until = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    status = factory.SubFactory(StartedEstimationStatusFactory)
    reporting_period = factory.SubFactory(StandardReportingPeriodFactory)
<file_sep>/koalixcrm/crm/views/pdfexport.py
# -*- coding: utf-8 -*-
from os import path
from wsgiref.util import FileWrapper
from subprocess import CalledProcessError
from django.http import Http404
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from koalixcrm.crm.exceptions import *
from koalixcrm.djangoUserExtension.exceptions import *
from django.contrib import messages


class PDFExportView:

    @staticmethod
    def export_pdf(calling_model_admin, request, source, redirect_to, template_to_use, *args, **kwargs):
        """This method exports PDFs provided by different Models in the crm application

            Args:
              calling_model_admin (ModelAdmin):  The calling ModelAdmin must be provided for error message response.
              request: The request User is to know where to save the error message
              source:  The model from which a PDF should be exported
              redirect_to (str): String that describes to where the method should redirect in case of an error
              template_to_use (Template Set): For some documents that need to be created there exists more
              than one template with this parameter the template set can be set during the export function


            Returns:
              HTTpResponse with a PDF when successful
              HTTpResponseRedirect when not successful

            Raises:
              raises Http404 exception if anything goes wrong"""
        try:
            pdf = source.create_pdf(template_to_use, request.user, *args, **kwargs)
            response = HttpResponse(FileWrapper(open(pdf, 'rb')), content_type='application/pdf')
            response['Content-Length'] = path.getsize(pdf)
        except (TemplateSetMissing,
                TemplateSetMissingInContract,
                UserExtensionMissing,
                CalledProcessError,
                UserExtensionEmailAddressMissing,
                UserExtensionPhoneAddressMissing,
                TemplateSetMissingForUserExtension) as e:
            if isinstance(e, UserExtensionMissing):
                response = HttpResponseRedirect(redirect_to)
                calling_model_admin.message_user(request, _("User Extension Missing"),
                                                 level=messages.ERROR)
            elif isinstance(e, UserExtensionEmailAddressMissing):
                response = HttpResponseRedirect(redirect_to)
                calling_model_admin.message_user(request, _("User Extension Email Missing"),
                                                 level=messages.ERROR)
            elif isinstance(e, UserExtensionPhoneAddressMissing):
                response = HttpResponseRedirect(redirect_to)
                calling_model_admin.message_user(request, _("User Extension Phone Missing"),
                                                 level=messages.ERROR)
            elif isinstance(e, TemplateSetMissing):
                response = HttpResponseRedirect(redirect_to)
                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)
                calling_model_admin.message_user(request, _("Fop Config File Missing in TemplateSet"),
                                                 level=messages.ERROR)
            elif isinstance(e, TemplateXSLTFileMissing):
                response = HttpResponseRedirect(redirect_to)
                calling_model_admin.message_user(request, _("XSLT File Missing in TemplateSet"),
                                                 level=messages.ERROR)
            elif isinstance(e, TemplateSetMissingForUserExtension):
                response = HttpResponseRedirect(redirect_to)
                calling_model_admin.message_user(request, _("Work report template missing in the user extension"),
                                                 level=messages.ERROR)
            elif type(e) == CalledProcessError:
                response = HttpResponseRedirect(redirect_to)
                calling_model_admin.message_user(request, e.output)
            else:
                raise Http404
        return response
<file_sep>/koalixcrm/djangoUserExtension/factories/factory_user_extension.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.djangoUserExtension.models import UserExtension
from koalixcrm.crm.factories.factory_user import StaffUserFactory
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.djangoUserExtension.factories.factory_template_set import StandardTemplateSetFactory


class StandardUserExtensionFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = UserExtension
        django_get_or_create = ('user',)

    user = factory.SubFactory(StaffUserFactory)
    default_template_set = factory.SubFactory(StandardTemplateSetFactory)
    default_currency = factory.SubFactory(StandardCurrencyFactory)
<file_sep>/koalixcrm/crm/factories/factory_unit.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Unit


class StandardUnitFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Unit
        django_get_or_create = ('short_name',)

    description = "Kilogram"
    short_name = "kg"
    is_a_fraction_of = None
    fraction_factor_to_next_higher_unit = None


class SmallUnitFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Unit
        django_get_or_create = ('short_name',)

    description = "Gram"
    short_name = "g"
    is_a_fraction_of = factory.SubFactory(StandardUnitFactory)
    fraction_factor_to_next_higher_unit = 1000
<file_sep>/koalixcrm/crm/factories/factory_human_resource.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import HumanResource
from koalixcrm.djangoUserExtension.factories.factory_user_extension import StandardUserExtensionFactory
from koalixcrm.crm.factories.factory_resource_type import StandardResourceTypeFactory
from koalixcrm.crm.factories.factory_resource_manager import StandardResourceManagerFactory


class StandardHumanResourceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = HumanResource

    user = factory.SubFactory(StandardUserExtensionFactory)
    resource_type = factory.SubFactory(StandardResourceTypeFactory)
    resource_manager = factory.SubFactory(StandardResourceManagerFactory)
<file_sep>/koalixcrm/crm/reporting/resource.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _


class Resource(models.Model):
    resource_manager = models.ForeignKey("ResourceManager",
                                         verbose_name=_("Manager"),
                                         blank=True,
                                         null=True)
    resource_type = models.ForeignKey("ResourceType",
                                      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"

<file_sep>/koalixcrm/accounting/accounting/accounting_period.py
# -*- coding: utf-8 -*-

from datetime import *
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from django import forms
from koalixcrm.accounting.models import Account
from koalixcrm.crm.documents.pdf_export import PDFExport
from koalixcrm.accounting.exceptions import AccountingPeriodNotFound
from koalixcrm.accounting.exceptions import TemplateSetMissingInAccountingPeriod
from koalixcrm.accounting.models import InlineBookings


class AccountingPeriod(models.Model):
    """Accounting period represents the equivalent of the business logic element of a fiscal year
    the accounting period is referred in the booking and is used as a supporting object to generate
    balance sheets and profit/loss statements"""
    title = models.CharField(max_length=200, verbose_name=_("Title"))  # For example "Year 2009", "1st Quarter 2009"
    begin = models.DateField(verbose_name=_("Begin"))
    end = models.DateField(verbose_name=_("End"))
    template_set_balance_sheet = models.ForeignKey("djangoUserExtension.DocumentTemplate",
                                                   verbose_name=_("Referred template for balance sheet"),
                                                   related_name='db_balancesheet_template_set',
                                                   null=True,
                                                   blank=True)
    template_profit_loss_statement = models.ForeignKey("djangoUserExtension.DocumentTemplate",
                                                       verbose_name=_("Referred template for profit, loss statement"),
                                                       related_name='db_profit_loss_statement_template_set',
                                                       null=True,
                                                       blank=True)

    def get_template_set(self, template_set):
        if template_set == self.template_set_balance_sheet:
            if self.template_set_balance_sheet:
                return self.template_set_balance_sheet
            else:
                raise TemplateSetMissingInAccountingPeriod((_("Template Set for balance sheet " +
                                                              "is missing in Accounting Period" + str(self))))
        elif template_set == self.template_profit_loss_statement:
            if self.template_profit_loss_statement:
                return self.template_profit_loss_statement
            else:
                raise TemplateSetMissingInAccountingPeriod((_("Template Set for profit loss statement" +
                                                              " is missing in Accounting Period" + str(self))))

    def get_fop_config_file(self, template_set):
        template_set = self.get_template_set(template_set)
        return template_set.get_fop_config_file()

    def get_xsl_file(self, template_set):
        template_set = self.get_template_set(template_set)
        return template_set.get_xsl_file()

    def create_pdf(self, template_set, printed_by):
        import koalixcrm.crm
        return koalixcrm.crm.documents.pdfexport.PDFExport.create_pdf(self, template_set, printed_by)

    def overall_earnings(self):
        earnings = 0
        accounts = Account.objects.all()
        for account in list(accounts):
            if account.account_type == "E":
                earnings += account.sum_of_all_bookings_within_accounting_period(self)
        return earnings

    def overall_spendings(self):
        spendings = 0
        accounts = Account.objects.all()
        for account in list(accounts):
            if account.account_type == "S":
                spendings += account.sum_of_all_bookings_within_accounting_period(self)
        return spendings

    def overall_assets(self):
        assets = 0
        accounts = Account.objects.all()
        for account in list(accounts):
            if account.account_type == "A":
                assets += account.sum_of_all_bookings_through_now(self)
        return assets

    def overall_liabilities(self):
        liabilities = 0
        accounts = Account.objects.all()
        for account in list(accounts):
            if account.account_type == "L":
                liabilities += account.sum_of_all_bookings_through_now(self)
        return liabilities

    def serialize_to_xml(self):
        objects = [self, ]
        main_xml = PDFExport.write_xml(objects)
        accounts = Account.objects.all()
        for account in accounts:
            account_xml = account.serialize_to_xml(self)
            main_xml = PDFExport.merge_xml(main_xml, account_xml)
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Overall_Earnings",
                                                       self.overall_earnings())
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Overall_Spendings",
                                                       self.overall_spendings())
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>']",
                                                       "Overall_Assets",
                                                       self.overall_assets())
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[@<EMAIL>='<EMAIL>']",
                                                       "Overall_Liabilities",
                                                       self.overall_liabilities())
        return main_xml

    @staticmethod
    def get_current_valid_accounting_period():
        """Returns the accounting period that is currently valid. Valid is an accounting_period when the current date
          lies between begin and end of the accounting_period

        Args:
          no arguments

        Returns:
          accounting_period (AccoutingPeriod)

        Raises:
          AccountingPeriodNotFound when there is no valid accounting Period"""
        current_valid_accounting_period = None
        for accounting_period in AccountingPeriod.objects.all():
            if accounting_period.begin < date.today() and accounting_period.end > date.today():
                return accounting_period
        if not current_valid_accounting_period:
            raise AccountingPeriodNotFound("The accounting period was not found")

    def get_all_prior_accounting_periods(self):
        """Returns the accounting period that is currently valid. Valid is an accountingPeriod when the current date
          lies between begin and end of the accountingPeriod

        Args:
          no arguments

        Returns:
          accounting_period (List of AccoutingPeriod)

        Raises:
          AccountingPeriodNotFound when there is no valid accounting Period"""
        accounting_periods = []
        for accounting_period in AccountingPeriod.objects.all():
            if accounting_period.end < self.begin:
                accounting_periods.append(accounting_period)
        if accounting_periods == []:
            raise AccountingPeriodNotFound("Accounting Period does not exist")
        return accounting_periods

    def __str__(self):
        return self.title

        # TODO: def createNewAccountingPeriod() Neues Geschäftsjahr erstellen

    class Meta:
        app_label = "accounting"
        verbose_name = _('Accounting Period')
        verbose_name_plural = _('Accounting Periods')


class AccountingPeriodForm(forms.ModelForm):
    """AccountingPeriodForm is used to overwrite the clean method of the
    original form and to add an additional check to the model"""

    class Meta:
        model = AccountingPeriod
        fields = '__all__'

    def clean(self):
        super(AccountingPeriodForm, self).clean()
        errors = []
        try:
            if self.cleaned_data['begin'] > self.cleaned_data['end']:
                errors.append(_('The begin date cannot be later than the end date.'))
        except KeyError:
            errors.append(_('The begin and the end date may not be empty'))
        if errors:
            raise forms.ValidationError(errors)
        return self.cleaned_data


class OptionAccountingPeriod(admin.ModelAdmin):
    list_display = ('title',
                    'begin',
                    'end',
                    'template_set_balance_sheet',
                    'template_profit_loss_statement')
    list_display_links = ('title',
                          'begin',
                          'end',
                          'template_set_balance_sheet',
                          'template_profit_loss_statement')
    fieldsets = (
        (_('Basics'), {
            'fields': ('title',
                       'begin',
                       'end',
                       'template_set_balance_sheet',
                       'template_profit_loss_statement')
        }),
    )
    inlines = [InlineBookings, ]
    save_as = True

    form = AccountingPeriodForm

    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for instance in instances:
            if change:
                instance.last_modified_by = request.user
            else:
                instance.last_modified_by = request.user
                instance.staff = request.user
            instance.save()

    def create_pdf_of_balance_sheet(self, request, queryset):
        from koalixcrm.crm.views.pdfexport import PDFExportView
        for obj in queryset:
            response = PDFExportView.export_pdf(self,
                                                request,
                                                obj,
                                                ("/admin/accounting/"+obj.__class__.__name__.lower()+"/"),
                                                obj.template_set_balance_sheet)
            return response

    create_pdf_of_balance_sheet.short_description = _("Create PDF of Balance Sheet")

    def create_pdf_of_profit_loss_statement(self, request, queryset):
        from koalixcrm.crm.views.pdfexport import PDFExportView
        for obj in queryset:
            response = PDFExportView.export_pdf(self,
                                                request,
                                                obj,
                                                ("/admin/accounting/"+obj.__class__.__name__.lower()+"/"),
                                                obj.template_profit_loss_statement,)
            return response

    create_pdf_of_profit_loss_statement.short_description = _("Create PDF of Profit Loss Statement Sheet")

    actions = ['create_pdf_of_balance_sheet',
               'create_pdf_of_profit_loss_statement']
<file_sep>/koalixcrm/crm/migrations/0055_auto_20181022_1937.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-22 19:37
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0054_auto_20181020_1845'),
    ]

    operations = [
        migrations.AlterField(
            model_name='currencytransform',
            name='factor',
            field=models.DecimalField(decimal_places=2, default=1, max_digits=17, verbose_name='Factor between From and To Currency'),
            preserve_default=False,
        ),
        migrations.AlterField(
            model_name='customergrouptransform',
            name='factor',
            field=models.DecimalField(decimal_places=2, default=1, max_digits=17, verbose_name='Factor between From and To Customer Group'),
            preserve_default=False,
        ),
        migrations.AlterField(
            model_name='unittransform',
            name='factor',
            field=models.DecimalField(decimal_places=2, default=1, max_digits=17, verbose_name='Factor between From and To Unit'),
            preserve_default=False,
        ),
    ]
<file_sep>/koalixcrm/crm/documents/purchase_order.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _
from koalixcrm.crm.const.status import *
from koalixcrm.crm.documents.sales_document import SalesDocument, OptionSalesDocument
from koalixcrm.plugin import *


class PurchaseOrder(SalesDocument):
    supplier = models.ForeignKey("Supplier", verbose_name=_("Supplier"), null=True)
    status = models.CharField(max_length=1, choices=PURCHASEORDERSTATUS)

    def create_from_reference(self, calling_model):
        self.create_sales_document(calling_model)
        self.status = 'O'
        self.template_set = self.contract.get_template_set(self)
        self.save()
        self.attach_sales_document_positions(calling_model)
        self.attach_text_paragraphs()
        self.staff = calling_model.staff

    def __str__(self):
        return _("Purchase Order") + ": " + self.id.__str__() + " " + _("from Contract") + ": " + self.contract.id.__str__()

    class Meta:
        app_label = "crm"
        verbose_name = _('Purchase Order')
        verbose_name_plural = _('Purchase Orders')


class OptionPurchaseOrder(OptionSalesDocument):
    list_display = OptionSalesDocument.list_display + ('supplier', 'status',)
    list_filter = OptionSalesDocument.list_filter + ('status',)
    ordering = OptionSalesDocument.ordering
    search_fields = OptionSalesDocument.search_fields
    fieldsets = OptionSalesDocument.fieldsets + (
        (_('Purchase Order specific'), {
            'fields': ('supplier', 'status',)
        }),
    )

    save_as = OptionSalesDocument.save_as
    inlines = OptionSalesDocument.inlines
    actions = ['create_purchase_confirmation', 'create_invoice', 'create_quote',
               'create_delivery_note', 'create_pdf',
               'register_invoice_in_accounting', 'register_payment_in_accounting',]

    pluginProcessor = PluginProcessor()
    inlines.extend(pluginProcessor.getPluginAdditions("quoteInlines"))
<file_sep>/koalixcrm/crm/reporting/resource_manager.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from koalixcrm.djangoUserExtension.user_extension.user_extension import UserExtension
from django.utils.translation import ugettext as _


class ResourceManager(models.Model):
    user = models.ForeignKey(UserExtension,
                             verbose_name=_("User"))


class ResourceManagerAdminView(admin.ModelAdmin):
    list_display = ('id',
                    'user',)
    fieldsets = (
        (_('Basics'), {
            'fields': ('user',)
        }),
    )<file_sep>/koalixcrm/crm/tests/test_calculations_document.py
import pytest
import datetime
from django.test import TestCase
from koalixcrm.crm.documents.calculations import Calculations
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.crm.factories.factory_quote import StandardQuoteFactory
from koalixcrm.crm.factories.factory_sales_document_position import StandardSalesDocumentPositionFactory
from koalixcrm.crm.factories.factory_product_type import StandardProductTypeFactory
from koalixcrm.crm.factories.factory_product_price import StandardPriceFactory
from koalixcrm.crm.factories.factory_customer import StandardCustomerFactory
from koalixcrm.crm.factories.factory_customer_group import StandardCustomerGroupFactory, AdvancedCustomerGroupFactory
from koalixcrm.crm.factories.factory_tax import StandardTaxFactory
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory, SmallUnitFactory
from koalixcrm.crm.factories.factory_customer_group_transform import StandardCustomerGroupTransformFactory
from koalixcrm.crm.factories.factory_unit_transform import StandardUnitTransformFactory
from koalixcrm.crm.factories.factory_currency_transform import StandardCurrencyTransformFactory
from koalixcrm.test_support_functions import make_date_utc


class DocumentCalculationsTest(TestCase):
    def setUp(self):
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        start_date = (datetime_now - datetime.timedelta(days=30)).date()
        end_date = (datetime_now + datetime.timedelta(days=30)).date()
        self.tax = StandardTaxFactory.create(tax_rate=10)
        self.test_currency_with_rounding = StandardCurrencyFactory.create(rounding=1)
        self.test_currency_without_rounding = StandardCurrencyFactory.create(
            rounding=None,
            description='Euro',
            short_name='EUR'
        )
        self.alternative_currency = self.test_currency_without_rounding
        self.customer_group = StandardCustomerGroupFactory.create()
        self.alternative_customer_group = AdvancedCustomerGroupFactory.create()
        self.customer = StandardCustomerFactory.create()
        self.customer.is_member_of.add(self.customer_group)
        self.customer.save()
        self.unit = StandardUnitFactory.create()
        self.alternative_unit = SmallUnitFactory.create()
        self.product_without_dates = StandardProductTypeFactory.create(
            product_type_identifier="A",
            tax=self.tax
        )
        self.product_without_date_from = StandardProductTypeFactory.create(
            product_type_identifier="B",
            tax=self.tax
        )
        self.product_without_date_until = StandardProductTypeFactory.create(
            product_type_identifier="C",
            tax=self.tax
        )
        self.product_without_customer_group = StandardProductTypeFactory.create(
            product_type_identifier="D",
            tax=self.tax
        )
        self.product_with_currency_rounding = StandardProductTypeFactory.create(
            product_type_identifier="E",
            tax=self.tax
        )
        self.product_without_currency_rounding = StandardProductTypeFactory.create(
            product_type_identifier="F",
            tax=self.tax
        )
        self.product_with_alternative_customer_group = StandardProductTypeFactory.create(
            product_type_identifier="G",
            tax=self.tax
        )
        self.product_with_alternative_unit = StandardProductTypeFactory.create(
            product_type_identifier="H",
            tax=self.tax
        )
        self.product_with_alternative_currency = StandardProductTypeFactory.create(
            product_type_identifier="I",
            tax=self.tax
        )
        self.price_without_customer_group = StandardPriceFactory.create(
            product_type=self.product_without_customer_group,
            customer_group=None,
            price=100,
            unit=self.unit,
            currency=self.test_currency_with_rounding,
            valid_from=start_date,
            valid_until=end_date
        )
        self.price_without_dates = StandardPriceFactory.create(
            product_type=self.product_without_dates,
            customer_group=self.customer_group,
            valid_from=None,
            valid_until=None,
            unit=self.unit,
            currency=self.test_currency_with_rounding,
            price=80
        )
        self.price_without_date_to = StandardPriceFactory.create(
            product_type=self.product_without_date_until,
            customer_group=self.customer_group,
            valid_from=start_date,
            valid_until=None,
            unit=self.unit,
            currency=self.test_currency_with_rounding,
            price=130
        )
        self.price_without_date_from = StandardPriceFactory.create(
            product_type=self.product_without_date_from,
            customer_group=self.customer_group,
            valid_from=None,
            valid_until=end_date,
            unit=self.unit,
            currency=self.test_currency_with_rounding,
            price=50
        )
        self.price_without_date_from = StandardPriceFactory.create(
            product_type=self.product_without_currency_rounding,
            currency=self.test_currency_without_rounding,
            customer_group=self.customer_group,
            price=25,
            unit=self.unit,
            valid_from=start_date,
            valid_until=end_date
        )
        self.price_without_date_from = StandardPriceFactory.create(
            product_type=self.product_with_currency_rounding,
            currency=self.test_currency_with_rounding,
            customer_group=self.customer_group,
            price=33,
            unit=self.unit,
            valid_from=start_date,
            valid_until=end_date
        )
        self.price_with_alternative_customer_group = StandardPriceFactory.create(
            product_type=self.product_with_alternative_customer_group,
            customer_group=self.alternative_customer_group,
            valid_from=start_date,
            valid_until=end_date,
            unit=self.unit,
            currency=self.test_currency_with_rounding,
            price=80
        )
        self.customer_group_transform = StandardCustomerGroupTransformFactory.create(
            from_customer_group=self.alternative_customer_group,
            to_customer_group=self.customer_group,
            product_type=self.product_with_alternative_customer_group,
            factor=0.50
        )
        self.price_with_alternative_currency = StandardPriceFactory.create(
            product_type=self.product_with_alternative_currency,
            customer_group=self.customer_group,
            valid_from=start_date,
            valid_until=end_date,
            unit=self.unit,
            currency=self.alternative_currency,
            price=80
        )
        self.currency_transform = StandardCurrencyTransformFactory.create(
            from_currency=self.alternative_currency,
            to_currency=self.test_currency_with_rounding,
            product_type=self.product_with_alternative_currency,
            factor=0.50
        )
        self.price_with_alternative_unit = StandardPriceFactory.create(
            product_type=self.product_with_alternative_unit,
            customer_group=self.customer_group,
            valid_from=start_date,
            valid_until=end_date,
            unit=self.alternative_unit,
            currency=self.test_currency_with_rounding,
            price=80
        )
        self.currency_transform = StandardUnitTransformFactory.create(
            from_unit=self.alternative_unit,
            to_unit=self.unit,
            product_type=self.product_with_alternative_unit,
            factor=0.50
        )

    @pytest.mark.back_end_tests
    def test_calculate_document_price_without_customer_group(self):
        quote_1 = StandardQuoteFactory.create(customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            product_type=self.product_without_customer_group,
            overwrite_product_price=False,
            unit=self.unit,
            sales_document=quote_1
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_1,
            pricing_date=date_now)
        self.assertEqual(
            quote_1.last_calculated_price.__str__(), "90.00")
        self.assertEqual(
            quote_1.last_calculated_tax.__str__(), "10.00")

    @pytest.mark.back_end_tests
    def test_calculate_document_price_without_date_from(self):
        quote_2 = StandardQuoteFactory.create(customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            unit=self.unit,
            product_type=self.product_without_date_from,
            overwrite_product_price=False,
            sales_document=quote_2
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_2,
            pricing_date=date_now)
        self.assertEqual(
            quote_2.last_calculated_price.__str__(), "45.00")
        self.assertEqual(
            quote_2.last_calculated_tax.__str__(), "5.00")

    @pytest.mark.back_end_tests
    def test_calculate_document_price_without_date_until(self):
        quote_3 = StandardQuoteFactory.create(customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            unit=self.unit,
            product_type=self.product_without_date_until,
            overwrite_product_price=False,
            sales_document=quote_3
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_3,
            pricing_date=date_now)
        self.assertEqual(
            quote_3.last_calculated_price.__str__(), "117.00")
        self.assertEqual(
            quote_3.last_calculated_tax.__str__(), "13.00")

    @pytest.mark.back_end_tests
    def test_calculate_document_price_without_dates(self):
        quote_4 = StandardQuoteFactory.create(customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            unit=self.unit,
            product_type=self.product_without_dates,
            overwrite_product_price=False,
            sales_document=quote_4
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_4,
            pricing_date=date_now)
        self.assertEqual(
            quote_4.last_calculated_price.__str__(), "72.00")
        self.assertEqual(
            quote_4.last_calculated_tax.__str__(), "8.00")

    @pytest.mark.back_end_tests
    def test_calculate_document_price_with_currency_rounding(self):
        quote_5 = StandardQuoteFactory.create(
            customer=self.customer,
            currency=self.test_currency_with_rounding
        )
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=7.5,
            unit=self.unit,
            product_type=self.product_with_currency_rounding,
            overwrite_product_price=False,
            sales_document=quote_5
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_5,
            pricing_date=date_now)
        self.assertEqual(
            quote_5.last_calculated_price.__str__(), "27")
        self.assertEqual(
            quote_5.last_calculated_tax.__str__(), "3")

    @pytest.mark.back_end_tests
    def test_calculate_document_price_without_currency_rounding(self):
        quote_6 = StandardQuoteFactory.create(
            customer=self.customer,
            currency=self.test_currency_without_rounding
        )
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=7.5,
            unit=self.unit,
            product_type=self.product_without_currency_rounding,
            overwrite_product_price=False,
            sales_document=quote_6
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_6,
            pricing_date=date_now)
        self.assertEqual(
            quote_6.last_calculated_price.__str__(), "20.80")
        self.assertEqual(
            quote_6.last_calculated_tax.__str__(), "2.30")

    @pytest.mark.back_end_tests
    def test_calculate_document_price_with_document_discount(self):
        quote_7 = StandardQuoteFactory.create(
            customer=self.customer,
            currency=self.test_currency_without_rounding,
            discount=10
        )
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=7.5,
            unit=self.unit,
            product_type=self.product_without_currency_rounding,
            overwrite_product_price=False,
            sales_document=quote_7
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_7,
            pricing_date=date_now)
        self.assertEqual(
            quote_7.last_calculated_price.__str__(), "18.70")
        self.assertEqual(
            quote_7.last_calculated_tax.__str__(), "2.05")

    @pytest.mark.back_end_tests
    def test_calculate_document_with_customer_group_transform(self):
        quote_8 = StandardQuoteFactory.create(
            customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            unit=self.unit,
            product_type=self.product_with_alternative_customer_group,
            overwrite_product_price=False,
            sales_document=quote_8
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_8,
            pricing_date=date_now)
        self.assertEqual(
            quote_8.last_calculated_price.__str__(), "36.00")
        self.assertEqual(
            quote_8.last_calculated_tax.__str__(), "4.00")

    @pytest.mark.back_end_tests
    def test_calculate_document_with_currency_transform(self):
        quote_9 = StandardQuoteFactory.create(
            currency=self.test_currency_with_rounding,
            customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            unit=self.unit,
            product_type=self.product_with_alternative_currency,
            overwrite_product_price=False,
            sales_document=quote_9
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_9,
            pricing_date=date_now)
        self.assertEqual(
            quote_9.last_calculated_price.__str__(), "36")
        self.assertEqual(
            quote_9.last_calculated_tax.__str__(), "4")

    @pytest.mark.back_end_tests
    def test_calculate_document_with_unit_transform(self):
        quote_10 = StandardQuoteFactory.create(customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            unit=self.unit,
            product_type=self.product_with_alternative_unit,
            overwrite_product_price=False,
            sales_document=quote_10
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_10,
            pricing_date=date_now)
        self.assertEqual(
            quote_10.last_calculated_price.__str__(), "36.00")
        self.assertEqual(
            quote_10.last_calculated_tax.__str__(), "4.00")
<file_sep>/koalixcrm/crm/factories/factory_purchase_order.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import PurchaseOrder
from koalixcrm.crm.factories.factory_supplier import StandardSupplierFactory
from koalixcrm.crm.factories.factory_sales_document import StandardSalesDocumentFactory


class StandardPurchaseOrderFactory(StandardSalesDocumentFactory):
    class Meta:
        model = PurchaseOrder

    supplier = factory.SubFactory(StandardSupplierFactory)
    status = "C"
<file_sep>/koalixcrm/crm/factories/factory_task.py
# -*- coding: utf-8 -*-

import factory
import datetime
from koalixcrm.crm.models import Task
from koalixcrm.crm.factories.factory_project import StandardProjectFactory
from koalixcrm.crm.factories.factory_task_status import StartedTaskStatusFactory
from koalixcrm.test_support_functions import make_date_utc


class StandardTaskFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Task

    title = 'This is a test Task'
    project = factory.SubFactory(StandardProjectFactory)
    description = "This is a description"
    status = factory.SubFactory(StartedTaskStatusFactory)
    last_status_change = make_date_utc(datetime.datetime(2018, 6, 15, 00))
<file_sep>/koalixcrm/crm/tests/test_task_effective_duration.py
import datetime
import pytest
from django.test import TestCase
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_work import StandardWorkFactory
from koalixcrm.crm.factories.factory_task_status import DoneTaskStatusFactory
from koalixcrm.crm.factories.factory_reporting_period import StandardReportingPeriodFactory
from koalixcrm.crm.factories.factory_human_resource import StandardHumanResourceFactory
from koalixcrm.crm.factories.factory_task import StandardTaskFactory
from koalixcrm.test_support_functions import make_date_utc
from koalixcrm.crm.factories.factory_estimation import StandardHumanResourceEstimationToTaskFactory


@pytest.fixture()
def freeze(monkeypatch):
    """ Now() manager patches date return a fixed, settable, value
        (freezes date)
    """
    original = datetime.date

    class FreezeMeta(type):
        def __instancecheck__(self, instance):
            if instance.isinstance(original) or instance.isinstance(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 TaskEffectiveDuration(TestCase):

    @pytest.fixture(autouse=True)
    def freeze_time(self, freeze):
        self._freeze = freeze

    def setUp(self):
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        start_date = (datetime_now - datetime.timedelta(days=30))
        end_date_first_task = (datetime_now + datetime.timedelta(days=30))
        end_date_second_task = (datetime_now + datetime.timedelta(days=60))

        self.test_billing_cycle = StandardCustomerBillingCycleFactory.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.human_resource = StandardHumanResourceFactory.create()
        self.test_reporting_period = StandardReportingPeriodFactory.create()
        self.test_1st_task = StandardTaskFactory.create(title="1st Test Task",
                                                        project=self.test_reporting_period.project)
        self.estimation_1st_task = StandardHumanResourceEstimationToTaskFactory(task=self.test_1st_task,
                                                                                date_from=start_date,
                                                                                date_until=end_date_first_task)
        self.test_2nd_task = StandardTaskFactory.create(title="2nd Test Task",
                                                        project=self.test_reporting_period.project)
        self.estimation_2nd_task = StandardHumanResourceEstimationToTaskFactory(task=self.test_2nd_task,
                                                                                date_from=start_date,
                                                                                date_until=end_date_second_task)

    @pytest.mark.back_end_tests
    def test_effective_duration(self):
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        datetime_later_1 = make_date_utc(datetime.datetime(2024, 1, 2, 2, 00))
        datetime_later_2 = make_date_utc(datetime.datetime(2024, 1, 2, 3, 30))
        datetime_later_3 = make_date_utc(datetime.datetime(2024, 1, 4, 5, 45))
        datetime_later_4 = make_date_utc(datetime.datetime(2024, 1, 4, 6, 15))
        self.assertEqual(
            (self.test_1st_task.planned_duration()).__str__(), "60")
        self.assertEqual(
            (self.test_1st_task.effective_duration()).__str__(), "0")
        self.assertEqual(
            (self.test_2nd_task.planned_duration()).__str__(), "90")
        self.assertEqual(
            (self.test_2nd_task.effective_duration()).__str__(), "0")

        new_status = DoneTaskStatusFactory.create()
        self._freeze.freeze(datetime.date(2024, 2, 2))
        self.test_1st_task.status = new_status
        self.test_1st_task.save()
        self._freeze.freeze(datetime.date(2024, 2, 5))
        self.test_2nd_task.status = new_status
        self.test_2nd_task.save()

        self.assertEqual(
            (self.test_1st_task.planned_duration()).__str__(), "60")
        self.assertEqual(
            (self.test_1st_task.effective_duration()).__str__(), "62")
        self.assertEqual(
            (self.test_2nd_task.planned_duration()).__str__(), "90")
        self.assertEqual(
            (self.test_2nd_task.effective_duration()).__str__(), "65")

        StandardWorkFactory.create(
            human_resource=self.human_resource,
            date=datetime_now,
            start_time=datetime_now,
            stop_time=datetime_later_1,
            task=self.test_1st_task,
            reporting_period=self.test_reporting_period
        )
        StandardWorkFactory.create(
            human_resource=self.human_resource,
            date=datetime_later_2,
            start_time=datetime_later_1,
            stop_time=datetime_later_2,
            task=self.test_1st_task,
            reporting_period=self.test_reporting_period
        )
        StandardWorkFactory.create(
            human_resource=self.human_resource,
            date=datetime_now,
            start_time=datetime_now,
            stop_time=datetime_later_3,
            task=self.test_2nd_task,
            reporting_period=self.test_reporting_period
        )
        StandardWorkFactory.create(
            human_resource=self.human_resource,
            date=datetime_later_4,
            start_time=datetime_now,
            stop_time=datetime_later_4,
            task=self.test_2nd_task,
            reporting_period=self.test_reporting_period
        )

        self.assertEqual(
            (self.test_1st_task.planned_duration()).__str__(), "60")
        self.assertEqual(
            (self.test_1st_task.effective_duration()).__str__(), "1")
        self.assertEqual(
            (self.test_2nd_task.planned_duration()).__str__(), "90")
        self.assertEqual(
            (self.test_2nd_task.effective_duration()).__str__(), "3")
<file_sep>/koalixcrm/crm/documents/invoice.py
# -*- coding: utf-8 -*-

from datetime import *
from django import forms
from django.db import models
from django.contrib import admin
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from django.utils.html import format_html
from django.contrib.admin import helpers
from django.shortcuts import render
from django.contrib import messages
from django.template.context_processors import csrf
from koalixcrm.crm.const.status import *
from koalixcrm.crm.exceptions import *
from koalixcrm import accounting
from koalixcrm.crm.documents.sales_document import SalesDocument, OptionSalesDocument
from koalixcrm.crm.documents.sales_document_position import SalesDocumentPosition
from koalixcrm.plugin import *
from koalixcrm.accounting.models import Account
from koalixcrm.global_support_functions import limit_string_length


class Invoice(SalesDocument):
    payable_until = models.DateField(verbose_name=_("To pay until"))
    payment_bank_reference = models.CharField(verbose_name=_("Payment Bank Reference"), max_length=100, blank=True,
                                              null=True)
    status = models.CharField(max_length=1, choices=INVOICESTATUS)

    def link_to_invoice(self):
        if self.id:
            return format_html("<a href='/admin/crm/invoice/%s' >%s</a>" % (str(self.id),
                                                                            limit_string_length(str(self.description),
                                                                                                30)))
        else:
            return "Not present"
    link_to_invoice.short_description = _("Invoice")

    def create_from_reference(self, calling_model):
        self.create_sales_document(calling_model)
        self.status = 'C'
        self.payable_until = date.today() + \
                             timedelta(days=self.customer.default_customer_billing_cycle.time_to_payment_date)
        self.date_of_creation = date.today().__str__()
        self.template_set = self.contract.get_template_set(self)
        self.save()
        self.attach_sales_document_positions(calling_model)
        self.attach_text_paragraphs()

    def register_invoice_in_accounting(self, request):
        dict_prices = dict()
        dict_tax = dict()
        current_valid_accounting_period = accounting.models.AccountingPeriod.get_current_valid_accounting_period()
        activa_account = accounting.models.Account.objects.filter(isopeninterestaccount=True)
        if not self.is_complete_with_price():
            raise IncompleteInvoice(_("Complete invoice and run price recalculation. Price may not be Zero"))
        if len(activa_account) == 0:
            raise OpenInterestAccountMissing(_("Please specify one open interest account in the accounting"))
        for position in list(SalesDocumentPosition.objects.filter(sales_document=self.id)):
            profit_account = position.product.accounting_product_categorie.profitAccount
            dict_prices[profit_account] = position.last_calculated_price
            dict_tax[profit_account] = position.last_calculated_tax

        for booking in accounting.models.Booking.objects.filter(accountingPeriod=current_valid_accounting_period):
            if booking.bookingReference == self:
                raise InvoiceAlreadyRegistered("The invoice is already registered")
        for profit_account, amount in iter(dict_prices.items()):
            booking = accounting.models.Booking()
            booking.toAccount = activa_account[0]
            booking.fromAccount = profit_account
            booking.bookingReference = self
            booking.accountingPeriod = current_valid_accounting_period
            booking.bookingDate = date.today().__str__()
            booking.staff = request.user
            booking.amount = amount
            booking.lastmodifiedby = request.user
            booking.save()

    def register_payment_in_accounting(self, request, amount, payment_account):
        current_valid_accounting_period = accounting.models.AccountingPeriod.get_current_valid_accounting_period()
        activa_account = accounting.models.Account.objects.filter(isopeninterestaccount=True)
        booking = accounting.models.Booking()
        booking.toAccount = payment_account
        booking.fromAccount = activa_account[0]
        booking.bookingDate = date.today().__str__()
        booking.bookingReference = self
        booking.accountingPeriod = current_valid_accounting_period
        booking.amount = amount
        booking.staff = request.user
        booking.lastmodifiedby = request.user
        booking.save()

    def __str__(self):
        return _("Invoice") + ": " + self.id.__str__() + " " + _("from Contract") + ": " + self.contract.id.__str__()

    class Meta:
        app_label = "crm"
        verbose_name = _('Invoice')
        verbose_name_plural = _('Invoices')


class OptionInvoice(OptionSalesDocument):
    list_display = OptionSalesDocument.list_display + ('payable_until', 'status',)
    list_filter = OptionSalesDocument.list_filter + ('status',)
    ordering = OptionSalesDocument.ordering
    search_fields = OptionSalesDocument.search_fields
    fieldsets = OptionSalesDocument.fieldsets + (
        (_('Invoice specific'), {
            'fields': ('payable_until', 'status', 'payment_bank_reference' )
        }),
    )

    class PaymentForm(forms.Form):
        payment_amount = forms.DecimalField()
        _selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
        payment_account = forms.ModelChoiceField(Account.objects.filter(account_type="A"))

    def register_invoice_in_accounting(self, request, queryset):
        try:
            for obj in queryset:
                obj.register_invoice_in_accounting(request)
            self.message_user(request, _("Successfully registered Invoice in the Accounting"))
            return;
        except OpenInterestAccountMissing as e:
            self.message_user(request, "Did not register Invoice in Accounting: " + e.__str__(), level=messages.ERROR)
            return;
        except IncompleteInvoice as e:
            self.message_user(request, "Did not register Invoice in Accounting: " + e.__str__(), level=messages.ERROR)
            return;

    register_invoice_in_accounting.short_description = _("Register Invoice in Accounting")

    # def unregisterInvoiceInAccounting(self, request, queryset):
    # for obj in queryset:
    # obj.createPDF(deliveryorder=True)
    # self.message_user(request, _("Successfully unregistered Invoice in the Accounting"))
    # unregisterInvoiceInAccounting.short_description = _("Unregister Invoice in Accounting")

    def register_payment_in_accounting(self, request, queryset):
        form = None
        if request.POST.get('post'):
            if 'cancel' in request.POST:
                self.message_user(request, _("Canceled registration of payment in the accounting"), level=messages.ERROR)
                return
            elif 'register' in request.POST:
                form = self.PaymentForm(request.POST)
                if form.is_valid():
                    payment_amount = form.cleaned_data['payment_amount']
                    payment_account = form.cleaned_data['payment_account']
                    for obj in queryset:
                        obj.register_payment_in_accounting(request, payment_amount, payment_account)
                    self.message_user(request, _("Successfully registered Payment in the Accounting"))
                    return HttpResponseRedirect(request.get_full_path())
        else:
            form = self.PaymentForm
            c = {'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
                 'queryset': queryset,
                 'form': form}
            c.update(csrf(request))
            return render(request, 'crm/admin/register_payment.html', c)

    register_payment_in_accounting.short_description = _("Register Payment in Accounting")

    save_as = OptionSalesDocument.save_as
    inlines = OptionSalesDocument.inlines

    actions = ['create_purchase_confirmation', 'create_quote',
               'create_delivery_note', 'create_purchase_order', 'create_pdf', 'create_payment_reminder',
               'register_invoice_in_accounting', 'register_payment_in_accounting',]

    pluginProcessor = PluginProcessor()
    actions.extend(pluginProcessor.getPluginAdditions("invoiceActions"))
    inlines.extend(pluginProcessor.getPluginAdditions("invoiceInlines"))


class InlineInvoice(admin.TabularInline):
    model = Invoice
    classes = ['collapse']
    show_change_link = True
    can_delete = True
    extra = 1
    readonly_fields = ('link_to_invoice',
                       'contract',
                       'customer',
                       'payable_until',
                       'status',
                       'last_pricing_date',
                       'last_calculated_price',
                       'last_calculated_tax')
    fieldsets = (
        (_('Invoice'), {
            'fields': ('link_to_invoice',
                       'contract',
                       'customer',
                       'payable_until',
                       'status',
                       'last_pricing_date',
                       'last_calculated_price',
                       'last_calculated_tax')
        }),
    )

    allow_add = False
<file_sep>/koalixcrm/crm/product/product_type.py
# -*- coding: utf-8 -*-
from django.contrib import admin
from django.db import models
from django.utils.translation import ugettext as _

from koalixcrm.crm.product.product_price import ProductPrice
from koalixcrm.crm.product.product_price import ProductPriceInlineAdminView
from koalixcrm.crm.product.unit_transform import UnitTransformInlineAdminView
from koalixcrm.crm.product.customer_group_transform import CustomerGroupTransformInlineAdminView
from koalixcrm.crm.product.currency_transform import CurrencyTransformInlineAdminView


class ProductType(models.Model):
    description = models.TextField(verbose_name=_("Description"),
                                   null=True,
                                   blank=True)
    title = models.CharField(verbose_name=_("Title"),
                             max_length=200)
    product_type_identifier = models.CharField(verbose_name=_("Product Number"),
                                               max_length=200,
                                               null=True,
                                               blank=True)
    default_unit = models.ForeignKey("Unit", verbose_name=_("Unit"))
    tax = models.ForeignKey("Tax",
                            blank=False,
                            null=False)
    last_modification = models.DateTimeField(verbose_name=_("Last modified"),
                                             auto_now=True)
    last_modified_by = models.ForeignKey('auth.User',
                                         limit_choices_to={'is_staff': True},
                                         verbose_name=_("Last modified by"),
                                         null=True,
                                         blank=True)
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"),
                                            auto_now_add=True)
    accounting_product_category = models.ForeignKey('accounting.ProductCategory',
                                                    verbose_name=_("Accounting Product Category"),
                                                    null=True,
                                                    blank=True)

    def get_price(self, date, unit, customer, currency):
        """The function searches for a valid price and returns the price of the product as a decimal value.

        Args:
            koalixcrm.crm.contact.customer customer
            koalixcrm.crm.product.unit unit
            koalixcrm.crm.product.currency currency
            datetime.date date

        Returns:
            when a match is found: dict customer_group_factors name=customer_group, value=factor
            when no match is found: customer_group_factors is None

        Raises:
            In case the algorithm does not find a valid product price, the function raises a
            NoPriceFound Exception"""
        prices = ProductPrice.objects.filter(product_type=self)
        valid_prices = list()
        for price in list(prices):
            currency_factor = price.get_currency_transform_factor(currency, self.id)
            unit_factor = price.get_unit_transform_factor(unit, self.id)
            group_factor = price.get_customer_group_transform_factor(customer, self.id)
            date_in_range = price.is_date_in_range(date)
            if date_in_range \
                    and currency_factor != 0 \
                    and unit_factor != 0 \
                    and group_factor != 0:
                transformed_price = price.price*group_factor*unit_factor*currency_factor
                valid_prices.append(transformed_price)
        if len(valid_prices) > 0:
            lowest_price = valid_prices[0]
            for price in valid_prices:
                if price < lowest_price:
                    lowest_price = price
            return lowest_price
        else:
            raise ProductType.NoPriceFound(customer, unit, date, currency, self)

    def get_tax_rate(self):
        return self.tax.get_tax_rate()

    def __str__(self):
        return str(self.product_type_identifier) + ' ' + self.title.__str__()

    class Meta:
        app_label = "crm"
        verbose_name = _('Product Type')
        verbose_name_plural = _('Product Types')

    class NoPriceFound(Exception):
        def __init__(self, customer, unit, date, currency, product):
            self.customer = customer
            self.unit = unit
            self.date = date
            self.product = product
            self.currency = currency
            return

        def __str__(self):
            return _("There is no Price for this product type") + ": " + self.product.__str__() + _(
                "that matches the date") + ": " + self.date.__str__() + " ," + _(
                "customer") + ": " + self.customer.__str__() + " ," + _(
                "currency") + ": " + self.currency.__str__() + _(" and unit") + ":" + self.unit.__str__()


class ProductTypeAdminView(admin.ModelAdmin):
    list_display = (
        'product_type_identifier',
        'title',
        'default_unit',
        'tax',
        'accounting_product_category')
    list_display_links = ('product_type_identifier',)
    fieldsets = (
        (_('Basics'), {
            'fields': (
                'product_type_identifier',
                'title',
                'description',
                'default_unit',
                'tax',
                'accounting_product_category')
        }),
    )
    inlines = [ProductPriceInlineAdminView,
               UnitTransformInlineAdminView,
               CurrencyTransformInlineAdminView,
               CustomerGroupTransformInlineAdminView]
<file_sep>/koalixcrm/accounting/views.py
# -*- coding: utf-8 -*-
from os import path
from wsgiref.util import FileWrapper
from django.http import Http404
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from koalixcrm.crm.exceptions import *
from koalixcrm.djangoUserExtension.exceptions import *
from django.utils.translation import ugettext as _


def export_pdf(calling_model_admin, request, whereToCreateFrom, whatToCreate, redirectTo):
    """This method exports PDFs provided by different Models in the accounting application

        Args:
          calling_model_admin (ModelAdmin):  The calling ModelAdmin must be provided for error message response.
          request: The request User is required to get the Calling User TemplateSets and to know where to
          save the error message whereToCreateFrom (Model):  The model from which a PDF should be exported
          whatToCreate (str): What document Type that has to be created
          redirectTo (str): String that describes to where the method sould redirect in case of an error

        Returns:
          HTTpResponse with a PDF when successful
          HTTpResponseRedirect when not successful

        Raises:
          Http404 exception if anything goes wrong"""
    try:
        pdf = whereToCreateFrom.createPDF(request.user, whatToCreate)
        response = HttpResponse(FileWrapper(open(pdf, 'rb')), content_type='application/pdf')
        response['Content-Length'] = path.getsize(pdf)
    except (TemplateSetMissing, UserExtensionMissing) as e:
        if e.isinstance(UserExtensionMissing):
            response = HttpResponseRedirect(redirectTo)
            calling_model_admin.message_user(request, _("User Extension Missing"))
        elif e.isinstance(TemplateSetMissing):
            response = HttpResponseRedirect(redirectTo)
            calling_model_admin.message_user(request, _("Templateset Missing"))
        else:
            raise Http404
    return response


def export_xml(callingModelAdmin, request, whereToCreateFrom, whatToCreate, redirectTo):
    """This method exports XMLs provided by different Models in the accounting application

        Args:
          callingModelAdmin (ModelAdmin):  The calling ModelAdmin must be provided for error message response.
          request: The request User is required to get the Calling User TemplateSets and to know where
          to save the error message hereToCreateFrom (Model):  The model from which a PDF should be exported
          whatToCreate (str): What objects that have to be serialized
          redirectTo (str): String that describes to where the method sould redirect in case of an error

        Returns:
          HTTpResponse with a PDF when successful
          HTTpResponseRedirect when not successful

        Raises:
          raises Http404 exception if anything goes wrong"""
    try:
        xml = whereToCreateFrom.createXML(request.user, whatToCreate)
        response = HttpResponse(FileWrapper(open(xml, 'rb')), mimetype='application/xml')
        response['Content-Length'] = path.getsize(xml)
    except (TemplateSetMissing, UserExtensionMissing) as e:
        if e.isinstance(UserExtensionMissing):
            response = HttpResponseRedirect(redirectTo)
            callingModelAdmin.message_user(request, _("User Extension Missing"))
        elif e.isinstance(TemplateSetMissing):
            response = HttpResponseRedirect(redirectTo)
            callingModelAdmin.message_user(request, _("Templateset Missing"))
        else:
            raise Http404
    return response
<file_sep>/koalixcrm/djangoUserExtension/migrations/0004_auto_20171210_2126.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-12-10 21:26
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import filebrowser.fields


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0003_auto_20171110_1732'),
    ]

    operations = [
        migrations.CreateModel(
            name='DocumentTemplate',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(blank=True, max_length=100, null=True, verbose_name='Title')),
                ('xsl_file', filebrowser.fields.FileBrowseField(max_length=200, verbose_name='XSL File')),
                ('fop_config_file', filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name='FOP Configuration File')),
                ('logo', filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name='Logo for the PDF generation')),
            ],
            options={
                'verbose_name': 'Document template',
                'verbose_name_plural': 'Document templates',
            },
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='addresser',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='balancesheetXSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='bankingaccountref',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='deilveryorderXSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='footerTextpurchaseorders',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='footerTextsalesorders',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='fopConfigurationFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='headerTextpurchaseorders',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='headerTextsalesorders',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='invoiceXSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='logo',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='organisationname',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='pagefooterleft',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='pagefootermiddle',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='profitLossStatementXSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='purchaseconfirmationXSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='purchaseorderXSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='quoteXSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='reminder1XSLFile',
        ),
        migrations.RemoveField(
            model_name='templateset',
            name='reminder2XSLFile',
        ),
        migrations.CreateModel(
            name='BalanceSheetTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Balance sheet template',
                'verbose_name_plural': 'Balance sheet templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.CreateModel(
            name='DeliveryNoteTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Delivery note template',
                'verbose_name_plural': 'Delivery note templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.CreateModel(
            name='InvoiceTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Invoice template',
                'verbose_name_plural': 'Invoice templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.CreateModel(
            name='PaymentReminderTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Payment reminder template',
                'verbose_name_plural': 'Payment reminder templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.CreateModel(
            name='ProfitLossStatementTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Profit loss statement template',
                'verbose_name_plural': 'Profit loss statement templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.CreateModel(
            name='PurchaseConfirmationTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Purchase confirmation template',
                'verbose_name_plural': 'Purchase confirmation templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.CreateModel(
            name='PurchaseOrderTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Purchase order template',
                'verbose_name_plural': 'Purchase order templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.CreateModel(
            name='QuoteTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Quote template',
                'verbose_name_plural': 'Quote templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.DeleteModel(
            name='XSLFile',
        ),
        migrations.AddField(
            model_name='templateset',
            name='balance_sheet_statement_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.BalanceSheetTemplate'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='delivery_note_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.DeliveryNoteTemplate'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='invoice_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.InvoiceTemplate'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='payment_reminder_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.PaymentReminderTemplate'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='profit_loss_statement_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.ProfitLossStatementTemplate'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='purchase_confirmation_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.PurchaseConfirmationTemplate'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='purchase_order_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.PurchaseOrderTemplate'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='quote_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.QuoteTemplate'),
        ),
    ]
<file_sep>/documentation/source/code_documentation/task.rst
.. highlight:: rst

Task
----

.. automodule:: koalixcrm.crm.reporting.task
   :members:

<file_sep>/koalixcrm/djangoUserExtension/migrations/0001_initial.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:02
from __future__ import unicode_literals

import django.db.models.deletion
import filebrowser.fields
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
    initial = True

    dependencies = [
        ('crm', '0001_initial'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='TemplateSet',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('organisationname', models.CharField(max_length=200, verbose_name='Name of the Organisation')),
                ('title', models.CharField(max_length=100, verbose_name='Title')),
                ('logo', filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True,
                                                            verbose_name='Logo for the PDF generation')),
                ('bankingaccountref',
                 models.CharField(blank=True, max_length=60, null=True, verbose_name='Reference to Banking Account')),
                ('addresser', models.CharField(blank=True, max_length=200, null=True, verbose_name='Addresser')),
                ('fopConfigurationFile', filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True,
                                                                            verbose_name='FOP Configuration File')),
                ('footerTextsalesorders',
                 models.TextField(blank=True, null=True, verbose_name='Footer Text On Salesorders')),
                ('headerTextsalesorders',
                 models.TextField(blank=True, null=True, verbose_name='Header Text On Salesorders')),
                ('headerTextpurchaseorders',
                 models.TextField(blank=True, null=True, verbose_name='Header Text On Purchaseorders')),
                ('footerTextpurchaseorders',
                 models.TextField(blank=True, null=True, verbose_name='Footer Text On Purchaseorders')),
                ('pagefooterleft',
                 models.CharField(blank=True, max_length=40, null=True, verbose_name='Page Footer Left')),
                ('pagefootermiddle',
                 models.CharField(blank=True, max_length=40, null=True, verbose_name='Page Footer Middle')),
            ],
            options={
                'verbose_name': 'Templateset',
                'verbose_name_plural': 'Templatesets',
            },
        ),
        migrations.CreateModel(
            name='UserExtension',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('defaultCurrency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Currency')),
                ('defaultTemplateSet',
                 models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.TemplateSet')),
                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
            ],
            options={
                'verbose_name': 'User Extention',
                'verbose_name_plural': 'User Extentions',
            },
        ),
        migrations.CreateModel(
            name='UserExtensionEmailAddress',
            fields=[
                ('emailaddress_ptr',
                 models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
                                      primary_key=True, serialize=False, to='crm.EmailAddress')),
                ('purpose', models.CharField(
                    choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business')],
                    max_length=1, verbose_name='Purpose')),
                ('userExtension', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                                    to='djangoUserExtension.UserExtension')),
            ],
            options={
                'verbose_name': 'Email Address for User Extention',
                'verbose_name_plural': 'Email Address for User Extention',
            },
            bases=('crm.emailaddress',),
        ),
        migrations.CreateModel(
            name='UserExtensionPhoneAddress',
            fields=[
                ('phoneaddress_ptr',
                 models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
                                      primary_key=True, serialize=False, to='crm.PhoneAddress')),
                ('purpose', models.CharField(
                    choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business')],
                    max_length=1, verbose_name='Purpose')),
                ('userExtension', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                                    to='djangoUserExtension.UserExtension')),
            ],
            options={
                'verbose_name': 'Phonenumber for User Extention',
                'verbose_name_plural': 'Phonenumber for User Extention',
            },
            bases=('crm.phoneaddress',),
        ),
        migrations.CreateModel(
            name='UserExtensionPostalAddress',
            fields=[
                ('postaladdress_ptr',
                 models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
                                      primary_key=True, serialize=False, to='crm.PostalAddress')),
                ('purpose', models.CharField(
                    choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business')],
                    max_length=1, verbose_name='Purpose')),
                ('userExtension', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                                    to='djangoUserExtension.UserExtension')),
            ],
            options={
                'verbose_name': 'Postal Address for User Extention',
                'verbose_name_plural': 'Postal Address for User Extention',
            },
            bases=('crm.postaladdress',),
        ),
        migrations.CreateModel(
            name='XSLFile',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(blank=True, max_length=100, null=True, verbose_name='Title')),
                ('xslfile', filebrowser.fields.FileBrowseField(max_length=200, verbose_name='XSL File')),
            ],
            options={
                'verbose_name': 'XSL File',
                'verbose_name_plural': 'XSL Files',
            },
        ),
        migrations.AddField(
            model_name='templateset',
            name='balancesheetXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                    related_name='db_reltemplatebalancesheet', to='djangoUserExtension.XSLFile',
                                    verbose_name='XSL File for Balancesheet'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='deilveryorderXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                    related_name='db_reltemplatedeliveryorder', to='djangoUserExtension.XSLFile',
                                    verbose_name='XSL File for Deilvery Order'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='invoiceXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplateinvoice',
                                    to='djangoUserExtension.XSLFile', verbose_name='XSL File for Invoice'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='profitLossStatementXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                    related_name='db_reltemplateprofitlossstatement', to='djangoUserExtension.XSLFile',
                                    verbose_name='XSL File for Profit Loss Statement'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='purchaseconfirmationXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                    related_name='db_reltemplatepurchaseconfirmation', to='djangoUserExtension.XSLFile',
                                    verbose_name='XSL File for Purchase Confirmation'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='purchaseorderXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                    related_name='db_reltemplatepurchaseorder', to='djangoUserExtension.XSLFile',
                                    verbose_name='XSL File for Purchaseorder'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='quoteXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatequote',
                                    to='djangoUserExtension.XSLFile', verbose_name='XSL File for Quote'),
        ),
    ]
<file_sep>/documentation/source/architecture.rst
.. highlight:: rst

Architecture
========

Standard installation with docker, postgres
-------------------------------------------
.. image:: /images/koalixcrm_architecture_django_webserver.png

Standard installation with docker, postgres and apache2
-------------------------------------------------------
.. image:: /images/koalixcrm_architecture_dedicated_webserver.png


<file_sep>/koalixcrm/djangoUserExtension/migrations/0007_auto_20180702_1628.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-07-02 16:28
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0006_auto_20180701_2128'),
    ]

    operations = [
        migrations.RenameField(
            model_name='userextension',
            old_name='defaultCurrency',
            new_name='default_currency',
        ),
        migrations.RenameField(
            model_name='userextension',
            old_name='defaultTemplateSet',
            new_name='default_template_set',
        ),
    ]
<file_sep>/koalixcrm/djangoUserExtension/user_extension/text_paragraph.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _

from koalixcrm.crm.const.purpose import *


class TextParagraphInDocumentTemplate(models.Model):
    document_template = models.ForeignKey("djangoUserExtension.DocumentTemplate")
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=2, choices=PURPOSESTEXTPARAGRAPHINDOCUMENTS)
    text_paragraph = models.TextField(verbose_name=_("Text"), blank=False, null=False)

    class Meta:
        app_label = "crm"
        verbose_name = _('TextParagraphInDocumentTemplate')
        verbose_name_plural = _('TextParagraphInDocumentTemplates')

    def __str__(self):
        return str(self.id)


class InlineTextParagraph(admin.TabularInline):
    model = TextParagraphInDocumentTemplate
    extra = 1
    classes = ('collapse-open',)
    fieldsets = (
        (_('Basics'), {
            'fields': ('purpose', 'text_paragraph',)
        }),
    )
    allow_add = True<file_sep>/koalixcrm/djangoUserExtension/migrations/0005_auto_20180612_1924.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-12 19:24
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0004_auto_20171210_2126'),
    ]

    operations = [
        migrations.CreateModel(
            name='MonthlyProjectSummaryTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Monthly project summary template',
                'verbose_name_plural': 'Monthly project summary templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.AddField(
            model_name='templateset',
            name='monthly_project_summary_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.MonthlyProjectSummaryTemplate'),
        ),
    ]
<file_sep>/koalixcrm/crm/tests/test_view_work_entry_form.py
# -*- coding: utf-8 -*-
import pytest
import datetime
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from koalixcrm.test_support_functions import *
from koalixcrm.crm.factories.factory_user import AdminUserFactory
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.djangoUserExtension.factories.factory_user_extension import StandardUserExtensionFactory
from koalixcrm.crm.factories.factory_reporting_period import StandardReportingPeriodFactory
from koalixcrm.crm.factories.factory_task import StandardTaskFactory
from koalixcrm.crm.reporting.work import Work
from koalixcrm.crm.factories.factory_human_resource import StandardHumanResourceFactory


class TimeTrackingWorkEntry(LiveServerTestCase):

    def setUp(self):
        firefox_options = webdriver.firefox.options.Options()
        firefox_options.set_headless(headless=True)
        self.selenium = webdriver.Firefox(firefox_options=firefox_options)
        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_human_resource = StandardHumanResourceFactory.create(user=self.test_user_extension)
        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_reporting_period = StandardReportingPeriodFactory.create()
        self.test_1st_task = StandardTaskFactory.create(title="1st Test Task",
                                                        project=self.test_reporting_period.project,
                                                        )

    def tearDown(self):
        self.selenium.quit()

    @pytest.mark.front_end_tests
    def test_registration_of_work(self):
        selenium = self.selenium
        # login
        selenium.get('%s%s' % (self.live_server_url, '/koalixcrm/crm/reporting/time_tracking/'))
        # the browser will be redirected to the login page
        timeout = 5
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'id_username'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        username = selenium.find_element_by_xpath('//*[@id="id_username"]')
        password = selenium.find_element_by_xpath('//*[@id="id_password"]')
        submit_button = selenium.find_element_by_xpath('/html/body/div/article/div/div/form/div/ul/li/input')
        username.send_keys("admin")
        password.send_keys("<PASSWORD>")
        submit_button.send_keys(Keys.RETURN)
        # after the login, the browser is redirected to the target url /koalixcrm/crm/reporting/time_tracking
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'id_form-0-project'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        # find the form element
        assert_when_element_does_not_exist(self, '//*[@id="id_form-0-project"]')
        assert_when_element_does_not_exist(self, '//*[@id="id_form-0-task"]')
        assert_when_element_does_not_exist(self, '//*[@id="id_form-0-date"]')
        assert_when_element_does_not_exist(self, '//*[@id="id_form-0-start_time"]')
        assert_when_element_does_not_exist(self, '//*[@id="id_form-0-stop_time"]')
        assert_when_element_does_not_exist(self, '//*[@id="id_form-0-worked_hours"]')
        assert_when_element_does_not_exist(self, '//*[@id="id_form-0-description"]')
        assert_when_element_does_not_exist(self, 'save')
        project = selenium.find_element_by_xpath('//*[@id="id_form-0-project"]')
        task = selenium.find_element_by_xpath('//*[@id="id_form-0-task"]')
        date = selenium.find_element_by_xpath('//*[@id="id_form-0-date"]')
        start_time = selenium.find_element_by_xpath('//*[@id="id_form-0-start_time"]')
        stop_time = selenium.find_element_by_xpath('//*[@id="id_form-0-stop_time"]')
        description = selenium.find_element_by_xpath('//*[@id="id_form-0-description"]')
        project.send_keys(self.test_reporting_period.project.id.__str__())
        date.send_keys(datetime.date.today().__str__())
        start_time.send_keys(datetime.time(11, 55).__str__())
        stop_time.send_keys(datetime.time(12, 55).__str__())
        description.send_keys("This is a test work entered through the front-end")
        task.send_keys(self.test_1st_task.id.__str__())
        save_button = selenium.find_element_by_name('save')
        save_button.send_keys(Keys.RETURN)
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'id_form-1-project'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        assert_when_element_does_not_exist(self, '//*[@id="id_form-1-task"]')
        work = Work.objects.get(description="This is a test work entered through the front-end")
        self.assertEqual(type(work), Work)
<file_sep>/koalixcrm/crm/migrations/0002_auto_20170927_2042.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-09-27 20:42
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0001_initial'),
    ]

    operations = [
        migrations.AlterField(
            model_name='salescontract',
            name='lastCalculatedPrice',
            field=models.DecimalField(blank=True, decimal_places=2, max_digits=17, null=True, verbose_name='Last Calculated Price With Tax'),
        ),
    ]
<file_sep>/koalixcrm/crm/contact/call.py
# -*- coding: utf-8 -*-

from datetime import *
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from django.utils import timezone
from koalixcrm.crm.const.status import *


class Call(models.Model):
    staff = models.ForeignKey('auth.User',
                              limit_choices_to={'is_staff': True},
                              verbose_name=_("Staff"),
                              related_name="db_relcallstaff",
                              blank=True,
                              null=True)
    description = models.TextField(verbose_name=_("Description"))
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"),
                                            auto_now_add=True)
    date_due = models.DateTimeField(verbose_name=_("Date due"),
                                    default=datetime.now,
                                    blank=True)
    last_modification = models.DateTimeField(verbose_name=_("Last modified"),
                                             auto_now=True)
    last_modified_by = models.ForeignKey('auth.User',
                                         limit_choices_to={'is_staff': True},
                                         blank=True,
                                         null=True,
                                         verbose_name=_("Last modified by"),
                                         related_name="db_calllstmodified")
    status = models.CharField(verbose_name=_("Status"),
                              max_length=1,
                              choices=CALLSTATUS,
                              default="P")
    
    def __str__(self):
        return _("Call") + " " + str(self.id)


class CallOverdueFilter(admin.SimpleListFilter):
    title = _('Is call overdue')
    parameter_name = 'date_due'

    def lookups(self, request, model_admin):
        return (
            ('overdue', _('Overdue')),
            ('planned', _('Planned')),
        )

    def queryset(self, request, queryset):
        if self.value() == 'planned':
            return queryset.filter(date_due__gt=timezone.now())
        elif self.value() == 'overdue':
            return queryset.filter(date_due__lt=timezone.now()).exclude(status__in=['F', 'S'])
        else:
            return queryset


class OptionCall(admin.ModelAdmin):
    list_display = ('id',
                    'description',
                    'date_due',
                    'purpose',
                    'status',
                    'cperson',
                    'is_call_overdue',
                    'get_contact_name')
    fieldsets = (('', {'fields': ('staff',
                                  'description',
                                  'date_due',
                                  'purpose',
                                  'company',
                                  'cperson',
                                  'status')}),)
    list_filter = [CallOverdueFilter]

    @staticmethod
    def get_contact_name(obj):
        return obj.company.name

    get_contact_name.short_description = _("Company")

    @staticmethod
    def is_call_overdue(obj):
        if obj.date_due < timezone.now() and obj.status not in ['F', 'S']:
            overdue = True
        else:
            overdue = False
        return overdue

    is_call_overdue.short_description = _("Is call overdue")


class OptionVisit(admin.ModelAdmin):
    list_display = ('id',
                    'description',
                    'cperson',
                    'date_due',
                    'purpose',
                    'get_contact_name',
                    'status',
                    'ref_call',)
    fieldsets = (('', {'fields': ('staff',
                                  'description',
                                  'date_due',
                                  'purpose',
                                  'company',
                                  'cperson',
                                  'status')}),)

    @staticmethod
    def get_contact_name(obj):
        return obj.company.name

    get_contact_name.short_description = _("Company")
<file_sep>/koalixcrm/crm/rest/product_rest.py
from rest_framework import serializers

from koalixcrm.accounting.accounting.product_category import ProductCategory
from koalixcrm.accounting.rest.product_categorie_rest import ProductCategoryMinimalJSONSerializer
from koalixcrm.crm.product.product_type import ProductType
from koalixcrm.crm.product.tax import Tax
from koalixcrm.crm.product.unit import Unit
from koalixcrm.crm.rest.tax_rest import OptionTaxJSONSerializer
from koalixcrm.crm.rest.unit_rest import OptionUnitJSONSerializer


class ProductJSONSerializer(serializers.HyperlinkedModelSerializer):
    productNumber = serializers.IntegerField(source='product_number', allow_null=False)
    unit = OptionUnitJSONSerializer(source='default_unit', allow_null=False)
    tax = OptionTaxJSONSerializer(allow_null=False)
    productCategory = ProductCategoryMinimalJSONSerializer(source='accounting_product_categorie', allow_null=False)

    class Meta:
        model = ProductType
        fields = ('id',
                  'productNumber',
                  'title',
                  'unit',
                  'tax',
                  'productCategory')
        depth = 1

    def create(self, validated_data):
        product = ProductType()
        product.product_number = validated_data['product_number']
        product.title = validated_data['title']

        # Deserialize default_unit
        default_unit = validated_data.pop('default_unit')
        if default_unit:
            if default_unit.get('id', None):
                product.default_unit = Unit.objects.get(id=default_unit.get('id', None))
            else:
                product.default_unit = None

        # Deserialize tax
        tax = validated_data.pop('tax')
        if tax:
            if tax.get('id', None):
                product.tax = Tax.objects.get(id=tax.get('id', None))
            else:
                product.tax = None

        # Deserialize product category
        product_category = validated_data.pop('accounting_product_categorie')
        if product_category:
            if product_category.get('id', None):
                product.accounting_product_category = ProductCategory.objects.get(id=product_category.get('id', None))
            else:
                product.accounting_product_category = None

        product.save()
        return product

    def update(self, instance, validated_data):
        instance.title = validated_data['title']
        instance.product_number = validated_data['product_number']

        # Deserialize default_unit
        default_unit = validated_data.pop('default_unit')
        if default_unit:
            if default_unit.get('id', instance.default_unit):
                instance.default_unit = Unit.objects.get(id=default_unit.get('id', None))
            else:
                instance.default_unit = instance.default_unit
        else:
            instance.default_unit = None

        # Deserialize tax
        tax = validated_data.pop('tax')
        if tax:
            if tax.get('id', instance.default_unit):
                instance.tax = Tax.objects.get(id=tax.get('id', None))
            else:
                instance.tax = instance.tax
        else:
            instance.tax = None

        # Deserialize product category
        product_category = validated_data.pop('accounting_product_categorie')
        if product_category:
            if product_category.get('id', instance.accounting_product_categorie):
                instance.accounting_product_categorie = ProductCategory.objects.get(
                    id=product_category.get('id', None))
            else:
                instance.accounting_product_categorie = instance.accounting_product_categorie
        else:
            instance.accounting_product_categorie = None

        instance.save()
        return instance
<file_sep>/koalixcrm/crm/reporting/resource_price.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.crm.product.price import Price
from koalixcrm.crm.reporting.resource import Resource


class ResourcePrice(Price):
    resource = models.ForeignKey(Resource,
                                 verbose_name=_('Resource'),
                                 blank=False,
                                 null=False)

    def __str__(self):
        return str(self.price) + " " + str(self.currency.short_name)


class ResourcePriceInlineAdminView(admin.TabularInline):
    model = ResourcePrice
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basic', {
            'fields': ('price',
                       'currency',
                       'unit',
                       'valid_from',
                       'valid_until',
                       'customer_group')
        }),
    )
    allow_add = True
<file_sep>/koalixcrm/crm/reporting/work.py
# -*- coding: utf-8 -*-

from django.db import models
from django.forms import ValidationError
from django.contrib import admin
from django.utils.html import format_html
from django.utils.translation import ugettext as _
from koalixcrm.crm.documents.pdf_export import PDFExport
from koalixcrm.global_support_functions import *
from koalixcrm.crm.exceptions import ReportingPeriodDoneDeleteNotPossible
from django.contrib import messages


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)
    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)

    def link_to_work(self):
        if self.id:
            return format_html("<a href='/admin/crm/work/%s' >%s</a>" % (str(self.id), str(self.id)))
        else:
            return "Not present"
    link_to_work.short_description = _("Work")

    def get_short_description(self):
        if self.short_description:
            return self.short_description
        elif self.description:
            return limit_string_length(self.description, 100)
        else:
            return _("Please add description")
    get_short_description.short_description = _("Short description");

    def serialize_to_xml(self):
        objects = [self, ]
        main_xml = PDFExport.write_xml(objects)
        return main_xml

    def effort_hours(self):
        if self.effort_seconds() != 0:
            return self.effort_seconds()/3600
        else:
            return 0

    def effort_seconds(self):
        if not self.start_stop_pattern_complete() and not bool(self.worked_hours):
            return 0
        elif (not bool(self.stop_time)) or (not bool(self.start_time)):
            return float(self.worked_hours)*3600
        else:
            return (self.stop_time - self.start_time).total_seconds()

    def effort_as_string(self):
        return str(self.effort_hours()) + " h"

    def start_stop_pattern_complete(self):
        return bool(self.start_time) & bool(self.stop_time)

    def start_stop_pattern_stop_missing(self):
        return bool(self.start_time) & (not bool(self.stop_time))

    def start_stop_pattern_start_missing(self):
        return bool(self.stop_time) & (not bool(self.start_time))

    def __str__(self):
        return _("Work") + ": " + str(self.id) + " " + _("from Person") + ": " + str(self.human_resource.id)

    def check_working_hours(self):
        """This method checks that the working hour is correctly provided either using the start_stop pattern
        or by providing the worked_hours in total.

        Args:

        Returns:
          True when no ValidationError was raised

        Raises:
          may raise ValidationError exception"""
        if self.start_stop_pattern_complete() & bool(self.worked_hours):
            raise ValidationError('Please either set the start, stop time or worked hours (not both)', code='invalid')
        elif self.start_stop_pattern_start_missing() or self.start_stop_pattern_stop_missing():
            raise ValidationError('Set start and stop time', code='invalid')
        return True

    def clean(self):
        cleaned_data = super(Work, self).clean()
        self.check_working_hours()
        return cleaned_data

    def delete(self, using=None, keep_parents=False):
        if self.reporting_period.status.is_done:
            raise ReportingPeriodDoneDeleteNotPossible("It was not possible to delete the work")
        else:
            super(Work, self).delete(using, keep_parents)

    def save(self, *args, **kwargs):
        if self.reporting_period.status.is_done:
            raise ReportingPeriodDoneDeleteNotPossible("It was not possible to update the work")
        else:
            super(Work, self).save(*args, **kwargs)

    class Meta:
        app_label = "crm"
        verbose_name = _('Work')
        verbose_name_plural = _('Work')


class WorkAdminView(admin.ModelAdmin):
    list_display = ('link_to_work',
                    'human_resource',
                    'task',
                    'get_short_description',
                    'date',
                    'reporting_period',
                    'effort_as_string')

    list_filter = ('task', 'date')
    ordering = ('-id',)

    fieldsets = (
        (_('Work'), {
            'fields': ('human_resource',
                       'date',
                       'start_time',
                       'stop_time',
                       'worked_hours',
                       'short_description',
                       'description',
                       'task',
                       'reporting_period',)
        }),
    )
    save_as = True

    actions = ['delete_selected', ]

    def delete_selected(self, request, queryset):
        for obj in queryset:
            if obj.reporting_period.status.is_done:
                self.message_user(request, _("Delete is not allowed because the work"
                                             " is used in a reporting period which is marked "
                                             "'as done'"),
                                  level=messages.ERROR)
            else:
                obj.delete()

    delete_selected.short_description = _("Delete Selected Work")


class WorkInlineAdminView(admin.TabularInline):
    model = Work
    readonly_fields = ('link_to_work',
                       'get_short_description',
                       'human_resource',
                       'date',
                       'effort_as_string',)
    fieldsets = (
        (_('Work'), {
            'fields': ('link_to_work',
                       'get_short_description',
                       'human_resource',
                       'date',
                       'effort_as_string',)
        }),
    )
    extra = 0

    def has_add_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False
<file_sep>/koalixcrm/crm/const/status.py
# -*- coding: utf-8 -*

from django.utils.translation import ugettext as _

INVOICESTATUS = (
    ('P', _('Payed')),
    ('C', _('Invoice created')),
    ('I', _('Invoice sent')),
    ('F', _('First reminder sent')),
    ('R', _('Second reminder sent')),
    ('U', _('Customer cant pay')),
    ('D', _('Deleted')),
)

QUOTESTATUS = (
    ('S', _('Success')),
    ('I', _('Quote created')),
    ('Q', _('Quote sent')),
    ('F', _('First reminder sent')),
    ('R', _('Second reminder sent')),
    ('D', _('Deleted')),
)

PURCHASEORDERSTATUS = (
    ('O', _('Ordered')),
    ('D', _('Delayed')),
    ('Y', _('Delivered')),
    ('I', _('Invoice registered')),
    ('P', _('Invoice payed')),
)

DELIVERYNOTESTATUS = (
    ('C', _('Created')),
    ('S', _('Sent')),
    ('R', _('Received')),
    ('R', _('Lost')),
)

CALLSTATUS = (
    ('P', _('Planned')),
    ('D', _('Delayed')),
    ('R', _('ToRecall')),
    ('F', _('Failed')),
    ('S', _('Success')),
)
<file_sep>/koalixcrm/crm/factories/factory_agreement_status.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import AgreementStatus


class AgreedAgreementStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = AgreementStatus
        django_get_or_create = ('title',)

    title = "Done"
    description = "This agreement is agreed"
    is_agreed = True


class PlannedAgreementStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = AgreementStatus
        django_get_or_create = ('title',)

    title = "Planned"
    description = "This agreement is planned"
    is_agreed = False


class StartedAgreementStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = AgreementStatus
        django_get_or_create = ('title',)

    title = "Started"
    description = "This agreement is started"
    is_agreed = False
<file_sep>/koalixcrm/crm/migrations/0014_auto_20180108_2048.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-08 20:48
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0013_auto_20180105_2159'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='invoice',
            name='derived_from_quote',
        ),
        migrations.AddField(
            model_name='salescontract',
            name='derived_from_sales_contract',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.SalesContract'),
        ),
    ]
<file_sep>/koalixcrm/crm/tests/test_version_increase.py
from django.test import TestCase
from koalixcrm.global_support_functions import get_string_between
from subprocess import Popen, PIPE
from koalixcrm.version import KOALIXCRM_VERSION
import pytest


class VersionIncreaseTest(TestCase):
    @staticmethod
    def get_all_koalixcrm_version_from_pip():
        process_out = Popen(['pip install koalix-crm=='], shell=True, stderr=PIPE)
        output, string_containing_all_version = process_out.communicate()
        all_koalixcrm_version_csv = get_string_between(string_containing_all_version.__str__(), "koalix-crm== (", ")")
        all_koalixcrm_version = all_koalixcrm_version_csv.split(", ")
        return all_koalixcrm_version

    def setUp(self):
        self.available_versions = VersionIncreaseTest.get_all_koalixcrm_version_from_pip()

    @pytest.mark.version_increase
    def test_version_increase(self):
        last_version = ""
        for version in self.available_versions:
            last_version = version
            if version == KOALIXCRM_VERSION:
                break
        self.assertNotEqual(last_version, KOALIXCRM_VERSION)

<file_sep>/koalixcrm/crm/documents/sales_document_position.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.core.validators import MinValueValidator
from django.utils.translation import ugettext as _


class Position(models.Model):
    position_number = models.PositiveIntegerField(verbose_name=_("Position Number"),
                                                  validators=[MinValueValidator(1)])
    quantity = models.DecimalField(verbose_name=_("Quantity"),
                                   decimal_places=3,
                                   max_digits=10)
    description = models.TextField(verbose_name=_("Description"),
                                   blank=True, null=True)
    discount = models.DecimalField(max_digits=5, decimal_places=2,
                                   verbose_name=_("Discount"),
                                   blank=True,
                                   null=True)
    product_type = models.ForeignKey("ProductType",
                                     verbose_name=_("Product"),
                                     blank=False,
                                     null=True)
    unit = models.ForeignKey("Unit", verbose_name=_("Unit"),
                             blank=True,
                             null=True)
    sent_on = models.DateField(verbose_name=_("Shipment on"),
                               blank=True,
                               null=True)
    overwrite_product_price = models.BooleanField(verbose_name=_('Overwrite Product Price'))
    position_price_per_unit = models.DecimalField(verbose_name=_("Price Per Unit"),
                                                  max_digits=17,
                                                  decimal_places=2,
                                                  blank=True, null=True)
    last_pricing_date = models.DateField(verbose_name=_("Last Pricing Date"),
                                         blank=True,
                                         null=True)
    last_calculated_price = models.DecimalField(max_digits=17,
                                                decimal_places=2,
                                                verbose_name=_("Last Calculated Price"),
                                                blank=True, null=True)
    last_calculated_tax = models.DecimalField(max_digits=17,
                                              decimal_places=2,
                                              verbose_name=_("Last Calculated Tax"),
                                              blank=True,
                                              null=True)

    def __str__(self):
        return _("Position") + ": " + self.id.__str__()

    class Meta:
        app_label = "crm"
        ordering = ["position_number"]
        verbose_name = _('Position')
        verbose_name_plural = _('Positions')


class SalesDocumentPosition(Position):
    sales_document = models.ForeignKey("SalesDocument", verbose_name=_("Contract"))

    class Meta:
        app_label = "crm"
        verbose_name = _('Position in Sales Document')
        verbose_name_plural = _('Positions Sales Document')

    @staticmethod
    def add_positions(position_class, object_to_create_pdf):
        from koalixcrm.crm.product.unit import Unit
        from koalixcrm.crm.product.product_type import ProductType
        objects = list(position_class.objects.filter(sales_document=object_to_create_pdf.id))
        for position in list(position_class.objects.filter(sales_document=object_to_create_pdf.id)):
            objects += list(Position.objects.filter(id=position.id))
            objects += list(ProductType.objects.filter(id=position.product_type.id))
            objects += list(Unit.objects.filter(id=position.unit.id))
        return objects

    def create_position(self, calling_model, attach_to_model):
        """Copies all the content of the calling model and attaches
        links itself to the attach_to_model, this function is usually
        used within the create invoice, quote, reminder,... functions"""

        self.product_type = calling_model.product_type
        self.position_number = calling_model.position_number
        self.quantity = calling_model.quantity
        self.description = calling_model.description
        self.discount = calling_model.discount
        self.unit = calling_model.unit
        self.sent_on = calling_model.sent_on
        self.overwrite_product_price = calling_model.overwrite_product_price
        self.position_price_per_unit = calling_model.position_price_per_unit
        self.last_pricing_date = calling_model.last_pricing_date
        self.last_calculated_price = calling_model.last_calculated_price
        self.last_calculated_tax = calling_model.last_calculated_tax
        self.sales_document = attach_to_model
        self.save()

    def __str__(self):
        return _("Sales Document Position") + ": " + str(self.id)


class SalesDocumentInlinePosition(admin.TabularInline):
    model = SalesDocumentPosition
    extra = 1
    classes = ['expand']
    fieldsets = (
        ('', {
            'fields': (
                'position_number',
                'quantity',
                'unit',
                'product_type',
                'description',
                'discount',
                'overwrite_product_price',
                'position_price_per_unit',
                'sent_on')
        }),
    )
    allow_add = True

<file_sep>/koalixcrm/accounting/rest/product_categorie_rest.py
# -*- coding: utf-8 -*-
from koalixcrm.accounting.rest.account_rest import OptionAccountJSONSerializer
from rest_framework import serializers

from koalixcrm.accounting.accounting.product_category import ProductCategory
from koalixcrm.accounting.models import Account


class ProductCategoryMinimalJSONSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.IntegerField(required=False)
    title = serializers.CharField(read_only=True)

    class Meta:
        model = Account
        fields = ('id',
                  'title')


class ProductCategoryJSONSerializer(serializers.HyperlinkedModelSerializer):
    profitAccount = OptionAccountJSONSerializer(source='profit_account')
    lossAccount = OptionAccountJSONSerializer(source='loss_account')

    class Meta:
        model = ProductCategory
        fields = ('id',
                  'title',
                  'profitAccount',
                  'lossAccount')
        depth = 1

    def create(self, validated_data):
        product_category = ProductCategory()
        product_category.title = validated_data['title']

        # Deserialize profit account
        profit_account = validated_data.pop('profit_account')
        if profit_account:
            if profit_account.get('id', None):
                product_category.profit_account = Account.objects.get(id=profit_account.get('id', None))
            else:
                product_category.profit_account = None

        # Deserialize loss account
        loss_account = validated_data.pop('loss_account')
        if loss_account:
            if loss_account.get('id', None):
                product_category.loss_account = Account.objects.get(id=loss_account.get('id', None))
            else:
                product_category.loss_account = None

        product_category.save()
        return product_category

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title', instance.title)

        # Deserialize profit account
        profit_account = validated_data.pop('profit_account')
        if profit_account:
            if profit_account.get('id', instance.profit_account_id):
                instance.profit_account = Account.objects.get(id=profit_account.get('id', None))
            else:
                instance.profit_account = instance.profit_account_id
        else:
            instance.profit_account = None

        # Deserialize loss account
        loss_account = validated_data.pop('loss_account')
        if loss_account:
            if loss_account.get('id', instance.loss_account_id):
                instance.loss_account = Account.objects.get(id=loss_account.get('id', None))
            else:
                instance.loss_account = instance.loss_account_id
        else:
            instance.loss_account = None

        instance.save()
        return instance
<file_sep>/koalixcrm/crm/contact/person.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _
from koalixcrm.crm.const.postaladdressprefix import *


class Person(models.Model):
    prefix = models.CharField(max_length=1,
                              choices=POSTALADDRESSPREFIX,
                              verbose_name=_("Prefix"),
                              blank=True,
                              null=True)
    name = models.CharField(max_length=100,
                            verbose_name=_("Name"),
                            blank=True,
                            null=True)
    pre_name = models.CharField(max_length=100,
                                verbose_name=_("Pre-name"),
                                blank=True,
                                null=True)
    email = models.EmailField(max_length=200,
                              verbose_name=_("Email Address"))
    phone = models.CharField(max_length=20,
                             verbose_name=_("Phone Number"))
    role = models.CharField(max_length=100,
                            verbose_name=_("Role"),
                            blank=True,
                            null=True)
    companies = models.ManyToManyField("Contact",
                                       through='ContactPersonAssociation',
                                       verbose_name=_('Works at'), blank=True)

    def get_pre_name(self):
        if self.pre_name:
            return self.pre_name
        else:
            return "n/a"

    def get_name(self):
        if self.name:
            return self.name
        else:
            return "n/a"

    def get_email(self):
        if self.email:
            return self.email
        else:
            return "n/a"

    def __str__(self):
        return self.get_pre_name() + ' ' + self.get_name() + ' - ' + self.get_email()
    
    class Meta:
        app_label = "crm"
        verbose_name = _('Person')
        verbose_name_plural = _('People')
<file_sep>/koalixcrm/crm/rest/restinterface.py
# -*- coding: utf-8 -*-

from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from rest_framework import viewsets
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
from rest_framework_xml.renderers import XMLRenderer

from koalixcrm.crm.documents.contract import Contract, ContractJSONSerializer
from koalixcrm.crm.product.currency import Currency
from koalixcrm.crm.product.product_type import ProductType
from koalixcrm.crm.product.tax import Tax
from koalixcrm.crm.product.unit import Unit
from koalixcrm.crm.reporting.project import Project, ProjectJSONSerializer
from koalixcrm.crm.reporting.task import Task, TaskSerializer
from koalixcrm.crm.reporting.task_status import TaskStatus
from koalixcrm.crm.rest.currency_rest import CurrencyJSONSerializer
from koalixcrm.crm.rest.product_rest import ProductJSONSerializer
from koalixcrm.crm.rest.tax_rest import TaxJSONSerializer
from koalixcrm.crm.rest.unit_rest import UnitJSONSerializer
from koalixcrm.crm.views.renderer import XSLFORenderer
from koalixcrm.global_support_functions import ConditionalMethodDecorator


class TaskAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed.
    """
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer)
    filter_fields = ('project',)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(TaskAsJSON, self).dispatch(*args, **kwargs)


class ContractAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed.
    """
    queryset = Contract.objects.all()
    serializer_class = ContractJSONSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(ContractAsJSON, self).dispatch(*args, **kwargs)


class TaskStatusAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed.
    """
    queryset = TaskStatus.objects.all()
    serializer_class = TaskSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(TaskStatusAsJSON, self).dispatch(*args, **kwargs)


class CurrencyAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows currencies to be viewed.
    """
    queryset = Currency.objects.all()
    serializer_class = CurrencyJSONSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(CurrencyAsJSON, self).dispatch(*args, **kwargs)


class TaxAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows taxes to be viewed.
    """
    queryset = Tax.objects.all()
    serializer_class = TaxJSONSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(TaxAsJSON, self).dispatch(*args, **kwargs)


class UnitAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows units to be viewed.
    """
    queryset = Unit.objects.all()
    serializer_class = UnitJSONSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(UnitAsJSON, self).dispatch(*args, **kwargs)


class ProductAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows products to be viewed.
    """
    queryset = ProductType.objects.all()
    serializer_class = ProductJSONSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(ProductAsJSON, self).dispatch(*args, **kwargs)


class ProjectAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed.
    """
    queryset = Project.objects.all()
    serializer_class = ProjectJSONSerializer
    renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XMLRenderer, XSLFORenderer)
    file_name = "this_is_the_ProjectList.xml"

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(ProjectAsJSON, self).dispatch(*args, **kwargs)
<file_sep>/koalixcrm/crm/factories/factory_currency_transform.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import CurrencyTransform
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory, SecondStandardCurrencyFactory
from koalixcrm.crm.factories.factory_product_type import StandardProductTypeFactory


class StandardCurrencyTransformFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = CurrencyTransform
        django_get_or_create = ('from_currency',
                                'to_currency')

    from_currency = factory.SubFactory(StandardCurrencyFactory)
    to_currency = factory.SubFactory(SecondStandardCurrencyFactory)
    product_type = factory.SubFactory(StandardProductTypeFactory)
    factor = 0.90
<file_sep>/koalixcrm/crm/factories/factory_reporting_period.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import ReportingPeriod
from koalixcrm.crm.factories.factory_project import StandardProjectFactory
from koalixcrm.crm.factories.factory_reporting_period_status import ReportingReportingPeriodStatusFactory


class StandardReportingPeriodFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ReportingPeriod
        django_get_or_create = ('title',)

    project = factory.SubFactory(StandardProjectFactory)
    title = "This is a test project"
    begin = '2018-06-15'
    end = '2044-06-15'
    status = factory.SubFactory(ReportingReportingPeriodStatusFactory)
<file_sep>/koalixcrm/crm/factories/factory_payment_reminder.py
# -*- coding: utf-8 -*-

from koalixcrm.crm.models import PaymentReminder
from koalixcrm.crm.factories.factory_sales_document import StandardSalesDocumentFactory


class StandardPaymentReminderFactory(StandardSalesDocumentFactory):
    class Meta:
        model = PaymentReminder

    payable_until = "2018-05-20"
    payment_bank_reference = "This is a bank account reference"
    iteration_number = "1"
    status = "C"
<file_sep>/koalixcrm/crm/migrations/0053_auto_20181014_2305.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-12 20:56
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


def initiate_human_resource(apps, schema_editor):
    Work = apps.get_model("crm", "Work")
    HumanResource = apps.get_model("crm", "HumanResource")
    db_alias = schema_editor.connection.alias
    all_works = Work.objects.using(db_alias).all()
    for work in all_works:
        all_human_resources = HumanResource.objects.using(db_alias).all()
        human_resource_found = False
        for human_resource in all_human_resources:
            if work.employee == human_resource.user:
                human_resource_found = True
                work.human_resource = human_resource
                break
        if not human_resource_found:
            new_human_resource = HumanResource.objects.using(db_alias).create(user=work.employee)
            new_human_resource.save()
            work.human_resource = new_human_resource
        work.save()


def reverse_func(apps, schema_editor):
    return 1


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0052_auto_20181014_2304'),
    ]

    operations = [
        migrations.AlterField(
            model_name='customergrouptransform',
            name='product_type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.ProductType', verbose_name='Product Type'),
            preserve_default=False,
        ),
        migrations.AlterField(
            model_name='unittransform',
            name='product_type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.ProductType', verbose_name='Product Type'),
            preserve_default=False,
        ),
        migrations.RemoveField(
            model_name='customergrouptransform',
            name='product_backup',
        ),
        migrations.RemoveField(
            model_name='position',
            name='product_backup',
        ),
        migrations.RemoveField(
            model_name='price',
            name='product_backup',
        ),
        migrations.RemoveField(
            model_name='unittransform',
            name='product_backup',
        ),

        migrations.AddField(
            model_name='producttype',
            name='accounting_product_category',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='accounting.ProductCategory', verbose_name='Accounting Product Category'),
            preserve_default=False,
        ),
        migrations.RemoveField(model_name='producttype',
                               name='accounting_product_categorie'),
        migrations.AlterModelOptions(
            name='producttype',
            options={'verbose_name': 'Product Type', 'verbose_name_plural': 'Product Types'},
        ),
        migrations.RemoveField(
            model_name='producttype',
            name='product_number',
        ),
        migrations.AddField(
            model_name='producttype',
            name='product_type_identifier',
            field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Product Number'),
        ),
        migrations.AlterField(
            model_name='position',
            name='product_type',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.ProductType', verbose_name='Product'),
        ),
        migrations.AlterField(
            model_name='productprice',
            name='product_type',
            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='crm.ProductType', verbose_name='Product Type'),
            preserve_default=False,
        ),
        migrations.AlterField(
            model_name='producttype',
            name='last_modified_by',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Last modified by'),
        ),
        migrations.CreateModel(
            name='Product',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('identifier', models.CharField(verbose_name="Product Number",
                                                max_length=200,
                                                null=True,
                                                blank=True)),
                ('product_type', models.ForeignKey("ProductType", verbose_name='Product Type')),
            ],
            options={
                'verbose_name': 'Product',
                'verbose_name_plural': 'Products',
            },
        ),
        migrations.CreateModel(
            name='Agreement',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('date_from', models.DateField(verbose_name='Agreement From')),
                ('date_until', models.DateField(verbose_name='Agreement To')),
                ('amount', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='Amount')),
            ],
            options={
                'verbose_name': 'Agreement',
                'verbose_name_plural': 'Agreements',
            },
        ),
        migrations.CreateModel(
            name='AgreementStatus',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=250, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
                ('is_agreed', models.BooleanField(verbose_name='Status represents agreement exists')),
            ],
            options={
                'verbose_name': 'Agreement Status',
                'verbose_name_plural': 'Agreement Status',
            },
        ),
        migrations.CreateModel(
            name='AgreementType',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=300, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
            ],
            options={
                'verbose_name': 'Agreement Type',
                'verbose_name_plural': 'Agreement Type',
            },
        ),
        migrations.CreateModel(
            name='CurrencyTransform',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('factor', models.IntegerField(blank=True, null=True, verbose_name='Factor between From and To Currency')),
                ('from_currency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltransformfromcurrency', to='crm.Currency', verbose_name='From Currency')),
            ],
            options={
                'verbose_name': 'Currency Transform',
                'verbose_name_plural': 'Currency Transforms',
            },
        ),
        migrations.CreateModel(
            name='Estimation',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('date_from', models.DateField(verbose_name='Estimation From')),
                ('date_until', models.DateField(verbose_name='Estimation To')),
                ('amount', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='Amount')),
                ('reporting_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.ReportingPeriod', verbose_name='Reporting Period based on which the estimation was done')),
            ],
            options={
                'verbose_name': 'Estimation of Resource Consumption',
                'verbose_name_plural': 'Estimation of Resource Consumptions',
            },
        ),
        migrations.CreateModel(
            name='EstimationStatus',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=250, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
                ('is_obsolete', models.BooleanField(verbose_name='Status represents estimation is obsolete')),
            ],
            options={
                'verbose_name': 'Estimation Status',
                'verbose_name_plural': 'Estimation Status',
            },
        ),
        migrations.CreateModel(
            name='Resource',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
            ],
        ),
        migrations.CreateModel(
            name='ResourceManager',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.UserExtension', verbose_name='User')),
            ],
        ),
        migrations.CreateModel(
            name='ResourcePrice',
            fields=[
                ('price_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.Price')),
            ],
            bases=('crm.price',),
        ),
        migrations.CreateModel(
            name='ResourceType',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=300, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
            ],
            options={
                'verbose_name': 'Resource Link Type',
                'verbose_name_plural': 'Resource Link Type',
            },
        ),
        migrations.RemoveField(
            model_name='employeeassignmenttotask',
            name='employee',
        ),
        migrations.RemoveField(
            model_name='employeeassignmenttotask',
            name='task',
        ),
        migrations.AlterModelOptions(
            name='customergrouptransform',
            options={'verbose_name': 'Customer Group Price Transform', 'verbose_name_plural': 'Customer Group Price Transforms'},
        ),
        migrations.AlterModelOptions(
            name='product',
            options={},
        ),
        migrations.AlterModelOptions(
            name='unittransform',
            options={'verbose_name': 'Unit Transform', 'verbose_name_plural': 'Unit Transforms'},
        ),
        migrations.RenameField(
            model_name='genericprojectlink',
            old_name='task',
            new_name='project',
        ),
        migrations.RemoveField(
            model_name='task',
            name='planned_end_date',
        ),
        migrations.RemoveField(
            model_name='task',
            name='planned_start_date',
        ),
        migrations.AddField(
            model_name='project',
            name='default_currency',
            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='crm.Currency', verbose_name='Default Currency'),
            preserve_default=False,
        ),
        migrations.AlterField(
            model_name='customergrouptransform',
            name='from_customer_group',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltransfromfromcustomergroup', to='crm.CustomerGroup', verbose_name='From Customer Group'),
        ),
        migrations.AlterField(
            model_name='customergrouptransform',
            name='to_customer_group',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltransfromtocustomergroup', to='crm.CustomerGroup', verbose_name='To Customer Group'),
        ),
        migrations.AlterField(
            model_name='salesdocument',
            name='custom_date_field',
            field=models.DateField(blank=True, null=True, verbose_name='Custom Date'),
        ),
        migrations.CreateModel(
            name='HumanResource',
            fields=[
                ('resource_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.Resource')),
                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.UserExtension', verbose_name='User')),
            ],
            bases=('crm.resource',),
        ),
        migrations.DeleteModel(
            name='EmployeeAssignmentToTask',
        ),
        migrations.AddField(
            model_name='resourceprice',
            name='resource',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Resource', verbose_name='Resource'),
        ),
        migrations.AddField(
            model_name='resource',
            name='resource_manager',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.ResourceManager', verbose_name='Manager'),
        ),
        migrations.AddField(
            model_name='resource',
            name='resource_type',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.ResourceType', verbose_name='Resource Type'),
        ),
        migrations.AddField(
            model_name='estimation',
            name='resource',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Resource'),
        ),
        migrations.AddField(
            model_name='estimation',
            name='status',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.EstimationStatus', verbose_name='Status of the estimation'),
        ),
        migrations.AddField(
            model_name='estimation',
            name='task',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Task', verbose_name='Task'),
        ),
        migrations.AddField(
            model_name='currencytransform',
            name='to_currency',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltransformtocurrency', to='crm.Currency', verbose_name='To Currency'),
        ),
        migrations.AddField(
            model_name='agreement',
            name='costs',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.ResourcePrice'),
        ),
        migrations.AddField(
            model_name='agreement',
            name='resource',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Resource'),
        ),
        migrations.AddField(
            model_name='agreement',
            name='status',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.AgreementStatus'),
        ),
        migrations.AddField(
            model_name='agreement',
            name='task',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Task', verbose_name='Task'),
        ),
        migrations.AddField(
            model_name='agreement',
            name='type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.AgreementType'),
        ),
        migrations.AddField(
            model_name='agreement',
            name='unit',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Unit'),
        ),
        migrations.AddField(
            model_name='work',
            name='human_resource',
            field=models.ForeignKey(null=True, to='crm.HumanResource', on_delete=django.db.models.deletion.SET_NULL),
            preserve_default=False,
        ),
        migrations.AddField(
            model_name='currencytransform',
            name='product_type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.ProductType', verbose_name='Product'),
        ),
        migrations.RunPython(initiate_human_resource, reverse_func),
        migrations.RemoveField(
            model_name='work',
            name='employee',
        ),
        migrations.AlterField(
            model_name='product',
            name='product_type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.ProductType', verbose_name='Product Type'),
            preserve_default=False,
        ),
        migrations.AlterField(
            model_name='work',
            name='human_resource',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.HumanResource'),
        ),
    ]
<file_sep>/koalixcrm/accounting/rest/restinterface.py
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from rest_framework import viewsets

from koalixcrm.accounting.models import Account, AccountingPeriod, Booking, ProductCategory
from koalixcrm.accounting.rest.account_rest import AccountJSONSerializer
from koalixcrm.accounting.rest.accounting_period_rest import AccountingPeriodJSONSerializer
from koalixcrm.accounting.rest.booking_rest import BookingJSONSerializer
from koalixcrm.accounting.rest.product_categorie_rest import ProductCategoryJSONSerializer
from koalixcrm.global_support_functions import ConditionalMethodDecorator


class AccountAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows accounts to be created, viewed and modified.
    """
    queryset = Account.objects.all()
    serializer_class = AccountJSONSerializer
    filter_fields = ('account_type',)

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(AccountAsJSON, self).dispatch(*args, **kwargs)


class AccountingPeriodAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows accounting periods to be created, viewed and modified.
    """
    queryset = AccountingPeriod.objects.all()
    serializer_class = AccountingPeriodJSONSerializer

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(AccountingPeriodAsJSON, self).dispatch(*args, **kwargs)


class BookingAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows bookings to be created, viewed and modified.
    """
    queryset = Booking.objects.all()
    serializer_class = BookingJSONSerializer

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(BookingAsJSON, self).dispatch(*args, **kwargs)


class ProductCategoryAsJSON(viewsets.ModelViewSet):
    """
    API endpoint that allows product categories to be created, viewed and modified.
    """
    queryset = ProductCategory.objects.all()
    serializer_class = ProductCategoryJSONSerializer

    @ConditionalMethodDecorator(method_decorator(login_required), settings.KOALIXCRM_REST_API_AUTH)
    def dispatch(self, *args, **kwargs):
        return super(ProductCategoryAsJSON, self).dispatch(*args, **kwargs)
<file_sep>/koalixcrm/crm/rest/unit_rest.py
from rest_framework import serializers

from koalixcrm.crm.product.unit import Unit


class OptionUnitJSONSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.IntegerField(required=False)
    description = serializers.CharField(read_only=True)
    shortName = serializers.CharField(source='short_name', read_only=True)

    class Meta:
        model = Unit
        fields = ('id',
                  'description',
                  'shortName')


class UnitJSONSerializer(serializers.HyperlinkedModelSerializer):
    shortName = serializers.CharField(source='short_name')
    description = serializers.CharField()
    isFractionOf = OptionUnitJSONSerializer(source='is_a_fraction_of',
                                            allow_null=True)
    fractionFactor = serializers.DecimalField(source='fraction_factor_to_next_higher_unit',
                                              max_digits=20,
                                              decimal_places=10,
                                              required=False,
                                              allow_null=True)

    class Meta:
        model = Unit
        fields = ('id',
                  'description',
                  'shortName',
                  'isFractionOf',
                  'fractionFactor')
        depth = 1

    def create(self, validated_data):
        unit = Unit()
        unit.description = validated_data['description']
        unit.short_name = validated_data['short_name']
        if 'fraction_factor_to_next_higher_unit' in validated_data:
            unit.fraction_factor_to_next_higher_unit = validated_data['fraction_factor_to_next_higher_unit']

        parent_unit = validated_data.pop('is_a_fraction_of')
        if parent_unit:
            if parent_unit.get('id', None):
                unit.is_a_fraction_of = Unit.objects.get(id=parent_unit.get('id', None))
            else:
                unit.is_a_fraction_of = None
        unit.save()
        return unit

    def update(self, instance, validated_data):
        instance.description = validated_data.get('description', instance.description)
        instance.short_name = validated_data.get('short_name', instance.short_name)

        parent_unit = validated_data.pop('is_a_fraction_of')
        if parent_unit:
            if parent_unit.get('id', instance.is_a_fraction_of_id):
                instance.is_a_fraction_of = Unit.objects.get(id=parent_unit.get('id', None))
            else:
                instance.is_a_fraction_of = instance.is_a_fraction_of_id
        else:
            instance.is_a_fraction_of = None
        instance.fraction_factor_to_next_higher_unit = validated_data.get('fraction_factor_to_next_higher_unit',
                                                                          instance.fraction_factor_to_next_higher_unit)
        instance.save()

        return instance
<file_sep>/koalixcrm/djangoUserExtension/migrations/0002_auto_20171009_1949.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-09 19:49
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import filebrowser.fields


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0001_initial'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='templateset',
            options={'verbose_name': 'Vorlagenset', 'verbose_name_plural': 'Vorlagensets'},
        ),
        migrations.AlterModelOptions(
            name='userextension',
            options={'verbose_name': 'Benutzer Erweiterung', 'verbose_name_plural': 'Benutzer Erweiterungen'},
        ),
        migrations.AlterModelOptions(
            name='xslfile',
            options={'verbose_name': 'XSL Datei', 'verbose_name_plural': 'XSL Dateien'},
        ),
        migrations.AlterField(
            model_name='templateset',
            name='addresser',
            field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Absender'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='balancesheetXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatebalancesheet', to='djangoUserExtension.XSLFile', verbose_name='XSL Datei für die Billanz'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='bankingaccountref',
            field=models.CharField(blank=True, max_length=60, null=True, verbose_name='Bankverbindung'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='deilveryorderXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatedeliveryorder', to='djangoUserExtension.XSLFile', verbose_name='XSL Datei für den Lieferschein'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='footerTextpurchaseorders',
            field=models.TextField(blank=True, null=True, verbose_name='Grussfromel für die Kaufaufträge'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='footerTextsalesorders',
            field=models.TextField(blank=True, null=True, verbose_name='Grussfromel für die Verkaufsaufträge'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='fopConfigurationFile',
            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name='FOP Configurations Datei'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='headerTextpurchaseorders',
            field=models.TextField(blank=True, null=True, verbose_name='Begrüssungsformel für die Kaufaufträge'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='headerTextsalesorders',
            field=models.TextField(blank=True, null=True, verbose_name='Begrüssungsfromel für die Verkaufsaufträge'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='invoiceXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplateinvoice', to='djangoUserExtension.XSLFile', verbose_name='XSL Datei für die Rechnung'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='logo',
            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name="Logo für die PDF's"),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='organisationname',
            field=models.CharField(max_length=200, verbose_name='Name der Organisation'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='pagefooterleft',
            field=models.CharField(blank=True, max_length=40, null=True, verbose_name='Fussnote Links'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='pagefootermiddle',
            field=models.CharField(blank=True, max_length=40, null=True, verbose_name='Fussnote Mitte'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='profitLossStatementXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplateprofitlossstatement', to='djangoUserExtension.XSLFile', verbose_name='XSL Datei für die Erfolgsrechung'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='purchaseorderXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatepurchaseorder', to='djangoUserExtension.XSLFile', verbose_name='XSL Datei für die Kaufsbestätigung'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='quoteXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatequote', to='djangoUserExtension.XSLFile', verbose_name='XSL Datei für die Offerte'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='title',
            field=models.CharField(max_length=100, verbose_name='Titel'),
        ),
        migrations.AlterField(
            model_name='userextensionemailaddress',
            name='purpose',
            field=models.CharField(choices=[('H', 'Privat'), ('O', 'Geschäftlich'), ('P', 'Mobil Privat'), ('B', 'Mobil Geschäftlich')], max_length=1, verbose_name='Verwendungszweck'),
        ),
        migrations.AlterField(
            model_name='userextensionphoneaddress',
            name='purpose',
            field=models.CharField(choices=[('H', 'Privat'), ('O', 'Geschäftlich'), ('P', 'Mobil Privat'), ('B', 'Mobil Geschäftlich')], max_length=1, verbose_name='Verwendungszweck'),
        ),
        migrations.AlterField(
            model_name='userextensionpostaladdress',
            name='purpose',
            field=models.CharField(choices=[('H', 'Privat'), ('O', 'Geschäftlich'), ('P', 'Mobil Privat'), ('B', 'Mobil Geschäftlich')], max_length=1, verbose_name='Verwendungszweck'),
        ),
        migrations.AlterField(
            model_name='xslfile',
            name='title',
            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Titel'),
        ),
        migrations.AlterField(
            model_name='xslfile',
            name='xslfile',
            field=filebrowser.fields.FileBrowseField(max_length=200, verbose_name='XSL Datei'),
        ),
    ]
<file_sep>/koalixcrm/accounting/models.py
# -*- coding: utf-8 -*-

from koalixcrm.accounting.accounting.account import Account
from koalixcrm.accounting.accounting.booking import Booking
from koalixcrm.accounting.accounting.booking import InlineBookings
from koalixcrm.accounting.accounting.accounting_period import AccountingPeriod
from koalixcrm.accounting.accounting.product_category import ProductCategory






<file_sep>/koalixcrm/crm/product/currency_transform.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _


class CurrencyTransform(models.Model):
    from_currency = models.ForeignKey('Currency',
                                      blank=False,
                                      null=False,
                                      verbose_name=_("From Currency"),
                                      related_name="db_reltransformfromcurrency")
    to_currency = models.ForeignKey('Currency',
                                    blank=False,
                                    null=False,
                                    verbose_name=_("To Currency"),
                                    related_name="db_reltransformtocurrency")
    product_type = models.ForeignKey('ProductType',
                                     blank=False,
                                     null=False,
                                     verbose_name=_("Product"))
    factor = models.DecimalField(verbose_name=_("Factor between From and To Currency"),
                                 blank=False,
                                 null=False,
                                 max_digits=17,
                                 decimal_places=2,)

    def get_transform_factor(self):
        return self.factor

    def __str__(self):
        return "From " + self.from_currency.short_name + " to " + self.to_currency.short_name

    class Meta:
        app_label = "crm"
        verbose_name = _('Currency Transform')
        verbose_name_plural = _('Currency Transforms')


class CurrencyTransformInlineAdminView(admin.TabularInline):
    model = CurrencyTransform
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('', {
            'fields': ('from_currency',
                       'to_currency',
                       'factor',)
        }),
    )
    allow_add = True
<file_sep>/koalixcrm/crm/migrations/0011_auto_20180104_2152.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-04 21:52
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0010_auto_20180104_2138'),
    ]

    operations = [
        migrations.RenameField(
            model_name='currency',
            old_name='shortName',
            new_name='short_name',
        ),
        migrations.RenameField(
            model_name='customergrouptransform',
            old_name='fromCustomerGroup',
            new_name='from_customer_group',
        ),
        migrations.RenameField(
            model_name='customergrouptransform',
            old_name='toCustomerGroup',
            new_name='to_customer_group',
        ),
        migrations.RenameField(
            model_name='price',
            old_name='customerGroup',
            new_name='customer_group',
        ),
        migrations.RenameField(
            model_name='price',
            old_name='validfrom',
            new_name='valid_from',
        ),
        migrations.RenameField(
            model_name='price',
            old_name='validuntil',
            new_name='valid_until',
        ),
        migrations.RenameField(
            model_name='tax',
            old_name='accountActiva',
            new_name='account_activa',
        ),
        migrations.RenameField(
            model_name='tax',
            old_name='accountPassiva',
            new_name='account_passiva',
        ),
        migrations.RenameField(
            model_name='tax',
            old_name='taxrate',
            new_name='tax_rate',
        ),
        migrations.RenameField(
            model_name='unit',
            old_name='fractionFactorToNextHigherUnit',
            new_name='fraction_factor_to_next_higher_unit',
        ),
        migrations.RenameField(
            model_name='unit',
            old_name='isAFractionOf',
            new_name='is_a_fraction_of',
        ),
        migrations.RenameField(
            model_name='unit',
            old_name='shortName',
            new_name='short_name',
        ),
        migrations.RenameField(
            model_name='unittransform',
            old_name='fromUnit',
            new_name='from_unit',
        ),
        migrations.RenameField(
            model_name='unittransform',
            old_name='toUnit',
            new_name='to_unit',
        ),
    ]
<file_sep>/koalixcrm/accounting/migrations/0005_auto_20171110_1732.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-11-10 17:32
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('accounting', '0004_auto_20171009_1949'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='account',
            options={'ordering': ['accountNumber'], 'verbose_name': 'Account', 'verbose_name_plural': 'Account'},
        ),
        migrations.AlterModelOptions(
            name='accountingperiod',
            options={'verbose_name': 'Accounting Period', 'verbose_name_plural': 'Accounting Periods'},
        ),
        migrations.AlterModelOptions(
            name='booking',
            options={'verbose_name': 'Booking', 'verbose_name_plural': 'Bookings'},
        ),
        migrations.AlterModelOptions(
            name='productcategorie',
            options={'verbose_name': 'Product Categorie', 'verbose_name_plural': 'Product Categories'},
        ),
        migrations.AlterField(
            model_name='account',
            name='accountNumber',
            field=models.IntegerField(verbose_name='Account Number'),
        ),
        migrations.AlterField(
            model_name='account',
            name='accountType',
            field=models.CharField(choices=[('E', 'Earnings'), ('S', 'Spendings'), ('L', 'Liabilities'), ('A', 'Assets')], max_length=1, verbose_name='Account Type'),
        ),
        migrations.AlterField(
            model_name='account',
            name='description',
            field=models.TextField(blank=True, null=True, verbose_name='Description'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isACustomerPaymentAccount',
            field=models.BooleanField(verbose_name='Is a Customer Payment Account'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isProductInventoryActiva',
            field=models.BooleanField(verbose_name='Is a Product Inventory Account'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isopeninterestaccount',
            field=models.BooleanField(verbose_name='Is The Open Interests Account'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isopenreliabilitiesaccount',
            field=models.BooleanField(verbose_name='Is The Open Liabilities Account'),
        ),
        migrations.AlterField(
            model_name='account',
            name='title',
            field=models.CharField(max_length=50, verbose_name='Account Title'),
        ),
        migrations.AlterField(
            model_name='accountingperiod',
            name='begin',
            field=models.DateField(verbose_name='Begin'),
        ),
        migrations.AlterField(
            model_name='accountingperiod',
            name='end',
            field=models.DateField(verbose_name='End'),
        ),
        migrations.AlterField(
            model_name='accountingperiod',
            name='title',
            field=models.CharField(max_length=200, verbose_name='Title'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='accountingPeriod',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounting.AccountingPeriod', verbose_name='AccountingPeriod'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='amount',
            field=models.DecimalField(decimal_places=2, max_digits=20, verbose_name='Amount'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='bookingDate',
            field=models.DateTimeField(verbose_name='Booking at'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='bookingReference',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Invoice', verbose_name='Booking Reference'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='dateofcreation',
            field=models.DateTimeField(auto_now=True, verbose_name='Created at'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='description',
            field=models.CharField(blank=True, max_length=120, null=True, verbose_name='Description'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='fromAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_fromaccount', to='accounting.Account', verbose_name='From Account'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='lastmodification',
            field=models.DateTimeField(auto_now_add=True, verbose_name='Last modified'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='lastmodifiedby',
            field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_lstmodified', to=settings.AUTH_USER_MODEL, verbose_name='Last modified by'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='staff',
            field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_refstaff', to=settings.AUTH_USER_MODEL, verbose_name='Reference Staff'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='toAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_toaccount', to='accounting.Account', verbose_name='To Account'),
        ),
        migrations.AlterField(
            model_name='productcategorie',
            name='lossAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_loss_account', to='accounting.Account', verbose_name='Loss Account'),
        ),
        migrations.AlterField(
            model_name='productcategorie',
            name='profitAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_profit_account', to='accounting.Account', verbose_name='Profit Account'),
        ),
        migrations.AlterField(
            model_name='productcategorie',
            name='title',
            field=models.CharField(max_length=50, verbose_name='Product Categorie Title'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_resource.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Resource
from koalixcrm.crm.factories.factory_resource_type import StandardResourceTypeFactory
from koalixcrm.crm.factories.factory_resource_manager import StandardResourceManagerFactory


class StandardResourceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Resource

    resource_type = factory.SubFactory(StandardResourceTypeFactory)
    resource_manager = factory.SubFactory(StandardResourceManagerFactory)
<file_sep>/koalixcrm/accounting/tests/test_accountingModelTest.py
import datetime
from django.test import TestCase
from django.contrib.auth.models import User
from koalixcrm.accounting.models import Account
from koalixcrm.accounting.models import AccountingPeriod
from koalixcrm.accounting.models import Booking
from koalixcrm.crm.documents.pdf_export import PDFExport
from koalixcrm.test_support_functions import make_date_utc


class AccountingModelTest(TestCase):
    def setUp(self):
        user = User.objects.create(username='Username',
                                   password="<PASSWORD>")

        cash = Account.objects.create(account_number="1000",
                                      title="Cash",
                                      account_type="A",
                                      description="Highest liquid asset",
                                      is_open_interest_account=False,
                                      is_open_reliabilities_account=False,
                                      is_product_inventory_activa=False,
                                      is_a_customer_payment_account=True)
        bank_account = Account.objects.create(account_number="1300",
                                              title="Bank Account",
                                              account_type="A",
                                              description="Moderate liquid asset",
                                              is_open_interest_account=False,
                                              is_open_reliabilities_account=False,
                                              is_product_inventory_activa=False,
                                              is_a_customer_payment_account=True)
        bank_loan = Account.objects.create(account_number="2000",
                                      title="Shortterm bankloans",
                                      account_type="L",
                                      description="Shortterm loan",
                                      is_open_interest_account=False,
                                      is_open_reliabilities_account=False,
                                      is_product_inventory_activa=False,
                                      is_a_customer_payment_account=False)
        investment_capital = Account.objects.create(account_number="2900",
                                      title="Investment capital",
                                      account_type="L",
                                      description="Very longterm  loan",
                                      is_open_interest_account=False,
                                      is_open_reliabilities_account=False,
                                      is_product_inventory_activa=False,
                                      is_a_customer_payment_account=False)
        spendings = Account.objects.create(account_number="3000",
                                           title="Spendings",
                                           account_type="S",
                                           description="Purchase spendings",
                                           is_open_interest_account=False,
                                           is_open_reliabilities_account=False,
                                           is_product_inventory_activa=False,
                                           is_a_customer_payment_account=False)
        earnings = Account.objects.create(account_number="4000",
                                          title="Earnings",
                                          account_type="E",
                                          description="Sales account",
                                          is_open_interest_account=False,
                                          is_open_reliabilities_account=False,
                                          is_product_inventory_activa=False,
                                          is_a_customer_payment_account=False)
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        from_date = datetime_now.date()
        to_date = (datetime_now + datetime.timedelta(days=365)).date()
        accounting_period_2024 = AccountingPeriod.objects.create(title="Fiscal year 2024",
                                                                 begin=from_date,
                                                                 end=to_date)
        from_date = (datetime_now + datetime.timedelta(days=(365+1))).date()
        to_date = (datetime_now + datetime.timedelta(days=(365*2))).date()
        accounting_period_2025 = AccountingPeriod.objects.create(title="Fiscal year 2025",
                                        begin=from_date,
                                        end=to_date)
        from_date = (datetime_now + datetime.timedelta(days=(365*2+1))).date()
        to_date = (datetime_now + datetime.timedelta(days=(365*3))).date()
        AccountingPeriod.objects.create(title="Fiscal year 2026",
                                        begin=from_date,
                                        end=to_date)
        Booking.objects.create(from_account=cash,
                               to_account=spendings,
                               amount="1000",
                               description="This is the first booking",
                               booking_date=make_date_utc(datetime.datetime(2025, 1, 1, 0, 00)),
                               accounting_period=accounting_period_2025,
                               staff=user,
                               last_modified_by=user)

        Booking.objects.create(from_account=earnings,
                               to_account=cash,
                               amount="500",
                               description="This is the first booking",
                               booking_date=make_date_utc(datetime.datetime(2025, 1, 1, 0, 00)),
                               accounting_period=accounting_period_2025,
                               staff=user,
                               last_modified_by=user)

        Booking.objects.create(from_account=bank_loan,
                               to_account=cash,
                               amount="5000",
                               description="This is the first booking",
                               booking_date=make_date_utc(datetime.datetime(2025, 1, 1, 0, 00)),
                               accounting_period=accounting_period_2025,
                               staff=user,
                               last_modified_by=user)

        Booking.objects.create(from_account=investment_capital,
                               to_account=bank_account,
                               amount="490000",
                               description="This is the first booking",
                               booking_date=make_date_utc(datetime.datetime(2025, 1, 1, 0, 00)),
                               accounting_period=accounting_period_2024,
                               staff=user,
                               last_modified_by=user)

    def test_sumOfAllBookings(self):
        cash_account = Account.objects.get(title="Cash")
        spendings_account = Account.objects.get(title="Spendings")
        earnings_account = Account.objects.get(title="Earnings")
        self.assertEqual((cash_account.sum_of_all_bookings()).__str__(), "4500.00")
        self.assertEqual((spendings_account.sum_of_all_bookings()).__str__(), "1000.00")
        self.assertEqual((earnings_account.sum_of_all_bookings()).__str__(), "500.00")

    def test_sumOfAllBookingsBeforeAccountPeriod(self):
        cash_account = Account.objects.get(title="Cash")
        spendings_account = Account.objects.get(title="Spendings")
        earnings_account = Account.objects.get(title="Earnings")
        accounting_period_2026 = AccountingPeriod.objects.get(title="Fiscal year 2026")
        self.assertEqual((cash_account.sum_of_all_bookings_before_accounting_period(accounting_period_2026)).__str__(), "4500.00")
        self.assertEqual((spendings_account.sum_of_all_bookings_before_accounting_period(accounting_period_2026)).__str__(), "1000.00")
        self.assertEqual((earnings_account.sum_of_all_bookings_before_accounting_period(accounting_period_2026)).__str__(), "500.00")

    def test_sumOfAllBookingsWithinAccountgPeriod(self):
        cash_account = Account.objects.get(title="Cash")
        spendings_account = Account.objects.get(title="Spendings")
        earnings_account = Account.objects.get(title="Earnings")
        accounting_period_2024 = AccountingPeriod.objects.get(title="Fiscal year 2024")
        self.assertEqual((cash_account.sum_of_all_bookings_within_accounting_period(accounting_period_2024)).__str__(), "0")
        self.assertEqual((spendings_account.sum_of_all_bookings_within_accounting_period(accounting_period_2024)).__str__(), "0")
        self.assertEqual((earnings_account.sum_of_all_bookings_within_accounting_period(accounting_period_2024)).__str__(), "0")

    def test_overall_liabilities(self):
        accounting_period_2025 = AccountingPeriod.objects.get(title="Fiscal year 2025")
        self.assertEqual(
            (accounting_period_2025.overall_liabilities()).__str__(), "495000.00")

    def test_overall_assets(self):
        accounting_period_2025 = AccountingPeriod.objects.get(title="Fiscal year 2025")
        self.assertEqual(
            (accounting_period_2025.overall_assets()).__str__(), "494500.00")

    def test_overall_earnings(self):
        accounting_period_2025 = AccountingPeriod.objects.get(title="Fiscal year 2025")
        self.assertEqual(
            (accounting_period_2025.overall_earnings()).__str__(), "500.00")

    def test_overall_spendings(self):
        accounting_period_2025 = AccountingPeriod.objects.get(title="Fiscal year 2025")
        self.assertEqual(
            (accounting_period_2025.overall_spendings()).__str__(), "1000.00")

    def test_serialize_to_xml(self):
        accounting_period_2025 = AccountingPeriod.objects.get(title="Fiscal year 2025")
        xml = accounting_period_2025.serialize_to_xml()
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>']/field[@name='title']", 'Earnings')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>']/field[@name='title']", 'Spendings')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>']/field[@name='title']", 'Investment capital')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>']/field[@name='title']", 'Shortterm bankloans')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>']/field[@name='title']",'Cash')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>='<EMAIL>']/field[@name='title']",'Bank Account')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>='<EMAIL>']/Overall_Spendings", '1000.00')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>='<EMAIL>']/Overall_Earnings", '500.00')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>='<EMAIL>']/Overall_Assets", '494500.00')
        self.assertEqual(result, 1)
        result=PDFExport.find_element_in_xml(xml, "object/[<EMAIL>='<EMAIL>']/Overall_Liabilities", '495000.00')
        self.assertEqual(result, 1)
<file_sep>/koalixcrm/crm/documents/calculations.py
# -*- coding: utf-8 -*-

from decimal import *
from koalixcrm.crm.documents.sales_document_position import SalesDocumentPosition


class Calculations:

    @staticmethod
    def calculate_document_price(document, pricing_date):
        """Performs a price recalculation on contact documents.
        The calculated price is stored in the last_calculated_price and last_calculated_tax.
        The date when the price was calculated is stored in last_pricing_date

        Args:
            document: possible document classes must be derived from Contract
            pricing_date: must be a python date object

        Returns:
            1 (Boolean) when passed
            or raises exception

        Raises:
            Can trow Product.NoPriceFound when Product Price could not be found"""

        price = 0
        tax = 0
        positions = SalesDocumentPosition.objects.filter(sales_document=document.id)
        contact_for_price_calculation = document.customer
        if positions.exists():
            for position in positions:
                price += Calculations.calculate_position_price(position,
                                                               pricing_date,
                                                               contact_for_price_calculation,
                                                               document.currency)
                tax += Calculations.calculate_position_tax(position, document.currency)

            if document.discount is not None:
                discount = Decimal(document.discount)
                total_price = price * (1 - discount / 100)
                total_tax = tax * (1 - discount / 100)
                getcontext().prec = 5
                total_price = Decimal(total_price)
                total_tax = Decimal(total_tax)
                price = document.currency.round(total_price)
                tax = document.currency.round(total_tax)
        document.last_calculated_price = price
        document.last_calculated_tax = tax
        document.last_pricing_date = pricing_date
        document.save()
        return 1

    @staticmethod
    def calculate_position_price(position, pricing_date, contact, currency):
        """Performs a price calculation a position.
        The calculated price is stored in the last_calculated_price
        The date when the price was calculated is stored in last_pricing_date

        Args:
            position: possible document classes must be derived from Contract class
            pricing_date: must be a python date object
            contact: is used for the lookup of the correct price
            currency: is used for the lookup of the correct price and for the definition of the rounding

        Returns:
            calculated price
            or raises exception

        Raises:
            Can trow Product.NoPriceFound when Product Price could not be found"""

        if not position.overwrite_product_price:
            position.position_price_per_unit = position.product_type.get_price(pricing_date,
                                                                               position.unit,
                                                                               contact,
                                                                               currency)
        nominal_total = position.position_price_per_unit * position.quantity
        if isinstance(position.discount, Decimal):
            nominal_minus_discount = nominal_total * (1 - position.discount / 100)
        else:
            nominal_minus_discount = nominal_total
        total_with_tax = nominal_minus_discount * ((100-position.product_type.get_tax_rate()) / 100)
        getcontext().prec = 5
        total_with_tax = Decimal(total_with_tax)
        position.last_calculated_price = currency.round(total_with_tax)
        position.last_pricing_date = pricing_date
        position.save()
        return position.last_calculated_price

    @staticmethod
    def calculate_position_tax(position, currency):
        """Performs a tax calculation a position.
        The calculated tax is stored in the last_calculated_tax

        Args:
            position: possible document classes must be derived from Position class
            currency: is used for the definition of the rounding

        Returns:
            calculated tax
            or raises exception

        Raises:
            Can trow Product.NoPriceFound when Product Price could not be found"""
        nominal_total = position.position_price_per_unit * position.quantity
        if isinstance(position.discount, Decimal):
            nominal_minus_discount = nominal_total * (1 - position.discount / 100)
        else:
            nominal_minus_discount = nominal_total
        total_tax = nominal_minus_discount * position.product_type.get_tax_rate() / 100
        getcontext().prec = 5
        total_tax = Decimal(total_tax)
        position.last_calculated_tax = currency.round(total_tax)
        position.save()
        return position.last_calculated_tax
<file_sep>/documentation/source/intro.rst
.. highlight:: rst

Intro
=====

This Intro should give you an overview of koalixcrm's basic functions and structure.

Basic Structure
---------------
koalixcrm tries to keep its structure near the real world objects it represents.
Lets say you want to register a customer in koalixcrm then you find a customer
in your koalixcrm. When you receive a question for a quote, you find the quote
in your koalixcrm. Same for many kind of business objects you know in your company.
Every such real world objects is represented in the admin start page
(called "dashboard") as row of the Applications table.
Every object has a name - of cause - an Add and a Change button.
When you want to create a customer you just press the "Add" button in the
Customers row and you will see a form (called "object_modify_view") to fill
out for your customer. By pressing the Change or name button (eg "Customers")
of the object you see a list of all objects (called "objects_list") you have
already saved.

dashboard
---------
The dashboard ("Site Administration") is the place you start after logging in.
In the dashboard you find a list of all object types (called objectlist)
in three groups (called applications): crm, accounting and djangoUserExtentions.

.. image:: /images/dashboard.png

Administrator only
^^^^^^^^^^^^^^^^^^
When you are administrator, you can also see a group called auth where you
can manage users and groups. During the installation you are asked to
create a first, administrator user. Every new user after that starts with no privileges.
To be able to log in to koalixcrm the new user must be moderator.
When you want to create an accounting-only user you must
select all accounting privileges in the user_modification_view.

.. image:: /images/userprivileges.png

objects_list
------------
From the dashboard you are able to select one object type by clicking
on the name of it. What you get is a quite detailed list
of objects of this object type.

To modify an object you simply press press on the id of the object to
get to a form-view of the object.

Adding A New Object To The List
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To add a new object to the list you simply have to press the "Add button"
on the top right of the page

.. image:: /images/addbutton.png


Apply An Action On An Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Applying actions to objects is a basic point of using koalixcrm.
To be able to remove objects from your list you have to select all objects
you want to remove by clicking on the checkbox on the left side of the object list.

When you finish making your selection you will see a new selection field rising
from the bottom of your page. Here you can select what action you would
like to to apply to the objects. Serveral things can be done with these actions.

.. image:: /images/checkboxobjectlist.png

.. image:: /images/actionslist.png

Apply A Filter On The List Of Objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
On some list of objects you may apply filter functions.
This filter function can be found on the right side of the page.
Depending on the filter you apply on the list you get a smaller number of objects

.. image:: /images/filter.png

Do A Search Through All Objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The more objects you registered in your system, the more difficult it becomes
to find the one you are looking for. On some object lists you will find a search box.
The keywords you enter in this searchbox will be searched in
the objects name, id, description and so on.
All objects that include your selected keywords will be displayed in the list.

.. image:: /images/searchbox.png

object_modify_view
------------------
Lets say you logged into the system as a moderator with enough
privileges to modify a customer. You first access the
customer objects list. You will find your exiting list of customers on the screen

.. image:: /images/customer_object_list.png

As soon as you press on the button "ADD CUSTOMER", the browser will load a new screen: The
"object modify view". In the modify view you will find several fields to fill out.
As soon as the fields have been filled out you can press the "SAVE" button at the
bottom of the page. The field entries are then transmit and validated by the
application.

The fields which have a bold title are "must"-fields - they have to be filled out before
saving the customer.

.. image:: /images/customer_modify_view_collapsed.png

In case you have entered something wrong or invalid, the application will
complain and ask you to correct the invalid data.

.. image:: /images/customer_modify_view_error.png

Some of the optional fields are hidden (collapsed) by default.
By pressing the "SHOW" button beside "POSTAL ADDRESS FOR CONTACT" you can expand these
fields.

.. image:: /images/customer_modify_view_expand.png<file_sep>/koalixcrm/crm/factories/factory_product.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Product


class StandardProductFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Product
        django_get_or_create = ('product_number',)
<file_sep>/koalixcrm/crm/reporting/estimation.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.crm.reporting.resource_price import ResourcePrice


class Estimation(models.Model):
    task = models.ForeignKey("Task",
                             verbose_name=_('Task'),
                             blank=False,
                             null=False)
    resource = models.ForeignKey("Resource")
    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):
        default_resource_price = ResourcePrice.objects.filter(id=self.resource.id)
        if len(default_resource_price) == 0:
            costs = 0
        else:
            for resource_price in default_resource_price:
                costs = self.amount*resource_price.price
                break
        return costs

    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 EstimationInlineAdminView(admin.TabularInline):
    model = Estimation
    fieldsets = (
        (_('Work'), {
            'fields': ('task',
                       'resource',
                       'amount',
                       'date_from',
                       'date_until',
                       'status',
                       'reporting_period')
        }),
    )
    extra = 1
<file_sep>/koalixcrm/accounting/migrations/0008_auto_20181012_2056.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-12 20:56
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('accounting', '0007_auto_20180422_2105'),
    ]

    operations = [
        migrations.CreateModel(
            name='ProductCategory',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=50, verbose_name='Product Category Title')),
            ],
            options={
                'verbose_name': 'Product Category',
                'verbose_name_plural': 'Product Categories',
            },
        ),
    ]
<file_sep>/koalixcrm/djangoUserExtension/const/purpose.py
# -*- coding: utf-8 -*

from django.utils.translation import ugettext as _

PURPOSESADDRESSINUSEREXTENTION = (
    ('H', _('Private')),
    ('O', _('Business')),
    ('P', _('Mobile Private')),
    ('B', _('Mobile Business')),
)
<file_sep>/koalixcrm/crm/migrations/0021_purchaseorder.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-16 20:59
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0020_auto_20180116_2056'),
    ]

    operations = [
        migrations.CreateModel(
            name='PurchaseOrder',
            fields=[
                ('salesdocument_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.SalesDocument')),
                ('status', models.CharField(choices=[('O', 'Ordered'), ('D', 'Delayed'), ('Y', 'Delivered'), ('I', 'Invoice registered'), ('P', 'Invoice payed')], max_length=1)),
                ('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Supplier', verbose_name='Supplier')),
            ],
            options={
                'verbose_name': 'Payment Reminder',
                'verbose_name_plural': 'Payment Reminders',
            },
            bases=('crm.salesdocument',),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_task_link_type.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import TaskLinkType


class RelatedToTaskLinkTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = TaskLinkType
        django_get_or_create = ('title',)

    title = "Is related to"
    description = "This task is related with ...."


class RequiresLinkTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = TaskLinkType
        django_get_or_create = ('title',)

    title = "This task requires"
    description = "This task requires the completion or the existence of ...."
<file_sep>/setup.py
from setuptools import setup, find_packages
from koalixcrm.version import KOALIXCRM_VERSION

setup(name='koalix-crm',
      version=KOALIXCRM_VERSION,
      description='koalixcrm is a tiny and easy to use Customer-Relationship-Management '
                  'Software (CRM) including tiny and easy to use Accounting Software',
      url='http://github.com/scaphilo/koalixcrm',
      author='<NAME>',
      author_email='<EMAIL>',
      license='BSD',
      packages=find_packages(exclude=["projectsettings", "documentation"]),
      install_requires=['Django==1.11.21',
                        'django-filebrowser==3.9.1',
                        'lxml>=3.8.0',
                        'olefile>=0.44',
                        'Pillow==4.2.1',
                        'psycopg2_binary>=2.7.5',
                        'pytz==2017.2',
                        'django-grappelli==2.10.1',
                        'djangorestframework>=3.9.1',
                        'markdown==2.6.11',
                        'django-filter==1.1.0',
                        'djangorestframework-xml==1.3.0'
                        ],
      zip_safe=False,
      classifiers=['Development Status :: 4 - Beta',
                   'Programming Language :: Python :: 3.5',
                   ],
      python_requires='~=3.5',
      include_package_data=True,
)
<file_sep>/koalixcrm/crm/migrations/0010_auto_20180104_2138.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-04 21:38
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0009_auto_20180104_2111'),
    ]

    operations = [
        migrations.RenameField(
            model_name='position',
            old_name='positionPricePerUnit',
            new_name='position_price_per_unit',
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_customer_group_transform.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.product.customer_group_transform import CustomerGroupTransform
from koalixcrm.crm.factories.factory_product_type import StandardProductTypeFactory
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_type = factory.SubFactory(StandardProductTypeFactory)
    factor = "10"
<file_sep>/requirements/heroku_requirements.txt
-r base_requirements.txt
-r test_requirements.txt

gunicorn==19.7.1
dj-database-url==0.4.2
<file_sep>/koalixcrm/crm/reporting/project.py
# -*- coding: utf-8 -*-

from decimal import *
from datetime import *
from django.db import models
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 GenericLinkInlineAdminView
from koalixcrm.crm.reporting.reporting_period import ReportingPeriodInlineAdminView, ReportingPeriod
from koalixcrm.crm.reporting.task import TaskInlineAdminView
from koalixcrm.crm.documents.pdf_export import PDFExport
from koalixcrm.crm.exceptions import TemplateSetMissingInContract
from koalixcrm.crm.models import Task
from rest_framework import serializers


class Project(models.Model):
    project_manager = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True},
                                        verbose_name=_("Staff"),
                                        related_name="db_rel_project_staff",
                                        blank=True,
                                        null=True)
    project_name = models.CharField(verbose_name=_("Project name"),
                                    max_length=100,
                                    null=True,
                                    blank=True)
    description = models.TextField(verbose_name=_("Description"),
                                   null=True,
                                   blank=True)
    project_status = models.ForeignKey("ProjectStatus",
                                       verbose_name=_('Project Status'),
                                       blank=True,
                                       null=True)
    default_template_set = models.ForeignKey("djangoUserExtension.TemplateSet",
                                             verbose_name=_("Default Template Set"),
                                             null=True,
                                             blank=True)
    default_currency = models.ForeignKey("Currency",
                                         verbose_name=_("Default Currency"),
                                         null=False,
                                         blank=False)
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"),
                                            auto_now_add=True)
    last_modification = models.DateTimeField(verbose_name=_("Last modified"),
                                             auto_now=True)
    last_modified_by = models.ForeignKey('auth.User',
                                         limit_choices_to={'is_staff': True},
                                         verbose_name=_("Last modified by"),
                                         related_name="db_project_last_modified")

    def link_to_project(self):
        if self.id:
            return format_html("<a href='/admin/crm/project/%s' >%s</a>" % (str(self.id), str(self.project_name)))
        else:
            return "Not present"
    link_to_project.short_description = _("Project")

    def create_pdf(self, template_set, printed_by):
        self.last_print_date = datetime.now()
        self.save()
        return PDFExport.create_pdf(self, template_set, printed_by)

    def get_template_set(self):
        if self.default_template_set.monthly_project_summary_template:
            return self.default_template_set.monthly_project_summary_template
        else:
            raise TemplateSetMissingInContract((_("Template Set missing in Project" + str(self))))

    def get_fop_config_file(self, template_set):
        template_set = self.get_template_set()
        return template_set.get_fop_config_file()

    def get_xsl_file(self, template_set):
        template_set = self.get_template_set()
        return template_set.get_xsl_file()

    def get_reporting_period(self, search_date):
        from koalixcrm.crm.reporting.reporting_period import ReportingPeriod
        """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:
          no arguments

        Returns:
          accounting_period (ReportPeriod)

        Raises:
          ReportPeriodNotFound when there is no valid reporting Period"""
        return ReportingPeriod.get_reporting_period(self, search_date)

    def serialize_to_xml(self, **kwargs):
        reporting_period = kwargs.get('reporting_period', None)
        from koalixcrm.djangoUserExtension.models import UserExtension
        objects = [self, ]
        objects += UserExtension.objects_to_serialize(self, self.project_manager)
        main_xml = PDFExport.write_xml(objects)
        for task in Task.objects.filter(project=self.id):
            task_xml = task.serialize_to_xml(reporting_period=reporting_period)
            main_xml = PDFExport.merge_xml(main_xml, task_xml)
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Effective_Effort_Overall",
                                                       self.effective_costs(reporting_period=None))
        if reporting_period:
            main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                           "object/[<EMAIL>']",
                                                           "Effective_Effort_InPeriod",
                                                           self.effective_costs(reporting_period=reporting_period))
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Planned_Effort",
                                                       self.planned_costs())
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Effective_Duration",
                                                       self.effective_duration())
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Planned_Duration",
                                                       self.planned_duration())
        return main_xml

    def effective_accumulated_costs(self, reporting_period=None):
        if reporting_period:
            reporting_periods = ReportingPeriod.get_all_predecessors(target_reporting_period=reporting_period,
                                                                     project=self)
        else:
            reporting_periods = ReportingPeriod.objects.filter(project=self.id)
        effective_accumulated_costs = 0
        for single_reporting_period in reporting_periods:
            all_project_tasks = Task.objects.filter(project=self.id)
            for task in all_project_tasks:
                effective_accumulated_costs += float(task.effective_costs(reporting_period=single_reporting_period))
        getcontext().prec = 5
        effective_accumulated_costs = Decimal(effective_accumulated_costs)
        self.default_currency.round(effective_accumulated_costs)
        return effective_accumulated_costs

    effective_accumulated_costs.short_description = _("Effective Accumulated costs")
    effective_accumulated_costs.tags = True

    def effective_costs(self, reporting_period):
        effective_cost = 0
        for task in Task.objects.filter(project=self.id):
            effective_cost += task.effective_costs(reporting_period=reporting_period)
        self.default_currency.round(effective_cost)
        return effective_cost

    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)
        getcontext().prec = 5
        planned_effort_accumulated = Decimal(planned_effort_accumulated)
        self.default_currency.round(planned_effort_accumulated)
        return planned_effort_accumulated
    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

        Args:
        no arguments

        Returns:
        effective_start (Date) or None when not yet started

        Raises:
        No exceptions planned"""
        no_tasks_started = True
        all_project_tasks = Task.objects.filter(project=self.id)
        effective_project_start = None
        if len(all_project_tasks) == 0:
            effective_project_start = None
        else:
            for task in all_project_tasks:
                if not effective_project_start:
                    if task.effective_start():
                        effective_project_start = task.effective_start()
                        no_tasks_started = False
                effective_task_start = task.effective_start()
                if effective_task_start:
                    if effective_task_start < effective_project_start:
                        effective_project_start = effective_task_start
            if no_tasks_started:
                effective_project_start = None
        return effective_project_start
    effective_start.short_description = _("Effective Start")
    effective_start.tags = True

    def effective_end(self):
        """The function return the effective end of a project as a date

        Args:
        no arguments

        Returns:
        effective_end (Date) or None when not yet ended

        Raises:
        No exceptions planned"""
        all_tasks_done = True
        all_project_tasks = Task.objects.filter(project=self.id)
        effective_project_end = None
        if len(all_project_tasks) == 0:
            effective_project_end = None
        else:
            i = 0
            for task in all_project_tasks:
                if not effective_project_end:
                    if not task.effective_start():
                        all_tasks_done = False
                        break
                    else:
                        effective_project_end = task.effective_start()
                effective_task_end = task.effective_end()
                if not effective_task_end:
                    all_tasks_done = False
                    break
                elif effective_task_end > effective_project_end:
                    effective_project_end = effective_task_end
                i = i+1
            if not all_tasks_done:
                effective_project_end = None
        return effective_project_end
    effective_end.short_description = _("Effective End")
    effective_end.tags = True

    def effective_duration(self):
        """The function return the effective overall duration of a project as a string in days
        The function is reading the effective_starts and effective_ends of the project and
        subtract them from each other.

        Args:
        no arguments

        Returns:
        duration_in_days or description (String)

        Raises:
        No exceptions planned"""
        effective_end = self.effective_end()
        effective_start = self.effective_start()
        if not effective_start:
            duration_as_string = "Project has not yet started"
        elif not effective_end:
            duration_as_string = "Project has not yet ended"
        else:
            duration_as_date = self.effective_end()-self.effective_start()
            duration_as_string = duration_as_date.days.__str__()
        return duration_as_string
    effective_duration.short_description = _("Effective Duration [dys]")
    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 = None
            for task in tasks:
                if task.planned_start():
                    if i == 0:
                        project_start = task.planned_start()
                    elif task.planned_start() < project_start:
                        project_start = task.planned_start()
                    i += 1
            return project_start
        else:
            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 = None
            for task in tasks:
                if task.planned_end():
                    if i == 0:
                        project_end = task.planned_end()
                    elif task.planned_end() > project_end:
                        project_end = task.planned_end()
                    i += 1
                    return project_end
        else:
            return None

    def planned_duration(self):
        """The function return planned overall duration of a project as a string in days

        Args:
        no arguments

        Returns:
        duration_in_days (String)

        Raises:
        No exceptions planned"""
        if (not self.planned_start()) or (not self.planned_end()):
            duration_in_days = "n/a"
        elif self.planned_start() > self.planned_end():
            duration_in_days = "n/a"
        else:
            duration_in_days = (self.planned_end()-self.planned_start()).days.__str__()
        return duration_in_days
    planned_duration.short_description = _("Planned Duration [dys]")
    planned_duration.tags = True

    def get_project_name(self):
        """The function safely returns a project_name even if the values is empty, in such a case
        the function returns a n/a"

        Args:
        no arguments

        Returns:
        project_name (String)

        Raises:
        No exceptions planned"""
        if self.project_name:
            return self.project_name
        else:
            return "n/a"

    def is_reporting_allowed(self):
        """The function returns a boolean True when it is allowed to report on the project and
        on one of the reporting periods does also allow reporting

        Args:
          no arguments

        Returns:
          reporting_allowed (Boolean)

        Raises:
          No exceptions planned"""
        from koalixcrm.crm.reporting.reporting_period import ReportingPeriod
        reporting_periods = ReportingPeriod.objects.filter(project=self.id, status__is_done=False)
        if len(reporting_periods) != 0:
            if not self.project_status.is_done:
                return True
            else:
                return False
        else:
            return False

    def __str__(self):
        return str(self.id)+" "+self.get_project_name()

    class Meta:
        app_label = "crm"
        verbose_name = _('Project')
        verbose_name_plural = _('Projects')


class ProjectAdminView(admin.ModelAdmin):
    list_display = ('id',
                    'project_status',
                    'project_name',
                    'project_manager',
                    'default_currency',
                    'planned_duration',
                    'planned_costs',
                    'effective_duration',
                    'effective_accumulated_costs'
                    )

    list_display_links = ('id',)
    ordering = ('-id',)

    fieldsets = (
        (_('Project'), {
            'fields': ('project_status',
                       'project_manager',
                       'project_name',
                       'description',
                       'default_currency',
                       'default_template_set',)
        }),
    )

    inlines = [TaskInlineAdminView,
               GenericLinkInlineAdminView,
               ReportingPeriodInlineAdminView]
    actions = ['create_report_pdf', ]

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_modified_by = request.user
        else:
            obj.last_modified_by = request.user
            obj.staff = request.user
        obj.save()

    def create_report_pdf(self, request, queryset):
        from koalixcrm.crm.views.pdfexport import PDFExportView
        for obj in queryset:
            response = PDFExportView.export_pdf(self,
                                                request,
                                                obj,
                                                ("/admin/crm/"+obj.__class__.__name__.lower()+"/"),
                                                obj.default_template_set.monthly_project_summary_template)
            return response

    create_report_pdf.short_description = _("Create Report PDF")


class ProjectInlineAdminView(admin.TabularInline):
    model = Project
    readonly_fields = ('link_to_project',
                       'project_status',
                       'project_manager',
                       'description',
                       'default_template_set')
    fieldsets = (
        (_('Project'), {
            'fields': ('link_to_project',
                       'project_status',
                       'project_manager',
                       'description',
                       'default_template_set')
        }),
    )
    extra = 0

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False


class ProjectJSONSerializer(serializers.ModelSerializer):
    from koalixcrm.crm.reporting.task import TaskSerializer
    tasks = TaskSerializer(many=True, read_only=True)

    is_reporting_allowed = serializers.SerializerMethodField()

    def get_is_reporting_allowed(self, obj):
        if obj.is_reporting_allowed():
            return "True"
        else:
            return "False"

    class Meta:
        model = Project
        fields = ('id',
                  'project_manager',
                  'project_name',
                  'tasks',
                  'is_reporting_allowed')<file_sep>/koalixcrm/crm/factories/factory_currency.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Currency


class StandardCurrencyFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Currency
        django_get_or_create = ('description',)

    description = "Swiss Francs"
    short_name = "CHF"
    rounding = "0.05"


class SecondStandardCurrencyFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Currency
        django_get_or_create = ('description',)

    description = "Euro"
    short_name = "EUR"
    rounding = "0.01"
<file_sep>/koalixcrm/subscriptions/models.py
# -*- coding: utf-8 -*-

from datetime import *
from django.db import models
from django.utils.translation import ugettext as _
from filebrowser.fields import FileBrowseField
from koalixcrm.subscriptions.const.events import *
import koalixcrm.crm.documents


class Subscription(models.Model):
    contract = models.ForeignKey('crm.Contract', verbose_name=_('Subscription Type'))
    subscription_type = models.ForeignKey('SubscriptionType', verbose_name=_('Subscription Type'), null=True)

    def create_subscription_from_contract(self, contract):
        self.contract = contract
        self.save()
        return self

    def create_quote(self):
        quote = koalixcrm.crm.documents.quote.Quote()
        quote.contract = self.contract
        quote.discount = 0
        quote.staff = self.contract.staff
        quote.customer = self.contract.defaultcustomer
        quote.status = 'C'
        quote.currency = self.contract.defaultcurrency
        quote.valid_until = date.today().__str__()
        quote.date_of_creation = date.today().__str__()
        quote.save()
        return quote

    def create_invoice(self):
        invoice = koalixcrm.crm.documents.invoice.Invoice()
        invoice.contract = self.contract
        invoice.discount = 0
        invoice.staff = self.contract.staff
        invoice.customer = self.contract.default_customer
        invoice.status = 'C'
        invoice.currency = self.contract.default_currency
        invoice.payable_until = date.today() + timedelta(
            days=self.contract.defaultcustomer.defaultCustomerBillingCycle.timeToPaymentDate)
        invoice.date_of_creation = date.today().__str__()
        invoice.save()
        return invoice

    class Meta:
        app_label = "subscriptions"
        verbose_name = _('Subscription')
        verbose_name_plural = _('Subscriptions')


class SubscriptionEvent(models.Model):
    subscriptions = models.ForeignKey('Subscription',
                                      verbose_name=_('Subscription'))
    event_date = models.DateField(verbose_name=_("Event Date"),
                                  blank=True, null=True)
    event = models.CharField(max_length=1, choices=SUBSCRITIONEVENTS,
                             verbose_name=_('Event'))

    def __str__(self):
        return self.event

    class Meta:
        app_label = "subscriptions"
        verbose_name = _('Subscription Event')
        verbose_name_plural = _('Subscription Events')


class SubscriptionType(models.Model):
    product_type = models.ForeignKey('crm.ProductType',
                                     verbose_name=_('Product Type'),
                                     on_delete=models.deletion.SET_NULL,
                                     null=True,
                                     blank=True)
    cancellation_period = models.IntegerField(verbose_name=_("Cancellation Period (months)"),
                                              blank=True,
                                              null=True)
    automatic_contract_extension = models.IntegerField(verbose_name=_("Automatic Contract Extension (months)"),
                                                       blank=True,
                                                       null=True)
    automatic_contract_extension_reminder = models.IntegerField(
        verbose_name=_("Automatic Contract Extension Reminder (days)"),
        blank=True,
        null=True)
    minimum_duration = models.IntegerField(verbose_name=_("Minimum Contract Duration"),
                                           blank=True,
                                           null=True)
    payment_interval = models.IntegerField(verbose_name=_("Payment Interval (days)"),
                                           blank=True,
                                           null=True)
    contract_document = FileBrowseField(verbose_name=_("Contract Documents"),
                                        blank=True,
                                        null=True,
                                        max_length=200)

    class Meta:
        app_label = "subscriptions"
        verbose_name = _('Subscription Type')
        verbose_name_plural = _('Subscription Types')
<file_sep>/koalixcrm/crm/migrations/0019_auto_20180112_2020.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-12 20:20
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0018_auto_20180111_2031'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='emailaddressforsalesdocument',
            options={'verbose_name': 'Email Address For Sales Documents', 'verbose_name_plural': 'Email Address For Sales Documents'},
        ),
        migrations.AlterModelOptions(
            name='phoneaddressforsalesdocument',
            options={'verbose_name': 'Phone Address For Sales Documents', 'verbose_name_plural': 'Phone Address For Sales Documents'},
        ),
        migrations.AlterModelOptions(
            name='postaladdressforsalesdocument',
            options={'verbose_name': 'Postal Address For Sales Documents', 'verbose_name_plural': 'Postal Address For Sales Documents'},
        ),
        migrations.RenameField(
            model_name='customerbillingcycle',
            old_name='timeToPaymentDate',
            new_name='time_to_payment_date',
        ),
        migrations.RemoveField(
            model_name='deliverynote',
            name='derived_from_invoice',
        ),
        migrations.RemoveField(
            model_name='deliverynote',
            name='derived_from_quote',
        ),
        migrations.RemoveField(
            model_name='paymentreminder',
            name='derived_from_invoice',
        ),
        migrations.RemoveField(
            model_name='purchaseconfirmation',
            name='derived_from_quote',
        ),
        migrations.AddField(
            model_name='customerbillingcycle',
            name='payment_reminder_time_to_payment',
            field=models.IntegerField(default=10, verbose_name='Payment Reminder, Days To Payment Date '),
            preserve_default=False,
        ),
    ]
<file_sep>/koalixcrm/subscriptions/migrations/0003_auto_20171110_1732.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-11-10 17:32
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import filebrowser.fields


class Migration(migrations.Migration):

    dependencies = [
        ('subscriptions', '0002_auto_20171009_1949'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='subscription',
            options={'verbose_name': 'Subscription', 'verbose_name_plural': 'Subscriptions'},
        ),
        migrations.AlterModelOptions(
            name='subscriptionevent',
            options={'verbose_name': 'Subscription Event', 'verbose_name_plural': 'Subscription Events'},
        ),
        migrations.AlterModelOptions(
            name='subscriptiontype',
            options={'verbose_name': 'Subscription Type', 'verbose_name_plural': 'Subscription Types'},
        ),
        migrations.AlterField(
            model_name='subscription',
            name='contract',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Contract', verbose_name='Subscription Type'),
        ),
        migrations.AlterField(
            model_name='subscription',
            name='subscriptiontype',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='subscriptions.SubscriptionType', verbose_name='Subscription Type'),
        ),
        migrations.AlterField(
            model_name='subscriptionevent',
            name='event',
            field=models.CharField(choices=[('O', 'Offered'), ('C', 'Canceled'), ('S', 'Signed')], max_length=1, verbose_name='Event'),
        ),
        migrations.AlterField(
            model_name='subscriptionevent',
            name='eventdate',
            field=models.DateField(blank=True, null=True, verbose_name='Event Date'),
        ),
        migrations.AlterField(
            model_name='subscriptionevent',
            name='subscriptions',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='subscriptions.Subscription', verbose_name='Subscription'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='automaticContractExtension',
            field=models.IntegerField(blank=True, null=True, verbose_name='Automatic Contract Extension (months)'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='automaticContractExtensionReminder',
            field=models.IntegerField(blank=True, null=True, verbose_name='Automatic Contract Extensoin Reminder (days)'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='cancelationPeriod',
            field=models.IntegerField(blank=True, null=True, verbose_name='Cancelation Period (months)'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='contractDocument',
            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name='Contract Documents'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='minimumDuration',
            field=models.IntegerField(blank=True, null=True, verbose_name='Minimum Contract Duration'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='paymentIntervall',
            field=models.IntegerField(blank=True, null=True, verbose_name='Payment Intervall (days)'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_generic_project_link.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import GenericProjectLink
from koalixcrm.crm.factories.factory_project import StandardProjectFactory
from koalixcrm.crm.factories.factory_task_link_type import RelatedToTaskLinkTypeFactory
from koalixcrm.crm.factories.factory_user import StaffUserFactory
from koalixcrm.crm.factories.factory_task import StandardTaskFactory
from django.contrib.contenttypes.models import ContentType


class StandardGenericTaskLinkFactory(factory.django.DjangoModelFactory):
    class Meta:
        exclude = ['generic_crm_object']

    task = factory.SubFactory(StandardProjectFactory)
    task_link_type = factory.SubFactory(RelatedToTaskLinkTypeFactory)
    content_type = factory.LazyAttribute(
        lambda o: ContentType.objects.get_for_model(o.generic_crm_object))
    object_id = factory.SelfAttribute('generic_crm_object.id')
    date_of_creation = "2018-05-01"
    last_modified_by = factory.SubFactory(StaffUserFactory)


class LinkToTaskGenericTaskLinkFactory(StandardGenericTaskLinkFactory):
    class Meta:
        model = GenericProjectLink

    generic_crm_object = factory.SubFactory(StandardTaskFactory)
<file_sep>/koalixcrm/crm/tests/test_task_constructor.py
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)
    """
    original = datetime.date

    class FreezeMeta(type):
        def __instancecheck__(self, instance):
            if instance.isinstance(original) or instance.isinstance(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))
<file_sep>/koalixcrm/crm/factories/factory_product_type.py
# -*- coding: utf-8 -*-

import factory
import datetime
from koalixcrm.crm.models import ProductType
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory
from koalixcrm.crm.factories.factory_user import StaffUserFactory
from koalixcrm.crm.factories.factory_tax import StandardTaxFactory
from koalixcrm.test_support_functions import make_date_utc


class StandardProductTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProductType
        django_get_or_create = ('product_type_identifier',)

    title = "This is a test Product"
    description = "This is a test Product"
    product_type_identifier = "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(StandardTaxFactory)
<file_sep>/requirements/base_requirements.txt
Django==1.11.21
django-filebrowser==3.9.1
lxml==3.8.0
olefile==0.44
Pillow==6.2.0
psycopg2-binary==2.7.5
pytz==2017.2
django-grappelli==2.10.1
djangorestframework>=3.9.1
djangorestframework-xml==1.4.0
markdown==2.6.11
django-filter==1.1.0
<file_sep>/koalixcrm/crm/contact/customer_billing_cycle.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _


class CustomerBillingCycle(models.Model):
    name = models.CharField(max_length=300,
                            verbose_name=_("Name"))
    time_to_payment_date = models.IntegerField(verbose_name=_("Days To Payment Date"))
    payment_reminder_time_to_payment = models.IntegerField(verbose_name=_("Payment Reminder, Days To Payment Date "))

    class Meta:
        app_label = "crm"
        verbose_name = _('Customer Billing Cycle')
        verbose_name_plural = _('Customer Billing Cycle')

    def __str__(self):
        return self.id.__str__() + ' ' + self.name


class OptionCustomerBillingCycle(admin.ModelAdmin):
    list_display = ('id',
                    'name',
                    'time_to_payment_date',
                    'payment_reminder_time_to_payment')
    fieldsets = (('', {'fields': ('name',
                                  'time_to_payment_date',
                                  'payment_reminder_time_to_payment',
                                  )}),)
    allow_add = True
<file_sep>/koalixcrm/crm/reporting/human_resource.py
# -*- coding: utf-8 -*-

import datetime
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.djangoUserExtension.user_extension.user_extension import UserExtension
from koalixcrm.crm.reporting.resource import Resource
from koalixcrm.crm.reporting.resource_price import ResourcePriceInlineAdminView
from koalixcrm.crm.reporting.work import Work
from koalixcrm.crm.documents.pdf_export import PDFExport


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())
        date_first_of_the_month = date_from.replace(day=1)
        date_to_month = date_to.month
        date_end_of_the_month = date_from.replace(day=1).replace(month=date_to_month+1) - datetime.timedelta(days=1)
        date = date_first_of_the_month
        days = dict()
        weeks = dict()
        months = dict()
        projects = self.resource_contribution_project(date_from, date_to)
        objects = [self, self.user]
        objects.extend(projects)
        main_xml = PDFExport.write_xml(objects)
        while date < date_from:
            project_efforts = dict()
            for project in projects:
                project_efforts[project] = {'effort': "-",
                                            'project': project.id.__str__()}
            days[date] = {'effort': "-",
                          "day": str(date.day),
                          "week": str(date.isocalendar()[1]),
                          "week_day": str(date.isoweekday()),
                          "month": str(date.month),
                          "year": str(date.year),
                          "project_efforts": project_efforts}
            date += datetime.timedelta(days=1)
        while date <= date_to:
            project_efforts_day = dict()
            project_efforts_week = dict()
            project_efforts_month = dict()
            for project in projects:
                project_efforts_day[project] = {'effort': 0,
                                                'project': project.id.__str__()}
                project_efforts_week[project] = {'effort': 0,
                                                 'project': project.id.__str__()}
                project_efforts_month[project] = {'effort': 0,
                                                  'project': project.id.__str__()}
            days[date] = {'effort': 0,
                          "day": str(date.day),
                          "week": str(date.isocalendar()[1]),
                          "week_day": str(date.isoweekday()),
                          "month": str(date.month),
                          "year": str(date.year),
                          "project_efforts": project_efforts_day}
            month_key = str(date.month)+"/"+str(date.year)
            week_key = str(date.isocalendar()[1])+"/"+str(date.year)
            if not (week_key in weeks):
                weeks[week_key] = {'effort': 0,
                                   'week': str(date.isocalendar()[1]),
                                   'year': str(date.year),
                                   "project_efforts": project_efforts_week}
            if not (month_key in months):
                months[month_key] = {'effort': 0,
                                     'month': str(date.month),
                                     'year': str(date.year),
                                     "project_efforts": project_efforts_month}
            date += datetime.timedelta(days=1)
        while date < date_end_of_the_month:
            project_efforts = dict()
            for project in projects:
                project_efforts[project] = {'effort': "-",
                                            'project': project.id.__str__()}
            days[date] = {'effort': "-",
                          "day": str(date.day),
                          "week": str(date.isocalendar()[1]),
                          "week_day": str(date.isoweekday()),
                          "month": str(date.month),
                          "year": str(date.year),
                          "project_efforts": project_efforts}
            date += datetime.timedelta(days=1)
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       ".",
                                                       "range_from",
                                                       date_from.__str__(),
                                                       attributes={"day": str(date_from.day),
                                                                   "week": str(date_from.isocalendar()[1]),
                                                                   "week_day": str(date_from.isoweekday()),
                                                                   "month": str(date_from.month),
                                                                   "year": str(date_from.year)})
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       ".",
                                                       "range_to",
                                                       date_to.__str__(),
                                                       attributes={"day": str(date_to.day),
                                                                   "week": str(date_to.isocalendar()[1]),
                                                                   "week_day": str(date_to.isoweekday()),
                                                                   "month": str(date_to.month),
                                                                   "year": str(date_to.year)})
        works = Work.objects.filter(human_resource=self, date__range=(date_from, date_to))
        for work in works:
            days[work.date]['effort'] += work.effort_hours()
            days[work.date]['project_efforts'][work.task.project]['effort'] += work.effort_hours()
            month_key = str(work.date.month)+"/"+str(work.date.year)
            week_key = str(work.date.isocalendar()[1])+"/"+str(work.date.year)
            weeks[week_key]['effort'] += work.effort_hours()
            weeks[week_key]['project_efforts'][work.task.project]['effort'] += work.effort_hours()
            months[month_key]['effort'] += work.effort_hours()
            months[month_key]['project_efforts'][work.task.project]['effort'] += work.effort_hours()
            work_xml = work.serialize_to_xml()
            main_xml = PDFExport.merge_xml(main_xml, work_xml)
        for day_key in days.keys():
            main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                           "object/[@<EMAIL>='django<EMAIL>']",
                                                           "Day_Work_Hours",
                                                           str(days[day_key]['effort']),
                                                           attributes={"day": days[day_key]['day'],
                                                                       "week": days[day_key]['week'],
                                                                       "week_day": days[day_key]['week_day'],
                                                                       "month": days[day_key]['month'],
                                                                       "year": days[day_key]['year']})
            for project_key in days[day_key]['project_efforts'].keys():
                main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                               "object/[@<EMAIL>='<EMAIL>']",
                                                               "Day_Project_Work_Hours",
                                                               str(days[day_key]['project_efforts'][project_key]['effort']),
                                                               attributes={"day": days[day_key]['day'],
                                                                           "week": days[day_key]['week'],
                                                                           "week_day": days[day_key]['week_day'],
                                                                           "month": days[day_key]['month'],
                                                                           "year": days[day_key]['year'],
                                                                           "project": days[day_key]['project_efforts'][project_key]['project']})
        for week_key in weeks.keys():
            main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                           "object/[@model='<EMAIL>']",
                                                           "Week_Work_Hours",
                                                           str(weeks[week_key]['effort']),
                                                           attributes={"week": weeks[week_key]['week'],
                                                                       "year": weeks[week_key]['year']})
            for project_key in weeks[week_key]['project_efforts'].keys():
                main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                               "object/[@model='django<EMAIL>']",
                                                               "Week_Project_Work_Hours",
                                                               str(weeks[week_key]['project_efforts'][project_key]['effort']),
                                                               attributes={"week": weeks[week_key]['week'],
                                                                           "year": weeks[week_key]['year'],
                                                                           "project": weeks[week_key]['project_efforts'][project_key]['project']})
        for month_key in months.keys():
            main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                           "object/[@model='django<EMAIL>']",
                                                           "Month_Work_Hours",
                                                           str(months[month_key]['effort']),
                                                           attributes={"month": months[month_key]['month'],
                                                                       "year": months[month_key]['year']})
            for project_key in months[month_key]['project_efforts'].keys():
                main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                               "object/[@model='django<EMAIL>']",
                                                               "Month_Project_Work_Hours",
                                                               str(months[month_key]['project_efforts'][project_key]['effort']),
                                                               attributes={"month": months[month_key]['month'],
                                                                           "year": months[month_key]['year'],
                                                                           "project": months[month_key]['project_efforts'][project_key]['project']})
        return main_xml

    def create_pdf(self, template_set, printed_by, *args, **kwargs):
        return PDFExport.create_pdf(self, template_set, printed_by, *args, **kwargs)

    def get_template_set(self, template_set):
        return self.user.get_template_set(template_set)

    def get_fop_config_file(self, template_set):
        return self.user.get_fop_config_file(template_set)

    def get_xsl_file(self, template_set):
        return self.user.get_xsl_file(template_set)

    def resource_contribution_project(self, date_from, date_to):
        works = Work.objects.filter(human_resource=self,
                                    date__range=(date_from, date_to))
        projects = []
        for work in works:
            if work.task.project not in projects:
                projects.append(work.task.project)
        return projects


class HumanResourceAdminView(admin.ModelAdmin):
    list_display = ('id',
                    'user',
                    'resource_manager',
                    'resource_type')
    list_display_links = ('id',
                          'user')
    list_filter = ('user',)
    ordering = ('id',)
    search_fields = ('id',
                     'user')
    fieldsets = (
        (_('Basics'), {
            'fields': ('user',
                       'resource_manager',
                       'resource_type')
        }),
    )

    def create_work_report_pdf(self, request, queryset):
        from koalixcrm.crm.views.create_work_report import create_work_report

        return create_work_report(self, request, queryset)

    create_work_report_pdf.short_description = _("Work Report PDF")

    save_as = True
    actions = [create_work_report_pdf]
    inlines = [ResourcePriceInlineAdminView]
<file_sep>/koalixcrm/crm/views/user_is_not_human_resource.py
# -*- coding: utf-8 -*-

from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render
from django.template.context_processors import csrf
from django.contrib.admin import helpers
from django.contrib.admin.widgets import *
from koalixcrm.djangoUserExtension.exceptions import TooManyUserExtensionsAvailable


class ReportingPeriodMissingForm(forms.Form):
    NEXT_STEPS = (
        ('create_human_resource', 'Create Human Resource'),
        ('return_to_start', 'Return To Start'),
    )
    next_steps = forms.ChoiceField(required=True,
                                   widget=forms.Select,
                                   choices=NEXT_STEPS)


def user_is_not_human_resource(request):
    try:
        if request.POST.get('post'):
            if 'confirm_selection' in request.POST:
                reporting_period_missing_form = ReportingPeriodMissingForm(request.POST)
                if reporting_period_missing_form.is_valid():
                    if reporting_period_missing_form.cleaned_data['next_steps'] == 'return_to_start':
                        return HttpResponseRedirect('/admin/')
                    else:
                        return HttpResponseRedirect('/admin/crm/humanresource/add/')
        else:
            reporting_period_missing_form = ReportingPeriodMissingForm(initial={'next_steps': 'create_user_extension'})
        title = "User is not registered as Human Resource"
        description = "The operation you have selected requires that the currently active user is a registered " \
                      "as a Human Resource. "
        c = {'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
             'form': reporting_period_missing_form,
             'description': description,
             'title': title}
        c.update(csrf(request))
        return render(request, 'crm/admin/exception.html', c)
    except TooManyUserExtensionsAvailable:
        raise Http404


<file_sep>/koalixcrm/crm/contact/postal_address.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _
from koalixcrm.crm.const.country import *
from koalixcrm.crm.const.postaladdressprefix import *


class PostalAddress(models.Model):
    prefix = models.CharField(max_length=1,
                              choices=POSTALADDRESSPREFIX,
                              verbose_name=_("Prefix"), blank=True,
                              null=True)
    name = models.CharField(max_length=100,
                            verbose_name=_("Name"),
                            blank=True,
                            null=True)
    pre_name = models.CharField(max_length=100,
                                verbose_name=_("Pre-name"),
                                blank=True,
                                null=True)
    address_line_1 = models.CharField(max_length=200,
                                      verbose_name=_("Address line 1"),
                                      blank=True,
                                      null=True)
    address_line_2 = models.CharField(max_length=200,
                                      verbose_name=_("Address line 2"),
                                      blank=True,
                                      null=True)
    address_line_3 = models.CharField(max_length=200,
                                      verbose_name=_("Address line 3"),
                                      blank=True,
                                      null=True)
    address_line_4 = models.CharField(max_length=200,
                                      verbose_name=_("Address line 4"),
                                      blank=True,
                                      null=True)
    zip_code = models.IntegerField(verbose_name=_("Zip Code"),
                                   blank=True,
                                   null=True)
    town = models.CharField(max_length=100,
                            verbose_name=_("City"),
                            blank=True,
                            null=True)
    state = models.CharField(max_length=100,
                             verbose_name=_("State"),
                             blank=True,
                             null=True)
    country = models.CharField(max_length=2,
                               choices=[(x[0], x[3]) for x in COUNTRIES],
                               verbose_name=_("Country"),
                               blank=True,
                               null=True)

    class Meta:
        app_label = "crm"
        verbose_name = _('Postal Address')
        verbose_name_plural = _('Postal Address')
<file_sep>/koalixcrm/crm/tests/test_create_task.py
# -*- coding: utf-8 -*-

import pytest
from django.test import TestCase
from koalixcrm.crm.factories.factory_sales_document_position import StandardSalesDocumentPositionFactory
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.crm.factories.factory_quote import StandardQuoteFactory
from koalixcrm.crm.factories.factory_user import StaffUserFactory
from koalixcrm.crm.factories.factory_product_type import StandardProductTypeFactory
from koalixcrm.crm.factories.factory_product_price import StandardPriceFactory
from koalixcrm.crm.models import Task
from koalixcrm.crm.views.create_task import CreateTaskView


class DocumentCalculationsTest(TestCase):
    def setUp(self):
        self.test_currency = StandardCurrencyFactory.create()
        self.test_quote = StandardQuoteFactory.create()
        self.test_user = StaffUserFactory.create()
        for i in range(10):
            test_product = StandardProductTypeFactory.create(
                description="This is a test product " + i.__str__(),
                title="This is a test product " + i.__str__(),
                product_type_identifier=12334235+i,
            )
            StandardPriceFactory.create(
                product_type=test_product,
            )
            StandardSalesDocumentPositionFactory.create(
                sales_document=self.test_quote,
                position_number=i*10,
                quantity=0.333*i,
                description="This is a test position " + i.__str__(),
                discount=i*5
            )

    @pytest.mark.back_end_tests
    def test_create_task(self):
        project = CreateTaskView.create_project_from_document(self.test_user, self.test_quote)
        tasks = Task.objects.filter(project=project.id)
        task_counter = 0
        for task_current in tasks:
            self.assertEqual(
                task_current.title.__str__()[:24], "This is a test position ")
            task_counter += 1
        self.assertEqual(
            task_counter.__str__(), "10")

<file_sep>/koalixcrm/crm/factories/factory_sales_document.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import SalesDocument
from koalixcrm.crm.factories.factory_user import StaffUserFactory
from koalixcrm.crm.factories.factory_contract import StandardContractFactory
from koalixcrm.crm.factories.factory_customer import StandardCustomerFactory
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.djangoUserExtension.factories.factory_document_template import StandardQuoteTemplateFactory


class StandardSalesDocumentFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = SalesDocument

    contract = factory.SubFactory(StandardContractFactory)
    external_reference = "This is an external Reference"
    discount = "0"
    description = "This is the description of a sales document"
    last_pricing_date = "2018-05-01"
    last_calculated_price = "220.00"
    last_calculated_tax = "10.00"
    customer = factory.SubFactory(StandardCustomerFactory)
    staff = factory.SubFactory(StaffUserFactory)
    currency = factory.SubFactory(StandardCurrencyFactory)
    date_of_creation = "2018-05-01"
    custom_date_field = "2018-05-20"
    last_modification = "2018-05-25"
    last_modified_by = factory.SubFactory(StaffUserFactory)
    template_set = factory.SubFactory(StandardQuoteTemplateFactory)
    derived_from_sales_document = None
    last_print_date = "2018-05-26"

<file_sep>/koalixcrm/crm/factories/factory_project_status.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import ProjectStatus


class DoneProjectStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProjectStatus

    title = "Done"
    description = "This project is done"
    is_done = True


class StartedProjectStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProjectStatus

    title = "Started"
    description = "This project has started"
    is_done = False
<file_sep>/koalixcrm/crm/factories/factory_quote.py
# -*- coding: utf-8 -*-

from koalixcrm.crm.models import Quote
from koalixcrm.crm.factories.factory_sales_document import StandardSalesDocumentFactory


class StandardQuoteFactory(StandardSalesDocumentFactory):
    class Meta:
        model = Quote

    valid_until = "2018-05-20"
    status = "C"
<file_sep>/koalixcrm/crm/product/customer_group_transform.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _


class CustomerGroupTransform(models.Model):
    from_customer_group = models.ForeignKey('CustomerGroup',
                                            verbose_name=_("From Customer Group"),
                                            related_name="db_reltransfromfromcustomergroup",
                                            blank=False,
                                            null=False)
    to_customer_group = models.ForeignKey('CustomerGroup',
                                          verbose_name=_("To Customer Group"),
                                          related_name="db_reltransfromtocustomergroup",
                                          blank=False,
                                          null=False)
    product_type = models.ForeignKey('ProductType',
                                     verbose_name=_("Product Type"),
                                     blank=False,
                                     null=False)
    factor = models.DecimalField(verbose_name=_("Factor between From and To Customer Group"),
                                 blank=False,
                                 null=False,
                                 max_digits=17,
                                 decimal_places=2,)

    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 get_transform_factor(self):
        return self.factor

    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')


class CustomerGroupTransformInlineAdminView(admin.TabularInline):
    model = CustomerGroupTransform
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('', {
            'fields': ('from_customer_group',
                       'to_customer_group',
                       'factor',)
        }),
    )
    allow_add = True
<file_sep>/koalixcrm/crm/factories/factory_user.py
# -*- coding: utf-8 -*-

import factory
from django.contrib.auth.models import User


class StaffUserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
        django_get_or_create = ('username',)

    username = 'staff_user'
    email = '<EMAIL>'

    password = factory.PostGenerationMethodCall('set_password', '<PASSWORD>')

    is_superuser = False
    is_staff = True
    is_active = True


class AdminUserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
        django_get_or_create = ('username',)

    username = 'admin'
    email = '<EMAIL>'

    password = factory.PostGenerationMethodCall('set_password', '<PASSWORD>')

    is_superuser = True
    is_staff = True
    is_active = True


class StandardUserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
        django_get_or_create = ('username',)

    username = 'standard'
    email = '<EMAIL>'

    password = factory.PostGenerationMethodCall('set_password', '<PASSWORD>')

    is_superuser = False
    is_staff = False
    is_active = True
<file_sep>/koalixcrm/crm/tests/test_project_planned_costs.py
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.factories.factory_task import StandardTaskFactory
from koalixcrm.crm.factories.factory_estimation import StandardEstimationToTaskFactory
from koalixcrm.crm.factories.factory_human_resource import StandardHumanResourceFactory
from koalixcrm.crm.factories.factory_resource_price import StandardResourcePriceFactory
from koalixcrm.crm.factories.factory_project import StandardProjectFactory
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory
from koalixcrm.crm.factories.factory_estimation import StandardHumanResourceEstimationToTaskFactory
from koalixcrm.test_support_functions import make_date_utc


class TaskPlannedEffort(TestCase):
    def setUp(self):

        self.test_billing_cycle = StandardCustomerBillingCycleFactory.create()
        self.test_user = AdminUserFactory.create()
        self.test_customer_group = StandardCustomerGroupFactory.create()
        self.test_unit = StandardUnitFactory.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_project = StandardProjectFactory.create()
        self.test_human_resource = StandardHumanResourceFactory.create()

    @pytest.mark.back_end_tests
    def test_project_planned_costs(self):
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        start_date = (datetime_now - datetime.timedelta(days=30)).date()
        end_date_first_task = (datetime_now + datetime.timedelta(days=30)).date()
        end_date_second_task = (datetime_now + datetime.timedelta(days=60)).date()
        self.assertEqual(
            (self.test_project.planned_costs()).__str__(), "0")
        self.test_reporting_period = StandardReportingPeriodFactory.create(
            project=self.test_project
        )
        self.assertEqual(
            (self.test_project.planned_costs()).__str__(), "0")
        self.test_1st_task = StandardTaskFactory.create(title="1st Test Task",
                                                        project=self.test_project)
        self.test_2nd_task = StandardTaskFactory.create(title="2nd Test Task",
                                                        project=self.test_project)
        self.assertEqual(
            (self.test_project.planned_costs()).__str__(), "0")
        self.estimation_1st_task = StandardHumanResourceEstimationToTaskFactory(task=self.test_1st_task,
                                                                                date_from=start_date,
                                                                                date_until=end_date_first_task,
                                                                                amount=0)
        self.estimation_2nd_task = StandardHumanResourceEstimationToTaskFactory(task=self.test_2nd_task,
                                                                                date_from=start_date,
                                                                                date_until=end_date_second_task,
                                                                                amount=0)
        self.resource_price = StandardResourcePriceFactory.create(
            resource=self.test_human_resource,
            unit=self.test_unit,
            currency=self.test_currency,
            customer_group=self.test_customer_group,
            price="120",
        )
        self.assertEqual(
            (self.test_project.planned_costs()).__str__(), "0")
        StandardEstimationToTaskFactory.create(resource=self.test_human_resource,
                                               amount="2.00",
                                               task=self.test_1st_task,
                                               reporting_period=self.test_reporting_period)
        StandardEstimationToTaskFactory.create(resource=self.test_human_resource,
                                               amount="1.50",
                                               task=self.test_1st_task,
                                               reporting_period=self.test_reporting_period)
        StandardEstimationToTaskFactory.create(resource=self.test_human_resource,
                                               amount="4.75",
                                               task=self.test_2nd_task,
                                               reporting_period=self.test_reporting_period)
        StandardEstimationToTaskFactory.create(resource=self.test_human_resource,
                                               amount="3.25",
                                               task=self.test_2nd_task,
                                               reporting_period=self.test_reporting_period)
        self.assertEqual(
            (self.test_project.planned_costs(reporting_period=self.test_reporting_period)).__str__(), "1380.0")
<file_sep>/koalixcrm/crm/reporting/urls.py
# coding: utf-8

# DJANGO IMPORTS
from django.conf.urls import url

from koalixcrm.crm.views.time_tracking import work_report
from koalixcrm.crm.views.user_extension_missing import user_extension_missing
from koalixcrm.crm.views.reporting_period_missing import reporting_period_missing
from koalixcrm.crm.views.user_is_not_human_resource import user_is_not_human_resource

urlpatterns = [
    url(r'^time_tracking/$', work_report, name="monthly_report"),
    url(r'^user_extension_missing/$', user_extension_missing, name="user_extension_missing"),
    url(r'^reporting_period_missing/$', reporting_period_missing, name="reporting_period_missing"),
    url(r'^user_is_not_human_resource/$', user_is_not_human_resource, name="user_is_not_human_resource"),
]<file_sep>/CONTRIBUTING.md
# Contributing

When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change. 

Please note we have a code of conduct, please follow it in all your interactions with the project.

## Pull Request Process

1. Ensure any install or build dependencies are removed before the end of the layer when doing a 
   build.
2. Update the README.md with details of changes to the interface, this includes new environment 
   variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in all version.py files to the new version that this
   Pull Request would represent. The versioning scheme (see next section)
4. Crate a Pull Request towards the "development" branch, ensure that your pull-request 
   has at least 80% test-coverage and the Pull Request does not show issues in Travis 
5. Once the Pull Request is reviewed it will be merged by one of the core developers

## Versioning scheme
- 1.2.0.dev1  - Development release
- 1.2.0a1     - Alpha Release
- 1.2.0b1     - Beta Release
- 1.2.0rc1    - Release Candidate
- 1.2.0       - Final Release
- 1.2.0.post1 - Post Release
<file_sep>/koalixcrm/crm/migrations/0006_auto_20171210_1805.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-12-10 18:05
from __future__ import unicode_literals

import django.core.validators
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0003_auto_20171110_1732'),
        ('crm', '0005_auto_20171110_1732'),
    ]

    operations = [
        migrations.CreateModel(
            name='DeliveryNote',
            fields=[
                ('salescontract_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.SalesContract')),
                ('status', models.CharField(choices=[('C', 'Created'), ('S', 'Sent'), ('R', 'Received'), ('R', 'Lost')], max_length=1)),
                ('derived_from_invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Invoice')),
            ],
            options={
                'verbose_name_plural': 'Delivery Notes',
                'verbose_name': 'Delivery Note',
            },
            bases=('crm.salescontract',),
        ),
        migrations.CreateModel(
            name='PaymentReminder',
            fields=[
                ('salescontract_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.SalesContract')),
                ('iteration_number', models.IntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(3)], verbose_name='Iteration Number')),
                ('status', models.CharField(choices=[('P', 'Payed'), ('C', 'Invoice created'), ('I', 'Invoice sent'), ('F', 'First reminder sent'), ('R', 'Second reminder sent'), ('U', 'Customer cant pay'), ('D', 'Deleted')], max_length=1)),
                ('derived_from_invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Invoice')),
            ],
            options={
                'verbose_name_plural': 'Payment Reminders',
                'verbose_name': 'Payment Reminder',
            },
            bases=('crm.salescontract',),
        ),
        migrations.CreateModel(
            name='PurchaseConfirmation',
            fields=[
                ('salescontract_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.SalesContract')),
                ('validuntil', models.DateField(verbose_name='Valid until')),
                ('status', models.CharField(choices=[('S', 'Success'), ('I', 'Quote created'), ('Q', 'Quote sent'), ('F', 'First reminder sent'), ('R', 'Second reminder sent'), ('D', 'Deleted')], max_length=1, verbose_name='Status')),
            ],
            options={
                'verbose_name_plural': 'Purchase Confirmations',
                'verbose_name': 'Purchase Confirmation',
            },
            bases=('crm.salescontract',),
        ),
        migrations.CreateModel(
            name='TextParagraphInSalesContract',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('purpose', models.CharField(choices=[('BS', 'Before subject'), ('AS', 'After subject'), ('BT', 'Before total'), ('AT', 'After total'), ('BW', 'Before wishes'), ('AW', 'After wishes'), ('C1', 'Custom 1'), ('C2', 'Custom 2'), ('C3', 'Custom 3'), ('C4', 'Custom 4')], max_length=1, verbose_name='Purpose')),
                ('text_paragraph', models.TextField(verbose_name='Text')),
            ],
            options={
                'verbose_name_plural': 'TextParagraphsInSalesContract',
                'verbose_name': 'TextParagraphInSalesContract',
            },
        ),
        migrations.AlterModelOptions(
            name='supplier',
            options={'verbose_name': 'Supplier', 'verbose_name_plural': 'Suppliers'},
        ),
        migrations.RemoveField(
            model_name='position',
            name='shipmentID',
        ),
        migrations.RemoveField(
            model_name='position',
            name='supplier',
        ),
        migrations.AddField(
            model_name='salescontract',
            name='template_set',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.TemplateSet', verbose_name='Referred Template Set'),
        ),
        migrations.AddField(
            model_name='textparagraphinsalescontract',
            name='SalesContract',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.SalesContract'),
        ),
    ]
<file_sep>/koalixcrm/crm/migrations/0033_auto_20180612_1936.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-12 19:36
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0032_auto_20180612_1925'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='projectstatus',
            options={'verbose_name': 'Project Status', 'verbose_name_plural': 'Project Status'},
        ),
        migrations.AlterField(
            model_name='task',
            name='project',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Project', verbose_name='Project'),
        ),
    ]
<file_sep>/documentation/source/accounting.rst
.. highlight:: rst

Accounting
============

Accouting Periods
-------------------------

An Accounting Period is the timeframe over which you make your Profit and Loss calculation.
Its often called Fiscal Year, Business Year or Business Quarter. What ever you like.
Accounting Periods have nothing to do with the Accounts but with the Bookings.
When you open an existing Accounting Period you find a list of all
Bookings during the period below as an inline table on the page.
Feel free to add your bookings here. Be careful when you set up a new
Accounting Period -  koalixcrm does not check if you create bookings that are outside the
possible Accounting Period range nor does it check if there are two accounting periods
for the same time period. It is up to the user to check that.


Account section
---------------

Is a Customer Payment Account
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

These are the accounts you are able to select when you choose "register payment"
in your invoice detail view as destinations for Customers to pay into.

Is the Open Liabilities Account
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This is not implemented yet because the link between Accounting and CRM
is not yet finised for the purchase order side
(where you were buying not selling).
This is usually not used by companies because its to much effort to copy
all the invoices they receive from suppliers into your system.
Normally you will only book this manually and store the invoice
you got from your supplier physically in a filer in your office.
If you do so think about using a good description so that you find
the invoice in case of a tax audit (I have never had a tax audit in my
company but I think they are really interested in such things :-) ).

Is the Open Intrest Account
^^^^^^^^^^^^^^^^^^^^^^^^^^^

There should only be one Open Intrest Account in your Accounting
and it is used to book invoiced contract amounts. You will have to set
this open intrest account. If this is not set on at least one account
the link between Accounting and CRM will not work correctly.

Is a Product Inventory Account
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You may choose this option but it has no effect yet
because Product inventory is not yet implemented.


Bookings
--------

A booking is a common part of all accounting. Link to wikipage.

Product Categories
------------------

Product Categories are needed for the interface between the CRM and Accounting.
You can introduce different product Categories for different kinds of companies.
In this example, we have an "IT support company". An IT Support company usually
provides Support and Hardware. Support can be for on-site support or in-house support.
Its possible that the  support must be bought from an other company because
you were not able to do everything yourself. Then the Hardware, you normally
don't produce hardware on your own, you buy them from a supplier.

Now lets record that as the following products you may offer to your customers.

+-------------+---------------------+
| Product Nr. |  Description        |
+-------------+---------------------+
| 1           |  in-house work      |
| 2           |  on-site work       |
| 3           |  computer hardware  |
| 4           |  voip hardware      |
+-------------+---------------------+

To be able to do automatic accounting you will have to set the accounts where
you want to book your income and spending for each product.

+------------+------------------+------------------------------------+
|Product Nr. | Earnings Acc.    | Spendings Acc.                     |
+------------+------------------+------------------------------------+
|1.          | Income Support   | Spending Support from third        |
|2.          | Income Support   | Spending Support from third        |
|3.          | Income Hardware  | Spendings Hardware                 |
|4.          | Income Hardware  | Spendings Hardware                 |
+------------+------------------+------------------------------------+

Of course, there are more than four products in your inventory - I hope -
and depending on your "love of details" you will have hundreds of them.
Instead of setting the Earnings and Spendings Accounts for every Product
individually koalixcrm has useful product categories where you only
have to set it once. After that you are able to  link the product
categories when you add a product in your CRM. Its also possible to
leave this blank if you don't want to make the link between CRM and Accounting
<file_sep>/documentation/source/code_documentation.rst
Code Documentation
==================

.. toctree::
   :maxdepth: 2

   code_documentation/project
   code_documentation/task
   code_documentation/work
   code_documentation/estimation
   code_documentation/agreement
   code_documentation/resource

<file_sep>/koalixcrm/crm/reporting/agreement.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.crm.reporting.resource_price import ResourcePrice


class Agreement(models.Model):
    task = models.ForeignKey("Task",
                             verbose_name=_('Task'),
                             blank=False,
                             null=False)
    resource = models.ForeignKey("Resource")
    unit = models.ForeignKey("Unit")
    costs = models.ForeignKey(ResourcePrice)
    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):
        return 0

    def match_with_work(self, work):
        """This method checks whether the provided work can be covered by the agreement.
        the method checks whether the reported work corresponds with the resource and whether the
        reported work was within the time-frame of the agreement.
        The method returns False when the Agreement is not yet in status agreed

        Args:
          work (koalixcrm.crm.reporting.work.Work)

        Returns:
          True when no ValidationError was raised

        Raises:
          should not raise exceptions"""
        matches = False
        if self.status.is_agreed:
            if (work.date >= self.date_from) and (work.date <= self.date_until):
                if self.resource.id == work.human_resource.id:
                    matches = True
        return matches

    def __str__(self):
        return _("Agreement of Resource Consumption") + ": " + str(self.id)

    class Meta:
        app_label = "crm"
        verbose_name = _('Agreement')
        verbose_name_plural = _('Agreements')


class AgreementInlineAdminView(admin.TabularInline):
    model = Agreement
    fieldsets = (
        (_('Work'), {
            'fields': ('task',
                       'resource',
                       'amount',
                       'unit',
                       'costs',
                       'date_from',
                       'date_until',
                       'type',
                       'status')
        }),
    )
    extra = 1
<file_sep>/requirements/development_requirements.txt
-r base_requirements.txt
<file_sep>/koalixcrm/crm/product/product_price.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.crm.product.price import Price


class ProductPrice(Price):
    product_type = models.ForeignKey("ProductType",
                                     verbose_name=_("Product Type"))

    def __str__(self):
        return str(self.price) + " " + str(self.currency.short_name)

    class Meta:
        app_label = "crm"
        verbose_name = _('Product Price')
        verbose_name_plural = _('Product Prices')


class ProductPriceInlineAdminView(admin.TabularInline):
    model = ProductPrice
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('', {
            'fields': ('price',
                       'currency',
                       'unit',
                       'valid_from',
                       'valid_until',
                       'customer_group')
        }),
    )
    allow_add = True
<file_sep>/koalixcrm/crm/migrations/0007_auto_20171210_2126.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-12-10 21:26
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0004_auto_20171210_2126'),
        ('crm', '0006_auto_20171210_1805'),
    ]

    operations = [
        migrations.CreateModel(
            name='TextParagraphInDocumentTemplate',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('purpose', models.CharField(choices=[('BS', 'Before subject'), ('AS', 'After subject'), ('BT', 'Before total'), ('AT', 'After total'), ('BW', 'Before wishes'), ('AW', 'After wishes'), ('C1', 'Custom 1'), ('C2', 'Custom 2'), ('C3', 'Custom 3'), ('C4', 'Custom 4')], max_length=2, verbose_name='Purpose')),
                ('text_paragraph', models.TextField(verbose_name='Text')),
                ('document_template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'TextParagraphInDocumentTemplate',
                'verbose_name_plural': 'TextParagraphInDocumentTemplates',
            },
        ),
        migrations.AlterField(
            model_name='textparagraphinsalescontract',
            name='purpose',
            field=models.CharField(choices=[('BS', 'Before subject'), ('AS', 'After subject'), ('BT', 'Before total'), ('AT', 'After total'), ('BW', 'Before wishes'), ('AW', 'After wishes'), ('C1', 'Custom 1'), ('C2', 'Custom 2'), ('C3', 'Custom 3'), ('C4', 'Custom 4')], max_length=2, verbose_name='Purpose'),
        ),
    ]
<file_sep>/koalixcrm/crm/migrations/0013_auto_20180105_2159.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-05 21:59
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0004_auto_20171210_2126'),
        ('crm', '0012_auto_20180105_1840'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='purchaseconfirmation',
            name='status',
        ),
        migrations.RemoveField(
            model_name='purchaseconfirmation',
            name='valid_until',
        ),
        migrations.AddField(
            model_name='deliverynote',
            name='derived_from_quote',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Quote'),
        ),
        migrations.AddField(
            model_name='deliverynote',
            name='tracking_reference',
            field=models.CharField(blank=True, max_length=100, verbose_name='Tracking Reference'),
        ),
        migrations.AddField(
            model_name='paymentreminder',
            name='payable_until',
            field=models.DateField(default=django.utils.timezone.now, verbose_name='To pay until'),
            preserve_default=False,
        ),
        migrations.AddField(
            model_name='paymentreminder',
            name='payment_bank_reference',
            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Payment Bank Reference'),
        ),
        migrations.AddField(
            model_name='purchaseconfirmation',
            name='derived_from_quote',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Quote'),
        ),
        migrations.AddField(
            model_name='purchaseorder',
            name='template_set',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.DocumentTemplate', verbose_name='Referred Template'),
        ),
    ]
<file_sep>/koalixcrm/crm/views/renderer.py
# -*- coding: utf-8 -*-
"""
Provides XML rendering support.
"""

import os
from subprocess import check_output, STDOUT
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework_xml.renderers import XMLRenderer


class XSLFORenderer(TemplateHTMLRenderer):
    """
    Renderer which serializes to PDF using XSLT and FO
    """
    media_type_unused = 'application/pdf'
    media_type = 'plain/text'
    format = 'pdf'
    charset = 'utf-8'

    def get_template_names(self, response, view):
        """Override with xslt_names. This allows for you
        to still render HTML if you want
        """
        if hasattr(response, 'xslt_name'):
            return [response.latex_name]
        elif self.xslt_name:
            return [self.xslt_name]
        elif hasattr(view, 'get_xslt_names'):
            return view.get_xslt_names()
        elif hasattr(view, 'xslt_name'):
            return [view.xslt_name]
        raise ImproperlyConfigured(
            u'Returned a template response with no `xslt_name` attribute '
            u'set on either the view or response')

    def perform_xsl_transformation(self, file_with_serialized_xml, xsl_file, fop_config_file, file_output_pdf):
        check_output([settings.FOP_EXECUTABLE,
                      '-c', fop_config_file.path_full,
                      '-xml', os.path.join(settings.PDF_OUTPUT_ROOT, file_with_serialized_xml),
                      '-xsl', xsl_file.path_full,
                      '-pdf', file_output_pdf], stderr=STDOUT)

    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        First renders `data` into serialized XML, afterwards start fop and return PDF
        """
        if data is None:
            return ''

        super(XSLFORenderer, self).render(data, accepted_media_type, renderer_context)

        xml_renderer = XMLRenderer()
        xml_string = xml_renderer.render(data, accepted_media_type, renderer_context)
        xml_file = open(renderer_context['view'].file_name, "wb+")
        xml_file.truncate()
        xml_file.write(xml_string.encode('utf-8'))
        xml_file.close()
        # perform xsl transformation
        self.perform_xsl_transformation(xml_file,
                                        self.xsl_file,
                                        self.fop_config_file,
                                        self.file_output_pdf)

        # Read file
        with open(self.file_output_pdf, 'rb') as f:
            rendered_output = f.read()

        return rendered_output<file_sep>/koalixcrm/crm/migrations/0036_auto_20180626_2059.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-26 20:59
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0035_auto_20180619_1924'),
    ]

    operations = [
        migrations.CreateModel(
            name='ReportingPeriod',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=200, verbose_name='Title')),
                ('begin', models.DateField(verbose_name='Begin')),
                ('end', models.DateField(verbose_name='End')),
                ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Project', verbose_name='Project')),
            ],
            options={
                'verbose_name': 'Project',
                'verbose_name_plural': 'Projects',
            },
        ),
        migrations.AddField(
            model_name='work',
            name='reporting_period',
            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='crm.ReportingPeriod', verbose_name='Reporting Period'),
            preserve_default=False,
        ),
    ]
<file_sep>/koalixcrm/subscriptions/const/events.py
# -*- coding: utf-8 -*

from django.utils.translation import ugettext as _

SUBSCRITIONEVENTS = (
    ('O', _('Offered')),
    ('C', _('Canceled')),
    ('S', _('Signed')),
)
<file_sep>/koalixcrm/accounting/rest/account_rest.py
# -*- coding: utf-8 -*-
from rest_framework import serializers

from koalixcrm.accounting.accounting.account import Account


class OptionAccountJSONSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.IntegerField(required=False)
    accountNumber = serializers.IntegerField(source='account_number', read_only=True)
    title = serializers.CharField(read_only=True)

    class Meta:
        model = Account
        fields = ('id',
                  'accountNumber',
                  'title')


class AccountJSONSerializer(serializers.HyperlinkedModelSerializer):
    accountNumber = serializers.IntegerField(source='account_number', allow_null=False)
    accountType = serializers.CharField(source='account_type', allow_null=False)
    isOpenReliabilitiesAccount = serializers.BooleanField(source='is_open_reliabilities_account')
    isOpenInterestAccount = serializers.BooleanField(source='is_open_interest_account')
    isProductInventoryActiva = serializers.BooleanField(source='is_product_inventory_activa')
    isCustomerPaymentAccount = serializers.BooleanField(source='is_a_customer_payment_account')

    class Meta:
        model = Account
        fields = ('id',
                  'accountNumber',
                  'title',
                  'accountType',
                  'description',
                  'isOpenReliabilitiesAccount',
                  'isOpenInterestAccount',
                  'isProductInventoryActiva',
                  'isCustomerPaymentAccount')
        depth = 1
<file_sep>/koalixcrm/crm/migrations/0052_auto_20181014_2304.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-12 20:56
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


def reverse_func(apps, schema_editor):
    return 1


def restore_from_backup(apps, schema_editor):
    Position = apps.get_model("crm", "Position")
    ProductType = apps.get_model("crm", "ProductType")
    CustomerGroupTransform = apps.get_model("crm", "CustomerGroupTransform")
    Price = apps.get_model("crm", "Price")
    ProductPrice = apps.get_model("crm", "ProductPrice")
    UnitTransform = apps.get_model("crm", "UnitTransform")
    db_alias = schema_editor.connection.alias
    all_positions = Position.objects.using(db_alias).all()
    for position in all_positions:
        product_type = ProductType.objects.using(db_alias).get(id=position.product_backup)
        position.product_type = product_type
        position.save()
    all_customer_group_transforms = CustomerGroupTransform.objects.using(db_alias).all()
    for customer_group_transform in all_customer_group_transforms:
        product_type = ProductType.objects.using(db_alias).get(id=customer_group_transform.product_backup)
        customer_group_transform.product_type = product_type
        customer_group_transform.save()
    all_prices = Price.objects.using(db_alias).all()
    for price in all_prices:
        product_type = ProductType.objects.using(db_alias).get(id=price.product_backup)
        new_product_price = ProductPrice.objects.using(db_alias).create(unit=price.unit,
                                                                        currency=price.currency,
                                                                        customer_group=price.customer_group,
                                                                        price=price.price,
                                                                        valid_from=price.valid_from,
                                                                        valid_until=price.valid_until,
                                                                        product_type=product_type)
        new_product_price.save()
        price.delete()
    all_unit_transforms = UnitTransform.objects.using(db_alias).all()
    for unit_transform in all_unit_transforms:
        product_type = ProductType.objects.using(db_alias).get(id=unit_transform.product_backup)
        unit_transform.product_type = product_type
        unit_transform.save()


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0051_auto_20181014_2302'),
    ]

    operations = [
        migrations.RunPython(restore_from_backup, reverse_func),
    ]
<file_sep>/koalixcrm/crm/migrations/0032_auto_20180612_1925.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-12 19:25
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('djangoUserExtension', '0005_auto_20180612_1924'),
        ('crm', '0031_auto_20180612_1924'),
    ]

    operations = [
        migrations.CreateModel(
            name='Project',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('project_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Project name')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Description')),
                ('date_of_creation', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
                ('last_modification', models.DateTimeField(auto_now=True, verbose_name='Last modified')),
                ('default_template_set', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.TemplateSet', verbose_name='Default Template Set')),
                ('last_modified_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_project_last_modified', to=settings.AUTH_USER_MODEL, verbose_name='Last modified by')),
                ('project_manager', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_rel_project_staff', to=settings.AUTH_USER_MODEL, verbose_name='Staff')),
            ],
            options={
                'verbose_name': 'Project',
                'verbose_name_plural': 'Projects',
            },
        ),
        migrations.CreateModel(
            name='ProjectLinkType',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=300, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
            ],
            options={
                'verbose_name': 'Project Link Type',
                'verbose_name_plural': 'Project Link Type',
            },
        ),
        migrations.CreateModel(
            name='ProjectStatus',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=250, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
                ('is_done', models.BooleanField(verbose_name='Status represents project is done')),
            ],
            options={
                'verbose_name': 'Task Status',
                'verbose_name_plural': 'Task Status',
            },
        ),
        migrations.AddField(
            model_name='project',
            name='project_status',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.ProjectStatus', verbose_name='Project Status'),
        ),
    ]
<file_sep>/koalixcrm/crm/management/commands/koalixcrm_install_defaulttemplates.py
# -*- coding: utf-8 -*-

from os import path

from koalixcrm import crm
from koalixcrm import djangoUserExtension
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from filebrowser.base import FileObject
from django.utils.translation import ugettext as _
from django.conf import settings

DEFAULT_FILE = 'dashboard.py'


class Command(BaseCommand):
    help = (
    'This Command is going to install the default Templates, given by the koalixcrm base installation, in your django instance. Be sure you first run syncdb')
    args = '[]'
    label = 'application name'

    @staticmethod
    def store_default_template_xsl_file(language, file_name):
        file_path = Command.path_of_default_template_file(language, file_name)
        xsl_file = Command.store_xsl_file(file_path)
        return xsl_file

    @staticmethod
    def path_of_default_template_file(language, file_name):
        file_path = path.join(settings.STATIC_ROOT, "default_templates", language, file_name)
        f = None;
        try:
            f = open(file_path,'r')
        except (FileNotFoundError) as e:
            print(_("File not found:") + file_path)
            print(_("Run collectstatic command and fix potential errors"))
        finally:
            if f is not None:
                f.close()
        return file_path

    @staticmethod
    def store_xsl_file(xsl_file_path):
        xsl_file = djangoUserExtension.models.XSLFile()
        xsl_file.title = path.basename(xsl_file_path)
        xsl_file.xslfile = FileObject(xsl_file_path)
        xsl_file.save()
        return xsl_file

    def handle(self, *args, **options):
        template_set = djangoUserExtension.models.TemplateSet()
        template_set.title = 'default_template_set'
        template_set.invoiceXSLFile = Command.store_default_template_xsl_file("en", "invoice.xsl")
        template_set.quoteXSLFile = Command.store_default_template_xsl_file("en", "quote.xsl")
        template_set.purchaseconfirmationXSLFile = Command.store_default_template_xsl_file("en", "purchaseconfirmation.xsl")
        template_set.purchaseorderXSLFile = Command.store_default_template_xsl_file("en", "purchaseorder.xsl")
        template_set.deilveryorderXSLFile = Command.store_default_template_xsl_file("en", "deliveryorder.xsl")

        if 'koalixcrm.accounting' in settings.INSTALLED_APPS:
            template_set.profitLossStatementXSLFile = Command.store_default_template_xsl_file("en", "profitlossstatement.xsl")
            template_set.balancesheetXSLFile = Command.store_default_template_xsl_file("en", "balancesheet.xsl")

        template_set.logo = FileObject(Command.path_of_default_template_file("generic", "logo.jpg"))
        template_set.fopConfigurationFile = FileObject(Command.path_of_default_template_file("generic", "fontconfig.xml"))
        template_set.bankingaccountref = "xx-xxxxxx-x"
        template_set.addresser = _("<NAME>, Sample Company, 8976 Smallville")
        template_set.headerTextsalesorders = _(
            "According to your wishes the contract consists of the following positions:")
        template_set.footerTextsalesorders = _("Thank you for your interest in our company \n Best regards")
        template_set.headerTextpurchaseorders = _("We would like to order the following positions:")
        template_set.footerTextpurchaseorders = _("Best regards")
        template_set.pagefooterleft = _("Sample Company")
        template_set.pagefootermiddle = _("Sample Address")
        template_set.save()
        currency = crm.models.Currency()
        currency.description = "US Dollar"
        currency.shortName = "USD"
        currency.rounding = "0.10"
        currency.save()
        user_extension = djangoUserExtension.models.UserExtension()
        user_extension.defaultTemplateSet = template_set
        user_extension.defaultCurrency = currency
        user_extension.user = User.objects.all()[0]
        user_extension.save()
        postaladdress = djangoUserExtension.models.UserExtensionPostalAddress()
        postaladdress.purpose = 'H'
        postaladdress.name = "John"
        postaladdress.prename = "Smith"
        postaladdress.addressline1 = "Ave 1"
        postaladdress.zipcode = 899887
        postaladdress.town = "Smallville"
        postaladdress.userExtension = user_extension
        postaladdress.save()
        phoneaddress = djangoUserExtension.models.UserExtensionPhoneAddress()
        phoneaddress.phone = "1293847"
        phoneaddress.purpose = 'H'
        phoneaddress.userExtension = user_extension
        phoneaddress.save()
        emailaddress = djangoUserExtension.models.UserExtensionEmailAddress()
        emailaddress.email = "<EMAIL>"
        emailaddress.purpose = 'H'
        emailaddress.userExtension = user_extension
        emailaddress.save()
<file_sep>/koalixcrm/accounting/rest/accounting_period_rest.py
# -*- coding: utf-8 -*-

from rest_framework import serializers

from koalixcrm.accounting.accounting.accounting_period import AccountingPeriod
from koalixcrm.accounting.models import Account


class OptionAccountingPeriodJSONSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.IntegerField(required=False)
    title = serializers.CharField(read_only=True)

    class Meta:
        model = Account
        fields = ('id',
                  'title')


class AccountingPeriodJSONSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = AccountingPeriod
        fields = ('id',
                  'title',
                  'begin',
                  'end')
        depth = 1
<file_sep>/koalixcrm/subscriptions/migrations/0005_auto_20181013_2228.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-13 22:28
from __future__ import unicode_literals

from django.db import migrations, models
from filebrowser.fields import FileBrowseField
import django.db.models.deletion


def fill_subscription_type_from_backup(apps, schema_editor):
    Subscription = apps.get_model("subscriptions", "Subscription")
    SubscriptionType = apps.get_model("subscriptions", "SubscriptionType")
    SubscriptionTypeBackup = apps.get_model("subscriptions", "SubscriptionTypeBackup")
    ProductType = apps.get_model("crm", "ProductType")
    db_alias = schema_editor.connection.alias
    all_subscription_type_backups = SubscriptionTypeBackup.objects.using(db_alias).all()
    for subscription_type_backup in all_subscription_type_backups:
        product_type = ProductType.objects.using(db_alias).create(
            description=subscription_type_backup.description,
            title=subscription_type_backup.title,
            default_unit=subscription_type_backup.default_unit,
            date_of_creation=subscription_type_backup.date_of_creation,
            last_modification=subscription_type_backup.last_modification,
            last_modified_by=subscription_type_backup.last_modified_by,
            tax=subscription_type_backup.tax)
        product_type.save()

        subscription_type = SubscriptionType.objects.using(db_alias).create(
            product_type=product_type,
            contract_document=subscription_type_backup.contract_document,
            payment_interval=subscription_type_backup.payment_interval,
            minimum_duration=subscription_type_backup.minimum_duration,
            automatic_contract_extension_reminder=subscription_type_backup.automatic_contract_extension_reminder,
            automatic_contract_extension=subscription_type_backup.automatic_contract_extension,
            cancellation_period=subscription_type_backup.cancellation_period)
        subscription_type.save()

        all_subscriptions = Subscription.objects.using(db_alias).filter(
            subscription_type_backup=subscription_type_backup.old_id)
        for subscription in all_subscriptions:
            subscription.subscription_type=subscription_type
            subscription.save()

def reverse_func(apps, schema_editor):
    return 1


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0053_auto_20181014_2305'),
        ('subscriptions', '0004_auto_20181013_2213'),
    ]

    operations = [
        migrations.CreateModel(
            name='SubscriptionType',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('cancellation_period', models.IntegerField(verbose_name="Cancellation Period (months)",
                                                            blank=True,
                                                            null=True)),
                ('automatic_contract_extension', models.IntegerField(
                    verbose_name="Automatic Contract Extension (months)",
                    blank=True,
                    null=True)),
                ('automatic_contract_extension_reminder', models.IntegerField(
                    verbose_name="Automatic Contract Extension Reminder (days)",
                    blank=True,
                    null=True)),
                ('minimum_duration', models.IntegerField(verbose_name="Minimum Contract Duration",
                                                         blank=True,
                                                         null=True)),
                ('payment_interval', models.IntegerField(verbose_name="Payment Interval (days)",
                                                         blank=True,
                                                         null=True)),
                ('contract_document', FileBrowseField(verbose_name="Contract Documents",
                                                      blank=True,
                                                      null=True,
                                                      max_length=200)),
                ('product_type', models.ForeignKey('crm.ProductType',
                                                   verbose_name='Product Type',
                                                   on_delete=models.deletion.SET_NULL,
                                                   null=True,
                                                   blank=True)),
             ],
            options={
                'verbose_name': 'Subscription Type',
                'verbose_name_plural': 'Subscription Types',
            },
        ),
        migrations.AddField(
            model_name='subscription',
            name='subscription_type',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
                                    to='SubscriptionType',
                                    verbose_name='Subscription Type'),
            preserve_default=False,
        ),
        migrations.RunPython(fill_subscription_type_from_backup, reverse_func),
        migrations.AlterField(
            model_name='subscription',
            name='subscription_type',
            field=models.ForeignKey(null=True,
                                    on_delete=django.db.models.deletion.CASCADE,
                                    to='subscriptions.SubscriptionType',
                                    verbose_name='Subscription Type'),

        ),
        migrations.DeleteModel('SubscriptionTypeBackup'),
        migrations.RemoveField(
            model_name='subscription',
            name='subscription_type_backup',
        ),
    ]
<file_sep>/koalixcrm/crm/views/create_task.py
# -*- coding: utf-8 -*-
from django.http import Http404
from django.http import HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext as _
from django.contrib.contenttypes.models import ContentType
from koalixcrm.crm.exceptions import *
from koalixcrm.djangoUserExtension.exceptions import *
from koalixcrm.crm.documents.sales_document import SalesDocument
from koalixcrm.crm.documents.sales_document_position import SalesDocumentPosition
from koalixcrm.crm.reporting.task import Task
from koalixcrm.crm.reporting.generic_task_link import GenericTaskLink
from koalixcrm.crm.reporting.project import Project
from koalixcrm.global_support_functions import *
from datetime import date


class CreateTaskView:

    @staticmethod
    def create_task_from_sales_document_position(sales_document_position,
                                                 user,
                                                 document,
                                                 project):
        date_now = date.today()
        content_type_sales_document_position = ContentType.objects.get_for_model(SalesDocumentPosition)
        task_title = limit_string_length(sales_document_position.description, 30)
        try:
            existing_task = GenericTaskLink.objects.get(content_type=content_type_sales_document_position,
                                                        object_id=sales_document_position.id)
            task_id = existing_task.task.id
            task = Task.objects.filter(id=task_id).update(
                title=task_title,
                planned_start_date=date_now,
                project=project,
                description=sales_document_position.description,
                last_status_change=date_now
            )
        except ObjectDoesNotExist:
            task = Task.objects.create(
                title=task_title,
                project=project,
                description=sales_document_position.description,
                last_status_change=date_now
            )
            GenericTaskLink.objects.create(
                task=task,
                content_type=content_type_sales_document_position,
                object_id=sales_document_position.id,
                last_modified_by=user
            )
            GenericTaskLink.objects.create(
                    task=task,
                    content_type=ContentType.objects.get_for_model(SalesDocument),
                    object_id=document.id,
                    last_modified_by=user
                )
        return task

    @staticmethod
    def create_project_from_document(user, document):
        sales_document_positions = SalesDocumentPosition.objects.filter(sales_document=document)
        project_name = limit_string_length(document.contract.description, 30)
        project = Project.objects.create(project_manager=user,
                                         project_name=project_name,
                                         description=document.contract.description,
                                         default_template_set=document.contract.default_template_set,
                                         date_of_creation=date.today(),
                                         last_modification=date.today(),
                                         last_modified_by=user,
                                         default_currency=document.currency)
        for sales_document_position in sales_document_positions:
            CreateTaskView.create_task_from_sales_document_position(sales_document_position,
                                                                    user,
                                                                    document,
                                                                    project)
        return project

    @staticmethod
    def create_project(calling_model_admin, request, document, redirect_to):
        """This method creates tasks from the positions of a sales document

            Args:
              calling_model_admin (ModelAdmin):  The calling ModelAdmin must be provided for error message response.
              request: The request User is to know where to save the error message
              document (SalesDocument):  The model from which a tasks shall be created
              redirect_to (str): String that describes to where the method should redirect in case of an error

            Returns:
              HTTpResponse with a PDF when successful
              HTTpResponseRedirect when not successful

            Raises:
              raises Http404 exception if anything goes wrong"""
        try:
            project = CreateTaskView.create_project_from_document(request.user, document)
            calling_model_admin.message_user(request, _("Successfully created Project and Tasks for this contract"))
            response = HttpResponseRedirect('/admin/crm/'+
                                            project.__class__.__name__.lower()+
                                            '/'+
                                            str(project.id))
        except (TemplateSetMissing,
                UserExtensionMissing,
                UserExtensionEmailAddressMissing,
                UserExtensionPhoneAddressMissing) as e:
            if isinstance(e, UserExtensionMissing):
                return render(request, 'crm/admin/exception.html')
            elif isinstance(e, UserExtensionEmailAddressMissing):
                return render(request, 'crm/admin/exception.html')
            else:
                raise Http404
        return response
<file_sep>/koalixcrm/crm/migrations/0042_auto_20180724_1816.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-07-24 18:16
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0041_auto_20180724_1657'),
    ]

    operations = [
        migrations.AlterField(
            model_name='unit',
            name='fraction_factor_to_next_higher_unit',
            field=models.DecimalField(blank=True, decimal_places=10, max_digits=20, null=True, verbose_name='Factor Between This And Next Higher Unit'),
        ),
    ]
<file_sep>/koalixcrm/djangoUserExtension/migrations/0006_auto_20180701_2128.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-07-01 21:28
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0005_auto_20180612_1924'),
    ]

    operations = [
        migrations.CreateModel(
            name='WorkReportTemplate',
            fields=[
                ('documenttemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='djangoUserExtension.DocumentTemplate')),
            ],
            options={
                'verbose_name': 'Work report template',
                'verbose_name_plural': 'Work report templates',
            },
            bases=('djangoUserExtension.documenttemplate',),
        ),
        migrations.AlterModelOptions(
            name='templateset',
            options={'verbose_name': 'Template-set', 'verbose_name_plural': 'Template-sets'},
        ),
        migrations.AlterModelOptions(
            name='userextension',
            options={'verbose_name': 'User Extension', 'verbose_name_plural': 'User Extension'},
        ),
        migrations.AlterModelOptions(
            name='userextensionemailaddress',
            options={'verbose_name': 'Email Address for User Extension', 'verbose_name_plural': 'Email Address for User Extension'},
        ),
        migrations.AlterModelOptions(
            name='userextensionphoneaddress',
            options={'verbose_name': 'Phone number for User Extension', 'verbose_name_plural': 'Phone number for User Extension'},
        ),
        migrations.AlterModelOptions(
            name='userextensionpostaladdress',
            options={'verbose_name': 'Postal Address for User Extension', 'verbose_name_plural': 'Postal Address for User Extension'},
        ),
        migrations.AddField(
            model_name='templateset',
            name='work_report_template',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.WorkReportTemplate'),
        ),
    ]
<file_sep>/koalixcrm/crm/contact/supplier.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.crm.contact.contact import Contact
from koalixcrm.crm.contact.contact import ContactPostalAddress
from koalixcrm.crm.contact.contact import ContactPhoneAddress
from koalixcrm.crm.contact.contact import ContactEmailAddress


class Supplier(Contact):
    offersShipmentToCustomers = models.BooleanField(verbose_name=_("Offers Shipment to Customer"))

    class Meta:
        app_label = "crm"
        verbose_name = _('Supplier')
        verbose_name_plural = _('Suppliers')

    def __str__(self):
        return str(self.id) + ' ' + self.name


class OptionSupplier(admin.ModelAdmin):
    list_display = ('id', 'name', 'offersShipmentToCustomers')
    fieldsets = (('', {'fields': ('name', 'offersShipmentToCustomers')}),)
    inlines = [ContactPostalAddress, ContactPhoneAddress, ContactEmailAddress]
    allow_add = True

    def save_model(self, request, obj, form, change):
        if (change == True):
            obj.lastmodifiedby = request.user
        else:
            obj.lastmodifiedby = request.user
            obj.staff = request.user
        obj.save()<file_sep>/koalixcrm/crm/factories/factory_sales_document_position.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import SalesDocumentPosition
from koalixcrm.crm.factories.factory_product_type import StandardProductTypeFactory
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory


class StandardSalesDocumentPositionFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = SalesDocumentPosition

    position_number = 5
    quantity = 5
    description = "This is a test sales document position"
    discount = 10
    product_type = factory.SubFactory(StandardProductTypeFactory)
    unit = factory.SubFactory(StandardUnitFactory)
    overwrite_product_price = True
    position_price_per_unit = 155

<file_sep>/koalixcrm/accounting/migrations/0009_auto_20181012_2056.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-12 20:56
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0053_auto_20181014_2305'),
        ('accounting', '0008_auto_20181012_2056'),
    ]

    operations = [
        migrations.DeleteModel(
            name='ProductCategorie',
        ),
        migrations.AddField(
            model_name='productcategory',
            name='loss_account',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_loss_account', to='accounting.Account', verbose_name='Loss Account'),
        ),
        migrations.AddField(
            model_name='productcategory',
            name='profit_account',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_profit_account', to='accounting.Account', verbose_name='Profit Account'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_agreement.py
# -*- coding: utf-8 -*-

import factory
import datetime
from koalixcrm.crm.models import Agreement
from koalixcrm.crm.factories.factory_resource import StandardResourceFactory
from koalixcrm.crm.factories.factory_human_resource import StandardHumanResourceFactory
from koalixcrm.crm.factories.factory_task import StandardTaskFactory
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory
from koalixcrm.crm.factories.factory_resource_price import StandardResourcePriceFactory
from koalixcrm.crm.factories.factory_agreement_type import StandardAgreementTypeFactory
from koalixcrm.crm.factories.factory_agreement_status import AgreedAgreementStatusFactory
from koalixcrm.test_support_functions import make_date_utc


class StandardAgreementToTaskFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Agreement

    date_from = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    date_until = make_date_utc(datetime.datetime(2024, 6, 15, 00))
    amount = "112.50"
    task = factory.SubFactory(StandardTaskFactory)
    resource = factory.SubFactory(StandardResourceFactory)
    unit = factory.SubFactory(StandardUnitFactory)
    status = factory.SubFactory(AgreedAgreementStatusFactory)
    costs = factory.SubFactory(StandardResourcePriceFactory)
    type = factory.SubFactory(StandardAgreementTypeFactory)


class StandardHumanResourceAgreementToTaskFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Agreement

    date_from = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    date_until = make_date_utc(datetime.datetime(2024, 6, 15, 00))
    amount = "112.50"
    task = factory.SubFactory(StandardTaskFactory)
    resource = factory.SubFactory(StandardHumanResourceFactory)
    unit = factory.SubFactory(StandardUnitFactory)
    status = factory.SubFactory(AgreedAgreementStatusFactory)
    costs = factory.SubFactory(StandardResourcePriceFactory)
    type = factory.SubFactory(StandardAgreementTypeFactory)
<file_sep>/koalixcrm/crm/migrations/0022_auto_20180117_2020.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-17 20:20
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0021_purchaseorder'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='purchaseorder',
            options={'verbose_name': 'Purchase Order', 'verbose_name_plural': 'Purchase Orders'},
        ),
        migrations.AlterField(
            model_name='purchaseorder',
            name='supplier',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Supplier', verbose_name='Supplier'),
        ),
    ]
<file_sep>/documentation/source/tutorial.rst
.. highlight:: rst

Tutorial
========

This tutorial is going to teach you how to 
  
  1. Create a customer 
  2. Create a currency
  3. Create a product
  4. Create a contract
  5. Create a quote
  6. Create an invoice
  7. Set up your accounting
  8. Record an invoice in your accounting
  9. Record a customer payment in the accounting

Before you start I recommend you first have a look into the :doc:`intro`.
This gives you an idea of the basic structure of koalixcrm.
When you understand the structure and functions of koalixcrm or any other
large Django application you are ready to start with the tutorial.
Further, have a look at the :doc:`installation` section

Create Your First Customer
--------------------------
Let say you want to Register the following Customer:
Private address::

  <NAME>
  Ave 1
  90990 Smallville
  Phone: 
  Mobile:
  Email:

Business address::

  <NAME>
  Ave 2
  120987 Largeville
  Phone: 
  Mobile:
  Email:

Logging In Adminpage
^^^^^^^^^^^^^^^^^^^^

Depending on where you installed your koalixcrm - in this tutorial I assume
you installed it on your localhost (the computer you are sitting in front of)
- visit http://localhost/admin in your browser.

.. image:: /images/adminlogin.png

fill out the username and password you set during :doc:`installation`
in the syncdb procedure.

Create a Currency
^^^^^^^^^^^^^^^^^

.. image:: /images/addcurrencyform.png

Select "Add Customer"
^^^^^^^^^^^^^^^^^^^^^

After login you will see the dashboard you already know from
the :doc:`intro`. In the Applications list select the "Customer" - add button

.. image:: /images/customeraddbutton.png

Fill out Fields
^^^^^^^^^^^^^^^

After adding the customer you will have to fill out all required values
to get a valid registered customer. You will have to fill out "postal address"
and "phone address". Further you will need to add a new "Default Billing Cycle"
This can be added by pressing the greeen "plus" sign beside the "Default billing cycle"
drop down box.

A new page will open up and give you the ability to add a new billing cycle.
Fill in the values as described in the image below:

.. image:: /images/addcustomerbillingcycleform.png

After you filled in all required values you will have to click on the save button.
This click will bring you back to the customer adding view ... and you will
see that the default payment method is now set. Next is the customer group, we
have to do exactly the same. Click on the green "plus" sign beside the groups
list and you will soon get a pop up where you can add your first default customer group.
Fill in the values as described in the image below

.. image:: /images/addcustomergroupform.png

After that you have to fill out all the addresses defined in the header of this tutorial.
Finish the customer registration by pressing the save button

..  image:: /images/addcustomerform.png


Look at your first Customer
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ok now we have registered your first Customer.
Of course to have a customer is not that interesting, we hope that a customer is going
to order something, or needs an invoice, a quote or something else.
This will be the next step of the tutorial. But first have a look at your bright shiny
customer by selecting customer's id the dashboard as described in the :doc:`intro`. 

Create Your First Contract
--------------------------
I expect you are still looking at your first customer we just created.
The next step is to click the checkbox to the left of your customer on
the CRM customer list and look at the the actions list that appears on the
bottom of the page.

.. image:: /images/customeractions.png

Select "Create Contract" from this list. This will bring you to the "add contract" form.
The advantage of doing it this way - instead of adding a new contract through
the dashboard - is that you have some values, like the default customer, already
set. this will give you some additional seconds for your daily work.
I expect you are a little bit surprised ... where do I select that I want
to have a quote or an invoice? Well, in  koalixcrm a contract is not an invoice
and a contract is not a quote. A contract is simply a place to store all kind
of documents that are related to the contract. This can of course be a invoice
or a quote but also purchase orders and so on.

Fill in the description field, then by clicking on the save button you have
finished the creation of a contract.

Create Your First Quote
-----------------------

Until now there are no products, no prices and no units registered. In order to
be able to offer a product to a customer we need some products first.... you
could do it the lazy way by adding the product while registering the
quote but in this case we are going to register the products, units and prices
before we create the quote.

Create Your First Product
^^^^^^^^^^^^^^^^^^^^^^^^^
To create your first product visit the dashboard by either following the
breadcrumps back to the dashboard

.. image:: /images/breadcrumps.png

or visit http://localhost/admin again. Press the Add button next to Units to
access the Unit adding form. Now fill out all the
required fields to register the unit "hours"

.. image:: /images/addunitformhour.png

Press save, add an other unit by again pressing the add button.
Now we create a unit "minute".

.. image:: /images/addunitformminute.png

Press save and go back to the dashboard

As we have registered some units, now we are able to create a product.

Press the Products add button to get to the products adding form. We start with
a common product called Manpower.
Fill all fields with the following values:

.. image:: /images/addproductform1.png

as you know every product has its price specially manpower - time is money.
That's why we have to add at least one price for this product by giving
the Prices fields the following values.

.. image:: /images/addproductform2.png

You will see an other part of this form called Unit Transfroms.
Unit Transforms are sometimes needed when, for example, you have
stacks of certain products but only one price per piece. Leave this
blank when you only have one unit for one product.

After adding this product you are ready to create your first Quote by going
to your dashboard. Open Contracts, select the check box beside the contract
you want to change and select "Create Quote" from the Actions list.
A form will open for you to fill out your fist quote.

.. image:: /images/addquoteform1.png

As you can see, there are lots of predefined values because we created
the quote with the action instead of via dashboard
and Quote Add. There are three major parts of a quote:

 * First the general values like "valid until", "description" and so on
 * Second are role positions of the quote
 * Lastly are Addresses related to the Contract

By pressing the "+" sign you can add as many positions as you like.
Fill in the values as described below.

.. image:: /images/addquoteform2.png

Click on the save button to finish your first quote. Go back to the dashboard,
go to quotes and select the newly created quote's checkbox.
From the actions select "Create PDF of Quote" to generate a pdf of this new quote.


Create Your First Invoice
-------------------------
This is going to be a very short chapter because all you have to do is
repeat the steps above but instead of selecting
"Create Quote" in the Contract Actions list you select "Create Invoice";
alternatively by selecting your new Quote and use the action
"Create Invoice". The second way is much easier and faster because the
program just takes all values and positions from the
quote and transforms it into a invoice.


<file_sep>/graph.py
# libraries and data
<file_sep>/koalixcrm/version.py
# -*- coding: utf-8 -*-

KOALIXCRM_VERSION = "1.12.1"<file_sep>/koalixcrm/crm/migrations/0046_auto_20180805_2051.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-08-05 20:51
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0045_auto_20180805_2047'),
    ]
    operations = [
        migrations.RemoveField(
            model_name='work',
            name='worked_hours')
    ]
<file_sep>/koalixcrm/crm/product/unit_transform.py
# -*- 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"),
                                  blank=False,
                                  null=False,
                                  related_name="db_reltransfromfromunit")
    to_unit = models.ForeignKey('Unit',
                                verbose_name=_("To Unit"),
                                blank=False,
                                null=False,
                                related_name="db_reltransfromtounit")
    product_type = models.ForeignKey('ProductType',
                                     blank=False,
                                     null=False,
                                     verbose_name=_("Product Type"))
    factor = models.DecimalField(verbose_name=_("Factor between From and To Unit"),
                                 blank=False,
                                 null=False,
                                 max_digits=17,
                                 decimal_places=2,)

    def transform(self, unit):
        if self.from_unit == unit:
            return self.to_unit
        else:
            return None

    def get_transform_factor(self):
        return self.factor

    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 UnitTransformInlineAdminView(admin.TabularInline):
    model = UnitTransform
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('', {
            'fields': ('from_unit',
                       'to_unit',
                       'factor',)
        }),
    )
    allow_add = True
<file_sep>/koalixcrm/crm/tests/test_user_is_not_human_resource.py
import pytest
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from koalixcrm.test_support_functions import *
from koalixcrm.crm.factories.factory_user import AdminUserFactory
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.djangoUserExtension.factories.factory_user_extension import StandardUserExtensionFactory


class TimeTrackingAddRow(LiveServerTestCase):

    def setUp(self):
        firefox_options = webdriver.firefox.options.Options()
        firefox_options.set_headless(headless=True)
        self.selenium = webdriver.Firefox(firefox_options=firefox_options)
        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)

    def tearDown(self):
        self.selenium.quit()

    @pytest.mark.front_end_tests
    def test_add_new_row(self):
        selenium = self.selenium
        # login
        selenium.get('%s%s' % (self.live_server_url, '/koalixcrm/crm/reporting/time_tracking/'))
        # the browser will be redirected to the login page
        timeout = 5
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'id_username'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        username = selenium.find_element_by_xpath('//*[@id="id_username"]')
        password = selenium.find_element_by_xpath('//*[@id="id_password"]')
        submit_button = selenium.find_element_by_xpath('/html/body/div/article/div/div/form/div/ul/li/input')
        username.send_keys("admin")
        password.send_keys("<PASSWORD>")
        submit_button.send_keys(Keys.RETURN)
        # after the login, the browser is redirected to the target url /koalixcrm/crm/reporting/time_tracking
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'id_next_steps'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")

        # Because the user is not equipped with a corresponding human_resource, the user must receive an exception
        # screen giving im the possibility to select what he wants to do next
        # In this test_step it is checked whether the form contains the required fields. the test will then select
        # to return to the start page instead of defining a new human resource

        assert_when_element_does_not_exist(self, '//*[@id="id_next_steps"]')
        assert_when_element_does_not_exist(self, 'confirm_selection')
        submit_button = selenium.find_element_by_xpath('/html/body/div/article/div/form/table/tbody/tr[3]/td/input[2]')
        selection = Select(selenium.find_element_by_xpath('//*[@id="id_next_steps"]'))
        selection.select_by_value("return_to_start")
        submit_button.send_keys(Keys.RETURN)
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'grp-content-title'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        assert_when_element_is_not_equal_to(self, 'grp-content-title', "<h1>Site administration</h1>")

        # Because the user is still not equipped with a corresponding human_resource, the user must
        # receive an exception screen giving im the possibility to select what he wants to do next
        # In this test_step it is checked whether the form contains the required fields. the test will then select
        # to return to the start page instead of defining a new human resource

        selenium.get('%s%s' % (self.live_server_url, '/koalixcrm/crm/reporting/time_tracking/'))
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'id_next_steps'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        assert_when_element_does_not_exist(self, '//*[@id="id_next_steps"]')
        assert_when_element_does_not_exist(self, 'confirm_selection')
        submit_button = selenium.find_element_by_xpath('/html/body/div/article/div/form/table/tbody/tr[3]/td/input[2]')
        selection = Select(selenium.find_element_by_xpath('//*[@id="id_next_steps"]'))
        selection.select_by_value("create_human_resource")
        submit_button.send_keys(Keys.RETURN)
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'grp-content-title'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        assert_when_element_is_not_equal_to(self, 'grp-content-title', "<h1>Add human resource</h1>")

        # This test is passed here, other testcase are performed to ensure proper functionality of this view
        # when the human resource is properly set"
<file_sep>/koalixcrm/crm/migrations/0026_auto_20180507_1957.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-05-07 19:57
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0025_auto_20180413_1937'),
    ]

    operations = [
        migrations.RenameField(
            model_name='contact',
            old_name='dateofcreation',
            new_name='date_of_creation',
        ),
        migrations.RenameField(
            model_name='contact',
            old_name='lastmodification',
            new_name='last_modification',
        ),
        migrations.RenameField(
            model_name='contact',
            old_name='lastmodifiedby',
            new_name='last_modified_by',
        ),
        migrations.RenameField(
            model_name='customer',
            old_name='defaultCustomerBillingCycle',
            new_name='default_customer_billing_cycle',
        ),
        migrations.RenameField(
            model_name='customer',
            old_name='ismemberof',
            new_name='is_member_of',
        ),
    ]
<file_sep>/koalixcrm/crm/contact/customer.py
# -*- coding: utf-8 -*-

from django.http import HttpResponseRedirect
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.plugin import *
from koalixcrm.crm.contact.contact import Contact, ContactCall, ContactVisit,\
    PeopleInlineAdmin, PostalAddressForContact, ContactPostalAddress, \
    ContactPhoneAddress, ContactEmailAddress, CityFilter, StateFilter
from koalixcrm.crm.documents.contract import Contract


class Customer(Contact):
    default_customer_billing_cycle = models.ForeignKey('CustomerBillingCycle',
                                                       verbose_name=_('Default Billing Cycle'))
    is_member_of = models.ManyToManyField("CustomerGroup",
                                          verbose_name=_('Is member of'),
                                          blank=True)
    is_lead = models.BooleanField(default=True)

    def create_contract(self, request):
        contract = Contract()
        contract.create_from_reference(self, request.user)
        return contract

    def create_invoice(self, request):
        contract = self.create_contract(request)
        invoice = contract.create_invoice()
        return invoice

    def create_quote(self, request):
        contract = self.create_contract(request)
        quote = contract.create_quote()
        return quote

    def is_in_group(self, customer_group):
        for customer_group_membership in self.is_member_of.all():
            if customer_group_membership.id == customer_group.id:
                return 1
        return 0

    class Meta:
        app_label = "crm"
        verbose_name = _('Customer')
        verbose_name_plural = _('Customers')

    def __str__(self):
        return str(self.id) + ' ' + self.name


class IsLeadFilter(admin.SimpleListFilter):
    title = _('Is lead')
    parameter_name = 'is_lead'

    def lookups(self, request, model_admin):
        return (
            ('lead', _('Lead')),
            ('customer', _('Customer')),
        )

    def queryset(self, request, queryset):
        if self.value() == 'lead':
            return queryset.filter(is_lead=True)
        elif self.value() == 'customer':
            return queryset.filter(is_lead=False)
        else:
            return queryset


class OptionCustomer(admin.ModelAdmin):
    list_display = ('id',
                    'name',
                    'default_customer_billing_cycle',
                    'get_state',
                    'get_town',
                    'date_of_creation',
                    'get_is_lead',)
    list_filter = ('is_member_of', StateFilter, CityFilter, IsLeadFilter)
    fieldsets = (('', {'fields': ('name',
                                  'default_customer_billing_cycle',
                                  'is_member_of',)}),)
    allow_add = True
    ordering = ('id',)
    search_fields = ('id', 'name')
    inlines = [ContactPostalAddress,
               ContactPhoneAddress,
               ContactEmailAddress,
               PeopleInlineAdmin,
               ContactCall,
               ContactVisit]
    pluginProcessor = PluginProcessor()
    inlines.extend(pluginProcessor.getPluginAdditions("customerInline"))

    @staticmethod
    def get_postal_address(obj):
        return PostalAddressForContact.objects.filter(person=obj.id).first()

    def get_state(self, obj):
        address = self.get_postal_address(obj)
        return address.state if address is not None else None

    get_state.short_description = _("State")

    def get_town(self, obj):
        address = self.get_postal_address(obj)
        return address.town if address is not None else None

    get_town.short_description = _("City")

    @staticmethod
    def get_is_lead(obj):
        return obj.is_lead

    get_is_lead.short_description = _("Is Lead")
    
    def create_contract(self, request, queryset):
        for obj in queryset:
            contract = obj.create_contract(request)
            response = HttpResponseRedirect('/admin/crm/contract/' + str(contract.id))
            return response

    create_contract.short_description = _("Create Contract")

    @staticmethod
    def create_quote(self, request, queryset):
        for obj in queryset:
            quote = obj.create_quote(request)
            response = HttpResponseRedirect('/admin/crm/quote/' + str(quote.id))
        return response

    create_quote.short_description = _("Create Quote")

    @staticmethod
    def create_invoice(self, request, queryset):
        for obj in queryset:
            invoice = obj.create_invoice(request)
            response = HttpResponseRedirect('/admin/crm/invoice/' + str(invoice.id))
        return response

    create_invoice.short_description = _("Create Invoice")

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_modified_by = request.user
        else:
            obj.last_modified_by = request.user
            obj.staff = request.user
        obj.save()

    actions = ['create_contract', 'create_invoice', 'create_quote']
    pluginProcessor = PluginProcessor()
    inlines.extend(pluginProcessor.getPluginAdditions("customerActions"))<file_sep>/koalixcrm/crm/reporting/task.py
# -*- coding: utf-8 -*-

from decimal import *
from django.db import models
from django.utils.translation import ugettext as _
from django.contrib import admin
from django.utils.html import format_html
from koalixcrm.crm.reporting.agreement import Agreement
from koalixcrm.crm.reporting.agreement import AgreementInlineAdminView
from koalixcrm.crm.reporting.estimation import EstimationInlineAdminView
from koalixcrm.crm.reporting.generic_task_link import InlineGenericTaskLink
from koalixcrm.crm.reporting.work import WorkInlineAdminView, Work
from koalixcrm.crm.reporting.reporting_period import ReportingPeriod
from koalixcrm.crm.reporting.resource_price import ResourcePrice
from koalixcrm.crm.reporting.estimation import Estimation
from koalixcrm.crm.documents.pdf_export import PDFExport
from koalixcrm.crm.exceptions import ReportingPeriodNotFound
from rest_framework import serializers
from koalixcrm import global_support_functions


class Task(models.Model):
    """ The Task model"""
    title = models.CharField(verbose_name=_("Title"),
                             max_length=100,
                             blank=True,
                             null=True)
    project = models.ForeignKey("Project",
                                verbose_name=_('Project'),
                                related_name='tasks',
                                blank=False,
                                null=False)
    description = models.TextField(verbose_name=_("Description"),
                                   blank=True,
                                   null=True)
    status = models.ForeignKey("TaskStatus", verbose_name=_('Status'),
                               blank=True,
                               null=True)
    last_status_change = models.DateField(verbose_name=_("Last Status Change"),
                                          blank=True,
                                          null=False)
    previous_status = None

    def __init__(self, *args, **kwargs):
        super(Task, self).__init__(*args, **kwargs)
        self.previous_status = self.status

    def save(self, *args, **kwargs):
        if self.id is not None:
            if self.status != self.previous_status:
                self.last_status_change = global_support_functions.get_today_date()
        elif self.last_status_change is None:
            self.last_status_change = global_support_functions.get_today_date()
        super(Task, self).save(*args, **kwargs)

    def link_to_task(self):
        if self.id:
            return format_html("<a href='/admin/crm/task/%s' >%s</a>" % (str(self.id), str(self.title)))
        else:
            return "Not present"
    link_to_task.short_description = _("Task")

    def planned_duration(self):
        if (not self.planned_start()) or (not self.planned_end()):
            duration_in_days = "n/a"
        elif self.planned_start() > self.planned_end():
            duration_in_days = "n/a"
        else:
            duration_in_days = (self.planned_end()-self.planned_start()).days
        return duration_in_days
    planned_duration.short_description = _("Planned Duration [dys]")
    planned_duration.tags = True

    def planned_start(self):
        """ The function return the planned start of a task as a date based on the estimations which are
         attached to the task in case there was no estimation attached to this task, the function returns None

         Args:
         no arguments

         Returns:
         planned_start (Date) or None

         Raises:
         No exceptions planned"""
        all_task_estimations = Estimation.objects.filter(task=self.id)
        planned_task_start = None
        if len(all_task_estimations) == 0:
            planned_task_start = None
        else:
            for estimation in all_task_estimations:
                if planned_task_start is None:
                    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

    def planned_end(self):
        """ The function return the planned end of a task as a date based on the estimations which are
         attached to the task in case there was no estimation attached to this task, the function returns None

         Args:
         no arguments

         Returns:
         planned_end (Date) or None

         Raises:
         No exceptions planned"""
        all_task_estimations = Estimation.objects.filter(task=self.id)
        planned_task_end = None
        if len(all_task_estimations) == 0:
            planned_task_end = None
        else:
            for estimation in all_task_estimations:
                if not planned_task_end:
                    planned_task_end = estimation.date_until
                elif estimation.date_until < planned_task_end:
                    planned_task_end = estimation.date_until
        return planned_task_end
    planned_end.short_description = _("Planned End")
    planned_end.tags = True

    def planned_effort(self, reporting_period=None):
        """The function return the planned effort 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 (Decimal) [hrs], 0 if when no estimation are present

        Raises:
        no exceptions expected"""
        try:
            if not reporting_period:
                reporting_period_internal = ReportingPeriod.get_latest_reporting_period(
                    self.project)
            else:
                reporting_period_internal = reporting_period
            estimations_to_this_task = Estimation.objects.filter(task=self.id,
                                                                 reporting_period=reporting_period_internal)
            effort = 0
            for estimation_to_this_task in estimations_to_this_task:
                effort += estimation_to_this_task.amount
        except ReportingPeriodNotFound:
            effort = 0
        return effort
    planned_effort.short_description = _("Planned Effort")
    planned_effort.tags = True

    def planned_costs(self, reporting_period=None):
        """The function returns the planned costs of resources which have been estimated for this task
         at a specific reporting period plus the costs of the effective effort before the provided reporting_period
         When no reporting_period is provided, the last reporting period

        is selected.

        Args:
        no arguments

        Returns:
        planned costs (Decimal), 0 if when no estimation or no reporting period is present

        Raises:
        No exceptions planned"""
        try:
            if not reporting_period:
                reporting_period_internal = ReportingPeriod.get_latest_reporting_period(self.project)

            else:
                reporting_period_internal = ReportingPeriod.objects.get(id=reporting_period.id)
            predecessor_reporting_periods = ReportingPeriod.get_all_predecessors(reporting_period_internal,
                                                                                 self.project)
            estimations_to_this_task = Estimation.objects.filter(task=self.id,
                                                                 reporting_period=reporting_period_internal)
            sum_costs = 0
            if len(estimations_to_this_task) != 0:
                for estimation_to_this_task in estimations_to_this_task:
                    sum_costs += estimation_to_this_task.calculated_costs()
            if len(predecessor_reporting_periods) != 0:
                for predecessor_reporting_period in predecessor_reporting_periods:
                    sum_costs += self.effective_costs(reporting_period=predecessor_reporting_period)
        except ReportingPeriodNotFound:
            sum_costs = 0
        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 task as a date. The
        function return the effective start of a task as a date based on the reported work
        in case there was no work reported to this task, the planned start is used as a
        fall-back.

        Args:
        no arguments

        Returns:
        effective_start (Date)

        Raises:
        No exceptions planned"""
        all_task_works = Work.objects.filter(task=self.id)
        effective_task_start = None
        if len(all_task_works) == 0:
            effective_task_start = self.planned_start()
        else:
            for work in all_task_works:
                if not effective_task_start:
                    effective_task_start = work.date
                elif work.date < effective_task_start:
                    effective_task_start = work.date
        return effective_task_start
    effective_start.short_description = _("Effective Start")
    effective_start.tags = True

    def task_end(self):
        """The function returns a boolean value True when the task is on
        status done. In all other case (example in no status case) the function
        returns False

        Args:
        no arguments

        Returns:
        task_ended (Boolean)

        Raises:
        No exceptions planned"""
        if not self.status:
            task_ended = False
        elif self.status.is_done:
            task_ended = self.status.is_done
        else:
            task_ended = False
        return task_ended

    def effective_end(self):
        """When the task has already ended, the
        function return the effective end of a task as a date based on the reported work
        in case there was no work reported to this task, the last status change is used as a
        fall-back. When the task did not yet end, the function returns None

        Args:
        no arguments

        Returns:
        effective_end (Date) or None when not yet ended

        Raises:
        No exceptions planned"""
        all_task_works = Work.objects.filter(task=self.id)
        effective_task_end = None
        if self.task_end():
            if len(all_task_works) == 0:
                effective_task_end = self.last_status_change
            else:
                for work in all_task_works:
                    if not effective_task_end:
                        effective_task_end = work.date
                    elif work.date > effective_task_end:
                        effective_task_end = work.date
        else:
            effective_task_end = None
        return effective_task_end
    effective_end.short_description = _("Effective End")
    effective_end.tags = True

    def effective_duration(self):
        """The function return the effective overall duration of a task as a string in days
        The function is reading the effective_starts and effective_ends of the task and
        subtract them from each other.

        Args:
        no arguments

        Returns:
        duration [dys]

        Raises:
        No exceptions planned"""
        effective_end = self.effective_end()
        effective_start = self.effective_start()
        if effective_start is None:
            duration_as_string = 0
        elif effective_end is None:
            duration_as_string = 0
        else:
            duration_as_date = effective_end-effective_start
            duration_as_string = duration_as_date.days.__str__()
        return duration_as_string
    effective_duration.short_description = _("Effective Duration [dys]")
    effective_duration.tags = True

    def serialize_to_xml(self, reporting_period):
        objects = [self, ]
        main_xml = PDFExport.write_xml(objects)
        if reporting_period:
            works = Work.objects.filter(task=self.id,
                                        reporting_period=reporting_period)
        else:
            works = Work.objects.filter(task=self.id)
        for work in works:
            work_xml = work.serialize_to_xml()
            main_xml = PDFExport.merge_xml(main_xml, work_xml)
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='crm.<EMAIL>']",
                                                       "Effective_Effort_Overall",
                                                       self.effective_costs(reporting_period=None))
        if reporting_period:
            main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                           "object/[<EMAIL>='<EMAIL>']",
                                                           "Effective_Effort_InPeriod",
                                                           self.effective_costs(reporting_period=reporting_period))
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Planned_Effort",
                                                       self.planned_costs())
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='crm.<EMAIL>']",
                                                       "Effective_Duration",
                                                       self.effective_duration())
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "Planned_Duration",
                                                       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_effort(self, reporting_period=None):
        """ 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:
            work_objects = Work.objects.filter(task=self.id,
                                               reporting_period=reporting_period)
        else:
            work_objects = Work.objects.filter(task=self.id)
        sum_effort = 0
        for work_object in work_objects:
            sum_effort += work_object.effort_seconds()
        sum_effort_in_hours = sum_effort / 3600
        return sum_effort_in_hours

    def effective_costs(self, reporting_period=None):
        """Returns the effective costs on a task
        when reporting_period is None, the effective costs overall is calculated
        when reporting_period is specified, the effective effort in this period is calculated
        the costs are calculated in the project currency

        Args:
          no arguments

        Returns:
          costs [Currency] (Decimal)

        Raises:
           no exception planned
        """
        agreements = Agreement.objects.filter(task=self)
        if reporting_period:
            all_work_in_task = Work.objects.filter(task=self.id,
                                                   reporting_period=reporting_period)
        else:
            all_work_in_task = Work.objects.filter(task=self.id)
        sum_costs = 0
        work_with_agreement = list()
        work_without_agreement = list()
        work_calculated = list()
        human_resource_list = dict()
        for work_object in all_work_in_task:
            if work_object.human_resource not in human_resource_list:
                human_resource_list[work_object.human_resource] = dict()
            for agreement in agreements:
                agreement_matches = agreement.match_with_work(work_object)
                if agreement_matches:
                    if work_object not in work_with_agreement:
                        work_with_agreement.append(work_object)
                    if agreement not in human_resource_list.get(work_object.human_resource):
                        human_resource_list.get(work_object.human_resource)[agreement] = list()
                    human_resource_list.get(work_object.human_resource).get(agreement).append(work_object)
            if work_object not in work_with_agreement:
                work_without_agreement.append(work_object)

        for human_resource_dict in human_resource_list:
            agreement_list = Agreement.objects.filter(task=self, resource=human_resource_dict).order_by('costs__price')
            for agreement in agreement_list:
                agreement_remaining_amount = agreement.amount
                if human_resource_list[human_resource_dict].get(agreement):
                    for work in human_resource_list[human_resource_dict].get(agreement):
                        if work in work_with_agreement:
                            if (agreement_remaining_amount - work.worked_hours) > 0:
                                agreement_remaining_amount -= work.worked_hours
                                getcontext().prec = 5
                                sum_costs += Decimal(work.effort_hours())*agreement.costs.price
                                work_calculated.append(work)
        for work in all_work_in_task:
            if work not in work_calculated:
                if work not in work_without_agreement:
                    work_without_agreement.append(work)
        for work in work_without_agreement:
            default_resource_prices = ResourcePrice.objects.filter(resource=work.human_resource.id).order_by('price')
            if default_resource_prices:
                default_resource_price = default_resource_prices[0]
                getcontext().prec = 5
                sum_costs += Decimal(work.effort_hours())*default_resource_price.price
            else:
                sum_costs = 0
                break
        sum_costs = self.project.default_currency.round(sum_costs)
        return sum_costs
    effective_costs.short_description = _("Effective costs")
    effective_costs.tags = True

    def is_reporting_allowed(self):
        """Returns True when the task is available for reporting,
        Returns False when the task is not available for reporting,
        The decision whether the task is available for reporting is purely depending
        on the task_status. When the status is done or when the status is unknown,
        the task is not longer available for reporting.

        Args:
          no arguments

        Returns:
          allowed (Boolean)

        Raises:
           when there is no valid reporting Period"""
        if self.status:
            if self.status.is_done:
                allowed = False
            else:
                allowed = True
        else:
            allowed = False
        return allowed
    is_reporting_allowed.short_description = _("Reporting")
    is_reporting_allowed.tags = True

    def get_title(self):
        if self.title:
            return self.title
        else:
            return "n/a"

    def __str__(self):
        return str(self.id) + " " + self.get_title()

    class Meta:
        app_label = "crm"
        verbose_name = _('Task')
        verbose_name_plural = _('Tasks')


class TaskAdminView(admin.ModelAdmin):
    list_display = ('link_to_task',
                    'planned_start',
                    'planned_end',
                    'project',
                    'status',
                    'last_status_change',
                    'planned_duration',
                    'planned_costs',
                    'effective_duration',
                    'effective_effort_overall',
                    'effective_costs')
    list_display_links = ('link_to_task',)
    list_filter = ('project',)
    ordering = ('-id',)

    fieldsets = (
        (_('Work'), {
            'fields': ('title',
                       'project',
                       'description',
                       'status')
        }),
    )
    save_as = True
    inlines = [AgreementInlineAdminView,
               EstimationInlineAdminView,
               InlineGenericTaskLink,
               WorkInlineAdminView]


class TaskInlineAdminView(admin.TabularInline):
    model = Task
    readonly_fields = ('link_to_task',
                       'last_status_change',
                       'planned_start',
                       'planned_end',
                       'planned_duration',
                       'planned_costs',
                       'effective_duration',
                       'effective_effort_overall',
                       'effective_costs')
    fieldsets = (
        (_('Task'), {
            'fields': ('link_to_task',
                       'title',
                       'planned_start',
                       'planned_end',
                       'status',
                       'last_status_change',
                       'planned_duration',
                       'planned_costs',
                       'effective_duration',
                       'effective_effort_overall',
                       'effective_costs')
        }),
    )
    extra = 1

    def has_add_permission(self, request):
        return True

    def has_delete_permission(self, request, obj=None):
        return False


class TaskSerializer(serializers.ModelSerializer):
    is_reporting_allowed = serializers.SerializerMethodField()

    def get_is_reporting_allowed(self, obj):
        if obj.is_reporting_allowed():
            return "True"
        else:
            return "False"

    class Meta:
        model = Task
        fields = ('id',
                  'title',
                  'planned_end',
                  'planned_start',
                  'project',
                  'description',
                  'status',
                  'is_reporting_allowed')
<file_sep>/koalixcrm/accounting/rest/booking_rest.py
# -*- coding: utf-8 -*-
from django.contrib.auth.models import User
from rest_framework import serializers

from koalixcrm.accounting.accounting.accounting_period import AccountingPeriod
from koalixcrm.accounting.accounting.booking import Booking
from koalixcrm.accounting.models import Account
from koalixcrm.accounting.rest.account_rest import OptionAccountJSONSerializer
from koalixcrm.accounting.rest.accounting_period_rest import OptionAccountingPeriodJSONSerializer


class UserJSONSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.IntegerField(required=False)
    username = serializers.CharField(read_only=True)

    class Meta:
        model = User
        fields = (
            'id',
            'username')


class BookingJSONSerializer(serializers.HyperlinkedModelSerializer):
    fromAccount = OptionAccountJSONSerializer(source='from_account', allow_null=False)
    toAccount = OptionAccountJSONSerializer(source='to_account', allow_null=False)
    bookingDate = serializers.DateTimeField(source='booking_date', format="%Y-%m-%dT%H:%M", input_formats=None,
                                            allow_null=False)
    bookingReference = serializers.CharField(source='booking_reference', allow_null=True)
    accountingPeriod = OptionAccountingPeriodJSONSerializer(source='accounting_period', allow_null=False)
    staff = UserJSONSerializer(allow_null=False)

    class Meta:
        model = Booking
        fields = ('id',
                  'fromAccount',
                  'toAccount',
                  'description',
                  'amount',
                  'bookingDate',
                  'staff',
                  'bookingReference',
                  'accountingPeriod')
        depth = 1

    def create(self, validated_data):
        booking = Booking()
        booking.description = validated_data['description']
        booking.amount = validated_data['amount']
        booking.booking_date = validated_data['booking_date']
        booking.booking_reference = validated_data['booking_reference']

        # Deserialize from staff
        staff = validated_data.pop('staff')
        if staff:
            if staff.get('id', None):
                user = User.objects.get(id=staff.get('id', None))

                booking.staff = user
                booking.last_modified_by = user
            else:
                booking.staff = None

        # Deserialize from account
        from_account = validated_data.pop('from_account')
        if from_account:
            if from_account.get('id', None):
                booking.from_account = Account.objects.get(id=from_account.get('id', None))
            else:
                booking.from_account = None

        # Deserialize to account
        to_account = validated_data.pop('to_account')
        if to_account:
            if to_account.get('id', None):
                booking.to_account = Account.objects.get(id=to_account.get('id', None))
            else:
                booking.to_account = None

        # Deserialize to accounting period
        accounting_period = validated_data.pop('accounting_period')
        if accounting_period:
            if accounting_period.get('id', None):
                booking.accounting_period = AccountingPeriod.objects.get(id=accounting_period.get('id', None))
            else:
                booking.accounting_period = None

        booking.save()
        return booking

    def update(self, instance, validated_data):
        instance.description = validated_data['description']
        instance.amount = validated_data['amount']
        instance.booking_date = validated_data['booking_date']
        instance.booking_reference = validated_data['booking_reference']

        # Deserialize from staff
        staff = validated_data.pop('staff')
        if staff:
            if staff.get('id', instance.staff):
                instance.staff = User.objects.get(id=staff.get('id', None))
            else:
                instance.staff = instance.staff_id
        else:
            instance.staff = None

        # Deserialize from account
        from_account = validated_data.pop('from_account')
        if from_account:
            if from_account.get('id', instance.from_account):
                instance.from_account = Account.objects.get(id=from_account.get('id', None))
            else:
                instance.from_account = instance.from_account_id
        else:
            instance.from_account = None

        # Deserialize to account
        to_account = validated_data.pop('to_account')
        if to_account:
            if to_account.get('id', instance.to_account):
                user = User.objects.get(id=staff.get('id', None))

                instance.staff = user
                instance.last_modified_by = user
            else:
                instance.to_account = instance.to_account_id
        else:
            instance.to_account = None

        # Deserialize to accounting period
        accounting_period = validated_data.pop('accounting_period')
        if accounting_period:
            if accounting_period.get('id', instance.accounting_period):
                instance.accounting_period = AccountingPeriod.objects.get(id=accounting_period.get('id', None))
            else:
                instance.accounting_period = instance.accounting_period_id
        else:
            instance.accounting_period = None

        instance.save()
        return instance
<file_sep>/koalixcrm/crm/factories/factory_resource_type.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import ResourceType


class StandardResourceTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ResourceType

    title = 'This is a test Resource Type'
    description = "This is a description of such a Resource Type"
<file_sep>/requirements.txt
-r requirements/heroku_requirements.txt<file_sep>/koalixcrm/subscriptions/admin.py
# -*- coding: utf-8 -*-
from django.contrib import admin
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from koalixcrm.subscriptions.models import *


class AdminSubscriptionEvent(admin.TabularInline):
    model = SubscriptionEvent
    extra = 1
    classes = ('collapse-open',)
    fieldsets = (
        ('Basics', {
            'fields': (
                'event_date',
                'event',)
        }),
    )
    allow_add = True


class InlineSubscription(admin.TabularInline):
    model = Subscription
    extra = 1
    classes = ('collapse-open',)
    readonly_fields = ('contract',
                       'subscription_type')
    fieldsets = (
        (_('Basics'), {
            'fields': (
                'contract',
                'subscription_type')
        }),
    )
    allow_add = False


class OptionSubscription(admin.ModelAdmin):
    list_display = ('id',
                    'contract',
                    'subscription_type',)
    ordering = ('id',
                'contract',
                'subscription_type')
    search_fields = ('id',
                     'contract',)
    fieldsets = (
        (_('Basics'), {
            'fields': (
                'contract',
                'subscription_type',)
        }),
    )
    inlines = [AdminSubscriptionEvent]

    @staticmethod
    def create_invoice(queryset):
        for obj in queryset:
            invoice = obj.create_invoice()
            response = HttpResponseRedirect('/admin/crm/invoice/' + str(invoice.id))
        return response
    create_invoice.short_description = _("Create Invoice")

    @staticmethod
    def create_quote(queryset):
        for obj in queryset:
            invoice = obj.create_invoice()
            response = HttpResponseRedirect('/admin/crm/invoice/' + str(invoice.id))
        return response

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_modified_by = request.user
        else:
            obj.last_modified_by = request.user
            obj.staff = request.user
        obj.save()

    actions = ['create_subscription_pdf', 'create_invoice']


class OptionSubscriptionType(admin.ModelAdmin):
    list_display = ('id',)
    list_display_links = ('id',)
    ordering = ('id', )
    search_fields = ('id',)
    fieldsets = (
        (_('Basics'), {
            'fields': (
                'cancellation_period',
                'automatic_contract_extension',
                'automatic_contract_extension_reminder',
                'minimum_duration',
                'payment_interval',
                'contract_document')
        }),
    )


def create_subscription(a, request, queryset):
    for contract in queryset:
        subscription = Subscription()
        subscription.create_subscription_from_contract(crmmodels.Contract.objects.get(id=contract.id))
        response = HttpResponseRedirect('/admin/subscriptions/' + str(subscription.id))
    return response


create_subscription.short_description = _("Create Subscription")


class KoalixcrmPluginInterface(object):
    contractInlines = [InlineSubscription]
    contractActions = [create_subscription]
    invoiceInlines = []
    invoiceActions = []
    quoteInlines = []
    quoteActions = []
    customerInlines = []
    customerActions = []


admin.site.register(Subscription, OptionSubscription)
admin.site.register(SubscriptionType, OptionSubscriptionType)
<file_sep>/koalixcrm/crm/views/range_selection_form.py
# -*- coding: utf-8 -*-

from django.contrib.admin.widgets import *


class RangeSelectionForm(forms.Form):
    """
    Form which allows to select the Range in which the work item can be registered
    and which define which range of work items are loaded from the database and shown
    to the user
    """
    from_date = forms.DateField(widget=AdminDateWidget)
    to_date = forms.DateField(widget=AdminDateWidget)
    original_from_date = forms.DateField(widget=forms.HiddenInput(),
                                         required=False)
    original_to_date = forms.DateField(widget=forms.HiddenInput(),
                                       required=False)

    def evaluate_pre_check_from_date(self):
        original_from_date = self.cleaned_data['original_from_date']
        new_from_date = self.cleaned_data['from_date']
        if original_from_date < new_from_date:
            from_date = original_from_date
        else:
            from_date = new_from_date
        return from_date

    def evaluate_pre_check_to_date(self):
        original_to_date = self.cleaned_data['original_to_date']
        new_to_date = self.cleaned_data['to_date']

        if original_to_date > new_to_date:
            to_date = original_to_date
        else:
            to_date = new_to_date
        return to_date

    def update_from_input(self):
        self.from_date = self.cleaned_data['from_date']
        self.to_date = self.cleaned_data['to_date']
        self.original_from_date = self.from_date
        self.original_to_date = self.to_date

    @staticmethod
    def create_range_selection_form(from_date, to_date):
        initial_form_data = {'from_date': from_date,
                             'to_date': to_date,
                             'original_from_date': from_date,
                             'original_to_date': to_date}
        range_selection_form = RangeSelectionForm(initial=initial_form_data)
        return range_selection_form

<file_sep>/koalixcrm/crm/factories/factory_work.py
# -*- coding: utf-8 -*-

import factory
import datetime
from koalixcrm.crm.models import Work
from koalixcrm.crm.factories.factory_task import StandardTaskFactory
from koalixcrm.crm.factories.factory_reporting_period import StandardReportingPeriodFactory
from koalixcrm.crm.factories.factory_human_resource import StandardUserExtensionFactory
from koalixcrm.test_support_functions import make_date_utc


class StandardWorkFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Work

    human_resource = factory.SubFactory(StandardUserExtensionFactory)
    date = make_date_utc(datetime.datetime(2018, 5, 1, 0, 00))
    start_time = None
    stop_time = None
    worked_hours = "1.50"
    short_description = "The employee did some work"
    description = "And here he describes some more about his work"
    task = factory.SubFactory(StandardTaskFactory)
    reporting_period = factory.SubFactory(StandardReportingPeriodFactory)
<file_sep>/koalixcrm/crm/factories/factory_contact.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Contact
from koalixcrm.crm.factories.factory_user import StaffUserFactory


class StandardContactFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Contact
        django_get_or_create = ('name',)

    name = "<NAME>"
    date_of_creation = "2018-05-01"
    last_modification = "2018-05-03"
    last_modified_by = factory.SubFactory(StaffUserFactory)
<file_sep>/koalixcrm/accounting/migrations/0003_remove_account_originalamount.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-07 12:40
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ('accounting', '0002_auto_20170705_1702'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='account',
            name='originalAmount',
        ),
    ]
<file_sep>/koalixcrm/crm/product/tax.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _


class Tax(models.Model):
    tax_rate = models.DecimalField(max_digits=5,
                                   decimal_places=2,
                                   verbose_name=_("Taxrate in Percentage"))
    name = models.CharField(verbose_name=_("Taxname"),
                            max_length=100)
    account_activa = models.ForeignKey('accounting.Account',
                                       verbose_name=_("Activa Account"),
                                       related_name="db_relaccountactiva",
                                       null=True,
                                       blank=True)
    account_passiva = models.ForeignKey('accounting.Account',
                                        verbose_name=_("Passiva Account"),
                                        related_name="db_relaccountpassiva",
                                        null=True,
                                        blank=True)

    def get_tax_rate(self):
        return self.tax_rate;

    def __str__(self):
        return self.name

    class Meta:
        app_label = "crm"
        verbose_name = _('Tax')
        verbose_name_plural = _('Taxes')


class OptionTax(admin.ModelAdmin):
    list_display = ('id',
                    'tax_rate',
                    'name',
                    'account_activa',
                    'account_passiva')
    fieldsets = (('', {'fields': ('tax_rate',
                                  'name',
                                  'account_activa',
                                  'account_passiva')}),)
    allow_add = True


<file_sep>/koalixcrm/accounting/migrations/0007_auto_20180422_2105.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-04-22 21:05
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0004_auto_20171210_2126'),
        ('accounting', '0006_auto_20180422_2048'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='accountingperiod',
            name='template_set',
        ),
        migrations.AddField(
            model_name='accountingperiod',
            name='template_profit_loss_statement',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_profit_loss_statement_template_set', to='djangoUserExtension.DocumentTemplate', verbose_name='Referred template for profit, loss statement'),
        ),
        migrations.AddField(
            model_name='accountingperiod',
            name='template_set_balance_sheet',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_balancesheet_template_set', to='djangoUserExtension.DocumentTemplate', verbose_name='Referred template for balance sheet'),
        ),
    ]
<file_sep>/koalixcrm/crm/documents/sales_document.py
# -*- coding: utf-8 -*-

from datetime import *
from django.db import models
from django.contrib import admin, messages
from django.utils.translation import ugettext as _
from koalixcrm.crm.const.purpose import *
from koalixcrm.global_support_functions import xstr
from koalixcrm.crm.contact.phone_address import PhoneAddress
from koalixcrm.crm.contact.email_address import EmailAddress
from koalixcrm.crm.contact.postal_address import PostalAddress
from koalixcrm.crm.documents.sales_document_position import SalesDocumentPosition, SalesDocumentInlinePosition
from koalixcrm.djangoUserExtension.models import TextParagraphInDocumentTemplate, UserExtension
from koalixcrm.crm.product.product_type import ProductType
from koalixcrm.crm.exceptions import TemplateSetMissingInContract
import koalixcrm.crm.documents.calculations
from koalixcrm.crm.documents.pdf_export import PDFExport


class TextParagraphInSalesDocument(models.Model):
    sales_document = models.ForeignKey("SalesDocument")
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=2, choices=PURPOSESTEXTPARAGRAPHINDOCUMENTS)
    text_paragraph = models.TextField(verbose_name=_("Text"), blank=False, null=False)

    def create_paragraph(self, default_paragraph, sales_document):
        self.sales_document = sales_document
        self.purpose = default_paragraph.purpose
        self.text_paragraph = default_paragraph.text_paragraph
        self.save()
        return

    class Meta:
        app_label = "crm"
        verbose_name = _('Text Paragraph In Sales Document')
        verbose_name_plural = _('Text Paragraphs In Sales Documents')

    def __str__(self):
        return self.id.__str__()


class SalesDocument(models.Model):
    contract = models.ForeignKey("Contract",
                                 verbose_name=_('Contract'))
    external_reference = models.CharField(verbose_name=_("External Reference"),
                                          max_length=100,
                                          blank=True)
    discount = models.DecimalField(max_digits=5,
                                   decimal_places=2,
                                   verbose_name=_("Discount"),
                                   blank=True,
                                   null=True)
    description = models.CharField(verbose_name=_("Description"),
                                   max_length=100,
                                   blank=True,
                                   null=True)
    last_pricing_date = models.DateField(verbose_name=_("Pricing Date"),
                                         blank=True,
                                         null=True)
    last_calculated_price = models.DecimalField(max_digits=17,
                                                decimal_places=2,
                                                verbose_name=_("Price without Tax "),
                                                blank=True,
                                                null=True)
    last_calculated_tax = models.DecimalField(max_digits=17,
                                              decimal_places=2,
                                              verbose_name=_("Tax"),
                                              blank=True,
                                              null=True)
    customer = models.ForeignKey("Customer",
                                 verbose_name=_("Customer"))
    staff = models.ForeignKey('auth.User',
                              limit_choices_to={'is_staff': True},
                              blank=True,
                              verbose_name=_("Staff"),
                              related_name="db_relscstaff",
                              null=True)
    currency = models.ForeignKey("Currency", verbose_name=_("Currency"),
                                 blank=False, null=False)
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"),
                                            auto_now_add=True)
    custom_date_field = models.DateField(verbose_name=_("Custom Date"),
                                         blank=True,
                                         null=True)
    last_modification = models.DateTimeField(verbose_name=_("Last modified"),
                                             auto_now=True)
    last_modified_by = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True},
                                         verbose_name=_("Last modified by"),
                                         related_name="db_lstscmodified",
                                         null=True,
                                         blank="True")
    template_set = models.ForeignKey("djangoUserExtension.DocumentTemplate",
                                     verbose_name=_("Referred Template"),
                                     null=True,
                                     blank=True)
    derived_from_sales_document = models.ForeignKey("SalesDocument",
                                                    blank=True,
                                                    null=True)
    last_print_date = models.DateTimeField(verbose_name=_("Last printed"),
                                           blank=True,
                                           null=True)

    class Meta:
        app_label = "crm"
        verbose_name = _('Sales Document')
        verbose_name_plural = _('Sales Documents')

    def serialize_to_xml(self):
        from koalixcrm.crm.models import PostalAddressForContact
        from koalixcrm.crm.models import Currency
        from koalixcrm.crm.models import PurchaseOrder
        from koalixcrm.crm.models import SalesDocument
        from koalixcrm.crm.models import SalesDocumentPosition
        from koalixcrm.crm.models import Contact
        from django.contrib import auth
        objects = [self, ]
        position_class = SalesDocumentPosition
        objects += list(SalesDocument.objects.filter(id=self.id))
        if isinstance(self, PurchaseOrder):
            objects += list(Contact.objects.filter(id=self.supplier.id))
            objects += list(PostalAddressForContact.objects.filter(person=self.supplier.id))
            for address in list(PostalAddressForContact.objects.filter(person=self.supplier.id)):
                objects += list(PostalAddress.objects.filter(id=address.id))
        else:
            objects += list(Contact.objects.filter(id=self.customer.id))
            objects += list(PostalAddressForContact.objects.filter(person=self.customer.id))
            for address in list(PostalAddressForContact.objects.filter(person=self.customer.id)):
                objects += list(PostalAddress.objects.filter(id=address.id))
        objects += list(TextParagraphInSalesDocument.objects.filter(sales_document=self.id))
        objects += list(Currency.objects.filter(id=self.currency.id))
        objects += SalesDocumentPosition.add_positions(position_class, self)
        objects += list(auth.models.User.objects.filter(id=self.staff.id))
        objects += UserExtension.objects_to_serialize(self, self.staff)
        main_xml = PDFExport.write_xml(objects)
        return main_xml

    def is_complete_with_price(self):
        """ Checks whether the SalesContract is completed with a price, in case the
        SalesContract was not completed or the price calculation was not performed,
        the method returns false"""

        if self.last_pricing_date and self.last_calculated_price:
            return True
        else:
            return False

    def create_sales_document(self, calling_model):
        self.staff = calling_model.staff
        if isinstance(calling_model, koalixcrm.crm.documents.contract.Contract):
            self.contract = calling_model
            self.customer = calling_model.default_customer
            self.currency = calling_model.default_currency
            self.description = calling_model.description
            self.discount = 0
        elif isinstance(calling_model, SalesDocument):
            self.derived_from_sales_document = calling_model
            self.contract = calling_model.contract
            self.customer = calling_model.customer
            self.currency = calling_model.currency
            self.description = calling_model.description
            self.discount = calling_model.discount

    def attach_text_paragraphs(self):
        default_paragraphs = TextParagraphInDocumentTemplate.objects.filter(document_template=self.template_set)
        for default_paragraph in list(default_paragraphs):
            invoice_paragraph = TextParagraphInSalesDocument()
            invoice_paragraph.create_paragraph(default_paragraph, self)

    def attach_sales_document_positions(self, calling_model):
        if isinstance(calling_model, SalesDocument):
            sales_document_positions = SalesDocumentPosition.objects.filter(sales_document=calling_model.id)
            for sales_document_position in list(sales_document_positions):
                new_position = SalesDocumentPosition()
                new_position.create_position(sales_document_position, self)

    def create_pdf(self, template_set, printed_by):
        self.last_print_date = datetime.now()
        self.save()
        return koalixcrm.crm.documents.pdf_export.PDFExport.create_pdf(self, template_set, printed_by)

    def get_template_set(self):
        if self.template_set:
            return self.template_set
        else:
            raise TemplateSetMissingInContract((_("Template Set missing in Sales Document" + str(self))))

    def get_fop_config_file(self, template_set):
        template_set = self.get_template_set()
        return template_set.get_fop_config_file()

    def get_xsl_file(self, template_set):
        template_set = self.get_template_set()
        return template_set.get_xsl_file()

    def __str__(self):
        return _("Sales Contract") + ": " + str(self.id) + " " + _("from Contract") + ": " + str(self.contract.id)


class PostalAddressForSalesDocument(PostalAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCONTRACT)
    sales_document = models.ForeignKey("SalesDocument")

    class Meta:
        app_label = "crm"
        verbose_name = _('Postal Address For Sales Documents')
        verbose_name_plural = _('Postal Address For Sales Documents')

    def __str__(self):
        return xstr(self.pre_name) + ' ' + xstr(self.name) + ' ' + xstr(self.address_line_1)


class EmailAddressForSalesDocument(EmailAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCONTRACT)
    sales_document = models.ForeignKey("SalesDocument")

    class Meta:
        app_label = "crm"
        verbose_name = _('Email Address For Sales Documents')
        verbose_name_plural = _('Email Address For Sales Documents')

    def __str__(self):
        return str(self.email)


class PhoneAddressForSalesDocument(PhoneAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCONTRACT)
    sales_document = models.ForeignKey("SalesDocument")

    class Meta:
        app_label = "crm"
        verbose_name = _('Phone Address For Sales Documents')
        verbose_name_plural = _('Phone Address For Sales Documents')

    def __str__(self):
        return str(self.phone)


class SalesDocumentTextParagraph(admin.StackedInline):
    model = TextParagraphInSalesDocument
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('text_paragraph', 'purpose', )
        }),
    )
    allow_add = True


class SalesDocumentPostalAddress(admin.StackedInline):
    model = PostalAddressForSalesDocument
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('prefix',
                       'pre_name',
                       'name',
                       'address_line_1',
                       'address_line_2',
                       'address_line_3',
                       'address_line_4',
                       'zip_code',
                       'town',
                       'state',
                       'country',
                       'purpose')
        }),
    )
    allow_add = True


class SalesDocumentPhoneAddress(admin.TabularInline):
    model = PhoneAddressForSalesDocument
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('phone', 'purpose',)
        }),
    )
    allow_add = True


class SalesDocumentEmailAddress(admin.TabularInline):
    model = EmailAddressForSalesDocument
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('email', 'purpose',)
        }),
    )
    allow_add = True


class OptionSalesDocument(admin.ModelAdmin):
    list_display = ('id',
                    'description',
                    'contract',
                    'customer',
                    'currency',
                    'staff',
                    'last_modified_by',
                    'last_calculated_price',
                    'last_calculated_tax',
                    'last_pricing_date',
                    'last_modification',
                    'last_print_date')
    list_display_links = ('id',)
    list_filter = ('customer',
                   'contract',
                   'currency',
                   'staff',
                   'last_modification')
    ordering = ('-id',)
    search_fields = ('contract__id',
                     'customer__name',
                     'currency__description')

    fieldsets = (
        (_('Sales Contract'), {
            'fields': ('contract',
                       'description',
                       'customer',
                       'currency',
                       'discount',
                       'staff',
                       'external_reference',
                       'template_set',
                       'custom_date_field')
        }),
    )
    save_as = True
    inlines = [SalesDocumentInlinePosition, SalesDocumentTextParagraph,
               SalesDocumentPostalAddress, SalesDocumentPhoneAddress,
               SalesDocumentEmailAddress]

    def response_add(self, request, obj, post_url_continue=None):
        new_obj = self.after_saving_model_and_related_inlines(request, obj)
        new_obj.custom_date_field = date.today().__str__()
        return super(OptionSalesDocument, self).response_add(request=request,
                                                             obj=new_obj,
                                                             post_url_continue=post_url_continue)

    def response_change(self, request, new_object):
        obj = self.after_saving_model_and_related_inlines(request, new_object)
        return super(OptionSalesDocument, self).response_change(request, obj)

    def after_saving_model_and_related_inlines(self, request, obj):
        try:
            koalixcrm.crm.documents.calculations.Calculations.calculate_document_price(obj, date.today())
            self.message_user(request, "Successfully calculated Prices")
        except ProductType.NoPriceFound as e:
            self.message_user(request, "Unsuccessful in updating the Prices " + e.__str__(), level=messages.ERROR)
        return obj

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_modified_by = request.user
        else:
            obj.last_modified_by = request.user
            obj.staff = request.user
        obj.save()

    def create_quote(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.quote.Quote,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_quote.short_description = _("Create Quote")

    def create_invoice(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.invoice.Invoice,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_invoice.short_description = _("Create Invoice")

    def create_purchase_confirmation(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.purchaseconfirmation.PurchaseConfirmation,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_purchase_confirmation.short_description = _("Create Purchase Confirmation")

    def create_delivery_note(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.deliverynote.DeliveryNote,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_delivery_note.short_description = _("Create Delivery note")

    def create_payment_reminder(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.paymentreminder.PaymentReminder,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_payment_reminder.short_description = _("Create Payment Reminder")

    def create_purchase_order(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.purchaseorder.PurchaseOrder,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_purchase_order.short_description = _("Create Purchase Order")

    def create_pdf(self, request, queryset):
        from koalixcrm.crm.views.pdfexport import PDFExportView
        for obj in queryset:
            response = PDFExportView.export_pdf(self,
                                                request,
                                                obj,
                                                ("/admin/crm/"+obj.__class__.__name__.lower()+"/"),
                                                obj.template_set)
            return response

    create_pdf.short_description = _("Create PDF")

    def create_project(self, request, queryset):
        from koalixcrm.crm.views.create_task import CreateTaskView
        for obj in queryset:
            response = CreateTaskView.create_project(self,
                                                     request,
                                                     obj,
                                                     ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_project.short_description = _("Create Project")
<file_sep>/koalixcrm/crm/documents/contract.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _

from koalixcrm.plugin import *
from koalixcrm.crm.contact.phone_address import PhoneAddress
from koalixcrm.crm.contact.email_address import EmailAddress
from koalixcrm.crm.contact.postal_address import PostalAddress
from koalixcrm.crm.documents.invoice import Invoice
from koalixcrm.crm.documents.quote import Quote
from koalixcrm.crm.documents.purchase_order import PurchaseOrder
from koalixcrm.global_support_functions import xstr
from koalixcrm.crm.const.purpose import *
from koalixcrm.crm.documents.invoice import InlineInvoice
from koalixcrm.crm.documents.quote import InlineQuote
from koalixcrm.crm.reporting.generic_project_link import InlineGenericProjectLink
from koalixcrm.crm.exceptions import *
from koalixcrm.djangoUserExtension.models import UserExtension
import koalixcrm.crm.documents.calculations
import koalixcrm.crm.documents.pdf_export
from rest_framework import serializers


class PostalAddressForContract(PostalAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCONTRACT)
    contract = models.ForeignKey('Contract')

    class Meta:
        app_label = "crm"
        verbose_name = _('Postal Address For Contracts')
        verbose_name_plural = _('Postal Address For Contracts')

    def __str__(self):
        return xstr(self.prename) + ' ' + xstr(self.name) + ' ' + xstr(self.addressline1)


class PhoneAddressForContract(PhoneAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCONTRACT)
    contract = models.ForeignKey('Contract')

    class Meta:
        app_label = "crm"
        verbose_name = _('Phone Address For Contracts')
        verbose_name_plural = _('Phone Address For Contracts')

    def __str__(self):
        return str(self.phone)


class EmailAddressForContract(EmailAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCONTRACT)
    contract = models.ForeignKey('Contract')

    class Meta:
        app_label = "crm"
        verbose_name = _('Email Address For Contracts')
        verbose_name_plural = _('Email Address For Contracts')

    def __str__(self):
        return str(self.email)


class ContractPostalAddress(admin.StackedInline):
    model = PostalAddressForContract
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('prefix',
                       'pre_name',
                       'name',
                       'address_line_1',
                       'address_line_2',
                       'address_line_3',
                       'address_line_4',
                       'zip_code',
                       'town',
                       'state',
                       'country',
                       'purpose'),
        }),
    )
    allow_add = True


class ContractPhoneAddress(admin.TabularInline):
    model = PhoneAddressForContract
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('phone', 'purpose',)
        }),
    )
    allow_add = True


class ContractEmailAddress(admin.TabularInline):
    model = EmailAddressForContract
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('email',
                       'purpose',)
        }),
    )
    allow_add = True


class Contract(models.Model):
    staff = models.ForeignKey('auth.User',
                              limit_choices_to={'is_staff': True},
                              verbose_name=_("Staff"),
                              related_name="db_relcontractstaff",
                              blank=True,
                              null=True)
    description = models.TextField(verbose_name=_("Description"))
    default_customer = models.ForeignKey("Customer",
                                         verbose_name=_("Default Customer"),
                                         null=True,
                                         blank=True)
    default_supplier = models.ForeignKey("Supplier",
                                         verbose_name=_("Default Supplier"),
                                         null=True,
                                         blank=True)
    default_currency = models.ForeignKey("Currency",
                                         verbose_name=_("Default Currency"),
                                         blank=False,
                                         null=False)
    default_template_set = models.ForeignKey("djangoUserExtension.TemplateSet",
                                             verbose_name=_("Default Template Set"), null=True, blank=True)
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"),
                                            auto_now_add=True)
    last_modification = models.DateTimeField(verbose_name=_("Last modified"),
                                             auto_now=True)
    last_modified_by = models.ForeignKey('auth.User',
                                         limit_choices_to={'is_staff': True},
                                         verbose_name=_("Last modified by"),
                                         related_name="db_contractlstmodified")

    class Meta:
        app_label = "crm"
        verbose_name = _('Contract')
        verbose_name_plural = _('Contracts')

    def get_template_set(self, calling_model):
        if self.default_template_set:
            required_template_set = str(type(calling_model).__name__)
            return self.default_template_set.get_template_set(required_template_set)
        else:
            raise TemplateSetMissingInContract("The Contract has no Default Template Set selected")

    def create_from_reference(self, calling_model, staff):
        staff_user_extension = UserExtension.get_user_extension(staff.id)
        self.default_customer = calling_model
        self.default_currency = staff_user_extension.defaultCurrency
        self.default_template_set = staff_user_extension.defaultTemplateSet
        self.last_modified_by = staff
        self.staff = staff
        self.save()
        return self

    def create_invoice(self):
        invoice = Invoice()
        invoice.create_from_reference(self)
        return invoice

    def create_quote(self):
        quote = Quote()
        quote.create_from_reference(self)
        return quote

    def create_purchase_order(self):
        purchase_order = PurchaseOrder()
        purchase_order.create_from_reference(self)
        return purchase_order

    def __str__(self):
        return _("Contract") + " " + str(self.id)


class OptionContract(admin.ModelAdmin):
    list_display = ('id',
                    'description',
                    'default_customer',
                    'default_supplier',
                    'staff',
                    'default_currency',
                    'date_of_creation',
                    'last_modification',
                    'last_modified_by')
    list_display_links = ('id',)
    list_filter = ('default_customer',
                   'default_supplier',
                   'staff',
                   'default_currency')
    ordering = ('id', )
    search_fields = ('id',
                     'contract')
    fieldsets = (
        (_('Basics'), {
            'fields': ('description',
                       'default_customer',
                       'staff',
                       'default_supplier',
                       'default_currency',
                       'default_template_set')
        }),
    )
    inlines = [ContractPostalAddress,
               ContractPhoneAddress,
               ContractEmailAddress,
               InlineQuote,
               InlineInvoice,
               InlineGenericProjectLink]
    pluginProcessor = PluginProcessor()
    inlines.extend(pluginProcessor.getPluginAdditions("contractInlines"))

    def create_quote(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.quote.Quote,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_quote.short_description = _("Create Quote")

    def create_invoice(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.invoice.Invoice,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_invoice.short_description = _("Create Invoice")

    def create_purchase_confirmation(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.purchaseconfirmation.PurchaseConfirmation,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_purchase_confirmation.short_description = _("Create Purchase Confirmation")

    def create_delivery_note(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.deliverynote.DeliveryNote,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_delivery_note.short_description = _("Create Delivery note")

    def create_payment_reminder(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.paymentreminder.PaymentReminder,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_payment_reminder.short_description = _("Create Payment Reminder")

    def create_purchase_order(self, request, queryset):
        from koalixcrm.crm.views.newdocument import CreateNewDocumentView
        for obj in queryset:
            response = CreateNewDocumentView.create_new_document(self, request, obj,
                                           koalixcrm.crm.documents.purchaseorder.PurchaseOrder,
                                           ("/admin/crm/"+obj.__class__.__name__.lower()+"/"))
            return response

    create_purchase_order.short_description = _("Create Purchase Order")

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_modified_by = request.user
        else:
            obj.last_modified_by = request.user
            obj.staff = request.user
        obj.save()

    actions = ['create_quote', 'create_invoice', 'create_purchase_order']
    pluginProcessor = PluginProcessor()
    actions.extend(pluginProcessor.getPluginAdditions("contractActions"))


class ContractJSONSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Contract
        fields = ('id',
                  'description')
<file_sep>/koalixcrm/crm/factories/factory_purchase_confirmation.py
# -*- coding: utf-8 -*-

from koalixcrm.crm.models import PurchaseConfirmation
from koalixcrm.crm.factories.factory_sales_document import StandardSalesDocumentFactory


class StandardPurchaseConfirmationFactory(StandardSalesDocumentFactory):
    class Meta:
        model = PurchaseConfirmation


<file_sep>/koalixcrm/accounting/factories/factory_product_category.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.accounting.models import ProductCategory


class StandardProductCategoryFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProductCategory
        django_get_or_create = ('title',)

    title = "Service"
    profit_account = ""
    loss_account = ""
<file_sep>/koalixcrm/djangoUserExtension/exceptions.py
# -*- coding: utf-8 -*-

from django import forms


class TemplateSetMissingForUserExtension(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class UserExtensionMissing(Exception):

    def __init__(self, value):
        self.value = value
        self.view = "/koalixcrm/crm/reporting/user_extension_missing"

    def __str__(self):
        return repr(self.value)


class TooManyUserExtensionsAvailable(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class UserExtensionPhoneAddressMissing(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class UserExtensionEmailAddressMissing(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)<file_sep>/documentation/source/code_documentation/resource.rst
.. highlight:: rst

Resource
--------

.. automodule:: koalixcrm.crm.reporting.resource
   :members:

<file_sep>/koalixcrm/subscriptions/migrations/0004_auto_20181013_2213.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-13 22:13
from __future__ import unicode_literals

from django.db import migrations, models
from filebrowser.fields import FileBrowseField


def fill_subscription_type_backup_table(apps, schema_editor):
    Subscription = apps.get_model("subscriptions", "Subscription")
    SubscriptionType = apps.get_model("subscriptions", "SubscriptionType")
    SubscriptionTypeBackup = apps.get_model("subscriptions", "SubscriptionTypeBackup")
    db_alias = schema_editor.connection.alias
    all_subscription_types = SubscriptionType.objects.using(db_alias).all()
    for subscription_type in all_subscription_types:
        subscription_type_backup = SubscriptionTypeBackup.objects.using(db_alias).create(
            cancellation_period=subscription_type.cancelationPeriod,
            automatic_contract_extension=subscription_type.automaticContractExtension,
            automatic_contract_extension_reminder=subscription_type.automaticContractExtensionReminder,
            minimum_duration=subscription_type.minimumDuration,
            payment_interval=subscription_type.paymentIntervall,
            contract_document=subscription_type.contractDocument,
            description=subscription_type.description,
            title=subscription_type.title,
            product_number=subscription_type.product_number,
            default_unit=subscription_type.default_unit,
            date_of_creation=subscription_type.date_of_creation,
            last_modification=subscription_type.last_modification,
            last_modified_by=subscription_type.last_modified_by,
            tax=subscription_type.tax,
            old_id=subscription_type.id)
        subscription_type_backup.save()
        subscriptions = Subscription.objects.using(db_alias).all()
        if len(subscriptions) > 0:
            for subscription in subscriptions:
                if subscription.subscriptiontype is not None:
                    subscription.subscription_type_backup = subscription.subscriptiontype.id
                    subscription.save()


def reverse_func(apps, schema_editor):
    return 1


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0047_work_worked_hours'),
        ('subscriptions', '0003_auto_20171110_1732'),
    ]

    operations = [
        migrations.RenameField(
            model_name='subscriptionevent',
            old_name='eventdate',
            new_name='event_date',
        ),
        migrations.AddField(
            model_name='Subscription',
            name='subscription_type_backup',
            field=models.IntegerField(blank=True, null=True, verbose_name='Subscription Type Backup'),
        ),
        migrations.CreateModel(
            name='SubscriptionTypeBackup',
            fields=[
                ('cancellation_period', models.IntegerField(verbose_name="Cancellation Period (months)",
                                                            blank=True,
                                                            null=True)),
                ('automatic_contract_extension', models.IntegerField(
                    verbose_name="Automatic Contract Extension (months)",
                    blank=True,
                    null=True)),
                ('automatic_contract_extension_reminder', models.IntegerField(
                    verbose_name="Automatic Contract Extension Reminder (days)",
                    blank=True,
                    null=True)),
                ('minimum_duration', models.IntegerField(verbose_name="Minimum Contract Duration",
                                                         blank=True,
                                                         null=True)),
                ('payment_interval', models.IntegerField(verbose_name="Payment Interval (days)",
                                                         blank=True,
                                                         null=True)),
                ('contract_document', FileBrowseField(verbose_name="Contract Documents",
                                                      blank=True,
                                                      null=True,
                                                      max_length=200)),
                ('description', models.TextField(verbose_name="Description",
                                                 null=True,
                                                 blank=True)),
                ('title', models.CharField(verbose_name="Title",
                                           max_length=200)),
                ('product_number', models.IntegerField(verbose_name="Product Number")),
                ('default_unit', models.ForeignKey("crm.Unit",
                                                   verbose_name="Unit")),
                ('date_of_creation', models.DateTimeField(verbose_name="Created at",
                                                          auto_now_add=True)),
                ('last_modification', models.DateTimeField(verbose_name="Last modified",
                                                           auto_now=True)),
                ('last_modified_by', models.ForeignKey('auth.User',
                                                       limit_choices_to={'is_staff': True},
                                                       verbose_name="Last modified by",
                                                       null=True,
                                                       blank="True")),
                ('tax', models.ForeignKey("crm.Tax",
                                          blank=False)),
                ('old_id', models.IntegerField(blank=True,
                                               null=True,
                                               verbose_name='Subscription Type Backup')),
            ],
            options={
                'verbose_name': 'SubscriptionTypeBackup',
                'verbose_name_plural': 'SubscriptionTypeBackups',
            },
        ),
        migrations.RunPython(fill_subscription_type_backup_table, reverse_func),
        migrations.RemoveField(
            model_name='subscription',
            name='subscriptiontype',
        ),
        migrations.DeleteModel('SubscriptionType'),
    ]
<file_sep>/koalixcrm/crm/views/newdocument.py
# -*- coding: utf-8 -*-
from django.http import Http404
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from django.contrib import messages
from koalixcrm.crm.exceptions import *


class CreateNewDocumentView:
    def create_new_document(calling_model_admin, request, calling_model, requested_document_type, redirect_to):
        """This method exports PDFs provided by different Models in the crm application

            Args:
              calling_model_admin (ModelAdmin):  The calling ModelAdmin must be provided for error message response.
              request: The request User is to know where to save the error message
              calling_model (Contract or SalesDocument):  The model from which a new document shall be created
              requested_document_type (str): The document type name that shall be created
              redirect_to (str): String that describes to where the method should redirect in case of an error

            Returns:
              HTTpResponse with a PDF when successful
              HTTpResponseRedirect when not successful

            Raises:
              raises Http404 exception if anything goes wrong"""
        from koalixcrm.crm.documents.contract import Contract
        try:
            new_document = requested_document_type()
            new_document.create_from_reference(calling_model)
            calling_model_admin.message_user(request, _(str(new_document) +
                                                        " created"))
            response = HttpResponseRedirect('/admin/crm/'+
                                            new_document.__class__.__name__.lower()+
                                            '/'+
                                            str(new_document.id))
        except (TemplateSetMissingInContract, TemplateMissingInTemplateSet) as e:
            if isinstance(calling_model, Contract):
                contract = calling_model
            else:
                contract = calling_model.contract
            if isinstance(e, TemplateSetMissingInContract):
                response = HttpResponseRedirect('/admin/crm/contract/'+
                                                str(contract.id))
                calling_model_admin.message_user(request, _("Missing Templateset "),
                                                 level=messages.ERROR)
            elif isinstance(e, TemplateMissingInTemplateSet):
                response = HttpResponseRedirect('/admin/djangoUserExtension/templateset/' +
                                                str(contract.default_template_set.id))
                calling_model_admin.message_user(request,
                                                 (_("Missing template for ")+
                                                  new_document.__class__.__name__),
                                                 level=messages.ERROR)
            else:
                raise Http404
        return response
<file_sep>/documentation/source/code_documentation/project.rst
.. highlight:: rst

Project
-------

.. automodule:: koalixcrm.crm.reporting.project
   :members:

<file_sep>/koalixcrm/crm/views/time_tracking.py
# -*- coding: utf-8 -*-

import datetime
from django.http import HttpResponseRedirect, Http404
from django.contrib import messages
from django.shortcuts import render
from django.utils.translation import ugettext as _
from django.template.context_processors import csrf
from django.contrib.auth.decorators import login_required
from koalixcrm.djangoUserExtension.models import UserExtension
from koalixcrm.crm.exceptions import ReportingPeriodNotFound
from koalixcrm.crm.exceptions import UserIsNoHumanResource
from koalixcrm.djangoUserExtension.exceptions import UserExtensionMissing, TooManyUserExtensionsAvailable
from koalixcrm.crm.views.range_selection_form import RangeSelectionForm
from koalixcrm.crm.views.work_entry_formset import BaseWorkEntryFormset
from koalixcrm.crm.reporting.human_resource import HumanResource


@login_required
def work_report(request):
    try:
        human_resource = HumanResource.objects.filter(user=UserExtension.get_user_extension(request.user))
        if len(human_resource) == 0:
            error_message = "User "+request.user.__str__()+" is not registered as human resource"
            raise UserIsNoHumanResource(error_message)
        if request.POST.get('post'):
            if 'cancel' in request.POST:
                return HttpResponseRedirect('/admin/')
            elif 'save' in request.POST:
                range_selection_form = RangeSelectionForm(request.POST)
                if range_selection_form.is_valid():
                    formset = BaseWorkEntryFormset.load_formset(range_selection_form,
                                                                request)
                    if not formset.is_valid():
                        c = {'range_selection_form': range_selection_form,
                             'formset': formset}
                        c.update(csrf(request))
                        return render(request, 'crm/admin/time_reporting.html', c)
                    else:
                        for form in formset:
                            form.update_work(request)
                        messages.success(request, _('you have successfully updated your work'))
                formset = BaseWorkEntryFormset.create_updated_formset(range_selection_form,
                                                                      human_resource)
                range_selection_form.update_from_input()
                c = {'range_selection_form': range_selection_form,
                     'formset': formset}
                c.update(csrf(request))
                return render(request, 'crm/admin/time_reporting.html', c)
            return HttpResponseRedirect('/admin/')
        else:
            datetime_now = datetime.datetime.today()
            to_date = (datetime_now + datetime.timedelta(days=30)).date()
            from_date = datetime_now.date()
            range_selection_form = RangeSelectionForm.create_range_selection_form(from_date, to_date)
            formset = BaseWorkEntryFormset.create_new_formset(from_date,
                                                              to_date,
                                                              human_resource)
            c = {'formset': formset,
                 'range_selection_form': range_selection_form}
            c.update(csrf(request))
            return render(request, 'crm/admin/time_reporting.html', c)
    except (UserExtensionMissing,
            ReportingPeriodNotFound,
            TooManyUserExtensionsAvailable,
            UserIsNoHumanResource) as e:
        if isinstance(e, UserExtensionMissing):
            return HttpResponseRedirect(e.view)
        elif isinstance(e, ReportingPeriodNotFound):
            return HttpResponseRedirect(e.view)
        elif isinstance(e, TooManyUserExtensionsAvailable):
            return HttpResponseRedirect(e.view)
        elif isinstance(e, UserIsNoHumanResource):
            return HttpResponseRedirect(e.view)
        else:
            raise Http404
<file_sep>/koalixcrm/crm/factories/factory_resource_price.py
# -*- coding: utf-8 -*-

import factory
import datetime
from koalixcrm.crm.models import ResourcePrice
from koalixcrm.crm.factories.factory_resource import StandardResourceFactory
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.crm.factories.factory_customer_group import StandardCustomerGroupFactory
from koalixcrm.test_support_functions import make_date_utc


class StandardResourcePriceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ResourcePrice

    resource = factory.SubFactory(StandardResourceFactory)
    unit = factory.SubFactory(StandardUnitFactory)
    currency = factory.SubFactory(StandardCurrencyFactory)
    customer_group = factory.SubFactory(StandardCustomerGroupFactory)
    price = "100.50"
    valid_from = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    valid_until = make_date_utc(datetime.datetime(2024, 6, 15, 00))


class HighResourcePriceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ResourcePrice

    resource = factory.SubFactory(StandardResourceFactory)
    unit = factory.SubFactory(StandardUnitFactory)
    currency = factory.SubFactory(StandardCurrencyFactory)
    customer_group = factory.SubFactory(StandardCustomerGroupFactory)
    price = "250.50"
    valid_from = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    valid_until = make_date_utc(datetime.datetime(2024, 6, 15, 00))
<file_sep>/koalixcrm/crm/models.py
# -*- coding: utf-8 -*-

from koalixcrm.crm.contact.contact import *
from koalixcrm.crm.contact.customer_group import *
from koalixcrm.crm.contact.customer import *
from koalixcrm.crm.contact.postal_address import *
from koalixcrm.crm.contact.customer_billing_cycle import *
from koalixcrm.crm.contact.email_address import *
from koalixcrm.crm.contact.phone_address import *
from koalixcrm.crm.contact.supplier import *

from koalixcrm.crm.documents.contract import Contract, PostalAddressForContract
from koalixcrm.crm.documents.contract import PhoneAddressForContract, EmailAddressForContract
from koalixcrm.crm.documents.sales_document_position import Position, SalesDocumentPosition
from koalixcrm.crm.documents.sales_document import SalesDocument, PostalAddressForSalesDocument
from koalixcrm.crm.documents.sales_document import EmailAddressForSalesDocument, PhoneAddressForSalesDocument
from koalixcrm.crm.documents.sales_document import TextParagraphInSalesDocument
from koalixcrm.crm.documents.invoice import Invoice
from koalixcrm.crm.documents.purchase_confirmation import PurchaseConfirmation
from koalixcrm.crm.documents.purchase_order import PurchaseOrder
from koalixcrm.crm.documents.quote import Quote
from koalixcrm.crm.documents.payment_reminder import PaymentReminder
from koalixcrm.crm.documents.delivery_note import DeliveryNote

from koalixcrm.crm.product.currency import *
from koalixcrm.crm.product.price import *
from koalixcrm.crm.product.product import *
from koalixcrm.crm.product.product_type import *
from koalixcrm.crm.product.product_price 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.agreement import *
from koalixcrm.crm.reporting.agreement_status import *
from koalixcrm.crm.reporting.agreement_type import *
from koalixcrm.crm.reporting.agreement_status import *
from koalixcrm.crm.reporting.estimation import *
from koalixcrm.crm.reporting.estimation_status import *
from koalixcrm.crm.reporting.human_resource import *
from koalixcrm.crm.reporting.resource_manager import *
from koalixcrm.crm.reporting.resource_type import *
from koalixcrm.crm.reporting.resource_price import *
from koalixcrm.crm.reporting.generic_task_link import *
from koalixcrm.crm.reporting.task import *
from koalixcrm.crm.reporting.task_link_type import *
from koalixcrm.crm.reporting.task_status import *
from koalixcrm.crm.reporting.work import *
from koalixcrm.crm.reporting.project import *
from koalixcrm.crm.reporting.project_link_type import *
from koalixcrm.crm.reporting.project_status import *
from koalixcrm.crm.reporting.generic_project_link import *
from koalixcrm.crm.reporting.reporting_period import *
from koalixcrm.crm.reporting.reporting_period_status import *






<file_sep>/koalixcrm/crm/views/reporting_period_missing.py
# -*- coding: utf-8 -*-

from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render
from django.template.context_processors import csrf
from django.contrib.admin import helpers
from django.contrib.admin.widgets import *
from koalixcrm.djangoUserExtension.exceptions import TooManyUserExtensionsAvailable


class ReportingPeriodMissingForm(forms.Form):
    NEXT_STEPS = (
        ('add_reporting_period', 'Add Reporting Period'),
        ('return_to_start', 'Return To Start'),
    )
    next_steps = forms.ChoiceField(required=True,
                                   widget=forms.Select,
                                   choices=NEXT_STEPS)


def reporting_period_missing(request):
    try:
        if request.POST.get('post'):
            if 'confirm_selection' in request.POST:
                reporting_period_missing_form = ReportingPeriodMissingForm(request.POST)
                if reporting_period_missing_form.is_valid():
                    if reporting_period_missing_form.cleaned_data['next_steps'] == 'return_to_start':
                        return HttpResponseRedirect('/admin/')
                    else:
                        return HttpResponseRedirect('/admin/crm/reportingperiod/add/')
        else:
            reporting_period_missing_form = ReportingPeriodMissingForm(initial={'next_steps': 'create_user_extension'})
        title = "User Extension Missing"
        description = "The operation you have selected requires an existing 'Reporting Period' element to exist" \
                      "for the project. For one of the required project this 'Reporting Period' did not exist" \
                      "Please choose one of the available options and proceed with the intended " \
                      "operation afterwards"
        c = {'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
             'form': reporting_period_missing_form,
             'description': description,
             'title': title}
        c.update(csrf(request))
        return render(request, 'crm/admin/exception.html', c)
    except TooManyUserExtensionsAvailable:
        raise Http404


<file_sep>/koalixcrm/crm/factories/factory_agreement_type.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import AgreementType


class StandardAgreementTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = AgreementType

    title = 'This is a test Agreement Type'
    description = "This is a description of such a Agreement Type"
<file_sep>/koalixcrm/accounting/migrations/0001_initial.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:02
from __future__ import unicode_literals

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Account',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('accountNumber', models.IntegerField(verbose_name='Account Number')),
                ('title', models.CharField(max_length=50, verbose_name='Account Title')),
                ('accountType', models.CharField(
                    choices=[('E', 'Earnings'), ('S', 'Spendings'), ('L', 'Liabilities'), ('A', 'Assets')],
                    max_length=1, verbose_name='Account Type')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Description')),
                ('originalAmount',
                 models.DecimalField(decimal_places=2, default=0.0, max_digits=20, verbose_name='Original Amount')),
                ('isopenreliabilitiesaccount', models.BooleanField(verbose_name='Is The Open Liabilities Account')),
                ('isopeninterestaccount', models.BooleanField(verbose_name='Is The Open Interests Account')),
                ('isProductInventoryActiva', models.BooleanField(verbose_name='Is a Product Inventory Account')),
                ('isACustomerPaymentAccount', models.BooleanField(verbose_name='Is a Customer Payment Account')),
            ],
            options={
                'ordering': ['accountNumber'],
                'verbose_name': 'Account',
                'verbose_name_plural': 'Account',
            },
        ),
        migrations.CreateModel(
            name='AccountingPeriod',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=200, verbose_name='Title')),
                ('begin', models.DateField(verbose_name='Begin')),
                ('end', models.DateField(verbose_name='End')),
            ],
            options={
                'verbose_name': 'Accounting Period',
                'verbose_name_plural': 'Accounting Periods',
            },
        ),
        migrations.CreateModel(
            name='Booking',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('amount', models.DecimalField(decimal_places=2, max_digits=20, verbose_name='Amount')),
                ('description', models.CharField(blank=True, max_length=120, null=True, verbose_name='Description')),
                ('bookingDate', models.DateTimeField(verbose_name='Booking at')),
                ('dateofcreation', models.DateTimeField(auto_now=True, verbose_name='Created at')),
                ('lastmodification', models.DateTimeField(auto_now_add=True, verbose_name='Last modified')),
                ('accountingPeriod',
                 models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounting.AccountingPeriod',
                                   verbose_name='AccountingPeriod')),
            ],
            options={
                'verbose_name': 'Booking',
                'verbose_name_plural': 'Bookings',
            },
        ),
        migrations.CreateModel(
            name='ProductCategorie',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=50, verbose_name='Product Categorie Title')),
                ('lossAccount',
                 models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_loss_account',
                                   to='accounting.Account', verbose_name='Loss Account')),
                ('profitAccount',
                 models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_profit_account',
                                   to='accounting.Account', verbose_name='Profit Account')),
            ],
            options={
                'verbose_name': 'Product Categorie',
                'verbose_name_plural': 'Product Categories',
            },
        ),
    ]
<file_sep>/koalixcrm/accounting/migrations/0004_auto_20171009_1949.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-09 19:49
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('accounting', '0003_remove_account_originalamount'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='account',
            options={'ordering': ['accountNumber'], 'verbose_name': 'Konto', 'verbose_name_plural': 'Konto'},
        ),
        migrations.AlterModelOptions(
            name='accountingperiod',
            options={'verbose_name': 'Abrechnungszeitraum', 'verbose_name_plural': 'Abrechnungszeiträume'},
        ),
        migrations.AlterModelOptions(
            name='booking',
            options={'verbose_name': 'Buchung', 'verbose_name_plural': 'Buchungen'},
        ),
        migrations.AlterModelOptions(
            name='productcategorie',
            options={'verbose_name': 'Produktekategorie', 'verbose_name_plural': 'Produktekategorien'},
        ),
        migrations.AlterField(
            model_name='account',
            name='accountNumber',
            field=models.IntegerField(verbose_name='Kontonummer'),
        ),
        migrations.AlterField(
            model_name='account',
            name='accountType',
            field=models.CharField(choices=[('E', 'Ertrag (Einnahmen)'), ('S', 'Aufwand (Ausgaben)'), ('L', 'Passiva (Schulden)'), ('A', 'Aktiva (Vermögen)')], max_length=1, verbose_name='Kontotyp'),
        ),
        migrations.AlterField(
            model_name='account',
            name='description',
            field=models.TextField(blank=True, null=True, verbose_name='Beschreibung'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isACustomerPaymentAccount',
            field=models.BooleanField(verbose_name='Konto für Kundeneinzahlungen'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isProductInventoryActiva',
            field=models.BooleanField(verbose_name='Konto für Lager'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isopeninterestaccount',
            field=models.BooleanField(verbose_name='Konto für offene Forderungen'),
        ),
        migrations.AlterField(
            model_name='account',
            name='isopenreliabilitiesaccount',
            field=models.BooleanField(verbose_name='Konto für offene Rechungen'),
        ),
        migrations.AlterField(
            model_name='account',
            name='title',
            field=models.CharField(max_length=50, verbose_name='Kontobezeichnung'),
        ),
        migrations.AlterField(
            model_name='accountingperiod',
            name='begin',
            field=models.DateField(verbose_name='Anfang'),
        ),
        migrations.AlterField(
            model_name='accountingperiod',
            name='end',
            field=models.DateField(verbose_name='Ende'),
        ),
        migrations.AlterField(
            model_name='accountingperiod',
            name='title',
            field=models.CharField(max_length=200, verbose_name='Titel'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='accountingPeriod',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounting.AccountingPeriod', verbose_name='Abrechnungszeitraum'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='amount',
            field=models.DecimalField(decimal_places=2, max_digits=20, verbose_name='Betrag'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='bookingDate',
            field=models.DateTimeField(verbose_name='Buchung am'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='bookingReference',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Invoice', verbose_name='Buchungsreferenz'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='dateofcreation',
            field=models.DateTimeField(auto_now=True, verbose_name='Erstelldatum'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='description',
            field=models.CharField(blank=True, max_length=120, null=True, verbose_name='Beschreibung'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='fromAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_fromaccount', to='accounting.Account', verbose_name='Vom Konto (Soll)'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='lastmodification',
            field=models.DateTimeField(auto_now_add=True, verbose_name='Zuletzt geändert'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='lastmodifiedby',
            field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_lstmodified', to=settings.AUTH_USER_MODEL, verbose_name='Zuletzt geändert durch'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='staff',
            field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_refstaff', to=settings.AUTH_USER_MODEL, verbose_name='Zuständiger Mitarbeiter'),
        ),
        migrations.AlterField(
            model_name='booking',
            name='toAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_toaccount', to='accounting.Account', verbose_name='Zu Konto (Haben)'),
        ),
        migrations.AlterField(
            model_name='productcategorie',
            name='lossAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_loss_account', to='accounting.Account', verbose_name='Aufwand Konto'),
        ),
        migrations.AlterField(
            model_name='productcategorie',
            name='profitAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_profit_account', to='accounting.Account', verbose_name='Ertrag Konto'),
        ),
        migrations.AlterField(
            model_name='productcategorie',
            name='title',
            field=models.CharField(max_length=50, verbose_name='Produktekategorie'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_tax.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Tax


class StandardTaxFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Tax
        django_get_or_create = ('name',)

    tax_rate = "7.7"
    name = "Swiss MwSt 7.7%"
    account_activa = None
    account_passiva = None
<file_sep>/koalixcrm/crm/migrations/0049_auto_20181014_2258.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-12 20:56
from __future__ import unicode_literals
from django.db import migrations


def reverse_func(apps, schema_editor):
    return 1


def backup_identifiers(apps, schema_editor):
    Position = apps.get_model("crm", "Position")
    CustomerGroupTransform = apps.get_model("crm", "CustomerGroupTransform")
    Price = apps.get_model("crm", "Price")
    UnitTransform = apps.get_model("crm", "UnitTransform")
    db_alias = schema_editor.connection.alias
    all_positions = Position.objects.using(db_alias).all()
    for position in all_positions:
        position.product_backup = position.product.id
        position.save()
    all_customer_group_transforms = CustomerGroupTransform.objects.using(db_alias).all()
    for customer_group_transform in all_customer_group_transforms:
        customer_group_transform.product_backup = customer_group_transform.product.id
        customer_group_transform.save()
    all_prices = Price.objects.using(db_alias).all()
    for price in all_prices:
        price.product_backup = price.product.id
        price.save()
    all_unit_transforms = UnitTransform.objects.using(db_alias).all()
    for unit_transform in all_unit_transforms:
        unit_transform.product_backup = unit_transform.product.id
        unit_transform.save()


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0048_auto_20181012_2056'),
    ]

    operations = [
        migrations.RunPython(backup_identifiers, reverse_func),
    ]
<file_sep>/projectsettings/dashboard.py
"""
This file was generated with the customdashboard management command and
contains the class for the main dashboard.

To activate your index dashboard add the following to your settings.py::
    GRAPPELLI_INDEX_DASHBOARD = 'koalixcrm.dashboard.CustomIndexDashboard'
"""

from django.utils.translation import ugettext_lazy as _
from grappelli.dashboard import modules, Dashboard
from grappelli.dashboard.utils import get_admin_site_name
from koalixcrm.version import KOALIXCRM_VERSION


class CustomIndexDashboard(Dashboard):
    """
    Custom index dashboard for www.
    """

    def init_with_context(self, context):
        self.children.append(modules.Group(
            _('koalixcrm Version' + KOALIXCRM_VERSION),
            column=1,
            collapsible=True,
            children=[
                   modules.ModelList(
                    _('Sales Documents and Contracts'),
                    column=1,
                    css_classes=('collapse closed',),
                    models=('koalixcrm.crm.documents.contract.Contract',
                            'koalixcrm.crm.documents.quote.Quote',
                            'koalixcrm.crm.documents.purchaseconfirmation.PurchaseConfirmation',
                            'koalixcrm.crm.documents.deliverynote.DeliveryNote',
                            'koalixcrm.crm.documents.invoice.Invoice',
                            'koalixcrm.crm.documents.paymentreminder.PaymentReminder',),
                    ),
                    modules.ModelList(
                        _('Scheduler'),
                        column=1,
                        css_classes=('collapse closed',),
                        models=('koalixcrm.crm.contact.contact.CallForContact',
                                'koalixcrm.crm.contact.contact.VisitForContact',),
                    ),
                    modules.ModelList(
                        _('Products'),
                        column=1,
                        css_classes=('collapse closed',),
                        models=('koalixcrm.crm.product.product.Product',),
                    ),
                    modules.ModelList(
                        _('Contacts'),
                        column=1,
                        css_classes=('collapse closed',),
                        models=('koalixcrm.crm.contact.customer.Customer',
                                'koalixcrm.crm.contact.supplier.Supplier',
                                'koalixcrm.crm.contact.person.Person',),
                    ),
                    modules.ModelList(
                        _('Accounting'),
                        column=1,
                        css_classes=('collapse closed',),
                        models=('koalixcrm.accounting.*',),
                    ),
                    modules.ModelList(
                        _('Projects'),
                        column=1,
                        css_classes=('collapse closed',),
                        models=('koalixcrm.crm.reporting.*',
                                'koalixcrm.crm.documents.purchaseorder.PurchaseOrder',),
                    ),
                    modules.LinkList(
                        _('Report Work And Expenses'),
                        column=1,
                        children=[{'title': _('Time Tracking'),
                                   'url': '/koalixcrm/crm/reporting/time_tracking/',
                                   'external': False}]
                    )

            ]
        ))

        # append a group for "Administration" & "Applications"
        self.children.append(modules.Group(
            _('Group: Administration & Applications'),
            column=1,
            collapsible=True,
            children=[
                modules.ModelList(
                    _('Administration'),
                    column=1,
                    collapsible=False,
                    models=('django.contrib.*',),
                ),
                modules.ModelList(
                    _('koalixcrm Settings'),
                    column=1,
                    css_classes=('collapse closed',),
                    models=('koalixcrm.crm.contact.customerbillingcycle.CustomerBillingCycle',
                            'koalixcrm.crm.contact.customergroup.CustomerGroup',
                            'koalixcrm.crm.product.tax.Tax',
                            'koalixcrm.crm.product.unit.Unit',
                            'koalixcrm.crm.product.currency.Currency',
                            'koalixcrm.djangoUserExtension.*'),
                ),
            ]
        ))

        # append another link list module for "support".
        self.children.append(modules.LinkList(
            _('Media Management'),
            column=2,
            children=[
                {
                    'title': _('FileBrowser'),
                    'url': '/admin/filebrowser/browse/',
                    'external': False,
                },
            ]
        ))

        # append another link list module for "support".
        self.children.append(modules.LinkList(
            _('Support'),
            column=2,
            children=[
                {
                    'title': _('koalixcrm on github'),
                    'url': 'https://github.com/scaphilo/koalixcrm/',
                    'external': True,
                },
                {
                    'title': _('Django Documentation'),
                    'url': 'http://docs.djangoproject.com/',
                    'external': True,
                },
                {
                    'title': _('Grappelli Documentation'),
                    'url': 'http://packages.python.org/django-grappelli/',
                    'external': True,
                },
                {
                    'title': _('Grappelli Google-Code'),
                    'url': 'http://code.google.com/p/django-grappelli/',
                    'external': True,
                },
            ]
        ))

        # append a feed module
        self.children.append(modules.Feed(
            _('Latest Django News'),
            column=2,
            feed_url='http://www.djangoproject.com/rss/weblog/',
            limit=5
        ))

        # append a recent actions module
        self.children.append(modules.RecentActions(
            _('Recent Actions'),
            limit=5,
            collapsible=False,
            column=3,
        ))


<file_sep>/koalixcrm/crm/const/postaladdressprefix.py
# -*- coding: utf-8 -*

from django.utils.translation import ugettext as _

POSTALADDRESSPREFIX = (
    ('F', _('Company')),
    ('W', _('Mrs')),
    ('H', _('Mr')),
    ('G', _('Ms')),
)
<file_sep>/koalixcrm/crm/migrations/0030_auto_20180611_1930.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-11 19:30
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('crm', '0029_auto_20180611_1903'),
    ]

    operations = [
        migrations.AddField(
            model_name='generictasklink',
            name='date_of_creation',
            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created at'),
            preserve_default=False,
        ),
        migrations.AddField(
            model_name='generictasklink',
            name='last_modified_by',
            field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, related_name='db_task_link_last_modified', to=settings.AUTH_USER_MODEL, verbose_name='Last modified by'),
            preserve_default=False,
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_reporting_period_status.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import ReportingPeriodStatus


class InPreparationReportingPeriodStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ReportingPeriodStatus
        django_get_or_create = ('title',)

    title = "In Preparation"
    description = "In Preparation, reporting not yet enabled"
    is_done = False


class ReportingReportingPeriodStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ReportingPeriodStatus
        django_get_or_create = ('title',)

    title = "Reporting"
    description = "Reporting is enabled"
    is_done = False


class DoneReportingPeriodStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ReportingPeriodStatus
        django_get_or_create = ('title',)

    title = "Done"
    description = "This reporting period is done"
    is_done = True
<file_sep>/koalixcrm/crm/migrations/0047_work_worked_hours.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-08-06 20:57
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0046_auto_20180805_2051'),
    ]

    operations = [
        migrations.AddField(
            model_name='work',
            name='worked_hours',
            field=models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='Worked Hours'),
        ),
    ]
<file_sep>/koalixcrm/crm/migrations/0018_auto_20180111_2031.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-11 20:31
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0017_auto_20180111_2022'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='textparagraphinsalesdocument',
            options={'verbose_name': 'Text Paragraph In Sales Document', 'verbose_name_plural': 'Text Paragraphs In Sales Documents'},
        ),
        migrations.RenameField(
            model_name='deliverynote',
            old_name='salescontract_ptr',
            new_name='salesdocument_ptr',
        ),
        migrations.RenameField(
            model_name='emailaddressforsalesdocument',
            old_name='contract',
            new_name='sales_document',
        ),
        migrations.RenameField(
            model_name='invoice',
            old_name='salescontract_ptr',
            new_name='salesdocument_ptr',
        ),
        migrations.RenameField(
            model_name='paymentreminder',
            old_name='salescontract_ptr',
            new_name='salesdocument_ptr',
        ),
        migrations.RenameField(
            model_name='phoneaddressforsalesdocument',
            old_name='contract',
            new_name='sales_document',
        ),
        migrations.RenameField(
            model_name='postaladdressforsalesdocument',
            old_name='contract',
            new_name='sales_document',
        ),
        migrations.RenameField(
            model_name='purchaseconfirmation',
            old_name='salescontract_ptr',
            new_name='salesdocument_ptr',
        ),
        migrations.RenameField(
            model_name='quote',
            old_name='salescontract_ptr',
            new_name='salesdocument_ptr',
        ),
        migrations.RenameField(
            model_name='salesdocumentposition',
            old_name='contract',
            new_name='sales_document',
        ),
        migrations.RenameField(
            model_name='textparagraphinsalesdocument',
            old_name='sales_contract',
            new_name='sales_document',
        ),
    ]
<file_sep>/koalixcrm/djangoUserExtension/user_extension/document_template.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _

from filebrowser.fields import FileBrowseField

from koalixcrm.djangoUserExtension.user_extension.text_paragraph import InlineTextParagraph
from koalixcrm.global_support_functions import xstr
from koalixcrm.crm.exceptions import *


class DocumentTemplate(models.Model):
    title = models.CharField(verbose_name=_("Title"),
                             max_length=100,
                             blank=True,
                             null=True)
    xsl_file = FileBrowseField(verbose_name=_("XSL File"),
                               max_length=200)
    fop_config_file = FileBrowseField(verbose_name=_("FOP Configuration File"),
                                      blank=True,
                                      null=True,
                                      max_length=200)
    logo = FileBrowseField(verbose_name=_("Logo for the PDF generation"),
                           blank=True,
                           null=True,
                           max_length=200)

    def get_fop_config_file(self):
        if self.fop_config_file:
            return self.fop_config_file
        else:
            raise TemplateFOPConfigFileMissing(_("Fop Config File missing in Document Template"+str(self)))

    def get_xsl_file(self):
        if self.xsl_file:
            return self.xsl_file
        else:
            raise TemplateXSLTFileMissing(_("XSL Template missing in Document Template"+str(self)))

    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Document template')
        verbose_name_plural = _('Document templates')

    def __str__(self):
        return xstr(self.id) + ' ' + xstr(self.title.__str__())


class InvoiceTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Invoice template')
        verbose_name_plural = _('Invoice templates')


class QuoteTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Quote template')
        verbose_name_plural = _('Quote templates')


class DeliveryNoteTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Delivery note template')
        verbose_name_plural = _('Delivery note templates')


class PaymentReminderTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Payment reminder template')
        verbose_name_plural = _('Payment reminder templates')


class PurchaseOrderTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Purchase order template')
        verbose_name_plural = _('Purchase order templates')


class PurchaseConfirmationTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Purchase confirmation template')
        verbose_name_plural = _('Purchase confirmation templates')


class ProfitLossStatementTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Profit loss statement template')
        verbose_name_plural = _('Profit loss statement templates')


class BalanceSheetTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Balance sheet template')
        verbose_name_plural = _('Balance sheet templates')


class MonthlyProjectSummaryTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Monthly project summary template')
        verbose_name_plural = _('Monthly project summary templates')


class WorkReportTemplate(DocumentTemplate):
    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Work report template')
        verbose_name_plural = _('Work report templates')


class OptionDocumentTemplate(admin.ModelAdmin):
    list_display = ('id', 'title')
    list_display_links = ('id', 'title')
    ordering = ('id',)
    search_fields = ('id', 'title')
    fieldsets = (
        (_('Basics'), {
            'fields': ('title',
                       'xsl_file',
                       'fop_config_file',
                       'logo')
        }),
    )
    inlines = [InlineTextParagraph]

<file_sep>/koalixcrm/djangoUserExtension/migrations/0003_auto_20171110_1732.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-11-10 17:32
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import filebrowser.fields


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0002_auto_20171009_1949'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='templateset',
            options={'verbose_name': 'Templateset', 'verbose_name_plural': 'Templatesets'},
        ),
        migrations.AlterModelOptions(
            name='userextension',
            options={'verbose_name': 'User Extention', 'verbose_name_plural': 'User Extentions'},
        ),
        migrations.AlterModelOptions(
            name='xslfile',
            options={'verbose_name': 'XSL File', 'verbose_name_plural': 'XSL Files'},
        ),
        migrations.AddField(
            model_name='templateset',
            name='reminder1XSLFile',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatepaymentreminder1', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Payment Reminder 1'),
        ),
        migrations.AddField(
            model_name='templateset',
            name='reminder2XSLFile',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatepaymentreminder2', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Payment Reminder 2'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='addresser',
            field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Addresser'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='balancesheetXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatebalancesheet', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Balancesheet'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='bankingaccountref',
            field=models.CharField(blank=True, max_length=60, null=True, verbose_name='Reference to Banking Account'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='deilveryorderXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatedeliveryorder', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Deilvery Order'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='footerTextpurchaseorders',
            field=models.TextField(blank=True, null=True, verbose_name='Footer Text On Purchaseorders'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='footerTextsalesorders',
            field=models.TextField(blank=True, null=True, verbose_name='Footer Text On Salesorders'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='fopConfigurationFile',
            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name='FOP Configuration File'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='headerTextpurchaseorders',
            field=models.TextField(blank=True, null=True, verbose_name='Header Text On Purchaseorders'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='headerTextsalesorders',
            field=models.TextField(blank=True, null=True, verbose_name='Header Text On Salesorders'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='invoiceXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplateinvoice', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Invoice'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='logo',
            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name='Logo for the PDF generation'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='organisationname',
            field=models.CharField(max_length=200, verbose_name='Name of the Organisation'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='pagefooterleft',
            field=models.CharField(blank=True, max_length=40, null=True, verbose_name='Page Footer Left'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='pagefootermiddle',
            field=models.CharField(blank=True, max_length=40, null=True, verbose_name='Page Footer Middle'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='profitLossStatementXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplateprofitlossstatement', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Profit Loss Statement'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='purchaseorderXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatepurchaseorder', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Purchaseorder'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='quoteXSLFile',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_reltemplatequote', to='djangoUserExtension.XSLFile', verbose_name='XSL File for Quote'),
        ),
        migrations.AlterField(
            model_name='templateset',
            name='title',
            field=models.CharField(max_length=100, verbose_name='Title'),
        ),
        migrations.AlterField(
            model_name='userextensionemailaddress',
            name='purpose',
            field=models.CharField(choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business')], max_length=1, verbose_name='Purpose'),
        ),
        migrations.AlterField(
            model_name='userextensionphoneaddress',
            name='purpose',
            field=models.CharField(choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business')], max_length=1, verbose_name='Purpose'),
        ),
        migrations.AlterField(
            model_name='userextensionpostaladdress',
            name='purpose',
            field=models.CharField(choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business')], max_length=1, verbose_name='Purpose'),
        ),
        migrations.AlterField(
            model_name='xslfile',
            name='title',
            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Title'),
        ),
        migrations.AlterField(
            model_name='xslfile',
            name='xslfile',
            field=filebrowser.fields.FileBrowseField(max_length=200, verbose_name='XSL File'),
        ),
    ]
<file_sep>/koalixcrm/djangoUserExtension/factories/factory_document_template.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.djangoUserExtension.models import QuoteTemplate
from filebrowser.base import FileObject


class StandardQuoteTemplateFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = QuoteTemplate

    title = "This is a test Quote Template"
    xsl_file = FileObject("~/path/to/xsl_file.xsl")
    fop_config_file = FileObject("~/path/to/fop_config_file.xml")
    logo = FileObject("~/path/to/logo_file.jpg")
<file_sep>/koalixcrm/crm/contact/phone_address.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _


class PhoneAddress(models.Model):
    phone = models.CharField(max_length=20,
                             verbose_name=_("Phone Number"))

    class Meta:
        app_label = "crm"
        verbose_name = _('Phone Address')
        verbose_name_plural = _('Phone Address')
<file_sep>/pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE=projectsettings.settings.development_sqlite_settings
<file_sep>/koalixcrm/crm/migrations/0024_auto_20180122_2121.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-22 21:21
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0023_auto_20180119_2102'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='position',
            options={'ordering': ['position_number'], 'verbose_name': 'Position', 'verbose_name_plural': 'Positions'},
        ),
        migrations.AddField(
            model_name='salesdocument',
            name='custom_date_field',
            field=models.DateTimeField(blank=True, null=True, verbose_name='Custom Date/Time'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_postal_address.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import PostalAddress


class StandardPostalAddressFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = PostalAddress

    prefix = "M"
    name = "Smith"
    pre_name = "John"
    address_line_1 = "Main-street 5"
    address_line_2 = None
    address_line_3 = None
    address_line_4 = None
    zip_code = 8000
    town = "Zürich"
    state = "ZH"
    country = "CH"
<file_sep>/koalixcrm/djangoUserExtension/user_extension/user_extension.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _

from koalixcrm.crm.contact.postal_address import PostalAddress
from koalixcrm.crm.contact.phone_address import PhoneAddress
from koalixcrm.crm.contact.email_address import EmailAddress
from koalixcrm.crm.documents.pdf_export import PDFExport
from koalixcrm.djangoUserExtension.const.purpose import *
from koalixcrm.djangoUserExtension.exceptions import *
from koalixcrm.global_support_functions import xstr


class UserExtension(models.Model):
    user = models.ForeignKey("auth.User",
                             blank=False,
                             null=False)
    default_template_set = models.ForeignKey("TemplateSet")
    default_currency = models.ForeignKey("crm.Currency")

    @staticmethod
    def objects_to_serialize(object_to_create_pdf, reference_user):
        from koalixcrm.crm.contact.phone_address import PhoneAddress
        from koalixcrm.crm.contact.email_address import EmailAddress
        from django.contrib import auth
        objects = list(auth.models.User.objects.filter(id=reference_user.id))
        user_extension = UserExtension.objects.filter(user=reference_user.id)
        if len(user_extension) == 0:
            raise UserExtensionMissing(_("During "+str(object_to_create_pdf)+" PDF Export"))
        phone_address = UserExtensionPhoneAddress.objects.filter(
            userExtension=user_extension[0].id)
        if len(phone_address) == 0:
            raise UserExtensionPhoneAddressMissing(_("During "+str(object_to_create_pdf)+" PDF Export"))
        email_address = UserExtensionEmailAddress.objects.filter(
            userExtension=user_extension[0].id)
        if len(email_address) == 0:
            raise UserExtensionEmailAddressMissing(_("During "+str(object_to_create_pdf)+" PDF Export"))
        objects += list(user_extension)
        objects += list(EmailAddress.objects.filter(id=email_address[0].id))
        objects += list(PhoneAddress.objects.filter(id=phone_address[0].id))
        return objects

    @staticmethod
    def get_user_extension(django_user):
        user_extensions = UserExtension.objects.filter(user=django_user)
        if len(user_extensions) > 1:
            raise TooManyUserExtensionsAvailable(_("More than one User Extension define for user ") + django_user.__str__())
        elif len(user_extensions) == 0:
            raise UserExtensionMissing(_("No User Extension define for user ") + django_user.__str__())
        return user_extensions[0]

    def get_template_set(self, template_set):
        if template_set == self.default_template_set.work_report_template:
            if self.default_template_set.work_report_template:
                return self.default_template_set.work_report_template
            else:
                raise TemplateSetMissingForUserExtension((_("Template Set for work report " +
                                                            "is missing for User Extension" + str(self))))

    def get_fop_config_file(self, template_set):
        template_set = self.get_template_set(template_set)
        return template_set.get_fop_config_file()

    def get_xsl_file(self, template_set):
        template_set = self.get_template_set(template_set)
        return template_set.get_xsl_file()

    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('User Extension')
        verbose_name_plural = _('User Extension')

    def __str__(self):
        return xstr(self.id) + ' ' + xstr(self.user.__str__())


class UserExtensionPostalAddress(PostalAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINUSEREXTENTION)
    userExtension = models.ForeignKey(UserExtension)

    def __str__(self):
        return xstr(self.name) + ' ' + xstr(self.pre_name)

    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Postal Address for User Extension')
        verbose_name_plural = _('Postal Address for User Extension')


class UserExtensionPhoneAddress(PhoneAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINUSEREXTENTION)
    userExtension = models.ForeignKey(UserExtension)

    def __str__(self):
        return xstr(self.phone)

    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Phone number for User Extension')
        verbose_name_plural = _('Phone number for User Extension')


class UserExtensionEmailAddress(EmailAddress):
    purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINUSEREXTENTION)
    userExtension = models.ForeignKey(UserExtension)

    def __str__(self):
        return xstr(self.email)

    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Email Address for User Extension')
        verbose_name_plural = _('Email Address for User Extension')


class InlineUserExtensionPostalAddress(admin.StackedInline):
    model = UserExtensionPostalAddress
    extra = 1
    classes = ('collapse-open',)
    fieldsets = (
        (_('Basics'), {
            'fields': (
                'prefix',
                'pre_name',
                'name',
                'address_line_1',
                'address_line_2',
                'address_line_3',
                'address_line_4',
                'zip_code',
                'town',
                'state',
                'country',
                'purpose')
        }),
    )
    allow_add = True


class InlineUserExtensionPhoneAddress(admin.StackedInline):
    model = UserExtensionPhoneAddress
    extra = 1
    classes = ('collapse-open',)
    fieldsets = (
        (_('Basics'), {
            'fields': ('phone',
                       'purpose',)
        }),
    )
    allow_add = True


class InlineUserExtensionEmailAddress(admin.StackedInline):
    model = UserExtensionEmailAddress
    extra = 1
    classes = ('collapse-open',)
    fieldsets = (
        (_('Basics'), {
            'fields': ('email',
                       'purpose',)
        }),
    )
    allow_add = True


class OptionUserExtension(admin.ModelAdmin):
    list_display = ('id',
                    'user',
                    'default_template_set',
                    'default_currency')
    list_display_links = ('id',
                          'user')
    list_filter = ('user',
                   'default_template_set',)
    ordering = ('id',)
    search_fields = ('id',
                     'user')
    fieldsets = (
        (_('Basics'), {
            'fields': ('user',
                       'default_template_set',
                       'default_currency')
        }),
    )

    def create_work_report_pdf(self, request, queryset):
        from koalixcrm.crm.views.create_work_report import create_work_report

        return create_work_report(self, request, queryset)

    create_work_report_pdf.short_description = _("Work Report PDF")

    save_as = True
    actions = [create_work_report_pdf]
    inlines = [InlineUserExtensionPostalAddress,
               InlineUserExtensionPhoneAddress,
               InlineUserExtensionEmailAddress]
<file_sep>/README.md
# Welcome to the koalixcrm 
## Why koalixcrm
<table><tr><th>Values:</th><th>Features:</th></tr>
<tr><td><ul>
 <li><b>Free and open</b></li>
 <li>REST interface to many entities </li>
 <li>Open source </li>
 <li>BSD license </li>
<li>Simple and beautiful user interface </li>
<li>High quality output documents </li>
<li>Small business <10 employees with access </li>
                       <li>Cloud hosted, Self-hosted, Not hosted (single-user, offline)</li></ul></td>
<td><ul>
<li> Manage Contacts, Leads, Persons</li>
<li> Manage Products and Prices</li>
<li> Manage Documents such as Invoices, Quotes, Purchase Orders, ...</li>
<li> Manage Projects, Tasks, Work (Traditional project management)</li>
<li> Manage Document Tempaltes</li>
<li>Double Entry Accounting</li>
<li> Create Project Reports</li>
<li> Adjust Access Rights </li></ul></td>
  </tr></table>

## Quality badges on master
| Project build: | Codacy results: |Docker: | Social Networks: |
| --- | --- | --- | --- |
| [![Build Status](https://travis-ci.org/scaphilo/koalixcrm.svg?branch=master)](https://travis-ci.org/scaphilo/koalixcrm) | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/4f0acae8f6d04c2b81c0c4a4b2b48e09)](https://www.codacy.com/app/simon.riedener/koalixcrm?utm_source=github.com&utm_medium=referral&utm_content=scaphilo/koalixcrm&utm_campaign=badger) [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/4f0acae8f6d04c2b81c0c4a4b2b48e09)](https://www.codacy.com/app/simon.riedener/koalixcrm?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=scaphilo/koalixcrm&amp;utm_campaign=Badge_Coverage) | [![Docker Automated build](https://img.shields.io/docker/automated/koalixswitzerland/koalixcrm.svg)]() [![Docker Build Status](https://img.shields.io/docker/build/koalixswitzerland/koalixcrm.svg)]()<br/> [![Docker Stars](https://img.shields.io/docker/stars/koalixswitzerland/koalixcrm.svg)]() [![Docker Pulls](https://img.shields.io/docker/pulls/koalixswitzerland/koalixcrm.svg)]() | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/koalix-crm/Lobby) |

## Demos
The demo-branch is automatically deployed and can be found here:
[Demo-Deployment](https://demo.koalixcrm.koalix.net/admin/).

To be able to log in use the following credentials 
<ul>
  <li><b>user: guest</b></li>
  <li><b>password: <PASSWORD></b></li>
 </ul>

## Documentation
You can find the documentation of koalixcrm here: [doc](http://readthedocs.org/docs/koalixcrm/en/master/)

## Installation
Some information about the installation of koalixcrm: [installation](https://github.com/scaphilo/koalixcrm/wiki/Installation)

## Development environment setup
Information about the development environment setup for this and similar projects can be found here: [Development environment setup](https://github.com/scaphilo/koalixcrm/wiki/Development-Environment-Setup)

## Release Process
Information about the release process: [Release Process](https://github.com/scaphilo/koalixcrm/wiki/Release-Process)

## Update from version 1.11 to 1.12
Some information about the update procedure from Version 1.11 to Version 1.12: [update](https://github.com/scaphilo/koalixcrm/wiki/Update) 
<file_sep>/koalixcrm/accounting/const/accountTypeChoices.py
# -*- coding: utf-8 -*

from django.utils.translation import ugettext as _

ACCOUNTTYPECHOICES = (
    ('E', _('Earnings')),
    ('S', _('Spendings')),
    ('L', _('Liabilities')),
    ('A', _('Assets')),
)
<file_sep>/koalixcrm/accounting/accounting/account.py
# -*- coding: utf-8 -*-
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from django import forms

from koalixcrm.accounting.const.accountTypeChoices import *
from koalixcrm.accounting.exceptions import AccountingPeriodNotFound
from koalixcrm.crm.documents.pdf_export import PDFExport


class Account(models.Model):
    account_number = models.IntegerField(verbose_name=_("Account Number"))
    title = models.CharField(verbose_name=_("Account Title"),
                             max_length=50)
    account_type = models.CharField(verbose_name=_("Account Type"),
                                    max_length=1,
                                    choices=ACCOUNTTYPECHOICES)
    description = models.TextField(verbose_name=_("Description"), null=True, blank=True)
    is_open_reliabilities_account = models.BooleanField(verbose_name=_("Is The Open Liabilities Account"))
    is_open_interest_account = models.BooleanField(verbose_name=_("Is The Open Interests Account"))
    is_product_inventory_activa = models.BooleanField(verbose_name=_("Is a Product Inventory Account"))
    is_a_customer_payment_account = models.BooleanField(verbose_name=_("Is a Customer Payment Account"))

    def sum_of_all_bookings(self):
        calculated_sum = self.all_bookings(from_account=False) - self.all_bookings(from_account=True)
        if self.account_type == 'E' or self.account_type == 'L':
            calculated_sum = 0 - calculated_sum
        return calculated_sum

    sum_of_all_bookings.short_description = _("Value")

    def sum_of_all_bookings_within_accounting_period(self, accounting_period):
        calculated_sum = self.all_bookings_within_accounting_period(from_account=False,
                                                                    accounting_period=accounting_period) - \
                         self.all_bookings_within_accounting_period(from_account=True,
                                                                    accounting_period=accounting_period)
        if self.account_type == 'E' or self.account_type == 'L':
            calculated_sum = -calculated_sum
        return calculated_sum

    def sum_of_all_bookings_before_accounting_period(self, current_accounting_period):
        try:
            accounting_periods = current_accounting_period.get_all_prior_accounting_periods()
        except AccountingPeriodNotFound:
            return 0
        sum_of_all_bookings = 0
        for accounting_period in accounting_periods:
            sum_of_all_bookings += self.all_bookings_within_accounting_period(from_account=False,
                                                              accounting_period=accounting_period) - self.all_bookings_within_accounting_period(
                from_account=True, accounting_period=accounting_period)
        if self.account_type == 'E' or self.account_type == 'L':
            sum_of_all_bookings = -sum_of_all_bookings
        return sum_of_all_bookings

    def sum_of_all_bookings_through_now(self, current_accounting_period):
        within_accounting_period = self.sum_of_all_bookings_within_accounting_period(current_accounting_period)
        before_accounting_period = self.sum_of_all_bookings_before_accounting_period(current_accounting_period)
        current_value = within_accounting_period + before_accounting_period
        return current_value

    def all_bookings(self, from_account):
        from koalixcrm.accounting.models import Booking
        sum_all = 0
        if from_account:
            bookings = Booking.objects.filter(from_account=self.id)
        else:
            bookings = Booking.objects.filter(to_account=self.id)

        for booking in list(bookings):
            sum_all += booking.amount

        return sum_all

    def all_bookings_within_accounting_period(self, from_account, accounting_period):
        from koalixcrm.accounting.models import Booking
        sum = 0
        if from_account:
            bookings = Booking.objects.filter(from_account=self.id, accounting_period=accounting_period.id)
        else:
            bookings = Booking.objects.filter(to_account=self.id, accounting_period=accounting_period.id)

        for booking in list(bookings):
            sum += booking.amount

        return sum

    def serialize_to_xml(self, accounting_period):
        objects = [self, ]
        main_xml = PDFExport.write_xml(objects)
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>']",
                                                       "sum_of_all_bookings_within_accounting_period",
                                                       self.sum_of_all_bookings_within_accounting_period(accounting_period))
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>']",
                                                       "sum_of_all_bookings_through_now",
                                                       self.sum_of_all_bookings_through_now(accounting_period))
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>']",
                                                       "sum_of_all_bookings_before_accounting_period",
                                                       self.sum_of_all_bookings_before_accounting_period(accounting_period))
        main_xml = PDFExport.append_element_to_pattern(main_xml,
                                                       "object/[<EMAIL>='<EMAIL>']",
                                                       "sum_of_all_bookings_through_now",
                                                       self.sum_of_all_bookings())
        return main_xml

    def __str__(self):
        return self.account_number.__str__() + " " + self.title

    class Meta:
        app_label = "accounting"
        verbose_name = _('Account')
        verbose_name_plural = _('Account')
        ordering = ['account_number']


class AccountForm(forms.ModelForm):
    """AccountForm is used to overwrite the clean method of the
    original form and to add an additional checks to the model"""

    class Meta:
        model = Account
        fields = '__all__'

    def clean(self):
        super(AccountForm, self).clean()
        errors = []
        if self.cleaned_data['is_open_reliabilities_account']:
            open_reliabilities_account = Account.objects.filter(is_open_reliabilities_account=True)
            if self.cleaned_data['account_type'] != "L":
                errors.append(_('The open liabilities account must be a liabities account'))
            elif open_reliabilities_account:
                errors.append(_('There may only be one open liablities account in the system'))
        if self.cleaned_data['is_open_interest_account']:
            open_interest_account = Account.objects.filter(is_open_interest_account=True)
            if self.cleaned_data['account_type'] != "A":
                errors.append(_('The open interests account must be an asset account'))
            elif open_interest_account:
                errors.append(_('There may only be one open intrests account in the system'))
        if self.cleaned_data['is_a_customer_payment_account']:
            if self.cleaned_data['account_type'] != "A":
                errors.append(_('A customer payment account must be an asset account'))
        if self.cleaned_data['is_product_inventory_activa']:
            if self.cleaned_data['account_type'] != "A":
                errors.append(_('A product inventory account must be an asset account'))
        if len(errors) > 0:
            raise forms.ValidationError(errors)
        return self.cleaned_data


class OptionAccount(admin.ModelAdmin):
    list_display = ('account_number',
                    'account_type',
                    'title',
                    'sum_of_all_bookings')
    list_display_links = ('account_number',
                          'account_type',
                          'title',
                          'sum_of_all_bookings')
    fieldsets = ((_('Basic'),
                  {'fields': ('account_number',
                              'account_type',
                              'title',
                              'description',
                              'is_open_reliabilities_account',
                              'is_open_interest_account',
                              'is_product_inventory_activa',
                              'is_a_customer_payment_account')}),)
    save_as = True

    form = AccountForm
<file_sep>/koalixcrm/crm/migrations/0016_auto_20180111_2011.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-11 20:11
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0015_auto_20180111_2043'),
    ]

    operations = [
        migrations.RenameModel(
            old_name='EmailAddressForSalesContract',
            new_name='EmailAddressForSalesDocument'
        ),
        migrations.RenameModel(
            old_name='PhoneAddressForSalesContract',
            new_name='PhoneAddressForSalesDocument',
        ),
        migrations.RenameModel(
            old_name='PostalAddressForSalesContract',
            new_name='PostalAddressForSalesDocument',
        ),
        migrations.RenameModel(
            old_name='SalesContractPosition',
            new_name='SalesDocumentPosition',
        ),
        migrations.RenameModel(
            old_name='TextParagraphInSalesContract',
            new_name='TextParagraphInSalesDocument',
        ),
        migrations.AddField(
            model_name='salesdocument',
            name='derived_from_sales_document',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.SalesDocument'),
        ),
    ]
<file_sep>/koalixcrm/crm/contact/customer_group.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _


class CustomerGroup(models.Model):
    name = models.CharField(max_length=300)

    def __str__(self):
        return str(self.id) + ' ' + self.name

    class Meta:
        app_label = "crm"
        verbose_name = _('Customer Group')
        verbose_name_plural = _('Customer Groups')


class OptionCustomerGroup(admin.ModelAdmin):
    list_display = ('id', 'name')
    fieldsets = (('', {'fields': ('name',)}),)
    allow_add = True<file_sep>/koalixcrm/crm/factories/factory_resource_manager.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import ResourceManager
from koalixcrm.djangoUserExtension.factories.factory_user_extension import StandardUserExtensionFactory


class StandardResourceManagerFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ResourceManager

    user = factory.SubFactory(StandardUserExtensionFactory)
<file_sep>/documentation/source/code_documentation/agreement.rst
.. highlight:: rst

Agreement
---------

.. automodule:: koalixcrm.crm.reporting.agreement
   :members:

<file_sep>/koalixcrm/accounting/accounting/booking.py
# -*- coding: utf-8 -*-
from django.contrib import admin
from django.db import models
from django.utils.translation import ugettext as _


class Booking(models.Model):
    from_account = models.ForeignKey('Account', verbose_name=_("From Account"), related_name="db_booking_fromaccount")
    to_account = models.ForeignKey('Account', verbose_name=_("To Account"), related_name="db_booking_toaccount")
    amount = models.DecimalField(max_digits=20, decimal_places=2, verbose_name=_("Amount"))
    description = models.CharField(verbose_name=_("Description"), max_length=120, null=True, blank=True)
    booking_reference = models.ForeignKey('crm.Invoice', verbose_name=_("Booking Reference"), null=True, blank=True)
    booking_date = models.DateTimeField(verbose_name=_("Booking at"))
    accounting_period = models.ForeignKey('AccountingPeriod', verbose_name=_("AccountingPeriod"))
    staff = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True}, blank=True,
                              verbose_name=_("Reference Staff"), related_name="db_booking_refstaff")
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"), auto_now=True)
    last_modification = models.DateTimeField(verbose_name=_("Last modified"), auto_now_add=True)
    last_modified_by = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True}, blank=True,
                                         verbose_name=_("Last modified by"), related_name="db_booking_lstmodified")

    def booking_date_only(self):
        return self.booking_date.date()

    booking_date_only.short_description = _("Date");

    def __str__(self):
        return self.from_account.__str__() + " " + self.to_account.__str__() + " " + self.amount.__str__()

    class Meta:
        app_label = "accounting"
        verbose_name = _('Booking')
        verbose_name_plural = _('Bookings')


class OptionBooking(admin.ModelAdmin):
    list_display = ('from_account',
                    'to_account',
                    'amount',
                    'booking_date_only',
                    'staff')
    fieldsets = ((_('Basic'), {'fields': ('from_account',
                                          'to_account',
                                          'amount',
                                          'booking_date',
                                          'staff',
                                          'description',
                                          'booking_reference',
                                          'accounting_period')}),)
    save_as = True

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_modified_by = request.user
        else:
            obj.last_modified_by = request.user
            obj.staff = request.user
        obj.save()


class InlineBookings(admin.TabularInline):
    model = Booking
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('from_account',
                       'to_account',
                       'description',
                       'amount',
                       'booking_date',
                       'staff',
                       'booking_reference',)
        }),
    )
    allow_add = False
<file_sep>/koalixcrm/crm/factories/factory_customer_billing_cycle.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import CustomerBillingCycle


class StandardCustomerBillingCycleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = CustomerBillingCycle
        django_get_or_create = ('name',)

    name = "This is a test billing cycle"
    time_to_payment_date = 30
    payment_reminder_time_to_payment = 20
<file_sep>/koalixcrm/crm/contact/contact.py
# -*- coding: utf-8 -*-

from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.crm.contact.phone_address import PhoneAddress
from koalixcrm.crm.contact.email_address import EmailAddress
from koalixcrm.crm.contact.postal_address import PostalAddress
from koalixcrm.crm.contact.call import Call
from koalixcrm.crm.contact.person import *
from koalixcrm.crm.const.purpose import *
from koalixcrm.global_support_functions import xstr
from koalixcrm.crm.inlinemixin import LimitedAdminInlineMixin


class Contact(models.Model):
    name = models.CharField(max_length=300,
                            verbose_name=_("Name"))
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"),
                                            auto_now_add=True)
    last_modification = models.DateTimeField(verbose_name=_("Last modified"),
                                             auto_now=True)
    last_modified_by = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True},
                                         blank=True,
                                         verbose_name=_("Last modified by"),
                                         editable=True)

    class Meta:
        app_label = "crm"
        verbose_name = _('Contact')
        verbose_name_plural = _('Contact')

    def __str__(self):
        return self.name


class PhoneAddressForContact(PhoneAddress):
    purpose = models.CharField(verbose_name=_("Purpose"),
                               max_length=1,
                               choices=PURPOSESADDRESSINCUSTOMER)
    person = models.ForeignKey(Contact)

    class Meta:
        app_label = "crm"
        verbose_name = _('Phone Address For Contact')
        verbose_name_plural = _('Phone Address For Contact')

    def __str__(self):
        return str(self.phone)


class EmailAddressForContact(EmailAddress):
    purpose = models.CharField(verbose_name=_("Purpose"),
                               max_length=1,
                               choices=PURPOSESADDRESSINCUSTOMER)
    person = models.ForeignKey(Contact)

    class Meta:
        app_label = "crm"
        verbose_name = _('Email Address For Contact')
        verbose_name_plural = _('Email Address For Contact')

    def __str__(self):
        return str(self.email)


class PostalAddressForContact(PostalAddress):
    purpose = models.CharField(verbose_name=_("Purpose"),
                               max_length=1,
                               choices=PURPOSESADDRESSINCUSTOMER)
    person = models.ForeignKey(Contact)

    class Meta:
        app_label = "crm"
        verbose_name = _('Postal Address For Contact')
        verbose_name_plural = _('Postal Address For Contact')

    def __str__(self):
        return xstr(self.pre_name) + ' ' + xstr(self.name) + ' ' + xstr(self.address_line_1)


class ContactPostalAddress(admin.StackedInline):
    model = PostalAddressForContact
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('prefix',
                       'pre_name',
                       'name',
                       'address_line_1',
                       'address_line_2',
                       'address_line_3',
                       'address_line_4',
                       'zip_code',
                       'town',
                       'state',
                       'country',
                       'purpose')
        }),
    )
    allow_add = True


class ContactPhoneAddress(admin.TabularInline):
    model = PhoneAddressForContact
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('phone', 'purpose',)
        }),
    )
    allow_add = True


class ContactEmailAddress(admin.TabularInline):
    model = EmailAddressForContact
    extra = 1
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': ('email', 'purpose',)
        }),
    )
    allow_add = True


class ContactPersonAssociation(models.Model):
    contact = models.ForeignKey(Contact, related_name='person_association', blank=True, null=True)
    person = models.ForeignKey(Person, related_name='contact_association', blank=True, null=True)

    class Meta:
        app_label = "crm"
        verbose_name = _('Contacts')
        verbose_name_plural = _('Contacts')

    def __str__(self):
        return ''


class PeopleInlineAdmin(admin.TabularInline):
    model = ContactPersonAssociation
    extra = 0
    show_change_link = True


class CompaniesInlineAdmin(admin.TabularInline):
    model = ContactPersonAssociation
    extra = 0
    show_change_link = True


class OptionPerson(admin.ModelAdmin):
    list_display = ('id',
                    'name',
                    'pre_name',
                    'email',
                    'role',
                    'get_companies',)
    fieldsets = (('', {'fields': ('prefix',
                                  'name',
                                  'pre_name',
                                  'role',
                                  'email',
                                  'phone',)}),)
    allow_add = True
    inlines = [CompaniesInlineAdmin]

    def get_companies(self, obj):
        items = []
        for c in obj.companies.all():
            items.append(c.name)
        return ','.join(items)
    
    get_companies.short_description = _("Works at")


class CallForContact(Call):
    company = models.ForeignKey(Contact)
    cperson = models.ForeignKey(Person, verbose_name=_("Person"),
                                blank=True,
                                null=True)
    purpose = models.CharField(verbose_name=_("Purpose"),
                               max_length=1,
                               choices=PURPOSECALLINCUSTOMER)
    
    class Meta:
        app_label = "crm"
        verbose_name = _('Call')
        verbose_name_plural = _('Calls')

    def __str__(self):
        return xstr(self.description) + ' ' + xstr(self.date_due)


class VisitForContact(Call):
    company = models.ForeignKey(Contact)
    cperson = models.ForeignKey(Person,
                                verbose_name=_("Person"),
                                blank=True,
                                null=True)
    purpose = models.CharField(verbose_name=_("Purpose"),
                               max_length=1,
                               choices=PURPOSEVISITINCUSTOMER)
    ref_call = models.ForeignKey(CallForContact,
                                 verbose_name=_("Reference Call"),
                                 blank=True,
                                 null=True)
    
    class Meta:
        app_label = "crm"
        verbose_name = _('Visit')
        verbose_name_plural = _('Visits')

    def __str__(self):
        return xstr(self.description) + ' ' + xstr(self.date_due)


class ContactCall(LimitedAdminInlineMixin, admin.StackedInline):
    model = CallForContact
    extra = 0
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': (
                'description',
                'date_due',
                'purpose',
                'status',
                'cperson',)
        }),
    )
    allow_add = True

    def get_filters(self, request, obj):
        return getattr(self, 'filters', ()) if obj is None else (('cperson', dict(companies=obj.id)),)


class ContactVisit(LimitedAdminInlineMixin, admin.StackedInline):
    model = VisitForContact
    extra = 0
    classes = ['collapse']
    fieldsets = (
        ('Basics', {
            'fields': (
                'description',
                'date_due',
                'purpose',
                'status',
                'cperson',
                'ref_call',)
        }),
    )
    allow_add = True
    
    def get_filters(self, request, obj):
        return getattr(self, 'filters', ()) if obj is None else (('cperson', dict(companies=obj.id)),('ref_call', dict(company=obj.id, status='S')))


class StateFilter(admin.SimpleListFilter):
    title = _('State')
    parameter_name = 'state'

    def lookups(self, request, model_admin):
        items = []
        for a in PostalAddressForContact.objects.values('state').distinct():
            if a['state']:
                items.append((a['state'], _(a['state'])))
        return (
            items
        )

    def queryset(self, request, queryset):
        if self.value():
            matching_addresses = PostalAddressForContact.objects.filter(state=self.value())
            ids = [a.person.id for a in matching_addresses]
            return queryset.filter(pk__in=ids)
        return queryset


class CityFilter(admin.SimpleListFilter):
    title = _('City')
    parameter_name = 'city'

    def lookups(self, request, model_admin):
        items = []
        state = request.GET.get('state', None)
        unique_list = PostalAddressForContact.objects.all().order_by('town')
        adjusted_queryset = unique_list if state is None else unique_list.filter(state=state)
        for a in adjusted_queryset.values('town').distinct():
            if a['town']:
                items.append((a['town'], _(a['town'])))
        return (
            items
        )

    def queryset(self, request, queryset):
        if self.value():
            matching_addresses = PostalAddressForContact.objects.filter(town=self.value())
            ids = [(a.person.id) for a in matching_addresses]
            return queryset.filter(pk__in=ids)
        return queryset

<file_sep>/koalixcrm/accounting/migrations/0006_auto_20180422_2048.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-04-22 20:48
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0004_auto_20171210_2126'),
        ('accounting', '0005_auto_20171110_1732'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='account',
            options={'ordering': ['account_number'], 'verbose_name': 'Account', 'verbose_name_plural': 'Account'},
        ),
        migrations.RenameField(
            model_name='account',
            old_name='accountNumber',
            new_name='account_number',
        ),
        migrations.RenameField(
            model_name='account',
            old_name='accountType',
            new_name='account_type',
        ),
        migrations.RenameField(
            model_name='account',
            old_name='isACustomerPaymentAccount',
            new_name='is_a_customer_payment_account',
        ),
        migrations.RenameField(
            model_name='account',
            old_name='isopeninterestaccount',
            new_name='is_open_interest_account',
        ),
        migrations.RenameField(
            model_name='account',
            old_name='isopenreliabilitiesaccount',
            new_name='is_open_reliabilities_account',
        ),
        migrations.RenameField(
            model_name='account',
            old_name='isProductInventoryActiva',
            new_name='is_product_inventory_activa',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='accountingPeriod',
            new_name='accounting_period',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='bookingDate',
            new_name='booking_date',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='bookingReference',
            new_name='booking_reference',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='dateofcreation',
            new_name='date_of_creation',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='fromAccount',
            new_name='from_account',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='lastmodification',
            new_name='last_modification',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='lastmodifiedby',
            new_name='last_modified_by',
        ),
        migrations.RenameField(
            model_name='booking',
            old_name='toAccount',
            new_name='to_account',
        ),
        migrations.RenameField(
            model_name='productcategorie',
            old_name='lossAccount',
            new_name='loss_account',
        ),
        migrations.RenameField(
            model_name='productcategorie',
            old_name='profitAccount',
            new_name='profit_account',
        ),
        migrations.AddField(
            model_name='accountingperiod',
            name='template_set',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.DocumentTemplate', verbose_name='Referred Template'),
        ),
    ]
<file_sep>/entrypoint.prod.sh
#!/bin/bash

# Install dependencies
pip install -r base_requirements.txt

# Install FOP 2.2
wget http://archive.apache.org/dist/xmlgraphics/fop/binaries/fop-2.2-bin.tar.gz
tar -xzf fop-2.2-bin.tar.gz -C ../usr/bin
rm -rf fop-2.2-bin.tar.gz
chmod 755 ../usr/bin/fop-2.2/fop

# Install Java 8
wget --no-check-certificate -c --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u162-b12/0da788060d494f5095bf8624735fa2f1/jdk-8u162-linux-x64.tar.gz
tar -xzf jdk-8u162-linux-x64.tar.gz -C ../usr/bin
rm -rf jdk-8u162-linux-x64.tar.gz

# Create /media/uploads/ directory which is required by django filebrowser
mkdir -p projectsettings/media/uploads
chmod -R 755 projectsettings/media

# Create /static/pdf for FOP PDF export
mkdir -p projectsettings/static/pdf
chmod -R 755 projectsettings/static/pdf

# Execute startup scripts
python manage.py collectstatic --noinput
python manage.py migrate
python manage.py runserver 0.0.0.0:8000<file_sep>/koalixcrm/crm/product/currency.py
# -*- coding: utf-8 -*-

from decimal import Decimal
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _


class Currency(models.Model):
    description = models.CharField(verbose_name=_("Description"),
                                   max_length=100)
    short_name = models.CharField(verbose_name=_("Displayed Name After Prices"),
                                  max_length=3)
    rounding = models.DecimalField(max_digits=5,
                                   decimal_places=2,
                                   verbose_name=_("Rounding"),
                                   blank=True,
                                   null=True)

    def get_rounding(self):
        """Returns either the stored rounding value for a currency or a default rounding value of 0.05

        Args: no arguments

        Returns: Decimal value

        Raises: should not return exceptions"""
        if self.rounding is None:
            return Decimal('0.05')
        else:
            return self.rounding

    def round(self, value):
        """Rounds the input value to the rounding resolution which is defined in the variable "rounding"

        Args: Decimal value value

        Returns: Decimal value

        Raises: should not return exceptions"""
        rounded_value = int(value / self.get_rounding()) * self.get_rounding()
        return rounded_value

    def __str__(self):
        return self.short_name

    class Meta:
        app_label = "crm"
        verbose_name = _('Currency')
        verbose_name_plural = _('Currency')


class OptionCurrency(admin.ModelAdmin):
    list_display = ('id',
                    'description',
                    'short_name',
                    'rounding')
    fieldsets = (('', {'fields': ('description',
                                  'short_name',
                                  'rounding')}),)
    allow_add = True
<file_sep>/koalixcrm/crm/migrations/0038_auto_20180702_1628.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-07-02 16:28
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0037_auto_20180701_2128'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='postaladdress',
            name='addressline1',
        ),
        migrations.RemoveField(
            model_name='postaladdress',
            name='addressline2',
        ),
        migrations.RemoveField(
            model_name='postaladdress',
            name='addressline3',
        ),
        migrations.RemoveField(
            model_name='postaladdress',
            name='addressline4',
        ),
        migrations.RemoveField(
            model_name='postaladdress',
            name='prename',
        ),
        migrations.RemoveField(
            model_name='postaladdress',
            name='zipcode',
        ),
        migrations.AddField(
            model_name='postaladdress',
            name='address_line_1',
            field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address line 1'),
        ),
        migrations.AddField(
            model_name='postaladdress',
            name='address_line_2',
            field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address line 2'),
        ),
        migrations.AddField(
            model_name='postaladdress',
            name='address_line_3',
            field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address line 3'),
        ),
        migrations.AddField(
            model_name='postaladdress',
            name='address_line_4',
            field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address line 4'),
        ),
        migrations.AddField(
            model_name='postaladdress',
            name='pre_name',
            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Pre-name'),
        ),
        migrations.AddField(
            model_name='postaladdress',
            name='zip_code',
            field=models.IntegerField(blank=True, null=True, verbose_name='Zip Code'),
        ),
    ]
<file_sep>/koalixcrm/crm/migrations/0017_auto_20180111_2022.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-11 20:22
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0016_auto_20180111_2011'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='emailaddressforsalesdocument',
            options={'verbose_name': 'Email Address For Contract', 'verbose_name_plural': 'Email Address For Contracts'},
        ),
        migrations.AlterModelOptions(
            name='phoneaddressforsalesdocument',
            options={'verbose_name': 'Phone Address For Sales Contract', 'verbose_name_plural': 'Phone Address For Contracts'},
        ),
        migrations.AlterModelOptions(
            name='postaladdressforsalesdocument',
            options={'verbose_name': 'Postal Address For Contract', 'verbose_name_plural': 'Postal Address For Contracts'},
        ),
        migrations.AlterModelOptions(
            name='salesdocument',
            options={'verbose_name': 'Sales Document', 'verbose_name_plural': 'Sales Documents'},
        ),
        migrations.AlterModelOptions(
            name='salesdocumentposition',
            options={'verbose_name': 'Position in Sales Document', 'verbose_name_plural': 'Positions Sales Document'},
        ),
        migrations.AlterField(
            model_name='position',
            name='last_calculated_price',
            field=models.DecimalField(blank=True, decimal_places=2, max_digits=17, null=True, verbose_name='Last Calculated Price'),
        ),
        migrations.AlterField(
            model_name='position',
            name='last_calculated_tax',
            field=models.DecimalField(blank=True, decimal_places=2, max_digits=17, null=True, verbose_name='Last Calculated Tax'),
        ),
    ]
<file_sep>/koalixcrm/crm/reporting/generic_task_link.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib import admin


class GenericTaskLink(models.Model):
    task = models.ForeignKey("Task", verbose_name=_('Task'), blank=False, null=False)
    task_link_type = models.ForeignKey("TaskLinkType", verbose_name=_('Task Link Type'), blank=True, null=True)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    generic_crm_object = GenericForeignKey('content_type', 'object_id')
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"), auto_now_add=True)
    last_modified_by = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True},
                                         verbose_name=_("Last modified by"), related_name="db_task_link_last_modified")

    def __str__(self):
        return str(self.id) + " " + str(self.title)

    class Meta:
        app_label = "crm"
        verbose_name = _('Task Link')
        verbose_name_plural = _('Task Links')


class InlineGenericTaskLink(admin.TabularInline):
    model = GenericTaskLink
    readonly_fields = ('task_link_type', 'content_type', 'object_id', 'date_of_creation', 'last_modified_by')
    extra = 0

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False<file_sep>/koalixcrm/crm/product/product.py
# -*- coding: utf-8 -*-
from django.db import models
from django.utils.translation import ugettext as _


class Product(models.Model):
    identifier = models.CharField(verbose_name=_("Product Number"),
                                  max_length=200,
                                  null=True,
                                  blank=True)
    product_type = models.ForeignKey("ProductType", verbose_name=_("Product Type"))

<file_sep>/koalixcrm/crm/reporting/reporting_period.py
# -*- coding: utf-8 -*-

from datetime import *
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.crm.documents.pdf_export import PDFExport
from koalixcrm.crm.exceptions import ReportingPeriodNotFound
from koalixcrm.crm.reporting.work import WorkInlineAdminView
from rest_framework import serializers
from django.core.exceptions import ValidationError
from django.forms import ModelForm


class ReportingPeriod(models.Model):
    """The reporting period is referred in the work, in the expenses and purchase orders, it is used as a
       supporting object to generate project reports"""
    project = models.ForeignKey("Project",
                                verbose_name=_("Project"),
                                blank=False,
                                null=False)
    title = models.CharField(max_length=200,
                             verbose_name=_("Title"),
                             blank=False,
                             null=False)  # For example "March 2018", "1st Quarter 2019"
    begin = models.DateField(verbose_name=_("Begin"),
                             blank=False,
                             null=False)
    end = models.DateField(verbose_name=_("End"),
                           blank=False,
                           null=False)
    status = models.ForeignKey("ReportingPeriodStatus",
                               verbose_name=_("Reporting Period Status"),
                               blank=True,
                               null=True)

    @staticmethod
    def get_reporting_period(project, search_date):
        """Returns the reporting period that is currently valid. Valid is a reporting period when the provided date
          lies between begin and end of the reporting period

        Args:
          no arguments

        Returns:
          reporting_period (ReportPeriod)

        Raises:
          ReportPeriodNotFound when there is no valid reporting Period"""
        current_valid_reporting_period = None
        for reporting_period in ReportingPeriod.objects.filter(project=project):
            if reporting_period.begin <= search_date <= reporting_period.end:
                return reporting_period
        if not current_valid_reporting_period:
            raise ReportingPeriodNotFound("Reporting Period does not exist")

    @staticmethod
    def get_latest_reporting_period(project):
        """Returns the latest reporting period

        Args:
          no arguments

        Returns:
          reporting_period (ReportPeriod)

        Raises:
          ReportPeriodNotFound when there is no valid reporting Period"""
        latest_reporting_period = None
        reporting_periods = ReportingPeriod.objects.filter(project=project)
        for reporting_period in reporting_periods:
            if latest_reporting_period is None:
                latest_reporting_period = reporting_period
            else:
                if latest_reporting_period.end <= reporting_period.begin:
                    latest_reporting_period = reporting_period
        if not latest_reporting_period:
            raise ReportingPeriodNotFound("Reporting Period does not exist")
        return latest_reporting_period

    @staticmethod
    def get_predecessor(target_reporting_period, project):
        """Returns the reporting period which was valid right before the provided target_reporting_period

        Args:
          no arguments

        Returns:
          predecessor_reporting_period (ReportPeriod)

        Raises:
          ReportPeriodNotFound when there is no valid reporting Period"""
        predecessor_reporting_period = None
        reporting_periods = ReportingPeriod.objects.filter(project=project)
        for reporting_period in reporting_periods:
            if reporting_period.end <= target_reporting_period.begin and\
                    predecessor_reporting_period is None:
                predecessor_reporting_period = reporting_period
            elif reporting_period.end <= target_reporting_period.begin and\
                    reporting_period.end <= predecessor_reporting_period.begin:
                predecessor_reporting_period = reporting_period
        if predecessor_reporting_period is None:
            raise ReportingPeriodNotFound("Reporting Period does not exist")
        return predecessor_reporting_period

    @staticmethod
    def get_all_predecessors(target_reporting_period, project):
        """Returns all reporting periods which have been valid before the provided target_reporting_period

        Args:
          no arguments

        Returns:
          predecessor_reporting_periods[] (Array of ReportPeriod)

        Raises:
          ReportPeriodNotFound when there is no valid reporting Period"""
        predecessor_reporting_periods = list()
        reporting_periods = ReportingPeriod.objects.filter(project=project)
        for reporting_period in reporting_periods:
            if reporting_period.end <= target_reporting_period.begin:
                predecessor_reporting_periods.append(reporting_period)
        return predecessor_reporting_periods

    def is_reporting_allowed(self):
        """Returns True when the reporting period is available for reporting,
        Returns False when the reporting period is not available for reporting,
        The decision whether the reporting period is available for reporting is purely depending
        on the reporting_period_status. When the status is done, the reporting period is not longer
        available for reporting. In all other cases the reporting is allowed.

        Args:
          no arguments

        Returns:
          allowed (Boolean)

        Raises:
           when there is no valid reporting Period"""
        if self.status:
            if self.status.is_done:
                allowed = False
            else:
                allowed = True
        else:
            allowed = False
        return allowed

    def create_pdf(self, template_set, printed_by):
        self.last_print_date = datetime.now()
        self.save()
        return PDFExport.create_pdf(self, template_set, printed_by)

    def get_template_set(self):
        return self.project.get_template_set()

    def get_fop_config_file(self, template_set):
        return self.project.get_fop_config_file(template_set=None)

    def get_xsl_file(self, template_set):
        return self.project.get_xsl_file(template_set=None)

    def serialize_to_xml(self):
        objects = [self, ]
        main_xml = PDFExport.write_xml(objects)
        project_xml = self.project.serialize_to_xml(reporting_period=self)
        main_xml = PDFExport.merge_xml(main_xml, project_xml)
        return main_xml

    def __str__(self):
        return str(self.id)+" "+self.title

    class Meta:
        app_label = "crm"
        verbose_name = _('Reporting Period')
        verbose_name_plural = _('Reporting Periods')


class ReportingPeriodAdminForm(ModelForm):
    def clean(self):
        """Check that the begin of the new reporting period is not located within an existing
        reporting period, Checks that the begin is date earlier than the end"""
        cleaned_data = super().clean()
        project = cleaned_data['project']
        end = cleaned_data['end']
        begin = cleaned_data['begin']
        reporting_periods = ReportingPeriod.objects.filter(project=project)
        for reporting_period in reporting_periods:
            if reporting_period.pk != self.instance.pk:
                if (begin < reporting_period.end) and (begin > reporting_period.begin):
                    raise ValidationError('The Reporting Period overlaps with an existing '
                                          'Reporting Period within the same project')
                if (end < reporting_period.end) and (end > reporting_period.begin):
                    raise ValidationError('The Reporting Period overlaps with an existing '
                                          'Reporting Period within the same project')
        if end < begin:
            raise ValidationError('Begin date must be earlier than end date')


class ReportingPeriodAdmin(admin.ModelAdmin):
    form = ReportingPeriodAdminForm
    list_display = ('id',
                    'project',
                    'title',
                    'begin',
                    'end',
                    'status')

    list_display_links = ('id',)
    ordering = ('-id',)

    fieldsets = (
        (_('ReportingPeriod'), {
            'fields': ('project',
                       'title',
                       'begin',
                       'end',
                       'status')
        }),
    )

    inlines = [WorkInlineAdminView, ]
    actions = ['create_report_pdf', ]

    def save_model(self, request, obj, form, change):
        if change:
            obj.last_modified_by = request.user
        else:
            obj.last_modified_by = request.user
            obj.staff = request.user
        obj.save()

    def create_report_pdf(self, request, queryset):
        from koalixcrm.crm.views.pdfexport import PDFExportView
        for obj in queryset:
            response = PDFExportView.export_pdf(self,
                                                request,
                                                obj,
                                                ("/admin/crm/"+obj.__class__.__name__.lower()+"/"),
                                                obj.project.default_template_set.monthly_project_summary_template)
            return response

    create_report_pdf.short_description = _("Create Report PDF")


class ReportingPeriodInlineAdminView(admin.TabularInline):
    model = ReportingPeriod
    fieldsets = (
        (_('ReportingPeriod'), {
            'fields': ('project',
                       'title',
                       'begin',
                       'end',
                       'status')
        }),
    )

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False


class ProjectJSONSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = ReportingPeriod
        fields = ('id',
                  'project',
                  'title',
                  'begin',
                  'end')<file_sep>/koalixcrm/crm/const/country.py
# -*- coding: utf-8 -*

from django.utils.translation import ugettext as _

COUNTRIES = (
    ('AF', 'AFG', '004', _('Afghanistan')),
    ('AX', 'ALA', '248', _('Aland Islands')),
    ('AL', 'ALB', '008', _('Albania')),
    ('DZ', 'DZA', '012', _('Algeria')),
    ('AS', 'ASM', '016', _('American Samoa')),
    ('AD', 'AND', '020', _('Andorra')),
    ('AO', 'AGO', '024', _('Angola')),
    ('AI', 'AIA', '660', _('Anguilla')),
    ('AQ', 'ATA', '010', _('Antarctica')),
    ('AG', 'ATG', '028', _('Antigua and Barbuda')),
    ('AR', 'ARG', '032', _('Argentina')),
    ('AM', 'ARM', '051', _('Armenia')),
    ('AW', 'ABW', '533', _('Aruba')),
    ('AU', 'AUS', '036', _('Australia')),
    ('AT', 'AUT', '040', _('Austria')),
    ('AZ', 'AZE', '031', _('Azerbaijan')),
    ('BS', 'BHS', '044', _('the Bahamas')),
    ('BH', 'BHR', '048', _('Bahrain')),
    ('BD', 'BGD', '050', _('Bangladesh')),
    ('BB', 'BRB', '052', _('Barbados')),
    ('BY', 'BLR', '112', _('Belarus')),
    ('BE', 'BEL', '056', _('Belgium')),
    ('BZ', 'BLZ', '084', _('Belize')),
    ('BJ', 'BEN', '204', _('Benin')),
    ('BM', 'BMU', '060', _('Bermuda')),
    ('BT', 'BTN', '064', _('Bhutan')),
    ('BO', 'BOL', '068', _('Bolivia')),
    ('BA', 'BIH', '070', _('Bosnia and Herzegovina')),
    ('BW', 'BWA', '072', _('Botswana')),
    ('BV', 'BVT', '074', _('Bouvet Island')),
    ('BR', 'BRA', '076', _('Brazil')),
    ('IO', 'IOT', '086', _('British Indian Ocean Territory')),
    ('BN', 'BRN', '096', _('Brunei Darussalam')),
    ('BG', 'BGR', '100', _('Bulgaria')),
    ('BF', 'BFA', '854', _('Burkina Faso')),
    ('BI', 'BDI', '108', _('Burundi')),
    ('KH', 'KHM', '116', _('Cambodia')),
    ('CM', 'CMR', '120', _('Cameroon')),
    ('CA', 'CAN', '124', _('Canada')),
    ('CV', 'CPV', '132', _('Cape Verde')),
    ('KY', 'CYM', '136', _('Cayman Islands')),
    ('CF', 'CAF', '140', _('Central African Republic')),
    ('TD', 'TCD', '148', _('Chad')),
    ('CL', 'CHL', '152', _('Chile')),
    ('CN', 'CHN', '156', _('China')),
    ('CX', 'CXR', '162', _('Christmas Island')),
    ('CC', 'CCK', '166', _('Cocos (Keeling) Islands')),
    ('CO', 'COL', '170', _('Colombia')),
    ('KM', 'COM', '174', _('Comoros')),
    ('CG', 'COG', '178', _('Congo')),
    ('CD', 'COD', '180', _('Democratic Republic of the Congo')),
    ('CK', 'COK', '184', _('Cook Islands')),
    ('CR', 'CRI', '188', _('Costa Rica')),
    ('CI', 'CIV', '384', _('Cote d\'Ivoire')),
    ('HR', 'HRV', '191', _('Croatia')),
    ('CU', 'CUB', '192', _('Cuba')),
    ('CY', 'CYP', '196', _('Cyprus')),
    ('CZ', 'CZE', '203', _('Czech Republic')),
    ('DK', 'DNK', '208', _('Denmark')),
    ('DJ', 'DJI', '262', _('Djibouti')),
    ('DM', 'DMA', '212', _('Dominica')),
    ('DO', 'DOM', '214', _('Dominican Republic')),
    ('EC', 'ECU', '218', _('Ecuador')),
    ('EG', 'EGY', '818', _('Egypt')),
    ('SV', 'SLV', '222', _('El Salvador')),
    ('GQ', 'GNQ', '226', _('Equatorial Guinea')),
    ('ER', 'ERI', '232', _('Eritrea')),
    ('EE', 'EST', '233', _('Estonia')),
    ('ET', 'ETH', '231', _('Ethiopia')),
    ('FK', 'FLK', '238', _('Falkland Islands (Malvinas)')),
    ('FO', 'FRO', '234', _('Faroe Islands')),
    ('FJ', 'FJI', '242', _('Fiji')),
    ('FI', 'FIN', '246', _('Finland')),
    ('FR', 'FRA', '250', _('France')),
    ('GF', 'GUF', '254', _('French Guiana')),
    ('PF', 'PYF', '258', _('French Polynesia')),
    ('TF', 'ATF', '260', _('French Southern and Antarctic Lands')),
    ('GA', 'GAB', '266', _('Gabon')),
    ('GM', 'GMB', '270', _('Gambia')),
    ('GE', 'GEO', '268', _('Georgia')),
    ('DE', 'DEU', '276', _('Germany')),
    ('GH', 'GHA', '288', _('Ghana')),
    ('GI', 'GIB', '292', _('Gibraltar')),
    ('GR', 'GRC', '300', _('Greece')),
    ('GL', 'GRL', '304', _('Greenland')),
    ('GD', 'GRD', '308', _('Grenada')),
    ('GP', 'GLP', '312', _('Guadeloupe')),
    ('GU', 'GUM', '316', _('Guam')),
    ('GT', 'GTM', '320', _('Guatemala')),
    ('GG', 'GGY', '831', _('Guernsey')),
    ('GN', 'GIN', '324', _('Guinea')),
    ('GW', 'GNB', '624', _('Guinea-Bissau')),
    ('GY', 'GUY', '328', _('Guyana')),
    ('HT', 'HTI', '332', _('Haiti')),
    ('HM', 'HMD', '334', _('Heard Island and McDonald Islands')),
    ('VA', 'VAT', '336', _('Vatican City Holy See')),
    ('HN', 'HND', '340', _('Honduras')),
    ('HK', 'HKG', '344', _('Hong Kong')),
    ('HU', 'HUN', '348', _('Hungary')),
    ('IS', 'ISL', '352', _('Iceland')),
    ('IN', 'IND', '356', _('India')),
    ('ID', 'IDN', '360', _('Indonesia')),
    ('IR', 'IRN', '364', _('Iran')),
    ('IQ', 'IRQ', '368', _('Iraq')),
    ('IE', 'IRL', '372', _('Ireland')),
    ('IM', 'IMN', '833', _('Isle of Man')),
    ('IL', 'ISR', '376', _('Israel')),
    ('IT', 'ITA', '380', _('Italy')),
    ('JM', 'JAM', '388', _('Jamaica')),
    ('JP', 'JPN', '392', _('Japan')),
    ('JE', 'JEY', '832', _('Jersey')),
    ('JO', 'JOR', '400', _('Jordan')),
    ('KZ', 'KAZ', '398', _('Kazakhstan')),
    ('KE', 'KEN', '404', _('Kenya')),
    ('KI', 'KIR', '296', _('Kiribati')),
    ('KP', 'PRK', '408', _('North Korea')),
    ('KR', 'KOR', '410', _('South Korea')),
    ('KW', 'KWT', '414', _('Kuwait')),
    ('KG', 'KGZ', '417', _('Kyrgyzstan')),
    ('LA', 'LAO', '418', _('Laos Lao')),
    ('LV', 'LVA', '428', _('Latvia')),
    ('LB', 'LBN', '422', _('Lebanon')),
    ('LS', 'LSO', '426', _('Lesotho')),
    ('LR', 'LBR', '430', _('Liberia')),
    ('LY', 'LBY', '434', _('Libya Libyan Arab Jamahiriya')),
    ('LI', 'LIE', '438', _('Liechtenstein')),
    ('LT', 'LTU', '440', _('Lithuania')),
    ('LU', 'LUX', '442', _('Luxembourg')),
    ('MO', 'MAC', '446', _('Macau Macao')),
    ('MK', 'MKD', '807', _('Macedonia')),
    ('MG', 'MDG', '450', _('Madagascar')),
    ('MW', 'MWI', '454', _('Malawi')),
    ('MY', 'MYS', '458', _('Malaysia')),
    ('MV', 'MDV', '462', _('Maldives')),
    ('ML', 'MLI', '466', _('Mali')),
    ('MT', 'MLT', '470', _('Malta')),
    ('MH', 'MHL', '584', _('Marshall Islands')),
    ('MQ', 'MTQ', '474', _('Martinique')),
    ('MR', 'MRT', '478', _('Mauritania')),
    ('MU', 'MUS', '480', _('Mauritius')),
    ('YT', 'MYT', '175', _('Mayotte')),
    ('MX', 'MEX', '484', _('Mexico')),
    ('FM', 'FSM', '583', _('Micronesia')),
    ('MD', 'MDA', '498', _('Moldova')),
    ('MC', 'MCO', '492', _('Monaco')),
    ('MN', 'MNG', '496', _('Mongolia')),
    ('ME', 'MNE', '499', _('Montenegro')),
    ('MS', 'MSR', '500', _('Montserrat')),
    ('MA', 'MAR', '504', _('Morocco')),
    ('MZ', 'MOZ', '508', _('Mozambique')),
    ('MM', 'MMR', '104', _('Myanmar')),
    ('NA', 'NAM', '516', _('Namibia')),
    ('NR', 'NRU', '520', _('Nauru')),
    ('NP', 'NPL', '524', _('Nepal')),
    ('NL', 'NLD', '528', _('Netherlands')),
    ('AN', 'ANT', '530', _('Netherlands Antilles')),
    ('NC', 'NCL', '540', _('New Caledonia')),
    ('NZ', 'NZL', '554', _('New Zealand')),
    ('NI', 'NIC', '558', _('Nicaragua')),
    ('NE', 'NER', '562', _('Niger')),
    ('NG', 'NGA', '566', _('Nigeria')),
    ('NU', 'NIU', '570', _('Niue')),
    ('NF', 'NFK', '574', _('Norfolk Island Norfolk Island')),
    ('MP', 'MNP', '580', _('Northern Mariana Islands')),
    ('NO', 'NOR', '578', _('Norway')),
    ('OM', 'OMN', '512', _('Oman')),
    ('PK', 'PAK', '586', _('Pakistan')),
    ('PW', 'PLW', '585', _('Palau')),
    ('PS', 'PSE', '275', _('Palestinian Territory')),
    ('PA', 'PAN', '591', _('Panama')),
    ('PG', 'PNG', '598', _('Papua New Guinea')),
    ('PY', 'PRY', '600', _('Paraguay')),
    ('PE', 'PER', '604', _('Peru')),
    ('PH', 'PHL', '608', _('Philippines')),
    ('PN', 'PCN', '612', _('Pitcairn Islands')),
    ('PL', 'POL', '616', _('Poland')),
    ('PT', 'PRT', '620', _('Portugal')),
    ('PR', 'PRI', '630', _('Puerto Rico')),
    ('QA', 'QAT', '634', _('Qatar')),
    ('RE', 'REU', '638', _('Reunion')),
    ('RO', 'ROU', '642', _('Romania')),
    ('RU', 'RUS', '643', _('Russia')),
    ('RW', 'RWA', '646', _('Rwanda')),
    ('SH', 'SHN', '654', _('Saint Helena')),
    ('KN', 'KNA', '659', _('Saint Kitts and Nevis')),
    ('LC', 'LCA', '662', _('Saint Lucia')),
    ('PM', 'SPM', '666', _('Saint Pierre and Miquelon')),
    ('VC', 'VCT', '670', _('Saint Vincent and the Grenadines')),
    ('WS', 'WSM', '882', _('Samoa')),
    ('SM', 'SMR', '674', _('San Marino')),
    ('ST', 'STP', '678', _('Sao Tome and Principe')),
    ('SA', 'SAU', '682', _('Saudi Arabia')),
    ('SN', 'SEN', '686', _('Senegal')),
    ('RS', 'SRB', '688', _('Serbia')),
    ('SC', 'SYC', '690', _('Seychelles')),
    ('SL', 'SLE', '694', _('Sierra Leone')),
    ('SG', 'SGP', '702', _('Singapore')),
    ('SK', 'SVK', '703', _('Slovakia')),
    ('SI', 'SVN', '705', _('Slovenia')),
    ('SB', 'SLB', '090', _('Solomon Islands')),
    ('SO', 'SOM', '706', _('Somalia')),
    ('ZA', 'ZAF', '710', _('South Africa')),
    ('GS', 'SGS', '239', _('South Georgia and the South Sandwich Islands')),
    ('ES', 'ESP', '724', _('Spain')),
    ('LK', 'LKA', '144', _('Sri Lanka')),
    ('SD', 'SDN', '736', _('Sudan')),
    ('SR', 'SUR', '740', _('Suriname')),
    ('SJ', 'SJM', '744', _('Svalbard and Jan Mayen')),
    ('SZ', 'SWZ', '748', _('Swaziland')),
    ('SE', 'SWE', '752', _('Sweden')),
    ('CH', 'CHE', '756', _('Switzerland')),
    ('SY', 'SYR', '760', _('Syria')),
    ('TW', 'TWN', '158', _('Taiwan')),
    ('TJ', 'TJK', '762', _('Tajikistan')),
    ('TZ', 'TZA', '834', _('Tanzania')),
    ('TH', 'THA', '764', _('Thailand')),
    ('TL', 'TLS', '626', _('East Timor')),
    ('TG', 'TGO', '768', _('Togo')),
    ('TK', 'TKL', '772', _('Tokelau')),
    ('TO', 'TON', '776', _('Tonga')),
    ('TT', 'TTO', '780', _('Trinidad and Tobago')),
    ('TN', 'TUN', '788', _('Tunisia')),
    ('TR', 'TUR', '792', _('Turkey')),
    ('TM', 'TKM', '795', _('Turkmenistan')),
    ('TC', 'TCA', '796', _('Turks and Caicos Islands')),
    ('TV', 'TUV', '798', _('Tuvalu')),
    ('UG', 'UGA', '800', _('Uganda')),
    ('UA', 'UKR', '804', _('Ukraine')),
    ('AE', 'ARE', '784', _('United Arab Emirates')),
    ('GB', 'GBR', '826', _('United Kingdom')),
    ('US', 'USA', '840', _('United States')),
    ('UM', 'UMI', '581', _('United States Minor Outlying Islands')),
    ('UY', 'URY', '858', _('Uruguay')),
    ('UZ', 'UZB', '860', _('Uzbekistan')),
    ('VU', 'VUT', '548', _('Vanuatu')),
    ('VE', 'VEN', '862', _('Venezuela')),
    ('VN', 'VNM', '704', _('Vietnam Viet Nam')),
    ('VG', 'VGB', '092', _('British Virgin Islands')),
    ('VI', 'VIR', '850', _('United States Virgin Islands')),
    ('WF', 'WLF', '876', _('Wallis and Futuna')),
    ('EH', 'ESH', '732', _('Western Sahara')),
    ('YE', 'YEM', '887', _('Yemen')),
    ('ZM', 'ZMB', '894', _('Zambia')),
    ('ZW', 'ZWE', '716', _('Zimbabwe')),
)
<file_sep>/koalixcrm/crm/documents/payment_reminder.py
# -*- coding: utf-8 -*-

from datetime import *
from django.db import models
from django.utils.translation import ugettext as _
from koalixcrm.crm.const.status import *
from django.core.validators import MaxValueValidator, MinValueValidator
from koalixcrm.crm.documents.sales_document import SalesDocument, OptionSalesDocument
from koalixcrm.plugin import *


class PaymentReminder(SalesDocument):
    payable_until = models.DateField(verbose_name=_("To pay until"))
    payment_bank_reference = models.CharField(verbose_name=_("Payment Bank Reference"), max_length=100, blank=True,
                                              null=True)
    iteration_number = models.IntegerField(blank=False, null=False, verbose_name=_("Iteration Number"),
                                           validators=[MinValueValidator(1), MaxValueValidator(3)])
    status = models.CharField(max_length=1, choices=INVOICESTATUS)

    def create_from_reference(self, calling_model):
        self.create_sales_document(calling_model)
        self.status = 'C'
        self.iteration_number = 1
        self.payable_until = date.today() + \
                             timedelta(days=self.customer.defaultCustomerBillingCycle.payment_reminder_time_to_payment)
        self.template_set = self.contract.get_template_set(self)
        self.save()
        self.attach_sales_document_positions(calling_model)
        self.attach_text_paragraphs()
        self.staff = calling_model.staff

    def __str__(self):
        return _("Payment Reminder") + ": " + self.id.__str__() + " " + _("from Contract") + ": " + self.contract.id.__str__()

    class Meta:
        app_label = "crm"
        verbose_name = _('Payment Reminder')
        verbose_name_plural = _('Payment Reminders')


class OptionPaymentReminder(OptionSalesDocument):
    list_display = OptionSalesDocument.list_display + ('payable_until', 'status', 'iteration_number')
    list_filter = OptionSalesDocument.list_filter + ('status',)
    ordering = OptionSalesDocument.ordering
    search_fields = OptionSalesDocument.search_fields
    fieldsets = OptionSalesDocument.fieldsets + (
        (_('Quote specific'), {
            'fields': ( 'payable_until', 'status', 'payment_bank_reference', 'iteration_number' )
        }),
    )

    save_as = OptionSalesDocument.save_as
    inlines = OptionSalesDocument.inlines
    actions = ['create_purchase_confirmation', 'create_invoice', 'create_quote',
               'create_delivery_note', 'create_pdf',
               'register_invoice_in_accounting', 'register_payment_in_accounting',]

    pluginProcessor = PluginProcessor()
    inlines.extend(pluginProcessor.getPluginAdditions("quoteInlines"))
<file_sep>/koalixcrm/crm/migrations/0045_auto_20180805_2047.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-08-05 20:47
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0044_reportingperiod_status'),
    ]

    operations = [
        migrations.AddField(
            model_name='work',
            name='worked_hours',
            field=models.DateTimeField(blank=True, null=True, verbose_name='Stop Time'),
        ),
        migrations.AlterField(
            model_name='task',
            name='planned_end_date',
            field=models.DateField(blank=True, null=True, verbose_name='Planned End'),
        ),
        migrations.AlterField(
            model_name='task',
            name='planned_start_date',
            field=models.DateField(blank=True, null=True, verbose_name='Planned Start'),
        ),
        migrations.AlterField(
            model_name='task',
            name='status',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.TaskStatus', verbose_name='Status'),
        ),
        migrations.AlterField(
            model_name='work',
            name='start_time',
            field=models.DateTimeField(blank=True, null=True, verbose_name='Start Time'),
        ),
        migrations.AlterField(
            model_name='work',
            name='stop_time',
            field=models.DateTimeField(blank=True, null=True, verbose_name='Stop Time'),
        ),
    ]
<file_sep>/koalixcrm/crm/migrations/0015_auto_20180111_2043.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-08 20:48
from __future__ import unicode_literals
from django.db import migrations
from django.db.migrations.operations.base import Operation
from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT


class MigrateSalesContractToSalesDocument(Operation):

    # If this is False, it means that this operation will be ignored by
    # sqlmigrate; if true, it will be run and the SQL collected for its output.
    reduces_to_sql = False

    # If this is False, Django will refuse to reverse past this operation.
    reversible = False

    def __init__(self, arg1, arg2):
        # Operations are usually instantiated with arguments in migration
        # files. Store the values of them on self for later use.
        pass


    def _get_model_tuple(self, remote_model, app_label, model_name):
        if remote_model == RECURSIVE_RELATIONSHIP_CONSTANT:
            return app_label, model_name.lower()
        elif '.' in remote_model:
            return tuple(remote_model.lower().split('.'))
        else:
            return app_label, remote_model.lower()

    def state_forwards(self, app_label, state):
        # Add a new model.
        renamed_model = state.models["crm", "salescontract"].clone()
        renamed_model.name = "SalesDocument"
        state.models["crm", "salesdocument"] = renamed_model
        # Repoint all fields pointing to the old model to the new one.
        old_model_tuple = "crm", "salescontract"
        old_remote_model = '%s.%s' % ("crm", "salescontract")
        new_remote_model = '%s.%s' % ("crm", "SalesDocument")
        to_reload = []
        for (model_app_label, model_name), model_state in state.models.items():
            if model_name != "salescontract" and model_name != "salesdocument":
                new_model_state = model_state
                model_with_new_base = state.models[model_app_label, model_name]
                for index, (name, field) in enumerate(model_state.fields):
                    changed_field = None
                    remote_field = field.remote_field
                    if remote_field:
                        remote_model_tuple = self._get_model_tuple(
                            remote_field.model, model_app_label, model_name
                        )
                        if remote_model_tuple == old_model_tuple:
                            changed_field = field.clone()

                            changed_field.remote_field.model = new_remote_model
                        through_model = getattr(remote_field, 'through', None)
                        if through_model:
                            through_model_tuple = self._get_model_tuple(
                                through_model, model_app_label, model_name
                            )
                            if through_model_tuple == old_model_tuple:
                                if changed_field is None:
                                    changed_field = field.clone()
                                changed_field.remote_field.through = new_remote_model
                    if changed_field:
                        new_model_state = model_state.clone()
                        new_model_state.fields[index] = name, changed_field
                        model_changed = True
                        if old_remote_model in model_state.bases:
                            new_bases = []
                            for base in model_state.bases:
                                if old_remote_model == base:
                                    new_bases.append(new_remote_model.lower())
                                    new_model_state.bases = tuple(new_bases)
                        if model_changed:
                            state.remove_model(model_app_label, model_name)
                            state.models[model_app_label, model_name] = new_model_state
                            to_reload.append((model_app_label, model_name))
        # Reload models related to old model before removing the old model.
        state.remove_model("crm", "salescontract")
        state.reload_model("crm", "salesdocument", delay=True)
        state.reload_models(to_reload, delay=True)
        # Remove the old model.

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        # The Operation should use schema_editor to apply any changes it
        # wants to make to the database.
        new_model = to_state.apps.get_model("crm", "SalesDocument")
        if self.allow_migrate_model(schema_editor.connection.alias, new_model):
            old_model = from_state.apps.get_model("crm", "SalesContract")
            # Move the main table
            schema_editor.alter_db_table(
                new_model,
                old_model._meta.db_table,
                new_model._meta.db_table,
            )
            # Alter the fields pointing to us
            for related_object in old_model._meta.related_objects:
                if related_object.related_model == old_model:
                    model = new_model
                    related_key = ("crm", "salesdocument")
                else:
                    model = related_object.related_model
                    related_key = (
                        related_object.related_model._meta.app_label,
                        related_object.related_model._meta.model_name,
                    )
                to_field = to_state.apps.get_model(*related_key)._meta.get_field(related_object.field.name)

                schema_editor.alter_field(
                    model,
                    related_object.field,
                    to_field,
                )
            # Rename M2M fields whose name is based on this model's name.
            fields = zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many)
            for (old_field, new_field) in fields:
                # Skip self-referential fields as these are renamed above.
                if new_field.model == new_field.related_model or not new_field.remote_field.through._meta.auto_created:
                    continue
                # Rename the M2M table that's based on this model's name.
                old_m2m_model = old_field.remote_field.through
                new_m2m_model = new_field.remote_field.through
                schema_editor.alter_db_table(
                    new_m2m_model,
                    old_m2m_model._meta.db_table,
                    new_m2m_model._meta.db_table,
                )
                # Rename the column in the M2M table that's based on this
                # model's name.
                schema_editor.alter_field(
                    new_m2m_model,
                    old_m2m_model._meta.get_field(old_model._meta.model_name),
                    new_m2m_model._meta.get_field(new_model._meta.model_name),
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        # If reversible is True, this is called when the operation is reversed.
        pass

    def describe(self):
        # This is used to describe what the operation does in console output.
        return "Custom Operation"

class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0014_auto_20180108_2048'),
    ]

    operations = [

        migrations.RemoveField(
            model_name='salescontract',
            name='derived_from_sales_contract',
        ),
        MigrateSalesContractToSalesDocument("crm", "salescontract")
    ]
<file_sep>/documentation/source/crm.rst
.. highlight:: rst

CRM
===

Introduction
------------

Products
--------

Contact
--------

Customer
^^^^^^^^

Documents
---------

Sales Contract
^^^^^^^^^^^^^^

Purchase Order
^^^^^^^^^^^^^^

Invoice
^^^^^^^

Quote
^^^^^

Reporting
---------



<file_sep>/koalixcrm/crm/migrations/0034_genericprojectlink.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-12 20:01
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('contenttypes', '0002_remove_content_type_name'),
        ('crm', '0033_auto_20180612_1936'),
    ]

    operations = [
        migrations.CreateModel(
            name='GenericProjectLink',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('object_id', models.PositiveIntegerField()),
                ('date_of_creation', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
                ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
                ('last_modified_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_project_link_last_modified', to=settings.AUTH_USER_MODEL, verbose_name='Last modified by')),
                ('project_link_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.ProjectLinkType', verbose_name='Project Link Type')),
                ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Project', verbose_name='Project')),
            ],
            options={
                'verbose_name': 'Project Link',
                'verbose_name_plural': 'Project Links',
            },
        ),
    ]
<file_sep>/koalixcrm/crm/reporting/estimation_status.py
# -*- 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',)
<file_sep>/koalixcrm/crm/migrations/0054_auto_20181020_1845.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-20 18:45
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0053_auto_20181014_2305'),
    ]

    operations = [
        migrations.AlterField(
            model_name='currency',
            name='short_name',
            field=models.CharField(max_length=3, verbose_name='Displayed Name After Prices'),
        ),
    ]
<file_sep>/koalixcrm/crm/tests/test_poject_admin.py
# -*- coding: utf-8 -*-
import pytest
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from koalixcrm.crm.factories.factory_user import AdminUserFactory


class TestProjectAdminView(LiveServerTestCase):

    def setUp(self):
        firefox_options = webdriver.firefox.options.Options()
        firefox_options.set_headless(headless=True)
        self.selenium = webdriver.Firefox(firefox_options=firefox_options)
        self.test_user = AdminUserFactory.create()

    def tearDown(self):
        self.selenium.quit()

    @pytest.mark.front_end_tests
    def test_project_admin(self):
        selenium = self.selenium
        # login
        selenium.get('%s%s' % (self.live_server_url, '/admin/crm/project/'))
        # the browser will be redirected to the login page
        timeout = 5
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, 'id_username'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        username = selenium.find_element_by_xpath('//*[@id="id_username"]')
        password = selenium.find_element_by_xpath('//*[@id="id_password"]')
        submit_button = selenium.find_element_by_xpath('/html/body/div/article/div/div/form/div/ul/li/input')
        username.send_keys("admin")
        password.send_keys("<PASSWORD>")
        submit_button.send_keys(Keys.RETURN)
        try:
            element_present = expected_conditions.presence_of_element_located((By.ID,
                                                                               '/html/body/div/article/header/ul/li/a'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
        # find the form element
        selenium.get('%s%s' % (self.live_server_url, '/admin/crm/project/add'))

        try:
            element_present = expected_conditions.presence_of_element_located((By.ID, '//*[@id="id_project_status"]'))
            WebDriverWait(selenium, timeout).until(element_present)
        except TimeoutException:
            print("Timed out waiting for page to load")
<file_sep>/koalixcrm/crm/factories/factory_delivery_note.py
# -*- coding: utf-8 -*-

from koalixcrm.crm.models import DeliveryNote
from koalixcrm.crm.factories.factory_sales_document import StandardSalesDocumentFactory


class StandardDeliveryNoteFactory(StandardSalesDocumentFactory):
    class Meta:
        model = DeliveryNote

    tracking_reference = "This is a tracking reference"
    status = "S"

<file_sep>/koalixcrm/crm/factories/factory_customer_group.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import CustomerGroup


class StandardCustomerGroupFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = CustomerGroup
        django_get_or_create = ('name',)

    name = factory.Sequence(lambda n: "Customer Group #%s" % n)


class AdvancedCustomerGroupFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = CustomerGroup

    name = factory.Sequence(lambda n: "Customer Group #%s" % n)
<file_sep>/koalixcrm/crm/migrations/0027_auto_20180606_2034.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-06-06 20:34
from __future__ import unicode_literals

import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('crm', '0026_auto_20180507_1957'),
    ]

    operations = [
        migrations.CreateModel(
            name='Call',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('description', models.TextField(verbose_name='Description')),
                ('date_of_creation', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
                ('date_due', models.DateTimeField(blank=True, default=datetime.datetime.now, verbose_name='Date due')),
                ('last_modification', models.DateTimeField(auto_now=True, verbose_name='Last modified')),
                ('status', models.CharField(choices=[('P', 'Planned'), ('D', 'Delayed'), ('R', 'ToRecall'), ('F', 'Failed'), ('S', 'Success')], default='P', max_length=1, verbose_name='Status')),
            ],
        ),
        migrations.CreateModel(
            name='ContactPersonAssociation',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('contact', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='person_association', to='crm.Contact')),
            ],
            options={
                'verbose_name': 'Contacts',
                'verbose_name_plural': 'Contacts',
            },
        ),
        migrations.CreateModel(
            name='Person',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('prefix', models.CharField(blank=True, choices=[('F', 'Company'), ('W', 'Mrs'), ('H', 'Mr'), ('G', 'Ms')], max_length=1, null=True, verbose_name='Prefix')),
                ('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Name')),
                ('prename', models.CharField(blank=True, max_length=100, null=True, verbose_name='Prename')),
                ('email', models.EmailField(max_length=200, verbose_name='Email Address')),
                ('phone', models.CharField(max_length=20, verbose_name='Phone Number')),
                ('role', models.CharField(blank=True, max_length=100, null=True, verbose_name='Role')),
                ('companies', models.ManyToManyField(blank=True, through='crm.ContactPersonAssociation', to='crm.Contact', verbose_name='Works at')),
            ],
            options={
                'verbose_name': 'Person',
                'verbose_name_plural': 'People',
            },
        ),
        migrations.AddField(
            model_name='customer',
            name='is_lead',
            field=models.BooleanField(default=True),
        ),
        migrations.CreateModel(
            name='CallForContact',
            fields=[
                ('call_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.Call')),
                ('purpose', models.CharField(choices=[('F', 'First commercial call'), ('S', 'Planned commercial call'), ('A', 'Assistance call')], max_length=1, verbose_name='Purpose')),
                ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Contact')),
                ('cperson', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Person', verbose_name='Person')),
            ],
            options={
                'verbose_name': 'Call',
                'verbose_name_plural': 'Calls',
            },
            bases=('crm.call',),
        ),
        migrations.CreateModel(
            name='VisitForContact',
            fields=[
                ('call_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.Call')),
                ('purpose', models.CharField(choices=[('F', 'First commercial visit'), ('S', 'Installation')], max_length=1, verbose_name='Purpose')),
                ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Contact')),
                ('cperson', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Person', verbose_name='Person')),
                ('ref_call', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.CallForContact', verbose_name='Reference Call')),
            ],
            options={
                'verbose_name': 'Visit',
                'verbose_name_plural': 'Visits',
            },
            bases=('crm.call',),
        ),
        migrations.AddField(
            model_name='contactpersonassociation',
            name='person',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contact_association', to='crm.Person'),
        ),
        migrations.AddField(
            model_name='call',
            name='last_modified_by',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_calllstmodified', to=settings.AUTH_USER_MODEL, verbose_name='Last modified by'),
        ),
        migrations.AddField(
            model_name='call',
            name='staff',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_relcallstaff', to=settings.AUTH_USER_MODEL, verbose_name='Staff'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_estimation_status.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import EstimationStatus


class ObsoleteEstimationStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = EstimationStatus
        django_get_or_create = ('title',)

    title = "Done"
    description = "This estimation is obsolete"
    is_obsolete = True


class PlannedEstimationStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = EstimationStatus
        django_get_or_create = ('title',)

    title = "Planned"
    description = "This estimation is planned"
    is_obsolete = False


class StartedEstimationStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = EstimationStatus
        django_get_or_create = ('title',)

    title = "Started"
    description = "This estimation is started"
    is_obsolete = False
<file_sep>/koalixcrm/crm/migrations/0025_auto_20180413_1937.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-04-13 19:37
from __future__ import unicode_literals

import django.core.validators
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0004_auto_20171210_2126'),
        ('contenttypes', '0002_remove_content_type_name'),
        ('crm', '0024_auto_20180122_2121'),
    ]

    operations = [
        migrations.CreateModel(
            name='EmployeeAssignmentToTask',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('planned_effort', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Effort')),
                ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.UserExtension')),
            ],
            options={
                'verbose_name': 'Employee Assignment',
                'verbose_name_plural': 'Employee Assignments',
            },
        ),
        migrations.CreateModel(
            name='GenericTaskLink',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('object_id', models.PositiveIntegerField()),
                ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
            ],
            options={
                'verbose_name': 'Task Link',
                'verbose_name_plural': 'Task Links',
            },
        ),
        migrations.CreateModel(
            name='Task',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('short_description', models.CharField(blank=True, max_length=100, null=True, verbose_name='Description')),
                ('planned_start_date', models.DateField(verbose_name='Planned Start Date')),
                ('planned_end_date', models.DateField(verbose_name='Planned End Date')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Description')),
                ('last_status_change', models.DateField(blank=True, verbose_name='Last Status Change')),
                ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Contract', verbose_name='Contract')),
            ],
            options={
                'verbose_name': 'Task',
                'verbose_name_plural': 'Tasks',
            },
        ),
        migrations.CreateModel(
            name='TaskLinkType',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=300, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
            ],
            options={
                'verbose_name': 'Task Link Type',
                'verbose_name_plural': 'Task Link Type',
            },
        ),
        migrations.CreateModel(
            name='TaskStatus',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=250, verbose_name='Title')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
                ('is_done', models.BooleanField(verbose_name='Status represents task done')),
            ],
            options={
                'verbose_name': 'Task Status',
                'verbose_name_plural': 'Task Status',
            },
        ),
        migrations.CreateModel(
            name='Work',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('date', models.DateField(verbose_name='Date')),
                ('start_time', models.DateTimeField(verbose_name='Start Time')),
                ('stop_time', models.DateTimeField(verbose_name='Stop Time')),
                ('short_description', models.CharField(max_length=300, verbose_name='Short Description')),
                ('description', models.TextField(blank=True, null=True, verbose_name='Text')),
                ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.UserExtension')),
                ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Task', verbose_name='Task')),
            ],
            options={
                'verbose_name': 'Work',
                'verbose_name_plural': 'Work',
            },
        ),
        migrations.AlterField(
            model_name='position',
            name='position_number',
            field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)], verbose_name='Position Number'),
        ),
        migrations.AddField(
            model_name='task',
            name='status',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.TaskStatus', verbose_name='Task Status'),
        ),
        migrations.AddField(
            model_name='generictasklink',
            name='task',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Task', verbose_name='Task'),
        ),
        migrations.AddField(
            model_name='generictasklink',
            name='task_link_type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.TaskLinkType', verbose_name='Task Link Type'),
        ),
        migrations.AddField(
            model_name='employeeassignmenttotask',
            name='task',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Task', verbose_name='Task'),
        ),
    ]
<file_sep>/koalixcrm/crm/rest/tax_rest.py
from rest_framework import serializers

from koalixcrm.crm.product.tax import Tax


class OptionTaxJSONSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.IntegerField(required=False)
    description = serializers.CharField(source='name', read_only=True)

    class Meta:
        model = Tax
        fields = ('id',
                  'description')


class TaxJSONSerializer(serializers.HyperlinkedModelSerializer):
    rate = serializers.CharField(source='tax_rate')
    description = serializers.CharField(source='name')

    class Meta:
        model = Tax
        fields = ('id',
                  'rate',
                  'description')
<file_sep>/documentation/source/code_documentation/estimation.rst
.. highlight:: rst

Estimation
----------

.. automodule:: koalixcrm.crm.reporting.estimation
   :members:

<file_sep>/koalixcrm/crm/factories/factory_customer.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Customer
from koalixcrm.crm.factories.factory_contact import StandardContactFactory
from koalixcrm.crm.factories.factory_customer_billing_cycle import StandardCustomerBillingCycleFactory


class StandardCustomerFactory(StandardContactFactory):
    class Meta:
        model = Customer

    default_customer_billing_cycle = factory.SubFactory(StandardCustomerBillingCycleFactory)
    is_lead = True

    @factory.post_generation
    def is_member_of(self, create, extracted):
        if not create:
            # Simple build, do nothing.
            return

        if extracted:
            # A list of groups were passed in, use them
            for group in extracted:
                self.is_member_of.add(group)
<file_sep>/koalixcrm/djangoUserExtension/admin.py
# -*- coding: utf-8 -*-


from koalixcrm.djangoUserExtension.models import *
from koalixcrm.djangoUserExtension.user_extension.user_extension import UserExtension, OptionUserExtension
from koalixcrm.djangoUserExtension.user_extension.template_set import TemplateSet, OptionTemplateSet
from koalixcrm.djangoUserExtension.user_extension.document_template import InvoiceTemplate
from koalixcrm.djangoUserExtension.user_extension.document_template import QuoteTemplate
from koalixcrm.djangoUserExtension.user_extension.document_template import DeliveryNoteTemplate
from koalixcrm.djangoUserExtension.user_extension.document_template import PaymentReminderTemplate
from koalixcrm.djangoUserExtension.user_extension.document_template import PurchaseOrderTemplate
from koalixcrm.djangoUserExtension.user_extension.document_template import ProfitLossStatementTemplate
from koalixcrm.djangoUserExtension.user_extension.document_template import BalanceSheetTemplate
from koalixcrm.djangoUserExtension.user_extension.document_template import OptionDocumentTemplate


admin.site.register(UserExtension, OptionUserExtension)
admin.site.register(TemplateSet, OptionTemplateSet)
admin.site.register(InvoiceTemplate, OptionDocumentTemplate)
admin.site.register(QuoteTemplate, OptionDocumentTemplate)
admin.site.register(DeliveryNoteTemplate, OptionDocumentTemplate)
admin.site.register(PaymentReminderTemplate, OptionDocumentTemplate)
admin.site.register(PurchaseOrderTemplate, OptionDocumentTemplate)
admin.site.register(PurchaseConfirmationTemplate, OptionDocumentTemplate)
admin.site.register(ProfitLossStatementTemplate, OptionDocumentTemplate)
admin.site.register(BalanceSheetTemplate, OptionDocumentTemplate)
admin.site.register(MonthlyProjectSummaryTemplate, OptionDocumentTemplate)
admin.site.register(WorkReportTemplate, OptionDocumentTemplate)
<file_sep>/koalixcrm/crm/rest/currency_rest.py
from rest_framework import serializers

from koalixcrm.crm.product.currency import Currency


class CurrencyJSONSerializer(serializers.HyperlinkedModelSerializer):
    shortName = serializers.CharField(source='short_name')

    class Meta:
        model = Currency
        fields = ('id',
                  'description',
                  'shortName',
                  'rounding')
<file_sep>/koalixcrm/subscriptions/migrations/0001_initial.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:02
from __future__ import unicode_literals

import django.db.models.deletion
import filebrowser.fields
from django.db import migrations, models


class Migration(migrations.Migration):
    initial = True

    dependencies = [
        ('crm', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='Subscription',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('contract', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Contract',
                                               verbose_name='Subscription Type')),
            ],
            options={
                'verbose_name': 'Subscription',
                'verbose_name_plural': 'Subscriptions',
            },
        ),
        migrations.CreateModel(
            name='SubscriptionEvent',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('eventdate', models.DateField(blank=True, null=True, verbose_name='Event Date')),
                ('event', models.CharField(choices=[('O', 'Offered'), ('C', 'Canceled'), ('S', 'Signed')], max_length=1,
                                           verbose_name='Event')),
                ('subscriptions',
                 models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='subscriptions.Subscription',
                                   verbose_name='Subscription')),
            ],
            options={
                'verbose_name': 'Subscription Event',
                'verbose_name_plural': 'Subscription Events',
            },
        ),
        migrations.CreateModel(
            name='SubscriptionType',
            fields=[
                ('product_ptr',
                 models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
                                      primary_key=True, serialize=False, to='crm.Product')),
                ('cancelationPeriod',
                 models.IntegerField(blank=True, null=True, verbose_name='Cancelation Period (months)')),
                ('automaticContractExtension',
                 models.IntegerField(blank=True, null=True, verbose_name='Automatic Contract Extension (months)')),
                ('automaticContractExtensionReminder', models.IntegerField(blank=True, null=True,
                                                                           verbose_name='Automatic Contract Extensoin Reminder (days)')),
                ('minimumDuration',
                 models.IntegerField(blank=True, null=True, verbose_name='Minimum Contract Duration')),
                ('paymentIntervall',
                 models.IntegerField(blank=True, null=True, verbose_name='Payment Intervall (days)')),
                ('contractDocument', filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True,
                                                                        verbose_name='Contract Documents')),
            ],
            options={
                'verbose_name': 'Subscription Type',
                'verbose_name_plural': 'Subscription Types',
            },
            bases=('crm.product',),
        ),
        migrations.AddField(
            model_name='subscription',
            name='subscriptiontype',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
                                    to='subscriptions.SubscriptionType', verbose_name='Subscription Type'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_invoice.py
# -*- coding: utf-8 -*-

from koalixcrm.crm.models import Invoice
from koalixcrm.crm.factories.factory_sales_document import StandardSalesDocumentFactory


class StandardInvoiceFactory(StandardSalesDocumentFactory):
    class Meta:
        model = Invoice

    payable_until = "2018-05-20"
    payment_bank_reference = "This is a bank account reference"
    status = "C"
<file_sep>/koalixcrm/accounting/migrations/0002_auto_20170705_1702.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:02
from __future__ import unicode_literals

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
    initial = True

    dependencies = [
        ('crm', '0001_initial'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('accounting', '0001_initial'),
    ]

    operations = [
        migrations.AddField(
            model_name='booking',
            name='bookingReference',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
                                    to='crm.Invoice', verbose_name='Booking Reference'),
        ),
        migrations.AddField(
            model_name='booking',
            name='fromAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_fromaccount',
                                    to='accounting.Account', verbose_name='From Account'),
        ),
        migrations.AddField(
            model_name='booking',
            name='lastmodifiedby',
            field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE,
                                    related_name='db_booking_lstmodified', to=settings.AUTH_USER_MODEL,
                                    verbose_name='Last modified by'),
        ),
        migrations.AddField(
            model_name='booking',
            name='staff',
            field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE,
                                    related_name='db_booking_refstaff', to=settings.AUTH_USER_MODEL,
                                    verbose_name='Reference Staff'),
        ),
        migrations.AddField(
            model_name='booking',
            name='toAccount',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='db_booking_toaccount',
                                    to='accounting.Account', verbose_name='To Account'),
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_product_price.py
# -*- coding: utf-8 -*-

import factory
import datetime
from koalixcrm.crm.models import ProductPrice
from koalixcrm.crm.factories.factory_product import StandardProductFactory
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.crm.factories.factory_customer_group import StandardCustomerGroupFactory
from koalixcrm.test_support_functions import make_date_utc


class StandardPriceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProductPrice
        django_get_or_create = ('product_type',
                                'unit',
                                'currency',
                                'customer_group',
                                'price',
                                'valid_from',
                                'valid_until')

    product_type = factory.SubFactory(StandardProductFactory)
    unit = factory.SubFactory(StandardUnitFactory)
    currency = factory.SubFactory(StandardCurrencyFactory)
    customer_group = factory.SubFactory(StandardCustomerGroupFactory)
    price = "100.50"
    valid_from = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    valid_until = make_date_utc(datetime.datetime(2024, 6, 15, 00))


class HighPriceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProductPrice
    product_type = factory.SubFactory(StandardProductFactory)
    unit = factory.SubFactory(StandardUnitFactory)
    currency = factory.SubFactory(StandardCurrencyFactory)
    customer_group = factory.SubFactory(StandardCustomerGroupFactory)
    price = "250.50"
    valid_from = make_date_utc(datetime.datetime(2018, 6, 15, 00))
    valid_until = make_date_utc(datetime.datetime(2024, 6, 15, 00))
<file_sep>/koalixcrm/crm/migrations/0048_auto_20181012_2056.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-12 20:56
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('djangoUserExtension', '0007_auto_20180702_1628'),
        ('accounting', '0008_auto_20181012_2056'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('crm', '0047_work_worked_hours'),
        ('subscriptions', '0004_auto_20181013_2213')
    ]

    operations = [
        migrations.AddField(
            model_name='customergrouptransform',
            name='product_backup',
            field=models.IntegerField(blank=True, null=True, verbose_name='Product Backup Identifier'),
        ),
        migrations.AddField(
            model_name='position',
            name='product_backup',
            field=models.IntegerField(blank=True, null=True, verbose_name='Product Backup Identifier'),
        ),
        migrations.AddField(
            model_name='unittransform',
            name='product_backup',
            field=models.IntegerField(blank=True, null=True, verbose_name='Product Backup Identifier'),
        ),
        migrations.AddField(
            model_name='price',
            name='product_backup',
            field=models.IntegerField(blank=True, null=True, verbose_name='Product Backup Identifier'),
        ),
    ]
<file_sep>/documentation/source/customisation.rst
.. highlight:: rst

Customising koalixcrm
=====================

There are several ways in which to customise koalixcrm, depending on your requirements.

In all cases contributions can be submitted back to koalixcrm by opening an
issue in `GitHub <https://github.com/scaphilo/koalixcrm>`_ - see our
repositories CONTRIBUTING.md file for more information.

Translators
-----------

The Application itself
^^^^^^^^^^^^^^^^^^^^^^
To translate the application itself the only thing you have to do is to install koalixcrm on a Linux or Windows Machine. Please excuse that I can't give you good advice in how to install
the software under Windows I simply do not have the nerves to play around with the Windows powershell or the cmd :-).  

::

  cd /var/www/koalixcrm/crm
  django-admin makemessages -l yourlanguage

this creates a new directory in the folder called ``/var/www/koalixcrm/crm/locale/yourlanguage/LC_MESSAGES/``
in this directory you will find one file called django.po -  edit this file in the following way:

Lets say you want to translate koalixcrm to French. Find all msgid's in django.po and look at the english text.
The msgstr following a msgid will be the place you write your translation.

Example::
  msgid "phonenumber"
  msgstr "phonenumber"

Adjust it to the following::
  msgid "phonenumber"
  msgstr "nombre de telefon"

After you finised translating the whole file test your translation by compiling it. 

::

  django-admin compilemessages 

After this you are able to use your translation. Sometimes setting your language in ``/var/www/koalixcrm/settings.py`` is required.
Go on with your translation for accounting and for djangouserextention folders.
As soon as you finish this part you will be able to work with koalixcrm in your own language. But there is still something missing: the templatefiles for pdf creation have to be translated as well.


Custom template files for PDF creation
--------------------------------------

For translators or localisers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To translate the templatefiles for PDF creation you will have to adjust the templatefiles in the ``/var/www/koalixcrm/templatefiles`` folder. There you will find a folder for every language code that is already 
translated by the koalixcrm comunity or me. To add your own language type the following::

  cp -R en yourlanguage

now open yourlanguage folder and adjust the content of every xml file.


Site specific customisation
^^^^^^^^^^^^^^^^^^^^^^^^^^^

As with customisations for translators, the focus for site specific
customisation is koalixcrm's install path/templatefiles/yourlanguage. Entries
in the yourlanguage folder can be edited as you wish, to produce documents with
your letterhead, contact details or other modifications.


Modders
------- 

There is currently no API available ... I'm going to start with that as soon as possible but certainly after I finish
the rest of the user documentation.
<file_sep>/koalixcrm/djangoUserExtension/factories/factory_template_set.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.djangoUserExtension.models import TemplateSet


class StandardTemplateSetFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = TemplateSet

    title = "Just an empty Template Set"
<file_sep>/koalixcrm/accounting/admin.py
# -*- coding: utf-8 -*-

from django.contrib import admin

from koalixcrm.accounting.accounting.accounting_period import AccountingPeriod, OptionAccountingPeriod
from koalixcrm.accounting.accounting.booking import Booking, OptionBooking
from koalixcrm.accounting.accounting.account import Account, OptionAccount
from koalixcrm.accounting.accounting.product_category import ProductCategory, OptionProductCategory

admin.site.register(Account, OptionAccount)
admin.site.register(Booking, OptionBooking)
admin.site.register(ProductCategory, OptionProductCategory)
admin.site.register(AccountingPeriod, OptionAccountingPeriod)
<file_sep>/koalixcrm/global_support_functions.py
# -*- coding: utf-8 -*-

import datetime


def limit_string_length(input_string, maximum_length):
    if len(input_string) > maximum_length:
        output_string = input_string[:maximum_length]
    else:
        output_string = input_string
    return output_string


def get_string_between(input_string, search_start, search_end):
    pos_start = input_string.index(search_start)
    pos_end = input_string.index(search_end)
    output_string = input_string[pos_start:pos_end]
    return output_string


def xstr(s):
    if s is None:
        return ""
    else:
        return str(s)


def get_today_date():
    return datetime.date.today()


def get_today_datetime():
    return datetime.datetime.today()


class ConditionalMethodDecorator(object):
    def __init__(self, dec, condition):
        self.decorator = dec
        self.condition = condition

    def __call__(self, func):
        if not self.condition:
            # Return the function unchanged, not decorated.
            return func
        return self.decorator(func)
<file_sep>/koalixcrm/subscriptions/migrations/0002_auto_20171009_1949.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-09 19:49
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import filebrowser.fields


class Migration(migrations.Migration):

    dependencies = [
        ('subscriptions', '0001_initial'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='subscription',
            options={'verbose_name': 'Abonnement', 'verbose_name_plural': 'Abonnemente'},
        ),
        migrations.AlterModelOptions(
            name='subscriptionevent',
            options={'verbose_name': 'Abonnement Ereignis', 'verbose_name_plural': 'Abonnement Ereignisse'},
        ),
        migrations.AlterModelOptions(
            name='subscriptiontype',
            options={'verbose_name': 'Abonnement Typ', 'verbose_name_plural': 'Abonnement Typen'},
        ),
        migrations.AlterField(
            model_name='subscription',
            name='contract',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Contract', verbose_name='Abonnement Typ'),
        ),
        migrations.AlterField(
            model_name='subscription',
            name='subscriptiontype',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='subscriptions.SubscriptionType', verbose_name='Abonnement Typ'),
        ),
        migrations.AlterField(
            model_name='subscriptionevent',
            name='event',
            field=models.CharField(choices=[('O', 'Offeriert'), ('C', 'Aufgelöst'), ('S', 'Unterschrieben')], max_length=1, verbose_name='Ereignis'),
        ),
        migrations.AlterField(
            model_name='subscriptionevent',
            name='eventdate',
            field=models.DateField(blank=True, null=True, verbose_name='Ereignis Datum'),
        ),
        migrations.AlterField(
            model_name='subscriptionevent',
            name='subscriptions',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='subscriptions.Subscription', verbose_name='Abonnement'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='automaticContractExtension',
            field=models.IntegerField(blank=True, null=True, verbose_name='Automatische Vertragsverlängerung in Monaten'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='automaticContractExtensionReminder',
            field=models.IntegerField(blank=True, null=True, verbose_name='Automatische Vertragsverlängerung Erinnerung in Tagen'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='cancelationPeriod',
            field=models.IntegerField(blank=True, null=True, verbose_name='Kündigungsfirst in Monaten'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='contractDocument',
            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, null=True, verbose_name='Vertragsdokumente'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='minimumDuration',
            field=models.IntegerField(blank=True, null=True, verbose_name='Minimale Vertragsdauer'),
        ),
        migrations.AlterField(
            model_name='subscriptiontype',
            name='paymentIntervall',
            field=models.IntegerField(blank=True, null=True, verbose_name='Zahlungsintervall'),
        ),
    ]
<file_sep>/koalixcrm/crm/migrations/0037_auto_20180701_2128.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-07-01 21:28
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0036_auto_20180626_2059'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='reportingperiod',
            options={'verbose_name': 'Reporting Period', 'verbose_name_plural': 'Reporting Periods'},
        ),
    ]
<file_sep>/koalixcrm/crm/documents/purchase_confirmation.py
# -*- coding: utf-8 -*-

from django.utils.translation import ugettext as _
from koalixcrm.crm.documents.sales_document import SalesDocument, OptionSalesDocument


class PurchaseConfirmation(SalesDocument):

    def create_from_reference(self, calling_model):
        self.create_sales_document(calling_model)
        self.template_set = self.contract.get_template_set(self)
        self.save()
        self.attach_sales_document_positions(calling_model)
        self.attach_text_paragraphs()

    def __str__(self):
        return _("Purchase Confirmation") + ": " + self.id.__str__() + " " + _("from Contract") + ": " + self.contract.id.__str__()

    class Meta:
        app_label = "crm"
        verbose_name = _('Purchase Confirmation')
        verbose_name_plural = _('Purchase Confirmations')


class OptionPurchaseConfirmation(OptionSalesDocument):
    list_display = OptionSalesDocument.list_display
    list_filter = OptionSalesDocument.list_filter
    ordering = OptionSalesDocument.ordering
    search_fields = OptionSalesDocument.search_fields
    fieldsets = OptionSalesDocument.fieldsets

    save_as = OptionSalesDocument.save_as
    inlines = OptionSalesDocument.inlines
    actions = ['create_invoice', 'create_quote',
               'create_delivery_note', 'create_purchase_order', 'create_', 'create_pdf']
<file_sep>/koalixcrm/crm/const/purpose.py
# -*- coding: utf-8 -*

from django.utils.translation import ugettext as _

PURPOSESADDRESSINCONTRACT = (
    ('D', _('Delivery Address')),
    ('B', _('Billing Address')),
    ('C', _('Contact Address')),
)

PURPOSESADDRESSINCUSTOMER = (
    ('H', _('Private')),
    ('O', _('Business')),
    ('P', _('Mobile Private')),
    ('B', _('Mobile Business')),
)

PURPOSESTEXTPARAGRAPHINDOCUMENTS = (
    ('BS', _('Before subject')),
    ('AS', _('After subject')),
    ('BT', _('Before total')),
    ('AT', _('After total')),
    ('BW', _('Before wishes')),
    ('AW', _('After wishes')),
    ('C1', _('Custom 1')),
    ('C2', _('Custom 2')),
    ('C3', _('Custom 3')),
    ('C4', _('Custom 4')),
)

PURPOSECALLINCUSTOMER = (
    ('F', _('First commercial call')),
    ('S', _('Planned commercial call')),
    ('A', _('Assistance call')),
)

PURPOSEVISITINCUSTOMER = (
    ('F', _('First commercial visit')),
    ('S', _('Installation')),
)<file_sep>/koalixcrm/crm/tests/test_incomplete_sales_document_position.py
import pytest
import datetime
from django.test import TestCase
from koalixcrm.crm.documents.calculations import Calculations
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.crm.factories.factory_quote import StandardQuoteFactory
from koalixcrm.crm.factories.factory_sales_document_position import StandardSalesDocumentPositionFactory
from koalixcrm.crm.factories.factory_product_type import StandardProductTypeFactory
from koalixcrm.crm.factories.factory_product_price import StandardPriceFactory
from koalixcrm.crm.factories.factory_customer import StandardCustomerFactory
from koalixcrm.crm.factories.factory_customer_group import StandardCustomerGroupFactory, AdvancedCustomerGroupFactory
from koalixcrm.crm.factories.factory_tax import StandardTaxFactory
from koalixcrm.crm.factories.factory_unit import StandardUnitFactory, SmallUnitFactory
from koalixcrm.test_support_functions import make_date_utc


class DocumentSalesDocumentPosition(TestCase):
    def setUp(self):
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        start_date = (datetime_now - datetime.timedelta(days=30)).date()
        end_date = (datetime_now + datetime.timedelta(days=30)).date()
        self.tax = StandardTaxFactory.create(tax_rate=10)
        self.test_currency_with_rounding = StandardCurrencyFactory.create(rounding=1)
        self.test_currency_without_rounding = StandardCurrencyFactory.create(
            rounding=None,
            description='Euro',
            short_name='EUR'
        )
        self.alternative_currency = self.test_currency_without_rounding
        self.customer_group = StandardCustomerGroupFactory.create()
        self.alternative_customer_group = AdvancedCustomerGroupFactory.create()
        self.customer = StandardCustomerFactory.create()
        self.customer.is_member_of.add(self.customer_group)
        self.customer.save()
        self.unit = StandardUnitFactory.create()
        self.alternative_unit = SmallUnitFactory.create()
        self.product_without_dates = StandardProductTypeFactory.create(
            product_type_identifier="A",
            tax=self.tax
        )
        self.price_without_customer_group = StandardPriceFactory.create(
            product_type=self.product_without_dates,
            customer_group=None,
            price=100,
            unit=self.unit,
            currency=self.test_currency_with_rounding,
            valid_from=start_date,
            valid_until=end_date
        )

    @pytest.mark.back_end_tests
    def test_calculate_document_price_overwritten(self):
        quote_1 = StandardQuoteFactory.create(customer=self.customer)
        StandardSalesDocumentPositionFactory.create(
            quantity=1,
            discount=0,
            product_type=self.product_without_dates,
            overwrite_product_price=True,
            position_price_per_unit=90,
            unit=self.unit,
            sales_document=quote_1
        )
        datetime_now = make_date_utc(datetime.datetime(2024, 1, 1, 0, 00))
        date_now = datetime_now.date()
        Calculations.calculate_document_price(
            document=quote_1,
            pricing_date=date_now)
        self.assertEqual(
            quote_1.last_calculated_price.__str__(), "81.00")
        self.assertEqual(
            quote_1.last_calculated_tax.__str__(), "9.00")

<file_sep>/koalixcrm/djangoUserExtension/models.py
# -*- coding: utf-8 -*-

from koalixcrm.djangoUserExtension.user_extension.document_template import *
from koalixcrm.djangoUserExtension.user_extension.template_set import *
from koalixcrm.djangoUserExtension.user_extension.user_extension import *
from koalixcrm.djangoUserExtension.user_extension.text_paragraph import *



<file_sep>/koalixcrm/crm/factories/factory_project_link_type.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import ProjectLinkType


class RelatedToProjectLinkTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProjectLinkType

    title = "Is related to"
    description = "This project is related with ...."


class RequiresProjectLinkTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProjectLinkType

    title = "This project requires"
    description = "This project requires the completion or the existence of ...."
<file_sep>/koalixcrm/crm/reporting/agreement_status.py
# -*- 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 AgreementStatus(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_agreed = models.BooleanField(verbose_name=_("Status represents agreement exists"),)

    class Meta:
        app_label = "crm"
        verbose_name = _('Agreement Status')
        verbose_name_plural = _('Agreement Status')

    def __str__(self):
        return str(self.id) + " " + str(self.title)


class AgreementStatusAdminView(admin.ModelAdmin):
    list_display = ('id',
                    'title',
                    'description',
                    'is_agreed')

    fieldsets = (
        (_('Agreement Status'), {
            'fields': ('title',
                       'description',
                       'is_agreed')
        }),
    )
    save_as = True


class AgreementStatusJSONSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = AgreementStatus
        fields = ('id',
                  'title',
                  'description',)
<file_sep>/koalixcrm/crm/migrations/0050_auto_20181014_2300.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-10-12 20:56
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0049_auto_20181014_2258'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='customergrouptransform',
            name='product',
        ),
        migrations.RemoveField(
            model_name='position',
            name='product',
        ),
        migrations.RemoveField(
            model_name='unittransform',
            name='product',
        ),
        migrations.RemoveField(
            model_name='Price',
            name='product',
        ),
        migrations.RenameModel(old_name='Product',
                               new_name='ProductType'),

        migrations.CreateModel(
            name='ProductPrice',
            fields=[
                ('price_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.Price')),
                ('product_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.ProductType', verbose_name='Product'),)
            ],
            options={
                'verbose_name': 'Product Price',
                'verbose_name_plural': 'Product Prices',
            },
            bases=('crm.price',),
        ),
        migrations.AddField(
            model_name='customergrouptransform',
            name='product_type',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='crm.ProductType', verbose_name='Product Type'),
            preserve_default=False,
        ),
        migrations.AddField(
            model_name='position',
            name='product_type',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='crm.ProductType', verbose_name='Product'),
        ),
        migrations.AddField(
            model_name='unittransform',
            name='product_type',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='crm.ProductType', verbose_name='Product Type'),
            preserve_default=False,
        ),
    ]
<file_sep>/koalixcrm/djangoUserExtension/user_extension/template_set.py
# -*- coding: utf-8 -*-

from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _

from koalixcrm.global_support_functions import xstr
from koalixcrm.crm.exceptions import *


class TemplateSet(models.Model):
    title = models.CharField(verbose_name=_("Title"),
                             max_length=100)
    invoice_template = models.ForeignKey("InvoiceTemplate",
                                         blank=True,
                                         null=True)
    quote_template = models.ForeignKey("QuoteTemplate",
                                       blank=True,
                                       null=True)
    delivery_note_template = models.ForeignKey("DeliveryNoteTemplate",
                                               blank=True, null=True)
    payment_reminder_template = models.ForeignKey("PaymentReminderTemplate",
                                                  blank=True,
                                                  null=True)
    purchase_confirmation_template = models.ForeignKey("PurchaseConfirmationTemplate",
                                                       blank=True, null=True)
    purchase_order_template = models.ForeignKey("PurchaseOrderTemplate",
                                                blank=True,
                                                null=True)
    profit_loss_statement_template = models.ForeignKey("ProfitLossStatementTemplate",
                                                       blank=True, null=True)
    balance_sheet_statement_template = models.ForeignKey("BalanceSheetTemplate",
                                                         blank=True,
                                                         null=True)
    monthly_project_summary_template = models.ForeignKey("MonthlyProjectSummaryTemplate",
                                                         blank=True,
                                                         null=True)
    work_report_template = models.ForeignKey("WorkReportTemplate",
                                             blank=True,
                                             null=True)

    class Meta:
        app_label = "djangoUserExtension"
        verbose_name = _('Template-set')
        verbose_name_plural = _('Template-sets')

    def __str__(self):
        return xstr(self.id) + ' ' + xstr(self.title)

    def get_template_set(self, required_template_set):
        mapping_class_to_templates = {"Invoice": self.invoice_template,
                                      "Quote": self.quote_template,
                                      "DeliveryNote": self.delivery_note_template,
                                      "PaymentReminder": self.payment_reminder_template,
                                      "PurchaseConfirmation": self.purchase_confirmation_template,
                                      "PurchaseOrder": self.purchase_order_template,
                                      "ProfitLossStatement": self.profit_loss_statement_template,
                                      "BalanceSheet": self.balance_sheet_statement_template,
                                      "MonthlyProjectSummaryTemplate": self.monthly_project_summary_template,
                                      "WorkReport": self.work_report_template}
        try:
            if mapping_class_to_templates[required_template_set]:
                return mapping_class_to_templates[required_template_set]
            else:
                raise TemplateMissingInTemplateSet("The TemplateSet does not contain a template for " +
                                                   required_template_set)
        except KeyError:
            raise IncorrectUseOfAPI("")


class OptionTemplateSet(admin.ModelAdmin):
    list_display = ('id', 'title')
    list_display_links = ('id', 'title')
    ordering = ('id',)
    search_fields = ('id', 'title')
    fieldsets = (
        (_('Basics'), {
            'fields': ('title',
                       'invoice_template',
                       'quote_template',
                       'delivery_note_template',
                       'payment_reminder_template',
                       'purchase_confirmation_template',
                       'purchase_order_template',
                       'profit_loss_statement_template',
                       'balance_sheet_statement_template',
                       'monthly_project_summary_template',
                       'work_report_template')
        }),
    )
<file_sep>/documentation/source/installation.rst
.. highlight:: rst

koalixcrm Installation
======================
on Windows 10 Version 1703
---------------------------
Install Python 3.6.2
^^^^^^^^^^^^^^^^^^^^
    Download from https://www.python.org/downloads/
    Install python with the defaults

Install FOP 2.2
^^^^^^^^^^^^^^^
    Download and install java from oracle website
    Download fop2.2 from apache fop website and install

Install koalixcrm app
^^^^^^^^^^^^^^^^^^^^^
    Run command
    C:\Users\YourUser\AppData\Local\Programs\Python\Python36-32\Scripts\pip.exe install koalix-crm

Setup django project
^^^^^^^^^^^^^^^^^^^^
    Run command
    C:\Users\YourUser\AppData\Local\Programs\Python\Python36-32\Scripts\django-admin.exe startproject test_koalixcrm
    mkdir test_koalixcrm\media
    mkdir test_koalixcrm\media\uploads

on ubuntu 17.04
---------------
Install required programs on your ubuntu
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    sudo bash
    apt-get install fop virtualenv python3.5
    exit

Create a virtual python environment for the koalixcrm project
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    mkdir ~/test_koalixcrm_env
    virtualenv --no-site-package --python=/usr/bin/python3.5 ~/test_koalixcrm_env
    source /test_koalixcrm_env/bin/activate
    pip install koalix-crm

Create a generic django project
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    cd ~
    django-admin startproject test_koalixcrm

Common on all Operating Systems
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Import koalixcrm to your project
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Open the file  called settings.py

    Search in the file the variable definition "INSTALLED_APPS"
    Add following lines to the at the end of INSTALLED_APPS:
    'koalixcrm.crm',
    'koalixcrm.accounting',
    'koalixcrm.djangoUserExtension',
    'koalixcrm.subscriptions',
    'filebrowser'

    Create a new variable defintion "KOALIXCRM_PLUGIN"
    KOALIXCRM_PLUGINS = (
        'koalixcrm.subscriptions',
    )

    At the very end of the seetings.py file add the following lines:
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

    MEDIA_URL = "/media/"
    MEDIA_ROOT = os.path.join(BASE_DIR, '')

    PROJECT_ROOT = BASE_DIR

    # Settings specific for koalixcrm
    PDF_OUTPUT_ROOT = os.path.join(STATIC_ROOT, 'pdf/')
    FOP_

    # Settings specific for filebrowser
    FILEBROWSER_DIRECTORY = 'uploads/'

Enable the customized additional view for filebrowser
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Open the file  called urls.py
Completely rewrite the file with following content

    from django.conf.urls.static import *
    from django.contrib.staticfiles.urls import static
    from django.contrib import admin
    from filebrowser.sites import FileBrowserSite
    from django.core.files.storage import DefaultStorage

    site = FileBrowserSite(name="filebrowser", storage=DefaultStorage())
    customsite = FileBrowserSite(name='custom_filebrowser', storage=DefaultStorage())
    customsite.directory = "uploads/"


    admin.autodiscover()

    urlpatterns = [
        url(r'^admin/filebrowser/', customsite.urls),
        url(r'^admin/', admin.site.urls),
    ]
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Afterwards start the django application
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    cd ~/test_koalixcrm
    python manage.py makemigrations
    python manage.py migrate
    python manage.py createsuperuser
    python manage.py runserver 127.0.0.1:8000

Log in to the admin website
^^^^^^^^^^^^^^^^^^^^^^^^^^^

What you want to do next is of cause the test the software. Visit your http://127.0.0.1:8000/admin, log in and start testing.

<file_sep>/koalixcrm/crm/migrations/0009_auto_20180104_2111.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-04 21:11
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0008_auto_20180104_2042'),
    ]

    operations = [
        migrations.RenameField(
            model_name='contract',
            old_name='dateofcreation',
            new_name='date_of_creation',
        ),
        migrations.RenameField(
            model_name='contract',
            old_name='defaultcurrency',
            new_name='default_currency',
        ),
        migrations.RenameField(
            model_name='contract',
            old_name='defaultcustomer',
            new_name='default_customer',
        ),
        migrations.RenameField(
            model_name='contract',
            old_name='defaultSupplier',
            new_name='default_supplier',
        ),
        migrations.RenameField(
            model_name='contract',
            old_name='lastmodification',
            new_name='last_modification',
        ),
        migrations.RenameField(
            model_name='contract',
            old_name='lastmodifiedby',
            new_name='last_modified_by',
        ),
        migrations.RenameField(
            model_name='invoice',
            old_name='derivatedFromQuote',
            new_name='derived_from_quote',
        ),
        migrations.RenameField(
            model_name='invoice',
            old_name='payableuntil',
            new_name='payable_until',
        ),
        migrations.RenameField(
            model_name='invoice',
            old_name='paymentBankReference',
            new_name='payment_bank_reference',
        ),
        migrations.RenameField(
            model_name='position',
            old_name='lastCalculatedPrice',
            new_name='last_calculated_price',
        ),
        migrations.RenameField(
            model_name='position',
            old_name='lastCalculatedTax',
            new_name='last_calculated_tax',
        ),
        migrations.RenameField(
            model_name='position',
            old_name='lastPricingDate',
            new_name='last_pricing_date',
        ),
        migrations.RenameField(
            model_name='position',
            old_name='overwriteProductPrice',
            new_name='overwrite_product_price',
        ),
        migrations.RenameField(
            model_name='position',
            old_name='positionNumber',
            new_name='position_number',
        ),
        migrations.RenameField(
            model_name='position',
            old_name='sentOn',
            new_name='sent_on',
        ),
        migrations.RenameField(
            model_name='product',
            old_name='accoutingProductCategorie',
            new_name='accounting_product_categorie',
        ),
        migrations.RenameField(
            model_name='product',
            old_name='dateofcreation',
            new_name='date_of_creation',
        ),
        migrations.RenameField(
            model_name='product',
            old_name='defaultunit',
            new_name='default_unit',
        ),
        migrations.RenameField(
            model_name='product',
            old_name='lastmodification',
            new_name='last_modification',
        ),
        migrations.RenameField(
            model_name='product',
            old_name='lastmodifiedby',
            new_name='last_modified_by',
        ),
        migrations.RenameField(
            model_name='product',
            old_name='productNumber',
            new_name='product_number',
        ),
        migrations.RenameField(
            model_name='purchaseconfirmation',
            old_name='validuntil',
            new_name='valid_until',
        ),
        migrations.RenameField(
            model_name='purchaseorder',
            old_name='dateofcreation',
            new_name='date_of_creation',
        ),
        migrations.RenameField(
            model_name='purchaseorder',
            old_name='externalReference',
            new_name='external_reference',
        ),
        migrations.RenameField(
            model_name='purchaseorder',
            old_name='lastCalculatedPrice',
            new_name='last_calculated_price',
        ),
        migrations.RenameField(
            model_name='purchaseorder',
            old_name='lastCalculatedTax',
            new_name='last_calculated_tax',
        ),
        migrations.RenameField(
            model_name='purchaseorder',
            old_name='lastmodification',
            new_name='last_modification',
        ),
        migrations.RenameField(
            model_name='purchaseorder',
            old_name='lastmodifiedby',
            new_name='last_modified_by',
        ),
        migrations.RenameField(
            model_name='purchaseorder',
            old_name='lastPricingDate',
            new_name='last_pricing_date',
        ),
        migrations.RenameField(
            model_name='quote',
            old_name='validuntil',
            new_name='valid_until',
        ),
        migrations.RenameField(
            model_name='salescontract',
            old_name='dateofcreation',
            new_name='date_of_creation',
        ),
        migrations.RenameField(
            model_name='salescontract',
            old_name='externalReference',
            new_name='external_reference',
        ),
        migrations.RenameField(
            model_name='salescontract',
            old_name='lastCalculatedPrice',
            new_name='last_calculated_price',
        ),
        migrations.RenameField(
            model_name='salescontract',
            old_name='lastCalculatedTax',
            new_name='last_calculated_tax',
        ),
        migrations.RenameField(
            model_name='salescontract',
            old_name='lastmodification',
            new_name='last_modification',
        ),
        migrations.RenameField(
            model_name='salescontract',
            old_name='lastmodifiedby',
            new_name='last_modified_by',
        ),
        migrations.RenameField(
            model_name='salescontract',
            old_name='lastPricingDate',
            new_name='last_pricing_date',
        ),
    ]
<file_sep>/koalixcrm/crm/factories/factory_contract.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Contract
from koalixcrm.crm.factories.factory_user import StaffUserFactory
from koalixcrm.crm.factories.factory_customer import StandardCustomerFactory
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory
from koalixcrm.djangoUserExtension.factories.factory_template_set import StandardTemplateSetFactory


class StandardContractFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Contract

    staff = factory.SubFactory(StaffUserFactory)
    description = "This is the description to a test contract"
    default_customer = factory.SubFactory(StandardCustomerFactory)
    default_currency = factory.SubFactory(StandardCurrencyFactory)
    default_template_set = factory.SubFactory(StandardTemplateSetFactory)
    date_of_creation = "2018-05-01"
    last_modification = "2018-05-03"
    last_modified_by = factory.SubFactory(StaffUserFactory)
<file_sep>/koalixcrm/crm/inlinemixin.py
class LimitedAdminInlineMixin(object):
    """
    InlineAdmin mixin limiting the selection of related items according to
    criteria which can depend on the current parent object being edited.

    A typical use case would be selecting a subset of related items from
    other inlines, ie. images, to have some relation to other inlines.

    Use as follows::

        class MyInline(LimitedAdminInlineMixin, admin.TabularInline):
            def get_filters(self, request, obj):
                return (('<field_name>', dict(<filters>)),)

    Here, <field_name> is the name of the FK field to be filtered and <filters>
    is a dict of parameters as you would normally specify them in the filter()
    method of QuerySets.

    Modified from:
    https://stackoverflow.com/a/5008984
    https://gist.github.com/dokterbob/828117
    """

    @staticmethod
    def limit_inline_choices(formset, field, empty=False, **filters):
        """
        This function fetches the queryset with available choices for a given
        `field` and filters it based on the criteria specified in filters,
        unless `empty=True`. In this case, no choices will be made available.
        """
        assert field in formset.form.base_fields
        qs = formset.form.base_fields[field].queryset
        if empty:
            formset.form.base_fields[field].queryset = qs.none()
        else:
            qs = qs.filter(**filters)
            formset.form.base_fields[field].queryset = qs

    def get_formset(self, request, obj=None, **kwargs):
        """
        Make sure we can only select variations that relate to the current
        item.
        """
        formset = super(LimitedAdminInlineMixin, self).get_formset(request,
                                                                   obj,
                                                                   **kwargs)
        inline_filters = self.get_filters(request, obj)
        if inline_filters:
            for (field, filters,) in inline_filters:
                self.limit_inline_choices(formset, field, **filters)
        return formset<file_sep>/koalixcrm/accounting/accounting/product_category.py
# -*- coding: utf-8 -*-
from django.db import models
from django.contrib import admin
from django.utils.translation import ugettext as _
from koalixcrm.accounting.models import Account


class ProductCategory(models.Model):
    title = models.CharField(verbose_name=_("Product Category Title"),
                             max_length=50)
    profit_account = models.ForeignKey(Account,
                                       verbose_name=_("Profit Account"),
                                       limit_choices_to={"account_type": "E"},
                                       related_name="db_profit_account")
    loss_account = models.ForeignKey(Account,
                                     verbose_name=_("Loss Account"),
                                     limit_choices_to={"account_type": "S"},
                                     related_name="db_loss_account")

    class Meta:
        app_label = "accounting"
        verbose_name = _('Product Category')
        verbose_name_plural = _('Product Categories')

    def __str__(self):
        return self.title


class OptionProductCategory(admin.ModelAdmin):
    list_display = ('title',
                    'profit_account',
                    'loss_account')
    list_display_links = ('title',
                          'profit_account',
                          'loss_account')
    fieldsets = (
        (_('Basics'), {
            'fields': ('title',
                       'profit_account',
                       'loss_account')
        }),
    )
    save_as = True
<file_sep>/koalixcrm/crm/factories/factory_project.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import Project
from koalixcrm.crm.factories.factory_project_status import StartedProjectStatusFactory
from koalixcrm.djangoUserExtension.factories.factory_template_set import StandardTemplateSetFactory
from koalixcrm.crm.factories.factory_user import StaffUserFactory
from koalixcrm.crm.factories.factory_currency import StandardCurrencyFactory


class StandardProjectFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Project
        django_get_or_create = ('project_name',)

    project_manager = factory.SubFactory(StaffUserFactory)
    project_name = "This is a Test Project"
    description = "This is description of a test Project"
    project_status = factory.SubFactory(StartedProjectStatusFactory)
    default_template_set = factory.SubFactory(StandardTemplateSetFactory)
    default_currency = factory.SubFactory(StandardCurrencyFactory)
    date_of_creation = "2018-05-01"
    last_modification = "2018-05-02"
    last_modified_by = factory.SubFactory(StaffUserFactory)
<file_sep>/koalixcrm/crm/views/create_work_report.py
# -*- coding: utf-8 -*-
import datetime
from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render
from django.contrib.admin.widgets import *
from django.contrib.admin import helpers
from django.template.context_processors import csrf
from koalixcrm.djangoUserExtension.exceptions import TooManyUserExtensionsAvailable
from koalixcrm.crm.views.pdfexport import PDFExportView


class CreateWorkReportForm(forms.Form):
    range_from = forms.DateField(widget=AdminDateWidget)
    range_to = forms.DateField(widget=AdminDateWidget)


def create_work_report(calling_model_admin, request, queryset):
    try:
        if request.POST.get('post'):
            if 'create_pdf' in request.POST:
                create_work_report_form = CreateWorkReportForm(request.POST)
                if create_work_report_form.is_valid():
                    for obj in queryset:
                        return PDFExportView.export_pdf(
                            calling_model_admin=calling_model_admin,
                            request=request,
                            source=obj,
                            redirect_to="/admin/",
                            template_to_use=obj.user.default_template_set.work_report_template,
                            date_from=create_work_report_form.cleaned_data["range_from"],
                            date_to=create_work_report_form.cleaned_data["range_to"])
        else:
            datetime_now = datetime.datetime.today()
            range_from_date = (datetime_now - datetime.timedelta(days=30)).date()
            range_to_date = datetime_now.date()
            create_work_report_form = CreateWorkReportForm(initial={'range_from': range_from_date,
                                                                    'range_to': range_to_date})
            c = {'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
                 'queryset': queryset,
                 'form': create_work_report_form}
            c.update(csrf(request))
            return render(request, 'crm/admin/create_work_report.html', c)
    except TooManyUserExtensionsAvailable:
        raise Http404


<file_sep>/documentation/source/code_documentation/work.rst
.. highlight:: rst

Work
----

.. automodule:: koalixcrm.crm.reporting.work
   :members:

<file_sep>/documentation/source/index.rst
Welcome to koalixcrm's documentation!
=====================================

Contents:

.. toctree::
   :maxdepth: 2

   installation
   intro
   tutorial
   accounting
   crm
   djangoUserExtention
   customisation
   architecture
   faq
   code_documentation

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

<file_sep>/koalixcrm/crm/migrations/0004_auto_20171009_2008.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-09 20:08
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0003_auto_20171009_1949'),
    ]

    operations = [
        migrations.AlterField(
            model_name='contact',
            name='dateofcreation',
            field=models.DateTimeField(auto_now_add=True, verbose_name='Erstelldatum'),
        ),
        migrations.AlterField(
            model_name='contact',
            name='lastmodification',
            field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert'),
        ),
        migrations.AlterField(
            model_name='contract',
            name='dateofcreation',
            field=models.DateTimeField(auto_now_add=True, verbose_name='Erstelldatum'),
        ),
        migrations.AlterField(
            model_name='contract',
            name='lastmodification',
            field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert'),
        ),
        migrations.AlterField(
            model_name='product',
            name='dateofcreation',
            field=models.DateTimeField(auto_now_add=True, verbose_name='Erstelldatum'),
        ),
        migrations.AlterField(
            model_name='product',
            name='lastmodification',
            field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert'),
        ),
        migrations.AlterField(
            model_name='purchaseorder',
            name='dateofcreation',
            field=models.DateTimeField(auto_now_add=True, verbose_name='Erstelldatum'),
        ),
        migrations.AlterField(
            model_name='purchaseorder',
            name='last_print_date',
            field=models.DateTimeField(blank=True, null=True, verbose_name='Zuletzt gedruckt'),
        ),
        migrations.AlterField(
            model_name='purchaseorder',
            name='lastmodification',
            field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert'),
        ),
        migrations.AlterField(
            model_name='salescontract',
            name='dateofcreation',
            field=models.DateTimeField(auto_now_add=True, verbose_name='Erstelldatum'),
        ),
        migrations.AlterField(
            model_name='salescontract',
            name='last_print_date',
            field=models.DateTimeField(blank=True, null=True, verbose_name='Zuletzt gedruckt'),
        ),
        migrations.AlterField(
            model_name='salescontract',
            name='lastmodification',
            field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert'),
        ),
    ]
<file_sep>/requirements/test_requirements.txt
pytest==3.7.0
pytest-cov==2.5.1
pytest-django==3.1.2
codacy-coverage==1.3.6
selenium==3.14.0
factory_boy==2.11.1<file_sep>/koalixcrm/crm/reporting/generic_project_link.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline


class GenericProjectLink(models.Model):
    project = models.ForeignKey("Project",
                                verbose_name=_('Project'),
                                blank=False,
                                null=False)
    project_link_type = models.ForeignKey("ProjectLinkType",
                                          verbose_name=_('Project Link Type'),
                                          blank=True,
                                          null=True)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    generic_crm_object = GenericForeignKey('content_type',
                                           'object_id')
    date_of_creation = models.DateTimeField(verbose_name=_("Created at"),
                                            auto_now_add=True)
    last_modified_by = models.ForeignKey('auth.User',
                                         limit_choices_to={'is_staff': True},
                                         verbose_name=_("Last modified by"),
                                         related_name="db_project_link_last_modified")

    def __str__(self):
        return _("Link to") + " " + str(self.project)

    class Meta:
        app_label = "crm"
        verbose_name = _('Project Link')
        verbose_name_plural = _('Project Links')


class GenericLinkInlineAdminView(admin.TabularInline):
    model = GenericProjectLink
    readonly_fields = ('project_link_type',
                       'content_type',
                       'object_id',
                       'date_of_creation',
                       'last_modified_by')
    extra = 0

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False


class InlineGenericProjectLink(GenericTabularInline):
    model = GenericProjectLink
    readonly_fields = ('project_link_type',
                       'content_type',
                       'object_id',
                       'date_of_creation',
                       'last_modified_by')
    extra = 0

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False
<file_sep>/koalixcrm/test_support_functions.py
# -*- coding: utf-8 -*-

import pytz
from selenium.common.exceptions import NoSuchElementException


def assert_when_element_does_not_exist(testcase, xpath):
    try:
        testcase.selenium.find_element_by_xpath(xpath)
    except NoSuchElementException:
        testcase.assertTrue(True, xpath+" does not exist")


def assert_when_element_exists(testcase, xpath):
    try:
        testcase.selenium.find_element_by_xpath(xpath)
    except NoSuchElementException:
        testcase.assertTrue(True, xpath+" does exist")


def assert_when_element_is_not_equal_to(testcase, xpath, string):
    try:
        element = testcase.selenium.find_element_by_xpath(xpath)
        if element.text != string:
            print("issue")
    except NoSuchElementException:
        return


def make_date_utc(input_date):
    output_date = pytz.timezone("UTC").localize(input_date, is_dst=None)
    return output_date
<file_sep>/koalixcrm/crm/factories/factory_task_status.py
# -*- coding: utf-8 -*-

import factory
from koalixcrm.crm.models import TaskStatus


class DoneTaskStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = TaskStatus
        django_get_or_create = ('title',)

    title = "Done"
    description = "This task is done"
    is_done = True


class PlannedTaskStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = TaskStatus
        django_get_or_create = ('title',)

    title = "Planned"
    description = "This task is planned"
    is_done = False


class StartedTaskStatusFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = TaskStatus
        django_get_or_create = ('title',)

    title = "Started"
    description = "This task is started"
    is_done = False
<file_sep>/koalixcrm/crm/documents/pdf_export.py
# -*- coding: utf-8 -*-

import os
from subprocess import check_output
from subprocess import STDOUT
from django.conf import settings
from django.core import serializers
from lxml import etree
import koalixcrm.crm.documents.sales_document
import koalixcrm.djangoUserExtension.models


class PDFExport:

    @staticmethod
    def find_element_in_xml(xml_string, find_pattern, find_value):
        parser = etree.XMLParser(encoding='utf-8', remove_blank_text=True)
        root_element = etree.fromstring(xml_string.encode('utf-8'), parser=parser)
        found_element = root_element.findall(find_pattern)
        if found_element is None:
            return 0
        else:
            for element in found_element:
                if element.text == find_value:
                    return 1
            return 0

    @staticmethod
    def append_element_to_pattern(xml_string, find_pattern, name_of_element, value_of_element, **kwargs):
        attributes = kwargs.get('attributes', None)
        parser = etree.XMLParser(encoding='utf-8', remove_blank_text=True)
        root_element = etree.fromstring(xml_string.encode('utf-8'), parser=parser)
        found_element = root_element.find(find_pattern)
        new_element = etree.SubElement(found_element, name_of_element, attrib=attributes)
        new_element.text = value_of_element.__str__()
        return (etree.tostring(root_element,
                               encoding='UTF-8',
                               xml_declaration=True,
                               pretty_print=True)).decode('utf-8')

    @staticmethod
    def merge_xml(xml_string_1, xml_string_2):
        parser = etree.XMLParser(encoding='utf-8', remove_blank_text=True)
        root_element_1 = etree.fromstring(xml_string_1.encode('utf-8'), parser=parser)
        root_element_2 = etree.fromstring(xml_string_2.encode('utf-8'), parser=parser)
        for child in root_element_2:
            root_element_1.append(child)
        return (etree.tostring(root_element_1,
                               encoding='UTF-8',
                               xml_declaration=True,
                               pretty_print=True)).decode('utf-8')

    @staticmethod
    def write_xml(objects_to_serialize):
        xml = serializers.serialize("xml", objects_to_serialize, indent=3)
        return xml

    @staticmethod
    def write_xml_file(xml, file_path):
        f = open(file_path, "wb+")
        f.truncate()
        f.write(xml.encode('utf-8'))
        f.close()

    @staticmethod
    def perform_xsl_transformation(file_with_serialized_xml, xsl_file, fop_config_file, file_output_pdf):
        check_output([settings.FOP_EXECUTABLE,
                      '-c', fop_config_file.path_full,
                      '-xml', os.path.join(settings.PDF_OUTPUT_ROOT, file_with_serialized_xml),
                      '-xsl', xsl_file.path_full,
                      '-pdf', file_output_pdf], stderr=STDOUT)

    @staticmethod
    def create_pdf(object_to_create_pdf, template_set, printed_by, *args, **kwargs):
        # define the files which are involved in pdf creation process
        fop_config_file = object_to_create_pdf.get_fop_config_file(template_set)
        xsl_file = object_to_create_pdf.get_xsl_file(template_set)
        file_with_serialized_xml = os.path.join(settings.PDF_OUTPUT_ROOT, (str(type(object_to_create_pdf).__name__) +
                                                                           "_" + str(object_to_create_pdf.id) + ".xml"))
        file_output_pdf = os.path.join(settings.PDF_OUTPUT_ROOT, (str(type(object_to_create_pdf).__name__) +
                                                                  "_" + str(object_to_create_pdf.id) + ".pdf"))

        # list the sub-objects which have to be serialized
        xml_string = object_to_create_pdf.serialize_to_xml(*args, **kwargs)
        objects_to_serialize = list(koalixcrm.djangoUserExtension.models.DocumentTemplate.objects.filter(id=template_set.id))
        xml_string_temp = PDFExport.write_xml(objects_to_serialize)
        xml_string = PDFExport.merge_xml(xml_string, xml_string_temp)
        objects_to_serialize = koalixcrm.djangoUserExtension.models.UserExtension.objects_to_serialize(object_to_create_pdf, printed_by)
        xml_string_temp = PDFExport.write_xml(objects_to_serialize)
        xml_string = PDFExport.merge_xml(xml_string, xml_string_temp)

        # extend the xml-string with required basic settings
        xml_string = PDFExport.append_element_to_pattern(xml_string,
                                                         ".",
                                                         "filebrowser_directory",
                                                         settings.MEDIA_ROOT)

        #  write xml-string to xml-file
        PDFExport.write_xml_file(xml_string, file_with_serialized_xml)

        # perform xsl transformation
        PDFExport.perform_xsl_transformation(file_with_serialized_xml, xsl_file, fop_config_file, file_output_pdf)

        return file_output_pdf
<file_sep>/koalixcrm/crm/contact/email_address.py
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext as _


class EmailAddress(models.Model):
    email = models.EmailField(max_length=200,
                              verbose_name=_("Email Address"))

    class Meta:
        app_label = "crm"
        verbose_name = _('Email Address')
        verbose_name_plural = _('Email Address')
<file_sep>/koalixcrm/crm/migrations/0012_auto_20180105_1840.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-01-05 18:40
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('crm', '0011_auto_20180104_2152'),
    ]

    operations = [
        migrations.AlterField(
            model_name='salescontract',
            name='template_set',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='djangoUserExtension.DocumentTemplate', verbose_name='Referred Template'),
        ),
    ]
<file_sep>/koalixcrm/crm/views/work_entry_formset.py
# -*- coding: utf-8 -*-

from django import forms
from koalixcrm.crm.views.work_entry_form import WorkEntry


class BaseWorkEntryFormset(forms.BaseFormSet):
    def __init__(self, *args, **kwargs):
        super(BaseWorkEntryFormset, self).__init__(*args, **kwargs)

    @staticmethod
    def generate_initial_data(start_date, stop_date, human_resource):
        from koalixcrm.crm.reporting.work import Work
        list_of_work = Work.objects.filter(human_resource=human_resource).filter(date__lte=stop_date).filter(date__gte=start_date).order_by("date")
        initial = []
        for work in list_of_work:
            initial.append({'work_id': work.id,
                            'task': work.task,
                            'project': work.task.project,
                            'date': work.date,
                            'start_time': work.start_time,
                            'stop_time': work.stop_time,
                            'worked_hours': work.worked_hours,
                            'description': work.description})
        return initial

    @staticmethod
    def load_formset(range_selection_form, request):
        WorkEntryFormSet = forms.formset_factory(WorkEntry,
                                                 extra=1,
                                                 max_num=60,
                                                 can_delete=True,
                                                 formset=BaseWorkEntryFormset)
        from_date = range_selection_form.evaluate_pre_check_from_date()
        to_date = range_selection_form.evaluate_pre_check_to_date()
        form_kwargs = BaseWorkEntryFormset.compose_form_kwargs(from_date, to_date)
        pre_check_formset = WorkEntryFormSet(request.POST,
                                             form_kwargs=form_kwargs)
        return pre_check_formset

    @staticmethod
    def compose_form_kwargs(from_date, to_date):
        form_kwargs = {'from_date': from_date, 'to_date': to_date}
        return form_kwargs

    @staticmethod
    def create_updated_formset(range_selection_form, human_resource):
        WorkEntryFormSet = forms.formset_factory(WorkEntry,
                                                 extra=1,
                                                 max_num=60,
                                                 can_delete=True,
                                                 formset=BaseWorkEntryFormset)
        from_date = range_selection_form.cleaned_data['from_date']
        to_date = range_selection_form.cleaned_data['to_date']
        initial_formset_data = BaseWorkEntryFormset.generate_initial_data(from_date,
                                                                          to_date,
                                                                          human_resource)
        form_kwargs = BaseWorkEntryFormset.compose_form_kwargs(from_date, to_date)
        formset = WorkEntryFormSet(initial=initial_formset_data,
                                   form_kwargs=form_kwargs)
        return formset

    @staticmethod
    def create_new_formset(from_date, to_date, human_resource):
        WorkEntryFormSet = forms.formset_factory(WorkEntry,
                                                 extra=1,
                                                 max_num=60,
                                                 can_delete=True,
                                                 formset=BaseWorkEntryFormset)
        initial_formset_data = BaseWorkEntryFormset.generate_initial_data(from_date,
                                                                          to_date,
                                                                          human_resource)
        form_kwargs = BaseWorkEntryFormset.compose_form_kwargs(from_date,
                                                               to_date)
        formset = WorkEntryFormSet(initial=initial_formset_data,
                                   form_kwargs=form_kwargs)
        return formset
