示例#1
0
    def test_glassfrog_requires_role(self):
        """Role is required with GLASSFROG"""
        project = factories.ProjectFactory.create()
        self.client.force_login(project.owned_by)

        response = self.client.post(
            project.urls["createhours"],
            {
                "modal-rendered_by": project.owned_by_id,
                "modal-rendered_on": in_days(0).isoformat(),
                "modal-hours": "0.1",
                "modal-description": "Test",
                "modal-service_title": "service title",
                "modal-service_description": "service description",
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertContains(response, "This field is required")

        response = self.client.post(
            project.urls["createhours"],
            {
                "modal-rendered_by": project.owned_by_id,
                "modal-rendered_on": in_days(0).isoformat(),
                "modal-hours": "0.1",
                "modal-description": "Test",
                "modal-service_title": "service title",
                "modal-service_description": "service description",
                "modal-service_role": factories.RoleFactory.create().pk,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 201)
示例#2
0
    def test_updates(self):
        """Test the planning updates functionality"""
        self.set_current_user()

        pw = factories.PlannedWorkFactory.create(
            weeks=[in_days(d) for d in (0, 7, 14)])
        original_pw_user = pw.user
        m = factories.MilestoneFactory.create(project=pw.project,
                                              date=dt.date.today())

        LoggedAction.objects.all().update(created_at=F("created_at") -
                                          dt.timedelta(days=14))

        pw.user = pw.project.owned_by
        pw.hours = 50
        pw.weeks = [in_days(d) for d in (7, 14, 21)]
        pw.milestone = m
        pw.save()
        m.date = dt.date.today() + dt.timedelta(days=1)
        m.save()

        c = updates.changes(since=timezone.now() - dt.timedelta(days=1))
        self.assertEqual(set(c), {original_pw_user, pw.user})

        from pprint import pprint

        pprint(c)

        updates.changes_mails()
        self.assertEqual(len(mail.outbox), 2)
示例#3
0
    def test_renewal_candidates(self):
        """Renewal candidates depends on start date and the day of period when
        the invoice is created"""
        r1 = factories.RecurringInvoiceFactory.create(
            starts_on=in_days(10),
            periodicity="monthly",
        )
        r2 = factories.RecurringInvoiceFactory.create(
            starts_on=in_days(30),
            periodicity="monthly",
        )

        self.assertEqual(set(RecurringInvoice.objects.renewal_candidates()),
                         {r1})

        r3 = factories.RecurringInvoiceFactory.create(
            starts_on=in_days(-250),
            periodicity="yearly",
            create_invoice_on_day=300,
        )
        r4 = factories.RecurringInvoiceFactory.create(
            starts_on=in_days(-350),
            periodicity="yearly",
            create_invoice_on_day=300,
        )

        self.assertEqual(set(RecurringInvoice.objects.renewal_candidates()),
                         {r1, r4})

        r2, r3  # Using those variables
示例#4
0
 def clean(self):
     data = super().clean()
     errors = {}
     if self.project.closed_on:
         if self.project.closed_on < in_days(-14):
             errors["__all__"] = _(
                 "This project has been closed too long ago.")
         else:
             self.add_warning(_("This project has been closed recently."),
                              code="project-closed")
     if self.instance.invoice_service:
         self.add_warning(_("This entry is already part of an invoice."),
                          code="part-of-invoice")
     if data.get("are_expenses") and not data.get("third_party_costs"):
         errors["third_party_costs"] = (
             _("Providing third party costs is necessary for expenses."), )
     if data.get("cost") and data.get("third_party_costs") is not None:
         if data["cost"] < data["third_party_costs"]:
             self.add_warning(
                 _("Third party costs shouldn't be higher than costs."),
                 code="third-party-costs-higher",
             )
     if data.get("rendered_on") and data["rendered_on"] > in_days(7):
         errors["rendered_on"] = _("That's too far in the future.")
     raise_if_errors(errors)
     return data
示例#5
0
    def test_calendar(self):
        """The absence calendar report does not crash"""
        user = factories.UserFactory.create()
        self.client.force_login(user)

        Absence.objects.create(
            user=user,
            starts_on=dt.date.today(),
            days=0,
            description="Test",
            reason=Absence.VACATION,
        )
        Absence.objects.create(
            user=user,
            starts_on=in_days(10),
            ends_on=in_days(20),
            days=0,
            description="Test",
            reason=Absence.VACATION,
        )

        code = check_code(self, "/report/absence-calendar/")
        code("")

        team = factories.TeamFactory.create()
        user.teams.add(team)
        code(f"team={team.pk}")
        code("team=abc", status_code=302)
示例#6
0
    def test_model_validation(self):
        """Offer model validation"""
        offer = Offer(
            title="Test",
            project=factories.ProjectFactory.create(),
            owned_by=factories.UserFactory.create(),
            status=Offer.OFFERED,
            postal_address="Test\nStreet\nCity",
            _code=1,
        )

        with self.assertRaises(ValidationError) as cm:
            offer.clean_fields()
        self.assertEqual(
            list(cm.exception),
            [
                ("offered_on", ["Offered on date missing for selected state."
                                ]),
                ("valid_until",
                 ["Valid until date missing for selected state."]),
            ],
        )

        with self.assertRaises(ValidationError) as cm:
            offer.offered_on = in_days(0)
            offer.valid_until = in_days(-1)
            offer.full_clean()
        self.assertEqual(
            list(cm.exception),
            [("valid_until",
              ["Valid until date has to be after offered on date."])],
        )
示例#7
0
    def test_reminders(self):
        """The reminders view allows exporting dunning letters"""
        invoice = factories.InvoiceFactory.create(
            invoiced_on=in_days(-60),
            due_on=in_days(-45),
            status=Invoice.SENT,
        )
        factories.InvoiceFactory.create(
            customer=invoice.customer,
            contact=invoice.contact,
            invoiced_on=in_days(-60),
            due_on=in_days(-45),
            status=Invoice.SENT,
        )

        self.client.force_login(factories.UserFactory.create())
        response = self.client.get("/invoices/reminders/")
        self.assertContains(response, "Not reminded yet")
        # print(response, response.content.decode("utf-8"))

        response = self.client.post(
            f"/invoices/dunning-letter/{invoice.customer_id}/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/pdf")

        invoice.refresh_from_db()
        self.assertEqual(invoice.last_reminded_on, dt.date.today())

        self.assertEqual(invoice.payment_reminders_sent_at(),
                         [dt.date.today()])

        response = self.client.get("/invoices/reminders/")
        self.assertNotContains(response, "Not reminded yet")
示例#8
0
    def test_please_decline(self):
        """Please do not decline offers but update them and send them again"""
        offer = factories.OfferFactory.create()
        self.client.force_login(offer.owned_by)

        response = self.client.post(
            offer.urls["update"],
            {
                "title": "Stuff",
                "owned_by": offer.owned_by_id,
                "discount": "10",
                "liable_to_vat": "1",
                "postal_address": "Anything\nStreet\nCity",
                "offered_on": in_days(0).isoformat(),
                "valid_until": in_days(60).isoformat(),
                "status": Offer.DECLINED,
            },
        )
        self.assertContains(
            response,
            "However, if you just want to change a few things and send the offer",
        )

        response = self.client.post(
            offer.urls["update"],
            {
                "title": "Stuff",
                "owned_by": offer.owned_by_id,
                "discount": "10",
                "liable_to_vat": "1",
                "postal_address": "Anything\nStreet\nCity",
                "offered_on": in_days(0).isoformat(),
                "valid_until": in_days(60).isoformat(),
                "status": Offer.DECLINED,
                WarningsForm.ignore_warnings_id: "yes-please-decline",
            },
        )
        self.assertRedirects(response,
                             offer.get_absolute_url(),
                             fetch_redirect_response=False)

        offer.refresh_from_db()  # Refresh the offer title etc.
        self.assertEqual(
            messages(response),
            [
                "All offers of project {} are declined. You might "
                "want to close the project now?".format(offer.project),
                f"Offer '{offer}' has been updated successfully.",
            ],
        )

        offer.project.closed_on = dt.date.today()
        self.assertIsNone(
            offer.project.solely_declined_offers_warning(request=None))
示例#9
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warn_if_not_in_preparation(self)

        field = Value._meta.get_field("value")
        values = {v.type_id: v.value for v in self.instance.values.all()}
        for vt in ValueType.objects.all():
            key = "value_{}".format(vt.id)

            if vt.is_archived and vt.id not in values:
                continue

            self.fields[key] = field.formfield(
                label="%s (%s)" % (vt.title, capfirst(_("total CHF"))),
                required=False,
                initial=values.get(vt.id),
            )

        attributes = ({
            a.group_id: a.id
            for a in self.instance.attributes.all()
        } if self.instance.id else {})
        for group in AttributeGroup.objects.all():
            key = "attribute_{}".format(group.id)
            if group.is_archived and group.id not in attributes:
                continue

            self.fields[key] = forms.ModelChoiceField(
                queryset=group.attributes.all(),
                required=group.is_required,
                label=group.title,
                widget=forms.RadioSelect,
                initial=attributes.get(group.id),
            )
            self.fields[key].choices = [(a.id, str(a))
                                        for a in group.attributes.active(
                                            include=attributes.get(group.id))]

        self.fields["decision_expected_on"].help_text = format_html(
            "{} {}",
            _("Expected"),
            format_html_join(
                ", ",
                '<a href="#" data-field-value="{}">{}</a>',
                [
                    (in_days(7).isoformat(), _("in one week")),
                    (in_days(30).isoformat(), _("in one month")),
                    (in_days(60).isoformat(), _("in two months")),
                    (in_days(90).isoformat(), _("in three months")),
                ],
            ),
        )
示例#10
0
    def test_related_offers(self):
        """Offers can be linked to deals"""
        deal = factories.DealFactory.create()
        offer = factories.OfferFactory.create(
            title="Test",
            postal_address="Test\nTest street\nTest",
            offered_on=in_days(0),
            valid_until=in_days(60),
        )

        self.client.force_login(deal.owned_by)

        # No related offers, field should not exist at all
        response = self.client.get(deal.urls["set_status"] + "?status=20")
        self.assertNotContains(response, "related_offers")

        response = self.client.post(deal.urls["add_offer"], {"modal-offer": ""})
        self.assertEqual(response.status_code, 200)

        response = self.client.post(deal.urls["add_offer"], {"modal-offer": offer.pk})
        self.assertEqual(response.status_code, 201)

        self.assertEqual(deal.related_offers.get(), offer)

        # Related offers should now appear in the form
        response = self.client.get(deal.urls["set_status"] + "?status=20")
        self.assertContains(response, "related_offers")
        self.assertContains(response, offer.code)

        # Accept the deal, and accept related offers while doing this
        closing_type = factories.ClosingTypeFactory.create(represents_a_win=True)
        response = self.client.post(
            deal.urls["set_status"] + "?status=20",
            {"closing_type": closing_type.pk, "related_offers": [offer.pk]},
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 202)

        offer.refresh_from_db()
        self.assertEqual(offer.status, offer.ACCEPTED)
        self.assertTrue(offer.closed_on is not None)

        # Remove offers
        response = self.client.post(deal.urls["remove_offer"], {"modal-offer": ""})
        self.assertRedirects(response, deal.urls["detail"])

        response = self.client.post(
            deal.urls["remove_offer"], {"modal-offer": offer.pk}
        )
        self.assertRedirects(response, deal.urls["detail"])
示例#11
0
def user_planning(request, pk, retro=False):
    instance = get_object_or_404(User.objects.active(), pk=pk)
    date_range = [in_days(-180), in_days(14)
                  ] if retro else [in_days(-14), in_days(400)]

    return render(
        request,
        "planning/user_planning.html",
        {
            "object": instance,
            "user": instance,
            "planning_data": reporting.user_planning(instance, date_range),
        },
    )
示例#12
0
def team_planning(request, pk, retro=False):
    instance = get_object_or_404(Team.objects.all(), pk=pk)
    date_range = [in_days(-180), in_days(14)
                  ] if retro else [in_days(-14), in_days(400)]

    return render(
        request,
        "planning/team_planning.html",
        {
            "object": instance,
            "team": instance,
            "planning_data": reporting.team_planning(instance, date_range),
        },
    )
示例#13
0
    def test_offer_not_valid_anymore(self):
        """Sent offers which are not valid anymore have a warning badge"""
        project = factories.ProjectFactory.create()
        offer = factories.OfferFactory.create(
            project=project,
            offered_on=in_days(-90),
            valid_until=in_days(-30),
            status=Offer.OFFERED,
        )

        self.assertEqual(
            offer.status_badge,
            '<span class="badge badge-warning">Offered on 04.02.2020, but not valid anymore</span>',  # noqa
        )
示例#14
0
    def test_create_and_update_logged_cost(self):
        """Creating and updating logged costs playthrough"""
        service = factories.ServiceFactory.create()
        project = service.project
        self.client.force_login(project.owned_by)

        def send(url=project.urls["createcost"], additional=None, **kwargs):
            data = {
                "modal-service": service.id,
                "modal-rendered_by": project.owned_by_id,
                "modal-rendered_on": dt.date.today().isoformat(),
                "modal-cost": "10",
                "modal-third_party_costs": "9",
                "modal-description": "Anything",
            }
            data.update(additional or {})
            data.update(
                {"modal-%s" % key: value
                 for key, value in kwargs.items()})
            return self.client.post(url,
                                    data,
                                    HTTP_X_REQUESTED_WITH="XMLHttpRequest")

        response = send()
        self.assertEqual(response.status_code, 201)

        cost = LoggedCost.objects.get()
        project.closed_on = dt.date.today()
        project.save()

        response = send(cost.urls["update"])
        self.assertContains(response, "This project has been closed recently.")

        response = send(
            cost.urls["update"],
            additional={WarningsForm.ignore_warnings_id: "project-closed"},
        )
        self.assertEqual(response.status_code, 202)

        project.closed_on = in_days(-20)
        project.save()

        response = send(cost.urls["update"])
        self.assertContains(response,
                            "This project has been closed too long ago.")

        response = send(cost.urls["update"],
                        rendered_on=in_days(10).isoformat())
        self.assertContains(response, "That&#x27;s too far in the future.")
示例#15
0
    def old_projects(self):
        from workbench.logbook.models import LoggedHours

        return (self.open().filter(id__in=LoggedHours.objects.order_by(
        ).values("service__project")).exclude(
            id__in=LoggedHours.objects.order_by().filter(
                rendered_on__gte=in_days(-60)).values("service__project")))
示例#16
0
    def test_list_pdfs(self):
        """Various checks when exporting PDFs of lists"""
        user = factories.UserFactory.create()
        self.client.force_login(user)

        response = self.client.get("/invoices/?export=pdf")
        self.assertEqual(response.status_code, 302)
        self.assertEqual(messages(response), ["No invoices found."])

        factories.InvoiceFactory.create(
            invoiced_on=in_days(-60),
            due_on=in_days(-45),
            status=Invoice.SENT,
        )
        response = self.client.get("/invoices/?export=pdf")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/pdf")
示例#17
0
    def test_decision_expected_on_status(self):
        """The status of a deal depends on the "decision expected on" field"""
        today = dt.date.today()
        deal = Deal(decision_expected_on=today)
        self.assertEqual(
            deal.pretty_status,
            "Decision expected on {}".format(local_date_format(today)),
        )
        self.assertIn("badge-info", deal.status_badge)

        deal = Deal(decision_expected_on=in_days(-1))

        self.assertEqual(
            deal.pretty_status,
            "Decision expected on {}".format(local_date_format(in_days(-1))),
        )
        self.assertIn("badge-warning", deal.status_badge)
示例#18
0
    def test_with_mocked_remote_data(self, mock_get):
        """Exchange rates determination"""
        self.assertEqual(mock_get.call_count, 0)
        rates = ExchangeRates.objects.newest()
        self.assertEqual(mock_get.call_count, 1)
        self.assertEqual(rates.rates["date"], "2019-12-10")

        rates = ExchangeRates.objects.create(day=in_days(1), rates={})
        self.assertEqual(ExchangeRates.objects.newest(), rates)
        self.assertEqual(mock_get.call_count, 1)
示例#19
0
    def test_break_warning(self):
        """Various places show a take-a-break warning if too much work and no break"""
        service = factories.ServiceFactory.create()
        user = service.project.owned_by
        self.client.force_login(user)

        factories.LoggedHoursFactory.create(rendered_by=user, hours=5)
        Break.objects.create(
            user=user,
            starts_at=c(dt.date.today(), dt.time(12, 0)),
            ends_at=c(dt.date.today(), dt.time(12, 5)),
        )

        data = {
            "modal-rendered_by": user.id,
            "modal-rendered_on": dt.date.today().isoformat(),
            "modal-service": service.id,
            "modal-hours": "2.0",
            "modal-description": "Test",
        }

        with override_settings(FEATURES={"skip_breaks": False}):
            response = self.client.post(
                service.project.urls["createhours"],
                data,
                HTTP_X_REQUESTED_WITH="XMLHttpRequest",
            )
            self.assertContains(response, "You should take")

            self.assertIsNotNone(user.take_a_break_warning(add=10))
            self.assertIsNotNone(user.take_a_break_warning(add=3))
            self.assertIsNotNone(user.take_a_break_warning(add=1))
            self.assertIsNone(user.take_a_break_warning(add=0))

            self.assertIsNone(user.take_a_break_warning(add=1, day=in_days(-1)))

            response = self.client.post(
                service.project.urls["createhours"],
                {**data, WarningsForm.ignore_warnings_id: "take-a-break"},
                HTTP_X_REQUESTED_WITH="XMLHttpRequest",
            )
            self.assertEqual(response.status_code, 201)

        # With skip_breaks=True, everything just works
        response = self.client.post(
            service.project.urls["createhours"],
            {**data, "modal-hours": "3.0"},  # No duplicate
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 201)

        # Now the message also appears by default
        with override_settings(FEATURES={"skip_breaks": False}):
            response = self.client.get("/")
            self.assertContains(response, "You should take")
示例#20
0
 def test_status(self):
     """Test various results of the status badge"""
     today = dt.date.today()
     yesterday = in_days(-1)
     fmt = local_date_format(today)
     self.assertEqual(
         Invoice(status=Invoice.IN_PREPARATION).pretty_status,
         "In preparation since {}".format(fmt),
     )
     self.assertEqual(
         Invoice(status=Invoice.SENT, invoiced_on=today).pretty_status,
         "Sent on {}".format(fmt),
     )
     self.assertEqual(
         Invoice(
             status=Invoice.SENT,
             invoiced_on=yesterday,
             due_on=in_days(-5),
         ).pretty_status,
         "Sent on {} but overdue".format(local_date_format(yesterday)),
     )
     self.assertIn(
         "badge-warning",
         Invoice(
             status=Invoice.SENT,
             invoiced_on=yesterday,
             due_on=in_days(-5),
         ).status_badge,
     )
     self.assertEqual(
         Invoice(status=Invoice.SENT,
                 invoiced_on=yesterday,
                 last_reminded_on=today).pretty_status,
         "Sent on {}, reminded on {}".format(local_date_format(yesterday),
                                             fmt),
     )
     self.assertEqual(
         Invoice(status=Invoice.PAID, closed_on=today).pretty_status,
         "Paid on {}".format(fmt),
     )
     self.assertEqual(
         Invoice(status=Invoice.CANCELED).pretty_status, "Canceled")
示例#21
0
 def queryset(self):
     data = self.cleaned_data
     if data.get("closed_during_the_last_year"):
         queryset = Project.objects.closed().filter(closed_on__gte=in_days(-366))
     else:
         queryset = Project.objects.open(on=self.cleaned_data.get("cutoff_date"))
     if data.get("internal"):
         queryset = queryset.filter(type=Project.INTERNAL)
     else:
         queryset = queryset.exclude(type=Project.INTERNAL)
     queryset = self.apply_owned_by(queryset)
     return queryset.select_related("owned_by")
示例#22
0
    def test_deal_group(self):
        """Deals are grouped according to their probability and how far off the
        decision is expected to come"""
        def idx(**kwargs):
            return deal_group(Deal(**kwargs))[0]

        self.assertEqual(idx(), 5)
        self.assertEqual(idx(probability=Deal.NORMAL), 4)
        self.assertEqual(idx(probability=Deal.HIGH), 3)
        self.assertEqual(
            idx(probability=Deal.HIGH, decision_expected_on=in_days(90)),
            3,
        )
        self.assertEqual(
            idx(probability=Deal.HIGH, decision_expected_on=in_days(30)),
            2,
        )
        self.assertEqual(
            idx(probability=Deal.HIGH, decision_expected_on=in_days(20)),
            1,
        )
示例#23
0
    def test_open_items(self):
        """The open items list offers filtering by cutoff date and XLSX exports"""
        invoice = factories.InvoiceFactory.create(
            invoiced_on=in_days(0),
            due_on=in_days(15),
            subtotal=50,
            status=factories.Invoice.SENT,
            third_party_costs=5,  # key data branch
        )
        for i in range(5):
            factories.InvoiceFactory.create(
                customer=invoice.customer,
                contact=invoice.contact,
                owned_by=invoice.owned_by,
                invoiced_on=in_days(0),
                due_on=in_days(15),
                subtotal=50,
                status=factories.Invoice.SENT,
                third_party_costs=5,  # key data branch
            )

        self.client.force_login(factories.UserFactory.create())
        response = self.client.get("/report/open-items-list/?cutoff_date=bla")
        self.assertRedirects(response, "/report/open-items-list/")
        self.assertEqual(messages(response), ["Form was invalid."])
        response = self.client.get("/report/open-items-list/")
        self.assertContains(response, '<th class="text-right">300.00</th>')

        response = self.client.get(
            "/report/open-items-list/?cutoff_date={}".format(in_days(-1).isoformat())
        )
        self.assertContains(response, '<th class="text-right">0.00</th>')

        self.assertEqual(
            self.client.get("/report/open-items-list/?export=xlsx").status_code, 200
        )
        # print(response, response.content.decode("utf-8"))

        # Hit the key data view to cover some branches and verify that it does not crash
        self.assertEqual(self.client.get("/report/key-data/").status_code, 200)
示例#24
0
    def test_deleted_project_changes(self):
        """Deleted projects and work still appears in the view"""

        self.set_current_user()

        pw = factories.PlannedWorkFactory.create(weeks=[in_days(0)])
        pw.project.delete()

        c = updates.changes(since=timezone.now() - dt.timedelta(days=1))

        from pprint import pprint

        pprint(c)
示例#25
0
    def active_projects(self):
        from workbench.projects.models import Project

        return Project.objects.filter(
            Q(
                closed_on__isnull=True,
                id__in=self.loggedhours.filter(rendered_on__gte=in_days(-7)).
                values("service__project").annotate(Count("id")).filter(
                    id__count__gte=3).values("service__project"),
            )
            | Q(id__in=self.loggedhours.filter(rendered_on__gte=dt.date.today(
            )).values("service__project"))).select_related(
                "customer", "contact__organization", "owned_by")
示例#26
0
    def test_send_invoice_with_past_invoice_date(self):
        """Advancing the status from in preparation with a past invoice date
        emits a warning"""
        invoice = factories.InvoiceFactory.create(
            title="Test",
            subtotal=20,
            invoiced_on=in_days(-7),
            due_on=dt.date.today(),
            status=Invoice.IN_PREPARATION,
        )
        self.client.force_login(invoice.owned_by)

        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(invoice, status=Invoice.SENT),
        )
        self.assertContains(response, "with an invoice date in the past")
示例#27
0
    def test_change_paid_invoice(self):
        """Changing paid invoices is possible too"""
        invoice = factories.InvoiceFactory.create(
            title="Test",
            subtotal=20,
            invoiced_on=in_days(-1),
            due_on=dt.date.today(),
            closed_on=dt.date.today(),
            status=Invoice.PAID,
            postal_address="Test\nStreet\nCity",
        )
        self.client.force_login(invoice.owned_by)

        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(invoice, status=Invoice.IN_PREPARATION),
        )
        self.assertContains(
            response,
            "Moving status from &#x27;Paid&#x27; to &#x27;In preparation&#x27;."
            " Are you sure?",
        )
        self.assertContains(
            response,
            "You are attempting to set status to &#x27;In preparation&#x27;,"
            " but the invoice has already been closed on {}."
            " Are you sure?".format(local_date_format(dt.date.today())),
        )

        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(
                invoice,
                status=Invoice.IN_PREPARATION,
                **{
                    WarningsForm.ignore_warnings_id:
                    ("status-unexpected status-change-but-already-closed")
                },
            ),
        )
        # print(response, response.content.decode("utf-8"))
        self.assertRedirects(response, invoice.urls["detail"])
        invoice.refresh_from_db()
        self.assertEqual(invoice.status, Invoice.IN_PREPARATION)
        self.assertIsNone(invoice.closed_on)
示例#28
0
    def test_move_to_past_week_forbidden(self):
        """Moving hours into the past week is not allowed"""
        hours = factories.LoggedHoursFactory.create()
        self.client.force_login(hours.rendered_by)

        response = self.client.post(
            hours.urls["update"],
            {
                "modal-rendered_by": hours.rendered_by_id,
                "modal-rendered_on": in_days(-7).isoformat(),
                "modal-service": hours.service_id,
                "modal-hours": "0.1",
                "modal-description": "Test",
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertContains(response,
                            "Hours have to be logged in the same week.")
示例#29
0
    def clean(self):
        data = super().clean()
        errors = {}
        if data.get("day"):
            if data["day"] < logbook_lock() - dt.timedelta(days=7):
                errors["day"] = _("Breaks have to be logged promptly.")
            elif data["day"] > in_days(7):
                errors["day"] = _("That's too far in the future.")

        raise_if_errors(errors)

        if all(data.get(f) for f in ("day", "starts_at", "ends_at")):
            data["starts_at"] = timezone.make_aware(
                dt.datetime.combine(data["day"], data["starts_at"]))
            data["ends_at"] = timezone.make_aware(
                dt.datetime.combine(data["day"], data["ends_at"]))

        return data
示例#30
0
 def test_update_old_disabled_fields(self):
     """Some fields are disabled when updating locked hours"""
     hours = factories.LoggedHoursFactory.create(rendered_on=in_days(-10))
     self.client.force_login(hours.rendered_by)
     response = self.client.get(hours.urls["update"],
                                HTTP_X_REQUESTED_WITH="XMLHttpRequest")
     self.assertContains(
         response,
         '<input type="number" name="modal-hours" value="1.0" step="0.1"'
         ' class="form-control" required disabled id="id_modal-hours">',
         html=True,
     )
     self.assertContains(
         response,
         '<input type="date" name="modal-rendered_on" value="{}"'
         ' class="form-control" required disabled id="id_modal-rendered_on">'
         "".format(hours.rendered_on.isoformat()),
         html=True,
     )