Exemplo n.º 1
0
    def test_account_statement_upload(self):
        """Uploading account statements with and without duplicates"""
        self.client.force_login(factories.UserFactory.create())
        ledger = factories.LedgerFactory.create()

        def send(data={}):
            with open(
                    os.path.join(settings.BASE_DIR, "workbench", "test",
                                 "account-statement.csv")) as f:
                return self.client.post(
                    "/credit-control/upload/",
                    {
                        "statement": f,
                        "ledger": ledger.pk,
                        **data
                    },
                )

        response = send({"ledger": -1})
        self.assertContains(response, "Select a valid choice.")

        response = send()
        self.assertContains(response, "no-known-payments")

        response = send({WarningsForm.ignore_warnings_id: "no-known-payments"})

        # print(response, response.content.decode("utf-8"))
        self.assertRedirects(response, "/credit-control/")
        self.assertEqual(messages(response), ["Created 2 credit entries."])

        response = send()

        self.assertRedirects(response, "/credit-control/")
        self.assertEqual(messages(response), ["Created 0 credit entries."])

        invoice = factories.InvoiceFactory.create(subtotal=Decimal("4000"),
                                                  _code="00001")
        self.assertAlmostEqual(invoice.total, Decimal("4308.00"))
        response = self.client.get("/credit-control/assign/")
        self.assertContains(response, "<strong><small>00001</small>")

        entry = CreditEntry.objects.get(reference_number="xxxx03130CF54579")
        response = self.client.post(
            entry.urls["update"],
            {
                "ledger": entry.ledger_id,
                "reference_number": entry.reference_number,
                "value_date": entry.value_date,
                "total": entry.total,
                "payment_notice": entry.payment_notice,
                "invoice": invoice.id,
                "notes": "",
            },
        )
        self.assertRedirects(response, entry.urls["detail"])

        invoice.refresh_from_db()
        self.assertEqual(invoice.status, invoice.PAID)
        self.assertEqual(invoice.closed_on, entry.value_date)
Exemplo n.º 2
0
    def test_form(self):
        """Assigning a note to a content object works as expected"""
        deal = factories.DealFactory.create()
        self.client.force_login(deal.owned_by)

        response = self.client.get(deal.urls["detail"])

        content_type = ContentType.objects.get_for_model(deal)

        self.assertContains(response, f'value="{content_type.pk}"')
        self.assertContains(response, f'value="{deal.pk}"')

        response = self.client.post(
            "/notes/add-note/",
            {
                "content_type": content_type.pk,
                "object_id": deal.pk,
                "title": "Test title",
                "description": "Test description",
                "next": deal.urls["detail"],
            },
        )
        self.assertRedirects(response, deal.urls["detail"])
        self.assertEqual(messages(response), [])

        response = self.client.post(
            "/notes/add-note/",
            {
                "content_type": content_type.pk,
                "object_id": deal.pk + 1,
                "title": "Test title",
                "description": "Test description",
                "next": deal.urls["detail"],
            },
        )
        self.assertRedirects(response, deal.urls["detail"])
        self.assertEqual(
            messages(response),
            ["Unable to determine the object this note should be added to."],
        )

        # print(response, response.content.decode("utf-8"))

        note = Note.objects.get()
        response = self.client.post(
            note.urls["update"],
            {"title": "Updated", "description": note.description},
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 202)

        note.refresh_from_db()
        self.assertEqual(note.title, "Updated")
Exemplo n.º 3
0
    def test_warning(self):
        """Changing the organization of a person with projects produces a warning"""
        person = factories.PersonFactory.create(
            organization=factories.OrganizationFactory.create(), salutation="Dear John"
        )
        factories.ProjectFactory.create(customer=person.organization, contact=person)

        self.client.force_login(person.primary_contact)
        response = self.client.post(
            person.urls["update"], person_to_dict(person, organization="")
        )
        # print(response, response.content.decode("utf-8"))
        self.assertContains(
            response,
            "This person is the contact of the following related objects:"
            " 1 project.",
        )

        factories.Project.objects.all().delete()
        response = self.client.post(
            person.urls["update"], person_to_dict(person, organization="")
        )
        self.assertRedirects(response, person.urls["detail"])
        self.assertEqual(
            messages(response),
            ["Person 'Vorname Nachname' has been updated successfully."],
        )
