feat: added creation endpoints
This commit is contained in:
@@ -9,4 +9,5 @@ urlpatterns = [
|
|||||||
"swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"
|
"swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"
|
||||||
),
|
),
|
||||||
path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
||||||
|
path("shop/", include("shop.api.v1.routers")),
|
||||||
]
|
]
|
||||||
|
|||||||
+10
-1
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from shop.models import Product, ProductPrice, OrderLine, Order, Tax
|
from shop.models import Product, ProductPrice, OrderLine, Order, Tax, Customer
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
|
||||||
@@ -35,6 +35,15 @@ class TaxAdmin(ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Customer)
|
||||||
|
class CustomerAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"vat_id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(OrderLine)
|
@admin.register(OrderLine)
|
||||||
class OrderLineAdmin(ModelAdmin):
|
class OrderLineAdmin(ModelAdmin):
|
||||||
autocomplete_fields = ("product",)
|
autocomplete_fields = ("product",)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from shop.api.v1.viewsets import (
|
||||||
|
TaxViewSet,
|
||||||
|
CustomerViewSet,
|
||||||
|
ProductViewSet,
|
||||||
|
ProductPriceViewSet,
|
||||||
|
OrderViewSet,
|
||||||
|
OrderLineViewSet,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
|
||||||
|
router.register("taxes", TaxViewSet)
|
||||||
|
router.register("customers", CustomerViewSet)
|
||||||
|
router.register("product", ProductViewSet)
|
||||||
|
router.register("product-prices", ProductPriceViewSet)
|
||||||
|
router.register("orders", OrderViewSet)
|
||||||
|
router.register("orders/(?P<order_id>[^/.]+)/lines", OrderLineViewSet)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from shop.models import Product, ProductPrice, Customer, Tax, OrderLine, Order
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = (
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"stock",
|
||||||
|
"unit",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductPriceSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ProductPrice
|
||||||
|
fields = (
|
||||||
|
"price",
|
||||||
|
"date",
|
||||||
|
"product",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Customer
|
||||||
|
fields = (
|
||||||
|
"vat_id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"email",
|
||||||
|
"address",
|
||||||
|
"city",
|
||||||
|
"state",
|
||||||
|
"country",
|
||||||
|
"zip",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TaxSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Tax
|
||||||
|
fields = (
|
||||||
|
"code",
|
||||||
|
"value",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Order
|
||||||
|
fields = (
|
||||||
|
"uuid",
|
||||||
|
"creation_date",
|
||||||
|
"last_modification_date",
|
||||||
|
"base_total",
|
||||||
|
"total",
|
||||||
|
"customer",
|
||||||
|
"billing_address",
|
||||||
|
"billing_city",
|
||||||
|
"billing_state",
|
||||||
|
"billing_country",
|
||||||
|
"billing_zip",
|
||||||
|
"shipping_address",
|
||||||
|
"shipping_city",
|
||||||
|
"shipping_state",
|
||||||
|
"shipping_country",
|
||||||
|
"shipping_zip",
|
||||||
|
"contact_phone",
|
||||||
|
)
|
||||||
|
read_only_fields = (
|
||||||
|
"uuid",
|
||||||
|
"base_total",
|
||||||
|
"total",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderLineSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = OrderLine
|
||||||
|
fields = (
|
||||||
|
"product",
|
||||||
|
"quantity",
|
||||||
|
"order",
|
||||||
|
"price",
|
||||||
|
"base_total",
|
||||||
|
"total",
|
||||||
|
"taxes",
|
||||||
|
"tax_value",
|
||||||
|
)
|
||||||
|
read_only_fields = (
|
||||||
|
"price",
|
||||||
|
"base_total",
|
||||||
|
"total",
|
||||||
|
"taxes",
|
||||||
|
"tax_value",
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from shop.api.v1.serializers import (
|
||||||
|
ProductSerializer,
|
||||||
|
ProductPriceSerializer,
|
||||||
|
CustomerSerializer,
|
||||||
|
TaxSerializer,
|
||||||
|
OrderSerializer,
|
||||||
|
OrderLineSerializer,
|
||||||
|
)
|
||||||
|
from shop.models import Product, ProductPrice, Customer, Tax, Order, OrderLine
|
||||||
|
|
||||||
|
|
||||||
|
class ProductViewSet(ModelViewSet):
|
||||||
|
serializer_class = ProductSerializer
|
||||||
|
queryset = Product.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ProductPriceViewSet(ModelViewSet):
|
||||||
|
serializer_class = ProductPriceSerializer
|
||||||
|
queryset = ProductPrice.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerViewSet(ModelViewSet):
|
||||||
|
serializer_class = CustomerSerializer
|
||||||
|
queryset = Customer.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class TaxViewSet(ModelViewSet):
|
||||||
|
serializer_class = TaxSerializer
|
||||||
|
queryset = Tax.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class OrderViewSet(ModelViewSet):
|
||||||
|
serializer_class = OrderSerializer
|
||||||
|
queryset = Order.objects.all()
|
||||||
|
lookup_field = "uuid"
|
||||||
|
|
||||||
|
|
||||||
|
class OrderLineViewSet(ModelViewSet):
|
||||||
|
serializer_class = OrderLineSerializer
|
||||||
|
queryset = OrderLine.objects.all()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(order_id=self.kwargs.get("order_id"))
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 5.0.3 on 2024-03-24 18:23
|
# Generated by Django 5.0.3 on 2024-03-24 22:50
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
import uuid
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -50,64 +51,6 @@ class Migration(migrations.Migration):
|
|||||||
"verbose_name_plural": "Clientes",
|
"verbose_name_plural": "Clientes",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name="OrderLine",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"quantity",
|
|
||||||
models.DecimalField(
|
|
||||||
decimal_places=4,
|
|
||||||
default=Decimal("1"),
|
|
||||||
max_digits=13,
|
|
||||||
verbose_name="Cantidad",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"price",
|
|
||||||
models.DecimalField(
|
|
||||||
decimal_places=4, max_digits=13, verbose_name="Precio"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"base_total",
|
|
||||||
models.DecimalField(
|
|
||||||
decimal_places=4,
|
|
||||||
default=Decimal("0"),
|
|
||||||
max_digits=13,
|
|
||||||
verbose_name="Total sin impuestos",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"tax_value",
|
|
||||||
models.PositiveIntegerField(verbose_name="Valor de impuestos"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"taxes",
|
|
||||||
models.DecimalField(
|
|
||||||
decimal_places=4, max_digits=13, verbose_name="Impuestos"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"total",
|
|
||||||
models.DecimalField(
|
|
||||||
decimal_places=4, max_digits=13, verbose_name="Total"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Línea de pedido",
|
|
||||||
"verbose_name_plural": "Líneas de pedido",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Product",
|
name="Product",
|
||||||
fields=[
|
fields=[
|
||||||
@@ -196,6 +139,7 @@ class Migration(migrations.Migration):
|
|||||||
verbose_name="ID",
|
verbose_name="ID",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
("uuid", models.UUIDField(default=uuid.uuid4, verbose_name="UUID")),
|
||||||
(
|
(
|
||||||
"creation_date",
|
"creation_date",
|
||||||
models.DateTimeField(
|
models.DateTimeField(
|
||||||
@@ -288,25 +232,87 @@ class Migration(migrations.Migration):
|
|||||||
verbose_name="Cliente",
|
verbose_name="Cliente",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
|
||||||
"lines",
|
|
||||||
models.ManyToManyField(to="shop.orderline", verbose_name="Líneas"),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
"verbose_name": "Pedido",
|
"verbose_name": "Pedido",
|
||||||
"verbose_name_plural": "Pedidos",
|
"verbose_name_plural": "Pedidos",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.CreateModel(
|
||||||
model_name="orderline",
|
name="OrderLine",
|
||||||
name="product",
|
fields=[
|
||||||
field=models.ForeignKey(
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"quantity",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=4,
|
||||||
|
default=Decimal("1"),
|
||||||
|
max_digits=13,
|
||||||
|
verbose_name="Cantidad",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"price",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=4, max_digits=13, verbose_name="Precio"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"base_total",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=4,
|
||||||
|
default=Decimal("0"),
|
||||||
|
max_digits=13,
|
||||||
|
verbose_name="Total sin impuestos",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tax_value",
|
||||||
|
models.PositiveIntegerField(verbose_name="Valor de impuestos"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"taxes",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=4, max_digits=13, verbose_name="Impuestos"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"total",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=4, max_digits=13, verbose_name="Total"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"order",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="lines",
|
||||||
|
to="shop.order",
|
||||||
|
verbose_name="Pedido",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"product",
|
||||||
|
models.ForeignKey(
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
to="shop.product",
|
to="shop.product",
|
||||||
verbose_name="Producto",
|
verbose_name="Producto",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Línea de pedido",
|
||||||
|
"verbose_name_plural": "Líneas de pedido",
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="ProductPrice",
|
name="ProductPrice",
|
||||||
fields=[
|
fields=[
|
||||||
|
|||||||
+8
-1
@@ -1,4 +1,5 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@@ -108,6 +109,12 @@ class Customer(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class OrderLine(models.Model):
|
class OrderLine(models.Model):
|
||||||
|
order = models.ForeignKey(
|
||||||
|
"shop.Order",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="lines",
|
||||||
|
verbose_name=_("Pedido"),
|
||||||
|
)
|
||||||
product = models.ForeignKey(
|
product = models.ForeignKey(
|
||||||
"shop.Product",
|
"shop.Product",
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@@ -163,7 +170,7 @@ class OrderLine(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
lines = models.ManyToManyField("shop.OrderLine", verbose_name=_("Líneas"))
|
uuid = models.UUIDField(default=uuid4, verbose_name=_("UUID"), db_index=True)
|
||||||
creation_date = models.DateTimeField(
|
creation_date = models.DateTimeField(
|
||||||
auto_now_add=True, verbose_name=_("Fecha de creación")
|
auto_now_add=True, verbose_name=_("Fecha de creación")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from rest_framework.test import APITestCase as TestCase
|
from rest_framework.test import APITestCase as TestCase
|
||||||
from shop.models import Product, ProductPrice, OrderLine, Tax, Customer
|
from shop.models import Product, ProductPrice, Tax, Customer
|
||||||
from shop.utils import create_order_line_for_product, create_order
|
from shop.utils import create_order_line_for_product, create_order
|
||||||
|
|
||||||
|
|
||||||
@@ -45,25 +45,8 @@ class ShopModelsTest(TestCase):
|
|||||||
country="Tatooine",
|
country="Tatooine",
|
||||||
)
|
)
|
||||||
|
|
||||||
l1 = create_order_line_for_product(
|
|
||||||
self.potatoes,
|
|
||||||
quantity=Decimal("1.5"),
|
|
||||||
tax=self.tax,
|
|
||||||
)
|
|
||||||
l2 = create_order_line_for_product(
|
|
||||||
self.gasoline,
|
|
||||||
quantity=Decimal("40"),
|
|
||||||
tax=self.tax,
|
|
||||||
)
|
|
||||||
l3 = create_order_line_for_product(
|
|
||||||
self.usb_c,
|
|
||||||
quantity=Decimal("1.00"),
|
|
||||||
tax=self.tax,
|
|
||||||
)
|
|
||||||
|
|
||||||
order = create_order(
|
order = create_order(
|
||||||
customer=self.customer,
|
customer=self.customer,
|
||||||
lines=OrderLine.objects.all(),
|
|
||||||
billing_address=self.customer.address,
|
billing_address=self.customer.address,
|
||||||
billing_city=self.customer.city,
|
billing_city=self.customer.city,
|
||||||
billing_state=self.customer.state,
|
billing_state=self.customer.state,
|
||||||
@@ -71,5 +54,25 @@ class ShopModelsTest(TestCase):
|
|||||||
billing_country=self.customer.country,
|
billing_country=self.customer.country,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
l1 = create_order_line_for_product(
|
||||||
|
self.potatoes,
|
||||||
|
quantity=Decimal("1.5"),
|
||||||
|
tax=self.tax,
|
||||||
|
order=order,
|
||||||
|
)
|
||||||
|
l2 = create_order_line_for_product(
|
||||||
|
self.gasoline,
|
||||||
|
quantity=Decimal("40"),
|
||||||
|
tax=self.tax,
|
||||||
|
order=order,
|
||||||
|
)
|
||||||
|
l3 = create_order_line_for_product(
|
||||||
|
self.usb_c,
|
||||||
|
quantity=Decimal("1.00"),
|
||||||
|
tax=self.tax,
|
||||||
|
order=order,
|
||||||
|
)
|
||||||
|
order.calculate_total_from_lines()
|
||||||
|
|
||||||
assert order.total == l1.total + l2.total + l3.total
|
assert order.total == l1.total + l2.total + l3.total
|
||||||
assert order.base_total == l1.base_total + l2.base_total + l3.base_total
|
assert order.base_total == l1.base_total + l2.base_total + l3.base_total
|
||||||
|
|||||||
+4
-6
@@ -5,12 +5,15 @@ from shop.models import OrderLine, Tax, Product, Order, Customer
|
|||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
|
||||||
def create_order_line_for_product(product: Product, quantity: Decimal, tax: Tax):
|
def create_order_line_for_product(
|
||||||
|
product: Product, quantity: Decimal, tax: Tax, order: Order
|
||||||
|
):
|
||||||
price = product.prices.last().price
|
price = product.prices.last().price
|
||||||
base_total = round(price * quantity, 2)
|
base_total = round(price * quantity, 2)
|
||||||
taxes = round(base_total * (tax.value / Decimal("100")), 2)
|
taxes = round(base_total * (tax.value / Decimal("100")), 2)
|
||||||
|
|
||||||
return OrderLine.objects.create(
|
return OrderLine.objects.create(
|
||||||
|
order=order,
|
||||||
product=product,
|
product=product,
|
||||||
quantity=quantity,
|
quantity=quantity,
|
||||||
price=price,
|
price=price,
|
||||||
@@ -23,7 +26,6 @@ def create_order_line_for_product(product: Product, quantity: Decimal, tax: Tax)
|
|||||||
|
|
||||||
def create_order(
|
def create_order(
|
||||||
customer: Customer,
|
customer: Customer,
|
||||||
lines: Iterable,
|
|
||||||
billing_address: str,
|
billing_address: str,
|
||||||
billing_city: str,
|
billing_city: str,
|
||||||
billing_state: str,
|
billing_state: str,
|
||||||
@@ -61,8 +63,4 @@ def create_order(
|
|||||||
shipping_zip=shipping_zip,
|
shipping_zip=shipping_zip,
|
||||||
)
|
)
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
order.lines.add(line)
|
|
||||||
|
|
||||||
order.calculate_total_from_lines()
|
|
||||||
return order
|
return order
|
||||||
|
|||||||
Reference in New Issue
Block a user