Exemplo n.º 1
0
    def add_absences(self, queryset):
        for absence in queryset.filter(
            Q(user__in=self._user_ids),
            Q(starts_on__lte=max(self.weeks)),
            Q(ends_on__isnull=False, ends_on__gte=min(self.weeks))
            | Q(ends_on__isnull=True, starts_on__gte=min(self.weeks)),
        ).select_related("user"):
            date_from = monday(absence.starts_on)
            date_until = monday(absence.ends_on or absence.starts_on)
            hours = absence.days * absence.user.planning_hours_per_day
            weeks = [
                (idx, week)
                for idx, week in enumerate(self.weeks)
                if date_from <= week <= date_until
            ]

            for idx, week in weeks:
                self._absences[absence.user][idx].append(
                    (
                        hours / len(weeks),
                        f"{absence.get_reason_display()} - {absence.description}",
                        absence.urls["detail"],
                    )
                )
                self._by_week[week] += hours / len(weeks)
Exemplo n.º 2
0
def absence_calendar(request, form):
    users = form.users()

    dates = {dt.date.today()}
    absences = defaultdict(list)
    cutoff = monday() - dt.timedelta(days=7)
    queryset = Absence.objects.annotate(
        _ends_on=Coalesce("ends_on", "starts_on")).filter(
            starts_on__lte=cutoff + dt.timedelta(days=366),
            _ends_on__gte=cutoff,
            user__in=users,
        )

    for absence in queryset:
        absences[absence.user_id].append(absence)
        dates.add(max(absence.starts_on, cutoff))
        dates.add(absence._ends_on)

    absences = [{
        "name":
        user.get_full_name(),
        "id":
        user.id,
        "absences": [{
            "id":
            absence.id,
            "reason":
            absence.reason,
            "reasonDisplay":
            absence.get_reason_display(),
            "startsOn":
            time.mktime(max(absence.starts_on, cutoff).timetuple()) * 1000,
            "endsOn":
            time.mktime(
                (absence.ends_on or absence.starts_on).timetuple()) * 1000,
            "days":
            absence.days,
            "description":
            absence.description,
        } for absence in absences[user.id]],
    } for user in users]

    return render(
        request,
        "awt/absence_calendar.html",
        {
            "absences_data": {
                "absencesByPerson": absences,
                "reasonList": Absence.REASON_CHOICES,
                "timeBoundaries": {
                    "start":
                    time.mktime(monday(min(dates)).timetuple()) * 1000,
                    "end": time.mktime(max(dates).timetuple()) * 1000,
                },
                "monday": time.mktime(monday().timetuple()),
            },
            "form": form,
        },
    )