Exemplo n.º 4
0
 def test_invalid_search_form(self):
     """Invalid filter form redirect test"""
     user = factories.UserFactory.create()
     self.client.force_login(user)
     response = self.client.get(Project.urls["list"] + "?org=0")
     self.assertRedirects(response, Project.urls["list"] + "?error=1")
     self.assertEqual(messages(response), ["Search form was invalid."])
Exemplo n.º 5
0
    def test_feature_required(self):
        """The feature_required decorator redirects users and adds a message"""
        user = factories.UserFactory.create()
        self.client.force_login(user)

        response = self.client.get(reverse("report_hours_by_circle"))
        self.assertRedirects(response, "/")
        self.assertEqual(messages(response), ["Feature not available"])
Exemplo n.º 6
0
 def test_no_expenses(self):
     """Expense report creation without expenses fails early"""
     self.client.force_login(factories.UserFactory.create())
     response = self.client.get("/expenses/create/")
     self.assertRedirects(response, "/expenses/")
     self.assertEqual(
         messages(response), ["Could not find any expenses to reimburse."]
     )
Exemplo n.º 7
0
    def test_server_flow_user_data_failure(self):
        """Failing to fetch user data shouldn't produce an internal server error"""
        FakeFlow.EMAIL = "*****@*****.**"
        FakeFlow.RAISE_EXCEPTION = True

        response = self.client.get("/accounts/oauth2/?code=x")
        self.assertRedirects(response, "/accounts/login/")
        self.assertEqual(
            messages(response),
            ["Fehler während Abholen der Daten. Bitte nochmals versuchen."],
        )
Exemplo n.º 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))
Exemplo n.º 9
0
 def test_create_message(self):
     """Attempting to directly create an offer only produces a help message"""
     self.client.force_login(factories.UserFactory.create())
     response = self.client.get(Offer.urls["create"])
     self.assertRedirects(response, Offer.urls["list"])
     self.assertEqual(
         messages(response),
         [
             "Offers can only be created from projects. Go to the project"
             " and add services first, then you'll be able to create the offer"
             " itself."
         ],
     )
Exemplo n.º 10
0
    def test_logged_hours_deletion(self):
        """Deleting hours from past weeks or locked hours is not allowed"""
        hours = factories.LoggedHoursFactory.create(rendered_on=in_days(-10))
        self.client.force_login(hours.rendered_by)
        response = self.client.post(hours.urls["delete"],
                                    HTTP_X_REQUESTED_WITH="XMLHttpRequest")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(messages(response),
                         ["Cannot delete logged hours from past weeks."])

        hours.archived_at = timezone.now()
        hours.save()

        response = self.client.post(hours.urls["delete"],
                                    HTTP_X_REQUESTED_WITH="XMLHttpRequest")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(messages(response),
                         ["Cannot delete archived logged hours."])

        hours = factories.LoggedHoursFactory.create()
        response = self.client.post(hours.urls["delete"],
                                    HTTP_X_REQUESTED_WITH="XMLHttpRequest")
        self.assertEqual(response.status_code, 204)
Exemplo n.º 11
0
    def test_logged_cost_deletion(self):
        """Deleting archived logged costs is not allowed"""
        costs = factories.LoggedCostFactory.create(archived_at=timezone.now())
        self.client.force_login(costs.created_by)

        response = self.client.post(costs.urls["delete"],
                                    HTTP_X_REQUESTED_WITH="XMLHttpRequest")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(messages(response),
                         ["Cannot delete archived logged cost entries."])

        costs = factories.LoggedCostFactory.create()
        response = self.client.post(costs.urls["delete"],
                                    HTTP_X_REQUESTED_WITH="XMLHttpRequest")
        self.assertEqual(response.status_code, 204)
Exemplo n.º 12
0
    def test_report_view(self):
        """The export view produces individual PDFs or ZIP files containing PDFs"""
        year = factories.YearFactory.create()
        user = factories.UserFactory.create(
            _full_name="Fritz", working_time_model=year.working_time_model
        )
        inactive = factories.UserFactory.create(
            working_time_model=year.working_time_model
        )
        Employment.objects.create(user=user, percentage=50, vacation_weeks=5)

        # New user has a different working time model...
        self.client.force_login(factories.UserFactory.create(_full_name="Hans"))

        url = "/report/annual-working-time/"
        response = self.client.get(url)
        self.assertNotContains(response, str(user))
        self.assertNotContains(response, str(inactive))

        response = self.client.get(url + "?user=active")
        self.assertContains(response, str(user))
        self.assertNotContains(response, str(inactive))

        response = self.client.get(url + "?user="******"?year=2018")
        self.assertEqual(
            messages(response),
            [
                "No annual working time defined for user Hans"
                " with working time model Test."
            ],
        )

        other = factories.UserFactory.create(working_time_model=year.working_time_model)
        Employment.objects.create(user=other, percentage=50, vacation_weeks=5)
        factories.AbsenceFactory.create(user=other)

        response = self.client.get(url + "?export=pdf&user=active")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/zip")

        response = self.client.get(url + f"?export=pdf&user={user.pk}")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/pdf")
