Compare commits
No commits in common. "cce071397cc43ff57723359c8e8cb6f6df13c795" and "c0d3a83c9de81ac794f637b710620de16c290e84" have entirely different histories.
cce071397c
...
c0d3a83c9d
5 changed files with 4 additions and 497 deletions
|
|
@ -9,11 +9,8 @@ from servala.core.forms import ControlPlaneAdminForm, ServiceDefinitionAdminForm
|
||||||
from servala.core.models import (
|
from servala.core.models import (
|
||||||
BillingEntity,
|
BillingEntity,
|
||||||
CloudProvider,
|
CloudProvider,
|
||||||
ComputePlan,
|
|
||||||
ComputePlanAssignment,
|
|
||||||
ControlPlane,
|
ControlPlane,
|
||||||
ControlPlaneCRD,
|
ControlPlaneCRD,
|
||||||
OdooObjectCache,
|
|
||||||
Organization,
|
Organization,
|
||||||
OrganizationInvitation,
|
OrganizationInvitation,
|
||||||
OrganizationMembership,
|
OrganizationMembership,
|
||||||
|
|
@ -272,19 +269,6 @@ class ControlPlaneAdmin(admin.ModelAdmin):
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
|
||||||
_("Storage Plan"),
|
|
||||||
{
|
|
||||||
"fields": (
|
|
||||||
"storage_plan_odoo_product_id",
|
|
||||||
"storage_plan_odoo_unit_id",
|
|
||||||
"storage_plan_price_per_gib",
|
|
||||||
),
|
|
||||||
"description": _(
|
|
||||||
"Storage plan configuration for this control plane (hardcoded per control plane)."
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_exclude(self, request, obj=None):
|
def get_exclude(self, request, obj=None):
|
||||||
|
|
@ -379,21 +363,15 @@ class ControlPlaneCRDAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(ServiceInstance)
|
@admin.register(ServiceInstance)
|
||||||
class ServiceInstanceAdmin(admin.ModelAdmin):
|
class ServiceInstanceAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = ("name", "organization", "context", "created_by")
|
||||||
"name",
|
list_filter = ("organization", "context")
|
||||||
"organization",
|
|
||||||
"context",
|
|
||||||
"compute_plan_assignment",
|
|
||||||
"created_by",
|
|
||||||
)
|
|
||||||
list_filter = ("organization", "context", "compute_plan_assignment")
|
|
||||||
search_fields = (
|
search_fields = (
|
||||||
"name",
|
"name",
|
||||||
"organization__name",
|
"organization__name",
|
||||||
"context__service_offering__service__name",
|
"context__service_offering__service__name",
|
||||||
)
|
)
|
||||||
readonly_fields = ("name", "organization", "context")
|
readonly_fields = ("name", "organization", "context")
|
||||||
autocomplete_fields = ("organization", "context", "compute_plan_assignment")
|
autocomplete_fields = ("organization", "context")
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
if obj: # If this is an edit (not a new instance)
|
if obj: # If this is an edit (not a new instance)
|
||||||
|
|
@ -412,10 +390,6 @@ class ServiceInstanceAdmin(admin.ModelAdmin):
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
|
||||||
_("Plan"),
|
|
||||||
{"fields": ("compute_plan_assignment",)},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -446,135 +420,3 @@ class ServiceOfferingAdmin(admin.ModelAdmin):
|
||||||
schema=external_links_schema
|
schema=external_links_schema
|
||||||
)
|
)
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
class ComputePlanAssignmentInline(admin.TabularInline):
|
|
||||||
model = ComputePlanAssignment
|
|
||||||
extra = 1
|
|
||||||
autocomplete_fields = ("control_plane_crd",)
|
|
||||||
fields = (
|
|
||||||
"compute_plan",
|
|
||||||
"control_plane_crd",
|
|
||||||
"sla",
|
|
||||||
"odoo_product_id",
|
|
||||||
"odoo_unit_id",
|
|
||||||
"price",
|
|
||||||
"minimum_service_size",
|
|
||||||
"sort_order",
|
|
||||||
"is_active",
|
|
||||||
)
|
|
||||||
readonly_fields = ()
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ComputePlan)
|
|
||||||
class ComputePlanAdmin(admin.ModelAdmin):
|
|
||||||
list_display = (
|
|
||||||
"name",
|
|
||||||
"is_active",
|
|
||||||
"memory_limits",
|
|
||||||
"cpu_limits",
|
|
||||||
)
|
|
||||||
list_filter = ("is_active",)
|
|
||||||
search_fields = ("name", "description")
|
|
||||||
inlines = (ComputePlanAssignmentInline,)
|
|
||||||
fieldsets = (
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
{
|
|
||||||
"fields": (
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"is_active",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
_("Resources"),
|
|
||||||
{
|
|
||||||
"fields": (
|
|
||||||
"memory_requests",
|
|
||||||
"memory_limits",
|
|
||||||
"cpu_requests",
|
|
||||||
"cpu_limits",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ComputePlanAssignment)
|
|
||||||
class ComputePlanAssignmentAdmin(admin.ModelAdmin):
|
|
||||||
list_display = (
|
|
||||||
"compute_plan",
|
|
||||||
"control_plane_crd",
|
|
||||||
"sla",
|
|
||||||
"price",
|
|
||||||
"sort_order",
|
|
||||||
"is_active",
|
|
||||||
)
|
|
||||||
list_filter = ("is_active", "sla", "control_plane_crd")
|
|
||||||
search_fields = (
|
|
||||||
"compute_plan__name",
|
|
||||||
"control_plane_crd__service_offering__service__name",
|
|
||||||
)
|
|
||||||
autocomplete_fields = ("compute_plan", "control_plane_crd")
|
|
||||||
fieldsets = (
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
{
|
|
||||||
"fields": (
|
|
||||||
"compute_plan",
|
|
||||||
"control_plane_crd",
|
|
||||||
"sla",
|
|
||||||
"is_active",
|
|
||||||
"sort_order",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
_("Odoo Integration"),
|
|
||||||
{
|
|
||||||
"fields": (
|
|
||||||
"odoo_product_id",
|
|
||||||
"odoo_unit_id",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
_("Pricing & Constraints"),
|
|
||||||
{
|
|
||||||
"fields": (
|
|
||||||
"price",
|
|
||||||
"minimum_service_size",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(OdooObjectCache)
|
|
||||||
class OdooObjectCacheAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("odoo_model", "odoo_id", "updated_at", "expires_at", "is_expired")
|
|
||||||
list_filter = ("odoo_model", "updated_at", "expires_at")
|
|
||||||
search_fields = ("odoo_model", "odoo_id")
|
|
||||||
readonly_fields = ("created_at", "updated_at")
|
|
||||||
actions = ["refresh_caches"]
|
|
||||||
|
|
||||||
def is_expired(self, obj):
|
|
||||||
return obj.is_expired()
|
|
||||||
|
|
||||||
is_expired.boolean = True
|
|
||||||
is_expired.short_description = _("Expired")
|
|
||||||
|
|
||||||
def refresh_caches(self, request, queryset):
|
|
||||||
"""Admin action to refresh selected Odoo caches."""
|
|
||||||
refreshed_count = 0
|
|
||||||
for cache_obj in queryset:
|
|
||||||
cache_obj.fetch_and_update()
|
|
||||||
refreshed_count += 1
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
_(f"Successfully refreshed {refreshed_count} cache(s)."),
|
|
||||||
)
|
|
||||||
|
|
||||||
refresh_caches.short_description = _("Refresh caches")
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from .odoo_cache import OdooObjectCache
|
|
||||||
from .organization import (
|
from .organization import (
|
||||||
BillingEntity,
|
BillingEntity,
|
||||||
Organization,
|
Organization,
|
||||||
|
|
@ -7,10 +6,6 @@ from .organization import (
|
||||||
OrganizationOrigin,
|
OrganizationOrigin,
|
||||||
OrganizationRole,
|
OrganizationRole,
|
||||||
)
|
)
|
||||||
from .plan import (
|
|
||||||
ComputePlan,
|
|
||||||
ComputePlanAssignment,
|
|
||||||
)
|
|
||||||
from .service import (
|
from .service import (
|
||||||
CloudProvider,
|
CloudProvider,
|
||||||
ControlPlane,
|
ControlPlane,
|
||||||
|
|
@ -26,11 +21,8 @@ from .user import User
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"BillingEntity",
|
"BillingEntity",
|
||||||
"CloudProvider",
|
"CloudProvider",
|
||||||
"ComputePlan",
|
|
||||||
"ComputePlanAssignment",
|
|
||||||
"ControlPlane",
|
"ControlPlane",
|
||||||
"ControlPlaneCRD",
|
"ControlPlaneCRD",
|
||||||
"OdooObjectCache",
|
|
||||||
"Organization",
|
"Organization",
|
||||||
"OrganizationInvitation",
|
"OrganizationInvitation",
|
||||||
"OrganizationMembership",
|
"OrganizationMembership",
|
||||||
|
|
@ -38,8 +30,8 @@ __all__ = [
|
||||||
"OrganizationRole",
|
"OrganizationRole",
|
||||||
"Service",
|
"Service",
|
||||||
"ServiceCategory",
|
"ServiceCategory",
|
||||||
"ServiceDefinition",
|
|
||||||
"ServiceInstance",
|
"ServiceInstance",
|
||||||
|
"ServiceDefinition",
|
||||||
"ServiceOffering",
|
"ServiceOffering",
|
||||||
"User",
|
"User",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from servala.core.models.mixins import ServalaModelMixin
|
|
||||||
|
|
||||||
|
|
||||||
class OdooObjectCache(ServalaModelMixin):
|
|
||||||
"""
|
|
||||||
Generic cache for Odoo API responses.
|
|
||||||
|
|
||||||
Caches data from various Odoo models (product.product, product.template, uom.uom, etc.)
|
|
||||||
to reduce API calls and improve performance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
odoo_model = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
verbose_name=_("Odoo model"),
|
|
||||||
help_text=_(
|
|
||||||
"Odoo model name: 'product.product', 'product.template', 'uom.uom', etc."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
odoo_id = models.PositiveIntegerField(
|
|
||||||
verbose_name=_("Odoo ID"),
|
|
||||||
help_text=_("ID in the Odoo model"),
|
|
||||||
)
|
|
||||||
data = models.JSONField(
|
|
||||||
verbose_name=_("Cached data"),
|
|
||||||
help_text=_("Cached Odoo data including price, reporting_product_id, etc."),
|
|
||||||
)
|
|
||||||
expires_at = models.DateTimeField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("Expires at"),
|
|
||||||
help_text=_("When cache should be refreshed (null = never expires)"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Odoo Object Cache")
|
|
||||||
verbose_name_plural = _("Odoo Object Caches")
|
|
||||||
unique_together = [["odoo_model", "odoo_id"]]
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=["odoo_model", "odoo_id"]),
|
|
||||||
models.Index(fields=["expires_at"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.odoo_model}({self.odoo_id})"
|
|
||||||
|
|
||||||
def is_expired(self):
|
|
||||||
"""Check if cache needs refresh."""
|
|
||||||
if self.expires_at is None:
|
|
||||||
return False
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
return timezone.now() > self.expires_at
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_or_fetch(cls, odoo_model, odoo_id, ttl_hours=24):
|
|
||||||
"""
|
|
||||||
Get cached data or fetch from Odoo if expired/missing.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
odoo_model: Odoo model name (e.g., 'product.product')
|
|
||||||
odoo_id: ID in the Odoo model
|
|
||||||
ttl_hours: Time-to-live in hours for the cache
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
OdooObjectCache instance with fresh data
|
|
||||||
"""
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
try:
|
|
||||||
cache_obj = cls.objects.get(odoo_model=odoo_model, odoo_id=odoo_id)
|
|
||||||
if not cache_obj.is_expired():
|
|
||||||
return cache_obj
|
|
||||||
# Cache exists but expired, refresh it
|
|
||||||
cache_obj.fetch_and_update(ttl_hours=ttl_hours)
|
|
||||||
return cache_obj
|
|
||||||
except cls.DoesNotExist:
|
|
||||||
# Create new cache entry
|
|
||||||
cache_obj = cls.objects.create(
|
|
||||||
odoo_model=odoo_model,
|
|
||||||
odoo_id=odoo_id,
|
|
||||||
data={},
|
|
||||||
expires_at=(
|
|
||||||
timezone.now() + timedelta(hours=ttl_hours) if ttl_hours else None
|
|
||||||
),
|
|
||||||
)
|
|
||||||
cache_obj.fetch_and_update(ttl_hours=ttl_hours)
|
|
||||||
return cache_obj
|
|
||||||
|
|
||||||
def fetch_and_update(self, ttl_hours=24):
|
|
||||||
"""
|
|
||||||
Fetch latest data from Odoo and update cache.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ttl_hours: Time-to-live in hours for the cache
|
|
||||||
"""
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from servala.core.odoo import CLIENT
|
|
||||||
|
|
||||||
# Fetch data from Odoo
|
|
||||||
results = CLIENT.search_read(
|
|
||||||
self.odoo_model,
|
|
||||||
[[("id", "=", self.odoo_id)]],
|
|
||||||
fields=None, # Fetch all fields
|
|
||||||
)
|
|
||||||
|
|
||||||
if results:
|
|
||||||
self.data = results[0]
|
|
||||||
self.expires_at = (
|
|
||||||
timezone.now() + timedelta(hours=ttl_hours) if ttl_hours else None
|
|
||||||
)
|
|
||||||
self.save(update_fields=["data", "expires_at", "updated_at"])
|
|
||||||
else:
|
|
||||||
# Object not found in Odoo, mark as expired immediately
|
|
||||||
self.data = {}
|
|
||||||
self.expires_at = timezone.now()
|
|
||||||
self.save(update_fields=["data", "expires_at", "updated_at"])
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from django.core.validators import MinValueValidator
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from servala.core.models.mixins import ServalaModelMixin
|
|
||||||
|
|
||||||
|
|
||||||
class ComputePlan(ServalaModelMixin):
|
|
||||||
"""
|
|
||||||
Compute resource plans for service instances.
|
|
||||||
|
|
||||||
Defines CPU and memory allocations. Pricing and service level are configured
|
|
||||||
per assignment to a ControlPlaneCRD.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
verbose_name=_("Name"),
|
|
||||||
)
|
|
||||||
description = models.TextField(
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("Description"),
|
|
||||||
)
|
|
||||||
is_active = models.BooleanField(
|
|
||||||
default=True,
|
|
||||||
verbose_name=_("Is active"),
|
|
||||||
help_text=_("Whether this plan is available for selection"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Kubernetes resource specifications (use Kubernetes format: "2Gi", "500m")
|
|
||||||
memory_requests = models.CharField(
|
|
||||||
max_length=20,
|
|
||||||
verbose_name=_("Memory requests"),
|
|
||||||
help_text=_("e.g., '2Gi', '512Mi'"),
|
|
||||||
)
|
|
||||||
memory_limits = models.CharField(
|
|
||||||
max_length=20,
|
|
||||||
verbose_name=_("Memory limits"),
|
|
||||||
help_text=_("e.g., '4Gi', '1Gi'"),
|
|
||||||
)
|
|
||||||
cpu_requests = models.CharField(
|
|
||||||
max_length=20,
|
|
||||||
verbose_name=_("CPU requests"),
|
|
||||||
help_text=_("e.g., '500m', '1', '2'"),
|
|
||||||
)
|
|
||||||
cpu_limits = models.CharField(
|
|
||||||
max_length=20,
|
|
||||||
verbose_name=_("CPU limits"),
|
|
||||||
help_text=_("e.g., '2000m', '2', '4'"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Compute Plan")
|
|
||||||
verbose_name_plural = _("Compute Plans")
|
|
||||||
ordering = ["name"]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_resource_summary(self):
|
|
||||||
"""
|
|
||||||
Get a human-readable summary of resources.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
String like "2 vCPU, 4Gi RAM"
|
|
||||||
"""
|
|
||||||
return f"{self.cpu_limits} vCPU, {self.memory_limits} RAM"
|
|
||||||
|
|
||||||
|
|
||||||
class ComputePlanAssignment(ServalaModelMixin):
|
|
||||||
"""
|
|
||||||
Links compute plans to control plane CRDs with pricing and service level.
|
|
||||||
|
|
||||||
A product in Odoo represents a service with a specific compute plan, control plane,
|
|
||||||
and SLA. This model stores that correlation. The same compute plan can be assigned
|
|
||||||
multiple times to the same CRD with different SLAs and pricing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
SLA_CHOICES = [
|
|
||||||
("besteffort", _("Best Effort")),
|
|
||||||
("guaranteed", _("Guaranteed Availability")),
|
|
||||||
]
|
|
||||||
|
|
||||||
compute_plan = models.ForeignKey(
|
|
||||||
ComputePlan,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="assignments",
|
|
||||||
verbose_name=_("Compute plan"),
|
|
||||||
)
|
|
||||||
control_plane_crd = models.ForeignKey(
|
|
||||||
"ControlPlaneCRD",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="compute_plan_assignments",
|
|
||||||
verbose_name=_("Control plane CRD"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Service Level Agreement
|
|
||||||
sla = models.CharField(
|
|
||||||
max_length=20,
|
|
||||||
choices=SLA_CHOICES,
|
|
||||||
verbose_name=_("SLA"),
|
|
||||||
help_text=_("Service Level Agreement"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Odoo product reference
|
|
||||||
odoo_product_id = models.IntegerField(
|
|
||||||
verbose_name=_("Odoo product ID"),
|
|
||||||
help_text=_("ID of the product in Odoo (product.product or product.template)"),
|
|
||||||
)
|
|
||||||
odoo_unit_id = models.IntegerField(
|
|
||||||
verbose_name=_("Odoo unit ID"),
|
|
||||||
help_text=_("ID of the unit of measure in Odoo (uom.uom)"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Pricing
|
|
||||||
price = models.DecimalField(
|
|
||||||
max_digits=10,
|
|
||||||
decimal_places=2,
|
|
||||||
validators=[MinValueValidator(Decimal("0.00"))],
|
|
||||||
verbose_name=_("Price"),
|
|
||||||
help_text=_("Price per unit"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Service constraints
|
|
||||||
minimum_service_size = models.PositiveIntegerField(
|
|
||||||
default=1,
|
|
||||||
validators=[MinValueValidator(1)],
|
|
||||||
verbose_name=_("Minimum service size"),
|
|
||||||
help_text=_(
|
|
||||||
"Minimum value for spec.parameters.instances "
|
|
||||||
"(Guaranteed Availability may require multiple instances)"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Display ordering in UI
|
|
||||||
sort_order = models.PositiveIntegerField(
|
|
||||||
default=0,
|
|
||||||
verbose_name=_("Sort order"),
|
|
||||||
help_text=_("Order in which plans are displayed to users"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Allow per-assignment activation
|
|
||||||
is_active = models.BooleanField(
|
|
||||||
default=True,
|
|
||||||
verbose_name=_("Is active"),
|
|
||||||
help_text=_("Whether this plan is available for this CRD"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Compute Plan Assignment")
|
|
||||||
verbose_name_plural = _("Compute Plan Assignments")
|
|
||||||
unique_together = [["compute_plan", "control_plane_crd", "sla"]]
|
|
||||||
ordering = ["sort_order", "compute_plan__name", "sla"]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.compute_plan.name} ({self.get_sla_display()}) → {self.control_plane_crd}"
|
|
||||||
|
|
||||||
def get_odoo_reporting_product_id(self):
|
|
||||||
"""
|
|
||||||
Get the reporting product ID for this plan.
|
|
||||||
|
|
||||||
In the future, this will query Odoo based on invoicing policy.
|
|
||||||
For now, returns the product ID directly.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The Odoo product ID to use for billing
|
|
||||||
"""
|
|
||||||
# TODO: Implement Odoo cache lookup when OdooObjectCache is integrated
|
|
||||||
# For now, just return the product ID
|
|
||||||
return self.odoo_product_id
|
|
||||||
|
|
@ -170,28 +170,6 @@ class ControlPlane(ServalaModelMixin, models.Model):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Storage plan configuration (hardcoded per control plane)
|
|
||||||
storage_plan_odoo_product_id = models.IntegerField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("Storage plan Odoo product ID"),
|
|
||||||
help_text=_("ID of the storage product in Odoo"),
|
|
||||||
)
|
|
||||||
storage_plan_odoo_unit_id = models.IntegerField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("Storage plan Odoo unit ID"),
|
|
||||||
help_text=_("ID of the unit of measure in Odoo (uom.uom)"),
|
|
||||||
)
|
|
||||||
storage_plan_price_per_gib = models.DecimalField(
|
|
||||||
max_digits=10,
|
|
||||||
decimal_places=2,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("Storage plan price per GiB"),
|
|
||||||
help_text=_("Price per GiB of storage"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Control plane")
|
verbose_name = _("Control plane")
|
||||||
verbose_name_plural = _("Control planes")
|
verbose_name_plural = _("Control planes")
|
||||||
|
|
@ -635,15 +613,6 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
||||||
related_name="service_instances",
|
related_name="service_instances",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
compute_plan_assignment = models.ForeignKey(
|
|
||||||
to="core.ComputePlanAssignment",
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
related_name="instances",
|
|
||||||
verbose_name=_("Compute plan assignment"),
|
|
||||||
help_text=_("Compute plan with SLA for this instance"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Service instance")
|
verbose_name = _("Service instance")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue