categorias-productos #51 #52

Open
mono wants to merge 13 commits from feat/51-categorias-productos into main
2 changed files with 166 additions and 4 deletions
Showing only changes of commit c0dcb7901a - Show all commits

View File

@@ -214,8 +214,50 @@ class ProductTrytonService:
changes.append(
f"Unidad: {product.measuring_unit} → {unit_name}"
)
category_changes = self._describe_category_changes(
product, tryton_product
)
if category_changes:
changes.append(category_changes)
return ", ".join(changes)
def _describe_category_changes(self, product, tryton_product):
"""Describe los cambios de categorías entre el producto local y Tryton"""
tryton_ids = set(
str(category_id)
for category_id in self._extract_category_ids(tryton_product)
)
local = {
external_id: name
for external_id, name in product.categories.values_list(
"external_id", "name"
)
if external_id
}
local_ids = set(local)
added = sorted(tryton_ids - local_ids)
removed = sorted(local_ids - tryton_ids)
if not added and not removed:
return ""
parts = []
if added:
names = {}
for external_id in added:
category = (
ProductCategory.objects.filter(
external_id=external_id
)
.values_list("name", flat=True)
.first()
)
names[external_id] = category or external_id
parts.append(", ".join(f"+{names[e]}" for e in added))
if removed:
parts.append(", ".join(f"-{local[e]}" for e in removed))
return f"Categoría: {', '.join(parts)}"
@staticmethod
def _format_price(value):
"""Formatea un precio sin ceros decimales innecesarios"""
@@ -269,6 +311,17 @@ class ProductTrytonService:
unit = tryton_product.get("default_uom.")
if not product.measuring_unit == unit.get("rec_name"):
return True
tryton_category_ids = set(
str(category_id)
for category_id in self._extract_category_ids(tryton_product)
)
local_category_ids = set(
product.categories.exclude(
external_id__isnull=True
).values_list("external_id", flat=True)
)
if tryton_category_ids != local_category_ids:
return True
return False
def _create_product(self, tryton_product):

View File

@@ -661,7 +661,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
@patch("sabatron_tryton_rpc_client.client.Client.call")
@patch("sabatron_tryton_rpc_client.client.Client.connect")
def test_import_assigns_categories_to_untouched_products(
def test_import_assigns_categories_marks_product_as_updated(
self, mock_connect, mock_call
):
mock_connect.return_value = None
@@ -724,12 +724,20 @@ class TestProductsFromTryton(TestCase, LoginMixin):
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode("utf-8"))
self.assertEqual(content["untouched_products"], [])
self.assertEqual(
content["untouched_products"], [{"id": 2, "name": "Papa"}]
content["updated_products"],
[
{
"id": 2,
"name": "Papa",
"detail": "Categoría: +Tubérculos",
}
],
)
untouched_product = Product.objects.get(id=2)
categories = untouched_product.categories.all()
updated_product = Product.objects.get(id=2)
categories = updated_product.categories.all()
self.assertEqual(len(categories), 1)
self.assertEqual(categories[0].name, "Tubérculos")
self.assertEqual(categories[0].external_id, "9")
@@ -744,3 +752,104 @@ class TestProductsFromTryton(TestCase, LoginMixin):
}
],
)
@patch("sabatron_tryton_rpc_client.client.Client.call")
@patch("sabatron_tryton_rpc_client.client.Client.connect")
def test_category_change_marks_product_as_updated(
self, mock_connect, mock_call
):
mock_connect.return_value = None
local_abarrotes = ProductCategory.objects.create(
name="Abarrotes", external_id="5"
)
self.product2.categories.add(local_abarrotes)
def fake_call(*args, **kwargs):
product_search = "model.product.product.search"
search_args = [
[["salable", "=", True]],
0,
1000,
[["rec_name", "ASC"], ["id", None]],
{"company": 1},
]
if args == (product_search, search_args):
return [192]
product_read = "model.product.product.read"
product_args = (
[192],
[
"id",
"name",
"default_uom.id",
"default_uom.rec_name",
"list_price",
"template.categories",
],
{"company": 1},
)
if args == (product_read, product_args):
return [
{
"id": 192,
"list_price": Decimal("4500"),
"name": "Papa",
"default_uom.": {"id": 2, "rec_name": "Kilogram"},
"template.": {"id": 999, "categories": [8]},
},
]
category_read = "model.product.category.read"
category_args = (
[8],
["id", "name"],
{"company": 1},
)
if args == (category_read, category_args):
return [
{"id": 8, "name": "Bebidas"},
]
raise Exception(
f"Sorry, args non expected on this test: {args}"
)
mock_call.side_effect = fake_call
url = "/don_confiao/api/importar_productos_de_tryton"
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode("utf-8"))
self.assertEqual(content["untouched_products"], [])
self.assertEqual(content["failed_products"], [])
self.assertEqual(
content["updated_products"],
[
{
"id": self.product2.pk,
"name": "Papa",
"detail": "Categoría: +Bebidas, -Abarrotes",
}
],
)
self.assertEqual(
content["created_categories"],
[
{
"id": ProductCategory.objects.get(
external_id="8"
).pk,
"name": "Bebidas",
"external_id": "8",
}
],
)
refreshed_product = Product.objects.get(pk=self.product2.pk)
categories = list(refreshed_product.categories.all())
self.assertEqual(
[(c.external_id, c.name) for c in categories],
[("8", "Bebidas")],
)