Exemplo n.º 13
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")
Exemplo n.º 14
0
    def test_too_many_invoices(self):
        """Creating a PDF with too many invoices fails"""
        invoice = factories.InvoiceFactory.create()

        for i in range(5):
            factories.InvoiceFactory.create(
                customer=invoice.customer,
                contact=invoice.contact,
                owned_by=invoice.owned_by,
            )

        self.client.force_login(invoice.owned_by)
        response = self.client.get("/invoices/?export=pdf")
        self.assertRedirects(response,
                             "/invoices/?error=1",
                             fetch_redirect_response=False)
        self.assertEqual(messages(response),
                         ["6 invoices in selection, that's too many."])
Exemplo n.º 15
0
    def test_deletion(self):
        """Related objects are checked automatically when attempting deletion"""
        hours = factories.LoggedHoursFactory.create(description="Bla")
        project = hours.service.project

        with self.assertRaises(ProtectedError):
            project.delete()

        self.client.force_login(hours.rendered_by)
        response = self.client.get(project.urls["delete"])
        self.assertRedirects(response, project.urls["detail"])
        self.assertEqual(
            messages(response),
            [
                "Cannot delete '{}' because of related objects (Any service: Bla)."
                "".format(project)
            ],
        )
Exemplo n.º 16
0
    def test_assignment(self):
        """Batch assignment of credit entries to invoices"""
        for i in range(10):
            invoice = factories.InvoiceFactory.create(subtotal=10 + i,
                                                      liable_to_vat=False)

        entry_0 = factories.CreditEntryFactory.create(total=12)
        entry_1 = factories.CreditEntryFactory.create(total=14)
        entry_2 = factories.CreditEntryFactory.create(total=19)

        self.client.force_login(factories.UserFactory.create())
        response = self.client.get("/credit-control/assign/")
        # print(response, response.content.decode("utf-8"))

        self.assertContains(response, "widget--radioselect", 3)
        response = self.client.post(
            "/credit-control/assign/",
            {
                f"entry_{entry_2.pk}_invoice": invoice.pk,
                f"entry_{entry_1.pk}_notes": "Stuff",
            },
        )
        self.assertRedirects(response, "/credit-control/assign/")

        response = self.client.get("/credit-control/assign/")
        self.assertContains(response, "widget--radioselect", 1)

        response = self.client.post(
            "/credit-control/assign/",
            {f"entry_{entry_0.pk}_notes": "Stuff"},
            follow=True,
        )
        self.assertRedirects(response, "/credit-control/")
        self.assertEqual(
            messages(response),
            [
                "credit entries have been updated successfully.",
                "All credit entries have already been assigned.",
            ],
        )
