#51 refactor: traer categorias anidadas en un solo request de productos
Issue #51: discriminacion y administracion de categorias de productos. - product.read ahora solicita template.categories.name; se elimina la llamada separada model.product.category.read - ProductTrytonService y CategoryTrytonService construyen el mapa {id_tryton: nombre} desde 'template.categories.' de los productos leídos - import_from_external pasa de 3 llamadas RPC a 2
This commit is contained in:
@@ -53,15 +53,14 @@ class CategoryTrytonService:
|
|||||||
return self._empty_report()
|
return self._empty_report()
|
||||||
tryton_products = self._get_product_details(product_ids, context)
|
tryton_products = self._get_product_details(product_ids, context)
|
||||||
|
|
||||||
category_ids = set()
|
category_names = self._extract_category_names(tryton_products)
|
||||||
for tryton_product in tryton_products:
|
if not category_names:
|
||||||
category_ids.update(self._extract_category_ids(tryton_product))
|
|
||||||
if not category_ids:
|
|
||||||
return self._empty_report()
|
return self._empty_report()
|
||||||
|
|
||||||
tryton_categories = self._get_category_details(
|
tryton_categories = [
|
||||||
sorted(category_ids), context
|
{"id": category_id, "name": category_names[category_id]}
|
||||||
)
|
for category_id in sorted(category_names)
|
||||||
|
]
|
||||||
|
|
||||||
checked = [
|
checked = [
|
||||||
{"id": c.get("id"), "name": c.get("name")}
|
{"id": c.get("id"), "name": c.get("name")}
|
||||||
@@ -121,21 +120,27 @@ class CategoryTrytonService:
|
|||||||
return self.client.call(method, params) or []
|
return self.client.call(method, params) or []
|
||||||
|
|
||||||
def _get_product_details(self, product_ids, context):
|
def _get_product_details(self, product_ids, context):
|
||||||
tryton_fields = ["id", "name", "template.categories"]
|
tryton_fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
|
]
|
||||||
method = "model.product.product.read"
|
method = "model.product.product.read"
|
||||||
params = (product_ids, tryton_fields, context)
|
params = (product_ids, tryton_fields, context)
|
||||||
return self.client.call(method, params)
|
return self.client.call(method, params)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_category_ids(tryton_product):
|
def _extract_category_names(tryton_products):
|
||||||
"""Extrae los IDs de categorías de la respuesta RPC de Tryton"""
|
"""Extrae el mapa {id_tryton: nombre} de las categorías anidadas en
|
||||||
|
la respuesta de product.read (clave 'template.categories.name',
|
||||||
|
agrupada bajo 'template.' como 'categories.')"""
|
||||||
|
names = {}
|
||||||
|
for tryton_product in tryton_products:
|
||||||
template = tryton_product.get("template.") or {}
|
template = tryton_product.get("template.") or {}
|
||||||
return template.get("categories") or []
|
for category in template.get("categories.") or []:
|
||||||
|
names[category.get("id")] = category.get("name")
|
||||||
def _get_category_details(self, category_ids, context):
|
return names
|
||||||
method = "model.product.category.read"
|
|
||||||
params = (category_ids, ["id", "name"], context)
|
|
||||||
return self.client.call(method, params)
|
|
||||||
|
|
||||||
def _empty_report(self):
|
def _empty_report(self):
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -107,19 +107,16 @@ class ProductTrytonService:
|
|||||||
reporte {"created": [...], "updated": [...]}
|
reporte {"created": [...], "updated": [...]}
|
||||||
"""
|
"""
|
||||||
empty_report = {"created": [], "updated": []}
|
empty_report = {"created": [], "updated": []}
|
||||||
category_ids = {
|
category_names = self._extract_category_names(tryton_products)
|
||||||
category_id
|
if not category_names:
|
||||||
for tryton_product in tryton_products
|
|
||||||
for category_id in self._extract_category_ids(tryton_product)
|
|
||||||
}
|
|
||||||
if not category_ids:
|
|
||||||
return {}, empty_report
|
return {}, empty_report
|
||||||
|
|
||||||
categories = {}
|
categories = {}
|
||||||
report = {"created": [], "updated": []}
|
report = {"created": [], "updated": []}
|
||||||
tryton_categories = self._get_category_details(
|
tryton_categories = [
|
||||||
sorted(category_ids), context
|
{"id": category_id, "name": category_names[category_id]}
|
||||||
)
|
for category_id in sorted(category_names)
|
||||||
|
]
|
||||||
for tryton_category in tryton_categories:
|
for tryton_category in tryton_categories:
|
||||||
try:
|
try:
|
||||||
category, status, changes = upsert_category(tryton_category)
|
category, status, changes = upsert_category(tryton_category)
|
||||||
@@ -149,11 +146,17 @@ class ProductTrytonService:
|
|||||||
template = tryton_product.get("template.") or {}
|
template = tryton_product.get("template.") or {}
|
||||||
return template.get("categories") or []
|
return template.get("categories") or []
|
||||||
|
|
||||||
def _get_category_details(self, category_ids, context):
|
@staticmethod
|
||||||
"""Obtiene los nombres de categorías desde Tryton"""
|
def _extract_category_names(tryton_products):
|
||||||
method = "model.product.category.read"
|
"""Extrae el mapa {id_tryton: nombre} de las categorías anidadas en
|
||||||
params = (category_ids, ["id", "name"], context)
|
la respuesta de product.read (clave 'template.categories.name',
|
||||||
return self.client.call(method, params)
|
agrupada bajo 'template.' como 'categories.')"""
|
||||||
|
names = {}
|
||||||
|
for tryton_product in tryton_products:
|
||||||
|
template = tryton_product.get("template.") or {}
|
||||||
|
for category in template.get("categories.") or []:
|
||||||
|
names[category.get("id")] = category.get("name")
|
||||||
|
return names
|
||||||
|
|
||||||
def _sync_product_categories(self, product, tryton_product, categories):
|
def _sync_product_categories(self, product, tryton_product, categories):
|
||||||
"""Vincula al producto las categorías importadas desde Tryton"""
|
"""Vincula al producto las categorías importadas desde Tryton"""
|
||||||
@@ -271,6 +274,7 @@ class ProductTrytonService:
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
]
|
]
|
||||||
method = "model.product.product.read"
|
method = "model.product.product.read"
|
||||||
params = (product_ids, tryton_fields, context)
|
params = (product_ids, tryton_fields, context)
|
||||||
|
|||||||
@@ -30,18 +30,21 @@ class TestCategoryTrytonImport(TestCase):
|
|||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"template.": {"id": 999, "categories": [6]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [6],
|
||||||
|
"categories.": [{"id": 6, "name": "Alimentos"}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "Producto 2",
|
"name": "Producto 2",
|
||||||
"template.": {"id": 999, "categories": [7]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [7],
|
||||||
|
"categories.": [{"id": 7, "name": "Verduras"}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
|
||||||
if args[0] == "model.product.category.read":
|
|
||||||
return [
|
|
||||||
{"id": 6, "name": "Alimentos"},
|
|
||||||
{"id": 7, "name": "Verduras"},
|
|
||||||
]
|
]
|
||||||
raise Exception(f"args no esperados: {args}")
|
raise Exception(f"args no esperados: {args}")
|
||||||
|
|
||||||
@@ -93,11 +96,15 @@ class TestCategoryTrytonImport(TestCase):
|
|||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"template.": {"id": 999, "categories": [7]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [7],
|
||||||
|
"categories.": [
|
||||||
|
{"id": 7, "name": "Cereales y Granos"}
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if args[0] == "model.product.category.read":
|
|
||||||
return [{"id": 7, "name": "Cereales y Granos"}]
|
|
||||||
raise Exception(f"args no esperados: {args}")
|
raise Exception(f"args no esperados: {args}")
|
||||||
|
|
||||||
mock_call.side_effect = fake_call
|
mock_call.side_effect = fake_call
|
||||||
@@ -135,11 +142,13 @@ class TestCategoryTrytonImport(TestCase):
|
|||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"template.": {"id": 999, "categories": [6]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [6],
|
||||||
|
"categories.": [{"id": 6, "name": "Alimentos"}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if args[0] == "model.product.category.read":
|
|
||||||
return [{"id": 6, "name": "Alimentos"}]
|
|
||||||
raise Exception(f"args no esperados: {args}")
|
raise Exception(f"args no esperados: {args}")
|
||||||
|
|
||||||
mock_call.side_effect = fake_call
|
mock_call.side_effect = fake_call
|
||||||
@@ -182,11 +191,13 @@ class TestCategoryTrytonImport(TestCase):
|
|||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"template.": {"id": 999, "categories": [6]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [6],
|
||||||
|
"categories.": [{"id": 6, "name": None}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if args[0] == "model.product.category.read":
|
|
||||||
return [{"id": 6, "name": None}]
|
|
||||||
raise Exception(f"args no esperados: {args}")
|
raise Exception(f"args no esperados: {args}")
|
||||||
|
|
||||||
mock_call.side_effect = fake_call
|
mock_call.side_effect = fake_call
|
||||||
|
|||||||
@@ -157,11 +157,13 @@ class ProductCategoryApiTest(TestCase):
|
|||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"template.": {"id": 999, "categories": [6]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [6],
|
||||||
|
"categories.": [{"id": 6, "name": "Alimentos"}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if args[0] == "model.product.category.read":
|
|
||||||
return [{"id": 6, "name": "Alimentos"}]
|
|
||||||
raise Exception(f"args no esperados: {args}")
|
raise Exception(f"args no esperados: {args}")
|
||||||
|
|
||||||
mock_call.side_effect = fake_call
|
mock_call.side_effect = fake_call
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -157,6 +158,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -226,6 +228,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -288,6 +291,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -354,6 +358,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -364,20 +369,15 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"list_price": Decimal("25000"),
|
"list_price": Decimal("25000"),
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
||||||
"template.": {"id": 999, "categories": [5, 8]},
|
"template.": {
|
||||||
},
|
"id": 999,
|
||||||
]
|
"categories": [5, 8],
|
||||||
|
"categories.": [
|
||||||
category_read = "model.product.category.read"
|
|
||||||
category_args = (
|
|
||||||
[5, 8],
|
|
||||||
["id", "name"],
|
|
||||||
{"company": 1},
|
|
||||||
)
|
|
||||||
if args == (category_read, category_args):
|
|
||||||
return [
|
|
||||||
{"id": 5, "name": "Abarrotes"},
|
{"id": 5, "name": "Abarrotes"},
|
||||||
{"id": 8, "name": "Bebidas"},
|
{"id": 8, "name": "Bebidas"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@@ -445,6 +445,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -455,19 +456,12 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"list_price": Decimal("25000"),
|
"list_price": Decimal("25000"),
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
||||||
"template.": {"id": 999, "categories": [7]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [7],
|
||||||
|
"categories.": [{"id": 7, "name": "Cereales"}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
|
||||||
|
|
||||||
category_read = "model.product.category.read"
|
|
||||||
category_args = (
|
|
||||||
[7],
|
|
||||||
["id", "name"],
|
|
||||||
{"company": 1},
|
|
||||||
)
|
|
||||||
if args == (category_read, category_args):
|
|
||||||
return [
|
|
||||||
{"id": 7, "name": "Cereales"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@@ -536,6 +530,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -546,19 +541,12 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"list_price": Decimal("25000"),
|
"list_price": Decimal("25000"),
|
||||||
"name": "Producto 1",
|
"name": "Producto 1",
|
||||||
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
||||||
"template.": {"id": 999, "categories": [5]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [5],
|
||||||
|
"categories.": [{"id": 5, "name": "Abarrotes"}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
|
||||||
|
|
||||||
category_read = "model.product.category.read"
|
|
||||||
category_args = (
|
|
||||||
[5],
|
|
||||||
["id", "name"],
|
|
||||||
{"company": 1},
|
|
||||||
)
|
|
||||||
if args == (category_read, category_args):
|
|
||||||
return [
|
|
||||||
{"id": 5, "name": "Abarrotes"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@@ -627,6 +615,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -688,6 +677,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -698,19 +688,14 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"list_price": Decimal("4500"),
|
"list_price": Decimal("4500"),
|
||||||
"name": "Papa",
|
"name": "Papa",
|
||||||
"default_uom.": {"id": 2, "rec_name": "Kilogram"},
|
"default_uom.": {"id": 2, "rec_name": "Kilogram"},
|
||||||
"template.": {"id": 999, "categories": [9]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [9],
|
||||||
|
"categories.": [
|
||||||
|
{"id": 9, "name": "Tubérculos"}
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
|
||||||
|
|
||||||
category_read = "model.product.category.read"
|
|
||||||
category_args = (
|
|
||||||
[9],
|
|
||||||
["id", "name"],
|
|
||||||
{"company": 1},
|
|
||||||
)
|
|
||||||
if args == (category_read, category_args):
|
|
||||||
return [
|
|
||||||
{"id": 9, "name": "Tubérculos"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@@ -786,6 +771,7 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"default_uom.rec_name",
|
"default_uom.rec_name",
|
||||||
"list_price",
|
"list_price",
|
||||||
"template.categories",
|
"template.categories",
|
||||||
|
"template.categories.name",
|
||||||
],
|
],
|
||||||
{"company": 1},
|
{"company": 1},
|
||||||
)
|
)
|
||||||
@@ -796,19 +782,12 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
"list_price": Decimal("4500"),
|
"list_price": Decimal("4500"),
|
||||||
"name": "Papa",
|
"name": "Papa",
|
||||||
"default_uom.": {"id": 2, "rec_name": "Kilogram"},
|
"default_uom.": {"id": 2, "rec_name": "Kilogram"},
|
||||||
"template.": {"id": 999, "categories": [8]},
|
"template.": {
|
||||||
|
"id": 999,
|
||||||
|
"categories": [8],
|
||||||
|
"categories.": [{"id": 8, "name": "Bebidas"}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
|
||||||
|
|
||||||
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(
|
raise Exception(
|
||||||
|
|||||||
Reference in New Issue
Block a user