diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index c113f78..fe22623 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -1,7 +1,7 @@ import rules import urlman from django.conf import settings -from django.db import models +from django.db import models, transaction from django.utils.functional import cached_property from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ @@ -9,6 +9,7 @@ from django_scopes import ScopedManager, scopes_disabled from servala.core import rules as perms from servala.core.models.mixins import ServalaModelMixin +from servala.core.odoo import CLIENT class Organization(ServalaModelMixin, models.Model): @@ -125,9 +126,63 @@ class BillingEntity(ServalaModelMixin, models.Model): 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 + @transaction.atomic + def create_from_data(cls, name, odoo_data): + """ + Creates a BillingEntity and corresponding Odoo records. + + This method creates a `res.partner` record in Odoo with `company_type='company'` + for the main company, and another `res.partner` record with `company_type='person'` + and `type='invoice'` (linked via `parent_id` to the first record) for the + invoice address. The IDs of these Odoo records are stored in the BillingEntity. + + Args: + odoo_data (dict): A dictionary containing the data for creating + the BillingEntity and Odoo records. + + Expected keys in `odoo_data`: + - `invoice_street` (str): Street for the invoice address. + - `invoice_street2` (str): Second line of street address for the invoice address. + - `invoice_city` (str): City for the invoice address. + - `invoice_zip` (str): ZIP/Postal code for the invoice address. + - `invoice_country` (int): Odoo `res.country` ID for the invoice address country. + - `invoice_email` (str): Email address for the invoice contact. + - `invoice_phone` (str): Phone number for the invoice contact. + """ + instance = cls.objects.create(name=name) + company_payload = { + "name": odoo_data.get("company_name", name), + "company_type": "company", + } + if vat := odoo_data.get("invoice_vat"): + company_payload["vat"] = vat + company_id = CLIENT.execute("res.partner", "create", [company_payload]) + instance.odoo_company_id = company_id + + invoice_address_payload = { + "name": name, + "company_type": "person", + "type": "invoice", + "parent_id": company_id, + } + invoice_optional_fields = { + "street": odoo_data.get("invoice_street"), + "street2": odoo_data.get("invoice_street2"), + "city": odoo_data.get("invoice_city"), + "zip": odoo_data.get("invoice_zip"), + "country_id": odoo_data.get("invoice_country"), + "email": odoo_data.get("invoice_email"), + } + invoice_address_payload.update( + {k: v for k, v in invoice_optional_fields.items() if v is not None} + ) + + invoice_address_id = CLIENT.execute( + "res.partner", "create", [invoice_address_payload] + ) + instance.odoo_invoice_id = invoice_address_id + + instance.save(update_fields=["odoo_company_id", "odoo_invoice_id"]) return instance @classmethod diff --git a/src/servala/frontend/forms/organization.py b/src/servala/frontend/forms/organization.py index a0b3800..b08c2f7 100644 --- a/src/servala/frontend/forms/organization.py +++ b/src/servala/frontend/forms/organization.py @@ -39,9 +39,7 @@ class OrganizationCreateForm(OrganizationForm): choices=get_odoo_countries(), ) invoice_email = forms.EmailField(label=_("Billing Email"), required=False) - invoice_phone = forms.CharField( - label=_("Billing Phone"), required=False, max_length=30 - ) + invoice_phone = forms.CharField(label=_("Phone"), required=False, max_length=30) invoice_vat = forms.CharField(label=_("VAT ID"), required=False, max_length=50) class Meta(OrganizationForm.Meta): @@ -77,18 +75,13 @@ class OrganizationCreateForm(OrganizationForm): 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.pop("billing_processing_choice") 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": + if not choice or choice == "new": required_fields = [ "invoice_street", "invoice_city", diff --git a/src/servala/frontend/views/organization.py b/src/servala/frontend/views/organization.py index fe0bbb2..834dede 100644 --- a/src/servala/frontend/views/organization.py +++ b/src/servala/frontend/views/organization.py @@ -22,13 +22,14 @@ class OrganizationCreateView(AutoPermissionRequiredMixin, CreateView): billing_choice = form.cleaned_data.get("billing_processing_choice") billing_entity = None - if billing_choice == "new": + if not billing_choice or billing_choice == "new": billing_entity = BillingEntity.create_from_data( + form.cleaned_data["name"], { - key[3:]: value + key: value for key, value in form.cleaned_data.items() - if key.startswith("ba_") - } + if key.startswith("invoice_") + }, ) elif odoo_id := form.cleaned_data.get("existing_odoo_address_id"): billing_entity = BillingEntity.objects.filter(