Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Cypress Tests

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

Expand Down
83 changes: 65 additions & 18 deletions backend/api/serializers/model_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
from django.conf import settings
from django.db.models import Max, Min, Sum
from drf_spectacular.utils import extend_schema_serializer
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from metering_billing.invoice import (
generate_balance_adjustment_invoice,
generate_invoice,
Expand All @@ -25,6 +22,7 @@
Feature,
Invoice,
InvoiceLineItem,
InvoiceLineItemAdjustment,
Metric,
NumericFilter,
Organization,
Expand Down Expand Up @@ -67,7 +65,8 @@
USAGE_BEHAVIOR,
USAGE_BILLING_BEHAVIOR,
)

from rest_framework import serializers
from rest_framework.exceptions import ValidationError

SVIX_CONNECTOR = settings.SVIX_CONNECTOR
logger = logging.getLogger("django.server")
Expand Down Expand Up @@ -318,6 +317,39 @@ class Meta:
)


class InvoiceLineItemAdjustmentSerializer(
ConvertEmptyStringToNullMixin, TimezoneFieldMixin, serializers.ModelSerializer
):
class Meta:
model = InvoiceLineItemAdjustment
fields = (
"amount",
"account",
"adjustment_type",
)

adjustment_type = serializers.SerializerMethodField()
account = serializers.SerializerMethodField()
amount = serializers.DecimalField(
max_digits=20, decimal_places=10, coerce_to_string=True
)

def get_adjustment_type(
self, obj
) -> serializers.ChoiceField(
choices=InvoiceLineItemAdjustment.AdjustmentType.labels
):
return obj.get_adjustment_type_display()

def get_account(self, obj) -> str:
return str(obj.account)


@extend_schema_serializer(
deprecate_fields=[
"subtotal",
]
)
class InvoiceLineItemSerializer(
ConvertEmptyStringToNullMixin, TimezoneFieldMixin, serializers.ModelSerializer
):
Expand All @@ -328,26 +360,46 @@ class Meta:
"start_date",
"end_date",
"quantity",
"subtotal",
"billing_type",
"metadata",
"plan",
"subscription_filters",
# amounts
"base",
"adjustments",
"amount",
# deprecated
"subtotal",
)
extra_kwargs = {
"name": {"required": True},
"start_date": {"required": True},
"end_date": {"required": True},
"quantity": {"required": True},
"subtotal": {"required": True},
"billing_type": {"required": True, "allow_blank": False},
"metadata": {"required": True},
"plan": {"required": True, "allow_null": True},
"subscription_filters": {"required": True, "allow_null": True},
# amounts
"base": {"required": True},
"adjustments": {"required": True},
"amount": {"required": True},
# deprecated
"subtotal": {"required": True},
}

plan = serializers.SerializerMethodField(allow_null=True)
subscription_filters = serializers.SerializerMethodField(allow_null=True)
subtotal = serializers.DecimalField(
max_digits=20,
decimal_places=10,
source="base",
)
adjustments = serializers.SerializerMethodField()

def get_adjustments(self, obj) -> InvoiceLineItemAdjustmentSerializer(many=True):
return InvoiceLineItemAdjustmentSerializer(
obj.adjustments.all(), many=True
).data

