From c124b5c89773d3bc32b7a4e564d542fd7bbfeebf Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Tue, 3 Sep 2024 15:09:01 +0530 Subject: [PATCH 01/20] Add Payment Invoice model --- .../migrations/0057_paymentinvoice.py | 29 +++++++++++++++++++ commcare_connect/opportunity/models.py | 10 +++++++ 2 files changed, 39 insertions(+) create mode 100644 commcare_connect/opportunity/migrations/0057_paymentinvoice.py diff --git a/commcare_connect/opportunity/migrations/0057_paymentinvoice.py b/commcare_connect/opportunity/migrations/0057_paymentinvoice.py new file mode 100644 index 00000000..52129f9b --- /dev/null +++ b/commcare_connect/opportunity/migrations/0057_paymentinvoice.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.5 on 2024-09-03 09:38 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("opportunity", "0056_payment_organization"), + ] + + operations = [ + migrations.CreateModel( + name="PaymentInvoice", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("amount", models.PositiveIntegerField()), + ("date", models.DateField()), + ("invoice_number", models.CharField(max_length=50)), + ( + "opportunity", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="opportunity.opportunity"), + ), + ], + options={ + "unique_together": {("opportunity", "invoice_number")}, + }, + ), + ] diff --git a/commcare_connect/opportunity/models.py b/commcare_connect/opportunity/models.py index 907388d6..8f52d0dd 100644 --- a/commcare_connect/opportunity/models.py +++ b/commcare_connect/opportunity/models.py @@ -632,3 +632,13 @@ class CatchmentArea(models.Model): class Meta: unique_together = ("site_code", "opportunity") + + +class PaymentInvoice(models.Model): + opportunity = models.ForeignKey(Opportunity, on_delete=models.CASCADE) + amount = models.PositiveIntegerField() + date = models.DateField() + invoice_number = models.CharField(max_length=50) + + class Meta: + unique_together = ("opportunity", "invoice_number") From 241925cd19119c89980f16433fc4de9243f74371 Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Wed, 4 Sep 2024 15:01:18 +0530 Subject: [PATCH 02/20] Add form and table for PaymentInvoice --- commcare_connect/opportunity/forms.py | 32 ++++++++++++++++++++++++++ commcare_connect/opportunity/tables.py | 8 +++++++ 2 files changed, 40 insertions(+) diff --git a/commcare_connect/opportunity/forms.py b/commcare_connect/opportunity/forms.py index 645fd119..ce4688a2 100644 --- a/commcare_connect/opportunity/forms.py +++ b/commcare_connect/opportunity/forms.py @@ -18,6 +18,7 @@ Opportunity, OpportunityAccess, OpportunityVerificationFlags, + PaymentInvoice, PaymentUnit, VisitValidationStatus, ) @@ -752,3 +753,34 @@ def __init__(self, *args, **kwargs): if self.instance: self.fields["form_submission_start"].initial = self.instance.form_submission_start self.fields["form_submission_end"].initial = self.instance.form_submission_end + + +class PaymentInvoiceForm(forms.ModelForm): + class Meta: + model = PaymentInvoice + fields = ("amount", "date", "invoice_number") + + def __init__(self, *args, **kwargs): + self.opportunity = kwargs.pop("opportunity") + super().__init__(*args, **kwargs) + + self.helper = FormHelper(self) + self.helper.layout = Layout( + Row(Field("amount")), + Row(Field("date")), + Row(Field("invoice_number")), + Submit(name="submit", value="Submit"), + ) + + def clean_invoice_number(self): + invoice_number = self.cleaned_data["invoice_number"] + if PaymentInvoice.objects.filter(opportunity=self.opportunity, invoice_number=invoice_number).exists(): + raise ValidationError(f"Invoice with {invoice_number} number already exists", code="invoice_number_reused") + return invoice_number + + def save(self, commit=True): + instance = super().save(commit=False) + instance.opportunity = self.opportunity + if commit: + instance.save() + return instance diff --git a/commcare_connect/opportunity/tables.py b/commcare_connect/opportunity/tables.py index 8d262833..138e55c8 100644 --- a/commcare_connect/opportunity/tables.py +++ b/commcare_connect/opportunity/tables.py @@ -8,6 +8,7 @@ CompletedWork, OpportunityAccess, Payment, + PaymentInvoice, PaymentUnit, UserInvite, UserInviteStatus, @@ -410,6 +411,13 @@ class Meta: orderable = False +class PaymentInvoiceTable(tables.Table): + class Meta: + model = PaymentInvoice + orderable = False + fields = ("pk", "amount", "date", "invoice_number") + + def popup_html(value, popup_title, popup_direction="top", popup_class="", popup_attributes=""): return format_html( "{}", From 2b6fb20059e16f912b0efa866bd46921c023893e Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Wed, 4 Sep 2024 15:21:09 +0530 Subject: [PATCH 03/20] Add invoice list and create views --- commcare_connect/opportunity/urls.py | 4 +++ commcare_connect/opportunity/views.py | 52 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/commcare_connect/opportunity/urls.py b/commcare_connect/opportunity/urls.py index 81041382..f3273aae 100644 --- a/commcare_connect/opportunity/urls.py +++ b/commcare_connect/opportunity/urls.py @@ -1,5 +1,6 @@ from django.urls import path +from commcare_connect.opportunity import views from commcare_connect.opportunity.views import ( OpportunityCompletedWorkTable, OpportunityCreate, @@ -107,4 +108,7 @@ ), path("/user_visit_review", user_visit_review, name="user_visit_review"), path("/payment_report", payment_report, name="payment_report"), + path("/invoice/", views.invoice_list, name="invoice_list"), + path("/invoice_table", views.PaymentInvoiceTableView.as_view(), name="invoice_table"), + path("/invoice/create/", views.invoice_create, name="invoice_create"), ] diff --git a/commcare_connect/opportunity/views.py b/commcare_connect/opportunity/views.py index 37a8250b..350e76ac 100644 --- a/commcare_connect/opportunity/views.py +++ b/commcare_connect/opportunity/views.py @@ -4,6 +4,7 @@ from functools import reduce from celery.result import AsyncResult +from crispy_forms.utils import render_crispy_form from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin @@ -33,6 +34,7 @@ OpportunityInitForm, OpportunityVerificationFlagsConfigForm, PaymentExportForm, + PaymentInvoiceForm, PaymentUnitForm, SendMessageMobileUsersForm, VisitExportForm, @@ -53,6 +55,7 @@ OpportunityClaimLimit, OpportunityVerificationFlags, Payment, + PaymentInvoice, PaymentUnit, UserVisit, VisitValidationStatus, @@ -62,6 +65,7 @@ DeliverStatusTable, LearnStatusTable, OpportunityPaymentTable, + PaymentInvoiceTable, PaymentReportTable, PaymentUnitTable, SuspendedUsersTable, @@ -1097,3 +1101,51 @@ def payment_report(request, org_slug, pk): total_nm_payment_accrued=total_nm_payment_accrued, ), ) + + +class PaymentInvoiceTableView(OrganizationUserMixin, SingleTableView): + model = PaymentInvoice + paginate_by = 25 + table_class = PaymentInvoiceTable + template_name = "tables/single_table.html" + filter_class = "" + + def get_table_kwargs(self): + kwargs = super().get_table_kwargs() + user_is_program_manager = self.request.org.program_manager and self.request.org_membership.is_admin + if not user_is_program_manager: + kwargs["exclude"] = ("pk",) + return kwargs + + def get_queryset(self): + opportunity_id = self.kwargs["pk"] + opportunity = get_opportunity_or_404(org_slug=self.request.org.slug, pk=opportunity_id) + return PaymentInvoice.objects.filter(opportunity=opportunity) + + +@org_member_required +def invoice_list(request, org_slug, pk): + opportunity = get_opportunity_or_404(pk, org_slug) + if not opportunity.managed: + return redirect("opportunity:detail", org_slug, pk) + user_is_program_manager = request.org.program_manager and request.org_membership.is_admin + form = PaymentInvoiceForm(opportunity=opportunity) + return render( + request, + "opportunity/invoice_list.html", + context=dict(opportunity=opportunity, user_is_program_manager=user_is_program_manager, form=form), + ) + + +@org_member_required +def invoice_create(request, org_slug, pk): + opportunity = get_opportunity_or_404(pk, org_slug) + user_is_program_manager = request.org.program_manager and request.org_membership.is_admin + if not opportunity.managed or user_is_program_manager: + return redirect("opportunity:detail", org_slug, pk) + form = PaymentInvoiceForm(data=request.POST or None, opportunity=opportunity) + if request.POST and form.is_valid(): + form.save() + form = PaymentInvoiceForm(opportunity=opportunity) + return HttpResponse(render_crispy_form(form), headers={"HX-Trigger": "newInvoice"}) + return HttpResponse(render_crispy_form(form)) From 2cafbf1e31fc168f12650d3018f69b71234bf401 Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Wed, 4 Sep 2024 16:02:25 +0530 Subject: [PATCH 04/20] Add template for invoice_list --- commcare_connect/opportunity/forms.py | 2 +- .../templates/opportunity/invoice_list.html | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 commcare_connect/templates/opportunity/invoice_list.html diff --git a/commcare_connect/opportunity/forms.py b/commcare_connect/opportunity/forms.py index ce4688a2..50836120 100644 --- a/commcare_connect/opportunity/forms.py +++ b/commcare_connect/opportunity/forms.py @@ -769,8 +769,8 @@ def __init__(self, *args, **kwargs): Row(Field("amount")), Row(Field("date")), Row(Field("invoice_number")), - Submit(name="submit", value="Submit"), ) + self.helper.form_tag = False def clean_invoice_number(self): invoice_number = self.cleaned_data["invoice_number"] diff --git a/commcare_connect/templates/opportunity/invoice_list.html b/commcare_connect/templates/opportunity/invoice_list.html new file mode 100644 index 00000000..3ce3535f --- /dev/null +++ b/commcare_connect/templates/opportunity/invoice_list.html @@ -0,0 +1,53 @@ +{% extends "opportunity/base.html" %} +{% load django_tables2 %} +{% load static %} +{% load crispy_forms_tags %} + +{% block title %}{{ request.org }} - Invoices{% endblock title %} + +{% block breadcrumbs_inner %} + {{ block.super }} + + +{% endblock %} + +{% block content %} +

