Skip to content

Commit 592df62

Browse files
authored
Merge pull request dimagi#986 from dimagi/cs/CI-501-remove-switch
Remove "automated_invoice" feature switch
2 parents 4e08161 + 2dedf3c commit 592df62

File tree

4 files changed

+23
-195
lines changed

4 files changed

+23
-195
lines changed
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
OPPORTUNITY_CREDENTIALS = "opportunity_credentials"
22
USER_VISIT_FILTERS = "user_visit_filters"
3-
AUTOMATED_INVOICES = "automated_invoices"
43
INVOICE_REVIEW = "invoice_review"
54
AUTOMATED_INVOICES_MONTHLY = "automated_invoices_monthly"
65
API_UUID = "api_uuid"

commcare_connect/opportunity/forms.py

Lines changed: 0 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,134 +1454,6 @@ def __init__(self, *args, **kwargs):
14541454
)
14551455

14561456

1457-
class PaymentInvoiceForm(forms.ModelForm):
1458-
usd_currency = forms.BooleanField(
1459-
required=False,
1460-
initial=False,
1461-
label=_("Specify in USD"),
1462-
widget=forms.CheckboxInput(),
1463-
)
1464-
1465-
class Meta:
1466-
model = PaymentInvoice
1467-
fields = ("amount", "date", "invoice_number", "service_delivery")
1468-
widgets = {
1469-
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
1470-
"amount": forms.NumberInput(attrs={"min": "0"}),
1471-
}
1472-
1473-
def __init__(self, *args, **kwargs):
1474-
self.opportunity = kwargs.pop("opportunity")
1475-
self.invoice_type = kwargs.pop("invoice_type", PaymentInvoice.InvoiceType.service_delivery)
1476-
self.read_only = kwargs.pop("read_only", False)
1477-
self.status = kwargs.pop("status", InvoiceStatus.PENDING)
1478-
super().__init__(*args, **kwargs)
1479-
if self.read_only:
1480-
self.fields["status"] = forms.CharField(required=False, label=gettext("Invoice Status"))
1481-
for field in self.fields.values():
1482-
field.widget.attrs["readonly"] = "readonly"
1483-
1484-
if self.instance.pk:
1485-
self.status = self.instance.status
1486-
self.fields["status"].initial = self.instance.get_status_display()
1487-
1488-
self.fields["usd_currency"].widget.attrs.update(
1489-
{
1490-
"x-ref": "currencyToggle",
1491-
"x-on:change": "currency = $event.target.checked; convert(true)",
1492-
}
1493-
)
1494-
1495-
currency_code = self.opportunity.currency_code
1496-
1497-
self.helper = FormHelper(self)
1498-
layout_fields = [
1499-
Row(
1500-
Field(
1501-
"date",
1502-
**{
1503-
"x-ref": "date",
1504-
"x-on:change": "convert()",
1505-
},
1506-
),
1507-
Field(
1508-
"amount",
1509-
label=f"Amount ({currency_code})" if currency_code else "Amount",
1510-
**{
1511-
"x-ref": "amount",
1512-
"x-on:input.debounce.300ms": "convert()",
1513-
},
1514-
),
1515-
Div(css_id="converted-amount-wrapper", css_class="space-y-1 text-sm text-gray-500 mb-4"),
1516-
Field("invoice_number"),
1517-
Field("status"),
1518-
Field(
1519-
"usd_currency",
1520-
css_class=CHECKBOX_CLASS,
1521-
wrapper_class="flex p-4 justify-between rounded-lg bg-gray-100",
1522-
),
1523-
css_class="flex flex-col",
1524-
),
1525-
]
1526-
1527-
if not self.read_only:
1528-
layout_fields.append(
1529-
Div(
1530-
Submit("submit", "Submit", css_class="button button-md primary-dark"),
1531-
css_class="flex justify-end mt-4",
1532-
)
1533-
)
1534-
1535-
self.helper.layout = Layout(*layout_fields)
1536-
self.helper.form_tag = False
1537-
1538-
def clean_invoice_number(self):
1539-
invoice_number = self.cleaned_data["invoice_number"]
1540-
if PaymentInvoice.objects.filter(opportunity=self.opportunity, invoice_number=invoice_number).exists():
1541-
raise ValidationError(
1542-
f'Invoice "{invoice_number}" already exists',
1543-
code="invoice_number_reused",
1544-
)
1545-
return invoice_number
1546-
1547-
def clean(self):
1548-
cleaned_data = super().clean()
1549-
amount = cleaned_data.get("amount")
1550-
usd_currency = cleaned_data.get("usd_currency")
1551-
date = cleaned_data.get("date")
1552-
1553-
if amount is None or date is None:
1554-
return cleaned_data # Let individual field errors handle missing values
1555-
1556-
currency = getattr(self.opportunity, "currency", None)
1557-
exchange_rate = ExchangeRate.latest_exchange_rate(currency.code if currency else None, date)
1558-
if not exchange_rate:
1559-
raise ValidationError("Exchange rate not available for selected date.")
1560-
1561-
cleaned_data["exchange_rate"] = exchange_rate
1562-
1563-
if usd_currency:
1564-
cleaned_data["amount_usd"] = amount
1565-
cleaned_data["amount"] = round(amount * exchange_rate.rate, 2)
1566-
else:
1567-
cleaned_data["amount"] = amount
1568-
cleaned_data["amount_usd"] = round(amount / exchange_rate.rate, 2)
1569-
1570-
return cleaned_data
1571-
1572-
def save(self, commit=True):
1573-
instance = super().save(commit=False)
1574-
instance.opportunity = self.opportunity
1575-
instance.amount_usd = self.cleaned_data["amount_usd"]
1576-
instance.exchange_rate = self.cleaned_data["exchange_rate"]
1577-
instance.service_delivery = self.invoice_type == PaymentInvoice.InvoiceType.service_delivery
1578-
instance.status = self.status
1579-
1580-
if commit:
1581-
instance.save()
1582-
return instance
1583-
1584-
15851457
class AutomatedPaymentInvoiceForm(forms.ModelForm):
15861458
amount = forms.DecimalField(
15871459
label=_("Amount"),

commcare_connect/opportunity/tests/test_views.py

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,8 @@
1313
from waffle.testutils import override_switch
1414

1515
from commcare_connect.connect_id_client.models import ConnectIdUser
16-
from commcare_connect.flags.switch_names import AUTOMATED_INVOICES, INVOICE_REVIEW
17-
from commcare_connect.opportunity.forms import (
18-
AddBudgetExistingUsersForm,
19-
AutomatedPaymentInvoiceForm,
20-
PaymentInvoiceForm,
21-
PaymentUnitForm,
22-
)
16+
from commcare_connect.flags.switch_names import INVOICE_REVIEW
17+
from commcare_connect.opportunity.forms import AddBudgetExistingUsersForm, AutomatedPaymentInvoiceForm, PaymentUnitForm
2318
from commcare_connect.opportunity.helpers import OpportunityData, TieredQueryset
2419
from commcare_connect.opportunity.models import (
2520
Opportunity,
@@ -1195,13 +1190,8 @@ def assert_readonly_form(response):
11951190
else:
11961191
assert field.widget.attrs.get("readonly") == "readonly", f"Field {field_name} should be readonly"
11971192

1198-
with override_switch(AUTOMATED_INVOICES, active=True):
1199-
response = client.get(url)
1200-
assert_readonly_form(response)
1201-
1202-
with override_switch(AUTOMATED_INVOICES, active=False):
1203-
response = client.get(url)
1204-
assert_readonly_form(response)
1193+
response = client.get(url)
1194+
assert_readonly_form(response)
12051195

12061196
@override_switch(INVOICE_REVIEW, active=True)
12071197
def test_custom_invoice_no_line_items(self, client, setup_invoice):
@@ -1215,16 +1205,15 @@ def test_custom_invoice_no_line_items(self, client, setup_invoice):
12151205
date=date(2025, 11, 1),
12161206
)
12171207

1218-
with override_switch(AUTOMATED_INVOICES, active=True):
1219-
client.force_login(user)
1220-
url = reverse(
1221-
"opportunity:invoice_review",
1222-
args=(opportunity.organization.slug, opportunity.opportunity_id, custom_invoice.payment_invoice_id),
1223-
)
1224-
response = client.get(url)
1208+
client.force_login(user)
1209+
url = reverse(
1210+
"opportunity:invoice_review",
1211+
args=(opportunity.organization.slug, opportunity.opportunity_id, custom_invoice.payment_invoice_id),
1212+
)
1213+
response = client.get(url)
12251214

1226-
form = response.context["form"]
1227-
assert form.line_items_table is None
1215+
form = response.context["form"]
1216+
assert form.line_items_table is None
12281217

12291218
@override_switch(INVOICE_REVIEW, active=True)
12301219
def test_unauthorized_user_cannot_access(self, client, setup_invoice):
@@ -1242,37 +1231,20 @@ def test_unauthorized_user_cannot_access(self, client, setup_invoice):
12421231
# Should redirect (permission denied) or return 403/404
12431232
assert response.status_code in [302, 403, 404]
12441233

1245-
@override_switch(INVOICE_REVIEW, active=True)
1246-
def test_legacy_form_when_switch_inactive(self, client, setup_invoice):
1247-
invoice = setup_invoice["invoice"]
1248-
opportunity = setup_invoice["opportunity"]
1249-
user = setup_invoice["user"]
1250-
1251-
with override_switch(AUTOMATED_INVOICES, active=False):
1252-
client.force_login(user)
1253-
url = reverse(
1254-
"opportunity:invoice_review",
1255-
args=(opportunity.organization.slug, opportunity.opportunity_id, invoice.payment_invoice_id),
1256-
)
1257-
response = client.get(url)
1258-
form = response.context["form"]
1259-
assert isinstance(form, PaymentInvoiceForm)
1260-
12611234
@override_switch(INVOICE_REVIEW, active=True)
12621235
def test_automated_form_when_switch_active(self, client, setup_invoice):
12631236
invoice = setup_invoice["invoice"]
12641237
opportunity = setup_invoice["opportunity"]
12651238
user = setup_invoice["user"]
12661239

1267-
with override_switch(AUTOMATED_INVOICES, active=True):
1268-
client.force_login(user)
1269-
url = reverse(
1270-
"opportunity:invoice_review",
1271-
args=(opportunity.organization.slug, opportunity.opportunity_id, invoice.payment_invoice_id),
1272-
)
1273-
response = client.get(url)
1274-
form = response.context["form"]
1275-
assert isinstance(form, AutomatedPaymentInvoiceForm)
1240+
client.force_login(user)
1241+
url = reverse(
1242+
"opportunity:invoice_review",
1243+
args=(opportunity.organization.slug, opportunity.opportunity_id, invoice.payment_invoice_id),
1244+
)
1245+
response = client.get(url)
1246+
form = response.context["form"]
1247+
assert isinstance(form, AutomatedPaymentInvoiceForm)
12761248

12771249

12781250
@pytest.mark.django_db

commcare_connect/opportunity/views.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from waffle import switch_is_active
5555

5656
from commcare_connect.connect_id_client import fetch_users
57-
from commcare_connect.flags.switch_names import AUTOMATED_INVOICES, INVOICE_REVIEW, USER_VISIT_FILTERS
57+
from commcare_connect.flags.switch_names import INVOICE_REVIEW, USER_VISIT_FILTERS
5858
from commcare_connect.form_receiver.serializers import XFormSerializer
5959
from commcare_connect.opportunity.api.serializers import remove_opportunity_access_cache
6060
from commcare_connect.opportunity.app_xml import AppNoBuildException
@@ -78,7 +78,6 @@
7878
OpportunityUserInviteForm,
7979
OpportunityVerificationFlagsConfigForm,
8080
PaymentExportForm,
81-
PaymentInvoiceForm,
8281
PaymentUnitForm,
8382
SendMessageMobileUsersForm,
8483
VisitExportForm,
@@ -1425,6 +1424,7 @@ def invoice_list(request, org_slug, opp_id):
14251424
class InvoiceCreateView(OrganizationUserMixin, OpportunityObjectMixin, CreateView):
14261425
model = PaymentInvoice
14271426
template_name = "opportunity/invoice_create.html"
1427+
form_class = AutomatedPaymentInvoiceForm
14281428

14291429
def get_context_data(self, **kwargs):
14301430
context = super().get_context_data(**kwargs)
@@ -1455,12 +1455,6 @@ def get_context_data(self, **kwargs):
14551455
)
14561456
return context
14571457

