]> git.parisson.com Git - mezzo.git/commitdiff
[Timesheet] : dashboard + style
authorEmilie Zawadzki <zawadzki@ircam.fr>
Wed, 18 Jan 2017 13:26:03 +0000 (14:26 +0100)
committerEmilie Zawadzki <zawadzki@ircam.fr>
Wed, 18 Jan 2017 13:26:03 +0000 (14:26 +0100)
14 files changed:
app/organization/core/templatetags/organization_tags.py
app/organization/network/admin.py
app/organization/network/api.py
app/organization/network/forms.py
app/organization/network/migrations/0084_auto_20170118_1119.py [new file with mode: 0644]
app/organization/network/migrations/0085_auto_20170118_1239.py [new file with mode: 0644]
app/organization/network/migrations/0086_auto_20170118_1247.py [new file with mode: 0644]
app/organization/network/models.py
app/organization/network/utils.py
app/organization/network/views.py
app/organization/projects/migrations/0042_auto_20170118_1239.py [new file with mode: 0644]
app/organization/projects/models.py
app/templates/network/person_activity_timesheet/includes/person_activity_timesheet_inline.html
app/templates/network/person_activity_timesheet/person_activity_timesheet_list.html

index 0cda8e9d6fa5f0133f3c9d0e7125d083e511ba71..155f743f5671e30758dd27ac344150f9a248806c 100644 (file)
@@ -21,6 +21,7 @@
 
 # -*- coding: utf-8 -*-
 import datetime
+import calendar
 from django.http import QueryDict
 from mezzanine.pages.models import Page
 from mezzanine.blog.models import BlogPost
@@ -202,3 +203,16 @@ def unspam(email):
 @register.filter
 def get_attr(obj, attr):
     return getattr(obj, attr)
+
+@register.filter
+def month_name(month_number):
+    return calendar.month_name[month_number]
+
+@register.filter
+def format_wp(work_packages):
+    work_packages = [str(wk.number) for wk in work_packages]
+    return ",".join(work_packages)
+
+@register.filter
+def format_percent(percent):
+    return str(percent * 100) + ' %'
index 7d8311123590de6c94ae248b8a62aebd4b9e3d64..15559c2f47a35d3703c847a368973b5b021e3c7b 100644 (file)
@@ -33,7 +33,7 @@ from organization.pages.models import *
 from organization.core.admin import *
 from organization.pages.admin import PageImageInline, PageBlockInline, PagePlaylistInline, DynamicContentPageInline, PageRelatedTitleAdmin
 from organization.shop.models import PageProductList
-from organization.network.utils import TimesheetXLS
+from organization.network.utils import TimesheetXLS, set_timesheets_validation_date
 
 class OrganizationAdminInline(StackedDynamicInlineAdmin):
 
@@ -280,7 +280,8 @@ class PersonActivityTimeSheetAdmin(BaseTranslationOrderedModelAdmin):
     search_fields = ['year','activity__person__last_name', "project__title"]
     list_display = ['person', 'activity', 'year', 'month', 'project', 'work_package', 'percentage',  'accounting', 'validation']
     list_filter = ['activity__person', 'year', 'project']
-    actions = ['export_xls',]
+    actions = ['export_xls', 'validate_timesheets']
+
 
     def person(self, instance):
         return instance.activity.person
@@ -292,6 +293,10 @@ class PersonActivityTimeSheetAdmin(BaseTranslationOrderedModelAdmin):
     def export_xls(self, request, queryset):
         xls = TimesheetXLS(queryset)
         return xls.write()
+
+    def validate_timesheets(self, request, queryset):
+        set_timesheets_validation_date(queryset)
+
     export_xls.short_description = "Export person timesheets"
 
 
index af2773c4a17ff9259f79fe471df8856d6e99fa0b..0e3428ed2af4eabb9a46ec70fe3b76303fdc643d 100644 (file)
@@ -3,7 +3,7 @@
 import requests
 import time
 from django.conf import settings
