From f7e8ea725e1f00e67922c5cf7b10d9b45874f336 Mon Sep 17 00:00:00 2001 From: lucaskmpz Date: Sat, 24 Jan 2026 11:40:07 -0300 Subject: [PATCH 1/5] feat: adding unit_price DecimalField and helpers, and removing conflicting property so Django can assing the field properly --- entities/models.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/entities/models.py b/entities/models.py index 0f55adb..a6c79fb 100644 --- a/entities/models.py +++ b/entities/models.py @@ -1,10 +1,23 @@ from django.db import models +from decimal import Decimal, ROUND_DOWN + class Entity(models.Model): type = models.CharField(max_length=100) name = models.CharField(max_length=255, blank=True, null=True) description = models.TextField(blank=True, null=True) + # store unit price as Decimal with 4 decimal places to preserve precision from measurements + unit_price = models.DecimalField(max_digits=12, decimal_places=4, default=Decimal('0.0000')) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"{self.type}: {self.name or self.pk}" \ No newline at end of file + return f"{self.type}: {self.name or self.pk}" + + def unit_price_float(self) -> float: + """Return unit price as float for convenience (not used for storage).""" + return float(self.unit_price or Decimal('0')) + + def price_cents_truncated(self) -> int: + """Return price in cents after truncating to 2 decimal places (no rounding).""" + val = (Decimal(self.unit_price or Decimal('0'))).quantize(Decimal('0.01'), rounding=ROUND_DOWN) + return int((val * 100).to_integral_value()) \ No newline at end of file From c29461d84717afbe14fc462f38b1d1c8403c4bec Mon Sep 17 00:00:00 2001 From: lucaskmpz Date: Sat, 24 Jan 2026 11:42:05 -0300 Subject: [PATCH 2/5] feat: switching API schemas to use Decimal for unit_price --- entities/schemas.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/entities/schemas.py b/entities/schemas.py index 4b505fe..77a44c3 100644 --- a/entities/schemas.py +++ b/entities/schemas.py @@ -1,11 +1,14 @@ from pydantic import BaseModel, constr from typing import Optional from datetime import datetime +from decimal import Decimal class EntityBase(BaseModel): type: constr(max_length=100) name: Optional[constr(max_length=255)] = None description: Optional[str] = None + # store and accept unit_price with Decimal precision; API accepts float/str and will be parsed + unit_price: Optional[Decimal] = None model_config = {"from_attributes": True} @@ -16,9 +19,12 @@ class EntityUpdate(BaseModel): type: Optional[constr(max_length=100)] = None name: Optional[constr(max_length=255)] = None description: Optional[str] = None + unit_price: Optional[Decimal] = None model_config = {"from_attributes": True} class EntityOut(EntityBase): id: int - created_at: datetime \ No newline at end of file + created_at: datetime + # unit_price returned as Decimal (serialized by Pydantic) + unit_price: Decimal \ No newline at end of file From 82c317a427aa5a77e73c33ad529718c02d6e41e5 Mon Sep 17 00:00:00 2001 From: lucaskmpz Date: Sat, 24 Jan 2026 11:42:52 -0300 Subject: [PATCH 3/5] feat: parse and normalize unit_price on create/update, quantize to 4 decimal places and persist to the model; preserve filtering helpers for list endpoint --- entities/services.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/entities/services.py b/entities/services.py index 79745f6..4acef86 100644 --- a/entities/services.py +++ b/entities/services.py @@ -1,6 +1,7 @@ from typing import Dict, Any from django.db import transaction from .models import Entity +from decimal import Decimal, InvalidOperation, ROUND_DOWN def list_entities( name: str = None, @@ -35,11 +36,32 @@ def get_entity(entity_id: int) -> Entity: return Entity.objects.get(pk=entity_id) def create_entity(data: Dict[str, Any]) -> Entity: + # accept `unit_price` (e.g. 50.99) and store as Decimal with 4 decimal places + unit = data.pop('unit_price', None) + if unit is not None: + # accept both 50.99 and '50,99' by normalizing comma to dot + unit_str = str(unit).replace(',', '.') + try: + d = Decimal(unit_str) + except (InvalidOperation, ValueError): + d = Decimal(float(unit_str)) + # store with 4 decimal places to preserve measurement precision, truncate (ROUND_DOWN) + data['unit_price'] = d.quantize(Decimal('0.0001'), rounding=ROUND_DOWN) with transaction.atomic(): return Entity.objects.create(**data) def update_entity(entity_id: int, data: Dict[str, Any]) -> Entity: with transaction.atomic(): + # support updating via `unit_price` as well + unit = data.pop('unit_price', None) + if unit is not None: + unit_str = str(unit).replace(',', '.') + try: + d = Decimal(unit_str) + except (InvalidOperation, ValueError): + d = Decimal(float(unit_str)) + data['unit_price'] = d.quantize(Decimal('0.0001'), rounding=ROUND_DOWN) + obj = Entity.objects.get(pk=entity_id) for k, v in data.items(): setattr(obj, k, v) From 949a16921c9561b3b9d347b4ae4c0525ba60acab Mon Sep 17 00:00:00 2001 From: lucaskmpz Date: Sat, 24 Jan 2026 11:43:16 -0300 Subject: [PATCH 4/5] Adding price_cents integer field migration - initial DB change before migrating values to unit_price --- entities/migrations/0002_entity_price_cents.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 entities/migrations/0002_entity_price_cents.py diff --git a/entities/migrations/0002_entity_price_cents.py b/entities/migrations/0002_entity_price_cents.py new file mode 100644 index 0000000..bb97e0e --- /dev/null +++ b/entities/migrations/0002_entity_price_cents.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.1 on 2026-01-24 03:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('entities', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='entity', + name='price_cents', + field=models.IntegerField(default=0), + ), + ] From bf88404647cc82317779110416ad314292da780e Mon Sep 17 00:00:00 2001 From: lucaskmpz Date: Sat, 24 Jan 2026 11:43:45 -0300 Subject: [PATCH 5/5] add migration to create unit_price DecimalField, migrate existing price_cents values into unit_price, and remove the price_cents field --- entities/migrations/0003_entity_unit_price.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 entities/migrations/0003_entity_unit_price.py diff --git a/entities/migrations/0003_entity_unit_price.py b/entities/migrations/0003_entity_unit_price.py new file mode 100644 index 0000000..55ef7b0 --- /dev/null +++ b/entities/migrations/0003_entity_unit_price.py @@ -0,0 +1,41 @@ +from decimal import Decimal +from django.db import migrations, models + + +def forwards(apps, schema_editor): + Entity = apps.get_model('entities', 'Entity') + for obj in Entity.objects.all(): + # convert existing price_cents (int) to unit_price Decimal + cents = getattr(obj, 'price_cents', None) + if cents is not None: + obj.unit_price = Decimal(cents) / Decimal('100') + obj.save(update_fields=['unit_price']) + + +def backwards(apps, schema_editor): + Entity = apps.get_model('entities', 'Entity') + for obj in Entity.objects.all(): + val = getattr(obj, 'unit_price', None) + if val is not None: + obj.price_cents = int((Decimal(val) * Decimal('100')).to_integral_value()) + obj.save(update_fields=['price_cents']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('entities', '0002_entity_price_cents'), + ] + + operations = [ + migrations.AddField( + model_name='entity', + name='unit_price', + field=models.DecimalField(decimal_places=4, default=Decimal('0.0000'), max_digits=12), + ), + migrations.RunPython(forwards, backwards), + migrations.RemoveField( + model_name='entity', + name='price_cents', + ), + ]