Exemplo n.º 17
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)
Exemplo n.º 18
0
    def test_offers_pdf(self):
        """Generate a PDF containing several offers"""
        project = factories.ProjectFactory.create()
        self.client.force_login(project.owned_by)

        response = self.client.get(project.urls["offers_pdf"])
        self.assertRedirects(response, project.get_absolute_url())
        self.assertEqual(messages(response), ["No offers in project."])

        offer = factories.OfferFactory.create(project=project)

        response = self.client.get(project.urls["offers_pdf"])
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/pdf")

        offer.offered_on = dt.date.today()
        offer.valid_until = in_days(60)
        offer.save()
        project.description = "Test"
        project.save()

        response = self.client.get(project.urls["offers_pdf"])
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/pdf")
Exemplo n.º 19
0
    def test_expenses(self):
        """Expense logging and expense report creation"""
        project = factories.ProjectFactory.create()
        self.client.force_login(project.owned_by)

        service = factories.ServiceFactory.create(project=project)

        response = self.client.post(
            project.urls["createcost"],
            {
                "modal-service": service.id,
                "modal-rendered_by": project.owned_by_id,
                "modal-rendered_on": local_date_format(dt.date.today()),
                "modal-cost": "10",
                "modal-description": "Anything",
                "modal-are_expenses": "on",
                # "modal-third_party_costs": "9",
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertContains(
            response, "Providing third party costs is necessary for expenses."
        )

        response = self.client.post(
            project.urls["createcost"],
            {
                "modal-service": service.id,
                "modal-rendered_by": project.owned_by_id,
                "modal-rendered_on": dt.date.today().isoformat(),
                "modal-cost": "10",
                "modal-description": "Anything",
                "modal-are_expenses": "on",
                "modal-third_party_costs": "9",
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 201)

        cost1 = LoggedCost.objects.get()
        self.assertEqual(
            list(LoggedCost.objects.expenses(user=project.owned_by)), [cost1]
        )

        # Another users' expenses
        factories.LoggedCostFactory.create(are_expenses=True, third_party_costs=5)

        response = self.client.get("/expenses/create/")
        # Only one expense
        self.assertContains(response, "id_expenses_0")
        self.assertNotContains(response, "id_expenses_1")

        response = self.client.post("/expenses/create/", {"expenses": [cost1.pk]})
        report = ExpenseReport.objects.get()
        self.assertRedirects(response, report.urls["detail"])
        self.assertEqual(report.total, 9)

        # Some fields are now disabled
        response = self.client.get(
            cost1.urls["update"], HTTP_X_REQUESTED_WITH="XMLHttpRequest"
        )
        self.assertContains(
            response,
            '<input type="number" name="modal-third_party_costs" value="9.00" step="0.01" class="form-control" disabled id="id_modal-third_party_costs">',  # noqa
            html=True,
        )

        response = self.client.get(
            cost1.urls["delete"], HTTP_X_REQUESTED_WITH="XMLHttpRequest"
        )
        self.assertContains(
            response, "Expenses are part of an expense report, cannot delete entry."
        )

        # More expenses of same user
        cost2 = factories.LoggedCostFactory.create(
            rendered_by=project.owned_by, are_expenses=True, third_party_costs=5
        )
        response = self.client.post(report.urls["update"], {"expenses": [cost2.pk]})
        self.assertRedirects(response, report.urls["detail"])
        report.refresh_from_db()
        self.assertEqual(report.total, 5)

        response = self.client.get(report.urls["pdf"])
        self.assertRedirects(response, report.urls["detail"])
        self.assertEqual(
            messages(response),
            [
                "Please close the expense report first. Generating PDFs"
                " for open expense reports isn't allowed."
            ],
        )

        response = self.client.post(report.urls["delete"])
        self.assertRedirects(response, "/expenses/")

        self.assertEqual(ExpenseReport.objects.count(), 0)

        response = self.client.post(
            "/expenses/create/", {"expenses": [cost1.pk], "is_closed": "on"}
        )
        report = ExpenseReport.objects.get()
        self.assertRedirects(response, report.urls["detail"])
        self.assertEqual(report.total, 9)

        response = self.client.post(report.urls["update"])
        self.assertRedirects(response, report.urls["detail"])
        self.assertEqual(messages(response), ["Cannot update a closed expense report."])

        response = self.client.post(report.urls["delete"])
        self.assertRedirects(response, report.urls["detail"])
        self.assertEqual(messages(response), ["Cannot delete a closed expense report."])
        # print(response, response.content.decode("utf-8"))

        self.assertEqual(self.client.get(report.urls["pdf"]).status_code, 200)
Exemplo n.º 20
0
    def test_delete_service_invoice_with_logs(self):
        """Deleting service invoices with related logbook entries unarchives
        those entries"""
        service = factories.ServiceFactory.create()
        cost = factories.LoggedCostFactory.create(cost=150,
                                                  service=service,
                                                  description="this")

        url = service.project.urls[
            "createinvoice"] + "?type=services&source=logbook"
        self.client.force_login(service.project.owned_by)
        response = self.client.post(
            url,
            {
                "contact": service.project.contact_id,
                "title": service.project.title,
                "owned_by": service.project.owned_by_id,
                "discount": "0",
                "liable_to_vat": "1",
                "postal_address": "Anything\nStreet\nCity",
                "selected_services": [service.pk],
                "disable_logging": 0,
            },
        )

        invoice = Invoice.objects.get()
        self.assertRedirects(response, invoice.urls["detail"])
        self.assertAlmostEqual(invoice.subtotal, Decimal(150))

        cost.refresh_from_db()
        self.assertEqual(cost.invoice_service.invoice, invoice)
        self.assertEqual(cost.invoice_service.project_service, service)

        response = self.client.get(invoice.urls["delete"])
        self.assertContains(response, WarningsForm.ignore_warnings_id)

        response = self.client.post(invoice.urls["delete"])
        self.assertContains(response,
                            "Logged services are linked with this invoice.")
        self.assertEqual(Invoice.objects.count(), 1)
        cost.refresh_from_db()
        self.assertTrue(cost.invoice_service)

        response = self.client.post(
            invoice.urls["delete"],
            {WarningsForm.ignore_warnings_id: "release-logged-services"},
        )
        self.assertRedirects(response, invoice.urls["list"])
        self.assertEqual(
            messages(response),
            [f"Invoice '{invoice}' has been deleted successfully."],
        )

        cost.refresh_from_db()
        self.assertEqual(cost.invoice_service, None)

        # Creating the invoice again succeeds.
        response = self.client.post(
            url,
            {
                "contact": service.project.contact_id,
                "title": service.project.title,
                "owned_by": service.project.owned_by_id,
                "discount": "0",
                "liable_to_vat": "1",
                "postal_address": "Anything\nStreet\nCity",
                "selected_services": [service.pk],
                "disable_logging": 0,
            },
        )
        invoice = Invoice.objects.get()
        self.assertRedirects(response, invoice.urls["detail"])
        self.assertAlmostEqual(invoice.subtotal, Decimal(150))
Exemplo n.º 21
0
    def test_update_invoice(self):
        """Updating invoices produces a variety of errors and warnings"""
        invoice = factories.InvoiceFactory.create(
            contact=None, postal_address="Test\nStreet\nCity")
        self.client.force_login(invoice.owned_by)
        response = self.client.post(invoice.urls["update"],
                                    invoice_to_dict(invoice))
        self.assertContains(response, "No contact selected.")

        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(invoice,
                            **{WarningsForm.ignore_warnings_id: "no-contact"}),
        )
        self.assertRedirects(response, invoice.urls["detail"])

        response = self.client.get(invoice.urls["delete"])
        self.assertEqual(response.status_code, 200)

        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(invoice, status=Invoice.SENT))
        self.assertContains(
            response, "Invoice and/or due date missing for selected state.")

        person = factories.PersonFactory.create(organization=invoice.customer)
        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(
                invoice,
                contact=person.id,
                status=Invoice.SENT,
                invoiced_on=dt.date.today().isoformat(),
                due_on=dt.date.today().isoformat(),
            ),
        )
        self.assertRedirects(response, invoice.urls["detail"])

        invoice.refresh_from_db()
        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(invoice, closed_on=dt.date.today().isoformat()),
        )
        self.assertContains(response,
                            "Invalid status when closed on is already set.")

        response = self.client.get(invoice.urls["delete"])
        self.assertRedirects(response, invoice.urls["detail"])
        self.assertEqual(
            messages(response),
            ["Invoices in preparation may be deleted, others not."])

        invoice.refresh_from_db()
        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(invoice,
                            postal_address=invoice.postal_address + " hello"),
        )
        # print(response, response.content.decode("utf-8"))
        self.assertContains(
            response,
            "You are attempting to change &#x27;Postal address&#x27;."
            " I am trying to prevent unintentional changes. Are you sure?",
        )

        response = self.client.post(
            invoice.urls["update"],
            invoice_to_dict(invoice, status=Invoice.PAID))
        self.assertRedirects(response, invoice.urls["detail"])

        invoice.refresh_from_db()
        self.assertEqual(invoice.closed_on, dt.date.today())
Exemplo n.º 22
0
    def test_update_offer(self):
        """Offer and bound services update warnings and errors"""
        offer = factories.OfferFactory.create(title="Test")
        service = factories.ServiceFactory.create(
            project=offer.project,
            effort_type="Programming",
            effort_rate=200,
            effort_hours=10,
        )
        self.client.force_login(offer.owned_by)

        response = self.client.get(service.urls["update"])
        self.assertRedirects(response, service.get_absolute_url())

        response = self.client.get(offer.urls["delete"])
        self.assertEqual(response.status_code, 200)

        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",
                "services": [service.id],
                # "offered_on": dt.date.today().isoformat(),
                "status": Offer.ACCEPTED,
            },
        )
        self.assertContains(response,
                            "Offered on date missing for selected state.")
        self.assertContains(response,
                            "Valid until date missing for selected state.")

        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",
                "services": [service.id],
                "offered_on": dt.date.today().isoformat(),
                "valid_until": in_days(60).isoformat(),
                "status": Offer.ACCEPTED,
            },
        )
        self.assertRedirects(response, offer.get_absolute_url())

        response = self.client.get(offer.urls["update"])
        self.assertContains(
            response,
            "This offer is not in preparation anymore. I assume you know what you",
        )

        offer.refresh_from_db()
        self.assertEqual(offer.closed_on, dt.date.today())
        self.assertAlmostEqual(offer.subtotal, Decimal("2000"))

        response = self.client.get(service.urls["detail"])
        self.assertRedirects(
            response, "{}#service{}".format(offer.project.urls["detail"],
                                            service.pk))

        response = self.client.get(service.urls["update"],
                                   HTTP_X_REQUESTED_WITH="XMLHttpRequest")
        self.assertContains(
            response,
            "Most fields are disabled because service is bound"
            " to an offer which is not in preparation anymore.",
        )

        self.assertTrue(service.allow_logging)
        response = self.client.post(
            service.urls["update"],
            {
                "allow_logging": False,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 202)
        service.refresh_from_db()
        self.assertFalse(service.allow_logging)
        self.assertEqual(
            messages(response),
            ["Service 'Any service' has been updated successfully."])
        self.assertEqual(service.offer, offer)

        response = self.client.get(service.urls["delete"],
                                   HTTP_X_REQUESTED_WITH="XMLHttpRequest")
        self.assertContains(
            response,
            "Cannot delete a service bound to an offer"
            " which is not in preparation anymore.",
        )

        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",
                "services": [service.id],
                "offered_on": dt.date.today().isoformat(),
                "status": Offer.IN_PREPARATION,
            },
        )
        self.assertContains(response, "but 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",
                "services": [service.id],
                "offered_on":
                dt.date.today().isoformat(),
                "status":
                Offer.IN_PREPARATION,
                WarningsForm.ignore_warnings_id:
                "status-change-but-already-closed",
            },
        )
        self.assertRedirects(response, offer.get_absolute_url())

        offer.refresh_from_db()
        self.assertIsNone(offer.closed_on)
Exemplo n.º 23
0
    def test_creation(self):
        """Creation of recurring invoices, with the customer/contaact pre-form"""
        person = factories.PersonFactory.create(
            organization=factories.OrganizationFactory.create())

        self.client.force_login(person.primary_contact)

        response = self.client.get("/recurring-invoices/create/")
        self.assertNotContains(response, 'name="title"')

        response = self.client.get(
            f"/recurring-invoices/create/?contact={person.pk}")
        self.assertContains(
            response,
            # No value!
            '<input type="number" name="third_party_costs" step="0.01" class="form-control" required id="id_third_party_costs">',  # noqa
            html=True,
        )
        # print(response, response.content.decode("utf-8"))

        response = self.client.post(
            f"/recurring-invoices/create/?contact={person.pk}",
            {
                "customer": person.organization_id,
                # "contact": person.id,
                "title": "recur",
                "owned_by": person.primary_contact_id,
                "starts_on": dt.date.today().isoformat(),
                "periodicity": "yearly",
                "create_invoice_on_day": -20,
                "subtotal": 500,
                "discount": 0,
                "liable_to_vat": "on",
                "third_party_costs": 0,
            },
        )
        self.assertContains(response, "No contact selected.")

        response = self.client.post(
            f"/recurring-invoices/create/?contact={person.pk}",
            {
                "customer": person.organization_id,
                "contact": person.id,
                "title": "recur",
                "owned_by": person.primary_contact_id,
                "starts_on": dt.date.today().isoformat(),
                "periodicity": "yearly",
                "create_invoice_on_day": -20,
                "subtotal": 500,
                "discount": 0,
                "liable_to_vat": "on",
                "third_party_costs": 0,
                "postal_address": "Anything",
            },
        )
        self.assertContains(response, 'value="short-postal-address"')

        response = self.client.post(
            f"/recurring-invoices/create/?contact={person.pk}",
            {
                "customer": person.organization_id,
                "contact": person.id,
                "title": "recur",
                "owned_by": person.primary_contact_id,
                "starts_on": dt.date.today().isoformat(),
                "periodicity": "yearly",
                "create_invoice_on_day": -20,
                "subtotal": 500,
                "discount": 0,
                "liable_to_vat": "on",
                "third_party_costs": 0,
                "postal_address": "Anything\nStreet\nCity",
            },
        )

        ri = RecurringInvoice.objects.get()
        self.assertRedirects(response, ri.urls["detail"])

        factories.PostalAddressFactory.create(person=person)
        response = self.client.get(ri.urls["update"])
        self.assertContains(response, 'name="postal_address"')
        self.assertContains(response, 'data-field-value="')

        response = self.client.get(ri.urls["detail"])
        self.assertContains(response, "?create_invoices=1")

        response = self.client.get(ri.urls["detail"] + "?create_invoices=1")
        self.assertRedirects(response, Invoice.urls["list"])
        self.assertEqual(messages(response), ["Created 1 invoice."])

        response = self.client.get(ri.urls["detail"] + "?create_invoices=1")
        self.assertRedirects(response, ri.urls["detail"])
        self.assertEqual(messages(response), ["Created 0 invoices."])