-from datetime import date, datetime, timedelta
+from datetime import date, timedelta
 import dateutil.parser
 
 WEEK_DAYS = {
index 6bdd504305f04cc6928697e3160900a964710189..61aafeac29274e992ab9f11a81aeb2163be3b586 100644 (file)
@@ -19,6 +19,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
+from django.utils import timezone
 from dal import autocomplete
 import dal_queryset_sequence
 import dal_select2_queryset_sequence
@@ -88,7 +89,11 @@ class OrganizationLinkedForm(forms.ModelForm):
 
 class PersonActivityTimeSheetForm(forms.ModelForm):
 
+    def save(self):
+        self.instance.accounting = timezone.now()
+        super(PersonActivityTimeSheetForm, self).save()
+
     class Meta:
         model = PersonActivityTimeSheet
         fields = ('__all__')
-        # PersonActivityTimeSheetviewfields = ['pub_date', 'headline', 'content', 'reporter']
+        exclude = ['accounting', 'validation']
diff --git a/app/organization/network/migrations/0084_auto_20170118_1119.py b/app/organization/network/migrations/0084_auto_20170118_1119.py
new file mode 100644 (file)
index 0000000..faa9037
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.11 on 2017-01-18 10:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('organization-network', '0083_auto_20170116_1235'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='personactivitytimesheet',
+            name='accounting',
+            field=models.DateField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='personactivitytimesheet',
+            name='validation',
+            field=models.DateField(blank=True, null=True),
+        ),
+    ]
diff --git a/app/organization/network/migrations/0085_auto_20170118_1239.py b/app/organization/network/migrations/0085_auto_20170118_1239.py
new file mode 100644 (file)
index 0000000..e64bf17
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.11 on 2017-01-18 11:39
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('organization-network', '0084_auto_20170118_1119'),
+    ]
+
+    operations = [
+        migrations.AlterUniqueTogether(
+            name='personactivitytimesheet',
+            unique_together=set([('activity', 'project', 'month', 'year')]),
+        ),
+    ]
diff --git a/app/organization/network/migrations/0086_auto_20170118_1247.py b/app/organization/network/migrations/0086_auto_20170118_1247.py
new file mode 100644 (file)
index 0000000..6b6e3e4
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.11 on 2017-01-18 11:47
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('organization-network', '0085_auto_20170118_1239'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='personactivitytimesheet',
+            name='month',
+            field=models.IntegerField(choices=[(1, 'January'), (2, 'February'), (3, 'March'), (4, 'April'), (5, 'May'), (6, 'June'), (7, 'July'), (8, 'August'), (9, 'September'), (10, 'October'), (11, 'November'), (12, 'December')], verbose_name='month'),
+        ),
+    ]
index 7cbb9ccdbd93c25a394ecbd13badef1451e5ed49..9d30483e40394ef048491aa2621fc41dfdd530ef 100644 (file)
@@ -38,7 +38,6 @@ from django.core.urlresolvers import reverse, reverse_lazy
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
-from django.utils import timezone
 
 from mezzanine.pages.models import Page
 from mezzanine.core.models import RichText, Displayable, Slugged
@@ -80,6 +79,21 @@ PATTERN_CHOICES = [
     ('pattern-bg--triangles', _('triangles')),
 ]
 
