diff --git a/.gitignore b/.gitignore index 1f649cb0..1d8a3154 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,9 @@ pip-log.txt .tox *~ -# VS Code +# IntelliJ +*.iml +/.idea/ + +#VSCODE .vscode -projectsettings/media/uploads -projectsettings/media/data_files -projectsettings/media/tmp diff --git a/.travis.yml b/.travis.yml index e470d6f6..dc3c2674 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,12 @@ language: python python: - '3.4' +before_install: + - wget https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz + - mkdir geckodriver + - tar -xzf geckodriver*.tar.gz -C geckodriver + - export PATH=$PATH:$PWD/geckodriver + install: - python setup.py install - pip install -r requirements/heroku_requirements.txt diff --git a/Dockerfile b/Dockerfile index 832bfbea..0d57b1d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,13 @@ FROM python:3.6 -RUN mkdir /app WORKDIR /app # Intall dependencies COPY /requirements/development_requirements.txt /app/ COPY /requirements/base_requirements.txt /app/ -RUN pip install -r development_requirements.txt COPY . /app RUN chmod +x /app/entrypoint.sh ENTRYPOINT ["/app/entrypoint.sh"] + diff --git a/Dockerfile.prod b/Dockerfile.prod index ffec0dd3..8926d967 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,17 +1,12 @@ FROM python:3.6 -RUN mkdir /app WORKDIR /app # Intall dependencies -COPY /requirements/development_requirements.txt /app/ COPY /requirements/base_requirements.txt /app/ -RUN pip install -r development_requirements.txt COPY . /app -# Add uWSGI config -ADD ./projectsettings/services/django-uwsgi.ini /etc/uwsgi/django-uwsgi.ini +RUN chmod +x /app/entrypoint.prod.sh +ENTRYPOINT ["/app/entrypoint.prod.sh"] -RUN chmod +x /app/entrypoint.sh -ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/LICENSE b/LICENSE index 114ca759..f5aebbdd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,29 @@ -This software is published under the BSD license as listed below. +BSD 3-Clause License -Copyright © 2009-2018 Aaron Riedener, Untereggen, Switzerland +Copyright (c) 2009-2018, Aaron Riedener, Untereggen, Switzerland All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Aaron Riedener nor the names of the contributors to koalixcrm may be used to endorse or promote products derived from this software without specific prior written permission. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e0580bdc..8e3a7161 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,39 +1,14 @@ -version: '3' +version: '2.0' -services: +services: db: image: postgres - volumes: - - pgdata:/var/lib/postgresql/data - koalixcrm: - build: . - volumes: - - .:/app/ - - static-files:/app/projectsettings/static - - media-files:/app/projectsettings/media - expose: - - "8000" - depends_on: - - db - env_file: - - ./projectsettings/environment/development.env - webserver: - build: - context: . - dockerfile: ./projectsettings/services/webserver/Dockerfile - volumes: - - static-files:/app/projectsettings/static - - media-files:/app/projectsettings/media + koalixcrm: + image: koalixswitzerland/koalixcrm:1.12.dev20180210 + environment: + - DJANGO_SETTINGS_MODULE=projectsettings.settings.production_settings + - JAVA_HOME=/usr/bin/jdk1.8.0_162/jre ports: - - "80:80" - - "443:443" + - "8000:8000" depends_on: - - koalixcrm - env_file: - - ./projectsettings/environment/development.env - -volumes: - .: - static-files: - media-files: - pgdata: \ No newline at end of file + - db \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index f63f5a49..a154b3bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,14 @@ -version: '3' +version: '2.0' -services: +services: db: image: postgres - volumes: - - pgdata:/var/lib/postgresql/data - koalixcrm: - build: . - volumes: - - .:/app/ + koalixcrm: + build: . + environment: + - DJANGO_SETTINGS_MODULE=projectsettings.settings.development_settings + - JAVA_HOME=/usr/bin/jdk1.8.0_162/jre ports: - "8000:8000" depends_on: - - db - env_file: - - ./projectsettings/environment/development.env - -volumes: - .: - pgdata: \ No newline at end of file + - db \ No newline at end of file diff --git a/documentation/source/architecture.rst b/documentation/source/architecture.rst new file mode 100644 index 00000000..d3276a1d --- /dev/null +++ b/documentation/source/architecture.rst @@ -0,0 +1,14 @@ +.. 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 + + diff --git a/documentation/source/crm.rst b/documentation/source/crm.rst index 8c786184..8879525a 100644 --- a/documentation/source/crm.rst +++ b/documentation/source/crm.rst @@ -6,12 +6,21 @@ CRM Introduction ------------ -Objecttypes ------------ +Products +-------- -Contract +Contact +-------- + +Customer ^^^^^^^^ +Documents +--------- + +Sales Contract +^^^^^^^^^^^^^^ + Purchase Order ^^^^^^^^^^^^^^ @@ -21,12 +30,8 @@ Invoice Quote ^^^^^ -Customer -^^^^^^^^ +Reporting +--------- -Distributors -^^^^^^^^^^^^ -Suppliers -^^^^^^^^^ diff --git a/documentation/source/customisation.rst b/documentation/source/customisation.rst index 093e120f..3ed45271 100644 --- a/documentation/source/customisation.rst +++ b/documentation/source/customisation.rst @@ -42,7 +42,7 @@ After you finised translating the whole file test your translation by compiling django-admin compilemessages -After this you are able to use your translation. Sometimes seting your language in ``/var/www/koalixcrm/settings.py`` is required. +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. diff --git a/documentation/source/images/koalixcrm_architecture_dedicated_webserver.png b/documentation/source/images/koalixcrm_architecture_dedicated_webserver.png new file mode 100644 index 00000000..7fb0c558 Binary files /dev/null and b/documentation/source/images/koalixcrm_architecture_dedicated_webserver.png differ diff --git a/documentation/source/images/koalixcrm_architecture_dedicated_webserver.svg b/documentation/source/images/koalixcrm_architecture_dedicated_webserver.svg new file mode 100644 index 00000000..2b0abb1d --- /dev/null +++ b/documentation/source/images/koalixcrm_architecture_dedicated_webserver.svg @@ -0,0 +1,577 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + koalixcrm back-end (django application) + + Database(postgresql database server application) + + + koalixcrm front-end (html and jquery based application) + Standardinstallation with docker with postgresql and apache2 + + apache fop(java application) + + + apache 2 + + + + + + + + + + File based + + TCP/IP + + TCP/IP + + TCP/IP + + koalixcrm-apache2-Dockerinstallation + + Browser ofEnd-user + + postgresql-Dockerinstallation + + libapache2 mod wsgi(apache library) + + diff --git a/documentation/source/images/koalixcrm_architecture_django_webserver.png b/documentation/source/images/koalixcrm_architecture_django_webserver.png new file mode 100644 index 00000000..312a6396 Binary files /dev/null and b/documentation/source/images/koalixcrm_architecture_django_webserver.png differ diff --git a/documentation/source/images/koalixcrm_architecture_django_webserver.svg b/documentation/source/images/koalixcrm_architecture_django_webserver.svg new file mode 100644 index 00000000..021776fd --- /dev/null +++ b/documentation/source/images/koalixcrm_architecture_django_webserver.svg @@ -0,0 +1,579 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + koalixcrm back-end (django server application) + + Database(postgresql database server application) + + + koalixcrm front-end (html and jquery based application) + Standardinstallation with docker with postgresql + + apache fop(java application) + + + koalixcrm back-end (django server static file serve) + + + + + + + + + + File based + + TCP/IP + + TCP/IP + + TCP/IP + + koalixcrm-Dockerinstallation + + Browser ofEnd-user + + postgresql-Dockerinstallation + + diff --git a/documentation/source/images/pdfcreation.png b/documentation/source/images/pdfcreation.png new file mode 100644 index 00000000..8aca0a64 Binary files /dev/null and b/documentation/source/images/pdfcreation.png differ diff --git a/documentation/source/images/pdfcreation.svg b/documentation/source/images/pdfcreation.svg index fb3fca74..bde0c6ce 100644 --- a/documentation/source/images/pdfcreation.svg +++ b/documentation/source/images/pdfcreation.svg @@ -13,8 +13,11 @@ height="297mm" id="svg2" version="1.1" - inkscape:version="0.48.0 r9654" - sodipodi:docname="Neues Dokument 1"> + inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" + sodipodi:docname="pdfcreation.svg" + inkscape:export-filename="/home/aaron/PycharmProjects/koalixcrm/documentation/source/images/pdfcreation.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> @@ -57,7 +60,7 @@ image/svg+xml - + @@ -95,60 +98,93 @@ y="695.50348" /> koalixcrmkoalixcrmdocument-xml-file + style="font-size:28px;line-height:1.25" + id="tspan5118">xml-file<xml></xml> Your indi-Your indi-vidual xsl-template -filevidual xsl-<xml>file + y="282.42639" + style="font-size:28px;line-height:1.25" + id="tspan5126"></xml> fo-xml-file + y="420.94489" + style="font-size:28px;line-height:1.25">fo-xml-file pdf-file + y="747.44696" + style="font-size:28px;line-height:1.25">pdf-file This part isThis part iscompletely donecompletely doneby apache-fop + x="-751.289" + y="212.36693" + id="tspan4665" + style="font-size:28px;line-height:1.25">by apache-fop <xml><xml></xml> + id="tspan4683" + style="font-size:28px;line-height:1.25"></xml> diff --git a/documentation/source/index.rst b/documentation/source/index.rst index 25e91ee4..34059117 100644 --- a/documentation/source/index.rst +++ b/documentation/source/index.rst @@ -4,7 +4,7 @@ Welcome to koalixcrm's documentation! Contents: .. toctree:: - :maxdepth: 3 + :maxdepth: 2 installation intro @@ -13,6 +13,7 @@ Contents: crm djangoUserExtention customisation + architecture faq Indices and tables diff --git a/entrypoint.prod.sh b/entrypoint.prod.sh index 28912987..7ab79801 100644 --- a/entrypoint.prod.sh +++ b/entrypoint.prod.sh @@ -25,9 +25,4 @@ chmod -R 755 projectsettings/static/pdf # Execute startup scripts python manage.py collectstatic --noinput python manage.py migrate - -# Create django user, will own the media dir -adduser --no-create-home --disabled-login --group --system django -chown -R django:django /app/projectsettings/media - -/usr/local/bin/uwsgi --emperor /etc/uwsgi/django-uwsgi.ini \ No newline at end of file +python manage.py runserver 0.0.0.0:8000 \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 6108a2e3..6ef73898 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/bash # Install dependencies -#pip install -r development_requirements.txt +pip install -r development_requirements.txt # Install FOP 2.2 wget http://archive.apache.org/dist/xmlgraphics/fop/binaries/fop-2.2-bin.tar.gz @@ -24,9 +24,5 @@ chmod -R 755 projectsettings/static/pdf # Execute startup scripts python manage.py collectstatic --noinput -#python manage.py makemigrations python manage.py migrate -#echo "from django.contrib.auth.models import User; User.objects.filter(email='admin@example.com').delete(); User.objects.create_superuser('admin', 'admin@example.com', 'sysadmin')" | python manage.py shell -python manage.py runserver 0.0.0.0:8000 - - +python manage.py runserver 0.0.0.0:8000 \ No newline at end of file diff --git a/koalixcrm/__init__.py b/koalixcrm/__init__.py index de98902b..e69de29b 100644 --- a/koalixcrm/__init__.py +++ b/koalixcrm/__init__.py @@ -1,7 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -# This will make sure the app is always imported when -# Django starts so that shared_task will use this app. -from .celery import app as celery_app - -__all__ = ['celery_app'] \ No newline at end of file diff --git a/koalixcrm/accounting/admin.py b/koalixcrm/accounting/admin.py index 7b4b13b9..4dd1740d 100644 --- a/koalixcrm/accounting/admin.py +++ b/koalixcrm/accounting/admin.py @@ -5,30 +5,27 @@ from koalixcrm.accounting.views import * -class AccountingPeriodBooking(admin.TabularInline): - model = Booking - extra = 1 - show_change_link = True - can_delete = True - classes = ['collapse'] - fields = ('fromAccount', 'toAccount', 'description', 'amount', 'bookingDateOnly', 'staff', 'bookingReference',) - readonly_fields = ( - 'fromAccount', 'toAccount', 'description', 'amount', 'bookingDateOnly', 'staff', 'bookingReference',) - allow_add = True - - class OptionBooking(admin.ModelAdmin): - list_display = ('fromAccount', 'toAccount', 'amount', 'bookingDateOnly', 'staff') - fieldsets = ((_('Basic'), {'fields': ( - 'fromAccount', 'toAccount', 'amount', 'bookingDate', 'staff', 'description', 'bookingReference', - 'accountingPeriod')}),) + 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 == True): - obj.lastmodifiedby = request.user + if change: + obj.last_modified_by = request.user else: - obj.lastmodifiedby = request.user + obj.last_modified_by = request.user obj.staff = request.user obj.save() @@ -39,11 +36,18 @@ class InlineBookings(admin.TabularInline): classes = ['collapse'] fieldsets = ( ('Basics', { - 'fields': ('fromAccount', 'toAccount', 'description', 'amount', 'bookingDate', 'staff', 'bookingReference',) + 'fields': ('from_account', + 'to_account', + 'description', + 'amount', + 'booking_date', + 'staff', + 'booking_reference',) }), ) allow_add = False + 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""" @@ -55,23 +59,23 @@ class Meta: def clean(self): super(AccountForm, self).clean() errors = [] - if (self.cleaned_data['isopenreliabilitiesaccount']): - openliabilitiesaccount = Account.objects.filter(isopenreliabilitiesaccount=True) - if (self.cleaned_data['accountType'] != "L"): + 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 liabilites account must be a liabities account')) - elif openliabilitiesaccount: + elif open_reliabilities_account: errors.append(_('There may only be one open liablities account in the system')) - if (self.cleaned_data['isopeninterestaccount']): - openinterestaccounts = Account.objects.filter(isopeninterestaccount=True) - if (self.cleaned_data['accountType'] != "A"): + 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 intrests account must be an asset account')) - elif openinterestaccounts: + elif open_interest_account: errors.append(_('There may only be one open intrests account in the system')) - if (self.cleaned_data['isACustomerPaymentAccount']): - if (self.cleaned_data['accountType'] != "A"): + 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['isProductInventoryActiva']): - if (self.cleaned_data['accountType'] != "A"): + 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) @@ -79,11 +83,23 @@ def clean(self): class OptionAccount(admin.ModelAdmin): - list_display = ('accountNumber', 'accountType', 'title', 'sumOfAllBookings') - list_display_links = ('accountNumber', 'accountType', 'title', 'sumOfAllBookings') - fieldsets = ((_('Basic'), {'fields': ( - 'accountNumber', 'accountType', 'title', 'description', 'isopenreliabilitiesaccount', 'isopeninterestaccount', - 'isProductInventoryActiva', 'isACustomerPaymentAccount')}),) + 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 @@ -111,14 +127,26 @@ def clean(self): class OptionAccountingPeriod(admin.ModelAdmin): - list_display = ('title', 'begin', 'end') - list_display_links = ('title', 'begin', 'end') + 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') + 'fields': ('title', + 'begin', + 'end', + 'template_set_balance_sheet', + 'template_profit_loss_statement') }), ) - inlines = [AccountingPeriodBooking, ] + inlines = [InlineBookings, ] save_as = True form = AccountingPeriodForm @@ -126,26 +154,36 @@ class OptionAccountingPeriod(admin.ModelAdmin): def save_formset(self, request, form, formset, change): instances = formset.save(commit=False) for instance in instances: - if (change == True): - instance.lastmodifiedby = request.user + if change: + instance.last_modified_by = request.user else: - instance.lastmodifiedby = request.user + instance.last_modified_by = request.user instance.staff = request.user instance.save() - def createBalanceSheet(self, request, queryset): + def create_pdf_of_balance_sheet(self, request, queryset): + from koalixcrm.crm.views.pdfexport import PDFExportView for obj in queryset: - response = exportPDF(self, request, obj, "balanceSheet", "/admin/accounting/accountingperiod/") + response = PDFExportView.export_pdf(self, + request, + obj, + ("/admin/accounting/"+obj.__class__.__name__.lower()+"/"), + obj.template_set_balance_sheet) return response - createBalanceSheet.short_description = _("Create PDF of Balance Sheet") + create_pdf_of_balance_sheet.short_description = _("Create PDF of Balance Sheet") - def createProfitLossStatement(self, request, queryset): + def create_pdf_of_profit_loss_statement(self, request, queryset): + from koalixcrm.crm.views.pdfexport import PDFExportView for obj in queryset: - response = exportPDF(self, request, obj, "profitLossStatement", "/admin/accounting/accountingperiod/") + response = PDFExportView.export_pdf(self, + request, + obj, + ("/admin/accounting/"+obj.__class__.__name__.lower()+"/"), + obj.template_profit_loss_statement,) return response - createProfitLossStatement.short_description = _("Create PDF of Profit Loss Statement Sheet") + create_pdf_of_profit_loss_statement.short_description = _("Create PDF of Profit Loss Statement Sheet") def exportAllAccounts(self, request, queryset): for obj in queryset: @@ -154,15 +192,15 @@ def exportAllAccounts(self, request, queryset): exportAllAccounts.short_description = _("Create XML of all Accounts") - actions = ['createBalanceSheet', 'createProfitLossStatement', 'exportAllAccounts', ] + actions = ['create_pdf_of_balance_sheet', 'create_pdf_of_profit_loss_statement', 'exportAllAccounts', ] class OptionProductCategorie(admin.ModelAdmin): - list_display = ('title', 'profitAccount', 'lossAccount') - list_display_links = ('title', 'profitAccount', 'lossAccount') + list_display = ('title', 'profit_account', 'loss_account') + list_display_links = ('title', 'profit_account', 'loss_account') fieldsets = ( (_('Basics'), { - 'fields': ('title', 'profitAccount', 'lossAccount') + 'fields': ('title', 'profit_account', 'loss_account') }), ) save_as = True diff --git a/koalixcrm/accounting/exceptions.py b/koalixcrm/accounting/exceptions.py index 2adba1a5..2a4ae541 100644 --- a/koalixcrm/accounting/exceptions.py +++ b/koalixcrm/accounting/exceptions.py @@ -15,3 +15,20 @@ def __init__(self, value): def __str__(self): return repr(self.value) + + +class AccountingPeriodNotFound(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +class TemplateSetMissingInAccountingPeriod(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + diff --git a/koalixcrm/accounting/migrations/0006_auto_20180422_2048.py b/koalixcrm/accounting/migrations/0006_auto_20180422_2048.py new file mode 100644 index 00000000..a8a95955 --- /dev/null +++ b/koalixcrm/accounting/migrations/0006_auto_20180422_2048.py @@ -0,0 +1,106 @@ +# -*- 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'), + ), + ] diff --git a/koalixcrm/accounting/migrations/0007_auto_20180422_2105.py b/koalixcrm/accounting/migrations/0007_auto_20180422_2105.py new file mode 100644 index 00000000..e0ed9fd2 --- /dev/null +++ b/koalixcrm/accounting/migrations/0007_auto_20180422_2105.py @@ -0,0 +1,31 @@ +# -*- 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'), + ), + ] diff --git a/koalixcrm/accounting/models.py b/koalixcrm/accounting/models.py index fdf7d9af..825c4c1f 100644 --- a/koalixcrm/accounting/models.py +++ b/koalixcrm/accounting/models.py @@ -1,52 +1,134 @@ # -*- coding: utf-8 -*- - -import os from datetime import * -from subprocess import check_output -from subprocess import STDOUT -from xml.dom.minidom import Document - -from django.conf import settings -from django.core import serializers from django.db import models from django.utils.translation import ugettext as _ -from koalixcrm import djangoUserExtension -from koalixcrm.crm.exceptions import UserExtensionMissing from koalixcrm.accounting.const.accountTypeChoices import * -from koalixcrm.accounting.exceptions import NoObjectsToBeSerialzed -from koalixcrm.accounting.exceptions import ProgrammingError +from koalixcrm.accounting.exceptions import AccountingPeriodNotFound +from koalixcrm.accounting.exceptions import TemplateSetMissingInAccountingPeriod +from koalixcrm.crm.documents.pdfexport import PDFExport class AccountingPeriod(models.Model): - """Accounting period repesents the equivalent of the business logic element of a fiscal year - the accounting period is refered in the booking and is used as a supporting object to generate + """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/[@model='accounting.accountingperiod']", + "Overall_Earnings", + self.overall_earnings()) + main_xml = PDFExport.append_element_to_pattern(main_xml, + "object/[@model='accounting.accountingperiod']", + "Overall_Spendings", + self.overall_spendings()) + main_xml = PDFExport.append_element_to_pattern(main_xml, + "object/[@model='accounting.accountingperiod']", + "Overall_Assets", + self.overall_assets()) + main_xml = PDFExport.append_element_to_pattern(main_xml, + "object/[@model='accounting.accountingperiod']", + "Overall_Liabilities", + self.overall_liabilities()) + return main_xml @staticmethod - def getCurrentValidAccountingPeriod(): - """Returns the accounting period that is currently valid. Valid is an accountingPeriod when the current date - lies between begin and end of the accountingPeriod + 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: - accoutingPeriod (AccoutingPeriod) + accounting_period (AccoutingPeriod) Raises: - NoFeasableAccountingPeriodFound when there is no valid accounting Period""" - currentValidAccountingPeriod = None - for accountingPeriod in AccountingPeriod.objects.all(): - if accountingPeriod.begin < date.today() and accountingPeriod.end > date.today(): - return accountingPeriod - if currentValidAccountingPeriod == None: - raise NoFeasableAccountingPeriodFound() + 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() @staticmethod - def getAllPriorAccountingPeriods(targetAccountingPeriod): + def get_all_prior_accounting_periods(target_accounting_period): """Returns the accounting period that is currently valid. Valid is an accountingPeriod when the current date lies between begin and end of the accountingPeriod @@ -54,131 +136,17 @@ def getAllPriorAccountingPeriods(targetAccountingPeriod): no arguments Returns: - accoutingPeriods (List of AccoutingPeriod) + accounting_period (List of AccoutingPeriod) Raises: - NoPriorAccountingPeriodFound when there is no valid accounting Period""" - currentValidAccountingPeriod = None - accountingPeriods = [] - for accountingPeriod in AccountingPeriod.objects.all(): - if accountingPeriod.end < targetAccountingPeriod.begin: - accountingPeriods.append(accountingPeriod) - if accountingPeriods == []: - raise NoPriorAccountingPeriodFound() - return accountingPeriods - - @staticmethod - def createXML(whatToCreate): - """This method serialize requestd objects into a XML file which is located in the PDF_OUTPUT_ROOT folder. - - Args: - whatToCreate (str): Which objects that have to be serialized - - Returns: - path_full to the location of the file - - Raises: - ProgrammingError will be raised when incorrect objects to be serialized was selected - NoObjectToBeSerialized will be raised when no object can be serialized""" - - XMLSerializer = serializers.get_serializer("xml") - xml_serializer = XMLSerializer() - if whatToCreate == "allAccount": - path_fullToOutputFile = os.path.join(settings.PDF_OUTPUT_ROOT, "accounts.xml") - objectsToSerialize = Account.objects.all() - else: - raise ProgrammingError( - _("During XML Export it was not correctly specified which data that has to be exported")) - out = open(os.path.join(settings.PDF_OUTPUT_ROOT, "accounts.xml"), "w") - if objectsToSerialize == '': - raise NoObjectsToBeSerialzed(_("During XML Export it was not correctly specied data has to be exported")) - else: - xml_serializer.serialize(objectsToSerialize, stream=out, indent=3) - out.close() - return path_fullToOutputFile - - # TODO def importAllAccountsXML(self): - - def createPDF(self, raisedbyuser, whatToCreate): - userExtension = djangoUserExtension.models.UserExtension.objects.filter(user=raisedbyuser.id) - if (len(userExtension) == 0): - raise UserExtensionMissing(_("During BalanceSheet PDF Export")) - doc = Document() - if whatToCreate == "balanceSheet": - main = doc.createElement("koalixaccountingbalacesheet") - out = open(os.path.join(settings.PDF_OUTPUT_ROOT, "balancesheet_" + str(self.id) + ".xml"), "wb") - else: - main = doc.createElement("koalixaccountingprofitlossstatement") - out = open(os.path.join(settings.PDF_OUTPUT_ROOT, "profitlossstatement_" + str(self.id) + ".xml"), "wb") - accountingPeriodName = doc.createElement("accountingPeriodName") - accountingPeriodName.appendChild(doc.createTextNode(self.__str__())) - main.appendChild(accountingPeriodName) - organisiationname = doc.createElement("organisiationname") - organisiationname.appendChild(doc.createTextNode(userExtension[0].defaultTemplateSet.organisationname)) - main.appendChild(organisiationname) - accountingPeriodTo = doc.createElement("accountingPeriodTo") - accountingPeriodTo.appendChild(doc.createTextNode(self.end.year.__str__())) - main.appendChild(accountingPeriodTo) - accountingPeriodFrom = doc.createElement("accountingPeriodFrom") - accountingPeriodFrom.appendChild(doc.createTextNode(self.begin.year.__str__())) - main.appendChild(accountingPeriodFrom) - headerPicture = doc.createElement("headerpicture") - headerPicture.appendChild(doc.createTextNode(userExtension[0].defaultTemplateSet.logo.path_full)) - main.appendChild(headerPicture) - accounts = Account.objects.all() - overallValueBalance = 0 - overallValueProfitLoss = 0 - for account in list(accounts): - withinAccountingPeriod = account.sumOfAllBookingsWithinAccountingPeriod(self) - beforeAccountingPeriod = account.sumOfAllBookingsBeforeAccountingPeriod(self) - currentValue = withinAccountingPeriod + beforeAccountingPeriod - if (currentValue != 0): - currentAccountElement = doc.createElement("Account") - accountNumber = doc.createElement("AccountNumber") - accountNumber.appendChild(doc.createTextNode(account.accountNumber.__str__())) - beforeAccountingPeriodAccountElement = doc.createElement("beforeAccountingPeriod") - beforeAccountingPeriodAccountElement.appendChild(doc.createTextNode(beforeAccountingPeriod.__str__())) - currentValueElement = doc.createElement("currentValue") - currentValueElement.appendChild(doc.createTextNode(currentValue.__str__())) - accountNameElement = doc.createElement("accountName") - accountNameElement.appendChild(doc.createTextNode(account.title)) - currentAccountElement.setAttribute("accountType", account.accountType.__str__()) - currentAccountElement.appendChild(accountNumber) - currentAccountElement.appendChild(accountNameElement) - currentAccountElement.appendChild(currentValueElement) - currentAccountElement.appendChild(beforeAccountingPeriodAccountElement) - main.appendChild(currentAccountElement) - if account.accountType == "A": - overallValueBalance = overallValueBalance + currentValue; - if account.accountType == "L": - overallValueBalance = overallValueBalance - currentValue; - if account.accountType == "E": - overallValueProfitLoss = overallValueProfitLoss + currentValue; - if account.accountType == "S": - overallValueProfitLoss = overallValueProfitLoss - currentValue; - totalProfitLoss = doc.createElement("TotalProfitLoss") - totalProfitLoss.appendChild(doc.createTextNode(overallValueProfitLoss.__str__())) - main.appendChild(totalProfitLoss) - totalBalance = doc.createElement("TotalBalance") - totalBalance.appendChild(doc.createTextNode(overallValueBalance.__str__())) - main.appendChild(totalBalance) - doc.appendChild(main) - out.write(doc.toprettyxml(indent=" ", newl="\n", encoding="utf-8")) - out.close() - if whatToCreate == "balanceSheet": - check_output( - [settings.FOP_EXECUTABLE, '-c', userExtension[0].defaultTemplateSet.fopConfigurationFile.path_full, '-xml', - os.path.join(settings.PDF_OUTPUT_ROOT, 'balancesheet_' + str(self.id) + '.xml'), '-xsl', - userExtension[0].defaultTemplateSet.balancesheetXSLFile.xslfile.path_full, '-pdf', - os.path.join(settings.PDF_OUTPUT_ROOT, 'balancesheet_' + str(self.id) + '.pdf')], stderr=STDOUT) - return os.path.join(settings.PDF_OUTPUT_ROOT, "balancesheet_" + str(self.id) + ".pdf") - else: - check_output( - [settings.FOP_EXECUTABLE, '-c', userExtension[0].defaultTemplateSet.fopConfigurationFile.path_full, '-xml', - os.path.join(settings.PDF_OUTPUT_ROOT, 'profitlossstatement_' + str(self.id) + '.xml'), '-xsl', - userExtension[0].defaultTemplateSet.profitLossStatementXSLFile.xslfile.path_full, '-pdf', - os.path.join(settings.PDF_OUTPUT_ROOT, 'profitlossstatement_' + str(self.id) + '.pdf')], stderr=STDOUT) - return os.path.join(settings.PDF_OUTPUT_ROOT, "profitlossstatement_" + str(self.id) + ".pdf") + AccountingPeriodNotFound when there is no valid accounting Period""" + accounting_periods = [] + for accounting_period in AccountingPeriod.objects.all(): + if accounting_period.end < target_accounting_period.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 @@ -192,81 +160,112 @@ class Meta: class Account(models.Model): - accountNumber = models.IntegerField(verbose_name=_("Account Number")) + account_number = models.IntegerField(verbose_name=_("Account Number")) title = models.CharField(verbose_name=_("Account Title"), max_length=50) - accountType = models.CharField(verbose_name=_("Account Type"), max_length=1, choices=ACCOUNTTYPECHOICES) + account_type = models.CharField(verbose_name=_("Account Type"), max_length=1, choices=ACCOUNTTYPECHOICES) description = models.TextField(verbose_name=_("Description"), null=True, blank=True) - 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")) - - def sumOfAllBookings(self): - calculated_sum = self.allBookings(fromAccount=False) - self.allBookings(fromAccount=True) - if self.accountType == 'S' or self.accountType == 'L': + 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 - sumOfAllBookings.short_description = _("Value"); + sum_of_all_bookings.short_description = _("Value"); - def sumOfAllBookingsWithinAccountingPeriod(self, accountingPeriod): - calculated_sum = self.allBookingsInAccountingPeriod(fromAccount=False, - accountingPeriod=accountingPeriod) - self.allBookingsInAccountingPeriod( - fromAccount=True, accountingPeriod=accountingPeriod) - if self.accountType == 'S' or self.accountType == 'L': - calculated_sum = 0 - calculated_sum + 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 sumOfAllBookingsBeforeAccountingPeriod(self, currentAccountingPeriod): - accountingPeriods = AccountingPeriod.getAllPriorAccountingPeriods(currentAccountingPeriod) + def sum_of_all_bookings_before_accounting_period(self, current_accounting_period): + try: + accounting_periods = AccountingPeriod.get_all_prior_accounting_periods(current_accounting_period) + except AccountingPeriodNotFound as e: + return 0; sum = 0 - for accountingPeriod in accountingPeriods: - sum = sum + self.allBookingsInAccountingPeriod(fromAccount=False, - accountingPeriod=accountingPeriod) - self.allBookingsInAccountingPeriod( - fromAccount=True, accountingPeriod=accountingPeriod) - if self.accountType == 'S' or self.accountType == 'L': - sum = 0 - sum + for accounting_period in accounting_periods: + 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': + sum = -sum return sum - def allBookings(self, fromAccount): + 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): sum = 0 - if fromAccount == True: - bookings = Booking.objects.filter(fromAccount=self.id) + if from_account: + bookings = Booking.objects.filter(from_account=self.id) else: - bookings = Booking.objects.filter(toAccount=self.id) + bookings = Booking.objects.filter(to_account=self.id) for booking in list(bookings): - sum = sum + booking.amount + sum += booking.amount return sum - def allBookingsInAccountingPeriod(self, fromAccount, accountingPeriod): + def all_bookings_within_accounting_period(self, from_account, accounting_period): sum = 0 - if (fromAccount == True): - bookings = Booking.objects.filter(fromAccount=self.id, accountingPeriod=accountingPeriod.id) + if from_account: + bookings = Booking.objects.filter(from_account=self.id, accounting_period=accounting_period.id) else: - bookings = Booking.objects.filter(toAccount=self.id, accountingPeriod=accountingPeriod.id) + bookings = Booking.objects.filter(to_account=self.id, accounting_period=accounting_period.id) for booking in list(bookings): - sum = sum + booking.amount + 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/[@model='accounting.account']", + "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/[@model='accounting.account']", + "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/[@model='accounting.account']", + "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/[@model='accounting.account']", + "sum_of_all_bookings_through_now", + self.sum_of_all_bookings()) + return main_xml + def __str__(self): - return self.accountNumber.__str__() + " " + self.title + return self.account_number.__str__() + " " + self.title class Meta: app_label = "accounting" verbose_name = _('Account') verbose_name_plural = _('Account') - ordering = ['accountNumber'] + ordering = ['account_number'] class ProductCategorie(models.Model): title = models.CharField(verbose_name=_("Product Categorie Title"), max_length=50) - profitAccount = models.ForeignKey(Account, verbose_name=_("Profit Account"), limit_choices_to={"accountType": "E"}, + profit_account = models.ForeignKey(Account, verbose_name=_("Profit Account"), limit_choices_to={"accountType": "E"}, related_name="db_profit_account") - lossAccount = models.ForeignKey(Account, verbose_name=_("Loss Account"), limit_choices_to={"accountType": "S"}, + loss_account = models.ForeignKey(Account, verbose_name=_("Loss Account"), limit_choices_to={"accountType": "S"}, related_name="db_loss_account") class Meta: @@ -279,27 +278,27 @@ def __str__(self): class Booking(models.Model): - fromAccount = models.ForeignKey(Account, verbose_name=_("From Account"), related_name="db_booking_fromaccount") - toAccount = models.ForeignKey(Account, verbose_name=_("To Account"), related_name="db_booking_toaccount") + 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) - bookingReference = models.ForeignKey('crm.Invoice', verbose_name=_("Booking Reference"), null=True, blank=True) - bookingDate = models.DateTimeField(verbose_name=_("Booking at")) - accountingPeriod = models.ForeignKey(AccountingPeriod, verbose_name=_("AccountingPeriod")) + 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") - dateofcreation = models.DateTimeField(verbose_name=_("Created at"), auto_now=True) - lastmodification = models.DateTimeField(verbose_name=_("Last modified"), auto_now_add=True) - lastmodifiedby = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True}, blank=True, + 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 bookingDateOnly(self): - return self.bookingDate.date() + def booking_date_only(self): + return self.booking_date.date() - bookingDateOnly.short_description = _("Date"); + booking_date_only.short_description = _("Date"); def __str__(self): - return self.fromAccount.__str__() + " " + self.toAccount.__str__() + " " + self.amount.__str__() + return self.from_account.__str__() + " " + self.to_account.__str__() + " " + self.amount.__str__() class Meta: app_label = "accounting" diff --git a/koalixcrm/accounting/static/default_templates/de/balancesheet.xsl b/koalixcrm/accounting/static/default_templates/de/balancesheet.xsl index 546d3f0d..feda5199 100644 --- a/koalixcrm/accounting/static/default_templates/de/balancesheet.xsl +++ b/koalixcrm/accounting/static/default_templates/de/balancesheet.xsl @@ -215,12 +215,7 @@ - - - + diff --git a/koalixcrm/accounting/static/default_templates/de/profitlossstatement.xsl b/koalixcrm/accounting/static/default_templates/de/profitlossstatement.xsl index 892a2919..f69b8e55 100644 --- a/koalixcrm/accounting/static/default_templates/de/profitlossstatement.xsl +++ b/koalixcrm/accounting/static/default_templates/de/profitlossstatement.xsl @@ -212,13 +212,7 @@ - - - - + diff --git a/koalixcrm/accounting/static/default_templates/en/balancesheet.xsl b/koalixcrm/accounting/static/default_templates/en/balancesheet.xsl index 546d3f0d..2c94fc48 100644 --- a/koalixcrm/accounting/static/default_templates/en/balancesheet.xsl +++ b/koalixcrm/accounting/static/default_templates/en/balancesheet.xsl @@ -1,8 +1,9 @@ + - + @@ -10,7 +11,7 @@ page-height="29.7cm" page-width="21cm" margin-top="1.5cm" - margin-bottom="0.5cm" + margin-bottom="1.0cm" margin-left="1.5cm" margin-right="1.5cm"> @@ -20,32 +21,15 @@ - - - - - - - - - - - - - - - - - Balanancesheet of + Balanancesheet of "To be set in the Template file" - + @@ -100,34 +84,44 @@ - - - - - - - + + + - + - + - CHF + CHF + + + + + Total assets + + + + + + + + CHF + + - - - - - + + + + - + - + - CHF + CHF - + + + + Total liabilities + + + + + + + + CHF + + + - CHF + CHF - - - + diff --git a/koalixcrm/accounting/static/default_templates/en/profitlossstatement.xsl b/koalixcrm/accounting/static/default_templates/en/profitlossstatement.xsl index 579ee0cb..2a7ff54f 100644 --- a/koalixcrm/accounting/static/default_templates/en/profitlossstatement.xsl +++ b/koalixcrm/accounting/static/default_templates/en/profitlossstatement.xsl @@ -219,13 +219,7 @@ - - - - + diff --git a/koalixcrm/accounting/static/default_templates/en/purchaseconfirmation.xsl b/koalixcrm/accounting/static/default_templates/en/purchaseconfirmation.xsl deleted file mode 100644 index b8a5ef44..00000000 --- a/koalixcrm/accounting/static/default_templates/en/purchaseconfirmation.xsl +++ /dev/null @@ -1,438 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Erstelldatum: - - Kundennummer: - Auftragsnummer: - - MwSt.-Nummer: - Postkonto: - Gültig bis: - - Ansprechpartner: - Telefon Direkt: - E-mail Direkt: - - - - .. - - KU- - RE- - - keine - PC 000054545 - .. - - - +41(0)545878948 - - - - Guest Guesterich, Gueststreet, CH-9602 Bazenheid - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -   - - - Rechnung RE- - - - - - - - - - - - - - - - - Guest Guesterich         Gueststreet         9602 Bazenheid         +41 (0)71 511 21 18         info@koalix.com - - - Seite / - - - - - - - - - - - - - - - - - - - Pos. - - - - - Beschreibung - - - - - Anzahl - - - - - Einzelpreis - - - - - Rabatt - - - - - Betrag - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -   - - - - - - - - - - - - - - - - - - - - - - % - - - - - - - - - - - - - - - - - - -   - - - - -   - - - - - Zwischensumme - - - - - - - - - - - - - - - - - - - - - - MwSt. - - - - - - - - - - - - - - - - - - - - - - Total - - - - - - - - - - - - - - - - -   - - - Freundliche Grüsse - - -   - - - Guest Guesterich - - - - - - - diff --git a/projectsettings/static/grappelli/stylesheets/mueller/screen.css b/koalixcrm/accounting/tests/__init__.py similarity index 100% rename from projectsettings/static/grappelli/stylesheets/mueller/screen.css rename to koalixcrm/accounting/tests/__init__.py diff --git a/koalixcrm/accounting/tests/test_accountingModelTest.py b/koalixcrm/accounting/tests/test_accountingModelTest.py index df8fe6b9..50694ce2 100644 --- a/koalixcrm/accounting/tests/test_accountingModelTest.py +++ b/koalixcrm/accounting/tests/test_accountingModelTest.py @@ -1,14 +1,183 @@ -from unittest import TestCase - +from django.test import TestCase from koalixcrm.accounting.models import Account +from koalixcrm.accounting.models import AccountingPeriod +from koalixcrm.accounting.models import Booking +from django.contrib.auth.models import User +from koalixcrm.crm.documents.pdfexport import PDFExport +import datetime class AccountingModelTest(TestCase): + def setUp(self): + user = User.objects.create(username='Username', + password="Userone") + + 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 = 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=datetime.date.today(), + 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=datetime.date.today(), + 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=datetime.date.today(), + 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=datetime.date.today(), + accounting_period=accounting_period_2024, + staff=user, + last_modified_by=user) def test_sumOfAllBookings(self): - account = Account( - accountNumber=1234, - title="Account1234") + 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") - assert account.accountNumber == 1234 - assert account.title == "Account1234" + 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/[@model='accounting.account']/field[@name='title']", 'Earnings') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.account']/field[@name='title']", 'Spendings') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.account']/field[@name='title']", 'Investment capital') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.account']/field[@name='title']", 'Shortterm bankloans') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.account']/field[@name='title']",'Cash') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.account']/field[@name='title']",'Bank Account') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.accountingperiod']/Overall_Spendings", '1000.00') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.accountingperiod']/Overall_Earnings", '500.00') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.accountingperiod']/Overall_Assets", '494500.00') + self.assertEqual(result, 1) + result=PDFExport.find_element_in_xml(xml,"object/[@model='accounting.accountingperiod']/Overall_Liabilities", '495000.00') + self.assertEqual(result, 1) diff --git a/koalixcrm/accounting/tests/test_models.py b/koalixcrm/accounting/tests/test_models.py deleted file mode 100644 index 1488766f..00000000 --- a/koalixcrm/accounting/tests/test_models.py +++ /dev/null @@ -1,6 +0,0 @@ -import unittest - - -class TestExample(unittest.TestCase): - def test_hello_world(self): - self.assertEqual("hello world", "hello world") \ No newline at end of file diff --git a/koalixcrm/celery.py b/koalixcrm/celery.py deleted file mode 100644 index 1576e3e2..00000000 --- a/koalixcrm/celery.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import absolute_import, unicode_literals -import os -from celery import Celery - -# set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'projectsettings.settings.development_settings') - -app = Celery('koalixcrm') - -# Using a string here means the worker doesn't have to serialize -# the configuration object to child processes. -# - namespace='CELERY' means all celery-related configuration keys -# should have a `CELERY_` prefix. -app.config_from_object('django.conf:settings', namespace='CELERY') - -# Load task modules from all registered Django app configs. -app.autodiscover_tasks() - - -@app.task(bind=True) -def debug_task(self): - print('Request: {0!r}'.format(self.request)) \ No newline at end of file diff --git a/koalixcrm/crm/admin.py b/koalixcrm/crm/admin.py index 21557884..6675719f 100644 --- a/koalixcrm/crm/admin.py +++ b/koalixcrm/crm/admin.py @@ -8,47 +8,30 @@ from koalixcrm.crm.documents.paymentreminder import PaymentReminder, OptionPaymentReminder from koalixcrm.crm.documents.purchaseorder import PurchaseOrder, OptionPurchaseOrder from koalixcrm.crm.documents.contract import Contract, OptionContract -from koalixcrm.crm.documents.activity import Call -from koalixcrm.crm.documents.visit import Visit, OptionVisit from koalixcrm.crm.product.tax import Tax, OptionTax from koalixcrm.crm.product.unit import Unit, OptionUnit from koalixcrm.crm.product.product import Product, OptionProduct from koalixcrm.crm.product.currency import Currency, OptionCurrency -from koalixcrm.crm.product.attribute import AttributeSet, OptionAttributeSet, Attribute, OptionAttribute from koalixcrm.crm.contact.customer import Customer, OptionCustomer from koalixcrm.crm.contact.supplier import Supplier, OptionSupplier from koalixcrm.crm.contact.customergroup import CustomerGroup, OptionCustomerGroup from koalixcrm.crm.contact.customerbillingcycle import CustomerBillingCycle, OptionCustomerBillingCycle from koalixcrm.crm.contact.person import Person -from koalixcrm.crm.contact.contact import CallForContact, OptionCall, OptionPerson -from koalixcrm.crm.contact.data_import import ContactImportData -from koalixcrm.crm.forms import ImportDataContactForm +from koalixcrm.crm.contact.contact import OptionPerson, CallForContact, VisitForContact +from koalixcrm.crm.documents.activity import OptionCall, OptionVisit +from koalixcrm.crm.reporting.task import Task, OptionTask +from koalixcrm.crm.reporting.tasklinktype import TaskLinkType, OptionTaskLinkType +from koalixcrm.crm.reporting.taskstatus import TaskStatus, OptionTaskStatus +from koalixcrm.crm.reporting.work import Work, OptionWork -class ContactImportDataAdmin(admin.ModelAdmin): - model = ContactImportData - list_display = ('data_file', 'contact_type',) - form = ImportDataContactForm - group_fieldsets = True - - def get_actions(self, request): - actions = super(ContactImportDataAdmin, self).get_actions(request) - if 'delete_selected' in actions: - del actions['delete_selected'] - return actions - - def get_form(self, request, obj=None, **kwargs): - form = super(ContactImportDataAdmin, self).get_form(request, obj, **kwargs) - form.current_user = request.user - return form - - def __init__(self, *args, **kwargs): - super(ContactImportDataAdmin, self).__init__(*args, **kwargs) - #self.list_display_links = (None, ) 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) @@ -62,10 +45,8 @@ def __init__(self, *args, **kwargs): admin.site.register(Currency, OptionCurrency) admin.site.register(Tax, OptionTax) admin.site.register(Product, OptionProduct) -admin.site.register(AttributeSet, OptionAttributeSet) -admin.site.register(Attribute, OptionAttribute) -admin.site.register(CallForContact, OptionCall) -admin.site.register(Visit, OptionVisit) -admin.site.register(Person, OptionPerson) -admin.site.register(ContactImportData, ContactImportDataAdmin) +admin.site.register(Task, OptionTask) +admin.site.register(TaskLinkType, OptionTaskLinkType) +admin.site.register(TaskStatus, OptionTaskStatus) +admin.site.register(Work, OptionWork) diff --git a/koalixcrm/crm/const/contactimport.py b/koalixcrm/crm/const/contactimport.py deleted file mode 100644 index 30d9decb..00000000 --- a/koalixcrm/crm/const/contactimport.py +++ /dev/null @@ -1,91 +0,0 @@ -#IMPOTER FILE COLUMN MAPPING -COMPANY = 0 -ISLEAD = 1 -TYPEOFACTIVITY = 2 -RATING = 3 -VAT = 4 -ZIPCODE = 5 -ADDRESS = 6 -ADDRESS_NO = 7 -CITY = 8 -STATE = 9 -COUNTRY = 10 -MOBILE1 = 11 -PHONE1 = 12 -PHONE2 = 13 -FAX = 14 -PERSONPREFIX = 15 -NAME = 16 -LASTNAME = 17 -ROLE = 18 -PERSONEMAIL = 19 -COMPANYEMAIL = 20 -FIRSTCONTACTDATE = 21 -LASTCONTACTDATE = 22 -EXTLINENOTES = 23 -EXTERNALLINES = 24 -INTERNALLINES = 25 -LINETYPE = 26 -HASSWITCHBOARD = 27 -YEAROFINSTALLATION = 28 -SWITCHBOARDMODEL = 29 -SWITCHBOARDPROVIDER = 30 -MAINTAINER = 31 -PHONEPROVIDE1 = 32 -PHONEPROVIDER2 = 33 -MOBILEPROVIDER = 34 -PHONEEXPIREDATE = 35 -ANALOGPHONES = 36 -DIGITALPHONES = 37 -PHONEMANUFACTURER1 = 38 -PHONEMANUFACTURER2 = 39 -HASINTERNET = 40 -TYPEOFINTERNETCONNECTION = 41 -PHONEMANUFACTURER3 = 42 -PHONEEXPENSES = 43 -INTERNETPROVIDER = 44 -LAN_PC = 45 -INTERNETEXPIREDATE = 46 -INTERNETEXPENSES = 47 -PCMANUFACTURER = 48 -HASPOS = 49 -PHONEFROMPC = 50 -HASCCTV = 51 -ISDIGITALCCTV = 52 -NUMBEROFVC = 53 -WANTSCCTV = 54 -NOOFEMPLOYEES = 55 -TURNOVER = 56 -NETREVENUE = 57 -SHARECAPITAL = 58 -MEETINGNOTES = 59 - -#VALUES -SKIP_ROW_VALUE = '6' - -PHONE_SYSTEM_P_TYPE = 'phone_system' -ANALOG_PHONE_P_TYPE = 'analog_phones' -DIGITAL_PHONE_P_TYPE = 'digital_phones' -INTERNET_P_TYPE = 'internet' -MOBILE_P_TYPE = 'mobile' - -DEFAULT_ATTRIBUTE_SET = 'base_product' -DEFAULT_PHONE_SERVICE_TYPE = 'Sistema telefonico' -DEFAULT_PHONE_ATTRIBUTE_SET = 'Centralino' -DEFAULT_PHONE_PRODUCT = 'CENT01' -DEFAULT_PHONE_PRODUCT_TITLE = 'Centralino Base' -DEFAULT_PHONE_SUPPLIER = 'Telecom' -DEFAULT_PHONE_ATTRIBUTE_SET2 = 'Telefono' -DEFAULT_PHONE_PRODUCT2 = 'PHONE01' -DEFAULT_PHONE_PRODUCT_TITLE2 = 'Telefono base' -DEFAULT_ANALOG_PHONE_SERVICE_TYPE = 'Telefoni analogici' -DEFAULT_DIGITAL_PHONE_SERVICE_TYPE = 'Telefoni digitali' -DEFAULT_MOBILE_SERVICE_TYPE = 'Telefonia mobile' -DEFAULT_INTERNET_SERVICE_TYPE = 'Connettività' -DEFAULT_INTERNET_ATTRIBUTE_SET = 'Connessione' -DEFAULT_INTERNET_PRODUCT = 'CONN01' -DEFAULT_INTERNET_PRODUCT_TITLE = 'Connessione base' -DEFAULT_EMPTY_SUPPLIER = 'Altro' -DEFAULT_TAX = 'IVA22' -DEFAULT_UNIT = 'PZ' -DEFAULT_UNIT_DESCRIPTION = 'Piece' diff --git a/koalixcrm/crm/const/modeltype.py b/koalixcrm/crm/const/modeltype.py deleted file mode 100644 index 9558f482..00000000 --- a/koalixcrm/crm/const/modeltype.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -* - -from django.utils.translation import ugettext as _ - -MODELTYPE = ( - ('V', _('Varchar')), - ('I', _('Integer')), - ('D', _('Decimal')), - ('T', _('Text')), -) \ No newline at end of file diff --git a/koalixcrm/crm/const/postaladdressprefix.py b/koalixcrm/crm/const/postaladdressprefix.py index e4477c65..894c2988 100644 --- a/koalixcrm/crm/const/postaladdressprefix.py +++ b/koalixcrm/crm/const/postaladdressprefix.py @@ -7,8 +7,4 @@ ('W', _('Mrs')), ('H', _('Mr')), ('G', _('Ms')), - ('J', _('J.D.')), - ('A', _('Ar')), - ('I', _('Ing')), - ('D', _('Dott')), ) diff --git a/koalixcrm/crm/const/purpose.py b/koalixcrm/crm/const/purpose.py index 3a6d874a..b1976882 100644 --- a/koalixcrm/crm/const/purpose.py +++ b/koalixcrm/crm/const/purpose.py @@ -13,7 +13,6 @@ ('O', _('Business')), ('P', _('Mobile Private')), ('B', _('Mobile Business')), - ('F', _('Fax')), ) PURPOSESTEXTPARAGRAPHINDOCUMENTS = ( @@ -35,11 +34,7 @@ ('A', _('Assistance call')), ) -SERVICETYPE = ( - ('P', _('Phone')), -) - -CONTACTTYPE = ( - ('C', _('Customer')), - ('S', _('Supplier')), -) +PURPOSEVISITINCUSTOMER = ( + ('F', _('First commercial visit')), + ('S', _('Installation')), +) \ No newline at end of file diff --git a/koalixcrm/crm/contact/contact.py b/koalixcrm/crm/contact/contact.py index 0a869dc5..3daf49b4 100644 --- a/koalixcrm/crm/contact/contact.py +++ b/koalixcrm/crm/contact/contact.py @@ -2,10 +2,7 @@ from django.db import models from django.contrib import admin -from django import forms from django.utils.translation import ugettext as _ -from koalixcrm.crm.const.country import * -from koalixcrm.crm.const.postaladdressprefix import * from koalixcrm.crm.contact.phoneaddress import PhoneAddress from koalixcrm.crm.contact.emailaddress import EmailAddress from koalixcrm.crm.contact.postaladdress import PostalAddress @@ -15,31 +12,26 @@ from koalixcrm.globalSupportFunctions import xstr from koalixcrm.crm.inlinemixin import LimitedAdminInlineMixin -from django.utils import timezone - class Contact(models.Model): name = models.CharField(max_length=300, verbose_name=_("Name")) - dateofcreation = models.DateTimeField(verbose_name=_("Created at"), auto_now_add=True) - lastmodification = models.DateTimeField(verbose_name=_("Last modified"), auto_now=True) - lastmodifiedby = models.ForeignKey('auth.User', limit_choices_to={'is_staff': 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}, blank=True, verbose_name=_("Last modified by"), editable=True) - vatnumber = models.CharField(max_length=20, verbose_name=_("Vat Number"), blank=True) - - people = models.ManyToManyField("Person", through='ContactPersonAssociation', verbose_name=_('Has contact'), blank=True) - - def __str__(self): - return self.name 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) - company = models.ForeignKey(Contact) + person = models.ForeignKey(Contact) class Meta: app_label = "crm" @@ -52,7 +44,7 @@ def __str__(self): class EmailAddressForContact(EmailAddress): purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCUSTOMER) - company = models.ForeignKey(Contact) + person = models.ForeignKey(Contact) class Meta: app_label = "crm" @@ -65,7 +57,7 @@ def __str__(self): class PostalAddressForContact(PostalAddress): purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCUSTOMER) - company = models.ForeignKey(Contact) + person = models.ForeignKey(Contact) class Meta: app_label = "crm" @@ -112,10 +104,53 @@ class ContactEmailAddress(admin.TabularInline): ) 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', 'prename', 'email', 'role', 'get_companies',) + #filter_horizontal = ('companies',) + fieldsets = (('', {'fields': ('prefix','name','prename','role','email','phone',)}),) + allow_add = True + inlines = [CompaniesInlineAdmin] + pluginProcessor = PluginProcessor() + inlines.extend(pluginProcessor.getPluginAdditions("personInline")) + + actions = [] + pluginProcessor = PluginProcessor() + inlines.extend(pluginProcessor.getPluginAdditions("personActions")) + + 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): - purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSECALLINCUSTOMER) 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" @@ -126,9 +161,9 @@ def __str__(self): return xstr(self.description) + ' ' + xstr(self.date_due) class VisitForContact(Call): - purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSECALLINCUSTOMER) 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: @@ -169,63 +204,6 @@ class ContactVisit(LimitedAdminInlineMixin, admin.StackedInline): 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 OptionCall(admin.ModelAdmin): - list_display = ('id','description','date_due','purpose','get_contactname', 'status', 'is_call_overdue',) - list_filter = [CallOverdueFilter] - - def get_contactname(self, obj): - return obj.company.name - - get_contactname.short_description = _("Company") - - def is_call_overdue(self, obj): - return (obj.date_due < timezone.now() and obj.status not in ['F', 'S']) - - is_call_overdue.short_description = _("Is call overdue") - -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', 'prename', 'email', 'role', 'get_companies',) - #filter_horizontal = ('companies',) - fieldsets = (('', {'fields': ('prefix','name','prename','role','email','phone',)}),) - allow_add = True - inlines = [CompaniesInlineAdmin] - pluginProcessor = PluginProcessor() - inlines.extend(pluginProcessor.getPluginAdditions("personInline")) - - actions = [] - pluginProcessor = PluginProcessor() - inlines.extend(pluginProcessor.getPluginAdditions("personActions")) - - 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 StateFilter(admin.SimpleListFilter): title = _('State') parameter_name = 'state' diff --git a/koalixcrm/crm/contact/customer.py b/koalixcrm/crm/contact/customer.py index 2bc6fa99..a6c7e897 100644 --- a/koalixcrm/crm/contact/customer.py +++ b/koalixcrm/crm/contact/customer.py @@ -1,244 +1,47 @@ # -*- 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 import djangoUserExtension from koalixcrm.crm.contact.contact import Contact, ContactCall, ContactVisit, PeopleInlineAdmin, PostalAddressForContact, ContactPostalAddress, ContactPhoneAddress, ContactEmailAddress, CityFilter, StateFilter -from koalixcrm.crm.contact.supplier import Supplier -from koalixcrm.crm.product.product import Product -from koalixcrm.crm.contact.person import * -from django.http import HttpResponseRedirect +from koalixcrm.crm.documents.contract import Contract -import koalixcrm.crm.documents.contract class Customer(Contact): - defaultCustomerBillingCycle = models.ForeignKey('CustomerBillingCycle', verbose_name=_('Default Billing Cycle')) - ismemberof = models.ManyToManyField("CustomerGroup", verbose_name=_('Is member of'), blank=True) - isLead = models.BooleanField(default=True) + 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 createContract(self, request): - contract = koalixcrm.crm.documents.contract.Contract() - contract.default_customer = self - contract.default_currency = djangoUserExtension.models.UserExtension.objects.filter(user=request.user.id)[ - 0].defaultCurrency - contract.last_modified_by = request.user - contract.staff = request.user - contract.save() + def create_contract(self, request): + contract = Contract() + contract.create_from_reference(self, request.user) return contract - def createInvoice(self, request): - contract = self.createContract(request) - invoice = contract.createInvoice() + def create_invoice(self, request): + contract = self.create_contract(request) + invoice = contract.create_invoice() return invoice - def createQuote(self): - contract = self.createContract() - quote = contract.createQuote() + def create_quote(self, request): + contract = self.create_contract(request) + quote = contract.create_quote() return quote - def isInGroup(self, customerGroup): - for customerGroupMembership in self.ismemberof.all(): - if (customerGroupMembership.id == customerGroup.id): + 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 - '''def hasPerson(self, person): - for customerContact in self.people.all(): - if (customerContact.id == person.id): - return 1 - return 0''' - class Meta: app_label = "crm" verbose_name = _('Customer') verbose_name_plural = _('Customers') def __str__(self): - return '{} ({})'.format(str(self.name), self.id) - -class ProductForCustomer(models.Model): - customer = models.ForeignKey(Customer, related_name='supplier_association', blank=True, null=True) - product = models.ForeignKey(Product, verbose_name=_("Related Product"), blank=True, null=True) - supplier = models.ForeignKey(Supplier, related_name='customer_association', blank=True, null=True) - service_type = models.CharField(verbose_name=_("Service Type"), max_length=100, blank=True, null=True) - quantity = models.IntegerField(verbose_name=_("Quantity"), blank=True, null=True) - maintainer = models.CharField(verbose_name=_("Maintainer"), max_length=100, blank=True, null=True) - year = models.CharField(verbose_name=_("Year of installation"), max_length=50, blank=True, null=True) - expire_date = models.DateTimeField(verbose_name=_("Expire Date"), blank=True, null=True) - - class Meta: - app_label = "crm" - verbose_name = _('Product') - verbose_name_plural = _('Products') - - def __str__(self): - return str(self.id) - -class SwitchboardForCustomer(ProductForCustomer): - internal_lines = models.IntegerField(verbose_name=_("Internal lines"), blank=True, null=True) - external_lines = models.IntegerField(verbose_name=_("External lines"), blank=True, null=True) - - class Meta: - app_label = "crm" - verbose_name = _('Switchboard') - verbose_name_plural = _('Switchboards') - - def __str__(self): - return str(self.id) - -class AnalogPhoneForCustomer(ProductForCustomer): - class Meta: - app_label = "crm" - verbose_name = _('Analog Phone') - verbose_name_plural = _('Analog Phones') - - def __str__(self): - return str(self.id) - -class DigitalPhoneForCustomer(ProductForCustomer): - class Meta: - app_label = "crm" - verbose_name = _('Digital Phone') - verbose_name_plural = _('Digital Phones') - - def __str__(self): - return str(self.id) - -class InternetForCustomer(ProductForCustomer): - class Meta: - app_label = "crm" - verbose_name = _('Internet Connection') - verbose_name_plural = _('Internet Connections') - - def __str__(self): - return str(self.id) - -class MobileForCustomer(ProductForCustomer): - class Meta: - app_label = "crm" - verbose_name = _('Mobile Service') - verbose_name_plural = _('Mobile Services') - - def __str__(self): - return str(self.id) - - -class CustomerPhoneSystem(admin.StackedInline): - model = SwitchboardForCustomer - extra = 0 - classes = ['collapse'] - raw_id_fields = ("product",) - autocomplete_lookup_fields = { - 'fk': ['product'], - } - fieldsets = ( - (None, {'fields': ['product']}), - ('Additional data', { - 'fields': ( - 'service_type', 'supplier', 'expire_date', 'year', 'external_lines', 'internal_lines',) - }), - ) - - def __str__(self): - return '{} ({})'.format(str(self.product.name), self.product.id) - -class CustomerAnalogPhones(admin.StackedInline): - model = AnalogPhoneForCustomer - extra = 0 - classes = ['collapse'] - raw_id_fields = ("product",) - autocomplete_lookup_fields = { - 'fk': ['product'], - } - fieldsets = ( - (None, {'fields': ['product']}), - ('Additional data', { - 'fields': ( - 'service_type', 'supplier', 'expire_date', 'year',) - }), - ) - - def __str__(self): - return '{} ({})'.format(str(self.product.name), self.product.id) - -class CustomerDigitalPhones(admin.StackedInline): - model = DigitalPhoneForCustomer - extra = 0 - classes = ['collapse'] - raw_id_fields = ("product",) - autocomplete_lookup_fields = { - 'fk': ['product'], - } - fieldsets = ( - (None, {'fields': ['product']}), - ('Additional data', { - 'fields': ( - 'service_type', 'supplier', 'expire_date', 'year',) - }), - ) - - def __str__(self): - return '{} ({})'.format(str(self.product.name), self.product.id) - -class CustomerMobilePhones(admin.StackedInline): - model = MobileForCustomer - extra = 0 - classes = ['collapse'] - raw_id_fields = ("product",) - autocomplete_lookup_fields = { - 'fk': ['product'], - } - fieldsets = ( - (None, {'fields': ['product']}), - ('Additional data', { - 'fields': ( - 'service_type', 'supplier', 'expire_date', 'year',) - }), - ) - - def __str__(self): - return '{} ({})'.format(str(self.product.name), self.product.id) - -class CustomerInternetConnection(admin.StackedInline): - model = InternetForCustomer - extra = 0 - classes = ['collapse'] - raw_id_fields = ("product",) - autocomplete_lookup_fields = { - 'fk': ['product'], - } - fieldsets = ( - (None, {'fields': ['product']}), - ('Additional data', { - 'fields': ( - 'service_type', 'supplier', 'expire_date', 'year',) - }), - ) - - def __str__(self): - return '{} ({})'.format(str(self.product.name), self.product.id) - -'''class PhoneProviderFilter(admin.SimpleListFilter): - title = _('Phone provider') - parameter_name = 'phone_provider' - - def lookups(self, request, model_admin): - list = [] - for s in Supplier.objects.all(): - list.append((s.id, _(s.name))) - return ( - list - ) - - def queryset(self, request, queryset): - for p in PhoneSystemForCustomer.objects.all(): - if self.value() == str(p.supplier.id): - cust_per_supplier = PhoneSystemForCustomer.objects.filter(supplier=p.supplier) - ids = [(c.customer.id) for c in cust_per_supplier] - return queryset.filter(pk__in=ids) - return queryset''' + return str(self.id) + ' ' + self.name class IsLeadFilter(admin.SimpleListFilter): title = _('Is lead') @@ -259,20 +62,18 @@ def queryset(self, request, queryset): return queryset class OptionCustomer(admin.ModelAdmin): - list_display = ('id', 'name', 'defaultCustomerBillingCycle', 'get_state', 'get_town', 'dateofcreation', 'get_is_lead',) - list_filter = ('ismemberof', StateFilter, CityFilter, IsLeadFilter) - #list_display_links = ('name',) - fieldsets = (('', {'fields': ('name', 'defaultCustomerBillingCycle', 'ismemberof',)}),) + 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, CustomerPhoneSystem, CustomerAnalogPhones, CustomerDigitalPhones, CustomerMobilePhones, CustomerInternetConnection, ContactCall, ContactVisit] - + inlines = [ContactPostalAddress, ContactPhoneAddress, ContactEmailAddress, PeopleInlineAdmin, ContactCall, ContactVisit] pluginProcessor = PluginProcessor() inlines.extend(pluginProcessor.getPluginAdditions("customerInline")) def get_postal_address(self, obj): - return PostalAddressForContact.objects.filter(company=obj.id).first() + return PostalAddressForContact.objects.filter(person=obj.id).first() def get_state(self, obj): address = self.get_postal_address(obj) @@ -287,45 +88,44 @@ def get_town(self, obj): get_town.short_description = _("City") def get_is_lead(self, obj): - return obj.isLead + return obj.is_lead get_is_lead.short_description = _("Is Lead") - def createContract(self, request, queryset): + def create_contract(self, request, queryset): for obj in queryset: - contract = obj.createContract(request) + contract = obj.create_contract(request) response = HttpResponseRedirect('/admin/crm/contract/' + str(contract.id)) return response - createContract.short_description = _("Create Contract") + create_contract.short_description = _("Create Contract") @staticmethod - def createQuote(self, request, queryset): + def create_quote(self, request, queryset): for obj in queryset: - quote = obj.createQuote() + quote = obj.create_quote(request) response = HttpResponseRedirect('/admin/crm/quote/' + str(quote.id)) return response - createQuote.short_description = _("Create Quote") + create_quote.short_description = _("Create Quote") @staticmethod - def createInvoice(self, request, queryset): + def create_invoice(self, request, queryset): for obj in queryset: - invoice = obj.createInvoice() + invoice = obj.create_invoice(request) response = HttpResponseRedirect('/admin/crm/invoice/' + str(invoice.id)) return response - createInvoice.short_description = _("Create Invoice") + create_invoice.short_description = _("Create Invoice") def save_model(self, request, obj, form, change): - if (change == True): - obj.lastmodifiedby = request.user + if change: + obj.last_modified_by = request.user else: - obj.lastmodifiedby = request.user + obj.last_modified_by = request.user obj.staff = request.user obj.save() - actions = ['createContract', 'createInvoice', 'createQuote'] + actions = ['create_contract', 'create_invoice', 'create_quote'] pluginProcessor = PluginProcessor() - inlines.extend(pluginProcessor.getPluginAdditions("customerActions")) - + inlines.extend(pluginProcessor.getPluginAdditions("customerActions")) \ No newline at end of file diff --git a/koalixcrm/crm/contact/data_import.py b/koalixcrm/crm/contact/data_import.py deleted file mode 100644 index bf0e9dfa..00000000 --- a/koalixcrm/crm/contact/data_import.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.db import models -from django.utils.translation import ugettext as _ -from koalixcrm.crm.const.purpose import * - -class ContactImportData(models.Model): - data_file = models.FileField(upload_to='data_files', max_length=255) - - contact_type = models.CharField(verbose_name=_("Contact Type"), max_length=1, choices=CONTACTTYPE) - - def file_link(self): - if self.data_file: - return "download" % (self.data_file.url,) - else: - return "No attachment" - - file_link.allow_tags = True - - def __str__(self): - return '{}'.format(self.data_file.name) - - class Meta: - """ - """ - verbose_name = 'Contact: Import Data from XLSX file' - verbose_name_plural = 'Contacts: Import Data from XLSX file' diff --git a/koalixcrm/crm/contact/person.py b/koalixcrm/crm/contact/person.py index c7366a2b..01b1790c 100644 --- a/koalixcrm/crm/contact/person.py +++ b/koalixcrm/crm/contact/person.py @@ -5,7 +5,6 @@ from django.utils.translation import ugettext as _ from koalixcrm.crm.const.postaladdressprefix import * from koalixcrm.plugin import * -#from koalixcrm.crm.contact.contact import ContactPersonAssociation class Person(models.Model): prefix = models.CharField(max_length=1, choices=POSTALADDRESSPREFIX, verbose_name=_("Prefix"), blank=True, @@ -24,10 +23,3 @@ class Meta: app_label = "crm" verbose_name = _('Person') verbose_name_plural = _('People') - - - - - - - diff --git a/koalixcrm/crm/contact/supplier.py b/koalixcrm/crm/contact/supplier.py index 94881a22..c8552b3f 100644 --- a/koalixcrm/crm/contact/supplier.py +++ b/koalixcrm/crm/contact/supplier.py @@ -9,7 +9,6 @@ from koalixcrm.crm.contact.contact import ContactEmailAddress - class Supplier(Contact): offersShipmentToCustomers = models.BooleanField(verbose_name=_("Offers Shipment to Customer")) @@ -24,7 +23,6 @@ def __str__(self): class OptionSupplier(admin.ModelAdmin): list_display = ('id', 'name', 'offersShipmentToCustomers') - #ist_display_links = ('name',) fieldsets = (('', {'fields': ('name', 'offersShipmentToCustomers')}),) inlines = [ContactPostalAddress, ContactPhoneAddress, ContactEmailAddress] allow_add = True diff --git a/koalixcrm/crm/documents/activity.py b/koalixcrm/crm/documents/activity.py index b455606b..26823645 100644 --- a/koalixcrm/crm/documents/activity.py +++ b/koalixcrm/crm/documents/activity.py @@ -7,7 +7,6 @@ from koalixcrm.crm.const.status import * from koalixcrm.plugin import * - from django.utils import timezone class Call(models.Model): @@ -42,3 +41,24 @@ def queryset(self, request, queryset): else: return queryset +class OptionCall(admin.ModelAdmin): + list_display = ('id','description','date_due','purpose','get_contactname', 'status', 'is_call_overdue',) + list_filter = [CallOverdueFilter] + + def get_contactname(self, obj): + return obj.company.name + + get_contactname.short_description = _("Company") + + def is_call_overdue(self, obj): + return (obj.date_due < timezone.now() and obj.status not in ['F', 'S']) + + is_call_overdue.short_description = _("Is call overdue") + +class OptionVisit(admin.ModelAdmin): + list_display = ('id','description','date_due','purpose','get_contactname', 'status', 'ref_call',) + + def get_contactname(self, obj): + return obj.company.name + + get_contactname.short_description = _("Company") diff --git a/koalixcrm/crm/documents/contract.py b/koalixcrm/crm/documents/contract.py index 5ad973bb..0f90a719 100644 --- a/koalixcrm/crm/documents/contract.py +++ b/koalixcrm/crm/documents/contract.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import * from django.db import models from django.contrib import admin from django.utils.translation import ugettext as _ @@ -17,9 +16,11 @@ from koalixcrm.crm.documents.invoice import InlineInvoice from koalixcrm.crm.documents.quote import InlineQuote from koalixcrm.crm.exceptions import * -from koalixcrm.crm.views import create_new_document +from koalixcrm.djangoUserExtension.models import UserExtension import koalixcrm.crm.documents.calculations import koalixcrm.crm.documents.pdfexport +from rest_framework import serializers + class PostalAddressForContract(PostalAddress): purpose = models.CharField(verbose_name=_("Purpose"), max_length=1, choices=PURPOSESADDRESSINCONTRACT) @@ -123,19 +124,29 @@ def get_template_set(self, calling_model): 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_invoice(self) + invoice.create_from_reference(self) return invoice def create_quote(self): quote = Quote() - quote.create_quote(self) + quote.create_from_reference(self) return quote def create_purchase_order(self): purchase_order = PurchaseOrder() - purchase_order.create_purchase_order(self) + purchase_order.create_from_reference(self) return purchase_order def __str__(self): @@ -158,8 +169,9 @@ class OptionContract(admin.ModelAdmin): inlines.extend(pluginProcessor.getPluginAdditions("contractInlines")) def create_quote(self, request, queryset): + from koalixcrm.crm.views.newdocument import CreateNewDocumentView for obj in queryset: - response = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.quote.Quote, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -167,8 +179,9 @@ def create_quote(self, request, queryset): create_quote.short_description = _("Create Quote") def create_invoice(self, request, queryset): + from koalixcrm.crm.views.newdocument import CreateNewDocumentView for obj in queryset: - response = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.invoice.Invoice, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -176,8 +189,9 @@ def create_invoice(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.purchaseconfirmation.PurchaseConfirmation, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -185,8 +199,9 @@ def create_purchase_confirmation(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.deliverynote.DeliveryNote, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -194,8 +209,9 @@ def create_delivery_note(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.paymentreminder.PaymentReminder, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -203,8 +219,9 @@ def create_payment_reminder(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.purchaseorder.PurchaseOrder, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -212,7 +229,7 @@ def create_purchase_order(self, request, queryset): create_purchase_order.short_description = _("Create Purchase Order") def save_model(self, request, obj, form, change): - if (change == True): + if change: obj.last_modified_by = request.user else: obj.last_modified_by = request.user @@ -223,3 +240,9 @@ def save_model(self, request, obj, form, change): pluginProcessor = PluginProcessor() actions.extend(pluginProcessor.getPluginAdditions("contractActions")) + +class ContractJSONSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Contract + fields = ('id', + 'description',) \ No newline at end of file diff --git a/koalixcrm/crm/documents/invoice.py b/koalixcrm/crm/documents/invoice.py index e4199f70..f5149740 100644 --- a/koalixcrm/crm/documents/invoice.py +++ b/koalixcrm/crm/documents/invoice.py @@ -15,6 +15,7 @@ from koalixcrm.accounting.models import Account from django.contrib.admin import helpers from django.shortcuts import render +from django.contrib import messages from django.template.context_processors import csrf @@ -28,7 +29,7 @@ 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.defaultCustomerBillingCycle.time_to_payment_date) + 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() @@ -38,18 +39,18 @@ def create_from_reference(self, calling_model): def register_invoice_in_accounting(self, request): dict_prices = dict() dict_tax = dict() - current_valid_accounting_period = accounting.models.AccountingPeriod.getCurrentValidAccountingPeriod() + 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 intrest account in the accounting")) - for position in list(SalesDocumentPosition.objects.filter(contract=self.id)): - profit_account = position.product.accoutingProductCategorie.profitAccount - dict_prices[profit_account] = position.lastCalculatedPrice - dict_tax[profit_account] = position.lastCalculatedTax + 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=currentValidAccountingPeriod): + for booking in accounting.models.Booking.objects.filter(accountingPeriod=current_valid_accounting_period): if booking.bookingReference == self: raise InvoiceAlreadyRegistered() for profit_account, amount in iter(dict_prices.items()): @@ -64,16 +65,16 @@ def register_invoice_in_accounting(self, request): booking.lastmodifiedby = request.user booking.save() - def register_payment_in_accounting(self, request, amount, paymentaccount): - current_valid_accounting_period = accounting.models.AccountingPeriod.getCurrentValidAccountingPeriod() + 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 = paymentaccount + 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 = self.last_calculated_price + booking.amount = amount booking.staff = request.user booking.lastmodifiedby = request.user booking.save() @@ -99,9 +100,9 @@ class OptionInvoice(OptionSalesDocument): ) class PaymentForm(forms.Form): - paymentAmount = forms.DecimalField() + payment_amount = forms.DecimalField() _selected_action = forms.CharField(widget=forms.MultipleHiddenInput) - paymentAccount = forms.ModelChoiceField(Account.objects.filter(accountType="A")) + payment_account = forms.ModelChoiceField(Account.objects.filter(account_type="A")) def register_invoice_in_accounting(self, request, queryset): try: @@ -128,13 +129,13 @@ 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 registeration of payment in the accounting"), level=messages.ERROR) + 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['paymentAmount'] - payment_account = form.cleaned_data['paymentAccount'] + 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")) @@ -143,7 +144,7 @@ def register_payment_in_accounting(self, request, queryset): form = self.PaymentForm c = {'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'queryset': queryset, 'form': form} c.update(csrf(request)) - return render(request, 'crm/admin/registerPayment.html', c) + return render(request, 'crm/admin/register_payment.html', c) register_payment_in_accounting.short_description = _("Register Payment in Accounting") diff --git a/koalixcrm/crm/documents/pdfexport.py b/koalixcrm/crm/documents/pdfexport.py index 953be317..6b01d752 100644 --- a/koalixcrm/crm/documents/pdfexport.py +++ b/koalixcrm/crm/documents/pdfexport.py @@ -3,94 +3,90 @@ import os from subprocess import check_output from subprocess import STDOUT - from django.conf import settings -from django.contrib import auth from django.core import serializers from django.utils.translation import ugettext as _ from koalixcrm.crm.exceptions import * -from koalixcrm import djangoUserExtension from koalixcrm.crm.contact.contact import Contact -from koalixcrm.crm.contact.contact import PostalAddressForContact -from koalixcrm.crm.contact.phoneaddress import PhoneAddress -from koalixcrm.crm.contact.emailaddress import EmailAddress -from koalixcrm.crm.contact.postaladdress import PostalAddress -from koalixcrm.crm.product.currency import Currency -from koalixcrm.crm.product.unit import Unit -from koalixcrm.crm.product.product import Product from lxml import etree - from koalixcrm.crm.documents.salesdocumentposition import Position import koalixcrm.crm.documents.salesdocument +import koalixcrm.djangoUserExtension.models class PDFExport: @staticmethod - def extend_xml_with_root_element(file_with_serialized_xml): - xml = etree.parse(file_with_serialized_xml) - root_element = xml.getroot() - filebrowser_directory = etree.SubElement(root_element, "filebrowser_directory") - filebrowser_directory.text = settings.MEDIA_ROOT - xml.write(file_with_serialized_xml) + def find_element_in_xml(xml_string, find_pattern, find_value): + parser = etree.XMLParser(encoding='utf-8') + 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 add_positions(objects_to_serialize, position_class, object_to_create_pdf): - objects_to_serialize += 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_to_serialize += list(Position.objects.filter(id=position.id)) - objects_to_serialize += list(Product.objects.filter(id=position.product.id)) - objects_to_serialize += list(Unit.objects.filter(id=position.unit.id)) - return objects_to_serialize + def append_element_to_root(xml_string, name_of_element, value_of_element): + parser = etree.XMLParser(encoding='utf-8') + root_element = etree.fromstring(xml_string.encode('utf-8'), parser=parser) + new_element = etree.SubElement(root_element, name_of_element) + 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 create_list_of_objects_to_serialize(object_to_create_pdf): + def append_element_to_pattern(xml_string, find_pattern, name_of_element, value_of_element): + parser = etree.XMLParser(encoding='utf-8') + 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) + 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') + 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') - position_class = koalixcrm.crm.documents.salesdocumentposition.SalesDocumentPosition - objects_to_serialize = list(type(object_to_create_pdf).objects.filter(id=object_to_create_pdf.id)) - objects_to_serialize += list(koalixcrm.crm.documents.salesdocument.SalesDocument.objects.filter(id=object_to_create_pdf.id)) - if isinstance(object_to_create_pdf, koalixcrm.crm.documents.purchaseorder.PurchaseOrder): - objects_to_serialize += list(Contact.objects.filter(id=object_to_create_pdf.supplier.id)) - objects_to_serialize += list(PostalAddressForContact.objects.filter(person=object_to_create_pdf.supplier.id)) - for address in list(PostalAddressForContact.objects.filter(person=object_to_create_pdf.supplier.id)): - objects_to_serialize += list(PostalAddress.objects.filter(id=address.id)) + + @staticmethod + def create_list_of_objects_to_serialize(object_to_create_pdf): + if isinstance(object_to_create_pdf, koalixcrm.crm.documents.salesdocument.SalesDocument): + return object_to_create_pdf.serialize_to_xml() + elif isinstance(object_to_create_pdf, koalixcrm.accounting.models.AccountingPeriod): + return object_to_create_pdf.serialize_to_xml() else: - objects_to_serialize += list(Contact.objects.filter(id=object_to_create_pdf.customer.id)) - objects_to_serialize += list(PostalAddressForContact.objects.filter(person=object_to_create_pdf.customer.id)) - for address in list(PostalAddressForContact.objects.filter(person=object_to_create_pdf.customer.id)): - objects_to_serialize += list(PostalAddress.objects.filter(id=address.id)) - objects_to_serialize += list(koalixcrm.crm.documents.salesdocument.TextParagraphInSalesDocument.objects.filter(sales_document=object_to_create_pdf.id)) - objects_to_serialize += list(Currency.objects.filter(id=object_to_create_pdf.currency.id)) - objects_to_serialize = PDFExport.add_positions(objects_to_serialize, position_class, object_to_create_pdf) - objects_to_serialize += list(auth.models.User.objects.filter(id=object_to_create_pdf.staff.id)) - userExtension = djangoUserExtension.models.UserExtension.objects.filter(user=object_to_create_pdf.staff.id) - if len(userExtension) == 0: - raise UserExtensionMissing(_("During "+str(object_to_create_pdf)+" PDF Export")) - phone_address = djangoUserExtension.models.UserExtensionPhoneAddress.objects.filter( - userExtension=userExtension[0].id) - if len(phone_address) == 0: - raise UserExtensionPhoneAddressMissing(_("During "+type(object_to_create_pdf)+" PDF Export")) - email_address = djangoUserExtension.models.UserExtensionEmailAddress.objects.filter( - userExtension=userExtension[0].id) - if len(email_address) == 0: - raise UserExtensionEmailAddressMissing(_("During "+type(object_to_create_pdf)+" PDF Export")) - objects_to_serialize += list(userExtension) - objects_to_serialize += list(EmailAddress.objects.filter(id=email_address[0].id)) - objects_to_serialize += list(PhoneAddress.objects.filter(id=phone_address[0].id)) - template_set = djangoUserExtension.models.DocumentTemplate.objects.filter( - id=object_to_create_pdf.template_set.id) - if len(template_set) == 0: - raise TemplateSetMissing(_("During "+type(object_to_create_pdf)+" PDF Export")) - objects_to_serialize += list(template_set) - return objects_to_serialize + raise NoSerializationPatternFound(_("During "+str(object_to_create_pdf)+" PDF Export")) @staticmethod - def write_xml_file(objects_to_serialize, file_with_serialized_xml): - XMLSerializer = serializers.get_serializer("xml") - xml_serializer = XMLSerializer() - out = open(file_with_serialized_xml, "wb") - xml_serializer.serialize(objects_to_serialize, stream=out, indent=3) - out.close() + 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): @@ -101,23 +97,29 @@ def perform_xsl_transformation(file_with_serialized_xml, xsl_file, fop_config_fi '-pdf', file_output_pdf], stderr=STDOUT) @staticmethod - def create_pdf(object_to_create_pdf): + def create_pdf(object_to_create_pdf, template_set, printed_by): # define the files which are involved in pdf creation process - fop_config_file = object_to_create_pdf.get_fop_config_file() - xsl_file = object_to_create_pdf.get_xsl_file() + 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 to be serialized - objects_to_serialize = PDFExport.create_list_of_objects_to_serialize(object_to_create_pdf) + # list the sub-objects which have to be serialized + xml_string = PDFExport.create_list_of_objects_to_serialize(object_to_create_pdf) + 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) - # serialize the objects to xml-file - PDFExport.write_xml_file(objects_to_serialize, file_with_serialized_xml) + # extend the xml-string with required basic settings + xml_string = PDFExport.append_element_to_root(xml_string, "filebrowser_directory", settings.MEDIA_ROOT) - # extend the xml-file with required basic settings - PDFExport.extend_xml_with_root_element(file_with_serialized_xml) + # 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) diff --git a/koalixcrm/crm/documents/purchaseconfirmation.py b/koalixcrm/crm/documents/purchaseconfirmation.py index 22e77ca7..8cce4baa 100644 --- a/koalixcrm/crm/documents/purchaseconfirmation.py +++ b/koalixcrm/crm/documents/purchaseconfirmation.py @@ -33,4 +33,4 @@ class OptionPurchaseConfirmation(OptionSalesDocument): save_as = OptionSalesDocument.save_as inlines = OptionSalesDocument.inlines actions = ['create_invoice', 'create_quote', - 'create_delivery_note', 'create_purchase_order', 'create_pdf'] + 'create_delivery_note', 'create_purchase_order', 'create_', 'create_pdf'] diff --git a/koalixcrm/crm/documents/salesdocument.py b/koalixcrm/crm/documents/salesdocument.py index d5418d5b..371b305d 100644 --- a/koalixcrm/crm/documents/salesdocument.py +++ b/koalixcrm/crm/documents/salesdocument.py @@ -10,13 +10,11 @@ from koalixcrm.crm.contact.emailaddress import EmailAddress from koalixcrm.crm.contact.postaladdress import PostalAddress from koalixcrm.crm.documents.salesdocumentposition import SalesDocumentPosition, SalesDocumentInlinePosition -from koalixcrm.djangoUserExtension.models import TextParagraphInDocumentTemplate -from koalixcrm.crm.views import export_pdf -from koalixcrm.crm.views import create_new_document +from koalixcrm.djangoUserExtension.models import TextParagraphInDocumentTemplate, UserExtension from koalixcrm.crm.product.product import Product from koalixcrm.crm.exceptions import TemplateSetMissingInContract import koalixcrm.crm.documents.calculations -import koalixcrm.crm.documents.pdfexport +from koalixcrm.crm.documents.pdfexport import PDFExport class TextParagraphInSalesDocument(models.Model): @@ -70,6 +68,33 @@ class Meta: verbose_name = _('Sales Document') verbose_name_plural = _('Sales Documents') + def serialize_to_xml(self): + from koalixcrm.crm.contact.contact import PostalAddressForContact + from koalixcrm.crm.contact.postaladdress import PostalAddress + from koalixcrm.crm.product.currency import Currency + from koalixcrm.crm.contact.contact import Contact + from django.contrib import auth + objects = [self, ] + position_class = koalixcrm.crm.documents.salesdocumentposition.SalesDocumentPosition + objects += list(koalixcrm.crm.documents.salesdocument.SalesDocument.objects.filter(id=self.id)) + if isinstance(self, koalixcrm.crm.documents.purchaseorder.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, @@ -109,10 +134,10 @@ def attach_sales_document_positions(self, calling_model): new_position = SalesDocumentPosition() new_position.create_position(sales_document_position, self) - def create_pdf(self): + def create_pdf(self, template_set, printed_by): self.last_print_date = datetime.now() self.save() - return koalixcrm.crm.documents.pdfexport.PDFExport.create_pdf(self) + return koalixcrm.crm.documents.pdfexport.PDFExport.create_pdf(self, template_set, printed_by) def get_template_set(self): if self.template_set: @@ -120,11 +145,11 @@ def get_template_set(self): else: raise TemplateSetMissingInContract((_("Template Set missing in Sales Document" + str(self)))) - def get_fop_config_file(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): + def get_xsl_file(self, template_set): template_set = self.get_template_set() return template_set.get_xsl_file() @@ -268,8 +293,9 @@ def save_model(self, request, obj, form, change): obj.save() def create_quote(self, request, queryset): + from koalixcrm.crm.views.newdocument import CreateNewDocumentView for obj in queryset: - response = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.quote.Quote, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -277,8 +303,9 @@ def create_quote(self, request, queryset): create_quote.short_description = _("Create Quote") def create_invoice(self, request, queryset): + from koalixcrm.crm.views.newdocument import CreateNewDocumentView for obj in queryset: - response = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.invoice.Invoice, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -286,8 +313,9 @@ def create_invoice(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.purchaseconfirmation.PurchaseConfirmation, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -295,8 +323,9 @@ def create_purchase_confirmation(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.deliverynote.DeliveryNote, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -304,8 +333,9 @@ def create_delivery_note(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.paymentreminder.PaymentReminder, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -313,8 +343,9 @@ def create_payment_reminder(self, request, queryset): 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 = create_new_document(self, request, obj, + response = CreateNewDocumentView.create_new_document(self, request, obj, koalixcrm.crm.documents.purchaseorder.PurchaseOrder, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) return response @@ -322,8 +353,13 @@ def create_purchase_order(self, request, queryset): 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 = export_pdf(self, request, obj, ("/admin/crm/"+obj.__class__.__name__.lower()+"/")) + response = PDFExportView.export_pdf(self, + request, + obj, + ("/admin/crm/"+obj.__class__.__name__.lower()+"/"), + obj.template_set) return response create_pdf.short_description = _("Create PDF") diff --git a/koalixcrm/crm/documents/salesdocumentposition.py b/koalixcrm/crm/documents/salesdocumentposition.py index fe7f9961..57fb7534 100644 --- a/koalixcrm/crm/documents/salesdocumentposition.py +++ b/koalixcrm/crm/documents/salesdocumentposition.py @@ -42,6 +42,17 @@ class Meta: 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 import Product + 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(Product.objects.filter(id=position.product.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 diff --git a/koalixcrm/crm/documents/visit.py b/koalixcrm/crm/documents/visit.py deleted file mode 100644 index 6ff77fa9..00000000 --- a/koalixcrm/crm/documents/visit.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- 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.plugin import * - -class Visit(models.Model): - staff = models.ForeignKey('auth.User', limit_choices_to={'is_staff': True}, blank=True, verbose_name=_("Staff"), - related_name="db_relvisitstaff", 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_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_visitlstmodified") - - def __str__(self): - return _("Call") + " " + str(self.id) - -class OptionVisit(admin.ModelAdmin): - list_display = ('id',) \ No newline at end of file diff --git a/koalixcrm/crm/exceptions.py b/koalixcrm/crm/exceptions.py index 595f324f..71dfc192 100644 --- a/koalixcrm/crm/exceptions.py +++ b/koalixcrm/crm/exceptions.py @@ -49,6 +49,13 @@ 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 @@ -65,6 +72,14 @@ 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 diff --git a/koalixcrm/crm/forms.py b/koalixcrm/crm/forms.py deleted file mode 100644 index c69e1238..00000000 --- a/koalixcrm/crm/forms.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -from django.conf import settings -from django.core.files.storage import default_storage -from django.core.files.base import ContentFile -from django import forms -from django.forms import models -from koalixcrm.crm.contact.data_import import ContactImportData -from koalixcrm.crm.tasks import import_contact_data - -class ImportDataContactForm(models.ModelForm): - - class Meta: - model = ContactImportData - fields = ('contact_type', 'data_file',) - - def clean_data_file(self): - file_xlsx = self.cleaned_data['data_file'] - path = default_storage.save('tmp/'+file_xlsx.name, - ContentFile(file_xlsx.read())) - - tmp_file = os.path.join(settings.MEDIA_ROOT, path) - - contact_type = self.cleaned_data['contact_type'] - current_user = self.current_user.id - import_contact_data(tmp_file, contact_type, current_user) - - return file_xlsx \ No newline at end of file diff --git a/koalixcrm/crm/management/commands/importcontactdata.py b/koalixcrm/crm/management/commands/importcontactdata.py deleted file mode 100644 index c66d34f3..00000000 --- a/koalixcrm/crm/management/commands/importcontactdata.py +++ /dev/null @@ -1,453 +0,0 @@ -from optparse import make_option -from django.core.management.base import BaseCommand, CommandError -from django.db import transaction -import re - -from django.contrib.auth.models import User - -from koalixcrm.crm.contact.contact import Contact, PostalAddressForContact, PhoneAddressForContact, EmailAddressForContact, ContactPersonAssociation, CallForContact -from koalixcrm.crm.contact.person import Person -from koalixcrm.crm.contact.customer import Customer, SwitchboardForCustomer, AnalogPhoneForCustomer, DigitalPhoneForCustomer, InternetForCustomer, MobileForCustomer -from koalixcrm.crm.contact.supplier import Supplier -from koalixcrm.crm.contact.customerbillingcycle import CustomerBillingCycle -from koalixcrm.crm.contact.customergroup import CustomerGroup -from koalixcrm.crm.product.product import Product -from koalixcrm.crm.product.unit import Unit -from koalixcrm.crm.product.tax import Tax -from koalixcrm.crm.product.attribute import AttributeSet -from koalixcrm.crm.const.purpose import * -from koalixcrm.crm.const.contactimport import * - -import xlrd -from xlrd.sheet import ctype_text -from dateutil.parser import parse -import datetime - - -class Object(object): - pass - -class Command(BaseCommand): - help = 'Import Contact Data.' - - USERID = 0 - def set_user_id(self, user_id): - with open('log.txt', 'w') as logfile: - logfile.write("User id : %s" % user_id) - self.USERID = user_id - - def get_user_id(self): - return self.USERID - - def format_phone_number(self, value): - res = str(value).replace(" ", "") - res = re.sub(r"^[a-zA-Z]+(\d+)$", r"\1", res) - res = re.sub(r"^(\d+)[a-zA-Z]+$", r"\1", res) - res = re.sub(r"\.0$", r"", res) - return res - - def format_city_name(self, value): - res = str(value).strip() - res = re.sub(r"^(.*)\s?\([a-zA-Z]+\)$", r"\1", res) - return res.title() - - def format_state_name(self, value): - if str(value): - return str(value).strip().upper() - return value - - def format_int_string(self, value): - if str(value): - res = str(value).strip() - res = re.sub(r".0$", r"", res) - return res - - def prepare_product_args(self, product_type, sheet, row_num): - DEFAULT_RETURN = None, None - specific_product_args = None - - if product_type == PHONE_SYSTEM_P_TYPE: - check_field = sheet.cell(row_num, HASSWITCHBOARD).value - if not check_field: return DEFAULT_RETURN - model_name = str(sheet.cell(row_num, SWITCHBOARDMODEL).value) - - p_product_number = model_name[:30] if model_name else DEFAULT_PHONE_PRODUCT - p_title = model_name if model_name else DEFAULT_PHONE_PRODUCT_TITLE - p_description = None - p_default_unit, created = Unit.objects.get_or_create(short_name=DEFAULT_UNIT, description=DEFAULT_UNIT_DESCRIPTION) - p_tax, created = Tax.objects.get_or_create(name=DEFAULT_TAX) - p_attribute_set, created = AttributeSet.objects.get_or_create(name=DEFAULT_PHONE_ATTRIBUTE_SET) - supplier_name = str(sheet.cell(row_num, SWITCHBOARDPROVIDER).value) if str(sheet.cell(row_num, SWITCHBOARDPROVIDER).value) else DEFAULT_EMPTY_SUPPLIER - p_service_type = DEFAULT_PHONE_SERVICE_TYPE - try: - p_expire_date = parse(str(sheet.cell(row_num, PHONEEXPIREDATE).value)) - except ValueError: - p_expire_date = None - p_year = str(sheet.cell(row_num, YEAROFINSTALLATION).value).strip() - p_quantity = 1 - p_maintainer = sheet.cell(row_num, MAINTAINER).value - p_internal_lines = int(sheet.cell(row_num, INTERNALLINES).value) if sheet.cell(row_num, INTERNALLINES).value else 0 - p_external_lines = int(sheet.cell(row_num, EXTERNALLINES).value) if sheet.cell(row_num, EXTERNALLINES).value else 0 - - specific_product_args = { - 'internal_lines': p_internal_lines, - 'external_lines': p_external_lines - } - - elif product_type == ANALOG_PHONE_P_TYPE: - check_field = sheet.cell(row_num, ANALOGPHONES).value - if not check_field: return DEFAULT_RETURN - model_name = str(sheet.cell(row_num, PHONEMANUFACTURER1).value) - - p_product_number = model_name[:30] if model_name else DEFAULT_PHONE_PRODUCT - p_title = model_name if model_name else DEFAULT_PHONE_PRODUCT_TITLE - p_description = None - p_default_unit, created = Unit.objects.get_or_create(short_name=DEFAULT_UNIT, description=DEFAULT_UNIT_DESCRIPTION) - p_tax, created = Tax.objects.get_or_create(name=DEFAULT_TAX) - p_attribute_set, created = AttributeSet.objects.get_or_create(name=DEFAULT_PHONE_ATTRIBUTE_SET) - supplier_name = str(sheet.cell(row_num, PHONEMANUFACTURER1).value) if str(sheet.cell(row_num, PHONEMANUFACTURER1).value) else DEFAULT_EMPTY_SUPPLIER - p_service_type = DEFAULT_ANALOG_PHONE_SERVICE_TYPE - p_expire_date = None - p_year = None - p_quantity = ANALOGPHONES - p_maintainer = None - - elif product_type == DIGITAL_PHONE_P_TYPE: - check_field = sheet.cell(row_num, DIGITALPHONES).value - if not check_field: return DEFAULT_RETURN - model_name = str(sheet.cell(row_num, PHONEMANUFACTURER1).value) - - p_product_number = model_name[:30] if model_name else DEFAULT_PHONE_PRODUCT2 - p_title = model_name if model_name else DEFAULT_PHONE_PRODUCT_TITLE2 - p_description = None - p_default_unit, created = Unit.objects.get_or_create(short_name=DEFAULT_UNIT, description=DEFAULT_UNIT_DESCRIPTION) - p_tax, created = Tax.objects.get_or_create(name=DEFAULT_TAX) - p_attribute_set, created = AttributeSet.objects.get_or_create(name=DEFAULT_PHONE_ATTRIBUTE_SET2) - supplier_name = str(sheet.cell(row_num, PHONEMANUFACTURER1).value) if str(sheet.cell(row_num, PHONEMANUFACTURER1).value) else DEFAULT_EMPTY_SUPPLIER - p_service_type = DEFAULT_DIGITAL_PHONE_SERVICE_TYPE - p_expire_date = None - p_year = None - p_quantity = DIGITALPHONES - p_maintainer = None - - elif product_type == MOBILE_P_TYPE: - check_field = sheet.cell(row_num, MOBILEPROVIDER).value - if not check_field: return DEFAULT_RETURN - model_name = str(sheet.cell(row_num, PHONEMANUFACTURER2).value) - - p_product_number = model_name[:30] if model_name else DEFAULT_PHONE_PRODUCT2 - p_title = model_name if model_name else DEFAULT_PHONE_PRODUCT_TITLE2 - p_description = None - p_default_unit, created = Unit.objects.get_or_create(short_name=DEFAULT_UNIT, description=DEFAULT_UNIT_DESCRIPTION) - p_tax, created = Tax.objects.get_or_create(name=DEFAULT_TAX) - p_attribute_set, created = AttributeSet.objects.get_or_create(name=DEFAULT_PHONE_ATTRIBUTE_SET2) - supplier_name = str(sheet.cell(row_num, PHONEMANUFACTURER2).value) if str(sheet.cell(row_num, PHONEMANUFACTURER2).value) else DEFAULT_EMPTY_SUPPLIER - p_service_type = DEFAULT_MOBILE_SERVICE_TYPE - p_expire_date = None - p_year = None - p_quantity = None - p_maintainer = None - - elif product_type == INTERNET_P_TYPE: - check_field = sheet.cell(row_num, TYPEOFINTERNETCONNECTION).value - if not check_field: return DEFAULT_RETURN - model_name = str(sheet.cell(row_num, TYPEOFINTERNETCONNECTION).value) - - p_product_number = model_name[:30] if model_name else DEFAULT_INTERNET_PRODUCT - p_title = model_name if model_name else DEFAULT_INTERNET_PRODUCT_TITLE - p_description = None - p_default_unit, created = Unit.objects.get_or_create(short_name=DEFAULT_UNIT, description=DEFAULT_UNIT_DESCRIPTION) - p_tax, created = Tax.objects.get_or_create(name=DEFAULT_TAX) - p_attribute_set, created = AttributeSet.objects.get_or_create(name=DEFAULT_INTERNET_ATTRIBUTE_SET) - supplier_name = str(sheet.cell(row_num, INTERNETPROVIDER).value) if str(sheet.cell(row_num, INTERNETPROVIDER).value) else DEFAULT_EMPTY_SUPPLIER - p_service_type = DEFAULT_INTERNET_SERVICE_TYPE - try: - p_expire_date = parse(str(sheet.cell(row_num, INTERNETEXPIREDATE).value)) - except ValueError: - p_expire_date = None - p_year = str(sheet.cell(row_num, YEAROFINSTALLATION).value).strip(), - p_quantity = 1 - p_maintainer = None - - - product_args = { - 'product_number': p_product_number, - 'title': p_title, - 'description': p_description, - 'default_unit': p_default_unit, - 'tax': p_tax, - 'attribute_set': p_attribute_set - } - - relation_args = { - 'supplier': self.get_or_create_supplier_by_name(supplier_name), - 'service_type': p_service_type, - 'quantity': p_quantity, - 'expire_date': p_expire_date, - 'year': p_year, - 'maintainer': p_maintainer - } - if specific_product_args: - relation_args = { **relation_args, **specific_product_args } - - return product_args, relation_args - - - def add_product(self, product_type, contact, sheet, row_num): - product_args, relation_args = self.prepare_product_args(product_type, sheet, row_num) - if product_args is None: return - - product, created = Product.objects.update_or_create(**product_args) - if product_type == PHONE_SYSTEM_P_TYPE: - switchboard, created = SwitchboardForCustomer.objects.get_or_create( - customer=contact, - product=product, - ) - - updated = SwitchboardForCustomer.objects.filter(pk=switchboard.pk).update(**relation_args) - elif product_type == ANALOG_PHONE_P_TYPE: - analogphone, created = AnalogPhoneForCustomer.objects.get_or_create( - customer=contact, - product=product, - ) - updated = AnalogPhoneForCustomer.objects.filter(pk=analogphone.pk).update(**relation_args) - elif product_type == DIGITAL_PHONE_P_TYPE: - digitalphone, created = DigitalPhoneForCustomer.objects.get_or_create( - customer=contact, - product=product, - ) - updated = DigitalPhoneForCustomer.objects.filter(pk=digitalphone.pk).update(**relation_args) - elif product_type == MOBILE_P_TYPE: - mobilephone, created = MobileForCustomer.objects.get_or_create( - customer=contact, - product=product, - ) - updated = MobileForCustomer.objects.filter(pk=mobilephone.pk).update(**relation_args) - elif product_type == INTERNET_P_TYPE: - internet, created = InternetForCustomer.objects.get_or_create( - customer=contact, - product=product, - ) - updated = InternetForCustomer.objects.filter(pk=internet.pk).update(**relation_args) - - - def get_or_create_supplier_by_name(self, name): - with open('log.txt', 'w') as logfile: - logfile.write("get User id : %s" % self.get_user_id()) - supplier_args = { - 'name': name, - 'lastmodifiedby': User.objects.get(id=self.get_user_id()), - 'vatnumber': '0', - 'offersShipmentToCustomers': False - } - supplier, created = Supplier.objects.get_or_create(**supplier_args) - return supplier - - def add_arguments(self, parser): - parser.add_argument( - '-x', - '--excel-file', - dest='excel_file', - type=str, - help='Input Contacts Table as XLSX File.') - parser.add_argument( - '-t', - '--contact-type', - dest='contact_type', - type=str, - help='Customer/Supplier') - parser.add_argument( - '-u', - '--current-user', - dest='current_user', - type=str, - help='Current user.') - return parser - - def handle(self, **options): - excel_file = options.get('excel_file') - contact_type = options.get('contact_type') - user_id = options.get('current_user') - - self.set_user_id(user_id) - - if not excel_file or len(excel_file) == 0: - raise CommandError("Input Contacts '--excel_file' is mandatory") - - wb = xlrd.open_workbook(filename=excel_file) - - sheet = wb.sheet_by_index(0) - row_headers = sheet.row(0) - - col_num = 0 - for idx, cell_obj in enumerate(row_headers): - col_num += 1 - if col_num >= 0: - count = 0 - for row_num in range(1, sheet.nrows): - #check rating - if str(sheet.cell(row_num, RATING).value) != SKIP_ROW_VALUE: - #Create generic object for Contact - contact = None - person = None - c = Object() - c.name = str(sheet.cell(row_num, COMPANY).value).strip() - c.vatnumber = str(sheet.cell(row_num, VAT).value).strip() - c.lastmodification = datetime.datetime.now() - c.lastmodifiedby = User.objects.get(id=user_id) - - #Initialize dictionaries for related Classes - pa = {} - pha_mobile = {} - pha_1 = {} - pha_2 = {} - pha_fax = {} - ea = {} - call = {} - - _pa_prename = sheet.cell(row_num, LASTNAME).value - pa['prefix'] = sheet.cell(row_num, PERSONPREFIX).value - pa['name'] = sheet.cell(row_num, NAME).value - pa['addressline1'] = str(sheet.cell(row_num, ADDRESS).value).strip() - pa['addressline2'] = self.format_int_string(sheet.cell(row_num, ADDRESS_NO).value) - #pa['addressline3'] = sheet.cell(row_num, 0).value - #pa['addressline4'] = sheet.cell(row_num, 0).value - pa['zipcode'] = int(sheet.cell(row_num, ZIPCODE).value) if (sheet.cell(row_num, ZIPCODE).value) else 0 - pa['town'] = self.format_city_name(sheet.cell(row_num, CITY).value) - pa['state'] = self.format_state_name(sheet.cell(row_num, STATE).value) - pa['country'] = sheet.cell(row_num, COUNTRY).value - - _mobile = str(sheet.cell(row_num, MOBILE1).value) - _phone_1 = str(sheet.cell(row_num, PHONE1).value) - _phone_2 = str(sheet.cell(row_num, PHONE2).value) - _fax = str(sheet.cell(row_num, FAX).value) - pha_mobile['phone'] = self.format_phone_number(_mobile)[:20] - pha_1['phone'] = self.format_phone_number(_phone_1)[:20] - pha_2['phone'] = self.format_phone_number(_phone_2)[:20] - pha_fax['phone'] = self.format_phone_number(_fax)[:20] - - #create record for Person if prename and email found - prename = str(sheet.cell(row_num, LASTNAME).value) - email = str(sheet.cell(row_num, PERSONEMAIL).value) - if prename and email: - person, created = Person.objects.get_or_create(email=email) - person.prefix = sheet.cell(row_num, PERSONPREFIX).value - person.name = sheet.cell(row_num, NAME).value - person.prename = prename - person.phone = self.format_phone_number(_mobile)[:20] - person.role = sheet.cell(row_num, ROLE).value - with transaction.atomic(): - person.save() - - ea['email'] = sheet.cell(row_num, COMPANYEMAIL).value - - #Determine Contact Type (Customer or Supplier) - if contact_type == 'C': - isLead = str(sheet.cell(row_num, ISLEAD).value) - c.isLead = isLead == '1' - c.defaultCustomerBillingCycle = CustomerBillingCycle.objects.all()[:1].get() - customer_group = str(sheet.cell(row_num, TYPEOFACTIVITY).value).strip() - - try: - customer = Customer.objects.get(name__iexact=c.name.lower()) - for key, value in vars(c).items(): - setattr(customer, key, value) - with transaction.atomic(): - customer.save() - except Customer.DoesNotExist: - #set date of creation only if new Entry - #raise NameError("customer non trovato: {}".format(c.name)) - c.dateofcreation = datetime.datetime.now() - customer = Customer(**vars(c)) - with transaction.atomic(): - customer.save() - if customer_group != '': - group, created = CustomerGroup.objects.get_or_create(name=customer_group) - customer.ismemberof.add(group) - contact = customer - - #region products - #Switchboard - self.add_product(PHONE_SYSTEM_P_TYPE, contact, sheet, row_num) - - #Analog phones - self.add_product(ANALOG_PHONE_P_TYPE, contact, sheet, row_num) - - #Digital phones - self.add_product(DIGITAL_PHONE_P_TYPE, contact, sheet, row_num) - - #Internet provider - self.add_product(INTERNET_P_TYPE, contact, sheet, row_num) - - #Phone provider - pp = {} - - #Mobile provider - mb = {} - self.add_product(MOBILE_P_TYPE, contact, sheet, row_num) - - #endregion - - #Set objects from inlines - if pa['zipcode'] > 0: - pa['purpose'] = 'O' - pa['company'] = contact - postal_address, created = PostalAddressForContact.objects.get_or_create(company=contact, purpose='O', prename=_pa_prename) - updated = PostalAddressForContact.objects.filter(pk=postal_address.pk).update(**pa) - - if pha_mobile['phone']: - pha_mobile['purpose'] = 'B' - pha_mobile['company'] = contact - phone_address_mobile = PhoneAddressForContact.objects.update_or_create(**pha_mobile) - - if pha_1['phone']: - pha_1['purpose'] = 'O' - pha_1['company'] = contact - phone_address_1 = PhoneAddressForContact.objects.update_or_create(**pha_1) - - if pha_2['phone']: - pha_2['purpose'] = 'O' - pha_2['company'] = contact - phone_address_2 = PhoneAddressForContact.objects.update_or_create(**pha_2) - - if pha_fax['phone']: - pha_fax['purpose'] = 'F' - pha_fax['company'] = contact - phone_address_fax = PhoneAddressForContact.objects.update_or_create(**pha_fax) - - if ea['email']: - ea['purpose'] = 'O' - ea['company'] = contact - email_address = EmailAddressForContact.objects.update_or_create(**ea) - - #create a call to report existing notes on Lead - if str(sheet.cell(row_num, MEETINGNOTES).value): - call['description'] = str(sheet.cell(row_num, MEETINGNOTES).value) - call['status'] = 'D' - call['purpose'] = 'S' - call['company'] = contact - call_for_contact = CallForContact.objects.update_or_create(**call) - - if person: - ContactPersonAssociation.objects.update_or_create(contact=contact, person=person) - - count += 1 - print('imported contact {}'.format(contact.name)) - - elif contact_type == 'S': - c.offersShipmentToCustomers = False - try: - supplier = Supplier.objects.get(name=c.name) - for key, value in vars(c).items(): - setattr(supplier, key, value) - supplier.save() - except Supplier.DoesNotExist: - supplier = Supplier(**vars(c)) - supplier.save() - contact = supplier - else: - raise CommandError("Cannot determine contact type") - - return '{}'.format(count) - - \ No newline at end of file diff --git a/koalixcrm/crm/management/commands/koalixcrm_install_defaulttemplates.py b/koalixcrm/crm/management/commands/koalixcrm_install_defaulttemplates.py index c1282d25..eba26673 100644 --- a/koalixcrm/crm/management/commands/koalixcrm_install_defaulttemplates.py +++ b/koalixcrm/crm/management/commands/koalixcrm_install_defaulttemplates.py @@ -22,13 +22,13 @@ class Command(BaseCommand): @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, 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 + f = None; try: f = open(file_path,'r') except (FileNotFoundError) as e: @@ -40,20 +40,17 @@ def path_of_default_template_file(language, file_name): return file_path @staticmethod - def store_xsl_file(xsl_file_path, file_name): - if file_name[:-4] == 'invoice': - xsl_file = djangoUserExtension.models.InvoiceTemplate() - xsl_file.title = path.basename(xsl_file_path) - xsl_file.xslfile = FileObject(xsl_file_path) - xsl_file.save() - return xsl_file - return - + 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.invoice_template = Command.store_default_template_xsl_file("en", "invoice.xsl") + 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") diff --git a/koalixcrm/crm/migrations/0025_auto_20180220_1640.py b/koalixcrm/crm/migrations/0025_auto_20180220_1640.py deleted file mode 100644 index 3259e9cd..00000000 --- a/koalixcrm/crm/migrations/0025_auto_20180220_1640.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-20 16:40 -from __future__ import unicode_literals - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0024_auto_20180122_2121'), - ] - - operations = [ - migrations.AlterField( - model_name='position', - name='position_number', - field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)], verbose_name='Position Number'), - ), - ] diff --git a/koalixcrm/crm/migrations/0025_auto_20180413_1937.py b/koalixcrm/crm/migrations/0025_auto_20180413_1937.py new file mode 100644 index 00000000..df512aa9 --- /dev/null +++ b/koalixcrm/crm/migrations/0025_auto_20180413_1937.py @@ -0,0 +1,126 @@ +# -*- 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'), + ), + ] diff --git a/koalixcrm/crm/migrations/0026_auto_20180507_1957.py b/koalixcrm/crm/migrations/0026_auto_20180507_1957.py new file mode 100644 index 00000000..cd49e7ce --- /dev/null +++ b/koalixcrm/crm/migrations/0026_auto_20180507_1957.py @@ -0,0 +1,40 @@ +# -*- 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', + ), + ] diff --git a/koalixcrm/crm/migrations/0026_call.py b/koalixcrm/crm/migrations/0026_call.py deleted file mode 100644 index 8a7a32a6..00000000 --- a/koalixcrm/crm/migrations/0026_call.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-20 17:53 -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', '0004_auto_20171210_2126'), - ('crm', '0025_auto_20180220_1640'), - ] - - 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')), - ('last_modification', models.DateTimeField(auto_now=True, verbose_name='Last modified')), - ('default_customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Customer', verbose_name='Default Customer')), - ('default_supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Supplier', verbose_name='Default Supplier')), - ('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_calllstmodified', to=settings.AUTH_USER_MODEL, verbose_name='Last modified by')), - ('staff', 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')), - ], - ), - ] diff --git a/koalixcrm/crm/migrations/0027_auto_20180220_2146.py b/koalixcrm/crm/migrations/0027_auto_20180220_2146.py deleted file mode 100644 index 928ee11e..00000000 --- a/koalixcrm/crm/migrations/0027_auto_20180220_2146.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-20 21:46 -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 = [ - ('djangoUserExtension', '0004_auto_20171210_2126'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('crm', '0026_call'), - ] - - operations = [ - migrations.CreateModel( - name='Visit', - 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')), - ('last_modification', models.DateTimeField(auto_now=True, verbose_name='Last modified')), - ('default_customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Customer', verbose_name='Default Customer')), - ('default_supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Supplier', verbose_name='Default Supplier')), - ('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_visitlstmodified', to=settings.AUTH_USER_MODEL, verbose_name='Last modified by')), - ('staff', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='db_relvisitstaff', to=settings.AUTH_USER_MODEL, verbose_name='Staff')), - ], - ), - migrations.AddField( - model_name='contact', - name='town', - field=models.CharField(max_length=300, null=True, verbose_name='City'), - ), - ] diff --git a/koalixcrm/crm/migrations/0028_auto_20180220_2249.py b/koalixcrm/crm/migrations/0028_auto_20180220_2249.py deleted file mode 100644 index 651b3f63..00000000 --- a/koalixcrm/crm/migrations/0028_auto_20180220_2249.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-20 22:49 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0027_auto_20180220_2146'), - ] - - operations = [ - migrations.AddField( - model_name='contact', - name='addressline1', - field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Addressline 1'), - ), - migrations.AddField( - model_name='contact', - name='addressline2', - field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Addressline 2'), - ), - migrations.AddField( - model_name='contact', - name='addressline3', - field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Addressline 3'), - ), - migrations.AddField( - model_name='contact', - name='addressline4', - field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Addressline 4'), - ), - migrations.AddField( - model_name='contact', - name='country', - field=models.CharField(blank=True, choices=[('AF', 'Afghanistan'), ('AX', 'Aland Islands'), ('AL', 'Albania'), ('DZ', 'Algeria'), ('AS', 'American Samoa'), ('AD', 'Andorra'), ('AO', 'Angola'), ('AI', 'Anguilla'), ('AQ', 'Antarctica'), ('AG', 'Antigua and Barbuda'), ('AR', 'Argentina'), ('AM', 'Armenia'), ('AW', 'Aruba'), ('AU', 'Australia'), ('AT', 'Austria'), ('AZ', 'Azerbaijan'), ('BS', 'the Bahamas'), ('BH', 'Bahrain'), ('BD', 'Bangladesh'), ('BB', 'Barbados'), ('BY', 'Belarus'), ('BE', 'Belgium'), ('BZ', 'Belize'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BT', 'Bhutan'), ('BO', 'Bolivia'), ('BA', 'Bosnia and Herzegovina'), ('BW', 'Botswana'), ('BV', 'Bouvet Island'), ('BR', 'Brazil'), ('IO', 'British Indian Ocean Territory'), ('BN', 'Brunei Darussalam'), ('BG', 'Bulgaria'), ('BF', 'Burkina Faso'), ('BI', 'Burundi'), ('KH', 'Cambodia'), ('CM', 'Cameroon'), ('CA', 'Canada'), ('CV', 'Cape Verde'), ('KY', 'Cayman Islands'), ('CF', 'Central African Republic'), ('TD', 'Chad'), ('CL', 'Chile'), ('CN', 'China'), ('CX', 'Christmas Island'), ('CC', 'Cocos (Keeling) Islands'), ('CO', 'Colombia'), ('KM', 'Comoros'), ('CG', 'Congo'), ('CD', 'Democratic Republic of the Congo'), ('CK', 'Cook Islands'), ('CR', 'Costa Rica'), ('CI', "Cote d'Ivoire"), ('HR', 'Croatia'), ('CU', 'Cuba'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DK', 'Denmark'), ('DJ', 'Djibouti'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('EC', 'Ecuador'), ('EG', 'Egypt'), ('SV', 'El Salvador'), ('GQ', 'Equatorial Guinea'), ('ER', 'Eritrea'), ('EE', 'Estonia'), ('ET', 'Ethiopia'), ('FK', 'Falkland Islands (Malvinas)'), ('FO', 'Faroe Islands'), ('FJ', 'Fiji'), ('FI', 'Finland'), ('FR', 'France'), ('GF', 'French Guiana'), ('PF', 'French Polynesia'), ('TF', 'French Southern and Antarctic Lands'), ('GA', 'Gabon'), ('GM', 'Gambia'), ('GE', 'Georgia'), ('DE', 'Germany'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GR', 'Greece'), ('GL', 'Greenland'), ('GD', 'Grenada'), ('GP', 'Guadeloupe'), ('GU', 'Guam'), ('GT', 'Guatemala'), ('GG', 'Guernsey'), ('GN', 'Guinea'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HT', 'Haiti'), ('HM', 'Heard Island and McDonald Islands'), ('VA', 'Vatican City Holy See'), ('HN', 'Honduras'), ('HK', 'Hong Kong'), ('HU', 'Hungary'), ('IS', 'Iceland'), ('IN', 'India'), ('ID', 'Indonesia'), ('IR', 'Iran'), ('IQ', 'Iraq'), ('IE', 'Ireland'), ('IM', 'Isle of Man'), ('IL', 'Israel'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JP', 'Japan'), ('JE', 'Jersey'), ('JO', 'Jordan'), ('KZ', 'Kazakhstan'), ('KE', 'Kenya'), ('KI', 'Kiribati'), ('KP', 'North Korea'), ('KR', 'South Korea'), ('KW', 'Kuwait'), ('KG', 'Kyrgyzstan'), ('LA', 'Laos Lao'), ('LV', 'Latvia'), ('LB', 'Lebanon'), ('LS', 'Lesotho'), ('LR', 'Liberia'), ('LY', 'Libya Libyan Arab Jamahiriya'), ('LI', 'Liechtenstein'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('MO', 'Macau Macao'), ('MK', 'Macedonia'), ('MG', 'Madagascar'), ('MW', 'Malawi'), ('MY', 'Malaysia'), ('MV', 'Maldives'), ('ML', 'Mali'), ('MT', 'Malta'), ('MH', 'Marshall Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MU', 'Mauritius'), ('YT', 'Mayotte'), ('MX', 'Mexico'), ('FM', 'Micronesia'), ('MD', 'Moldova'), ('MC', 'Monaco'), ('MN', 'Mongolia'), ('ME', 'Montenegro'), ('MS', 'Montserrat'), ('MA', 'Morocco'), ('MZ', 'Mozambique'), ('MM', 'Myanmar'), ('NA', 'Namibia'), ('NR', 'Nauru'), ('NP', 'Nepal'), ('NL', 'Netherlands'), ('AN', 'Netherlands Antilles'), ('NC', 'New Caledonia'), ('NZ', 'New Zealand'), ('NI', 'Nicaragua'), ('NE', 'Niger'), ('NG', 'Nigeria'), ('NU', 'Niue'), ('NF', 'Norfolk Island Norfolk Island'), ('MP', 'Northern Mariana Islands'), ('NO', 'Norway'), ('OM', 'Oman'), ('PK', 'Pakistan'), ('PW', 'Palau'), ('PS', 'Palestinian Territory'), ('PA', 'Panama'), ('PG', 'Papua New Guinea'), ('PY', 'Paraguay'), ('PE', 'Peru'), ('PH', 'Philippines'), ('PN', 'Pitcairn Islands'), ('PL', 'Poland'), ('PT', 'Portugal'), ('PR', 'Puerto Rico'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russia'), ('RW', 'Rwanda'), ('SH', 'Saint Helena'), ('KN', 'Saint Kitts and Nevis'), ('LC', 'Saint Lucia'), ('PM', 'Saint Pierre and Miquelon'), ('VC', 'Saint Vincent and the Grenadines'), ('WS', 'Samoa'), ('SM', 'San Marino'), ('ST', 'Sao Tome and Principe'), ('SA', 'Saudi Arabia'), ('SN', 'Senegal'), ('RS', 'Serbia'), ('SC', 'Seychelles'), ('SL', 'Sierra Leone'), ('SG', 'Singapore'), ('SK', 'Slovakia'), ('SI', 'Slovenia'), ('SB', 'Solomon Islands'), ('SO', 'Somalia'), ('ZA', 'South Africa'), ('GS', 'South Georgia and the South Sandwich Islands'), ('ES', 'Spain'), ('LK', 'Sri Lanka'), ('SD', 'Sudan'), ('SR', 'Suriname'), ('SJ', 'Svalbard and Jan Mayen'), ('SZ', 'Swaziland'), ('SE', 'Sweden'), ('CH', 'Switzerland'), ('SY', 'Syria'), ('TW', 'Taiwan'), ('TJ', 'Tajikistan'), ('TZ', 'Tanzania'), ('TH', 'Thailand'), ('TL', 'East Timor'), ('TG', 'Togo'), ('TK', 'Tokelau'), ('TO', 'Tonga'), ('TT', 'Trinidad and Tobago'), ('TN', 'Tunisia'), ('TR', 'Turkey'), ('TM', 'Turkmenistan'), ('TC', 'Turks and Caicos Islands'), ('TV', 'Tuvalu'), ('UG', 'Uganda'), ('UA', 'Ukraine'), ('AE', 'United Arab Emirates'), ('GB', 'United Kingdom'), ('US', 'United States'), ('UM', 'United States Minor Outlying Islands'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VU', 'Vanuatu'), ('VE', 'Venezuela'), ('VN', 'Vietnam Viet Nam'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('WF', 'Wallis and Futuna'), ('EH', 'Western Sahara'), ('YE', 'Yemen'), ('ZM', 'Zambia'), ('ZW', 'Zimbabwe')], max_length=2, null=True, verbose_name='Country'), - ), - migrations.AddField( - model_name='contact', - name='state', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='State'), - ), - migrations.AddField( - model_name='contact', - name='zipcode', - field=models.IntegerField(blank=True, null=True, verbose_name='Zipcode'), - ), - migrations.AlterField( - model_name='contact', - name='town', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='City'), - ), - ] diff --git a/koalixcrm/crm/migrations/0029_auto_20180220_2348.py b/koalixcrm/crm/migrations/0029_auto_20180220_2348.py deleted file mode 100644 index 4ada4758..00000000 --- a/koalixcrm/crm/migrations/0029_auto_20180220_2348.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-20 23:48 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0028_auto_20180220_2249'), - ] - - operations = [ - migrations.CreateModel( - name='CustomerPersonAssociation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='person_association', to='crm.Customer')), - ], - ), - 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')), - ('customers', models.ManyToManyField(through='crm.CustomerPersonAssociation', to='crm.Customer')), - ], - options={ - 'verbose_name': 'Person', - 'verbose_name_plural': 'People', - }, - ), - migrations.AddField( - model_name='customerpersonassociation', - name='person', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customer_association', to='crm.Person'), - ), - migrations.AddField( - model_name='customer', - name='people', - field=models.ManyToManyField(through='crm.CustomerPersonAssociation', to='crm.Person'), - ), - ] diff --git a/koalixcrm/crm/migrations/0030_auto_20180221_1354.py b/koalixcrm/crm/migrations/0030_auto_20180221_1354.py deleted file mode 100644 index c53d408e..00000000 --- a/koalixcrm/crm/migrations/0030_auto_20180221_1354.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 13:54 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0029_auto_20180220_2348'), - ] - - operations = [ - migrations.AlterField( - model_name='customer', - name='people', - field=models.ManyToManyField(blank=True, through='crm.CustomerPersonAssociation', to='crm.Person'), - ), - migrations.AlterField( - model_name='person', - name='customers', - field=models.ManyToManyField(blank=True, through='crm.CustomerPersonAssociation', to='crm.Customer'), - ), - ] diff --git a/koalixcrm/crm/migrations/0031_auto_20180221_1402.py b/koalixcrm/crm/migrations/0031_auto_20180221_1402.py deleted file mode 100644 index d34c3638..00000000 --- a/koalixcrm/crm/migrations/0031_auto_20180221_1402.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 14:02 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0030_auto_20180221_1354'), - ] - - operations = [ - migrations.RemoveField( - model_name='customerpersonassociation', - name='customer', - ), - migrations.RemoveField( - model_name='customerpersonassociation', - name='person', - ), - migrations.RemoveField( - model_name='customer', - name='people', - ), - migrations.RemoveField( - model_name='person', - name='customers', - ), - migrations.DeleteModel( - name='CustomerPersonAssociation', - ), - ] diff --git a/koalixcrm/crm/migrations/0032_customer_people.py b/koalixcrm/crm/migrations/0032_customer_people.py deleted file mode 100644 index 4066ac2c..00000000 --- a/koalixcrm/crm/migrations/0032_customer_people.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 14:03 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0031_auto_20180221_1402'), - ] - - operations = [ - migrations.AddField( - model_name='customer', - name='people', - field=models.ManyToManyField(blank=True, to='crm.Person', verbose_name='Has contact'), - ), - ] diff --git a/koalixcrm/crm/migrations/0033_auto_20180221_1539.py b/koalixcrm/crm/migrations/0033_auto_20180221_1539.py deleted file mode 100644 index 82a14d17..00000000 --- a/koalixcrm/crm/migrations/0033_auto_20180221_1539.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 15:39 -from __future__ import unicode_literals - -import datetime -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0032_customer_people'), - ] - - operations = [ - 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')), - ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Contact')), - ], - options={ - 'verbose_name': 'Call For Contact', - 'verbose_name_plural': 'Call For Contact', - }, - bases=('crm.call',), - ), - migrations.RenameField( - model_name='call', - old_name='default_customer', - new_name='customer', - ), - migrations.RenameField( - model_name='call', - old_name='default_supplier', - new_name='supplier', - ), - migrations.RenameField( - model_name='call', - old_name='default_template_set', - new_name='template_set', - ), - migrations.AddField( - model_name='call', - name='date_due', - field=models.DateTimeField(blank=True, default=datetime.datetime.now, verbose_name='Date due'), - ), - migrations.AddField( - model_name='call', - name='status', - field=models.CharField(choices=[('P', 'Planned'), ('D', 'Delayed'), ('R', 'ToRecall'), ('A', 'Failed')], default='P', max_length=1, verbose_name='Status'), - ), - ] diff --git a/koalixcrm/crm/migrations/0034_auto_20180221_1555.py b/koalixcrm/crm/migrations/0034_auto_20180221_1555.py deleted file mode 100644 index d7b01e53..00000000 --- a/koalixcrm/crm/migrations/0034_auto_20180221_1555.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 15:55 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0033_auto_20180221_1539'), - ] - - operations = [ - migrations.RemoveField( - model_name='call', - name='customer', - ), - migrations.RemoveField( - model_name='call', - name='supplier', - ), - ] diff --git a/koalixcrm/crm/migrations/0035_remove_call_template_set.py b/koalixcrm/crm/migrations/0035_remove_call_template_set.py deleted file mode 100644 index 4a07f3e9..00000000 --- a/koalixcrm/crm/migrations/0035_remove_call_template_set.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 16:04 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0034_auto_20180221_1555'), - ] - - operations = [ - migrations.RemoveField( - model_name='call', - name='template_set', - ), - ] diff --git a/koalixcrm/crm/migrations/0036_auto_20180221_1612.py b/koalixcrm/crm/migrations/0036_auto_20180221_1612.py deleted file mode 100644 index 2f1c518f..00000000 --- a/koalixcrm/crm/migrations/0036_auto_20180221_1612.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 16:12 -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', '0035_remove_call_template_set'), - ] - - operations = [ - migrations.AlterField( - 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'), - ), - ] diff --git a/koalixcrm/crm/migrations/0037_customer_no_phones.py b/koalixcrm/crm/migrations/0037_customer_no_phones.py deleted file mode 100644 index 733c12f3..00000000 --- a/koalixcrm/crm/migrations/0037_customer_no_phones.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 18:16 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0036_auto_20180221_1612'), - ] - - operations = [ - migrations.AddField( - model_name='customer', - name='no_phones', - field=models.IntegerField(blank=True, null=True, verbose_name='No Phones'), - ), - ] diff --git a/koalixcrm/crm/migrations/0038_person_role.py b/koalixcrm/crm/migrations/0038_person_role.py deleted file mode 100644 index 5687ad5b..00000000 --- a/koalixcrm/crm/migrations/0038_person_role.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 18:23 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0037_customer_no_phones'), - ] - - operations = [ - migrations.AddField( - model_name='person', - name='role', - field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Role'), - ), - ] diff --git a/koalixcrm/crm/migrations/0039_auto_20180221_1916.py b/koalixcrm/crm/migrations/0039_auto_20180221_1916.py deleted file mode 100644 index 177a9e03..00000000 --- a/koalixcrm/crm/migrations/0039_auto_20180221_1916.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 19:16 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0038_person_role'), - ] - - operations = [ - migrations.RenameField( - model_name='callforcontact', - old_name='person', - new_name='company', - ), - migrations.RenameField( - model_name='emailaddressforcontact', - old_name='person', - new_name='company', - ), - migrations.RenameField( - model_name='phoneaddressforcontact', - old_name='person', - new_name='company', - ), - migrations.RenameField( - model_name='postaladdressforcontact', - old_name='person', - new_name='company', - ), - ] diff --git a/koalixcrm/crm/migrations/0040_callforcontact_cperson.py b/koalixcrm/crm/migrations/0040_callforcontact_cperson.py deleted file mode 100644 index c7f9bb9c..00000000 --- a/koalixcrm/crm/migrations/0040_callforcontact_cperson.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 19:58 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0039_auto_20180221_1916'), - ] - - operations = [ - migrations.AddField( - model_name='callforcontact', - name='cperson', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Person'), - ), - ] diff --git a/koalixcrm/crm/migrations/0041_auto_20180221_2048.py b/koalixcrm/crm/migrations/0041_auto_20180221_2048.py deleted file mode 100644 index 1af27d78..00000000 --- a/koalixcrm/crm/migrations/0041_auto_20180221_2048.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 20:48 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0040_callforcontact_cperson'), - ] - - operations = [ - migrations.AddField( - model_name='person', - name='customers', - field=models.ManyToManyField(blank=True, to='crm.Customer', verbose_name='Works at'), - ), - migrations.AlterField( - model_name='callforcontact', - name='cperson', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Person', verbose_name='Person'), - ), - ] diff --git a/koalixcrm/crm/migrations/0042_auto_20180221_2056.py b/koalixcrm/crm/migrations/0042_auto_20180221_2056.py deleted file mode 100644 index 79ffa59b..00000000 --- a/koalixcrm/crm/migrations/0042_auto_20180221_2056.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 20:56 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0041_auto_20180221_2048'), - ] - - operations = [ - migrations.CreateModel( - name='CustomerPersonAssociation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.RemoveField( - model_name='customer', - name='people', - ), - migrations.RemoveField( - model_name='person', - name='customers', - ), - migrations.AddField( - model_name='customerpersonassociation', - name='customer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='person_association', to='crm.Customer'), - ), - migrations.AddField( - model_name='customerpersonassociation', - name='person', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customer_association', to='crm.Person'), - ), - ] diff --git a/koalixcrm/crm/migrations/0043_auto_20180221_2057.py b/koalixcrm/crm/migrations/0043_auto_20180221_2057.py deleted file mode 100644 index 855b243f..00000000 --- a/koalixcrm/crm/migrations/0043_auto_20180221_2057.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 20:57 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0042_auto_20180221_2056'), - ] - - operations = [ - migrations.AddField( - model_name='customer', - name='people', - field=models.ManyToManyField(blank=True, through='crm.CustomerPersonAssociation', to='crm.Person', verbose_name='Has contact'), - ), - migrations.AddField( - model_name='person', - name='customers', - field=models.ManyToManyField(blank=True, through='crm.CustomerPersonAssociation', to='crm.Customer', verbose_name='Works at'), - ), - ] diff --git a/koalixcrm/crm/migrations/0044_remove_customer_people.py b/koalixcrm/crm/migrations/0044_remove_customer_people.py deleted file mode 100644 index efd6d74b..00000000 --- a/koalixcrm/crm/migrations/0044_remove_customer_people.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 21:08 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0043_auto_20180221_2057'), - ] - - operations = [ - migrations.RemoveField( - model_name='customer', - name='people', - ), - ] diff --git a/koalixcrm/crm/migrations/0045_remove_person_customers.py b/koalixcrm/crm/migrations/0045_remove_person_customers.py deleted file mode 100644 index 3248328b..00000000 --- a/koalixcrm/crm/migrations/0045_remove_person_customers.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 21:10 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0044_remove_customer_people'), - ] - - operations = [ - migrations.RemoveField( - model_name='person', - name='customers', - ), - ] diff --git a/koalixcrm/crm/migrations/0046_auto_20180221_2112.py b/koalixcrm/crm/migrations/0046_auto_20180221_2112.py deleted file mode 100644 index f7a1e75a..00000000 --- a/koalixcrm/crm/migrations/0046_auto_20180221_2112.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-21 21:12 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0045_remove_person_customers'), - ] - - operations = [ - migrations.CreateModel( - name='ContactPersonAssociation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='person_association', to='crm.Contact')), - ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contact_association', to='crm.Person')), - ], - ), - migrations.RemoveField( - model_name='customerpersonassociation', - name='customer', - ), - migrations.RemoveField( - model_name='customerpersonassociation', - name='person', - ), - migrations.DeleteModel( - name='CustomerPersonAssociation', - ), - migrations.AddField( - model_name='contact', - name='people', - field=models.ManyToManyField(blank=True, through='crm.ContactPersonAssociation', to='crm.Person', verbose_name='Has contact'), - ), - migrations.AddField( - model_name='person', - name='companies', - field=models.ManyToManyField(blank=True, through='crm.ContactPersonAssociation', to='crm.Contact', verbose_name='Works at'), - ), - ] diff --git a/koalixcrm/crm/migrations/0047_auto_20180222_1101.py b/koalixcrm/crm/migrations/0047_auto_20180222_1101.py deleted file mode 100644 index bda7368c..00000000 --- a/koalixcrm/crm/migrations/0047_auto_20180222_1101.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-22 11:01 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0046_auto_20180221_2112'), - ] - - operations = [ - migrations.AlterField( - model_name='contactpersonassociation', - name='contact', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='person_association', to='crm.Contact'), - ), - migrations.AlterField( - 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'), - ), - ] diff --git a/koalixcrm/crm/migrations/0048_auto_20180222_1350.py b/koalixcrm/crm/migrations/0048_auto_20180222_1350.py deleted file mode 100644 index 5eb99485..00000000 --- a/koalixcrm/crm/migrations/0048_auto_20180222_1350.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-22 13:50 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0047_auto_20180222_1101'), - ] - - operations = [ - migrations.CreateModel( - name='PhoneSystem', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('system_model', models.CharField(blank=True, max_length=200, null=True, verbose_name='Model')), - ('year', models.IntegerField(blank=True, null=True, verbose_name='Year of installation')), - ('n_phones_ana', models.IntegerField(blank=True, null=True, verbose_name='Analog phones')), - ('n_phones_dig', models.IntegerField(blank=True, null=True, verbose_name='Digital phones')), - ('n_ext_lines', models.IntegerField(blank=True, null=True, verbose_name='External lines')), - ], - options={ - 'verbose_name': 'Phone System', - 'verbose_name_plural': 'Phone System', - }, - ), - migrations.RemoveField( - model_name='customer', - name='no_phones', - ), - migrations.CreateModel( - name='PhoneSystemForCustomer', - fields=[ - ('phonesystem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.PhoneSystem')), - ('service_type', models.CharField(blank=True, max_length=100, null=True, verbose_name='Service Type')), - ('expire_date', models.DateTimeField(blank=True, verbose_name='Expire Date')), - ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_association', to='crm.Customer')), - ('supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customer_association', to='crm.Supplier')), - ], - bases=('crm.phonesystem',), - ), - ] diff --git a/koalixcrm/crm/migrations/0049_auto_20180222_1358.py b/koalixcrm/crm/migrations/0049_auto_20180222_1358.py deleted file mode 100644 index 67f4a3ca..00000000 --- a/koalixcrm/crm/migrations/0049_auto_20180222_1358.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-22 13:58 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0048_auto_20180222_1350'), - ] - - operations = [ - migrations.RemoveField( - model_name='contact', - name='addressline3', - ), - migrations.RemoveField( - model_name='contact', - name='addressline4', - ), - ] diff --git a/koalixcrm/crm/migrations/0050_auto_20180222_1554.py b/koalixcrm/crm/migrations/0050_auto_20180222_1554.py deleted file mode 100644 index c2dee17c..00000000 --- a/koalixcrm/crm/migrations/0050_auto_20180222_1554.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-22 15:54 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0049_auto_20180222_1358'), - ] - - operations = [ - migrations.AlterModelOptions( - name='callforcontact', - options={'verbose_name': 'Calls', 'verbose_name_plural': 'Calls'}, - ), - migrations.AlterModelOptions( - name='contactpersonassociation', - options={'verbose_name': 'Contacts', 'verbose_name_plural': 'Contacts'}, - ), - migrations.AlterModelOptions( - name='phonesystemforcustomer', - options={'verbose_name': 'Phone System', 'verbose_name_plural': 'Phone System'}, - ), - ] diff --git a/koalixcrm/crm/migrations/0051_auto_20180222_1740.py b/koalixcrm/crm/migrations/0051_auto_20180222_1740.py deleted file mode 100644 index 7a1f8803..00000000 --- a/koalixcrm/crm/migrations/0051_auto_20180222_1740.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-22 17:40 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0050_auto_20180222_1554'), - ] - - operations = [ - migrations.AlterField( - model_name='phonesystemforcustomer', - name='expire_date', - field=models.DateTimeField(blank=True, null=True, verbose_name='Expire Date'), - ), - ] diff --git a/koalixcrm/crm/migrations/0052_auto_20180223_1609.py b/koalixcrm/crm/migrations/0052_auto_20180223_1609.py deleted file mode 100644 index e11bc3fc..00000000 --- a/koalixcrm/crm/migrations/0052_auto_20180223_1609.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-23 16:09 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0051_auto_20180222_1740'), - ] - - operations = [ - migrations.AlterField( - model_name='call', - name='status', - field=models.CharField(choices=[('P', 'Planned'), ('D', 'Delayed'), ('R', 'ToRecall'), ('A', 'Failed'), ('S', 'Success')], default='P', max_length=1, verbose_name='Status'), - ), - ] diff --git a/koalixcrm/crm/migrations/0053_auto_20180224_1614.py b/koalixcrm/crm/migrations/0053_auto_20180224_1614.py deleted file mode 100644 index 2030cae8..00000000 --- a/koalixcrm/crm/migrations/0053_auto_20180224_1614.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-24 16:14 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0052_auto_20180223_1609'), - ] - - operations = [ - migrations.RemoveField( - model_name='contact', - name='addressline1', - ), - migrations.RemoveField( - model_name='contact', - name='addressline2', - ), - migrations.RemoveField( - model_name='contact', - name='country', - ), - migrations.RemoveField( - model_name='contact', - name='state', - ), - migrations.RemoveField( - model_name='contact', - name='town', - ), - migrations.RemoveField( - model_name='contact', - name='zipcode', - ), - ] diff --git a/koalixcrm/crm/migrations/0054_auto_20180224_1954.py b/koalixcrm/crm/migrations/0054_auto_20180224_1954.py deleted file mode 100644 index 47f2b197..00000000 --- a/koalixcrm/crm/migrations/0054_auto_20180224_1954.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-24 19:54 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0053_auto_20180224_1614'), - ] - - operations = [ - migrations.AlterField( - model_name='call', - name='status', - field=models.CharField(choices=[('P', 'Planned'), ('D', 'Delayed'), ('R', 'ToRecall'), ('F', 'Failed'), ('S', 'Success')], default='P', max_length=1, verbose_name='Status'), - ), - ] diff --git a/koalixcrm/crm/migrations/0055_auto_20180224_2002.py b/koalixcrm/crm/migrations/0055_auto_20180224_2002.py deleted file mode 100644 index 4b1b5dd4..00000000 --- a/koalixcrm/crm/migrations/0055_auto_20180224_2002.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-24 20:02 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0054_auto_20180224_1954'), - ] - - operations = [ - migrations.CreateModel( - name='VisitForContact', - fields=[ - ('callforcontact_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.CallForContact')), - ('ref_call', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crm.Call', verbose_name='Reference Call')), - ], - options={ - 'verbose_name': 'Visit', - 'verbose_name_plural': 'Visits', - }, - bases=('crm.callforcontact',), - ), - migrations.AlterModelOptions( - name='callforcontact', - options={'verbose_name': 'Call', 'verbose_name_plural': 'Calls'}, - ), - ] diff --git a/koalixcrm/crm/migrations/0056_auto_20180224_2039.py b/koalixcrm/crm/migrations/0056_auto_20180224_2039.py deleted file mode 100644 index a65241df..00000000 --- a/koalixcrm/crm/migrations/0056_auto_20180224_2039.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-24 20:39 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0055_auto_20180224_2002'), - ] - - operations = [ - migrations.RemoveField( - model_name='visitforcontact', - name='callforcontact_ptr', - ), - migrations.RemoveField( - model_name='visitforcontact', - name='ref_call', - ), - migrations.DeleteModel( - name='VisitForContact', - ), - ] diff --git a/koalixcrm/crm/migrations/0057_visitforcontact.py b/koalixcrm/crm/migrations/0057_visitforcontact.py deleted file mode 100644 index ab14ea1a..00000000 --- a/koalixcrm/crm/migrations/0057_visitforcontact.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-24 20:39 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0056_auto_20180224_2039'), - ] - - operations = [ - 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 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')), - ('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',), - ), - ] diff --git a/koalixcrm/crm/migrations/0058_contactimportdata.py b/koalixcrm/crm/migrations/0058_contactimportdata.py deleted file mode 100644 index a6a9b242..00000000 --- a/koalixcrm/crm/migrations/0058_contactimportdata.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-03-04 14:12 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0057_visitforcontact'), - ] - - operations = [ - migrations.CreateModel( - name='ContactImportData', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('data_file', models.FileField(max_length=255, upload_to='data_files')), - ('contact_type', models.CharField(choices=[('C', 'Customer'), ('S', 'Supplier')], max_length=1, verbose_name='Contact Type')), - ], - options={ - 'verbose_name': 'Contact: Import Data from XLSX file', - 'verbose_name_plural': 'Contacts: Import Data from XLSX file', - }, - ), - ] diff --git a/koalixcrm/crm/migrations/0059_attributeset.py b/koalixcrm/crm/migrations/0059_attributeset.py deleted file mode 100644 index aad7c496..00000000 --- a/koalixcrm/crm/migrations/0059_attributeset.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-08 17:24 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0058_contactimportdata'), - ] - - operations = [ - migrations.CreateModel( - name='AttributeSet', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, verbose_name='Attribute Set')), - ], - options={ - 'verbose_name': 'Attribute Set', - 'verbose_name_plural': 'Attribute Set', - }, - ), - ] diff --git a/koalixcrm/crm/migrations/0060_attribute.py b/koalixcrm/crm/migrations/0060_attribute.py deleted file mode 100644 index 111b1ca2..00000000 --- a/koalixcrm/crm/migrations/0060_attribute.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-08 17:26 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0059_attributeset'), - ] - - operations = [ - migrations.CreateModel( - name='Attribute', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('code', models.CharField(max_length=50, verbose_name='Attribute Code')), - ('name', models.CharField(max_length=200, verbose_name='Attribute Name')), - ('model_type', models.CharField(choices=[('V', 'Varchar'), ('I', 'Integer'), ('D', 'Decimal'), ('T', 'Text')], max_length=1, verbose_name='Model Type')), - ('attribute_set', models.ManyToManyField(blank=True, to='crm.AttributeSet', verbose_name='Is member of')), - ], - options={ - 'verbose_name': 'Attribute', - 'verbose_name_plural': 'Attributes', - }, - ), - ] diff --git a/koalixcrm/crm/migrations/0061_product_attribute_set.py b/koalixcrm/crm/migrations/0061_product_attribute_set.py deleted file mode 100644 index 991e00ce..00000000 --- a/koalixcrm/crm/migrations/0061_product_attribute_set.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-08 17:30 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0060_attribute'), - ] - - operations = [ - migrations.AddField( - model_name='product', - name='attribute_set', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='crm.AttributeSet', verbose_name='Attribute Set'), - preserve_default=False, - ), - ] diff --git a/koalixcrm/crm/migrations/0062_auto_20180408_1757.py b/koalixcrm/crm/migrations/0062_auto_20180408_1757.py deleted file mode 100644 index 35bc224a..00000000 --- a/koalixcrm/crm/migrations/0062_auto_20180408_1757.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-08 17:57 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0061_product_attribute_set'), - ] - - operations = [ - migrations.CreateModel( - name='ProductForCustomer', - 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')), - ('service_type', models.CharField(blank=True, max_length=100, null=True, verbose_name='Service Type')), - ('expire_date', models.DateTimeField(blank=True, null=True, verbose_name='Expire Date')), - ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_association', to='crm.Customer')), - ('supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customer_association', to='crm.Supplier')), - ], - options={ - 'verbose_name': 'Phone System', - 'verbose_name_plural': 'Phone System', - }, - bases=('crm.product',), - ), - migrations.RemoveField( - model_name='phonesystemforcustomer', - name='customer', - ), - migrations.RemoveField( - model_name='phonesystemforcustomer', - name='phonesystem_ptr', - ), - migrations.RemoveField( - model_name='phonesystemforcustomer', - name='supplier', - ), - migrations.DeleteModel( - name='PhoneSystem', - ), - migrations.DeleteModel( - name='PhoneSystemForCustomer', - ), - ] diff --git a/koalixcrm/crm/migrations/0063_auto_20180408_1918.py b/koalixcrm/crm/migrations/0063_auto_20180408_1918.py deleted file mode 100644 index af0a81f4..00000000 --- a/koalixcrm/crm/migrations/0063_auto_20180408_1918.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-08 19:18 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0062_auto_20180408_1757'), - ] - - operations = [ - migrations.AlterModelOptions( - name='productforcustomer', - options={'verbose_name': 'Product', 'verbose_name_plural': 'Products'}, - ), - ] diff --git a/koalixcrm/crm/migrations/0064_auto_20180408_1929.py b/koalixcrm/crm/migrations/0064_auto_20180408_1929.py deleted file mode 100644 index e3bbac26..00000000 --- a/koalixcrm/crm/migrations/0064_auto_20180408_1929.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-08 19:29 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0063_auto_20180408_1918'), - ] - - operations = [ - migrations.RemoveField( - model_name='attribute', - name='attribute_set', - ), - migrations.AddField( - model_name='attributeset', - name='attributes', - field=models.ManyToManyField(blank=True, to='crm.Attribute', verbose_name='Attributes'), - ), - ] diff --git a/koalixcrm/crm/migrations/0065_productattributeassociation.py b/koalixcrm/crm/migrations/0065_productattributeassociation.py deleted file mode 100644 index a28e3008..00000000 --- a/koalixcrm/crm/migrations/0065_productattributeassociation.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-09 18:55 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0064_auto_20180408_1929'), - ] - - operations = [ - migrations.CreateModel( - name='ProductAttributeAssociation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('value', models.CharField(blank=True, max_length=255, null=True, verbose_name='Value')), - ('attribute', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_attribute', to='crm.Attribute')), - ('product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_product', to='crm.Product')), - ], - options={ - 'verbose_name': 'Specific field', - 'verbose_name_plural': 'Specific fields', - }, - ), - ] diff --git a/koalixcrm/crm/migrations/0066_auto_20180409_1952.py b/koalixcrm/crm/migrations/0066_auto_20180409_1952.py deleted file mode 100644 index af83477a..00000000 --- a/koalixcrm/crm/migrations/0066_auto_20180409_1952.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-09 19:52 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0065_productattributeassociation'), - ] - - operations = [ - migrations.RemoveField( - model_name='productforcustomer', - name='customer', - ), - migrations.RemoveField( - model_name='productforcustomer', - name='product_ptr', - ), - migrations.RemoveField( - model_name='productforcustomer', - name='supplier', - ), - migrations.DeleteModel( - name='ProductForCustomer', - ), - ] diff --git a/koalixcrm/crm/migrations/0067_productforcustomer.py b/koalixcrm/crm/migrations/0067_productforcustomer.py deleted file mode 100644 index 812300e2..00000000 --- a/koalixcrm/crm/migrations/0067_productforcustomer.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-09 19:52 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0066_auto_20180409_1952'), - ] - - operations = [ - migrations.CreateModel( - name='ProductForCustomer', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('service_type', models.CharField(blank=True, max_length=100, null=True, verbose_name='Service Type')), - ('expire_date', models.DateTimeField(blank=True, null=True, verbose_name='Expire Date')), - ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_association', to='crm.Customer')), - ('supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customer_association', to='crm.Supplier')), - ], - options={ - 'verbose_name': 'Product', - 'verbose_name_plural': 'Products', - }, - ), - ] diff --git a/koalixcrm/crm/migrations/0068_productforcustomer_product.py b/koalixcrm/crm/migrations/0068_productforcustomer_product.py deleted file mode 100644 index 6df6be16..00000000 --- a/koalixcrm/crm/migrations/0068_productforcustomer_product.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-09 19:56 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0067_productforcustomer'), - ] - - operations = [ - migrations.AddField( - model_name='productforcustomer', - name='product', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Product'), - ), - ] diff --git a/koalixcrm/crm/migrations/0069_auto_20180415_1626.py b/koalixcrm/crm/migrations/0069_auto_20180415_1626.py deleted file mode 100644 index 623260fa..00000000 --- a/koalixcrm/crm/migrations/0069_auto_20180415_1626.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-15 16:26 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0068_productforcustomer_product'), - ] - - operations = [ - migrations.AddField( - model_name='contact', - name='vatnumber', - field=models.CharField(blank=True, max_length=20, verbose_name='Vat Number'), - ), - migrations.AddField( - model_name='customer', - name='isLead', - field=models.BooleanField(default=True), - ), - migrations.AlterField( - model_name='productforcustomer', - name='product', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='crm.Product', verbose_name='Related Product'), - ), - ] diff --git a/koalixcrm/crm/migrations/0070_auto_20180428_1619.py b/koalixcrm/crm/migrations/0070_auto_20180428_1619.py deleted file mode 100644 index 4feb8781..00000000 --- a/koalixcrm/crm/migrations/0070_auto_20180428_1619.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-28 16:19 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0069_auto_20180415_1626'), - ] - - operations = [ - migrations.AlterField( - model_name='emailaddressforcontact', - name='purpose', - field=models.CharField(choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business'), ('F', 'Fax')], max_length=1, verbose_name='Purpose'), - ), - migrations.AlterField( - model_name='phoneaddressforcontact', - name='purpose', - field=models.CharField(choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business'), ('F', 'Fax')], max_length=1, verbose_name='Purpose'), - ), - migrations.AlterField( - model_name='postaladdressforcontact', - name='purpose', - field=models.CharField(choices=[('H', 'Private'), ('O', 'Business'), ('P', 'Mobile Private'), ('B', 'Mobile Business'), ('F', 'Fax')], max_length=1, verbose_name='Purpose'), - ), - ] diff --git a/koalixcrm/crm/migrations/0071_auto_20180510_0929.py b/koalixcrm/crm/migrations/0071_auto_20180510_0929.py deleted file mode 100644 index 336d8b9b..00000000 --- a/koalixcrm/crm/migrations/0071_auto_20180510_0929.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-05-10 09:29 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0070_auto_20180428_1619'), - ] - - operations = [ - migrations.AlterField( - model_name='person', - name='role', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Role'), - ), - ] diff --git a/koalixcrm/crm/migrations/0072_auto_20180510_1531.py b/koalixcrm/crm/migrations/0072_auto_20180510_1531.py deleted file mode 100644 index ca6bd240..00000000 --- a/koalixcrm/crm/migrations/0072_auto_20180510_1531.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-05-10 15:31 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0071_auto_20180510_0929'), - ] - - operations = [ - migrations.AlterField( - model_name='person', - name='prefix', - field=models.CharField(blank=True, choices=[('F', 'Company'), ('W', 'Mrs'), ('H', 'Mr'), ('G', 'Ms'), ('J', 'J.D.'), ('A', 'Ar'), ('I', 'Ing'), ('D', 'Dott')], max_length=1, null=True, verbose_name='Prefix'), - ), - migrations.AlterField( - model_name='postaladdress', - name='prefix', - field=models.CharField(blank=True, choices=[('F', 'Company'), ('W', 'Mrs'), ('H', 'Mr'), ('G', 'Ms'), ('J', 'J.D.'), ('A', 'Ar'), ('I', 'Ing'), ('D', 'Dott')], max_length=1, null=True, verbose_name='Prefix'), - ), - migrations.AlterField( - model_name='product', - name='product_number', - field=models.CharField(blank=True, max_length=30, null=True, verbose_name='Product Number'), - ), - ] diff --git a/koalixcrm/crm/migrations/0073_phonesystemforcustomer.py b/koalixcrm/crm/migrations/0073_phonesystemforcustomer.py deleted file mode 100644 index 4799d259..00000000 --- a/koalixcrm/crm/migrations/0073_phonesystemforcustomer.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-05-11 09:07 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0072_auto_20180510_1531'), - ] - - operations = [ - migrations.CreateModel( - name='PhoneSystemForCustomer', - fields=[ - ('productforcustomer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.ProductForCustomer')), - ('year', models.IntegerField(blank=True, null=True, verbose_name='Year of installation')), - ('no_ext_lines', models.IntegerField(blank=True, null=True, verbose_name='External lines')), - ('no_int_lines', models.IntegerField(blank=True, null=True, verbose_name='Internal lines')), - ], - options={ - 'verbose_name': 'Phone System', - 'verbose_name_plural': 'Phone Systems', - }, - bases=('crm.productforcustomer',), - ), - ] diff --git a/koalixcrm/crm/migrations/0074_auto_20180511_1005.py b/koalixcrm/crm/migrations/0074_auto_20180511_1005.py deleted file mode 100644 index de1270bf..00000000 --- a/koalixcrm/crm/migrations/0074_auto_20180511_1005.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-05-11 10:05 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0073_phonesystemforcustomer'), - ] - - operations = [ - migrations.AddField( - model_name='phonesystemforcustomer', - name='maintainer', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Maintainer'), - ), - migrations.AlterField( - model_name='phonesystemforcustomer', - name='year', - field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Year of installation'), - ), - ] diff --git a/koalixcrm/crm/migrations/0075_auto_20180519_1530.py b/koalixcrm/crm/migrations/0075_auto_20180519_1530.py deleted file mode 100644 index c7d830ea..00000000 --- a/koalixcrm/crm/migrations/0075_auto_20180519_1530.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-05-19 15:30 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('crm', '0074_auto_20180511_1005'), - ] - - operations = [ - migrations.RemoveField( - model_name='phonesystemforcustomer', - name='productforcustomer_ptr', - ), - migrations.DeleteModel( - name='PhoneSystemForCustomer', - ), - migrations.CreateModel( - name='AnalogPhoneForCustomer', - fields=[ - ('productforcustomer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.ProductForCustomer')), - ], - options={ - 'verbose_name': 'Analog Phone', - 'verbose_name_plural': 'Analog Phones', - }, - bases=('crm.productforcustomer',), - ), - migrations.CreateModel( - name='DigitalPhoneForCustomer', - fields=[ - ('productforcustomer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.ProductForCustomer')), - ], - options={ - 'verbose_name': 'Digital Phone', - 'verbose_name_plural': 'Digital Phones', - }, - bases=('crm.productforcustomer',), - ), - migrations.CreateModel( - name='InternetForCustomer', - fields=[ - ('productforcustomer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.ProductForCustomer')), - ], - options={ - 'verbose_name': 'Internet Connection', - 'verbose_name_plural': 'Internet Connections', - }, - bases=('crm.productforcustomer',), - ), - migrations.CreateModel( - name='MobileForCustomer', - fields=[ - ('productforcustomer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.ProductForCustomer')), - ], - options={ - 'verbose_name': 'Mobile Service', - 'verbose_name_plural': 'Mobile Services', - }, - bases=('crm.productforcustomer',), - ), - migrations.CreateModel( - name='SwitchboardForCustomer', - fields=[ - ('productforcustomer_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='crm.ProductForCustomer')), - ('internal_lines', models.IntegerField(blank=True, null=True, verbose_name='Internal lines')), - ('external_lines', models.IntegerField(blank=True, null=True, verbose_name='External lines')), - ], - options={ - 'verbose_name': 'Switchboard', - 'verbose_name_plural': 'Switchboards', - }, - bases=('crm.productforcustomer',), - ), - migrations.AddField( - model_name='productforcustomer', - name='maintainer', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Maintainer'), - ), - migrations.AddField( - model_name='productforcustomer', - name='quantity', - field=models.IntegerField(blank=True, null=True, verbose_name='Quantity'), - ), - migrations.AddField( - model_name='productforcustomer', - name='year', - field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Year of installation'), - ), - ] diff --git a/koalixcrm/crm/models.py b/koalixcrm/crm/models.py index db4cdf06..030f0591 100644 --- a/koalixcrm/crm/models.py +++ b/koalixcrm/crm/models.py @@ -26,7 +26,12 @@ from koalixcrm.crm.product.tax import * from koalixcrm.crm.product.unit import * - +from koalixcrm.crm.reporting.employeeassignmenttotask import * +from koalixcrm.crm.reporting.generictasklink import * +from koalixcrm.crm.reporting.task import * +from koalixcrm.crm.reporting.tasklinktype import * +from koalixcrm.crm.reporting.taskstatus import * +from koalixcrm.crm.reporting.work import * diff --git a/koalixcrm/crm/product/attribute.py b/koalixcrm/crm/product/attribute.py deleted file mode 100644 index 53ee3d29..00000000 --- a/koalixcrm/crm/product/attribute.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.db import models -from django.contrib import admin -from django.utils.translation import ugettext as _ - -from koalixcrm.crm.const.modeltype import * - -class Attribute(models.Model): - code = models.CharField(verbose_name=_("Attribute Code"), max_length=50) - name = models.CharField(verbose_name=_("Attribute Name"), max_length=200) - model_type = models.CharField(verbose_name=_("Model Type"), max_length=1, choices=MODELTYPE) - - def __str__(self): - return str(self.id) + ' ' + self.name - - class Meta: - app_label = "crm" - verbose_name = _('Attribute') - verbose_name_plural = _('Attributes') - -class AttributeSet(models.Model): - name = models.CharField(verbose_name=_("Attribute Set"), max_length=50) - attributes = models.ManyToManyField("Attribute", verbose_name=_('Attributes'), blank=True) - - def __str__(self): - return str(self.id) + ' ' + self.name - - class Meta: - app_label = "crm" - verbose_name = _('Attribute Set') - verbose_name_plural = _('Attribute Set') - -class OptionAttribute(admin.ModelAdmin): - list_display = ('code', 'name', 'model_type',) - -class OptionAttributeSet(admin.ModelAdmin): - list_display = ('name',) - filter_horizontal = ('attributes',) diff --git a/koalixcrm/crm/product/phonesystem.py b/koalixcrm/crm/product/phonesystem.py deleted file mode 100644 index 127ba363..00000000 --- a/koalixcrm/crm/product/phonesystem.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.db import models -from django.utils.translation import ugettext as _ -from koalixcrm.crm.product.product import Product - -class PhoneSystem(models.Model): - system_model = models.CharField(verbose_name=_("Model"), max_length=200, blank=True, null=True) - year = models.IntegerField(verbose_name=_("Year of installation"), blank=True, null=True) - n_phones_ana = models.IntegerField(verbose_name=_("Analog phones"), blank=True, null=True) - n_phones_dig = models.IntegerField(verbose_name=_("Digital phones"), blank=True, null=True) - n_ext_lines = models.IntegerField(verbose_name=_("External lines"), blank=True, null=True) - - class Meta: - app_label = "crm" - verbose_name = _('Phone System') - verbose_name_plural = _('Phone System') - - - diff --git a/koalixcrm/crm/product/product.py b/koalixcrm/crm/product/product.py index d67f1a09..5c3ae99f 100644 --- a/koalixcrm/crm/product/product.py +++ b/koalixcrm/crm/product/product.py @@ -8,13 +8,12 @@ from koalixcrm.crm.product.unit import ProductUnitTransform from koalixcrm.crm.product.price import ProductPrice import koalixcrm.crm.product.price -from koalixcrm.crm.product.attribute import AttributeSet, Attribute -from koalixcrm.crm.inlinemixin import LimitedAdminInlineMixin + class Product(models.Model): description = models.TextField(verbose_name=_("Description"), null=True, blank=True) title = models.CharField(verbose_name=_("Title"), max_length=200) - product_number = models.CharField(verbose_name=_("Product Number"), max_length=30, null=True, blank=True) + product_number = models.IntegerField(verbose_name=_("Product Number")) default_unit = models.ForeignKey("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) @@ -24,7 +23,6 @@ class Product(models.Model): accounting_product_categorie = models.ForeignKey('accounting.ProductCategorie', verbose_name=_("Accounting Product Categorie"), null=True, blank="True") - attribute_set = models.ForeignKey(AttributeSet, verbose_name=_("Attribute Set")) def get_price(self, date, unit, customer, currency): prices = koalixcrm.crm.product.price.Price.objects.filter(product=self.id) @@ -84,51 +82,12 @@ def __str__(self): "that matches the date") + ": " + self.date.__str__() + " ," + _( "customer") + ": " + self.customer.__str__() + " ," + _("currency")+ ": "+ self.currency.__str__()+ _(" and unit") + ":" + self.unit.__str__() - @staticmethod - def autocomplete_search_fields(): - return ("id__iexact", "title__icontains",) - -class ProductAttributeAssociation(models.Model): - product = models.ForeignKey(Product, related_name='related_product', blank=True, null=True) - attribute = models.ForeignKey(Attribute, related_name='related_attribute', blank=True, null=True) - value = models.CharField(max_length=255, verbose_name=_("Value"), blank=True, null=True) - - class Meta: - app_label = "crm" - verbose_name = _('Specific field') - verbose_name_plural = _('Specific fields') - - def __str__(self): - return '' - -class AttributeInlineAdmin(LimitedAdminInlineMixin, admin.TabularInline): - model = ProductAttributeAssociation - extra = 1 - classes = ['collapse'] - fieldsets = ( - ('Basics', { - 'fields': ( - 'attribute', 'value',) - }), - ) - allow_add = True - - def get_filters(self, request, obj): - return getattr(self, 'filters', ()) if obj is None else (('attribute', dict(attributeset=obj.id)),) class OptionProduct(admin.ModelAdmin): list_display = ('product_number', 'title', 'default_unit', 'tax', 'accounting_product_categorie') list_display_links = ('product_number',) fieldsets = ( (_('Basics'), { - 'fields': ('product_number', 'title', 'description', 'default_unit', 'tax', 'accounting_product_categorie', 'attribute_set') + 'fields': ('product_number', 'title', 'description', 'default_unit', 'tax', 'accounting_product_categorie') }),) - inlines = [ProductPrice, ProductUnitTransform, AttributeInlineAdmin] - - def get_readonly_fields(self, request, obj=None): - if obj: # editing an existing object - return self.readonly_fields + ('attribute_set',) - return self.readonly_fields - - def get_specific_fields(self, obj): - fields = obj.attribute_set.attributes \ No newline at end of file + inlines = [ProductPrice, ProductUnitTransform] \ No newline at end of file diff --git a/koalixcrm/crm/reporting/__init__.py b/koalixcrm/crm/reporting/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/koalixcrm/crm/reporting/employeeassignmenttotask.py b/koalixcrm/crm/reporting/employeeassignmenttotask.py new file mode 100644 index 00000000..d489e25a --- /dev/null +++ b/koalixcrm/crm/reporting/employeeassignmenttotask.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.utils.translation import ugettext as _ +from django.contrib import admin, messages +import koalixcrm + + +class EmployeeAssignmentToTask(models.Model): + employee = models.ForeignKey("djangoUserExtension.UserExtension") + planned_effort = models.DecimalField(verbose_name=_("Effort"), max_digits=10, decimal_places=2) + task = models.ForeignKey("Task", verbose_name=_('Task'), blank=False, null=False) + + def __str__(self): + return _("Employee Assignment") + ": " + str(self.employee.user.first_name) + + class Meta: + app_label = "crm" + verbose_name = _('Employee Assignment') + verbose_name_plural = _('Employee Assignments') + + +class InlineEmployeeAssignmentToTask(admin.TabularInline): + model = EmployeeAssignmentToTask + fieldsets = ( + (_('Work'), { + 'fields': ('employee', + 'planned_effort',) + }), + ) + extra = 1 \ No newline at end of file diff --git a/koalixcrm/crm/reporting/generictasklink.py b/koalixcrm/crm/reporting/generictasklink.py new file mode 100644 index 00000000..8787d923 --- /dev/null +++ b/koalixcrm/crm/reporting/generictasklink.py @@ -0,0 +1,28 @@ +# -*- 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.admin import GenericTabularInline +from django.contrib.contenttypes.models import ContentType + + +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=False, null=False) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + generic_crm_object = GenericForeignKey('content_type', 'object_id') + + def __str__(self): + return _("Task Link") + + class Meta: + app_label = "crm" + verbose_name = _('Task Link') + verbose_name_plural = _('Task Links') + + +class InlineGenericTaskLink(GenericTabularInline): + model = GenericTaskLink + extra = 1 \ No newline at end of file diff --git a/koalixcrm/crm/reporting/task.py b/koalixcrm/crm/reporting/task.py new file mode 100644 index 00000000..96ce376a --- /dev/null +++ b/koalixcrm/crm/reporting/task.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.utils.translation import ugettext as _ +from django.contrib import admin +from koalixcrm.crm.reporting.employeeassignmenttotask import EmployeeAssignmentToTask, InlineEmployeeAssignmentToTask +from koalixcrm.crm.reporting.generictasklink import InlineGenericTaskLink +from koalixcrm.crm.reporting.work import InlineWork +from datetime import * +from rest_framework import serializers +import koalixcrm + + +class Task(models.Model): + short_description = models.CharField(verbose_name=_("Description"), max_length=100, blank=True, null=True) + planned_start_date = models.DateField(verbose_name=_("Planned Start Date"), blank=False, null=False) + planned_end_date = models.DateField(verbose_name=_("Planned End Date"), blank=False, null=False) + project = models.ForeignKey("Contract", verbose_name=_('Contract'), blank=False, null=False) + description = models.TextField(verbose_name=_("Description"), blank=True, null=True) + status = models.ForeignKey("TaskStatus", verbose_name=_('Task Status'), blank=False, null=False) + last_status_change = models.DateField(verbose_name=_("Last Status Change"), blank=True, null=False) + + def planned_duration(self): + if (not self.planned_start_date) or (not self.planned_end_date): + return 0 + elif self.planned_start_date > self.planned_end_date: + return 0 + else: + return self.planned_end_date-self.planned_start_date + + def planned_effort(self): + assignments_to_this_task = EmployeeAssignmentToTask.objects.filter(task=self.id) + sum_effort = 0 + for assignment_to_this_task in assignments_to_this_task: + sum_effort += assignment_to_this_task.planned_effort + return str(sum_effort)+" h" + + def effective_duration(self): + if self.status.is_done: + if self.planned_start_date > self.last_status_change: + return 0 + else: + return self.last_status_change - self.planned_start_date + + def effective_effort(self): + return str(koalixcrm.crm.reporting.work.Work.get_sum_effort_in_hours(self))+" h" + + def __str__(self): + return _("Task") + ": " + str(self.id) + " " + _("from Project") + ": " + str(self.project.id) + + class Meta: + app_label = "crm" + verbose_name = _('Task') + verbose_name_plural = _('Tasks') + + +class OptionTask(admin.ModelAdmin): + list_display = ('id', + 'short_description', + 'planned_start_date', + 'planned_end_date', + 'project', + 'status', + 'last_status_change', + 'planned_duration', + 'planned_effort', + 'effective_duration', + 'effective_effort') + list_display_links = ('id',) + list_filter = ('project',) + ordering = ('-id',) + + fieldsets = ( + (_('Work'), { + 'fields': ('short_description', + 'planned_start_date', + 'planned_end_date', + 'project', + 'description', + 'status') + }), + ) + save_as = True + inlines = [InlineEmployeeAssignmentToTask, + InlineGenericTaskLink, + InlineWork] + + def save_model(self, request, obj, form, change): + obj.last_status_change = date.today().__str__() + obj.save() + + +class TaskJSONSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Task + fields = ('id', + 'short_description', + 'planned_end_date', + 'planned_start_date', + 'project', + 'description', + 'status') diff --git a/koalixcrm/crm/reporting/tasklinktype.py b/koalixcrm/crm/reporting/tasklinktype.py new file mode 100644 index 00000000..2e90afa1 --- /dev/null +++ b/koalixcrm/crm/reporting/tasklinktype.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.utils.translation import ugettext as _ +from django.contrib import admin, messages + + +class TaskLinkType(models.Model): + title = models.CharField(verbose_name=_("Title"), max_length=300, blank=False, null=False) + description = models.TextField(verbose_name=_("Text"), blank=True, null=True) + + class Meta: + app_label = "crm" + verbose_name = _('Task Link Type') + verbose_name_plural = _('Task Link Type') + + def __str__(self): + return _("Task Link Type") + " ID: " + str(self.id) + " title: " + str(self.title) + + +class OptionTaskLinkType(admin.ModelAdmin): + list_display = ('id', + 'title', + 'description') + + fieldsets = ( + (_('TaskLinkType'), { + 'fields': ('title', + 'description') + }), + ) + save_as = True diff --git a/koalixcrm/crm/reporting/taskstatus.py b/koalixcrm/crm/reporting/taskstatus.py new file mode 100644 index 00000000..89c63632 --- /dev/null +++ b/koalixcrm/crm/reporting/taskstatus.py @@ -0,0 +1,44 @@ +# -*- 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 TaskStatus(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_done = models.BooleanField(verbose_name=_("Status represents task done"),) + + class Meta: + app_label = "crm" + verbose_name = _('Task Status') + verbose_name_plural = _('Task Status') + + def __str__(self): + return _("Task Status") + " ID: " + str(self.id) + " title: " + str(self.title) + + +class OptionTaskStatus(admin.ModelAdmin): + list_display = ('id', + 'title', + 'description', + 'is_done') + + fieldsets = ( + (_('Work'), { + 'fields': ('title', + 'description', + 'is_done') + }), + ) + save_as = True + + +class TaskStatusJSONSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = TaskStatus + fields = ('id', + 'title', + 'description',) diff --git a/koalixcrm/crm/reporting/urls.py b/koalixcrm/crm/reporting/urls.py new file mode 100644 index 00000000..2cf5c057 --- /dev/null +++ b/koalixcrm/crm/reporting/urls.py @@ -0,0 +1,10 @@ +# coding: utf-8 + +# DJANGO IMPORTS +from django.conf.urls import url + +from koalixcrm.crm.views.monthlyreport import work_report + +urlpatterns = [ + url(r'^monthlyreport/$', work_report, name="monthly_report"), +] \ No newline at end of file diff --git a/koalixcrm/crm/reporting/work.py b/koalixcrm/crm/reporting/work.py new file mode 100644 index 00000000..fb403318 --- /dev/null +++ b/koalixcrm/crm/reporting/work.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.contrib import admin +from django.utils.translation import ugettext as _ + + +class Work(models.Model): + employee = models.ForeignKey("djangoUserExtension.UserExtension") + date = models.DateField(verbose_name=_("Date"), blank=False, null=False) + start_time = models.DateTimeField(verbose_name=_("Start Time"), blank=False, null=False) + stop_time = models.DateTimeField(verbose_name=_("Stop Time"), blank=False, null=False) + short_description = models.CharField(verbose_name=_("Short Description"), max_length=300, blank=False, null=False) + description = models.TextField(verbose_name=_("Text"), blank=True, null=True) + task = models.ForeignKey("Task", verbose_name=_('Task'), blank=False, null=False) + + @staticmethod + def get_sum_effort_in_hours(task): + work_objects = Work.objects.filter(task=task.id) + sum_effort = 0 + for work_object in work_objects: + if (not work_object.start_time) or (not work_object.stop_time): + sum_effort = 0 + elif work_object.start_time > work_object.stop_time: + sum_effort += 0 + else: + sum_effort += work_object.effort() + sum_effort_in_hours = sum_effort/3600 + return sum_effort_in_hours + + def effort(self): + if not self.stop_time or not self.start_time: + return 0 + else: + return (self.stop_time - self.start_time).total_seconds() + + def effort_as_string(self): + return str(self.effort()/3600) + " h" + + def __str__(self): + return _("Work") + ": " + str(self.id) + " " + _("from Person") + ": " + str(self.employee.id) + + class Meta: + app_label = "crm" + verbose_name = _('Work') + verbose_name_plural = _('Work') + + +class OptionWork(admin.ModelAdmin): + list_display = ('id', + 'employee', + 'task', + 'short_description', + 'date', + 'start_time', + 'stop_time', + 'effort_as_string',) + + list_display_links = ('id',) + list_filter = ('task', 'date') + ordering = ('-id',) + + fieldsets = ( + (_('Work'), { + 'fields': ('employee', + 'date', + 'start_time', + 'stop_time', + 'short_description', + 'description', + 'task') + }), + ) + save_as = True + + +class InlineWork(admin.TabularInline): + model = Work + readonly_fields = ('employee', + 'short_description', + 'date', + 'start_time', + 'stop_time',) + fieldsets = ( + (_('Work'), { + 'fields': ('employee', + 'short_description', + 'date', + 'start_time', + 'stop_time',) + }), + ) + extra = 0 + + def has_add_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/koalixcrm/crm/static/default_templates/de/deliveryorder.xsl b/koalixcrm/crm/static/default_templates/de/deliveryorder.xsl index 400ac23f..3c47dcb3 100644 --- a/koalixcrm/crm/static/default_templates/de/deliveryorder.xsl +++ b/koalixcrm/crm/static/default_templates/de/deliveryorder.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/de/invoice.xsl b/koalixcrm/crm/static/default_templates/de/invoice.xsl index c8866036..9310e62b 100644 --- a/koalixcrm/crm/static/default_templates/de/invoice.xsl +++ b/koalixcrm/crm/static/default_templates/de/invoice.xsl @@ -55,7 +55,7 @@ text-align="left" >emailaddress@gmail.com +41 79 xxx xx xx + text-align="left" >+41 xx xxx xx xx @@ -304,20 +304,21 @@ font-family="BitstreamVeraSans" color="black" text-align="left" - margin-top="2cm" + margin-top="1cm" linefeed-treatment="preserve"> - - Rechnung - + margin-top="1cm" + margin-bottom="0.5cm"> + Rechnung - @@ -583,6 +584,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/de/purchaseconfirmation.xsl b/koalixcrm/crm/static/default_templates/de/purchaseconfirmation.xsl index 6a2e7aa1..bf69bb60 100644 --- a/koalixcrm/crm/static/default_templates/de/purchaseconfirmation.xsl +++ b/koalixcrm/crm/static/default_templates/de/purchaseconfirmation.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/de/purchaseorder.xsl b/koalixcrm/crm/static/default_templates/de/purchaseorder.xsl index 022f2163..8e3eff91 100644 --- a/koalixcrm/crm/static/default_templates/de/purchaseorder.xsl +++ b/koalixcrm/crm/static/default_templates/de/purchaseorder.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/de/quote.xsl b/koalixcrm/crm/static/default_templates/de/quote.xsl index 74ff96a5..7ea75f61 100644 --- a/koalixcrm/crm/static/default_templates/de/quote.xsl +++ b/koalixcrm/crm/static/default_templates/de/quote.xsl @@ -304,20 +304,21 @@ font-family="BitstreamVeraSans" color="black" text-align="left" - margin-top="2cm" + margin-top="1cm" linefeed-treatment="preserve"> - - Offerte + margin-top="1cm" + margin-bottom="0.5cm"> + Offerte - @@ -326,9 +327,10 @@ font-family="BitstreamVeraSans" color="black" text-align="left" - margin-top="2cm" - linefeed-treatment="preserve" - page-break-after="always"> + margin-top="0.5cm" + margin-bottom="0.5cm" + linefeed-treatment="preserve" + page-break-after="always"> @@ -583,6 +585,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/en/deliveryorder.xsl b/koalixcrm/crm/static/default_templates/en/deliveryorder.xsl index 6d0cfacb..2d857e31 100644 --- a/koalixcrm/crm/static/default_templates/en/deliveryorder.xsl +++ b/koalixcrm/crm/static/default_templates/en/deliveryorder.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/en/invoice.xsl b/koalixcrm/crm/static/default_templates/en/invoice.xsl index f16a99f7..26d4f0f9 100644 --- a/koalixcrm/crm/static/default_templates/en/invoice.xsl +++ b/koalixcrm/crm/static/default_templates/en/invoice.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/en/purchaseconfirmation.xsl b/koalixcrm/crm/static/default_templates/en/purchaseconfirmation.xsl index b4592b85..ae49284e 100644 --- a/koalixcrm/crm/static/default_templates/en/purchaseconfirmation.xsl +++ b/koalixcrm/crm/static/default_templates/en/purchaseconfirmation.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/en/purchaseorder.xsl b/koalixcrm/crm/static/default_templates/en/purchaseorder.xsl index 022f2163..8e3eff91 100644 --- a/koalixcrm/crm/static/default_templates/en/purchaseorder.xsl +++ b/koalixcrm/crm/static/default_templates/en/purchaseorder.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/default_templates/en/quote.xsl b/koalixcrm/crm/static/default_templates/en/quote.xsl index 022f2163..8e3eff91 100644 --- a/koalixcrm/crm/static/default_templates/en/quote.xsl +++ b/koalixcrm/crm/static/default_templates/en/quote.xsl @@ -583,6 +583,7 @@ + diff --git a/koalixcrm/crm/static/taskslookup.js b/koalixcrm/crm/static/taskslookup.js new file mode 100644 index 00000000..d1563d6f --- /dev/null +++ b/koalixcrm/crm/static/taskslookup.js @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/koalixcrm/crm/tasks.py b/koalixcrm/crm/tasks.py deleted file mode 100644 index d055d957..00000000 --- a/koalixcrm/crm/tasks.py +++ /dev/null @@ -1,33 +0,0 @@ -import io - -from celery import shared_task -from django.conf import settings -from django.core.management import call_command - -from koalixcrm.crm.contact.customer import Customer -from koalixcrm.crm.contact.supplier import Supplier - -def import_contact_data(input_file, contact_type, current_user): - _import_contact_data.delay(input_file, contact_type, current_user) - -@shared_task -def _import_contact_data(input_file, contact_type, current_user): - out = io.StringIO() - contact = None - try: - call_command('importcontactdata', - excel_file=input_file, - contact_type=contact_type, - current_user=str(current_user), - stdout=out) - value = out.getvalue() - print('Number of contacts imported: {}'.format(value)) - - except ValueError as e: - value = None - if contact is not None: - contact.set_error() - error_message = "Sorry, the input file is not valid: {}".format(e) - raise - - \ No newline at end of file diff --git a/koalixcrm/crm/templates/crm/admin/registerPayment.html b/koalixcrm/crm/templates/crm/admin/registerPayment.html deleted file mode 100644 index a9f20759..00000000 --- a/koalixcrm/crm/templates/crm/admin/registerPayment.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "admin/base.html" %} -{% block title %} -Register Payment Amount -{% endblock %} - -{% block content %} - -
{% csrf_token %} - -{% for obj in queryset %} - - - - - - - -{% endfor %} - - - - - -{% endblock %} - diff --git a/koalixcrm/crm/templates/crm/admin/register_payment.html b/koalixcrm/crm/templates/crm/admin/register_payment.html new file mode 100644 index 00000000..cec83ba8 --- /dev/null +++ b/koalixcrm/crm/templates/crm/admin/register_payment.html @@ -0,0 +1,35 @@ +{% extends "admin/base.html" %} +{% block title %} +Register Payment Amount +{% endblock %} + +{% block content %} + +{% csrf_token %} +
- Amount to Register: - - {{ form }} - - Amount on Invoice: - - {{ obj.lastCalculatedPrice }} -
+{% for obj in queryset %} + + + {{ form }} + + + + + + +{% endfor %} + +{% endblock %} + diff --git a/koalixcrm/crm/templates/crm/admin/time_reporting.html b/koalixcrm/crm/templates/crm/admin/time_reporting.html new file mode 100644 index 00000000..07e71c5b --- /dev/null +++ b/koalixcrm/crm/templates/crm/admin/time_reporting.html @@ -0,0 +1,104 @@ +{% extends "admin/base.html" %} +{% block extrahead %} + +{% endblock %} +{% block title %} +Reporting +{% endblock %} + +{% block content %} +{% csrf_token %} + {{ formset.management_form }}
+ {{ range_selection_form}}

+
+
+ Amount to Register: + + Amount on Invoice: + + {{ obj.last_calculated_price }} +
+ + + + + +
+ {% for form in formset %} + {% if forloop.first %} + + {% for field in form.visible_fields %} + + {% endfor %} + + {% endif %} + {% for field in form.visible_fields %} + + {% endfor %} + {% for field in form.hidden_fields %}{{ field }} + {% endfor %} + {% endfor %} +
{{ field.label }}
{{ field }}
{{ field.errors }}
+ +
+
+ + + + + +{% endblock %} + diff --git a/koalixcrm/crm/tests/__init__.py b/koalixcrm/crm/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/koalixcrm/crm/tests/test_calculations_document.py b/koalixcrm/crm/tests/test_calculations_document.py new file mode 100644 index 00000000..e3001bea --- /dev/null +++ b/koalixcrm/crm/tests/test_calculations_document.py @@ -0,0 +1,114 @@ +from django.test import TestCase +from koalixcrm.crm.models import Contract +from koalixcrm.crm.models import Customer +from koalixcrm.crm.models import CustomerGroup +from koalixcrm.crm.models import CustomerBillingCycle +from koalixcrm.crm.models import Currency +from koalixcrm.crm.models import Product +from koalixcrm.crm.models import Tax +from koalixcrm.crm.models import Unit +from koalixcrm.crm.models import Quote +from koalixcrm.crm.models import Price +from koalixcrm.crm.models import SalesDocumentPosition +from django.contrib.auth.models import User +from koalixcrm.crm.documents.calculations import Calculations +import datetime + + +class DocumentCalculationsTest(TestCase): + def setUp(self): + datetime_now = datetime.datetime(2024, 1, 1, 0, 00) + valid_from = (datetime_now - datetime.timedelta(days=30)).date() + valid_until = (datetime_now + datetime.timedelta(days=30)).date() + date_now = datetime_now.date() + test_billing_cycle = CustomerBillingCycle.objects.create( + name="30 days to pay", + time_to_payment_date = 30, + payment_reminder_time_to_payment = 10 + ) + test_user = User.objects.create( + username='Username', + password="Userone") + test_customer_group = CustomerGroup.objects.create( + name = "Tripple A" + ) + test_customer = Customer.objects.create( + name="John Smith", + last_modified_by=test_user, + default_customer_billing_cycle=test_billing_cycle, + ) + test_customer.is_member_of=[test_customer_group,] + test_customer.save() + test_currency = Currency.objects.create( + description="Swiss Francs", + short_name = "CHF", + rounding = 0.05, + ) + test_contract = Contract.objects.create( + staff=test_user, + description = "This is a test contract", + default_customer = test_customer, + default_currency = test_currency, + last_modification = date_now, + last_modified_by = test_user + ) + test_unit = Unit.objects.create( + description="Kilogram", + short_name = "kg", + ) + test_tax = Tax.objects.create( + tax_rate = 7.7, + name = "MwSt 7.7%") + test_quote = Quote.objects.create( + valid_until=valid_until, + status="C", + contract=test_contract, + external_reference = "ThisIsAnExternalReference", + discount = "11.23", + description = "ThisIsATestOffer", + customer = test_customer, + staff = test_user, + currency = test_currency, + date_of_creation = date_now, + last_modified_by = test_user,) + for i in range(10): + test_product = Product.objects.create( + description="This is a test product " + i.__str__(), + title = "This is a test product " + i.__str__(), + product_number = 12334235+i, + default_unit = test_unit, + last_modification = date_now, + last_modified_by = test_user, + tax = test_tax, + ) + Price.objects.create( + product=test_product, + unit = test_unit, + currency = test_currency, + customer_group = test_customer_group, + price = i*100, + valid_from = valid_from, + valid_until = valid_until, + ) + SalesDocumentPosition.objects.create( + sales_document = test_quote, + position_number=i*10, + quantity = 0.333*i, + description = "This is a Testposition " + i.__str__(), + discount = i*5, + product = test_product, + unit = test_unit, + overwrite_product_price = False, + ) + def test_calculate_document_price(self): + datetime_now = datetime.datetime(2024, 1, 1, 0, 00) + date_now = datetime_now.date() + test_quote = Quote.objects.get(description="ThisIsATestOffer") + Calculations.calculate_document_price( + document=test_quote, + pricing_date=date_now) + self.assertEqual( + (test_quote.last_calculated_price).__str__(), "5431.50") + self.assertEqual( + (test_quote.last_calculated_tax).__str__(), "418.05") + diff --git a/koalixcrm/crm/tests/test_calculations_reporting.py b/koalixcrm/crm/tests/test_calculations_reporting.py new file mode 100644 index 00000000..56583ad3 --- /dev/null +++ b/koalixcrm/crm/tests/test_calculations_reporting.py @@ -0,0 +1,180 @@ +from django.test import TestCase +from django.test import LiveServerTestCase +from koalixcrm.crm.models import Contract +from koalixcrm.crm.models import Customer +from koalixcrm.crm.models import CustomerGroup +from koalixcrm.crm.models import CustomerBillingCycle +from koalixcrm.crm.models import Currency +from koalixcrm.crm.models import Task +from koalixcrm.crm.models import TaskStatus +from koalixcrm.djangoUserExtension.models import UserExtension +from koalixcrm.djangoUserExtension.models import TemplateSet +from koalixcrm.crm.models import Work +from koalixcrm.crm.models import EmployeeAssignmentToTask +from django.contrib.auth.models import User +from selenium import webdriver +from selenium.common.exceptions import TimeoutException +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +import datetime + + +class ReportingCalculationsTest(TestCase): + def setUp(self): + datetime_now=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() + date_now=datetime_now.date() + test_billing_cycle=CustomerBillingCycle.objects.create( + name="30 days to pay", + time_to_payment_date=30, + payment_reminder_time_to_payment=10 + ) + test_user=User.objects.create_superuser( + username='admin', + password='admin', + email='admin@admin.com') + test_customer_group=CustomerGroup.objects.create( + name="Tripple A" + ) + test_customer=Customer.objects.create( + name="John Smith", + last_modified_by=test_user, + default_customer_billing_cycle=test_billing_cycle, + ) + test_customer.is_member_of=[test_customer_group,] + test_customer.save() + test_currency=Currency.objects.create( + description="Swiss Francs", + short_name="CHF", + rounding=0.05, + ) + test_template_set=TemplateSet.objects.create( + title="Just an empty Template Set" + ) + UserExtension.objects.create( + user=test_user, + defaultTemplateSet = test_template_set, + defaultCurrency = test_currency + ) + test_contract=Contract.objects.create( + staff=test_user, + description="This is a test contract", + default_customer=test_customer, + default_currency=test_currency, + last_modification=date_now, + last_modified_by=test_user + ) + test_task_status=TaskStatus.objects.create( + title="planned", + description="This represents the state when something has been planned but not yet started", + is_done=False + ) + Task.objects.create( + short_description="Test Task", + planned_start_date=start_date, + planned_end_date=end_date, + project=test_contract, + description="This is a simple test task", + status=test_task_status, + last_status_change=date_now + ) + + def test_calculate_document_price(self): + datetime_now = datetime.datetime(2024, 1, 1, 0, 00) + datetime_later = datetime.datetime(2024, 1, 1, 2, 00) + datetime_even_later = datetime.datetime(2024, 1, 1, 3, 30) + date_now = datetime_now.date() + test_task = Task.objects.get(short_description="Test Task") + self.assertEqual( + (test_task.planned_duration()).__str__(), "60 days, 0:00:00") + self.assertEqual( + (test_task.planned_effort()).__str__(), "0 h") + test_user = User.objects.get(username="admin") + test_employee = UserExtension.objects.get(user=test_user) + EmployeeAssignmentToTask.objects.create( + employee=test_employee, + planned_effort="2.00", + task=test_task + ) + EmployeeAssignmentToTask.objects.create( + employee=test_employee, + planned_effort="1.50", + task=test_task + ) + self.assertEqual( + (test_task.planned_effort()).__str__(), "3.50 h") + self.assertEqual( + (test_task.effective_effort()).__str__(), "0.0 h") + Work.objects.create( + employee=test_employee, + date=date_now, + start_time=datetime_now, + stop_time=datetime_later, + short_description="Not really relevant", + description="Performed some hard work", + task=test_task + ) + Work.objects.create( + employee=test_employee, + date=date_now, + start_time=datetime_later, + stop_time=datetime_even_later, + short_description="Not really relevant 2nd part", + description="Performed some hard work 2nd part", + task=test_task + ) + self.assertEqual( + (test_task.effective_effort()).__str__(), "3.5 h") + + +class ReportingCalculationsUITest(LiveServerTestCase): + + def setUp(self): + firefox_options = webdriver.firefox.options.Options() + firefox_options.set_headless(headless=True) + self.selenium = webdriver.Firefox(firefox_options=firefox_options) + prepare_test = ReportingCalculationsTest() + prepare_test.setUp() + + def tearDown(self): + self.selenium.quit() + + def test_registration_of_work(self): + selenium = self.selenium + #login + selenium.get('%s%s' % (self.live_server_url, '/admin/')) + timeout = 5 + try: + element_present = EC.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("admin") + submit_button.send_keys(Keys.RETURN) + try: + element_present = EC.presence_of_element_located((By.ID, 'module_1_7')) + WebDriverWait(selenium, timeout).until(element_present) + except TimeoutException: + print("Timed out waiting for page to load") + #Opening the link we want to test + selenium.get('%s%s' % (self.live_server_url, '/koalixcrm/crm/reporting/monthlyreport/')) + try: + element_present = EC.presence_of_element_located((By.ID, 'id_form-0-projects')) + WebDriverWait(selenium, timeout).until(element_present) + except TimeoutException: + print("Timed out waiting for page to load") + #find the form element + project = selenium.find_element_by_xpath('//*[@id="id_form-0-projects"]') + 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"]') + save = selenium.find_element_by_name('save') \ No newline at end of file diff --git a/koalixcrm/crm/views.py b/koalixcrm/crm/views.py deleted file mode 100644 index 1a99bd21..00000000 --- a/koalixcrm/crm/views.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -from os import path -from wsgiref.util import FileWrapper -from django.contrib import messages -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 * -import koalixcrm - - -def export_pdf(calling_model_admin, request, document, 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 - document (Contract): 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 - - Returns: - HTTpResponse with a PDF when successful - HTTpResponseRedirect when not successful - - Raises: - raises Http404 exception if anything goes wrong""" - try: - pdf = document.create_pdf() - response = HttpResponse(FileWrapper(open(pdf, 'rb')), content_type='application/pdf') - response['Content-Length'] = path.getsize(pdf) - except (TemplateSetMissing, UserExtensionMissing, CalledProcessError, UserExtensionEmailAddressMissing, UserExtensionPhoneAddressMissing) as e: - if isinstance(e, UserExtensionMissing): - response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, _("User Extension Missing")) - elif isinstance(e, UserExtensionEmailAddressMissing): - response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, _("User Extension Email Missing")) - elif isinstance(e, UserExtensionPhoneAddressMissing): - response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, _("User Extension Phone Missing")) - elif isinstance(e, TemplateSetMissing): - response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, _("Templateset Missing")) - elif isinstance(e, TemplateFOPConfigFileMissing): - response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, _("Fop Config File Missing in TemplateSet")) - elif isinstance(e, TemplateXSLTFileMissing): - response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, _("XSLT File Missing in TemplateSet")) - elif type(e) == CalledProcessError: - response = HttpResponseRedirect(redirect_to) - calling_model_admin.message_user(request, e.output) - else: - raise Http404 - return response - - -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""" - 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, koalixcrm.crm.documents.contract.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 diff --git a/koalixcrm/crm/views/__init__.py b/koalixcrm/crm/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/koalixcrm/crm/views/monthlyreport.py b/koalixcrm/crm/views/monthlyreport.py new file mode 100644 index 00000000..4436599d --- /dev/null +++ b/koalixcrm/crm/views/monthlyreport.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.template.context_processors import csrf +from django.contrib.admin.widgets import * +from koalixcrm.djangoUserExtension.models import UserExtension +from koalixcrm.crm.reporting.task import Task +from koalixcrm.crm.documents.contract import Contract +import datetime + + +class RangeSelectionForm(forms.Form): + 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) + + +class BaseWorkEntryFormset(forms.models.BaseFormSet): + def __init__(self,*args, **kwargs): + super(BaseWorkEntryFormset, self).__init__(*args, **kwargs) + + +class WorkEntry(forms.Form): + task_list = Task.objects.all() + projects = forms.ModelChoiceField(Contract.objects.all()) + task = forms.ModelChoiceField(task_list) + date = forms.DateField(widget=AdminDateWidget) + start_time = forms.TimeField(widget=AdminTimeWidget) + stop_time = forms.TimeField(widget=AdminTimeWidget) + description = forms.CharField(widget=AdminTextareaWidget) + 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 + project_list = [] + for task_element in self.task_list: + project_list.append(task_element.project) + super(WorkEntry, self).__init__(*args, **kwargs) + + def clean_date(self): + date = self.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') + return date + + +def generate_initial_data(start_date, stop_date, employee): + from koalixcrm.crm.reporting.work import Work + list_of_work = Work.objects.filter(employee=employee).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, + 'projects': work.task.project, + 'date': work.date, + 'start_time': work.start_time, + 'stop_time': work.stop_time, + 'description': work.description}) + return initial + + +def evaluate_pre_check_from_date(range_selection_form): + original_from_date = range_selection_form.cleaned_data['original_from_date'] + new_from_date = range_selection_form.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(range_selection_form): + original_to_date = range_selection_form.cleaned_data['original_to_date'] + new_to_date = range_selection_form.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 load_formset(range_selection_form, request): + WorkEntryFormSet = forms.formset_factory(WorkEntry, + extra=1, + max_num=60, + can_delete=True, + formset=BaseWorkEntryFormset) + from_date = evaluate_pre_check_from_date(range_selection_form) + to_date = evaluate_pre_check_to_date(range_selection_form) + form_kwargs = compose_form_kwargs(from_date, to_date) + pre_check_formset = WorkEntryFormSet(request.POST, + form_kwargs=form_kwargs) + return pre_check_formset + + +def compose_form_kwargs(from_date, to_date): + form_kwargs = {'from_date': from_date, 'to_date': to_date} + return form_kwargs + + +def create_updated_formset(range_selection_form, request): + WorkEntryFormSet = forms.formset_factory(WorkEntry, + extra=1, + max_num=60, + can_delete=True, + formset=BaseWorkEntryFormset) + employee = UserExtension.get_user_extension(request.user) + from_date = range_selection_form.cleaned_data['from_date'] + to_date = range_selection_form.cleaned_data['to_date'] + initial_formset_data = generate_initial_data(from_date, + to_date, + employee) + form_kwargs = compose_form_kwargs(from_date, to_date) + formset = WorkEntryFormSet(initial=initial_formset_data, + form_kwargs=form_kwargs) + return formset + + +def create_new_formset(from_date, to_date, request): + WorkEntryFormSet = forms.formset_factory(WorkEntry, + extra=1, + max_num=60, + can_delete=True, + formset=BaseWorkEntryFormset) + employee = UserExtension.get_user_extension(request.user) + initial_formset_data = generate_initial_data(from_date, + to_date, + employee) + form_kwargs = compose_form_kwargs(from_date, to_date) + formset = WorkEntryFormSet(initial=initial_formset_data, + form_kwargs=form_kwargs) + return formset + + +def update_work(form, request): + from koalixcrm.crm.reporting.work import Work + if form.has_changed(): + if form.cleaned_data['work_id']: + work = Work.objects.get(id=form.cleaned_data['work_id']) + else: + work = Work() + if form.cleaned_data['DELETE']: + work.delete() + else: + work.task = form.cleaned_data['task'] + work.employee = UserExtension.get_user_extension(request.user) + work.date = form.cleaned_data['date'] + work.start_time = datetime.datetime.combine(form.cleaned_data['date'], + form.cleaned_data['start_time']) + work.stop_time = datetime.datetime.combine(form.cleaned_data['date'], + form.cleaned_data['stop_time']) + work.description = form.cleaned_data['description'] + work.save() + + +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 + + +def update_range_selection_form(old_range_selection_form): + from_date = old_range_selection_form.cleaned_data['from_date'] + to_date = old_range_selection_form.cleaned_data['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 + + +def work_report(request): + if request.POST.get('post'): + if 'cancel' in request.POST: + HttpResponseRedirect('/admin/') + elif 'save' in request.POST: + range_selection_form = RangeSelectionForm(request.POST) + if range_selection_form.is_valid(): + formset = 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: + update_work(form,request) + formset = create_updated_formset(range_selection_form, request) + range_selection_form = update_range_selection_form(range_selection_form) + c = {'range_selection_form': range_selection_form, + 'formset': formset} + c.update(csrf(request)) + return render(request, 'crm/admin/time_reporting.html', c) + 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 = create_range_selection_form(from_date, to_date) + formset = create_new_formset(from_date, to_date, request) + c = {'formset': formset, + 'range_selection_form': range_selection_form} + c.update(csrf(request)) + return render(request, 'crm/admin/time_reporting.html', c) + diff --git a/koalixcrm/crm/views/newdocument.py b/koalixcrm/crm/views/newdocument.py new file mode 100644 index 00000000..845097ad --- /dev/null +++ b/koalixcrm/crm/views/newdocument.py @@ -0,0 +1,55 @@ +# -*- 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 diff --git a/koalixcrm/crm/views/pdfexport.py b/koalixcrm/crm/views/pdfexport.py new file mode 100644 index 00000000..d29830c4 --- /dev/null +++ b/koalixcrm/crm/views/pdfexport.py @@ -0,0 +1,64 @@ +# -*- 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.accounting.exceptions import * + + +class PDFExportView: + def export_pdf(calling_model_admin, request, document, redirect_to, template_to_use): + """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 + document (Contract): 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 = document.create_pdf(template_to_use, request.user) + response = HttpResponse(FileWrapper(open(pdf, 'rb')), content_type='application/pdf') + response['Content-Length'] = path.getsize(pdf) + except (TemplateSetMissing, + UserExtensionMissing, + CalledProcessError, + UserExtensionEmailAddressMissing, + UserExtensionPhoneAddressMissing) as e: + if isinstance(e, UserExtensionMissing): + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, _("User Extension Missing")) + elif isinstance(e, UserExtensionEmailAddressMissing): + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, _("User Extension Email Missing")) + elif isinstance(e, UserExtensionPhoneAddressMissing): + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, _("User Extension Phone Missing")) + elif isinstance(e, TemplateSetMissing): + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, _("Templateset Missing")) + elif isinstance(e, TemplateFOPConfigFileMissing): + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, _("Fop Config File Missing in TemplateSet")) + elif isinstance(e, TemplateXSLTFileMissing): + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, _("XSLT File Missing in TemplateSet")) + elif type(e) == CalledProcessError: + response = HttpResponseRedirect(redirect_to) + calling_model_admin.message_user(request, e.output) + else: + raise Http404 + return response diff --git a/koalixcrm/crm/views/restinterface.py b/koalixcrm/crm/views/restinterface.py new file mode 100644 index 00000000..1e2de7fd --- /dev/null +++ b/koalixcrm/crm/views/restinterface.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from rest_framework import viewsets +from koalixcrm.crm.reporting.task import Task, TaskJSONSerializer +from koalixcrm.crm.reporting.taskstatus import TaskStatus, TaskStatusJSONSerializer +from koalixcrm.crm.documents.contract import Contract, ContractJSONSerializer + + + +class TaskAsJSON(viewsets.ReadOnlyModelViewSet): + """ + API endpoint that allows users to be viewed. + """ + queryset = Task.objects.all() + serializer_class = TaskJSONSerializer + filter_fields = ('project',) + + +class ContractAsJSON(viewsets.ReadOnlyModelViewSet): + """ + API endpoint that allows users to be viewed. + """ + queryset = Contract.objects.all() + serializer_class = ContractJSONSerializer + + +class TaskStatusAsJSON(viewsets.ReadOnlyModelViewSet): + """ + API endpoint that allows users to be viewed. + """ + queryset = TaskStatus.objects.all() + serializer_class = TaskStatusJSONSerializer \ No newline at end of file diff --git a/koalixcrm/djangoUserExtension/models.py b/koalixcrm/djangoUserExtension/models.py index e473cc05..84cacf3f 100644 --- a/koalixcrm/djangoUserExtension/models.py +++ b/koalixcrm/djangoUserExtension/models.py @@ -15,6 +15,37 @@ class UserExtension(models.Model): defaultTemplateSet = models.ForeignKey("TemplateSet") defaultCurrency = models.ForeignKey("crm.Currency") + @staticmethod + def objects_to_serialize(object_to_create_pdf, reference_user): + from koalixcrm.crm.contact.phoneaddress import PhoneAddress + from koalixcrm.crm.contact.emailaddress 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] + class Meta: app_label = "djangoUserExtension" verbose_name = _('User Extention') diff --git a/koalixcrm/subscriptions/admin.py b/koalixcrm/subscriptions/admin.py index 9bc8061a..fba82bf1 100644 --- a/koalixcrm/subscriptions/admin.py +++ b/koalixcrm/subscriptions/admin.py @@ -44,14 +44,14 @@ class OptionSubscription(admin.ModelAdmin): @staticmethod def createInvoice(queryset): for obj in queryset: - invoice = obj.createInvoice() + invoice = obj.create_invoice() response = HttpResponseRedirect('/admin/crm/invoice/' + str(invoice.id)) return response @staticmethod def createQuote(queryset): for obj in queryset: - invoice = obj.createInvoice() + invoice = obj.create_invoice() response = HttpResponseRedirect('/admin/crm/invoice/' + str(invoice.id)) return response @@ -103,8 +103,6 @@ class KoalixcrmPluginInterface(object): quoteActions = [] customerInlines = [] customerActions = [] - personInline = [] - personActions = [] admin.site.register(Subscription, OptionSubscription) diff --git a/log.txt b/log.txt deleted file mode 100644 index 5de23c83..00000000 --- a/log.txt +++ /dev/null @@ -1 +0,0 @@ -get User id : 1 \ No newline at end of file diff --git a/manage.py b/manage.py index b2ef6790..1cf43233 100755 --- a/manage.py +++ b/manage.py @@ -2,14 +2,6 @@ import os import sys -'''import ptvsd -try: - ptvsd.enable_attach("my_secret", address = ('0.0.0.0', 3000)) - ptvsd.wait_for_attach() - ptvsd.break_into_debugger() -except: - pass''' - if __name__ == "__main__": try: from django.core.management import execute_from_command_line diff --git a/projectsettings/__init__.py b/projectsettings/__init__.py index 7c68785e..40a96afc 100644 --- a/projectsettings/__init__.py +++ b/projectsettings/__init__.py @@ -1 +1 @@ -# -*- coding: utf-8 -*- \ No newline at end of file +# -*- coding: utf-8 -*- diff --git a/projectsettings/dashboard.py b/projectsettings/dashboard.py index 90c3221b..1fef5f18 100644 --- a/projectsettings/dashboard.py +++ b/projectsettings/dashboard.py @@ -22,7 +22,7 @@ def init_with_context(self, context): site_name = get_admin_site_name(context) self.children.append(modules.Group( - _('CRMLite (based on koalixcrm V1.12dev2)'), + _('Group: koalixcrm V1.12dev2'), column=1, collapsible=True, children = [ @@ -48,22 +48,13 @@ def init_with_context(self, context): column=1, css_classes=('collapse closed',), models=('koalixcrm.crm.contact.contact.CallForContact', - 'koalixcrm.crm.documents.visit.Visit', - 'koalixcrm.crm.contact.data_import.ContactImportData'), - ), - modules.ModelList( - _('Data Import'), - column=1, - css_classes=('collapse closed',), - models=('koalixcrm.crm.contact.contact.ContactImportData'), + 'koalixcrm.crm.contact.contact.VisitForContact',), ), modules.ModelList( _('Products'), column=1, css_classes=('collapse closed',), - models=('koalixcrm.crm.product.product.Product', - 'koalixcrm.crm.product.attribute.AttributeSet', - 'koalixcrm.crm.product.attribute.Attribute'), + models=('koalixcrm.crm.product.product.Product',), ), modules.ModelList( _('Contacts'), @@ -71,7 +62,7 @@ def init_with_context(self, context): css_classes=('collapse closed',), models=('koalixcrm.crm.contact.customer.Customer', 'koalixcrm.crm.contact.supplier.Supplier', - 'koalixcrm.crm.contact.person.Person'), + 'koalixcrm.crm.contact.person.Person',), ), modules.ModelList( _('Accounting'), @@ -79,6 +70,20 @@ def init_with_context(self, context): css_classes=('collapse closed',), models=('koalixcrm.accounting.*',), ), + modules.ModelList( + _('Reporting'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.reporting.*',), + ), + modules.LinkList( + _('Report Personaly work'), + column=1, + children=[{'title': _('Monthly Report'), + 'url': '/koalixcrm/crm/reporting/monthlyreport/', + 'external': False,},] + ) + ] )) @@ -95,7 +100,7 @@ def init_with_context(self, context): models=('django.contrib.*',), ), modules.ModelList( - _('Settings'), + _('koalixcrm Settings'), column=1, css_classes=('collapse closed',), models=('koalixcrm.crm.contact.customerbillingcycle.CustomerBillingCycle', diff --git a/projectsettings/environment/development.env b/projectsettings/environment/development.env deleted file mode 100644 index 9ea08f45..00000000 --- a/projectsettings/environment/development.env +++ /dev/null @@ -1,26 +0,0 @@ -##### -# Environment -##### - -PRODUCTION=false -JAVA_HOME=/usr/bin/jdk1.8.0_162/jre - - -##### -# Django -##### - -DJANGO_SETTINGS_MODULE=projectsettings.settings.development_settings - -##### -# Nginx -##### - -# Server name used in nginx.tmpl (projectsettings/services/webserver/config/nginx.tmpl) -NGINX_SERVER_NAME=localhost - -# Here you can specify the location of your SSL/TLS certificate and key. -# This will be specified in the nginx.tmpl When specifying localhost it will -# use the self-signed certificate provided by this project -NGINX_CRT_NAME=localhost -NGINX_KEY_NAME=localhost diff --git a/projectsettings/media/data_files/contatti_test_import.xlsx b/projectsettings/media/data_files/contatti_test_import.xlsx deleted file mode 100644 index fc28fea5..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_14c4p1e.xlsx b/projectsettings/media/data_files/contatti_test_import_14c4p1e.xlsx deleted file mode 100644 index 8d784182..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_14c4p1e.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_2Ecm1nx.xlsx b/projectsettings/media/data_files/contatti_test_import_2Ecm1nx.xlsx deleted file mode 100644 index 9a3c616d..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_2Ecm1nx.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_2Q0f0Zy.xlsx b/projectsettings/media/data_files/contatti_test_import_2Q0f0Zy.xlsx deleted file mode 100644 index 9a3c616d..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_2Q0f0Zy.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_59jaJH0.xlsx b/projectsettings/media/data_files/contatti_test_import_59jaJH0.xlsx deleted file mode 100644 index 8d784182..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_59jaJH0.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_7b3skjA.xlsx b/projectsettings/media/data_files/contatti_test_import_7b3skjA.xlsx deleted file mode 100644 index 5f304f8c..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_7b3skjA.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_8aYQ3xJ.xlsx b/projectsettings/media/data_files/contatti_test_import_8aYQ3xJ.xlsx deleted file mode 100644 index 9a3c616d..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_8aYQ3xJ.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_90JUfUo.xlsx b/projectsettings/media/data_files/contatti_test_import_90JUfUo.xlsx deleted file mode 100644 index 9a3c616d..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_90JUfUo.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_9lsOIXx.xlsx b/projectsettings/media/data_files/contatti_test_import_9lsOIXx.xlsx deleted file mode 100644 index 5f304f8c..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_9lsOIXx.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_Fm0MG2h.xlsx b/projectsettings/media/data_files/contatti_test_import_Fm0MG2h.xlsx deleted file mode 100644 index 238cb914..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_Fm0MG2h.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_JTAMGeo.xlsx b/projectsettings/media/data_files/contatti_test_import_JTAMGeo.xlsx deleted file mode 100644 index ed94e7c4..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_JTAMGeo.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_KXuANep.xlsx b/projectsettings/media/data_files/contatti_test_import_KXuANep.xlsx deleted file mode 100644 index c2811205..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_KXuANep.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_L6y0dNG.xlsx b/projectsettings/media/data_files/contatti_test_import_L6y0dNG.xlsx deleted file mode 100644 index 8d784182..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_L6y0dNG.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_MyQf5YE.xlsx b/projectsettings/media/data_files/contatti_test_import_MyQf5YE.xlsx deleted file mode 100644 index ad5c9ff3..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_MyQf5YE.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_PhiTotE.xlsx b/projectsettings/media/data_files/contatti_test_import_PhiTotE.xlsx deleted file mode 100644 index fc28fea5..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_PhiTotE.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_Pp3Y24G.xlsx b/projectsettings/media/data_files/contatti_test_import_Pp3Y24G.xlsx deleted file mode 100644 index 5f304f8c..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_Pp3Y24G.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_RBsEYue.xlsx b/projectsettings/media/data_files/contatti_test_import_RBsEYue.xlsx deleted file mode 100644 index eb2d1a2e..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_RBsEYue.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_TMu9Rpx.xlsx b/projectsettings/media/data_files/contatti_test_import_TMu9Rpx.xlsx deleted file mode 100644 index 9a3c616d..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_TMu9Rpx.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_VNk0e5B.xlsx b/projectsettings/media/data_files/contatti_test_import_VNk0e5B.xlsx deleted file mode 100644 index 5f304f8c..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_VNk0e5B.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_bEqWmXS.xlsx b/projectsettings/media/data_files/contatti_test_import_bEqWmXS.xlsx deleted file mode 100644 index 9a3c616d..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_bEqWmXS.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_e7bCzzS.xlsx b/projectsettings/media/data_files/contatti_test_import_e7bCzzS.xlsx deleted file mode 100644 index ad5c9ff3..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_e7bCzzS.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_fhn9Ujs.xlsx b/projectsettings/media/data_files/contatti_test_import_fhn9Ujs.xlsx deleted file mode 100644 index eb2d1a2e..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_fhn9Ujs.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_gqwz2BY.xlsx b/projectsettings/media/data_files/contatti_test_import_gqwz2BY.xlsx deleted file mode 100644 index 9a3c616d..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_gqwz2BY.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_l0huEta.xlsx b/projectsettings/media/data_files/contatti_test_import_l0huEta.xlsx deleted file mode 100644 index fc28fea5..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_l0huEta.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/contatti_test_import_qt1Ckwh.xlsx b/projectsettings/media/data_files/contatti_test_import_qt1Ckwh.xlsx deleted file mode 100644 index ed94e7c4..00000000 Binary files a/projectsettings/media/data_files/contatti_test_import_qt1Ckwh.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/fornitori.xlsx b/projectsettings/media/data_files/fornitori.xlsx deleted file mode 100644 index 63b14cdb..00000000 Binary files a/projectsettings/media/data_files/fornitori.xlsx and /dev/null differ diff --git a/projectsettings/media/data_files/fornitori_Y8lxpZd.xlsx b/projectsettings/media/data_files/fornitori_Y8lxpZd.xlsx deleted file mode 100644 index 63b14cdb..00000000 Binary files a/projectsettings/media/data_files/fornitori_Y8lxpZd.xlsx and /dev/null differ diff --git a/projectsettings/services/django-uwsgi.ini b/projectsettings/services/django-uwsgi.ini deleted file mode 100644 index 1c8478ff..00000000 --- a/projectsettings/services/django-uwsgi.ini +++ /dev/null @@ -1,45 +0,0 @@ -##### -# uWSGI configuration -# -# Change settings however you see fit. See following link for more in depth -# explanation of settings: -# http://ow.ly/IoWN7, http://ow.ly/IoYtp and http://ow.ly/IoWZp -##### - -[uwsgi] -uid = django -gid = django - -master = True -lazy-apps = True - -# Number of worker processes for handling requests -# %k = cpu count -processes = %(%k * 2) - -# Number of threads for handling requests -threads = %(%k * 2) - -# Respawn processes that take more than ... seconds -harakiri = 20 - -# Respawn processes after serving ... requests -max-requests = 5000 - -# Clear environment on exit -vacuum = True - -# the base directory (full path) -chdir = /app/ - -# Django's wsgi file (path starting from chdir/) -module = projectsettings.wsgi:application - -# location of settings -env = DJANGO_SETTINGS_MODULE=projectsettings.settings.development_settings - -# the socket -socket = :8000 - -# touch to reload uwsgi, usage: touch /etc/uwsgi/reload-uwsgi.ini -touch-reload=/etc/uwsgi/reload-uwsgi.ini diff --git a/projectsettings/services/webserver/Dockerfile b/projectsettings/services/webserver/Dockerfile deleted file mode 100644 index 5a50d670..00000000 --- a/projectsettings/services/webserver/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM nginx:1.11 - -# Add start script -ADD ./projectsettings/services/webserver/config/start.sh / -RUN chmod +x start.sh - -# Add nginx config file -ADD ./projectsettings/services/webserver/config/nginx.tmpl / - -# Add SSL certs to location specified in nginx.conf -ADD ./projectsettings/services/webserver/config/*.crt /etc/ssl/certs/ -ADD ./projectsettings/services/webserver/config/*.key /etc/ssl/private/ - -# Execute start script -CMD ["./start.sh"] diff --git a/projectsettings/services/webserver/config/localhost.crt b/projectsettings/services/webserver/config/localhost.crt deleted file mode 100644 index f88c154b..00000000 --- a/projectsettings/services/webserver/config/localhost.crt +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC+zCCAeOgAwIBAgIJAMoaz3CmjG3OMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV -BAMMCWxvY2FsaG9zdDAeFw0xNTAxMjExMTQ2NDNaFw0yNTAxMTgxMTQ2NDNaMBQx -EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAOTEOmKeZevAWzp9pCmfovm3bcxjuNu/GAQhbo+9C3pP57uEg7aHh2eBAOBx -ZT0Vjma1PD9Pi1duolGMmbdX6tTnfPfDoygnALbX/vvYHpcM086Yhs3UNEV/r5HG -7YFhNg9vioWs13rgdaAVSmJtSqX4Ay7BPxdeKYYDkrNOd4eJG5QJsWHfZXaL+4QU -RSj3LQ7Q22iDQ8/lYe8fI/uTE562GGdWniDt846cKlmEswo2M4Y8CL3ZS/7qmn34 -wfb7eCRksaGmucCoO1tIMnj/i1a4RC2oXLnyt+aN5mxIGdlT7h/3ilGGjhXgvTsu -fhHigBQ/QXs26l7YGMjOPWfFbikCAwEAAaNQME4wHQYDVR0OBBYEFCCbv2uewsWb -xbymmpyIAngK3TgRMB8GA1UdIwQYMBaAFCCbv2uewsWbxbymmpyIAngK3TgRMAwG -A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACzzjKtrHgFaJvRWSIRsFrxg -ZczrvoKqYADzQNBTZjCWzU9RHglcW+upACje67p73eDBwlohKHioeOTrZC+CE32N -ueJK3NzmU2WCWlyfmA+/kCRePzKUnxk4+JaOoVjKzXd7w0k8ymadzFjig55w1ylE -Nq4LcNl+HnKi3I6OwlaswRDbdigihZlzZntcuPDx3H2qJz2Qzmbn8f5qZT5TOQtU -VPZwQPZG+/e35EVXzRhDt+/9/XWARqtZiI0htQ5Lhgx1Z/KJuHTAMbK1O3hM4vwK -o+j67P4x8t5z04e9GBfHxrBUuw4g615u0d5SWTvD9gVpuCC4b+GltvdVENiUeew= ------END CERTIFICATE----- diff --git a/projectsettings/services/webserver/config/localhost.key b/projectsettings/services/webserver/config/localhost.key deleted file mode 100644 index c4818867..00000000 --- a/projectsettings/services/webserver/config/localhost.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA5MQ6Yp5l68BbOn2kKZ+i+bdtzGO4278YBCFuj70Lek/nu4SD -toeHZ4EA4HFlPRWOZrU8P0+LV26iUYyZt1fq1Od898OjKCcAttf++9gelwzTzpiG -zdQ0RX+vkcbtgWE2D2+KhazXeuB1oBVKYm1KpfgDLsE/F14phgOSs053h4kblAmx -Yd9ldov7hBRFKPctDtDbaINDz+Vh7x8j+5MTnrYYZ1aeIO3zjpwqWYSzCjYzhjwI -vdlL/uqaffjB9vt4JGSxoaa5wKg7W0gyeP+LVrhELahcufK35o3mbEgZ2VPuH/eK -UYaOFeC9Oy5+EeKAFD9BezbqXtgYyM49Z8VuKQIDAQABAoIBAQCJJqgGVgau140a -PRc9qSpxYipevm6muCNy3oRFgsYt11gMET9SIGDqien3paRXgeP06bUsE8lrarXA -BHZn+Dqe2AYRGOZpfTCtbkO9w21bi+I0Wzd+kTmhbRLaaBQeXCSoDmxTBjPNxGJg -zkLhTbrSVCW8Uzk7DkYAxYWfIG0DAOr25X6Z+ea36ZV/+MKaPesotdqHKMMHpcGq -XkJJMc9zau0R+DP7EkNiVWmBFkDghFzQVUY5Fk14OPUgT8J97715jAcgjP3r5v23 -N6BoNKirE8JoizOWZnoy9K3OZrk0DlFytqvqp89HL4cjl9pn2pIrwmqJgobTTa4g -oe9eKCLtAoGBAPuDXxoCHPvlFUz+q7kF1yLHQm3SIojra05Eng0MzspzlaKn8qZJ -pzEid2svy3rwWaQVQm32rJpbNI1PQnn8HE1FFdKGb16n5yv8+f++MXbtCi2uMVHm -5lDC6bqthJ0Rf6IepAthMohOm3wn4L0URDvLVAkOEl2D7n3Fp9MCwGYLAoGBAOjY -+btPd6mE2P9rmU46HxYWhvkJzeipPvdeU19myAjW7mRLFlfK7zo5rrM/2ggIAEUA -/HsXQnBggEiObN9ZiKOdjO8Da8AUtxFZBOhr8pflpb0Gd+vH3GY+aF+P8/IX5raA -J+WJOPAo+LVI63K92e92zbdjOc0yWNkfVe0xQeEbAoGAK3RgKwMwdMj4rCvYCYib -yfN5Y7YJp3RdPtGWKRUMaWgc81RnFRnmnBOjZE6Grn/2TOYrawpSefzGEKAgESlg -zmzgP1ZgyY7i3vO/UdNPgoej2Y6eENN++XcjoNA3+Fc8oQsANsMdUvWZ4ZKWPrTQ -sTAJHeNjguq3et16iHeSPE8CgYBPE8kUPPPFpXFQpUkmUQTXqoTiNNsyJbBVsDoe -TzimD+BcPUf0Pt4p5wcWpxuz2IvG44PbhdWxdoR6n6wH2k9WSZ5gWcgRLrfRc2sA -SBDWV06HOJgngfOu4Yju02Okl0tqNXHXOVgV78QvRPl5OVK3SaCppFAwCOY1rreu -2yilewKBgQDmRp/UM8HL9GfQc1mBhVIOjYqEWknN30TipJpY3LyKMdiNkWs7TGdN -+DVcXs6+ng+Cka+v0FrApMyJSuZgWd2YdS7VZRUSQCdbMkTe0WRIR0qe905aw9yG -L8iQjruxfNRJgYn8EYoXMfFRieDR2JXwVg2oUlefEjO+B4LvC7tVAg== ------END RSA PRIVATE KEY----- diff --git a/projectsettings/services/webserver/config/nginx.tmpl b/projectsettings/services/webserver/config/nginx.tmpl deleted file mode 100644 index 68dae418..00000000 --- a/projectsettings/services/webserver/config/nginx.tmpl +++ /dev/null @@ -1,61 +0,0 @@ -# normally you leave this at the default of 1024 -events { - worker_connections 1024; -} - -http { - # cf http://blog.maxcdn.com/accept-encoding-its-vary-important/ - gzip_vary on; - gzip_proxied any; - gzip_types *; - - # http://nginx.org/en/docs/http/configuring_https_servers.html#optimization - ssl_session_cache shared:SSL:1m; - ssl_session_timeout 10m; - - server_tokens off; - - upstream django { - server koalixcrm:8000; - } - - server { - # rewrite all HTTP to HTTPS - listen 80; - server_name ${NGINX_SERVER_NAME}; - - return 301 https://${NGINX_SERVER_NAME}$request_uri; - } - - server { - listen 443 ssl default_server; - server_name ${NGINX_SERVER_NAME}; - - # see http://nginx.org/en/docs/http/configuriNGINX_https_servers.html - ssl_certificate /etc/ssl/certs/${NGINX_CRT_NAME}.crt; - ssl_certificate_key /etc/ssl/private/${NGINX_KEY_NAME}.key; - - ssl_prefer_server_ciphers on; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # ie defaults minus SSLv3 - - location /static { - alias /app/projectsettings/static; - - # http://stackoverflow.com/q/19213510/1346257 - include /etc/nginx/mime.types; - } - - location = /robots.txt { return 200 "User-agent: *\nAllow: /"; } - location = /favicon.ico { access_log off; log_not_found off; return 404; } - - #Prevent serving of sysfiles / vim backup files - location ~ /\. { access_log off; log_not_found off; deny all; } - location ~ ~$ { access_log off; log_not_found off; deny all; } - - location / { - uwsgi_pass django; - include uwsgi_params; - } - } -} - diff --git a/projectsettings/services/webserver/config/start.sh b/projectsettings/services/webserver/config/start.sh deleted file mode 100644 index f912360c..00000000 --- a/projectsettings/services/webserver/config/start.sh +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash -envsubst < nginx.tmpl > /etc/nginx/nginx.conf - -nginx -g "daemon off;" diff --git a/projectsettings/settings/base_settings.py b/projectsettings/settings/base_settings.py index c8b15bd6..b4459a7c 100644 --- a/projectsettings/settings/base_settings.py +++ b/projectsettings/settings/base_settings.py @@ -33,6 +33,8 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', + 'django_filters', 'filebrowser' ] @@ -120,7 +122,7 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static') MEDIA_URL = "/media/" -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_ROOT = os.path.join(BASE_DIR) PROJECT_ROOT = BASE_DIR @@ -137,4 +139,8 @@ 'PNG': ['.png'], 'GIF': ['.gif'], 'TTF': ['.ttf'], +} + +REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',) } \ No newline at end of file diff --git a/projectsettings/settings/development_settings.py b/projectsettings/settings/development_settings.py index 8412f27c..b4ec067d 100644 --- a/projectsettings/settings/development_settings.py +++ b/projectsettings/settings/development_settings.py @@ -6,25 +6,9 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'postgres', - 'USER': 'postgres', - 'HOST': 'db', - 'PORT': 5432, + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } -GRAPPELLI_INDEX_DASHBOARD = 'projectsettings.dashboard.CustomIndexDashboard' - -GRAPPELLI_ADMIN_TITLE = 'JLSCom' - -ASYNC_SIGNALS = False -CELERY_BROKER_URL = 'memory://' -RABBITMQ_SIGNALS_BROKER_URL = 'amqp://localhost:5672' -if ASYNC_SIGNALS: - CELERY_BROKER_URL = RABBITMQ_SIGNALS_BROKER_URL - CELERY_RESULT_BACKEND = RABBITMQ_SIGNALS_BROKER_URL -CELERY_TASK_ALWAYS_EAGER = False if ASYNC_SIGNALS else True -CELERY_TASK_IGNORE_RESULT = False if ASYNC_SIGNALS else True -CELERY_TASK_SERIALIZER = 'json' -CELERY_ACCEPT_CONTENT = ['json', 'pickle'] \ No newline at end of file +GRAPPELLI_INDEX_DASHBOARD = 'projectsettings.dashboard.CustomIndexDashboard' \ No newline at end of file diff --git a/projectsettings/static/admin/css/base.css b/projectsettings/static/admin/css/base.css deleted file mode 100644 index b2a40c49..00000000 --- a/projectsettings/static/admin/css/base.css +++ /dev/null @@ -1,978 +0,0 @@ -/* - DJANGO Admin styles -*/ - -@import url(fonts.css); - -body { - margin: 0; - padding: 0; - font-size: 14px; - font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; - color: #333; - background: #fff; -} - -/* LINKS */ - -a:link, a:visited { - color: #447e9b; - text-decoration: none; -} - -a:focus, a:hover { - color: #036; -} - -a:focus { - text-decoration: underline; -} - -a img { - border: none; -} - -a.section:link, a.section:visited { - color: #fff; - text-decoration: none; -} - -a.section:focus, a.section:hover { - text-decoration: underline; -} - -/* GLOBAL DEFAULTS */ - -p, ol, ul, dl { - margin: .2em 0 .8em 0; -} - -p { - padding: 0; - line-height: 140%; -} - -h1,h2,h3,h4,h5 { - font-weight: bold; -} - -h1 { - margin: 0 0 20px; - font-weight: 300; - font-size: 20px; - color: #666; -} - -h2 { - font-size: 16px; - margin: 1em 0 .5em 0; -} - -h2.subhead { - font-weight: normal; - margin-top: 0; -} - -h3 { - font-size: 14px; - margin: .8em 0 .3em 0; - color: #666; - font-weight: bold; -} - -h4 { - font-size: 12px; - margin: 1em 0 .8em 0; - padding-bottom: 3px; -} - -h5 { - font-size: 10px; - margin: 1.5em 0 .5em 0; - color: #666; - text-transform: uppercase; - letter-spacing: 1px; -} - -ul li { - list-style-type: square; - padding: 1px 0; -} - -li ul { - margin-bottom: 0; -} - -li, dt, dd { - font-size: 13px; - line-height: 20px; -} - -dt { - font-weight: bold; - margin-top: 4px; -} - -dd { - margin-left: 0; -} - -form { - margin: 0; - padding: 0; -} - -fieldset { - margin: 0; - padding: 0; - border: none; - border-top: 1px solid #eee; -} - -blockquote { - font-size: 11px; - color: #777; - margin-left: 2px; - padding-left: 10px; - border-left: 5px solid #ddd; -} - -code, pre { - font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; - color: #666; - font-size: 12px; -} - -pre.literal-block { - margin: 10px; - background: #eee; - padding: 6px 8px; -} - -code strong { - color: #930; -} - -hr { - clear: both; - color: #eee; - background-color: #eee; - height: 1px; - border: none; - margin: 0; - padding: 0; - font-size: 1px; - line-height: 1px; -} - -/* TEXT STYLES & MODIFIERS */ - -.small { - font-size: 11px; -} - -.tiny { - font-size: 10px; -} - -p.tiny { - margin-top: -2px; -} - -.mini { - font-size: 10px; -} - -p.mini { - margin-top: -3px; -} - -.help, p.help, form p.help, div.help, form div.help, div.help li { - font-size: 11px; - color: #999; -} - -div.help ul { - margin-bottom: 0; -} - -.help-tooltip { - cursor: help; -} - -p img, h1 img, h2 img, h3 img, h4 img, td img { - vertical-align: middle; -} - -.quiet, a.quiet:link, a.quiet:visited { - color: #999; - font-weight: normal; -} - -.float-right { - float: right; -} - -.float-left { - float: left; -} - -.clear { - clear: both; -} - -.align-left { - text-align: left; -} - -.align-right { - text-align: right; -} - -.example { - margin: 10px 0; - padding: 5px 10px; - background: #efefef; -} - -.nowrap { - white-space: nowrap; -} - -/* TABLES */ - -table { - border-collapse: collapse; - border-color: #ccc; -} - -td, th { - font-size: 13px; - line-height: 16px; - border-bottom: 1px solid #eee; - vertical-align: top; - padding: 8px; - font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif; -} - -th { - font-weight: 600; - text-align: left; -} - -thead th, -tfoot td { - color: #666; - padding: 5px 10px; - font-size: 11px; - background: #fff; - border: none; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; -} - -tfoot td { - border-bottom: none; - border-top: 1px solid #eee; -} - -thead th.required { - color: #000; -} - -tr.alt { - background: #f6f6f6; -} - -.row1 { - background: #fff; -} - -.row2 { - background: #f9f9f9; -} - -/* SORTABLE TABLES */ - -thead th { - padding: 5px 10px; - line-height: normal; - text-transform: uppercase; - background: #f6f6f6; -} - -thead th a:link, thead th a:visited { - color: #666; -} - -thead th.sorted { - background: #eee; -} - -thead th.sorted .text { - padding-right: 42px; -} - -table thead th .text span { - padding: 8px 10px; - display: block; -} - -table thead th .text a { - display: block; - cursor: pointer; - padding: 8px 10px; -} - -table thead th .text a:focus, table thead th .text a:hover { - background: #eee; -} - -thead th.sorted a.sortremove { - visibility: hidden; -} - -table thead th.sorted:hover a.sortremove { - visibility: visible; -} - -table thead th.sorted .sortoptions { - display: block; - padding: 9px 5px 0 5px; - float: right; - text-align: right; -} - -table thead th.sorted .sortpriority { - font-size: .8em; - min-width: 12px; - text-align: center; - vertical-align: 3px; - margin-left: 2px; - margin-right: 2px; -} - -table thead th.sorted .sortoptions a { - position: relative; - width: 14px; - height: 14px; - display: inline-block; - background: url(../img/sorting-icons.svg) 0 0 no-repeat; - background-size: 14px auto; -} - -table thead th.sorted .sortoptions a.sortremove { - background-position: 0 0; -} - -table thead th.sorted .sortoptions a.sortremove:after { - content: '\\'; - position: absolute; - top: -6px; - left: 3px; - font-weight: 200; - font-size: 18px; - color: #999; -} - -table thead th.sorted .sortoptions a.sortremove:focus:after, -table thead th.sorted .sortoptions a.sortremove:hover:after { - color: #447e9b; -} - -table thead th.sorted .sortoptions a.sortremove:focus, -table thead th.sorted .sortoptions a.sortremove:hover { - background-position: 0 -14px; -} - -table thead th.sorted .sortoptions a.ascending { - background-position: 0 -28px; -} - -table thead th.sorted .sortoptions a.ascending:focus, -table thead th.sorted .sortoptions a.ascending:hover { - background-position: 0 -42px; -} - -table thead th.sorted .sortoptions a.descending { - top: 1px; - background-position: 0 -56px; -} - -table thead th.sorted .sortoptions a.descending:focus, -table thead th.sorted .sortoptions a.descending:hover { - background-position: 0 -70px; -} - -/* FORM DEFAULTS */ - -input, textarea, select, .form-row p, form .button { - margin: 2px 0; - padding: 2px 3px; - vertical-align: middle; - font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif; - font-weight: normal; - font-size: 13px; -} -.form-row div.help { - padding: 2px 3px; -} - -textarea { - vertical-align: top; -} - -input[type=text], input[type=password], input[type=email], input[type=url], -input[type=number], textarea, select, .vTextField { - border: 1px solid #ccc; - border-radius: 4px; - padding: 5px 6px; - margin-top: 0; -} - -input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, -input[type=url]:focus, input[type=number]:focus, textarea:focus, select:focus, -.vTextField:focus { - border-color: #999; -} - -select { - height: 30px; -} - -select[multiple] { - min-height: 150px; -} - -/* FORM BUTTONS */ - -.button, input[type=submit], input[type=button], .submit-row input, a.button { - background: #79aec8; - padding: 10px 15px; - border: none; - border-radius: 4px; - color: #fff; - cursor: pointer; -} - -a.button { - padding: 4px 5px; -} - -.button:active, input[type=submit]:active, input[type=button]:active, -.button:focus, input[type=submit]:focus, input[type=button]:focus, -.button:hover, input[type=submit]:hover, input[type=button]:hover { - background: #609ab6; -} - -.button[disabled], input[type=submit][disabled], input[type=button][disabled] { - opacity: 0.4; -} - -.button.default, input[type=submit].default, .submit-row input.default { - float: right; - border: none; - font-weight: 400; - background: #417690; -} - -.button.default:active, input[type=submit].default:active, -.button.default:focus, input[type=submit].default:focus, -.button.default:hover, input[type=submit].default:hover { - background: #205067; -} - -.button[disabled].default, -input[type=submit][disabled].default, -input[type=button][disabled].default { - opacity: 0.4; -} - - -/* MODULES */ - -.module { - border: none; - margin-bottom: 30px; - background: #fff; -} - -.module p, .module ul, .module h3, .module h4, .module dl, .module pre { - padding-left: 10px; - padding-right: 10px; -} - -.module blockquote { - margin-left: 12px; -} - -.module ul, .module ol { - margin-left: 1.5em; -} - -.module h3 { - margin-top: .6em; -} - -.module h2, .module caption, .inline-group h2 { - margin: 0; - padding: 8px; - font-weight: 400; - font-size: 13px; - text-align: left; - background: #79aec8; - color: #fff; -} - -.module caption, -.inline-group h2 { - font-size: 12px; - letter-spacing: 0.5px; - text-transform: uppercase; -} - -.module table { - border-collapse: collapse; -} - -/* MESSAGES & ERRORS */ - -ul.messagelist { - padding: 0; - margin: 0; -} - -ul.messagelist li { - display: block; - font-weight: 400; - font-size: 13px; - padding: 10px 10px 10px 65px; - margin: 0 0 10px 0; - background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat; - background-size: 16px auto; - color: #333; -} - -ul.messagelist li.warning { - background: #ffc url(../img/icon-alert.svg) 40px 14px no-repeat; - background-size: 14px auto; -} - -ul.messagelist li.error { - background: #ffefef url(../img/icon-no.svg) 40px 12px no-repeat; - background-size: 16px auto; -} - -.errornote { - font-size: 14px; - font-weight: 700; - display: block; - padding: 10px 12px; - margin: 0 0 10px 0; - color: #ba2121; - border: 1px solid #ba2121; - border-radius: 4px; - background-color: #fff; - background-position: 5px 12px; -} - -ul.errorlist { - margin: 0 0 4px; - padding: 0; - color: #ba2121; - background: #fff; -} - -ul.errorlist li { - font-size: 13px; - display: block; - margin-bottom: 4px; -} - -ul.errorlist li:first-child { - margin-top: 0; -} - -ul.errorlist li a { - color: inherit; - text-decoration: underline; -} - -td ul.errorlist { - margin: 0; - padding: 0; -} - -td ul.errorlist li { - margin: 0; -} - -.form-row.errors { - margin: 0; - border: none; - border-bottom: 1px solid #eee; - background: none; -} - -.form-row.errors ul.errorlist li { - padding-left: 0; -} - -.errors input, .errors select, .errors textarea { - border: 1px solid #ba2121; -} - -div.system-message { - background: #ffc; - margin: 10px; - padding: 6px 8px; - font-size: .8em; -} - -div.system-message p.system-message-title { - padding: 4px 5px 4px 25px; - margin: 0; - color: #c11; - background: #ffefef url(../img/icon-no.svg) 5px 5px no-repeat; -} - -.description { - font-size: 12px; - padding: 5px 0 0 12px; -} - -/* BREADCRUMBS */ - -div.breadcrumbs { - background: #79aec8; - padding: 10px 40px; - border: none; - font-size: 14px; - color: #c4dce8; - text-align: left; -} - -div.breadcrumbs a { - color: #fff; -} - -div.breadcrumbs a:focus, div.breadcrumbs a:hover { - color: #c4dce8; -} - -/* ACTION ICONS */ - -.addlink { - padding-left: 16px; - background: url(../img/icon-addlink.svg) 0 1px no-repeat; -} - -.changelink, .inlinechangelink { - padding-left: 16px; - background: url(../img/icon-changelink.svg) 0 1px no-repeat; -} - -.deletelink { - padding-left: 16px; - background: url(../img/icon-deletelink.svg) 0 1px no-repeat; -} - -a.deletelink:link, a.deletelink:visited { - color: #CC3434; -} - -a.deletelink:focus, a.deletelink:hover { - color: #993333; - text-decoration: none; -} - -/* OBJECT TOOLS */ - -.object-tools { - font-size: 10px; - font-weight: bold; - padding-left: 0; - float: right; - position: relative; - margin-top: -48px; -} - -.form-row .object-tools { - margin-top: 5px; - margin-bottom: 5px; - float: none; - height: 2em; - padding-left: 3.5em; -} - -.object-tools li { - display: block; - float: left; - margin-left: 5px; - height: 16px; -} - -.object-tools a { - border-radius: 15px; -} - -.object-tools a:link, .object-tools a:visited { - display: block; - float: left; - padding: 3px 12px; - background: #999; - font-weight: 400; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.5px; - color: #fff; -} - -.object-tools a:focus, .object-tools a:hover { - background-color: #417690; -} - -.object-tools a:focus{ - text-decoration: none; -} - -.object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink { - background-repeat: no-repeat; - background-position: right 7px center; - padding-right: 26px; -} - -.object-tools a.viewsitelink, .object-tools a.golink { - background-image: url(../img/tooltag-arrowright.svg); -} - -.object-tools a.addlink { - background-image: url(../img/tooltag-add.svg); -} - -/* OBJECT HISTORY */ - -table#change-history { - width: 100%; -} - -table#change-history tbody th { - width: 16em; -} - -/* PAGE STRUCTURE */ - -#container { - position: relative; - width: 100%; - min-width: 980px; - padding: 0; -} - -#content { - padding: 20px 40px; -} - -.dashboard #content { - width: 600px; -} - -#content-main { - float: left; - width: 100%; -} - -#content-related { - float: right; - width: 260px; - position: relative; - margin-right: -300px; -} - -#footer { - clear: both; - padding: 10px; -} - -/* COLUMN TYPES */ - -.colMS { - margin-right: 300px; -} - -.colSM { - margin-left: 300px; -} - -.colSM #content-related { - float: left; - margin-right: 0; - margin-left: -300px; -} - -.colSM #content-main { - float: right; -} - -.popup .colM { - width: auto; -} - -/* HEADER */ - -#header { - width: auto; - height: 40px; - padding: 10px 40px; - background: #417690; - line-height: 40px; - color: #ffc; - overflow: hidden; -} - -#header a:link, #header a:visited { - color: #fff; -} - -#header a:focus , #header a:hover { - text-decoration: underline; -} - -#branding { - float: left; -} - -#branding h1 { - padding: 0; - margin: 0 20px 0 0; - font-weight: 300; - font-size: 24px; - color: #f5dd5d; -} - -#branding h1, #branding h1 a:link, #branding h1 a:visited { - color: #f5dd5d; -} - -#branding h2 { - padding: 0 10px; - font-size: 14px; - margin: -8px 0 8px 0; - font-weight: normal; - color: #ffc; -} - -#branding a:hover { - text-decoration: none; -} - -#user-tools { - float: right; - padding: 0; - margin: 0 0 0 20px; - font-weight: 300; - font-size: 11px; - letter-spacing: 0.5px; - text-transform: uppercase; - text-align: right; -} - -#user-tools a { - border-bottom: 1px solid rgba(255, 255, 255, 0.25); -} - -#user-tools a:focus, #user-tools a:hover { - text-decoration: none; - border-bottom-color: #79aec8; - color: #79aec8; -} - -/* SIDEBAR */ - -#content-related { - background: #f8f8f8; -} - -#content-related .module { - background: none; -} - -#content-related h3 { - font-size: 14px; - color: #666; - padding: 0 16px; - margin: 0 0 16px; -} - -#content-related h4 { - font-size: 13px; -} - -#content-related p { - padding-left: 16px; - padding-right: 16px; -} - -#content-related .actionlist { - padding: 0; - margin: 16px; -} - -#content-related .actionlist li { - line-height: 1.2; - margin-bottom: 10px; - padding-left: 18px; -} - -#content-related .module h2 { - background: none; - padding: 16px; - margin-bottom: 16px; - border-bottom: 1px solid #eaeaea; - font-size: 18px; - color: #333; -} - -.delete-confirmation form input[type="submit"] { - background: #ba2121; - border-radius: 4px; - padding: 10px 15px; - color: #fff; -} - -.delete-confirmation form input[type="submit"]:active, -.delete-confirmation form input[type="submit"]:focus, -.delete-confirmation form input[type="submit"]:hover { - background: #a41515; -} - -.delete-confirmation form .cancel-link { - display: inline-block; - vertical-align: middle; - height: 15px; - line-height: 15px; - background: #ddd; - border-radius: 4px; - padding: 10px 15px; - color: #333; - margin: 0 0 0 10px; -} - -.delete-confirmation form .cancel-link:active, -.delete-confirmation form .cancel-link:focus, -.delete-confirmation form .cancel-link:hover { - background: #ccc; -} - -/* POPUP */ -.popup #content { - padding: 20px; -} - -.popup #container { - min-width: 0; -} - -.popup #header { - padding: 10px 20px; -} diff --git a/projectsettings/static/admin/css/changelists.css b/projectsettings/static/admin/css/changelists.css deleted file mode 100644 index 17690a34..00000000 --- a/projectsettings/static/admin/css/changelists.css +++ /dev/null @@ -1,344 +0,0 @@ -/* CHANGELISTS */ - -#changelist { - position: relative; - width: 100%; -} - -#changelist table { - width: 100%; -} - -.change-list .hiddenfields { display:none; } - -.change-list .filtered table { - border-right: none; -} - -.change-list .filtered { - min-height: 400px; -} - -.change-list .filtered .results, .change-list .filtered .paginator, -.filtered #toolbar, .filtered div.xfull { - margin-right: 280px; - width: auto; -} - -.change-list .filtered table tbody th { - padding-right: 1em; -} - -#changelist-form .results { - overflow-x: auto; -} - -#changelist .toplinks { - border-bottom: 1px solid #ddd; -} - -#changelist .paginator { - color: #666; - border-bottom: 1px solid #eee; - background: #fff; - overflow: hidden; -} - -/* CHANGELIST TABLES */ - -#changelist table thead th { - padding: 0; - white-space: nowrap; - vertical-align: middle; -} - -#changelist table thead th.action-checkbox-column { - width: 1.5em; - text-align: center; -} - -#changelist table tbody td.action-checkbox { - text-align: center; -} - -#changelist table tfoot { - color: #666; -} - -/* TOOLBAR */ - -#changelist #toolbar { - padding: 8px 10px; - margin-bottom: 15px; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; - background: #f8f8f8; - color: #666; -} - -#changelist #toolbar form input { - border-radius: 4px; - font-size: 14px; - padding: 5px; - color: #333; -} - -#changelist #toolbar form #searchbar { - height: 19px; - border: 1px solid #ccc; - padding: 2px 5px; - margin: 0; - vertical-align: top; - font-size: 13px; -} - -#changelist #toolbar form #searchbar:focus { - border-color: #999; -} - -#changelist #toolbar form input[type="submit"] { - border: 1px solid #ccc; - padding: 2px 10px; - margin: 0; - vertical-align: middle; - background: #fff; - box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; - cursor: pointer; - color: #333; -} - -#changelist #toolbar form input[type="submit"]:focus, -#changelist #toolbar form input[type="submit"]:hover { - border-color: #999; -} - -#changelist #changelist-search img { - vertical-align: middle; - margin-right: 4px; -} - -/* FILTER COLUMN */ - -#changelist-filter { - position: absolute; - top: 0; - right: 0; - z-index: 1000; - width: 240px; - background: #f8f8f8; - border-left: none; - margin: 0; -} - -#changelist-filter h2 { - font-size: 14px; - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 5px 15px; - margin-bottom: 12px; - border-bottom: none; -} - -#changelist-filter h3 { - font-weight: 400; - font-size: 14px; - padding: 0 15px; - margin-bottom: 10px; -} - -#changelist-filter ul { - margin: 5px 0; - padding: 0 15px 15px; - border-bottom: 1px solid #eaeaea; -} - -#changelist-filter ul:last-child { - border-bottom: none; - padding-bottom: none; -} - -#changelist-filter li { - list-style-type: none; - margin-left: 0; - padding-left: 0; -} - -#changelist-filter a { - display: block; - color: #999; - text-overflow: ellipsis; - overflow-x: hidden; -} - -#changelist-filter li.selected { - border-left: 5px solid #eaeaea; - padding-left: 10px; - margin-left: -15px; -} - -#changelist-filter li.selected a { - color: #5b80b2; -} - -#changelist-filter a:focus, #changelist-filter a:hover, -#changelist-filter li.selected a:focus, -#changelist-filter li.selected a:hover { - color: #036; -} - -/* DATE DRILLDOWN */ - -.change-list ul.toplinks { - display: block; - float: left; - padding: 0; - margin: 0; - width: 100%; -} - -.change-list ul.toplinks li { - padding: 3px 6px; - font-weight: bold; - list-style-type: none; - display: inline-block; -} - -.change-list ul.toplinks .date-back a { - color: #999; -} - -.change-list ul.toplinks .date-back a:focus, -.change-list ul.toplinks .date-back a:hover { - color: #036; -} - -/* PAGINATOR */ - -.paginator { - font-size: 13px; - padding-top: 10px; - padding-bottom: 10px; - line-height: 22px; - margin: 0; - border-top: 1px solid #ddd; -} - -.paginator a:link, .paginator a:visited { - padding: 2px 6px; - background: #79aec8; - text-decoration: none; - color: #fff; -} - -.paginator a.showall { - padding: 0; - border: none; - background: none; - color: #5b80b2; -} - -.paginator a.showall:focus, .paginator a.showall:hover { - background: none; - color: #036; -} - -.paginator .end { - margin-right: 6px; -} - -.paginator .this-page { - padding: 2px 6px; - font-weight: bold; - font-size: 13px; - vertical-align: top; -} - -.paginator a:focus, .paginator a:hover { - color: white; - background: #036; -} - -/* ACTIONS */ - -.filtered .actions { - margin-right: 280px; - border-right: none; -} - -#changelist table input { - margin: 0; - vertical-align: baseline; -} - -#changelist table tbody tr.selected { - background-color: #FFFFCC; -} - -#changelist .actions { - padding: 10px; - background: #fff; - border-top: none; - border-bottom: none; - line-height: 24px; - color: #999; -} - -#changelist .actions.selected { - background: #fffccf; - border-top: 1px solid #fffee8; - border-bottom: 1px solid #edecd6; -} - -#changelist .actions span.all, -#changelist .actions span.action-counter, -#changelist .actions span.clear, -#changelist .actions span.question { - font-size: 13px; - margin: 0 0.5em; - display: none; -} - -#changelist .actions:last-child { - border-bottom: none; -} - -#changelist .actions select { - vertical-align: top; - height: 24px; - background: none; - color: #000; - border: 1px solid #ccc; - border-radius: 4px; - font-size: 14px; - padding: 0 0 0 4px; - margin: 0; - margin-left: 10px; -} - -#changelist .actions select:focus { - border-color: #999; -} - -#changelist .actions label { - display: inline-block; - vertical-align: middle; - font-size: 13px; -} - -#changelist .actions .button { - font-size: 13px; - border: 1px solid #ccc; - border-radius: 4px; - background: #fff; - box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; - cursor: pointer; - height: 24px; - line-height: 1; - padding: 4px 8px; - margin: 0; - color: #333; -} - -#changelist .actions .button:focus, #changelist .actions .button:hover { - border-color: #999; -} diff --git a/projectsettings/static/admin/css/dashboard.css b/projectsettings/static/admin/css/dashboard.css deleted file mode 100644 index 1560c7b4..00000000 --- a/projectsettings/static/admin/css/dashboard.css +++ /dev/null @@ -1,27 +0,0 @@ -/* DASHBOARD */ - -.dashboard .module table th { - width: 100%; -} - -.dashboard .module table td { - white-space: nowrap; -} - -.dashboard .module table td a { - display: block; - padding-right: .6em; -} - -/* RECENT ACTIONS MODULE */ - -.module ul.actionlist { - margin-left: 0; -} - -ul.actionlist li { - list-style-type: none; - overflow: hidden; - text-overflow: ellipsis; - -o-text-overflow: ellipsis; -} diff --git a/projectsettings/static/admin/css/fonts.css b/projectsettings/static/admin/css/fonts.css deleted file mode 100644 index c837e017..00000000 --- a/projectsettings/static/admin/css/fonts.css +++ /dev/null @@ -1,20 +0,0 @@ -@font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Bold-webfont.woff'); - font-weight: 700; - font-style: normal; -} - -@font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Regular-webfont.woff'); - font-weight: 400; - font-style: normal; -} - -@font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Light-webfont.woff'); - font-weight: 300; - font-style: normal; -} diff --git a/projectsettings/static/admin/css/forms.css b/projectsettings/static/admin/css/forms.css deleted file mode 100644 index 77985d5d..00000000 --- a/projectsettings/static/admin/css/forms.css +++ /dev/null @@ -1,515 +0,0 @@ -@import url('widgets.css'); - -/* FORM ROWS */ - -.form-row { - overflow: hidden; - padding: 10px; - font-size: 13px; - border-bottom: 1px solid #eee; -} - -.form-row img, .form-row input { - vertical-align: middle; -} - -.form-row label input[type="checkbox"] { - margin-top: 0; - vertical-align: 0; -} - -form .form-row p { - padding-left: 0; -} - -.hidden { - display: none; -} - -/* FORM LABELS */ - -label { - font-weight: normal; - color: #666; - font-size: 13px; -} - -.required label, label.required { - font-weight: bold; - color: #333; -} - -/* RADIO BUTTONS */ - -form ul.radiolist li { - list-style-type: none; -} - -form ul.radiolist label { - float: none; - display: inline; -} - -form ul.radiolist input[type="radio"] { - margin: -2px 4px 0 0; - padding: 0; -} - -form ul.inline { - margin-left: 0; - padding: 0; -} - -form ul.inline li { - float: left; - padding-right: 7px; -} - -/* ALIGNED FIELDSETS */ - -.aligned label { - display: block; - padding: 4px 10px 0 0; - float: left; - width: 160px; - word-wrap: break-word; - line-height: 1; -} - -.aligned label:not(.vCheckboxLabel):after { - content: ''; - display: inline-block; - vertical-align: middle; - height: 26px; -} - -.aligned label + p, .aligned label + div.help, .aligned label + div.readonly { - padding: 6px 0; - margin-top: 0; - margin-bottom: 0; - margin-left: 170px; -} - -.aligned ul label { - display: inline; - float: none; - width: auto; -} - -.aligned .form-row input { - margin-bottom: 0; -} - -.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { - width: 350px; -} - -form .aligned ul { - margin-left: 160px; - padding-left: 10px; -} - -form .aligned ul.radiolist { - display: inline-block; - margin: 0; - padding: 0; -} - -form .aligned p.help, -form .aligned div.help { - clear: left; - margin-top: 0; - margin-left: 160px; - padding-left: 10px; -} - -form .aligned label + p.help, -form .aligned label + div.help { - margin-left: 0; - padding-left: 0; -} - -form .aligned p.help:last-child, -form .aligned div.help:last-child { - margin-bottom: 0; - padding-bottom: 0; -} - -form .aligned input + p.help, -form .aligned textarea + p.help, -form .aligned select + p.help, -form .aligned input + div.help, -form .aligned textarea + div.help, -form .aligned select + div.help { - margin-left: 160px; - padding-left: 10px; -} - -form .aligned ul li { - list-style: none; -} - -form .aligned table p { - margin-left: 0; - padding-left: 0; -} - -.aligned .vCheckboxLabel { - float: none; - width: auto; - display: inline-block; - vertical-align: -3px; - padding: 0 0 5px 5px; -} - -.aligned .vCheckboxLabel + p.help, -.aligned .vCheckboxLabel + div.help { - margin-top: -4px; -} - -.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { - width: 610px; -} - -.checkbox-row p.help, -.checkbox-row div.help { - margin-left: 0; - padding-left: 0; -} - -fieldset .field-box { - float: left; - margin-right: 20px; -} - -/* WIDE FIELDSETS */ - -.wide label { - width: 200px; -} - -form .wide p, -form .wide input + p.help, -form .wide input + div.help { - margin-left: 200px; -} - -form .wide p.help, -form .wide div.help { - padding-left: 38px; -} - -form div.help ul { - padding-left: 0; - margin-left: 0; -} - -.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { - width: 450px; -} - -/* COLLAPSED FIELDSETS */ - -fieldset.collapsed * { - display: none; -} - -fieldset.collapsed h2, fieldset.collapsed { - display: block; -} - -fieldset.collapsed { - border: 1px solid #eee; - border-radius: 4px; - overflow: hidden; -} - -fieldset.collapsed h2 { - background: #f8f8f8; - color: #666; -} - -fieldset .collapse-toggle { - color: #fff; -} - -fieldset.collapsed .collapse-toggle { - background: transparent; - display: inline; - color: #447e9b; -} - -/* MONOSPACE TEXTAREAS */ - -fieldset.monospace textarea { - font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; -} - -/* SUBMIT ROW */ - -.submit-row { - padding: 12px 14px; - margin: 0 0 20px; - background: #f8f8f8; - border: 1px solid #eee; - border-radius: 4px; - text-align: right; - overflow: hidden; -} - -body.popup .submit-row { - overflow: auto; -} - -.submit-row input { - height: 35px; - line-height: 15px; - margin: 0 0 0 5px; -} - -.submit-row input.default { - margin: 0 0 0 8px; - text-transform: uppercase; -} - -.submit-row p { - margin: 0.3em; -} - -.submit-row p.deletelink-box { - float: left; - margin: 0; -} - -.submit-row a.deletelink { - display: block; - background: #ba2121; - border-radius: 4px; - padding: 10px 15px; - height: 15px; - line-height: 15px; - color: #fff; -} - -.submit-row a.deletelink:focus, -.submit-row a.deletelink:hover, -.submit-row a.deletelink:active { - background: #a41515; -} - -/* CUSTOM FORM FIELDS */ - -.vSelectMultipleField { - vertical-align: top; -} - -.vCheckboxField { - border: none; -} - -.vDateField, .vTimeField { - margin-right: 2px; - margin-bottom: 4px; -} - -.vDateField { - min-width: 6.85em; -} - -.vTimeField { - min-width: 4.7em; -} - -.vURLField { - width: 30em; -} - -.vLargeTextField, .vXMLLargeTextField { - width: 48em; -} - -.flatpages-flatpage #id_content { - height: 40.2em; -} - -.module table .vPositiveSmallIntegerField { - width: 2.2em; -} - -.vTextField { - width: 20em; -} - -.vIntegerField { - width: 5em; -} - -.vBigIntegerField { - width: 10em; -} - -.vForeignKeyRawIdAdminField { - width: 5em; -} - -/* INLINES */ - -.inline-group { - padding: 0; - margin: 0 0 30px; -} - -.inline-group thead th { - padding: 8px 10px; -} - -.inline-group .aligned label { - width: 160px; -} - -.inline-related { - position: relative; -} - -.inline-related h3 { - margin: 0; - color: #666; - padding: 5px; - font-size: 13px; - background: #f8f8f8; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; -} - -.inline-related h3 span.delete { - float: right; -} - -.inline-related h3 span.delete label { - margin-left: 2px; - font-size: 11px; -} - -.inline-related fieldset { - margin: 0; - background: #fff; - border: none; - width: 100%; -} - -.inline-related fieldset.module h3 { - margin: 0; - padding: 2px 5px 3px 5px; - font-size: 11px; - text-align: left; - font-weight: bold; - background: #bcd; - color: #fff; -} - -.inline-group .tabular fieldset.module { - border: none; -} - -.inline-related.tabular fieldset.module table { - width: 100%; -} - -.last-related fieldset { - border: none; -} - -.inline-group .tabular tr.has_original td { - padding-top: 2em; -} - -.inline-group .tabular tr td.original { - padding: 2px 0 0 0; - width: 0; - _position: relative; -} - -.inline-group .tabular th.original { - width: 0px; - padding: 0; -} - -.inline-group .tabular td.original p { - position: absolute; - left: 0; - height: 1.1em; - padding: 2px 9px; - overflow: hidden; - font-size: 9px; - font-weight: bold; - color: #666; - _width: 700px; -} - -.inline-group ul.tools { - padding: 0; - margin: 0; - list-style: none; -} - -.inline-group ul.tools li { - display: inline; - padding: 0 5px; -} - -.inline-group div.add-row, -.inline-group .tabular tr.add-row td { - color: #666; - background: #f8f8f8; - padding: 8px 10px; - border-bottom: 1px solid #eee; -} - -.inline-group .tabular tr.add-row td { - padding: 8px 10px; - border-bottom: 1px solid #eee; -} - -.inline-group ul.tools a.add, -.inline-group div.add-row a, -.inline-group .tabular tr.add-row td a { - background: url(../img/icon-addlink.svg) 0 1px no-repeat; - padding-left: 16px; - font-size: 12px; -} - -.empty-form { - display: none; -} - -/* RELATED FIELD ADD ONE / LOOKUP */ - -.add-another, .related-lookup { - margin-left: 5px; - display: inline-block; - vertical-align: middle; - background-repeat: no-repeat; - background-size: 14px; -} - -.add-another { - width: 16px; - height: 16px; - background-image: url(../img/icon-addlink.svg); -} - -.related-lookup { - width: 16px; - height: 16px; - background-image: url(../img/search.svg); -} - -form .related-widget-wrapper ul { - display: inline-block; - margin-left: 0; - padding-left: 0; -} - -.clearable-file-input input { - margin-top: 0; -} diff --git a/projectsettings/static/admin/css/login.css b/projectsettings/static/admin/css/login.css deleted file mode 100644 index cab3bbf5..00000000 --- a/projectsettings/static/admin/css/login.css +++ /dev/null @@ -1,78 +0,0 @@ -/* LOGIN FORM */ - -body.login { - background: #f8f8f8; -} - -.login #header { - height: auto; - padding: 5px 16px; -} - -.login #header h1 { - font-size: 18px; -} - -.login #header h1 a { - color: #fff; -} - -.login #content { - padding: 20px 20px 0; -} - -.login #container { - background: #fff; - border: 1px solid #eaeaea; - border-radius: 4px; - overflow: hidden; - width: 28em; - min-width: 300px; - margin: 100px auto; -} - -.login #content-main { - width: 100%; -} - -.login .form-row { - padding: 4px 0; - float: left; - width: 100%; - border-bottom: none; -} - -.login .form-row label { - padding-right: 0.5em; - line-height: 2em; - font-size: 1em; - clear: both; - color: #333; -} - -.login .form-row #id_username, .login .form-row #id_password { - clear: both; - padding: 8px; - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.login span.help { - font-size: 10px; - display: block; -} - -.login .submit-row { - clear: both; - padding: 1em 0 0 9.4em; - margin: 0; - border: none; - background: none; - text-align: left; -} - -.login .password-reset-link { - text-align: center; -} diff --git a/projectsettings/static/admin/css/rtl.css b/projectsettings/static/admin/css/rtl.css deleted file mode 100644 index ef397815..00000000 --- a/projectsettings/static/admin/css/rtl.css +++ /dev/null @@ -1,264 +0,0 @@ -body { - direction: rtl; -} - -/* LOGIN */ - -.login .form-row { - float: right; -} - -.login .form-row label { - float: right; - padding-left: 0.5em; - padding-right: 0; - text-align: left; -} - -.login .submit-row { - clear: both; - padding: 1em 9.4em 0 0; -} - -/* GLOBAL */ - -th { - text-align: right; -} - -.module h2, .module caption { - text-align: right; -} - -.module ul, .module ol { - margin-left: 0; - margin-right: 1.5em; -} - -.addlink, .changelink { - padding-left: 0; - padding-right: 16px; - background-position: 100% 1px; -} - -.deletelink { - padding-left: 0; - padding-right: 16px; - background-position: 100% 1px; -} - -.object-tools { - float: left; -} - -thead th:first-child, -tfoot td:first-child { - border-left: none; -} - -/* LAYOUT */ - -#user-tools { - right: auto; - left: 0; - text-align: left; -} - -div.breadcrumbs { - text-align: right; -} - -#content-main { - float: right; -} - -#content-related { - float: left; - margin-left: -300px; - margin-right: auto; -} - -.colMS { - margin-left: 300px; - margin-right: 0; -} - -/* SORTABLE TABLES */ - -table thead th.sorted .sortoptions { - float: left; -} - -thead th.sorted .text { - padding-right: 0; - padding-left: 42px; -} - -/* dashboard styles */ - -.dashboard .module table td a { - padding-left: .6em; - padding-right: 16px; -} - -/* changelists styles */ - -.change-list .filtered table { - border-left: none; - border-right: 0px none; -} - -#changelist-filter { - right: auto; - left: 0; - border-left: none; - border-right: none; -} - -.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { - margin-right: 0; - margin-left: 280px; -} - -#changelist-filter li.selected { - border-left: none; - padding-left: 10px; - margin-left: 0; - border-right: 5px solid #eaeaea; - padding-right: 10px; - margin-right: -15px; -} - -.filtered .actions { - margin-left: 280px; - margin-right: 0; -} - -#changelist table tbody td:first-child, #changelist table tbody th:first-child { - border-right: none; - border-left: none; -} - -/* FORMS */ - -.aligned label { - padding: 0 0 3px 1em; - float: right; -} - -.submit-row { - text-align: left -} - -.submit-row p.deletelink-box { - float: right; -} - -.submit-row input.default { - margin-left: 0; -} - -.vDateField, .vTimeField { - margin-left: 2px; -} - -.aligned .form-row input { - margin-left: 5px; -} - -form .aligned p.help, form .aligned div.help { - clear: right; -} - -form ul.inline li { - float: right; - padding-right: 0; - padding-left: 7px; -} - -input[type=submit].default, .submit-row input.default { - float: left; -} - -fieldset .field-box { - float: right; - margin-left: 20px; - margin-right: 0; -} - -.errorlist li { - background-position: 100% 12px; - padding: 0; -} - -.errornote { - background-position: 100% 12px; - padding: 10px 12px; -} - -/* WIDGETS */ - -.calendarnav-previous { - top: 0; - left: auto; - right: 10px; -} - -.calendarnav-next { - top: 0; - right: auto; - left: 10px; -} - -.calendar caption, .calendarbox h2 { - text-align: center; -} - -.selector { - float: right; -} - -.selector .selector-filter { - text-align: right; -} - -.inline-deletelink { - float: left; -} - -form .form-row p.datetime { - overflow: hidden; -} - -.related-widget-wrapper { - float: right; -} - -/* MISC */ - -.inline-related h2, .inline-group h2 { - text-align: right -} - -.inline-related h3 span.delete { - padding-right: 20px; - padding-left: inherit; - left: 10px; - right: inherit; - float:left; -} - -.inline-related h3 span.delete label { - margin-left: inherit; - margin-right: 2px; -} - -/* IE7 specific bug fixes */ - -div.colM { - position: relative; -} - -.submit-row input { - float: left; -} diff --git a/projectsettings/static/admin/css/widgets.css b/projectsettings/static/admin/css/widgets.css deleted file mode 100644 index d3bd67ac..00000000 --- a/projectsettings/static/admin/css/widgets.css +++ /dev/null @@ -1,565 +0,0 @@ -/* SELECTOR (FILTER INTERFACE) */ - -.selector { - width: 800px; - float: left; -} - -.selector select { - width: 380px; - height: 17.2em; -} - -.selector-available, .selector-chosen { - float: left; - width: 380px; - text-align: center; - margin-bottom: 5px; -} - -.selector-chosen select { - border-top: none; -} - -.selector-available h2, .selector-chosen h2 { - border: 1px solid #ccc; - border-radius: 4px 4px 0 0; -} - -.selector-chosen h2 { - background: #79aec8; - color: #fff; -} - -.selector .selector-available h2 { - background: #f8f8f8; - color: #666; -} - -.selector .selector-filter { - background: white; - border: 1px solid #ccc; - border-width: 0 1px; - padding: 8px; - color: #999; - font-size: 10px; - margin: 0; - text-align: left; -} - -.selector .selector-filter label, -.inline-group .aligned .selector .selector-filter label { - float: left; - margin: 7px 0 0; - width: 18px; - height: 18px; - padding: 0; - overflow: hidden; - line-height: 1; -} - -.selector .selector-available input { - width: 320px; - margin-left: 8px; -} - -.selector ul.selector-chooser { - float: left; - width: 22px; - background-color: #eee; - border-radius: 10px; - margin: 10em 5px 0 5px; - padding: 0; -} - -.selector-chooser li { - margin: 0; - padding: 3px; - list-style-type: none; -} - -.selector select { - padding: 0 10px; - margin: 0 0 10px; - border-radius: 0 0 4px 4px; -} - -.selector-add, .selector-remove { - width: 16px; - height: 16px; - display: block; - text-indent: -3000px; - overflow: hidden; - cursor: default; - opacity: 0.3; -} - -.active.selector-add, .active.selector-remove { - opacity: 1; -} - -.active.selector-add:hover, .active.selector-remove:hover { - cursor: pointer; -} - -.selector-add { - background: url(../img/selector-icons.svg) 0 -96px no-repeat; -} - -.active.selector-add:focus, .active.selector-add:hover { - background-position: 0 -112px; -} - -.selector-remove { - background: url(../img/selector-icons.svg) 0 -64px no-repeat; -} - -.active.selector-remove:focus, .active.selector-remove:hover { - background-position: 0 -80px; -} - -a.selector-chooseall, a.selector-clearall { - display: inline-block; - height: 16px; - text-align: left; - margin: 1px auto 3px; - overflow: hidden; - font-weight: bold; - line-height: 16px; - color: #666; - text-decoration: none; - opacity: 0.3; -} - -a.active.selector-chooseall:focus, a.active.selector-clearall:focus, -a.active.selector-chooseall:hover, a.active.selector-clearall:hover { - color: #447e9b; -} - -a.active.selector-chooseall, a.active.selector-clearall { - opacity: 1; -} - -a.active.selector-chooseall:hover, a.active.selector-clearall:hover { - cursor: pointer; -} - -a.selector-chooseall { - padding: 0 18px 0 0; - background: url(../img/selector-icons.svg) right -160px no-repeat; - cursor: default; -} - -a.active.selector-chooseall:focus, a.active.selector-chooseall:hover { - background-position: 100% -176px; -} - -a.selector-clearall { - padding: 0 0 0 18px; - background: url(../img/selector-icons.svg) 0 -128px no-repeat; - cursor: default; -} - -a.active.selector-clearall:focus, a.active.selector-clearall:hover { - background-position: 0 -144px; -} - -/* STACKED SELECTORS */ - -.stacked { - float: left; - width: 490px; -} - -.stacked select { - width: 480px; - height: 10.1em; -} - -.stacked .selector-available, .stacked .selector-chosen { - width: 480px; -} - -.stacked .selector-available { - margin-bottom: 0; -} - -.stacked .selector-available input { - width: 422px; -} - -.stacked ul.selector-chooser { - height: 22px; - width: 50px; - margin: 0 0 10px 40%; - background-color: #eee; - border-radius: 10px; -} - -.stacked .selector-chooser li { - float: left; - padding: 3px 3px 3px 5px; -} - -.stacked .selector-chooseall, .stacked .selector-clearall { - display: none; -} - -.stacked .selector-add { - background: url(../img/selector-icons.svg) 0 -32px no-repeat; - cursor: default; -} - -.stacked .active.selector-add { - background-position: 0 -48px; - cursor: pointer; -} - -.stacked .selector-remove { - background: url(../img/selector-icons.svg) 0 0 no-repeat; - cursor: default; -} - -.stacked .active.selector-remove { - background-position: 0 -16px; - cursor: pointer; -} - -.selector .help-icon { - background: url(../img/icon-unknown.svg) 0 0 no-repeat; - display: inline-block; - vertical-align: middle; - margin: -2px 0 0 2px; - width: 13px; - height: 13px; -} - -.selector .selector-chosen .help-icon { - background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat; -} - -.selector .search-label-icon { - background: url(../img/search.svg) 0 0 no-repeat; - display: inline-block; - height: 18px; - width: 18px; -} - -/* DATE AND TIME */ - -p.datetime { - line-height: 20px; - margin: 0; - padding: 0; - color: #666; - font-weight: bold; -} - -.datetime span { - white-space: nowrap; - font-weight: normal; - font-size: 11px; - color: #ccc; -} - -.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField { - min-width: 0; - margin-left: 5px; - margin-bottom: 4px; -} - -table p.datetime { - font-size: 11px; - margin-left: 0; - padding-left: 0; -} - -.datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon { - position: relative; - display: inline-block; - vertical-align: middle; - height: 16px; - width: 16px; - overflow: hidden; -} - -.datetimeshortcuts .clock-icon { - background: url(../img/icon-clock.svg) 0 0 no-repeat; -} - -.datetimeshortcuts a:focus .clock-icon, -.datetimeshortcuts a:hover .clock-icon { - background-position: 0 -16px; -} - -.datetimeshortcuts .date-icon { - background: url(../img/icon-calendar.svg) 0 0 no-repeat; - top: -1px; -} - -.datetimeshortcuts a:focus .date-icon, -.datetimeshortcuts a:hover .date-icon { - background-position: 0 -16px; -} - -.timezonewarning { - font-size: 11px; - color: #999; -} - -/* URL */ - -p.url { - line-height: 20px; - margin: 0; - padding: 0; - color: #666; - font-size: 11px; - font-weight: bold; -} - -.url a { - font-weight: normal; -} - -/* FILE UPLOADS */ - -p.file-upload { - line-height: 20px; - margin: 0; - padding: 0; - color: #666; - font-size: 11px; - font-weight: bold; -} - -.aligned p.file-upload { - margin-left: 170px; -} - -.file-upload a { - font-weight: normal; -} - -.file-upload .deletelink { - margin-left: 5px; -} - -span.clearable-file-input label { - color: #333; - font-size: 11px; - display: inline; - float: none; -} - -/* CALENDARS & CLOCKS */ - -.calendarbox, .clockbox { - margin: 5px auto; - font-size: 12px; - width: 19em; - text-align: center; - background: white; - border: 1px solid #ddd; - border-radius: 4px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); - overflow: hidden; - position: relative; -} - -.clockbox { - width: auto; -} - -.calendar { - margin: 0; - padding: 0; -} - -.calendar table { - margin: 0; - padding: 0; - border-collapse: collapse; - background: white; - width: 100%; -} - -.calendar caption, .calendarbox h2 { - margin: 0; - text-align: center; - border-top: none; - background: #f5dd5d; - font-weight: 700; - font-size: 12px; - color: #333; -} - -.calendar th { - padding: 8px 5px; - background: #f8f8f8; - border-bottom: 1px solid #ddd; - font-weight: 400; - font-size: 12px; - text-align: center; - color: #666; -} - -.calendar td { - font-weight: 400; - font-size: 12px; - text-align: center; - padding: 0; - border-top: 1px solid #eee; - border-bottom: none; -} - -.calendar td.selected a { - background: #79aec8; - color: #fff; -} - -.calendar td.nonday { - background: #f8f8f8; -} - -.calendar td.today a { - font-weight: 700; -} - -.calendar td a, .timelist a { - display: block; - font-weight: 400; - padding: 6px; - text-decoration: none; - color: #444; -} - -.calendar td a:focus, .timelist a:focus, -.calendar td a:hover, .timelist a:hover { - background: #79aec8; - color: white; -} - -.calendar td a:active, .timelist a:active { - background: #417690; - color: white; -} - -.calendarnav { - font-size: 10px; - text-align: center; - color: #ccc; - margin: 0; - padding: 1px 3px; -} - -.calendarnav a:link, #calendarnav a:visited, -#calendarnav a:focus, #calendarnav a:hover { - color: #999; -} - -.calendar-shortcuts { - background: white; - font-size: 11px; - line-height: 11px; - border-top: 1px solid #eee; - padding: 8px 0; - color: #ccc; -} - -.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { - display: block; - position: absolute; - top: 8px; - width: 15px; - height: 15px; - text-indent: -9999px; - padding: 0; -} - -.calendarnav-previous { - left: 10px; - background: url(../img/calendar-icons.svg) 0 0 no-repeat; -} - -.calendarbox .calendarnav-previous:focus, -.calendarbox .calendarnav-previous:hover { - background-position: 0 -15px; -} - -.calendarnav-next { - right: 10px; - background: url(../img/calendar-icons.svg) 0 -30px no-repeat; -} - -.calendarbox .calendarnav-next:focus, -.calendarbox .calendarnav-next:hover { - background-position: 0 -45px; -} - -.calendar-cancel { - margin: 0; - padding: 4px 0; - font-size: 12px; - background: #eee; - border-top: 1px solid #ddd; - color: #333; -} - -.calendar-cancel:focus, .calendar-cancel:hover { - background: #ddd; -} - -.calendar-cancel a { - color: black; - display: block; -} - -ul.timelist, .timelist li { - list-style-type: none; - margin: 0; - padding: 0; -} - -.timelist a { - padding: 2px; -} - -/* EDIT INLINE */ - -.inline-deletelink { - float: right; - text-indent: -9999px; - background: url(../img/inline-delete.svg) 0 0 no-repeat; - width: 16px; - height: 16px; - border: 0px none; -} - -.inline-deletelink:focus, .inline-deletelink:hover { - cursor: pointer; -} - -/* RELATED WIDGET WRAPPER */ -.related-widget-wrapper { - float: left; /* display properly in form rows with multiple fields */ - overflow: hidden; /* clear floated contents */ -} - -.related-widget-wrapper-link { - opacity: 0.3; -} - -.related-widget-wrapper-link:link { - opacity: .8; -} - -.related-widget-wrapper-link:link:focus, -.related-widget-wrapper-link:link:hover { - opacity: 1; -} - -select + .related-widget-wrapper-link, -.related-widget-wrapper-link + .related-widget-wrapper-link { - margin-left: 7px; -} diff --git a/projectsettings/static/admin/fonts/LICENSE.txt b/projectsettings/static/admin/fonts/LICENSE.txt deleted file mode 100644 index 75b52484..00000000 --- a/projectsettings/static/admin/fonts/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/projectsettings/static/admin/fonts/README.txt b/projectsettings/static/admin/fonts/README.txt deleted file mode 100644 index cc2135a3..00000000 --- a/projectsettings/static/admin/fonts/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -Roboto webfont source: https://www.google.com/fonts/specimen/Roboto -Weights used in this project: Light (300), Regular (400), Bold (700) diff --git a/projectsettings/static/admin/fonts/Roboto-Bold-webfont.woff b/projectsettings/static/admin/fonts/Roboto-Bold-webfont.woff deleted file mode 100644 index 03357ce4..00000000 Binary files a/projectsettings/static/admin/fonts/Roboto-Bold-webfont.woff and /dev/null differ diff --git a/projectsettings/static/admin/fonts/Roboto-Light-webfont.woff b/projectsettings/static/admin/fonts/Roboto-Light-webfont.woff deleted file mode 100644 index f6abd871..00000000 Binary files a/projectsettings/static/admin/fonts/Roboto-Light-webfont.woff and /dev/null differ diff --git a/projectsettings/static/admin/fonts/Roboto-Regular-webfont.woff b/projectsettings/static/admin/fonts/Roboto-Regular-webfont.woff deleted file mode 100644 index 6ff6afd8..00000000 Binary files a/projectsettings/static/admin/fonts/Roboto-Regular-webfont.woff and /dev/null differ diff --git a/projectsettings/static/admin/img/LICENSE b/projectsettings/static/admin/img/LICENSE deleted file mode 100644 index a4faaa1d..00000000 --- a/projectsettings/static/admin/img/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Code Charm Ltd - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/projectsettings/static/admin/img/README.txt b/projectsettings/static/admin/img/README.txt deleted file mode 100644 index 43373ad1..00000000 --- a/projectsettings/static/admin/img/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -All icons are taken from Font Awesome (http://fontawesome.io/) project. -The Font Awesome font is licensed under the SIL OFL 1.1: -- http://scripts.sil.org/OFL - -SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG -Font-Awesome-SVG-PNG is licensed under the MIT license (see file license -in current folder). diff --git a/projectsettings/static/admin/img/calendar-icons.svg b/projectsettings/static/admin/img/calendar-icons.svg deleted file mode 100644 index dbf21c39..00000000 --- a/projectsettings/static/admin/img/calendar-icons.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/projectsettings/static/admin/img/gis/move_vertex_off.svg b/projectsettings/static/admin/img/gis/move_vertex_off.svg deleted file mode 100644 index 228854f3..00000000 --- a/projectsettings/static/admin/img/gis/move_vertex_off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/projectsettings/static/admin/img/gis/move_vertex_on.svg b/projectsettings/static/admin/img/gis/move_vertex_on.svg deleted file mode 100644 index 96b87fdd..00000000 --- a/projectsettings/static/admin/img/gis/move_vertex_on.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/projectsettings/static/admin/img/icon-addlink.svg b/projectsettings/static/admin/img/icon-addlink.svg deleted file mode 100644 index e004fb16..00000000 --- a/projectsettings/static/admin/img/icon-addlink.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/icon-alert.svg b/projectsettings/static/admin/img/icon-alert.svg deleted file mode 100644 index e51ea83f..00000000 --- a/projectsettings/static/admin/img/icon-alert.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/icon-calendar.svg b/projectsettings/static/admin/img/icon-calendar.svg deleted file mode 100644 index 97910a99..00000000 --- a/projectsettings/static/admin/img/icon-calendar.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/projectsettings/static/admin/img/icon-changelink.svg b/projectsettings/static/admin/img/icon-changelink.svg deleted file mode 100644 index bbb137aa..00000000 --- a/projectsettings/static/admin/img/icon-changelink.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/icon-clock.svg b/projectsettings/static/admin/img/icon-clock.svg deleted file mode 100644 index bf9985d3..00000000 --- a/projectsettings/static/admin/img/icon-clock.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/projectsettings/static/admin/img/icon-deletelink.svg b/projectsettings/static/admin/img/icon-deletelink.svg deleted file mode 100644 index 4059b155..00000000 --- a/projectsettings/static/admin/img/icon-deletelink.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/icon-no.svg b/projectsettings/static/admin/img/icon-no.svg deleted file mode 100644 index 2e0d3832..00000000 --- a/projectsettings/static/admin/img/icon-no.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/icon-unknown-alt.svg b/projectsettings/static/admin/img/icon-unknown-alt.svg deleted file mode 100644 index 1c6b99fc..00000000 --- a/projectsettings/static/admin/img/icon-unknown-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/icon-unknown.svg b/projectsettings/static/admin/img/icon-unknown.svg deleted file mode 100644 index 50b4f972..00000000 --- a/projectsettings/static/admin/img/icon-unknown.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/icon-yes.svg b/projectsettings/static/admin/img/icon-yes.svg deleted file mode 100644 index 5883d877..00000000 --- a/projectsettings/static/admin/img/icon-yes.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/inline-delete.svg b/projectsettings/static/admin/img/inline-delete.svg deleted file mode 100644 index 17d1ad67..00000000 --- a/projectsettings/static/admin/img/inline-delete.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/search.svg b/projectsettings/static/admin/img/search.svg deleted file mode 100644 index c8c69b2a..00000000 --- a/projectsettings/static/admin/img/search.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/selector-icons.svg b/projectsettings/static/admin/img/selector-icons.svg deleted file mode 100644 index 926b8e21..00000000 --- a/projectsettings/static/admin/img/selector-icons.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/projectsettings/static/admin/img/sorting-icons.svg b/projectsettings/static/admin/img/sorting-icons.svg deleted file mode 100644 index 7c31ec91..00000000 --- a/projectsettings/static/admin/img/sorting-icons.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/projectsettings/static/admin/img/tooltag-add.svg b/projectsettings/static/admin/img/tooltag-add.svg deleted file mode 100644 index 1ca64ae5..00000000 --- a/projectsettings/static/admin/img/tooltag-add.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/img/tooltag-arrowright.svg b/projectsettings/static/admin/img/tooltag-arrowright.svg deleted file mode 100644 index b664d619..00000000 --- a/projectsettings/static/admin/img/tooltag-arrowright.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projectsettings/static/admin/js/SelectBox.js b/projectsettings/static/admin/js/SelectBox.js deleted file mode 100644 index 3557108c..00000000 --- a/projectsettings/static/admin/js/SelectBox.js +++ /dev/null @@ -1,144 +0,0 @@ -(function($) { - 'use strict'; - var SelectBox = { - cache: {}, - init: function(id) { - var box = document.getElementById(id); - var node; - SelectBox.cache[id] = []; - var cache = SelectBox.cache[id]; - var boxOptions = box.options; - var boxOptionsLength = boxOptions.length; - for (var i = 0, j = boxOptionsLength; i < j; i++) { - node = boxOptions[i]; - cache.push({value: node.value, text: node.text, displayed: 1}); - } - }, - redisplay: function(id) { - // Repopulate HTML select box from cache - var box = document.getElementById(id); - var node; - $(box).empty(); // clear all options - var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag - var cache = SelectBox.cache[id]; - for (var i = 0, j = cache.length; i < j; i++) { - node = cache[i]; - if (node.displayed) { - var new_option = new Option(node.text, node.value, false, false); - // Shows a tooltip when hovering over the option - new_option.setAttribute("title", node.text); - new_options += new_option.outerHTML; - } - } - new_options += ''; - box.outerHTML = new_options; - }, - filter: function(id, text) { - // Redisplay the HTML select box, displaying only the choices containing ALL - // the words in text. (It's an AND search.) - var tokens = text.toLowerCase().split(/\s+/); - var node, token; - var cache = SelectBox.cache[id]; - for (var i = 0, j = cache.length; i < j; i++) { - node = cache[i]; - node.displayed = 1; - var node_text = node.text.toLowerCase(); - var numTokens = tokens.length; - for (var k = 0; k < numTokens; k++) { - token = tokens[k]; - if (node_text.indexOf(token) === -1) { - node.displayed = 0; - break; // Once the first token isn't found we're done - } - } - } - SelectBox.redisplay(id); - }, - delete_from_cache: function(id, value) { - var node, delete_index = null; - var cache = SelectBox.cache[id]; - for (var i = 0, j = cache.length; i < j; i++) { - node = cache[i]; - if (node.value === value) { - delete_index = i; - break; - } - } - cache.splice(delete_index, 1); - }, - add_to_cache: function(id, option) { - SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); - }, - cache_contains: function(id, value) { - // Check if an item is contained in the cache - var node; - var cache = SelectBox.cache[id]; - for (var i = 0, j = cache.length; i < j; i++) { - node = cache[i]; - if (node.value === value) { - return true; - } - } - return false; - }, - move: function(from, to) { - var from_box = document.getElementById(from); - var option; - var boxOptions = from_box.options; - var boxOptionsLength = boxOptions.length; - for (var i = 0, j = boxOptionsLength; i < j; i++) { - option = boxOptions[i]; - var option_value = option.value; - if (option.selected && SelectBox.cache_contains(from, option_value)) { - SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); - SelectBox.delete_from_cache(from, option_value); - } - } - SelectBox.redisplay(from); - SelectBox.redisplay(to); - }, - move_all: function(from, to) { - var from_box = document.getElementById(from); - var option; - var boxOptions = from_box.options; - var boxOptionsLength = boxOptions.length; - for (var i = 0, j = boxOptionsLength; i < j; i++) { - option = boxOptions[i]; - var option_value = option.value; - if (SelectBox.cache_contains(from, option_value)) { - SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); - SelectBox.delete_from_cache(from, option_value); - } - } - SelectBox.redisplay(from); - SelectBox.redisplay(to); - }, - sort: function(id) { - SelectBox.cache[id].sort(function(a, b) { - a = a.text.toLowerCase(); - b = b.text.toLowerCase(); - try { - if (a > b) { - return 1; - } - if (a < b) { - return -1; - } - } - catch (e) { - // silently fail on IE 'unknown' exception - } - return 0; - } ); - }, - select_all: function(id) { - var box = document.getElementById(id); - var boxOptions = box.options; - var boxOptionsLength = boxOptions.length; - for (var i = 0; i < boxOptionsLength; i++) { - boxOptions[i].selected = 'selected'; - } - } - }; - window.SelectBox = SelectBox; -})(grp.jQuery); diff --git a/projectsettings/static/admin/js/SelectFilter2.js b/projectsettings/static/admin/js/SelectFilter2.js deleted file mode 100644 index cdb70b25..00000000 --- a/projectsettings/static/admin/js/SelectFilter2.js +++ /dev/null @@ -1,236 +0,0 @@ -/*global SelectBox, addEvent, gettext, interpolate, quickElement, SelectFilter*/ -/* -SelectFilter2 - Turns a multiple-select box into a filter interface. - -Requires jQuery, core.js, and SelectBox.js. -*/ -(function($) { - 'use strict'; - function findForm(node) { - // returns the node of the form containing the given node - if (node.tagName.toLowerCase() !== 'form') { - return findForm(node.parentNode); - } - return node; - } - - window.SelectFilter = { - init: function(field_id, field_name, is_stacked) { - if (field_id.match(/__prefix__/)) { - // Don't initialize on empty forms. - return; - } - var from_box = document.getElementById(field_id); - from_box.id += '_from'; // change its ID - from_box.className = 'filtered'; - - var ps = from_box.parentNode.getElementsByTagName('p'); - for (var i = 0; i < ps.length; i++) { - if (ps[i].className.indexOf("info") !== -1) { - // Remove

, because it just gets in the way. - from_box.parentNode.removeChild(ps[i]); - } else if (ps[i].className.indexOf("help") !== -1) { - // Move help text up to the top so it isn't below the select - // boxes or wrapped off on the side to the right of the add - // button: - from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild); - } - } - - //

or
- var selector_div = quickElement('div', from_box.parentNode); - selector_div.className = is_stacked ? 'selector stacked' : 'selector'; - - //
- var selector_available = quickElement('div', selector_div); - selector_available.className = 'selector-available'; - var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name])); - quickElement( - 'span', title_available, '', - 'class', 'help help-tooltip help-icon', - 'title', interpolate( - gettext( - 'This is the list of available %s. You may choose some by ' + - 'selecting them in the box below and then clicking the ' + - '"Choose" arrow between the two boxes.' - ), - [field_name] - ) - ); - - var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter'); - filter_p.className = 'selector-filter'; - - var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + '_input'); - - quickElement( - 'span', search_filter_label, '', - 'class', 'help-tooltip search-label-icon', - 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name]) - ); - - filter_p.appendChild(document.createTextNode(' ')); - - var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter")); - filter_input.id = field_id + '_input'; - - selector_available.appendChild(from_box); - var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_add_all_link'); - choose_all.className = 'selector-chooseall'; - - //