diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index 201a8b9..c113f78 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -124,6 +124,19 @@ class BillingEntity(ServalaModelMixin, models.Model): def __str__(self): return self.name + @classmethod + def create_from_data(cls, odoo_data): + instance = BillingEntity.objects.create(name=odoo_data.get("name")) + # TODO implement odoo creation from data + return instance + + @classmethod + def create_from_id(cls, odoo_id): + # TODO implement odoo creation from ID + # instance = BillingEntity.objects.create(name=odoo_data.get("name")) + # return instance + pass + class OrganizationOrigin(ServalaModelMixin, models.Model): """ diff --git a/src/servala/core/odoo.py b/src/servala/core/odoo.py index 81a24bc..85f0b35 100644 --- a/src/servala/core/odoo.py +++ b/src/servala/core/odoo.py @@ -97,6 +97,7 @@ def get_invoice_addresses(user): # addresses / organizations created by the user # - if the user is associated with an odoo contact, return all billing # addresses with the same parent_id + email = user.email or_conditions = [("email", "ilike", email)] email = user if isinstance(user, str) else user.email diff --git a/src/servala/frontend/forms/organization.py b/src/servala/frontend/forms/organization.py index 41cc26c..115e574 100644 --- a/src/servala/frontend/forms/organization.py +++ b/src/servala/frontend/forms/organization.py @@ -1,6 +1,9 @@ +from django import forms from django.forms import ModelForm +from django.utils.translation import gettext_lazy as _ from servala.core.models import Organization +from servala.core.odoo import get_invoice_addresses from servala.frontend.forms.mixins import HtmxMixin @@ -8,3 +11,102 @@ class OrganizationForm(HtmxMixin, ModelForm): class Meta: model = Organization fields = ("name",) + + +class OrganizationCreateForm(OrganizationForm): + billing_processing_choice = forms.ChoiceField( + choices=[ + ("existing", _("Use an existing billing address")), + ("new", _("Create a new billing address")), + ], + widget=forms.RadioSelect, + label=_("Billing Address"), + initial="new", # Will change to 'existing' if options are found + ) + existing_odoo_address_id = forms.ChoiceField( + label=_("Existing Billing Address"), + required=False, + ) + + # Fields for creating a new billing address in Odoo, prefixed with 'ba_' + ba_name = forms.CharField( + label=_("Contact Person / Company Name"), required=False, max_length=100 + ) + ba_street = forms.CharField(label=_("Street"), required=False, max_length=100) + ba_street2 = forms.CharField( + label=_("Street 2 (Optional)"), required=False, max_length=100 + ) + ba_city = forms.CharField(label=_("City"), required=False, max_length=100) + ba_zip = forms.CharField(label=_("ZIP Code"), required=False, max_length=20) + # For state & country, Odoo uses structured data. For now, text input. + # These will need mapping logic when actual Odoo creation is implemented. + ba_state_name = forms.CharField( + label=_("State / Province"), required=False, max_length=100 + ) + ba_country_name = forms.CharField( + label=_("Country"), required=False, max_length=100 + ) + ba_email = forms.EmailField(label=_("Billing Email"), required=False) + ba_phone = forms.CharField(label=_("Billing Phone"), required=False, max_length=30) + ba_vat = forms.CharField(label=_("VAT ID"), required=False, max_length=50) + + class Meta(OrganizationForm.Meta): + pass + + def __init__(self, *args, user=None, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + self.odoo_addresses = get_invoice_addresses(self.user) + + if self.odoo_addresses: + address_choices = [("", _("---------"))] + for addr in self.odoo_addresses: + display_parts = [ + addr.get("name"), + addr.get("street"), + addr.get("city"), + addr.get("zip"), + ] + display_name = ", ".join(filter(None, display_parts)) + address_choices.append((str(addr["id"]), display_name)) + + self.fields["existing_odoo_address_id"].choices = address_choices + if not self.is_bound and "billing_processing_choice" not in self.initial: + self.fields["billing_processing_choice"].initial = "existing" + else: + # No existing Odoo addresses. Force 'new' choice. + self.fields["billing_processing_choice"].choices = [ + ("new", _("Create a new billing address")), + ] + self.fields["billing_processing_choice"].initial = "new" + self.fields["billing_processing_choice"].widget = forms.HiddenInput() + self.fields["existing_odoo_address_id"].widget = forms.HiddenInput() + + def clean(self): + cleaned_data = super().clean() + choice = cleaned_data.get("billing_processing_choice") + if choice == "new": + required_fields = [ + "ba_name", + "ba_street", + "ba_city", + "ba_zip", + "ba_state_name", + "ba_country_name", + "ba_email", + ] + for field_name in required_fields: + if not cleaned_data.get(field_name): + self.add_error( + field_name, + _( + "This field is required when creating a new billing address." + ), + ) + else: + existing_id_str = cleaned_data.get("existing_odoo_address_id") + if not existing_id_str: + self.add_error( + "existing_odoo_address_id", _("Please select an existing address.") + ) + return cleaned_data diff --git a/src/servala/frontend/templates/frontend/forms/errors.html b/src/servala/frontend/templates/frontend/forms/errors.html new file mode 100644 index 0000000..964687d --- /dev/null +++ b/src/servala/frontend/templates/frontend/forms/errors.html @@ -0,0 +1,18 @@ +{% load i18n %} +{% if form.non_field_errors or form.errors %} +