+MONTH_CHOICES = [
+    (1, _('January')),
+    (2, _('February')),
+    (3, _('March')),
+    (4, _('April')),
+    (5, _('May')),
+    (6, _('June')),
+    (7, _('July')),
+    (8, _('August')),
+    (9, _('September')),
+    (10, _('October')),
+    (11, _('November')),
+    (12, _('December')),
+]
+
 ALIGNMENT_CHOICES = (('left', _('left')), ('left', _('left')), ('right', _('right')))
 
 CSS_COLOR_CHOICES = [
@@ -561,10 +575,10 @@ class PersonActivityTimeSheet(models.Model):
     project = models.ForeignKey('organization-projects.Project', verbose_name=_('project'), related_name='timesheets')
     work_packages = models.ManyToManyField('organization-projects.ProjectWorkPackage', verbose_name=_('work package'), related_name='timesheets', blank=True)
     percentage = models.FloatField(_('% of work time on the project'), validators=[validate_positive])
-    month = models.IntegerField(_('month'))
+    month = models.IntegerField(_('month'), choices=MONTH_CHOICES)
     year = models.IntegerField(_('year'))
-    accounting = models.DateField(default=timezone.now(), blank=True, null=True)
-    validation = models.DateField(default=timezone.now(), blank=True, null=True)
+    accounting = models.DateField(blank=True, null=True)
+    validation = models.DateField(blank=True, null=True)
 
     @property
     def date(self):
@@ -574,6 +588,7 @@ class PersonActivityTimeSheet(models.Model):
         verbose_name = _('activity timesheet')
         verbose_name_plural = _('activity timesheets')
         ordering = ['month',]
+        unique_together = (("activity", "project", "month", "year"),)
 
 
 class PersonActivityVacation(Period):
index f2e0ef00b71fbab9d260da4481fc102c93cfc4ff..16825dfe3d1e7b5d24454d384e57faaaf4b026d1 100644 (file)
@@ -4,8 +4,9 @@ import csv
 from django.http import HttpResponse
 from xlwt import *
 import calendar
-from organization.network.api import *
 import datetime
+from django.utils import timezone
+from organization.network.api import *
 from collections import defaultdict, OrderedDict
 from pprint import pprint
 
@@ -83,12 +84,6 @@ def get_nb_half_days_by_period_per_month(date_from, date_to):
     return md_dict
 
 
-class TimesheetXLS2(object):
-
-    def __init__(self, timesheets):
-        self.timesheets = timesheets.order_by('activity','project', 'year', 'month')
-
-
 class TimesheetXLS(object):
 
     t_dict = OrderedDict()
@@ -163,11 +158,12 @@ class TimesheetXLS(object):
             if not person_slug in self.t_dict:
                 self.t_dict[person_slug] = {}
                 # caculate for each person leaved days in year
+                print("timesheet.year", timesheet.year)
                 date_from = datetime.date(timesheet.year, 1, 1)
                 date_to = datetime.date(timesheet.year, 12, 31)
                 nb_half_days = get_nb_half_days_by_period_per_month(date_from, date_to)
                 leave_days = get_leave_days_per_month(date_from, date_to, timesheet.activity.person.external_id)
-                worked_hours_by_month = {"test" : "1"}
+                worked_hours_by_month = {}
                 # for each month
                 for m_key, m_val in nb_half_days.items():
                     # for each week day
@@ -275,3 +271,11 @@ class TimesheetXLS(object):
         response['Content-Disposition'] = 'attachment; filename=users.xls'
         self.book.save(response)
         return response
+
+
+
+def set_timesheets_validation_date(timesheets):
+    """ Admin action to set validation date for selected timesheets """
+    for timesheet in timesheets :
+        timesheet.validation = timezone.now()
+        timesheet.save()
index 41cb3045687dcf8ff2c2133790fe6ddb4ac52d3a..5371a958bef933aac985c89bda6943975ec3e81f 100644 (file)
@@ -31,7 +31,9 @@ from organization.network.models import *
 from organization.core.views import *
 from datetime import date
 from organization.network.forms import *
-from organization.network.utils import TimesheetXLS, TimesheetXLS2
+from organization.network.utils import TimesheetXLS
+from collections import OrderedDict
+
 
 
 class PersonListView(ListView):
@@ -140,8 +142,13 @@ class TimeSheetCreateView(TimesheetAbstractView, CreateView):
     context_object_name = 'timesheet'
     form_class = PersonActivityTimeSheetForm
 
+    def get_success_url(self):
+        print(" self.kwargs['slug']",  self.kwargs['slug'])
+        return reverse('organization-network-timesheet-list-view', kwargs={'slug': self.kwargs['slug']})
+
     def get_initial(self):
         initial = super(TimeSheetCreateView, self).get_initial()
+        # get the more recent activity
         initial['activity'] = PersonActivity.objects.filter(person__slug=self.kwargs['slug']).first()
         initial['month'] = self.kwargs['month']
         initial['year'] = self.kwargs['year']
@@ -156,11 +163,22 @@ class TimeSheetCreateView(TimesheetAbstractView, CreateView):
 class PersonActivityTimeSheetListView(TimesheetAbstractView, ListView):
     model = PersonActivityTimeSheet
     template_name='network/person_activity_timesheet/person_activity_timesheet_list.html'
-    context_object_name = 'timesheet'
+    context_object_name = 'timesheets_by_year'
 
     def get_queryset(self):
         if 'slug' in self.kwargs:
-            return PersonActivityTimeSheet.objects.filter(activity__person__slug__exact=self.kwargs['slug'])
+            timesheets = PersonActivityTimeSheet.objects.filter(activity__person__slug__exact=self.kwargs['slug'])
+            t_dict = OrderedDict()
+            for timesheet in timesheets:
+                year = timesheet.year
+                if not year in t_dict:
+                    t_dict[year] = {}
+                project_slug = timesheet.project.title
+                # if new person
+                if not project_slug in t_dict[year]:
+                    t_dict[year][project_slug] = []
+                t_dict[year][project_slug].append(timesheet)
+            return t_dict
 
     def get_context_data(self, **kwargs):
         context = super(PersonActivityTimeSheetListView, self).get_context_data(**kwargs)
diff --git a/app/organization/projects/migrations/0042_auto_20170118_1239.py b/app/organization/projects/migrations/0042_auto_20170118_1239.py
new file mode 100644 (file)
index 0000000..a72b079
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.11 on 2017-01-18 11:39
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('organization-projects', '0041_merge'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='project',
+            name='manager',
+            field=models.ManyToManyField(blank=True, related_name='projects_manager', to='organization-network.Person', verbose_name='Manager'),
+        ),
+    ]
index 28fe7ed720ae89c5cea57a56484ba601a4a6d5ce..bc9ab0ad088b46a81896416ed4e4d0d45ed3ae82 100644 (file)
@@ -61,7 +61,7 @@ class Project(Displayable, Period, RichText):
     website = models.URLField(_('website'), max_length=512, blank=True)
     topic = models.ForeignKey('ProjectTopic', verbose_name=_('topic'), related_name='projects', blank=True, null=True)
     referring_person = models.ManyToManyField('organization-network.Person', verbose_name=_('Referring Person'), related_name='projects_referring_person', blank=True)