def get_subscription_filters(
self, obj
Expand Down Expand Up @@ -389,6 +441,7 @@ def get_address(self, obj) -> AddressSerializer(allow_null=True, required=False)
return None


@extend_schema_serializer(deprecate_fields=("cost_due",))
class InvoiceSerializer(
ConvertEmptyStringToNullMixin, TimezoneFieldMixin, serializers.ModelSerializer
):
Expand All @@ -398,6 +451,7 @@ class Meta:
"invoice_id",
"invoice_number",
"cost_due",
"amount",
"currency",
"issue_date",
"payment_status",
Expand All @@ -415,6 +469,7 @@ class Meta:
"invoice_id": {"required": True, "read_only": True},
"invoice_number": {"required": True, "read_only": True},
"cost_due": {"required": True, "read_only": True},
"amount": {"required": True, "read_only": True},
"issue_date": {"required": True, "read_only": True},
"payment_status": {"required": True, "read_only": True},
"due_date": {"required": True, "allow_null": True, "read_only": True},
Expand Down Expand Up @@ -450,6 +505,9 @@ class Meta:
end_date = serializers.SerializerMethodField()
seller = SellerSerializer(source="organization")
payment_status = serializers.SerializerMethodField()
cost_due = serializers.DecimalField(
max_digits=20, decimal_places=10, min_value=0, source="amount"
)

def get_payment_status(
self, obj
Expand Down Expand Up @@ -2187,14 +2245,3 @@ def create(self, validated_data):
if invoice_now:
generate_invoice(sr)
return sr
return sr
return sr
return sr
return sr
return sr
return sr
return sr
return sr
return sr
return sr
return sr
96 changes: 2 additions & 94 deletions backend/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@
)
from metering_billing.webhooks import (
customer_created_webhook,
subscription_created_webhook,
subscription_cancelled_webhook,
subscription_created_webhook,
)

POSTHOG_PERSON = settings.POSTHOG_PERSON
Expand Down Expand Up @@ -243,7 +243,7 @@ def get_queryset(self):
)
qs = qs.annotate(
total_amount_due=Sum(
"invoices__cost_due",
"invoices__amount",
filter=Q(invoices__payment_status=Invoice.PaymentStatus.UNPAID),
output_field=DecimalField(),
)
Expand Down Expand Up @@ -302,98 +302,6 @@ def archive(self, request, customer_id=None):
CustomerDeleteResponseSerializer().validate(return_data)
return Response(return_data, status=status.HTTP_200_OK)

# @extend_schema(
# request=inline_serializer(
# name="CustomerBatchCreateRequest",
# fields={
# "customers": CustomerCreateSerializer(many=True),
# "behavior_on_existing": serializers.ChoiceField(
# choices=["merge", "ignore", "overwrite"],
# help_text="Determines what to do if a customer with the same email or customer_id already exists. Ignore skips, merge merges the existing customer with the new customer, and overwrite overwrites the existing customer with the new customer.",
# ),
# },
# ),
# responses={
# 201: inline_serializer(
# name="CustomerBatchCreateSuccess",
# fields={
# "success": serializers.ChoiceField(choices=["all", "some"]),
# "failed_customers": serializers.DictField(
# required=False,
# help_text="Returns the customers that failed to be created, if any, in the same format as the request.",
# ),
# },
# ),
# 400: inline_serializer(
# name="CustomerBatchCreateFailure",
# fields={
# "success": serializers.ChoiceField(choices=["none"]),
# "failed_customers": serializers.DictField(
# help_text="Returns the customers that failed to be created in the same format as the request."
# ),
# },
# ),
# },
# )
# @action(detail=False, methods=["post"])
# def batch(self, request, format=None):
# organization = request.organization
# serializer = CustomerCreateSerializer(
# data=request.data["customers"],
# many=True,
# context={"organization": organization},
# )
# serializer.is_valid(raise_exception=True)
# failed_customers = {}
# behavior = request.data.get("behavior_on_existing", "merge")
# for customer in serializer.validated_data:
# try:
# match = Customer.objects.filter(
# Q(email=customer["email"]) | Q(customer_id=customer["customer_id"]),
# organization=organization,
# )
# if match.exists():
# match = match.first()
# if behavior == "ignore":
# pass
# else:
# if "customer_id" in customer:
# non_unique_id = Customer.objects.filter(
# ~Q(pk=match.pk), customer_id=customer["customer_id"]
# ).exists()
# if non_unique_id:
# failed_customers[
# customer["customer_id"]
# ] = "customer_id already exists"
# continue
# CustomerUpdateSerializer().update(
# match, customer, behavior=behavior
# )
# else:
# customer["organization"] = organization
# CustomerCreateSerializer().create(customer)
# except Exception as e:
# identifier = customer.get("customer_id", customer.get("email"))
# failed_customers[identifier] = str(e)

# if len(failed_customers) == 0 or len(failed_customers) < len(
# serializer.validated_data
# ):
# return Response(
# {
# "success": "all" if len(failed_customers) == 0 else "some",
# "failed_customers": failed_customers,
# },
# status=status.HTTP_201_CREATED,
# )
# return Response(
# {
# "success": "none",
# "failed_customers": failed_customers,
# },
# status=status.HTTP_400_BAD_REQUEST,
# )

def perform_create(self, serializer):
try:
return serializer.save(organization=self.request.organization)
Expand Down
Loading