Exemplo n.º 24
0
    def test_create_service_invoice_from_logbook(self):
        """Service invoice creation from the logbook works and results in the
        expected invoice total"""
        project = factories.ProjectFactory.create()
        service1 = factories.ServiceFactory.create(project=project,
                                                   title="cost-only",
                                                   cost=100)
        service2 = factories.ServiceFactory.create(project=project,
                                                   title="no-rate")
        service3 = factories.ServiceFactory.create(
            project=project,
            title="with-rate",
            effort_type="Consulting",
            effort_rate=200,
        )
        service4 = factories.ServiceFactory.create(project=project,
                                                   title="nothing")

        cost = factories.LoggedCostFactory.create(
            service=service1,
            cost=10,
            description="Test",
            rendered_on=dt.date(2020, 3, 18),
        )
        hours = factories.LoggedHoursFactory.create(
            service=service1,
            hours=1,
            description="Test",
            rendered_on=dt.date(2020, 3, 20),
        )
        factories.LoggedHoursFactory.create(service=service2,
                                            hours=2,
                                            rendered_on=dt.date(2020, 3, 20))
        factories.LoggedHoursFactory.create(service=service3,
                                            hours=3,
                                            rendered_on=dt.date(2020, 3, 22))

        url = project.urls["createinvoice"] + "?type=services&source=logbook"
        self.client.force_login(project.owned_by)
        response = self.client.get(url)
        # print(response, response.content.decode("utf-8"))

        self.assertContains(response, "<strong>cost-only</strong><br>10.00")
        self.assertContains(response,
                            "1.0h logged but no hourly rate defined.")
        self.assertContains(response, "<strong>no-rate</strong><br>0.00")
        self.assertContains(response,
                            "2.0h logged but no hourly rate defined.")
        self.assertContains(response, "<strong>with-rate</strong><br>600.00")
        self.assertContains(response, "id_show_service_details")

        cost.service = service1
        cost.save()

        response = self.client.post(
            url,
            {
                "contact":
                project.contact_id,
                "title":
                project.title,
                "owned_by":
                project.owned_by_id,
                "discount":
                "0",
                "liable_to_vat":
                "1",
                "postal_address":
                "Anything\nStreet\nCity",
                "selected_services": [
                    service1.pk,
                    service2.pk,
                    service3.pk,
                    service4.pk,
                ],
                "disable_logging":
                0,
            },
        )
        invoice = Invoice.objects.get()
        self.assertRedirects(response, invoice.urls["detail"])
        self.assertEqual(invoice.subtotal, 610)
        self.assertEqual(invoice.service_period, "18.03.2020 - 22.03.2020")

        cost.refresh_from_db()
        self.assertEqual(cost.invoice_service.invoice, invoice)
        hours.refresh_from_db()
        self.assertEqual(hours.invoice_service.invoice, invoice)

        self.assertEqual(service1.invoice_services.get().invoice, invoice)
        self.assertEqual(service2.invoice_services.get().invoice, invoice)
        self.assertEqual(service3.invoice_services.get().invoice, invoice)
        self.assertEqual(service4.invoice_services.count(), 0)

        response = self.client.post(
            cost.urls["update"],
            {
                "service": cost.service_id,
                "rendered_on": cost.rendered_on.isoformat(),
                "third_party_costs": cost.third_party_costs or "",
                "cost": 2 * cost.cost,
                "description": cost.description,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response,
                            "This entry is already part of an invoice.")

        response = self.client.post(
            hours.urls["update"],
            {
                "service": hours.service_id,
                "rendered_on": hours.rendered_on.isoformat(),
                "rendered_by": hours.rendered_by_id,
                "hours": hours.hours,
                "description": hours.description,
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response,
                            "This entry is already part of an invoice.")

        response = self.client.post(
            cost.urls["update"],
            {
                "modal-service": cost.service_id,
                "modal-rendered_by": cost.rendered_by_id,
                "modal-rendered_on": cost.rendered_on.isoformat(),
                "modal-third_party_costs": cost.third_party_costs or "",
                "modal-cost": 2 * cost.cost,
                "modal-description": cost.description,
                WarningsForm.ignore_warnings_id: "part-of-invoice",
            },
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 202)

        self.assertContains(
            self.client.get("/"),
            "Logged cost &#x27;Test&#x27; has been updated successfully.",
        )

        cost.refresh_from_db()
        self.assertAlmostEqual(cost.cost, Decimal("20"))
        invoice.refresh_from_db()
        self.assertAlmostEqual(invoice.subtotal, 610)  # unchanged

        response = self.client.get(invoice.urls["pdf"])
        self.assertEqual(response.status_code, 200)  # No crash

        response = self.client.get(invoice.urls["xlsx"])
        self.assertEqual(response.status_code, 200)  # No crash

        response = self.client.post(
            invoice.urls["delete"],
            {WarningsForm.ignore_warnings_id: "release-logged-services"},
        )
        self.assertRedirects(response, invoice.urls["list"])
        self.assertEqual(Invoice.objects.count(), 0)
        self.assertEqual(
            messages(response),
            [f"Invoice '{invoice}' has been deleted successfully."],
        )
Exemplo n.º 25
0
    def test_server_flow(self):
        """Exercise the OAuth2 webserver flow implementation"""
        FakeFlow.EMAIL = "*****@*****.**"
        FakeFlow.RAISE_EXCEPTION = False

        client = Client()
        response = client.get("/accounts/oauth2/")
        self.assertRedirects(
            response, "http://example.com/auth/", fetch_redirect_response=False
        )

        response = client.get("/accounts/oauth2/?code=x", HTTP_ACCEPT_LANGUAGE="en")
        self.assertRedirects(response, "/accounts/login/?error=1")
        self.assertEqual(
            messages(response),
            ["No user with email address [email protected] found."],
        )

        FakeFlow.EMAIL = "user@{}".format(settings.WORKBENCH.SSO_DOMAIN)
        response = client.get("/accounts/oauth2/?code=x", HTTP_ACCEPT_LANGUAGE="en")
        self.assertRedirects(response, "/accounts/update/")
        self.assertEqual(messages(response), ["Welcome! Please fill in your details."])
        response = client.post(
            "/accounts/update/",
            {
                "_full_name": "Test",
                "_short_name": "T",
                "language": "en",
                "working_time_model": factories.WorkingTimeModelFactory.create().pk,
            },
        )
        self.assertEqual(client.cookies.get("login_hint").value, FakeFlow.EMAIL)

        client = Client()
        response = client.get("/accounts/oauth2/?code=x")
        self.assertRedirects(response, "/")
        self.assertEqual(messages(response), [])
        self.assertEqual(client.cookies.get("login_hint").value, FakeFlow.EMAIL)

        # Disabled user
        User.objects.update(is_active=False)
        client = Client()
        FakeFlow.EMAIL = "user@{}".format(settings.WORKBENCH.SSO_DOMAIN)
        response = client.get("/accounts/oauth2/?code=x", HTTP_ACCEPT_LANGUAGE="en")
        self.assertRedirects(response, "/accounts/login/?error=1")
        self.assertEqual(
            messages(response),
            ["The user with email address [email protected] is inactive."],
        )

        client = Client()
        client.cookies.load({"login_hint": FakeFlow.EMAIL})

        FakeFlow.EMAIL = "*****@*****.**"
        response = client.get("/accounts/oauth2/?code=x", HTTP_ACCEPT_LANGUAGE="en")
        self.assertRedirects(response, "/accounts/login/?error=1")
        self.assertEqual(
            messages(response), ["No user with email address [email protected] found."]
        )
        # Login hint cookie has been removed when login fails
        self.assertEqual(client.cookies.get("login_hint").value, "")