-    manager =  models.ManyToManyField('organization-network.Person', verbose_name=_('Manager'), related_name='projects_manager', blank=True, null=True)
+    manager =  models.ManyToManyField('organization-network.Person', verbose_name=_('Manager'), related_name='projects_manager', blank=True)
 
     class Meta:
         verbose_name = _('project')
index 3cbd38cb7ed6729afcb74e097108f9d0568e0bd9..7b7c2cf0b002acf0d8edee3f210f1a07bc209c18 100644 (file)
@@ -1 +1,22 @@
-{{ object }}
+{% load organization_tags %}
+<table>
+    <tr>
+        <th>{{ timesheet.month|month_name }}</th>
+    </tr>
+    <tr>
+        <td>
+            {{ timesheet.percentage|format_percent }} 
+        </td>
+    </tr>
+    <tr>
+        <td>
+            {% with timesheet.work_packages.all as work_packages %}
+                {% if work_packages %}
+                    {{ timesheet.work_packages.all|format_wp }}
+                {% else %}
+                    <span>-</span>
+                {% endif %}
+            {% endwith %}
+        </td>
+    </tr>
+</table>
index 30a2295a47cb240bc9f14dea894fa42fee5dc4ed..31a3f2306d7cde3b1b13914c4199825ed73e0e89 100644 (file)
 {% block page_content %}
 
     <a class="pull-right button button--black" href="{% url 'organization-network-timesheet-create-view' slug current_year current_month %}" title="">Declare this month</a>
-
-    {% if person_activity_timesheet %}
-
-        {% for pat in person_activity_timesheet %}
-            {% include "network/person_activity_timesheet/includes/person_activity_timesheet_inline.html" with object=pat %}
+    {{ timesheets_by_project }}
+    {% if timesheets_by_year %}
+        {% for year_k, year_v in timesheets_by_year.items %}
+            <h2>{{ year_k }}</h2>
+            {% for project_k, project_v in year_v.items %}
+                <h3>{{ project_k }}</h3>
+                <table>
+                    <tr>
+                        {% for timesheet in project_v %}
+                            <td>
+                                {% include "network/person_activity_timesheet/includes/person_activity_timesheet_inline.html" %}
+                            </td>
+                        {% endfor %}
+                    </tr>
+                </table>
+            {% endfor %}
         {% endfor %}
 
     {% else %}