1458-
@property
1459-
def form_class(self):
1460-
if waffle.switch_is_active(AUTOMATED_INVOICES):
1461-
return AutomatedPaymentInvoiceForm
1462-
return PaymentInvoiceForm
1463-
14641458
@property
14651459
def breadcrumb_title(self):
14661460
service_delivery = PaymentInvoice.InvoiceType.service_delivery
@@ -1485,8 +1479,7 @@ def get_form_kwargs(self):
14851479
kwargs["opportunity"] = self.get_opportunity()
14861480
kwargs["invoice_type"] = self.request.GET.get("invoice_type", PaymentInvoice.InvoiceType.service_delivery)
14871481
kwargs["status"] = InvoiceStatus.SUBMITTED
1488-
if waffle.switch_is_active(AUTOMATED_INVOICES):
1489-
kwargs["is_opportunity_pm"] = self.request.is_opportunity_pm
1482+
kwargs["is_opportunity_pm"] = self.request.is_opportunity_pm
14901483
return kwargs
14911484

14921485
def get_success_url(self):
@@ -1549,14 +1542,6 @@ def get_form(self):
15491542
else PaymentInvoice.InvoiceType.custom
15501543
)
15511544

1552-
if not waffle.switch_is_active(AUTOMATED_INVOICES):
1553-
return PaymentInvoiceForm(
1554-
instance=invoice,
1555-
opportunity=opportunity,
1556-
invoice_type=invoice_type,
1557-
read_only=True,
1558-
)
1559-
15601545
line_items_table = None
15611546
if invoice.service_delivery:
15621547
completed_works = get_invoiced_visit_items(invoice)

0 commit comments

Comments
 (0)