Invoices

+ +
+
+ {% include "tables/table_placeholder.html" with num_cols=4 %} +
+{% endblock content %} + +{% block modal %} + +{% endblock modal %} From 7f00b83486f0fc594d64b907590c7a481d19bdfc Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Wed, 4 Sep 2024 17:18:14 +0530 Subject: [PATCH 05/20] Add model changes for invoice to payment --- .../migrations/0058_payment_invoice.py | 20 ++++++++++++++++++ commcare_connect/opportunity/models.py | 21 ++++++++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 commcare_connect/opportunity/migrations/0058_payment_invoice.py diff --git a/commcare_connect/opportunity/migrations/0058_payment_invoice.py b/commcare_connect/opportunity/migrations/0058_payment_invoice.py new file mode 100644 index 00000000..16684ba9 --- /dev/null +++ b/commcare_connect/opportunity/migrations/0058_payment_invoice.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.5 on 2024-09-05 04:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("opportunity", "0057_paymentinvoice"), + ] + + operations = [ + migrations.AddField( + model_name="payment", + name="invoice", + field=models.OneToOneField( + blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to="opportunity.paymentinvoice" + ), + ), + ] diff --git a/commcare_connect/opportunity/models.py b/commcare_connect/opportunity/models.py index 8f52d0dd..4e8d4ec6 100644 --- a/commcare_connect/opportunity/models.py +++ b/commcare_connect/opportunity/models.py @@ -387,6 +387,16 @@ class VisitValidationStatus(models.TextChoices): trial = "trial", gettext("Trial") +class PaymentInvoice(models.Model): + opportunity = models.ForeignKey(Opportunity, on_delete=models.CASCADE) + amount = models.PositiveIntegerField() + date = models.DateField() + invoice_number = models.CharField(max_length=50) + + class Meta: + unique_together = ("opportunity", "invoice_number") + + class Payment(models.Model): amount = models.PositiveIntegerField() date_paid = models.DateTimeField(auto_now_add=True) @@ -403,6 +413,7 @@ class Payment(models.Model): confirmation_date = models.DateTimeField(null=True) # This is used to indicate Payments made to Network Manager organizations organization = models.ForeignKey(Organization, on_delete=models.DO_NOTHING, null=True, blank=True) + invoice = models.OneToOneField(PaymentInvoice, on_delete=models.DO_NOTHING, null=True, blank=True) class CompletedWorkStatus(models.TextChoices): @@ -632,13 +643,3 @@ class CatchmentArea(models.Model): class Meta: unique_together = ("site_code", "opportunity") - - -class PaymentInvoice(models.Model): - opportunity = models.ForeignKey(Opportunity, on_delete=models.CASCADE) - amount = models.PositiveIntegerField() - date = models.DateField() - invoice_number = models.CharField(max_length=50) - - class Meta: - unique_together = ("opportunity", "invoice_number") From 090c1782639fee7bbae9b5193add8ff2c4b9abd3 Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Thu, 5 Sep 2024 10:00:45 +0530 Subject: [PATCH 06/20] Add is_program_manager property to org membership --- commcare_connect/organization/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/commcare_connect/organization/models.py b/commcare_connect/organization/models.py index 0b53eb88..bc013e03 100644 --- a/commcare_connect/organization/models.py +++ b/commcare_connect/organization/models.py @@ -53,6 +53,10 @@ def is_admin(self): def is_viewer(self): return self.role == self.Role.VIEWER + @property + def is_program_manager(self): + return self.organization.program_manager and self.is_admin + class Meta: db_table = "organization_membership" unique_together = ("user", "organization") From ad6fb09602667ab502409be0a0edaab965726b15 Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Thu, 5 Sep 2024 12:20:58 +0530 Subject: [PATCH 07/20] Add fixes for minor issues --- commcare_connect/opportunity/forms.py | 1 + commcare_connect/opportunity/urls.py | 2 +- commcare_connect/opportunity/views.py | 10 +++------- .../templates/opportunity/invoice_list.html | 12 +++++++----- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/commcare_connect/opportunity/forms.py b/commcare_connect/opportunity/forms.py index 50836120..7891a94e 100644 --- a/commcare_connect/opportunity/forms.py +++ b/commcare_connect/opportunity/forms.py @@ -759,6 +759,7 @@ class PaymentInvoiceForm(forms.ModelForm): class Meta: model = PaymentInvoice fields = ("amount", "date", "invoice_number") + widgets = {"date": forms.DateInput(attrs={"type": "date", "class": "form-control"})} def __init__(self, *args, **kwargs): self.opportunity = kwargs.pop("opportunity") diff --git a/commcare_connect/opportunity/urls.py b/commcare_connect/opportunity/urls.py index f3273aae..de118162 100644 --- a/commcare_connect/opportunity/urls.py +++ b/commcare_connect/opportunity/urls.py @@ -109,6 +109,6 @@ path("/user_visit_review", user_visit_review, name="user_visit_review"), path("/payment_report", payment_report, name="payment_report"), path("/invoice/", views.invoice_list, name="invoice_list"), - path("/invoice_table", views.PaymentInvoiceTableView.as_view(), name="invoice_table"), + path("/invoice_table/", views.PaymentInvoiceTableView.as_view(), name="invoice_table"), path("/invoice/create/", views.invoice_create, name="invoice_create"), ] diff --git a/commcare_connect/opportunity/views.py b/commcare_connect/opportunity/views.py index 350e76ac..90a1f2fa 100644 --- a/commcare_connect/opportunity/views.py +++ b/commcare_connect/opportunity/views.py @@ -1108,12 +1108,10 @@ class PaymentInvoiceTableView(OrganizationUserMixin, SingleTableView): paginate_by = 25 table_class = PaymentInvoiceTable template_name = "tables/single_table.html" - filter_class = "" def get_table_kwargs(self): kwargs = super().get_table_kwargs() - user_is_program_manager = self.request.org.program_manager and self.request.org_membership.is_admin - if not user_is_program_manager: + if not self.request.org_membership.is_program_manager: kwargs["exclude"] = ("pk",) return kwargs @@ -1128,20 +1126,18 @@ def invoice_list(request, org_slug, pk): opportunity = get_opportunity_or_404(pk, org_slug) if not opportunity.managed: return redirect("opportunity:detail", org_slug, pk) - user_is_program_manager = request.org.program_manager and request.org_membership.is_admin form = PaymentInvoiceForm(opportunity=opportunity) return render( request, "opportunity/invoice_list.html", - context=dict(opportunity=opportunity, user_is_program_manager=user_is_program_manager, form=form), + context=dict(opportunity=opportunity, form=form), ) @org_member_required def invoice_create(request, org_slug, pk): opportunity = get_opportunity_or_404(pk, org_slug) - user_is_program_manager = request.org.program_manager and request.org_membership.is_admin - if not opportunity.managed or user_is_program_manager: + if not opportunity.managed or request.org_membership.is_program_manager: return redirect("opportunity:detail", org_slug, pk) form = PaymentInvoiceForm(data=request.POST or None, opportunity=opportunity) if request.POST and form.is_valid(): diff --git a/commcare_connect/templates/opportunity/invoice_list.html b/commcare_connect/templates/opportunity/invoice_list.html index 3ce3535f..843b9cf1 100644 --- a/commcare_connect/templates/opportunity/invoice_list.html +++ b/commcare_connect/templates/opportunity/invoice_list.html @@ -27,18 +27,19 @@