Exemplo n.º 3
0
    def test_planned_work_crud(self):
        """Create, update and delete planned work"""
        service_types = factories.service_types()

        project = factories.ProjectFactory.create()
        self.client.force_login(project.owned_by)

        response = self.client.get(project.urls["creatework"] + "?request=bla")
        self.assertEqual(response.status_code, 200)  # No crash

        response = self.client.post(
            project.urls["creatework"],
            {
                "modal-user": project.owned_by.id,
                "modal-title": "bla",
                "modal-planned_hours": 50,
                "modal-weeks": [monday().isoformat()],
                "modal-service_type": service_types.consulting.pk,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 201)

        pw = PlannedWork.objects.get()
        response = self.client.post(
            pw.urls["update"],
            {
                "modal-user": project.owned_by.id,
                "modal-title": "bla",
                "modal-planned_hours": 50,
                "modal-weeks": [monday().isoformat()],
                "modal-service_type": service_types.consulting.pk,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 202)

        response = self.client.post(
            project.urls["creatework"],
            {
                "modal-user": project.owned_by.id,
                "modal-title": "bla",
                "modal-planned_hours": 50,
                "modal-weeks": [
                    monday().isoformat(),
                    (monday() + dt.timedelta(days=7)).isoformat(),
                ],
                "modal-service_type": service_types.consulting.pk,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        # print(response, response.content.decode("utf-8"))
        self.assertEqual(response.status_code, 201)
Exemplo n.º 4
0
def campaign_planning(campaign, *, external_view=False):
    projects = Project.objects.filter(campaign=campaign)
    projects_ids = ([project.id for project in projects],)

    with connections["default"].cursor() as cursor:
        cursor.execute(
            """\
WITH sq AS (
    SELECT unnest(weeks) AS week
    FROM planning_plannedwork
    WHERE project_id = ANY %s

    UNION ALL

    SELECT date_trunc('week', date)::date
    FROM planning_milestone
    WHERE project_id = ANY %s
)
SELECT MIN(week), MAX(week) FROM sq
            """,
            [projects_ids, projects_ids],
        )
        result = list(cursor)[0]

    if result[0]:
        result = (min(result[0], monday() - dt.timedelta(days=14)), result[1])
        weeks = list(
            islice(
                recurring(result[0], "weekly"),
                2 + (result[1] - result[0]).days // 7,
            )
        )
    else:
        weeks = list(islice(recurring(monday() - dt.timedelta(days=14), "weekly"), 80))

    planning = Planning(weeks=weeks, projects=projects, external_view=external_view)

    planning.add_planned_work_and_milestones(
        PlannedWork.objects.filter(project__campaign=campaign).select_related(
            "service_type"
        ),
        Milestone.objects.filter(project__campaign=campaign),
        ExternalWork.objects.filter(project__campaign=campaign).select_related(
            "service_type"
        ),
    )
    if not external_view:
        planning.add_worked_hours(LoggedHours.objects.all())
    planning.add_absences(Absence.objects.all())
    planning.add_public_holidays()
    planning.add_milestones(Milestone.objects.all())
    return planning.report()
Exemplo n.º 5
0
def date_ranges():
    this_month = dt.date.today().replace(day=1)
    last_month = (this_month - dt.timedelta(days=1)).replace(day=1)
    next_month = (this_month + dt.timedelta(days=31)).replace(day=1)

    this_quarter = dt.date(this_month.year,
                           1 + (this_month.month - 1) // 3 * 3, 1)
    last_quarter = (this_quarter - dt.timedelta(days=75)).replace(day=1)
    next_quarter = (this_quarter + dt.timedelta(days=105)).replace(day=1)

    return [
        (
            (monday() + dt.timedelta(days=0)).isoformat(),
            (monday() + dt.timedelta(days=6)).isoformat(),
            _("this week"),
        ),
        (
            (monday() - dt.timedelta(days=7)).isoformat(),
            (monday() - dt.timedelta(days=1)).isoformat(),
            _("last week"),
        ),
        (
            this_month.isoformat(),
            (next_month - dt.timedelta(days=1)).isoformat(),
            _("this month"),
        ),
        (
            last_month.isoformat(),
            (this_month - dt.timedelta(days=1)).isoformat(),
            _("last month"),
        ),
        (
            this_quarter.isoformat(),
            (next_quarter - dt.timedelta(days=1)).isoformat(),
            _("this quarter"),
        ),
        (
            last_quarter.isoformat(),
            (this_quarter - dt.timedelta(days=1)).isoformat(),
            _("last quarter"),
        ),
        (
            dt.date(this_month.year, 1, 1).isoformat(),
            dt.date(this_month.year, 12, 31).isoformat(),
            _("this year"),
        ),
        (
            dt.date(this_month.year - 1, 1, 1).isoformat(),
            dt.date(this_month.year - 1, 12, 31).isoformat(),
            _("last year"),
        ),
    ]
Exemplo n.º 6
0
    def __init__(self, data, *args, **kwargs):
        data = data.copy()
        data.setdefault("date_from", monday().isoformat())
        data.setdefault("date_until",
                        (monday() + dt.timedelta(days=6)).isoformat())
        super().__init__(data, *args, **kwargs)

        self.fields["date_from"].help_text = format_html(
            "{}: {}",
            _("Set predefined period"),
            format_html_join(", ",
                             '<a href="#" data-set-period="{}:{}">{}</a>',
                             date_ranges()),
        )
Exemplo n.º 7
0
    def test_declined_offer_warning(self):
        """Warn when offer is declined"""
        offer = factories.OfferFactory.create(status=factories.Offer.DECLINED)
        self.client.force_login(offer.owned_by)

        response = self.client.post(
            offer.project.urls["creatework"],
            {
                "modal-user": offer.owned_by_id,
                "modal-title": "bla",
                "modal-planned_hours": 50,
                "modal-weeks": [monday().isoformat()],
                "modal-offer": offer.id,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertContains(response, 'value="offer-is-declined"')

        response = self.client.post(
            offer.project.urls["createrequest"],
            {
                "modal-title": "Request",
                "modal-earliest_start_on": "2020-06-29",
                "modal-completion_requested_on": "2020-07-27",
                "modal-requested_hours": "40",
                "modal-receivers": [offer.owned_by.id],
                "modal-offer": offer.id,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertContains(response, 'value="offer-is-declined"')
Exemplo n.º 8
0
    def test_replanning(self):
        """Moving planned work between requests"""

        pr1 = factories.PlanningRequestFactory.create(requested_hours=50)
        pr2 = factories.PlanningRequestFactory.create(requested_hours=50)

        pw = factories.PlannedWorkFactory.create(request=pr1,
                                                 weeks=[monday()],
                                                 planned_hours=20)

        pr1.refresh_from_db()
        pr2.refresh_from_db()
        self.assertEqual(pr1.planned_hours, 20)
        self.assertEqual(pr2.planned_hours, 0)

        pw.request = pr2
        pw.save()

        pr1.refresh_from_db()
        pr2.refresh_from_db()
        self.assertEqual(pr1.planned_hours, 0)
        self.assertEqual(pr2.planned_hours, 20)

        pw.delete()

        pr1.refresh_from_db()
        pr2.refresh_from_db()
        self.assertEqual(pr1.planned_hours, 0)
        self.assertEqual(pr2.planned_hours, 0)
Exemplo n.º 9
0
    def test_reporting_smoke(self):
        """Smoke test the planned work report"""
        pw = factories.PlannedWorkFactory.create(weeks=[monday()])

        factories.EmploymentFactory.create(user=pw.user,
                                           date_from=dt.date(2020, 1, 1))
        service = factories.ServiceFactory.create(project=pw.project)
        factories.LoggedHoursFactory.create(rendered_by=pw.user,
                                            service=service)
        factories.AbsenceFactory.create(user=pw.user)
        pr = factories.PlanningRequestFactory.create(
            project=pw.project,
            earliest_start_on=monday() - dt.timedelta(days=21),
            completion_requested_on=monday() + dt.timedelta(days=700),
        )
        pr.receivers.add(pw.user)

        report = reporting.user_planning(pw.user)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("26"))
        self.assertEqual(len(report["projects_offers"]), 1)

        report = reporting.project_planning(pw.project)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("26"))
        self.assertEqual(len(report["projects_offers"]), 1)

        team = factories.TeamFactory.create()
        team.members.add(pw.user)
        report = reporting.team_planning(team)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("26"))
        self.assertEqual(len(report["projects_offers"]), 1)

        pw2 = factories.PlannedWorkFactory.create(
            project=pw.project,
            user=pw.user,
            weeks=[monday()],
            request=pr,
        )
        report = reporting.user_planning(pw.user)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("46"))
        self.assertEqual(len(report["projects_offers"]), 1)

        work_list = report["projects_offers"][0]["offers"][0]["work_list"]
        self.assertEqual(len(work_list), 3)
        self.assertEqual(work_list[0]["work"]["id"], pr.id)
        self.assertEqual(work_list[1]["work"]["id"], pw2.id)
        self.assertEqual(work_list[2]["work"]["id"], pw.id)
Exemplo n.º 10
0
 def maybe_actionable(self, *, user):
     day = monday()
     weeks = [day + dt.timedelta(days=days) for days in [0, 7, 14, 21]]
     return self.filter(
         Q(user=user) | Q(created_by=user) | Q(project__owned_by=user),
         Q(is_provisional=True),
         Q(weeks__overlap=weeks),
     )
Exemplo n.º 11
0
 def add_worked_hours(self, queryset):
     for row in (
         queryset.filter(service__project__in=self._project_ids)
         .values("service__project", "service__offer", "rendered_on")
         .annotate(Sum("hours"))
     ):
         self._worked_hours[row["service__project"]][
             monday(row["rendered_on"])
         ] += row["hours__sum"]
Exemplo n.º 12
0
def user_planning(user):
    weeks = list(
        islice(recurring(monday() - dt.timedelta(days=14), "weekly"), 80))
    planning = Planning(weeks=weeks, users=[user])
    planning.add_planned_work(user.planned_work.all())
    planning.add_planning_requests(user.received_planning_requests.all())
    planning.add_worked_hours(user.loggedhours.all())
    planning.add_absences(user.absences.all())
    return planning.report()
Exemplo n.º 13
0
def project_planning(project):
    with connections["default"].cursor() as cursor:
        cursor.execute(
            """\
WITH sq AS (
    SELECT unnest(weeks) AS week
    FROM planning_plannedwork
    WHERE project_id=%s

    UNION ALL

    SELECT earliest_start_on AS week
    FROM planning_planningrequest
    WHERE project_id=%s

    UNION ALL

    SELECT completion_requested_on - 7 AS week
    FROM planning_planningrequest
    WHERE project_id=%s
)
SELECT MIN(week), MAX(week) FROM sq
            """,
            [project.id, project.id, project.id],
        )
        result = list(cursor)[0]

    if result[0]:
        result = (min(result[0], monday() - dt.timedelta(days=14)), result[1])
        weeks = list(
            islice(
                recurring(result[0], "weekly"),
                2 + (result[1] - result[0]).days // 7,
            ))
    else:
        weeks = list(
            islice(recurring(monday() - dt.timedelta(days=14), "weekly"), 80))

    planning = Planning(weeks=weeks)
    planning.add_planned_work(project.planned_work.all())
    planning.add_planning_requests(project.planning_requests.all())
    planning.add_worked_hours(LoggedHours.objects.all())
    planning.add_absences(Absence.objects.all())
    return planning.report()
Exemplo n.º 14
0
    def test_reporting_smoke(self):
        """Smoke test the planned work report"""
        pw = factories.PlannedWorkFactory.create(weeks=[monday()])

        factories.EmploymentFactory.create(user=pw.user, date_from=dt.date(2020, 1, 1))
        service = factories.ServiceFactory.create(project=pw.project)
        factories.LoggedHoursFactory.create(rendered_by=pw.user, service=service)
        factories.AbsenceFactory.create(user=pw.user)

        report = reporting.user_planning(pw.user, date_range)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("28"))
        self.assertEqual(len(report["projects_offers"]), 1)

        report = reporting.project_planning(pw.project)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("28"))
        self.assertEqual(len(report["projects_offers"]), 1)

        team = factories.TeamFactory.create()
        team.members.add(pw.user)
        report = reporting.team_planning(team, date_range)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("28"))
        self.assertEqual(len(report["projects_offers"]), 1)

        pw2 = factories.PlannedWorkFactory.create(
            project=pw.project,
            user=pw.user,
            weeks=[monday()],
        )
        report = reporting.user_planning(pw.user, date_range)
        self.assertAlmostEqual(sum(report["by_week"]), Decimal("48"))
        self.assertEqual(len(report["projects_offers"]), 1)

        work_list = report["projects_offers"][0]["offers"][0]["work_list"]
        self.assertEqual(len(work_list), 2)
        self.assertEqual(work_list[0]["work"]["id"], pw2.id)
        self.assertEqual(work_list[1]["work"]["id"], pw.id)

        report = reporting.planning_vs_logbook(date_range, users=User.objects.all())
        self.assertAlmostEqual(report["logged"], Decimal("1.0"))
        self.assertAlmostEqual(report["planned"], Decimal("40.0"))
        # Exactly one customer
        (c,) = report["per_customer"]
        self.assertEqual(len(c["per_week"]), 1)
Exemplo n.º 15
0
    def hours(self):
        per_day = {
            row["rendered_on"]: row["hours__sum"]
            for row in self.loggedhours.filter(rendered_on__gte=monday()).
            order_by().values("rendered_on").annotate(Sum("hours"))
        }

        return {
            "today": per_day.get(dt.date.today(), Decimal("0.0")),
            "week": sum(per_day.values(), Decimal("0.0")),
        }
Exemplo n.º 16
0
    def test_receivers_with_work(self):
        """receivers_with_work returns all requested and planned work"""

        pr = factories.PlanningRequestFactory.create()
        only_receiver = factories.UserFactory.create()
        pr.receivers.add(only_receiver)
        only_pw = factories.PlannedWorkFactory.create(project=pr.project,
                                                      request=pr,
                                                      weeks=[monday()])
        both = factories.PlannedWorkFactory.create(project=pr.project,
                                                   request=pr,
                                                   weeks=[monday()])
        pr.receivers.add(both.user)

        self.assertEqual(set(pr.receivers.all()), {both.user, only_receiver})
        self.assertEqual(len(pr.receivers_with_work), 3)

        receivers = dict(pr.receivers_with_work)
        self.assertEqual(receivers[only_receiver], [])
        self.assertEqual(receivers[both.user], [both])
        self.assertEqual(receivers[only_pw.user], [only_pw])
Exemplo n.º 17
0
def team_planning(team):
    weeks = list(
        islice(recurring(monday() - dt.timedelta(days=14), "weekly"), 80))
    planning = Planning(weeks=weeks, users=list(team.members.active()))
    planning.add_planned_work(PlannedWork.objects.filter(user__teams=team))
    planning.add_planning_requests(
        PlanningRequest.objects.filter(
            Q(receivers__teams=team) | Q(planned_work__user__teams=team)))
    planning.add_worked_hours(
        LoggedHours.objects.filter(rendered_by__teams=team))
    planning.add_absences(Absence.objects.filter(user__teams=team))
    return planning.report()
Exemplo n.º 18
0
def team_planning(team, date_range):
    start, end = date_range
    weeks = list(takewhile(lambda x: x <= end, recurring(monday(start), "weekly")))
    planning = Planning(weeks=weeks, users=list(team.members.active()))
    planning.add_planned_work_and_milestones(
        PlannedWork.objects.filter(user__teams=team).select_related("service_type"),
        Milestone.objects.filter(project__planned_work__user__teams=team),
    )
    planning.add_worked_hours(LoggedHours.objects.filter(rendered_by__teams=team))
    planning.add_absences(Absence.objects.filter(user__teams=team))
    planning.add_public_holidays()
    planning.add_milestones(Milestone.objects.all())
    return planning.report()
Exemplo n.º 19
0
 def report(self):
     try:
         this_week_index = self.weeks.index(monday())
     except ValueError:
         this_week_index = None
     return {
         "this_week_index": this_week_index,
         "weeks": [
             {
                 "monday": week,
                 "month": local_date_format(week, fmt="M"),
                 "week": local_date_format(week, fmt="W"),
                 "period": "{}–{}".format(
                     local_date_format(week, fmt="j."),
                     local_date_format(week + dt.timedelta(days=6), fmt="j."),
                 ),
             }
             for week in self.weeks
         ],
         "projects_offers": sorted(
             filter(
                 None,
                 (
                     self._project_record(project, offers)
                     for project, offers in self._projects_offers.items()
                 ),
             ),
             key=lambda row: (
                 row["project"]["date_from"],
                 row["project"]["date_until"],
                 -row["project"]["planned_hours"],
             )
             if row["project"]["date_from"] and row["project"]["date_until"]
             else (),
         ),
         "by_week": [self._by_week[week] for week in self.weeks],
         "by_week_provisional": [
             self._by_week_provisional[week] for week in self.weeks
         ],
         "absences": [
             (str(user), lst) for user, lst in sorted(self._absences.items())
         ],
         "capacity": self.capacity() if self.users else None,
         "service_types": [
             {"id": type.id, "title": type.title, "color": type.color}
             for type in ServiceType.objects.all()
         ],
         "external_view": self.external,
     }
Exemplo n.º 20
0
def user_planning(user, date_range):
    start, end = date_range
    weeks = list(takewhile(lambda x: x <= end, recurring(monday(start), "weekly")))
    planning = Planning(weeks=weeks, users=[user])
    planning.add_planned_work_and_milestones(
        user.planned_work.select_related("service_type"),
        Milestone.objects.filter(
            Q(project__planned_work__user=user) | Q(project__owned_by=user)
        ),
    )
    planning.add_worked_hours(user.loggedhours.all())
    planning.add_absences(user.absences.all())
    planning.add_public_holidays()
    planning.add_milestones(Milestone.objects.all())
    return planning.report()
Exemplo n.º 21
0
    def add_project_milestone(self, project, milestone):
        if milestone and (not self._milestones[project][milestone]):

            start = (
                milestone.phase_starts_on
                if milestone.phase_starts_on
                else milestone.date
            )
            weeks = [
                1 if monday(start) <= w <= monday(milestone.date) else 0
                for w in self.weeks
            ]
            graphical_weeks = [
                1 if monday(milestone.date) == w else 0 for w in self.weeks
            ]

            self._milestones[project][milestone].update(
                {
                    "id": milestone.id,
                    "title": milestone.title,
                    "dow": local_date_format(milestone.date, fmt="l, j.n."),
                    "date": local_date_format(milestone.date, fmt="j."),
                    "range": "{} – {}".format(
                        local_date_format(start, fmt="d.m."),
                        local_date_format(milestone.date, fmt="d.m."),
                    )
                    if milestone.phase_starts_on
                    else None,
                    "hours": milestone.estimated_total_hours,
                    "phase_starts_on": start if milestone.phase_starts_on else None,
                    "weekday": milestone.date.isocalendar()[2],
                    "url": milestone.urls["detail"],
                    "weeks": weeks,
                    "graphical_weeks": graphical_weeks,
                }
            )
Exemplo n.º 22
0
 def report(self):
     try:
         this_week_index = self.weeks.index(monday())
     except ValueError:
         this_week_index = None
     return {
         "this_week_index":
         this_week_index,
         "weeks": [{
             "month":
             local_date_format(week, fmt="M"),
             "week":
             local_date_format(week, fmt="W"),
             "period":
             "{}–{}".format(
                 local_date_format(week, fmt="j."),
                 local_date_format(week + dt.timedelta(days=6), fmt="j."),
             ),
         } for week in self.weeks],
         "projects_offers":
         sorted(
             filter(
                 None,
                 (self._project_record(project, offers)
                  for project, offers in self._projects_offers.items()),
             ),
             key=lambda row: (
                 row["project"]["date_from"],
                 row["project"]["date_until"],
                 -row["project"]["planned_hours"],
             ),
         ),
         "by_week": [self._by_week[week] for week in self.weeks],
         "requested_by_week":
         [self._requested_by_week[week] for week in self.weeks],
         "absences":
         [(str(user), lst) for user, lst in sorted(self._absences.items())],
         "capacity":
         self.capacity() if self.users else None,
     }
Exemplo n.º 23
0
 def this_monday(self):
     return monday()
Exemplo n.º 24
0
def start_of_monday():
    return timezone.make_aware(dt.datetime.combine(monday(), dt.time.min))
Exemplo n.º 25
0
    def add_public_holidays(self):
        ud = {user.id: user for user in User.objects.filter(id__in=self._user_ids)}

        for (
            id,
            date,
            name,
            user_id,
            planning_hours_per_day,
            fraction,
            percentage,
        ) in query(
            """
select
    ph.id,
    ph.date,
    ph.name,
    user_id,
    planning_hours_per_day,
    fraction,
    percentage

from planning_publicholiday ph
left outer join lateral (
    select
        user_id,
        date_from,
        date_until,
        percentage,
        planning_hours_per_day
    from awt_employment
    left join accounts_user on awt_employment.user_id=accounts_user.id
    where user_id = any (%s)
) as employment
on employment.date_from <= ph.date and employment.date_until > ph.date

where ph.date between %s and %s and user_id is not null
order by ph.date
            """,
            [list(ud.keys()), min(self.weeks), max(self.weeks)],
        ):
            # Skip weekends
            if date.weekday() >= 5:
                continue

            week = monday(date)
            idx = self.weeks.index(week)

            user = ud[user_id]
            ph_hours = (
                (planning_hours_per_day or 0)
                * (fraction or 0)
                * (percentage or 0)
                / 100
            )
            detail = " × ".join(
                (
                    f"{hours(planning_hours_per_day)}/d",
                    f"{percentage}%",
                    f"{fraction}d",
                )
            )
            self._absences[user][idx].append(
                (
                    ph_hours,
                    f"{name} ({detail} = {hours(ph_hours)})",
                    reverse("planning_publicholiday_detail", kwargs={"pk": id}),
                )
            )
            self._by_week[week] += ph_hours
Exemplo n.º 26
0
                    }
                )

        super().__init__(*args, **kwargs)
        self.instance.project = self.project

        self.fields[
            "offer"
        ].choices = self.instance.project.offers.not_declined_choices(
            include=self.instance.offer_id
        )
        self.fields["milestone"].queryset = self.project.milestones.all()
        self.fields["service_type"].required = True

        date_from_options = [
            monday(),
            self.instance.weeks and min(self.instance.weeks),
            initial.get("weeks") and min(initial["weeks"]),
        ]
        date_from = min(filter(None, date_from_options)) - dt.timedelta(days=21)

        self.fields["weeks"] = forms.TypedMultipleChoiceField(
            label=capfirst(_("weeks")),
            choices=[
                (
                    day,
                    "KW{} ({} - {})".format(
                        local_date_format(day, fmt="W"),
                        local_date_format(day),
                        local_date_format(day + dt.timedelta(days=6)),
                    ),
Exemplo n.º 27
0
    def test_planned_work_crud(self):
        """Create, update and delete planned work"""
        pr = factories.PlanningRequestFactory.create(
            earliest_start_on=monday(),
            completion_requested_on=monday() + dt.timedelta(days=14),
        )
        self.client.force_login(pr.created_by)

        response = self.client.get(pr.project.urls["creatework"] +
                                   "?request=bla")
        self.assertEqual(response.status_code, 200)  # No crash

        response = self.client.post(
            pr.project.urls["creatework"],
            {
                "modal-user": pr.created_by.id,
                "modal-title": "bla",
                "modal-planned_hours": 50,
                "modal-weeks": [monday().isoformat()],
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 201)

        pw = PlannedWork.objects.get()
        response = self.client.post(
            pw.urls["update"],
            {
                "modal-user": pr.created_by.id,
                "modal-title": "bla",
                "modal-planned_hours": 50,
                "modal-weeks": [monday().isoformat()],
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 202)

        response = self.client.post(
            pr.project.urls["creatework"] + "?request={}".format(pr.id),
            {
                "modal-request":
                pr.id,
                "modal-user":
                pr.created_by.id,
                "modal-title":
                "bla",
                "modal-planned_hours":
                50,
                "modal-weeks": [
                    monday().isoformat(),
                    (monday() + dt.timedelta(days=7)).isoformat(),
                ],
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        # print(response, response.content.decode("utf-8"))
        self.assertEqual(response.status_code, 201)

        response = self.client.post(
            pr.project.urls["creatework"] + "?request={}".format(pr.id),
            {
                "modal-request":
                pr.id,
                "modal-user":
                pr.created_by.id,
                "modal-title":
                "bla",
                "modal-planned_hours":
                50,
                "modal-weeks": [
                    monday().isoformat(),
                    (monday() + dt.timedelta(days=14)).isoformat(),
                ],
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertContains(response, "weeks-outside-request")
Exemplo n.º 28
0
def logged_hours(user):
    stats = {}

    from_ = monday(in_days(-180))

    hours_per_week = {}
    for week, type, hours in query(
            """
WITH sq AS (
    SELECT
        date_trunc('week', rendered_on) AS week,
        project.type AS type,
        SUM(hours) AS hours
    FROM logbook_loggedhours hours
    LEFT JOIN projects_service service ON hours.service_id=service.id
    LEFT JOIN projects_project project ON service.project_id=project.id
    WHERE rendered_by_id=%s AND rendered_on>=%s
    GROUP BY week, project.type
)
SELECT series.week, sq.type, COALESCE(sq.hours, 0)
FROM generate_series(%s, %s, '7 days') AS series(week)
LEFT OUTER JOIN sq ON series.week=sq.week
ORDER BY series.week
""",
        [user.id, from_, from_,
         monday() + dt.timedelta(days=6)],
    ):
        if week in hours_per_week:
            hours_per_week[week]["hours"] += hours
            hours_per_week[week]["by_type"][type] = hours
        else:
            hours_per_week[week] = {
                "week": week,
                "hours": hours,
                "by_type": {
                    type: hours
                },
            }

    stats["hours_per_week"] = [
        row[1] for row in sorted(hours_per_week.items())
    ]

    hours_per_customer = defaultdict(dict)
    total_hours_per_customer = defaultdict(int)

    for week, customer, hours in query(
            """
WITH sq AS (
    SELECT
        date_trunc('week', rendered_on) AS week,
        customer.name AS customer,
        SUM(hours) AS hours
    FROM logbook_loggedhours hours
    LEFT JOIN projects_service service ON hours.service_id=service.id
    LEFT JOIN projects_project project ON service.project_id=project.id
    LEFT JOIN contacts_organization customer ON project.customer_id=customer.id
    WHERE rendered_by_id=%s AND rendered_on>=%s
    GROUP BY week, customer.name
)
SELECT series.week, COALESCE(sq.customer, ''), COALESCE(sq.hours, 0)
FROM generate_series(%s, %s, '7 days') AS series(week)
LEFT OUTER JOIN sq ON series.week=sq.week
ORDER BY series.week
        """,
        [user.id, from_, from_,
         monday() + dt.timedelta(days=6)],
    ):
        customer = customer.split("\n")[0]
        hours_per_customer[week][customer] = hours
        total_hours_per_customer[customer] += hours

    customers = [
        row[0] for row in sorted(total_hours_per_customer.items(),
                                 key=lambda row: row[1],
                                 reverse=True)
    ][:10]

    weeks = sorted(hours_per_customer.keys())
    stats["hours_per_customer"] = {
        "weeks":
        weeks,
        "by_customer": [{
            "name":
            customer,
            "hours":
            [hours_per_customer[week].get(customer, 0) for week in weeks],
        } for customer in customers],
    }
    customers = set(customers)
    stats["hours_per_customer"]["by_customer"].append({
        "name":
        _("All others"),
        "hours": [
            sum(
                (hours for customer, hours in hours_per_customer[week].items()
                 if customer not in customers),
                0,
            ) for week in weeks
        ],
    })

    dows = [
        None,
        _("Monday"),
        _("Tuesday"),
        _("Wednesday"),
        _("Thursday"),
        _("Friday"),
        _("Saturday"),
        _("Sunday"),
    ]

    stats["rendered_hours_per_weekday"] = [{
        "dow": int(dow),
        "name": dows[int(dow)],
        "hours": hours
    } for dow, hours in query(
        """
WITH sq AS (
    SELECT
        (extract(isodow from rendered_on)::integer) as dow,
        SUM(hours) AS hours
    FROM logbook_loggedhours
    WHERE rendered_by_id=%s AND rendered_on>=%s
    GROUP BY dow
    ORDER BY dow
)
SELECT series.dow, COALESCE(sq.hours, 0)
FROM generate_series(1, 7) AS series(dow)
LEFT OUTER JOIN sq ON series.dow=sq.dow
ORDER BY series.dow
            """,
        [user.id, from_],
    )]

    stats["created_hours_per_weekday"] = [{
        "dow": int(dow),
        "name": dows[int(dow)],
        "hours": hours
    } for dow, hours in query(
        """
WITH sq AS (
    SELECT
        (extract(isodow from timezone('CET', created_at))::integer) as dow,
        SUM(hours) AS hours
    FROM logbook_loggedhours
    WHERE rendered_by_id=%s AND rendered_on>=%s
    GROUP BY dow
    ORDER BY dow
)
SELECT series.dow, COALESCE(sq.hours, 0)
FROM generate_series(1, 7) AS series(dow)
LEFT OUTER JOIN sq ON series.dow=sq.dow
ORDER BY series.dow
            """,
        [user.id, from_],
    )]

    return stats
Exemplo n.º 29
0
                    "notes": service.description,
                    "planned_hours": service.service_hours,
                })

        super().__init__(*args, **kwargs)
        self.instance.project = self.project

        self.fields[
            "offer"].choices = self.instance.project.offers.not_declined_choices(
                include=self.instance.offer_id)
        self.fields[
            "request"].queryset = self.instance.project.planning_requests.all(
            )

        date_from_options = [
            monday(),
            self.instance.weeks and min(self.instance.weeks),
            pr and min(pr.weeks),
        ]
        date_from = min(filter(None,
                               date_from_options)) - dt.timedelta(days=21)

        self.fields["weeks"] = forms.TypedMultipleChoiceField(
            label=capfirst(_("weeks")),
            choices=[(
                day,
                "KW{} ({} - {})".format(
                    local_date_format(day, fmt="W"),
                    local_date_format(day),
                    local_date_format(day + dt.timedelta(days=6)),
                ),
Exemplo n.º 30
0
class ExternalWorkForm(ModelForm):
    class Meta:
        model = ExternalWork
        fields = (
            "provided_by",
            "title",
            "service_type",
            "notes",
            "milestone",
        )
        widgets = {
            "provided_by": Autocomplete(model=Organization),
            "notes": Textarea,
        }

    def __init__(self, *args, **kwargs):
        initial = kwargs.setdefault("initial", {})
        request = kwargs["request"]

        self.project = kwargs.pop("project", None)
        if not self.project:  # Updating
            self.project = kwargs["instance"].project
        else:
            initial["provided_by"] = self.project.customer_id

        if service_id := request.GET.get("service"):
            try:
                service = self.project.services.get(pk=service_id)
            except (self.project.services.model.DoesNotExist, TypeError, ValueError):
                pass
            else:
                initial.update(
                    {
                        "title": f"{self.project.title}: {service.title}",
                        "notes": service.description,
                        "planned_hours": service.service_hours,
                    }
                )

        # if pk := request.GET.get("copy"):
        #     try:
        #         pw = ExternalWork.objects.get(pk=pk)
        #     except (ExternalWork.DoesNotExist, TypeError, ValueError):
        #         pass
        #     else:
        #         initial.update(
        #             {
        #                 "project": pw.project_id,
        #                 "offer": pw.offer_id,
        #                 "title": pw.title,
        #                 "notes": pw.notes,
        #                 "planned_hours": pw.planned_hours,
        #                 "weeks": pw.weeks,
        #                 "is_provisional": pw.is_provisional,
        #                 "service_type": pw.service_type_id,
        #                 "milestone": pw.milestone_id,
        #             }
        #         )

        super().__init__(*args, **kwargs)
        self.instance.project = self.project

        self.fields["milestone"].queryset = self.project.milestones.all()
        # self.fields["service_type"].required = True

        date_from_options = [
            monday(),
            self.instance.weeks and min(self.instance.weeks),
            initial.get("weeks") and min(initial["weeks"]),
        ]
        date_from = min(filter(None, date_from_options)) - dt.timedelta(days=21)

        self.fields["weeks"] = forms.TypedMultipleChoiceField(
            label=capfirst(_("weeks")),
            choices=[
                (
                    day,
                    "KW{} ({} - {})".format(
                        local_date_format(day, fmt="W"),
                        local_date_format(day),
                        local_date_format(day + dt.timedelta(days=6)),
                    ),
                )
                for day in islice(recurring(date_from, "weekly"), 80)
            ],
            widget=forms.SelectMultiple(attrs={"size": 20}),
            initial=self.instance.weeks or [monday()],
            coerce=parse_date,
        )