From d63c6af374407f9a070bfad4735407907263811e Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Fri, 31 Jul 2026 04:34:01 -0700 Subject: [PATCH] Update company_site/financial/forms.py for Stripe invoices (#23) --- company_site/financial/forms.py | 155 +++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/company_site/financial/forms.py b/company_site/financial/forms.py index 986d6e8..d26ddea 100644 --- a/company_site/financial/forms.py +++ b/company_site/financial/forms.py @@ -2,7 +2,19 @@ import datetime from django import forms from django.contrib.auth.models import User from django.forms import ModelForm -from .models import Employee, Contract, ChargeNumber, TimeCardCell, AddressModel, UserProfile, set_user_type +from .models import ( + Employee, + Contract, + ChargeNumber, + TimeCardCell, + AddressModel, + UserProfile, + set_user_type, + BillingCustomer, + Invoice, + RecurringSubscription, +) +from .stripe_billing import dollars_to_cents class NewEmployeeForm(ModelForm): first_name = forms.CharField(max_length=30, required=False, label="First Name") @@ -99,3 +111,144 @@ class TimeLogForm(ModelForm): cleaned_data['hour'] = duration return cleaned_data + + +class BillingCustomerForm(ModelForm): + class Meta: + model = BillingCustomer + fields = ["name", "email", "company", "notes"] + + +class OneOffInvoiceForm(forms.Form): + customer = forms.ModelChoiceField( + queryset=BillingCustomer.objects.all(), + required=False, + help_text="Pick an existing customer, or fill New customer fields below.", + ) + new_customer_name = forms.CharField(max_length=200, required=False, label="New customer name") + new_customer_email = forms.EmailField(required=False, label="New customer email") + new_customer_company = forms.CharField( + max_length=200, required=False, label="New customer company" + ) + description = forms.CharField(max_length=500) + amount = forms.DecimalField( + max_digits=10, + decimal_places=2, + min_value=0.50, + label="Amount (USD)", + help_text="Minimum $0.50", + ) + due_date = forms.DateField( + required=False, + widget=forms.DateInput(attrs={"type": "date"}), + ) + notes = forms.CharField(required=False, widget=forms.Textarea(attrs={"rows": 3})) + send_email = forms.BooleanField( + required=False, + initial=True, + label="Email payment link to customer", + ) + + def clean(self): + cleaned = super().clean() + customer = cleaned.get("customer") + name = (cleaned.get("new_customer_name") or "").strip() + email = (cleaned.get("new_customer_email") or "").strip() + if not customer and not (name and email): + raise forms.ValidationError( + "Select an existing customer or provide new customer name and email." + ) + return cleaned + + def resolve_customer(self, user): + customer = self.cleaned_data.get("customer") + if customer: + return customer + customer = BillingCustomer( + name=self.cleaned_data["new_customer_name"].strip(), + email=self.cleaned_data["new_customer_email"].strip(), + company=(self.cleaned_data.get("new_customer_company") or "").strip(), + created_by=user, + last_modified_BY=user, + ) + customer.save() + return customer + + def build_invoice(self, user) -> Invoice: + customer = self.resolve_customer(user) + return Invoice( + customer=customer, + description=self.cleaned_data["description"], + amount_cents=dollars_to_cents(self.cleaned_data["amount"]), + due_date=self.cleaned_data.get("due_date"), + notes=self.cleaned_data.get("notes") or "", + created_by=user, + last_modified_BY=user, + ) + + +class RecurringSubscriptionForm(forms.Form): + customer = forms.ModelChoiceField( + queryset=BillingCustomer.objects.all(), + required=False, + help_text="Pick an existing customer, or fill New customer fields below.", + ) + new_customer_name = forms.CharField(max_length=200, required=False, label="New customer name") + new_customer_email = forms.EmailField(required=False, label="New customer email") + new_customer_company = forms.CharField( + max_length=200, required=False, label="New customer company" + ) + description = forms.CharField(max_length=500) + amount = forms.DecimalField( + max_digits=10, + decimal_places=2, + min_value=0.50, + label="Amount per period (USD)", + ) + interval = forms.ChoiceField( + choices=RecurringSubscription.Interval.choices, + initial=RecurringSubscription.Interval.MONTH, + ) + notes = forms.CharField(required=False, widget=forms.Textarea(attrs={"rows": 3})) + send_email = forms.BooleanField( + required=False, + initial=True, + label="Email checkout / payment link to customer", + ) + + def clean(self): + cleaned = super().clean() + customer = cleaned.get("customer") + name = (cleaned.get("new_customer_name") or "").strip() + email = (cleaned.get("new_customer_email") or "").strip() + if not customer and not (name and email): + raise forms.ValidationError( + "Select an existing customer or provide new customer name and email." + ) + return cleaned + + def resolve_customer(self, user): + customer = self.cleaned_data.get("customer") + if customer: + return customer + customer = BillingCustomer( + name=self.cleaned_data["new_customer_name"].strip(), + email=self.cleaned_data["new_customer_email"].strip(), + company=(self.cleaned_data.get("new_customer_company") or "").strip(), + created_by=user, + last_modified_BY=user, + ) + customer.save() + return customer + + def build_subscription(self, user) -> RecurringSubscription: + customer = self.resolve_customer(user) + return RecurringSubscription( + customer=customer, + description=self.cleaned_data["description"], + amount_cents=dollars_to_cents(self.cleaned_data["amount"]), + interval=self.cleaned_data["interval"], + notes=self.cleaned_data.get("notes") or "", + created_by=user, + last_modified_BY=user, + )