Invoices

{% endblock content %} +{% if not request.org_membership.is_program_manager %} {% block modal %} {% endblock modal %} {% endif %} + +{% block inline_javascript %} + +{% endblock inline_javascript %} From 5d5ebf325997e4740a818172f021ffc626da32dc Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Thu, 5 Sep 2024 13:14:44 +0530 Subject: [PATCH 10/20] Add payment approval --- commcare_connect/opportunity/tables.py | 2 +- commcare_connect/opportunity/urls.py | 1 + commcare_connect/opportunity/views.py | 18 +++++++++++++ .../templates/opportunity/invoice_list.html | 25 ++++++++++++++++++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/commcare_connect/opportunity/tables.py b/commcare_connect/opportunity/tables.py index 8d8d8a68..f658e034 100644 --- a/commcare_connect/opportunity/tables.py +++ b/commcare_connect/opportunity/tables.py @@ -436,7 +436,7 @@ def render_payment_status(self, value): def render_payment_date(self, value): if value is not None: - return value.date + return value.date_paid return diff --git a/commcare_connect/opportunity/urls.py b/commcare_connect/opportunity/urls.py index de118162..52b4da59 100644 --- a/commcare_connect/opportunity/urls.py +++ b/commcare_connect/opportunity/urls.py @@ -111,4 +111,5 @@ path("/invoice/", views.invoice_list, name="invoice_list"), path("/invoice_table/", views.PaymentInvoiceTableView.as_view(), name="invoice_table"), path("/invoice/create/", views.invoice_create, name="invoice_create"), + path("/invoice/approve/", views.invoice_approve, name="invoice_approve"), ] diff --git a/commcare_connect/opportunity/views.py b/commcare_connect/opportunity/views.py index 4f350851..9a61fb2c 100644 --- a/commcare_connect/opportunity/views.py +++ b/commcare_connect/opportunity/views.py @@ -1149,3 +1149,21 @@ def invoice_create(request, org_slug, pk): form = PaymentInvoiceForm(opportunity=opportunity) return HttpResponse(render_crispy_form(form), headers={"HX-Trigger": "newInvoice"}) return HttpResponse(render_crispy_form(form)) + + +@org_member_required +@require_POST +def invoice_approve(request, org_slug, pk): + opportunity = get_opportunity_or_404(pk, org_slug) + if not opportunity.managed or not request.org_membership.is_program_manager: + return redirect("opportunity:detail", org_slug, pk) + invoice_ids = request.POST.getlist("pk") + invoices = PaymentInvoice.objects.filter(opportunity=opportunity, pk__in=invoice_ids, payment__isnull=True) + for invoice in invoices: + payment = Payment( + amount=invoice.amount, + organization=opportunity.organization, + invoice=invoice, + ) + payment.save() + return HttpResponse(headers={"HX-Trigger": "newInvoice"}) diff --git a/commcare_connect/templates/opportunity/invoice_list.html b/commcare_connect/templates/opportunity/invoice_list.html index 94494ff2..1aa87281 100644 --- a/commcare_connect/templates/opportunity/invoice_list.html +++ b/commcare_connect/templates/opportunity/invoice_list.html @@ -25,7 +25,7 @@

Invoices

{% endif %}
+ {% if request.org_membership.is_program_manager %} +
+ {% csrf_token %} + {% endif %} +
{% include "tables/table_placeholder.html" with num_cols=4 %}
+ + {% if request.org_membership.is_program_manager %} + +
+ {% endif %} {% endblock content %} {% if not request.org_membership.is_program_manager %} From 355a0df80cb8baa90829b34b8733dafb60f7c46f Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Thu, 5 Sep 2024 13:32:38 +0530 Subject: [PATCH 11/20] Add invoice list link on opportunity detail --- .../templates/opportunity/opportunity_detail.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/commcare_connect/templates/opportunity/opportunity_detail.html b/commcare_connect/templates/opportunity/opportunity_detail.html index 053a2d37..4f7fe1eb 100644 --- a/commcare_connect/templates/opportunity/opportunity_detail.html +++ b/commcare_connect/templates/opportunity/opportunity_detail.html @@ -61,6 +61,13 @@

{{ object.name }}

{% translate "Payment Report" %} +
  • + + + {% translate "Invoices" %} + +
  • {% endif %}
  • Date: Thu, 5 Sep 2024 13:39:05 +0530 Subject: [PATCH 12/20] Merge migrations --- ...=> 0057_paymentinvoice_payment_invoice.py} | 9 ++++++++- .../migrations/0058_payment_invoice.py | 20 ------------------- 2 files changed, 8 insertions(+), 21 deletions(-) rename commcare_connect/opportunity/migrations/{0057_paymentinvoice.py => 0057_paymentinvoice_payment_invoice.py} (74%) delete mode 100644 commcare_connect/opportunity/migrations/0058_payment_invoice.py diff --git a/commcare_connect/opportunity/migrations/0057_paymentinvoice.py b/commcare_connect/opportunity/migrations/0057_paymentinvoice_payment_invoice.py similarity index 74% rename from commcare_connect/opportunity/migrations/0057_paymentinvoice.py rename to commcare_connect/opportunity/migrations/0057_paymentinvoice_payment_invoice.py index 52129f9b..5a8febe0 100644 --- a/commcare_connect/opportunity/migrations/0057_paymentinvoice.py +++ b/commcare_connect/opportunity/migrations/0057_paymentinvoice_payment_invoice.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2024-09-03 09:38 +# Generated by Django 4.2.5 on 2024-09-05 08:08 from django.db import migrations, models import django.db.models.deletion @@ -26,4 +26,11 @@ class Migration(migrations.Migration): "unique_together": {("opportunity", "invoice_number")}, }, ), + migrations.AddField( + model_name="payment", + name="invoice", + field=models.OneToOneField( + blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to="opportunity.paymentinvoice" + ), + ), ] diff --git a/commcare_connect/opportunity/migrations/0058_payment_invoice.py b/commcare_connect/opportunity/migrations/0058_payment_invoice.py deleted file mode 100644 index 16684ba9..00000000 --- a/commcare_connect/opportunity/migrations/0058_payment_invoice.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.2.5 on 2024-09-05 04:07 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ("opportunity", "0057_paymentinvoice"), - ] - - operations = [ - migrations.AddField( - model_name="payment", - name="invoice", - field=models.OneToOneField( - blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to="opportunity.paymentinvoice" - ), - ), - ] From bcf603c391edf61c11eb7be9bd44a67a69d96af9 Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Thu, 5 Sep 2024 16:57:30 +0530 Subject: [PATCH 13/20] Fix org_membership none check --- commcare_connect/opportunity/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commcare_connect/opportunity/views.py b/commcare_connect/opportunity/views.py index 9a61fb2c..e503e32f 100644 --- a/commcare_connect/opportunity/views.py +++ b/commcare_connect/opportunity/views.py @@ -1111,7 +1111,7 @@ class PaymentInvoiceTableView(OrganizationUserMixin, SingleTableView): def get_table_kwargs(self): kwargs = super().get_table_kwargs() - if not self.request.org_membership.is_program_manager: + if self.request.org_membership != None and not self.request.org_membership.is_program_manager: # noqa: E711 kwargs["exclude"] = ("pk",) return kwargs From 8a53dc12c9d8f4271ca532e6c34bbed808daafd1 Mon Sep 17 00:00:00 2001 From: Pawan Verma Date: Thu, 5 Sep 2024 17:38:04 +0530 Subject: [PATCH 14/20] Add close modal after save --- commcare_connect/templates/opportunity/invoice_list.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commcare_connect/templates/opportunity/invoice_list.html b/commcare_connect/templates/opportunity/invoice_list.html index 1aa87281..ea21d14b 100644 --- a/commcare_connect/templates/opportunity/invoice_list.html +++ b/commcare_connect/templates/opportunity/invoice_list.html @@ -69,7 +69,7 @@

    Invoices