clientGBP 2.2.2__tar.gz → 2.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clientgbp-2.2.3/PKG-INFO +61 -0
- clientgbp-2.2.3/README.md +36 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/__init__.py +0 -2
- clientgbp-2.2.3/gbp/parsers/items.py +58 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/ws.py +6 -9
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/base.py +1 -3
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/orders.py +2 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/pyproject.toml +1 -1
- clientgbp-2.2.2/PKG-INFO +0 -29
- clientgbp-2.2.2/README.md +0 -4
- clientgbp-2.2.2/gbp/parsers/items.py +0 -101
- {clientgbp-2.2.2 → clientgbp-2.2.3}/LICENSE +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/__init__.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/client.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/decorator/__init__.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/decorator/require_login.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/domain/__init__.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/domain/enums.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/domain/models.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/__init__.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/headers.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/http.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/urls.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/customers.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/entities.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/forms.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/html.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/orders.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/__init__.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/_constants.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/_form_utils.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/_viewstate_utils.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/auth.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/contacts.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/customers.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/orders.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/__init__.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/auth.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/contacts.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/customers.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/gbp_service.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/reports.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/validators/__init__.py +0 -0
- {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/validators/identifiers.py +0 -0
clientgbp-2.2.3/PKG-INFO
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clientGBP
|
|
3
|
+
Version: 2.2.3
|
|
4
|
+
Summary: Libreria para el uso de GBP mediante HTTP. Sin utilizar interfaz gráfica (Scraping).Usando las credenciales de una cuenta, se obtiene las cookies de sesión y se pueden realizar operaciones. Se realiza todo de manera sincrona.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Leandro Franco
|
|
8
|
+
Author-email: leandro00fr@gmail.com
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Dist: beautifulsoup4 (==4.13.5)
|
|
15
|
+
Requires-Dist: email-validator (==2.3.0)
|
|
16
|
+
Requires-Dist: pydantic-settings (==2.10.1)
|
|
17
|
+
Requires-Dist: pydantic[email] (==2.11.9)
|
|
18
|
+
Requires-Dist: requests (==2.32.3)
|
|
19
|
+
Requires-Dist: requests-toolbelt (==1.0.0)
|
|
20
|
+
Project-URL: Homepage, https://github.com/Netpoint-ARG/gbp
|
|
21
|
+
Project-URL: Issues, https://github.com/Netpoint-ARG/gbp/issues
|
|
22
|
+
Project-URL: Repository, https://github.com/Netpoint-ARG/gbp
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# GBP
|
|
26
|
+
|
|
27
|
+
La libreria permite realizar operaciones en Global Blue Point (GBP) mediante HTTP. Sin utilizar interfaz gráfica (Scraping). Usando las credenciales de una cuenta, se obtiene las cookies de sesión y se pueden realizar operaciones. Se realiza todo de manera sincrona.
|
|
28
|
+
|
|
29
|
+
## Instalación
|
|
30
|
+
|
|
31
|
+
Requiere Python 3.12 o superior. El paquete se publica en PyPI como `clientGBP`:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install clientGBP
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Uso
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from gbp import ClientGBP, Client, Contact, ClientType
|
|
41
|
+
from gbp.domain.models import OrderProduct
|
|
42
|
+
|
|
43
|
+
client = ClientGBP(username="user", password="pass")
|
|
44
|
+
client.login()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Operaciones disponibles en `ClientGBP` (todas salvo `login` requieren sesión; sin sesión devuelven `None`):
|
|
48
|
+
|
|
49
|
+
- **Clientes**: `exists_client_cuit`, `exists_client_email`, `get_client_by_dni`, `get_client_by_email`, `create_client`, `update_client`, `clean_client`. Si un CUIT tiene una ficha en dólares y una copia en pesos de Mercado Libre, se usa la de dólares.
|
|
50
|
+
- **Contactos**: `exists_contact`, `get_contact_by_email`, `get_contact_edit_url_by_email`, `search_contact_with_email_cuit`, `exists_contact_with_email_cuit`, `create_contact`, `update_contact`.
|
|
51
|
+
- **Órdenes de venta**: `create_order(client, products)`.
|
|
52
|
+
- **Reportes Excel**: `download_valorized_stock`, `download_potential_stock`, `download_price_list`, `download_customers`, `download_report`.
|
|
53
|
+
|
|
54
|
+
## Desarrollo
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
poetry install
|
|
58
|
+
poetry run ruff check gbp tests
|
|
59
|
+
poetry run pytest
|
|
60
|
+
```
|
|
61
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# GBP
|
|
2
|
+
|
|
3
|
+
La libreria permite realizar operaciones en Global Blue Point (GBP) mediante HTTP. Sin utilizar interfaz gráfica (Scraping). Usando las credenciales de una cuenta, se obtiene las cookies de sesión y se pueden realizar operaciones. Se realiza todo de manera sincrona.
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
Requiere Python 3.12 o superior. El paquete se publica en PyPI como `clientGBP`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install clientGBP
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Uso
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from gbp import ClientGBP, Client, Contact, ClientType
|
|
17
|
+
from gbp.domain.models import OrderProduct
|
|
18
|
+
|
|
19
|
+
client = ClientGBP(username="user", password="pass")
|
|
20
|
+
client.login()
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Operaciones disponibles en `ClientGBP` (todas salvo `login` requieren sesión; sin sesión devuelven `None`):
|
|
24
|
+
|
|
25
|
+
- **Clientes**: `exists_client_cuit`, `exists_client_email`, `get_client_by_dni`, `get_client_by_email`, `create_client`, `update_client`, `clean_client`. Si un CUIT tiene una ficha en dólares y una copia en pesos de Mercado Libre, se usa la de dólares.
|
|
26
|
+
- **Contactos**: `exists_contact`, `get_contact_by_email`, `get_contact_edit_url_by_email`, `search_contact_with_email_cuit`, `exists_contact_with_email_cuit`, `create_contact`, `update_contact`.
|
|
27
|
+
- **Órdenes de venta**: `create_order(client, products)`.
|
|
28
|
+
- **Reportes Excel**: `download_valorized_stock`, `download_potential_stock`, `download_price_list`, `download_customers`, `download_report`.
|
|
29
|
+
|
|
30
|
+
## Desarrollo
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
poetry install
|
|
34
|
+
poetry run ruff check gbp tests
|
|
35
|
+
poetry run pytest
|
|
36
|
+
```
|
|
@@ -14,7 +14,6 @@ from gbp.parsers.forms import (
|
|
|
14
14
|
from gbp.parsers.html import parse_html
|
|
15
15
|
from gbp.parsers.items import (
|
|
16
16
|
extract_item_code_from_row,
|
|
17
|
-
extract_sku_tail,
|
|
18
17
|
match_item_to_requested_sku,
|
|
19
18
|
normalize_sku_token,
|
|
20
19
|
)
|
|
@@ -37,7 +36,6 @@ __all__ = [
|
|
|
37
36
|
"extract_item_code_from_row",
|
|
38
37
|
"extract_order_message",
|
|
39
38
|
"extract_save_order_alert",
|
|
40
|
-
"extract_sku_tail",
|
|
41
39
|
"extract_usd_client_ids",
|
|
42
40
|
"get_input_value",
|
|
43
41
|
"get_option_value_by_text",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Parsers y normalizacion para items y SKUs."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
logger.addHandler(logging.NullHandler())
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def normalize_sku_token(value: str) -> str:
|
|
11
|
+
"""Normaliza un token de SKU para comparacion flexible.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
value (str): Valor de entrada a normalizar o procesar.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
str: Texto resultante del procesamiento.
|
|
18
|
+
"""
|
|
19
|
+
return re.sub(r"[^a-z0-9]", "", value.lower())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def extract_item_code_from_row(raw_item: str) -> str:
|
|
23
|
+
"""Extrae el codigo de articulo desde una fila devuelta por itemGetText.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
raw_item (str): Fila cruda devuelta por itemGetText.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: Texto resultante del procesamiento.
|
|
30
|
+
"""
|
|
31
|
+
parts = [part.strip() for part in raw_item.split("|")]
|
|
32
|
+
return parts[-1] if parts else raw_item
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def match_item_to_requested_sku(items: list[str], requested_sku: str) -> str | None:
|
|
36
|
+
"""Selecciona el item cuyo codigo es exactamente el SKU solicitado.
|
|
37
|
+
|
|
38
|
+
Sólo acepta el mismo codigo (sin distinguir mayusculas ni simbolos): un articulo parecido
|
|
39
|
+
es otro producto (`3NS-POS-SC415` no es `3NS-POS-SC415BT`, `PC084WV-8-240` no es `PC082WV-8-240`).
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
items (list[str]): Items devueltos por el servicio de catalogo.
|
|
43
|
+
requested_sku (str): SKU solicitado originalmente por el usuario.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str | None: Fila del item elegido o `None` cuando el codigo no esta en los resultados.
|
|
47
|
+
"""
|
|
48
|
+
requested_norm = normalize_sku_token(requested_sku.strip().lstrip("*"))
|
|
49
|
+
if not requested_norm:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
rows = [raw_item for raw_item in items if isinstance(raw_item, str)]
|
|
53
|
+
for row in rows:
|
|
54
|
+
if normalize_sku_token(extract_item_code_from_row(row)) == requested_norm:
|
|
55
|
+
return row
|
|
56
|
+
|
|
57
|
+
logger.error("SKU %s no esta en el catalogo de GBP. Resultados: %s", requested_sku, [extract_item_code_from_row(row) for row in rows])
|
|
58
|
+
return None
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Builders de payload JSON para webservices de GBP."""
|
|
2
2
|
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
3
|
from gbp.validators.identifiers import get_national_id
|
|
6
4
|
|
|
7
5
|
def crm_payload(tax_number: str, prefix_text: str | None = None) -> dict[str, str | int]:
|
|
@@ -29,14 +27,13 @@ def item_search_payload(sku: str) -> dict[str, str | int]:
|
|
|
29
27
|
Returns:
|
|
30
28
|
dict[str, str | int]: Payload serializable para enviar al endpoint correspondiente.
|
|
31
29
|
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
clean_sku = clean_sku.upper()
|
|
30
|
+
# SKU completo y sin limpiar: buscar sólo el último segmento ("**240") traía otros artículos y el exacto
|
|
31
|
+
# quedaba afuera del límite. El webservice no encuentra nada si el texto tiene un ".", así que se corta
|
|
32
|
+
# en el primero (`SEG-FUE-125-2.1` -> `SEG-FUE-125-2`) y se piden 100 resultados para que entre el exacto.
|
|
33
|
+
search_term = sku.strip().lstrip("*").split(".")[0].strip()
|
|
37
34
|
|
|
38
35
|
return {
|
|
39
|
-
"prefixText": f"**{
|
|
40
|
-
"count":
|
|
36
|
+
"prefixText": f"**{search_term}",
|
|
37
|
+
"count": 100,
|
|
41
38
|
"contextKey": "10/1/1/50031/1/1/8/0",
|
|
42
39
|
}
|
|
@@ -13,7 +13,6 @@ from gbp.http.http import HttpClient
|
|
|
13
13
|
from gbp.http.urls import BASE_URL, CUSTOMER_WS_URL, DEFAULT_TIMEOUT, ITEM_WS_URL, MAIN_URL, SEARCH_URL
|
|
14
14
|
from gbp.parsers import (
|
|
15
15
|
extract_client_edit_keys,
|
|
16
|
-
extract_sku_tail,
|
|
17
16
|
extract_usd_client_ids,
|
|
18
17
|
get_input_value,
|
|
19
18
|
match_item_to_requested_sku,
|
|
@@ -345,8 +344,7 @@ class BaseGBPService:
|
|
|
345
344
|
str | None: Cadena encontrada o `None` cuando el dato no esta disponible.
|
|
346
345
|
"""
|
|
347
346
|
try:
|
|
348
|
-
|
|
349
|
-
payload = item_search_payload(search_term)
|
|
347
|
+
payload = item_search_payload(sku)
|
|
350
348
|
resp_item = self._send_post_json(ITEM_WS_URL, payload, HEADERS_JSON_CONTACT)
|
|
351
349
|
|
|
352
350
|
if resp_item.status_code != 200:
|
|
@@ -13,6 +13,7 @@ from gbp.http.headers import HEADERS_RELOAD_ORDER, HEADERS_SALE_ORDER, get_heade
|
|
|
13
13
|
from gbp.http.urls import BASE_URL, ORIGIN_URL, SALE_ORDER, UPDATE_IS_EDITING_URL
|
|
14
14
|
from gbp.parsers import (
|
|
15
15
|
extract_add_item_form_data,
|
|
16
|
+
extract_item_code_from_row,
|
|
16
17
|
extract_all_form_fields,
|
|
17
18
|
extract_order_detail_dialog_url,
|
|
18
19
|
extract_order_message,
|
|
@@ -95,6 +96,7 @@ class OrderService(BaseGBPService):
|
|
|
95
96
|
if not item_data:
|
|
96
97
|
logger.error(f"No se encontró producto con SKU: {product.sku}")
|
|
97
98
|
return False
|
|
99
|
+
logger.info("Artículo de GBP para el SKU %s: %s", product.sku, extract_item_code_from_row(item_data))
|
|
98
100
|
|
|
99
101
|
resp_add_item_form = self._get_page_session_keys(add_item_url)
|
|
100
102
|
if not resp_add_item_form:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "clientGBP"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.3"
|
|
4
4
|
description = "Libreria para el uso de GBP mediante HTTP. Sin utilizar interfaz gráfica (Scraping).Usando las credenciales de una cuenta, se obtiene las cookies de sesión y se pueden realizar operaciones. Se realiza todo de manera sincrona."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Leandro Franco",email = "leandro00fr@gmail.com"}
|
clientgbp-2.2.2/PKG-INFO
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: clientGBP
|
|
3
|
-
Version: 2.2.2
|
|
4
|
-
Summary: Libreria para el uso de GBP mediante HTTP. Sin utilizar interfaz gráfica (Scraping).Usando las credenciales de una cuenta, se obtiene las cookies de sesión y se pueden realizar operaciones. Se realiza todo de manera sincrona.
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Author: Leandro Franco
|
|
8
|
-
Author-email: leandro00fr@gmail.com
|
|
9
|
-
Requires-Python: >=3.12
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Requires-Dist: beautifulsoup4 (==4.13.5)
|
|
15
|
-
Requires-Dist: email-validator (==2.3.0)
|
|
16
|
-
Requires-Dist: pydantic-settings (==2.10.1)
|
|
17
|
-
Requires-Dist: pydantic[email] (==2.11.9)
|
|
18
|
-
Requires-Dist: requests (==2.32.3)
|
|
19
|
-
Requires-Dist: requests-toolbelt (==1.0.0)
|
|
20
|
-
Project-URL: Homepage, https://github.com/Netpoint-ARG/gbp
|
|
21
|
-
Project-URL: Issues, https://github.com/Netpoint-ARG/gbp/issues
|
|
22
|
-
Project-URL: Repository, https://github.com/Netpoint-ARG/gbp
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
|
|
25
|
-
# GBP
|
|
26
|
-
|
|
27
|
-
La libreria permite realizar operaciones en Global Blue Point (GBP)mediante HTTP. Sin utilizar interfaz gráfica (Scraping). Usando las credenciales de una cuenta, se obtiene las cookies de sesión y se pueden realizar operaciones. Se realiza todo de manera sincrona.
|
|
28
|
-
|
|
29
|
-
|
clientgbp-2.2.2/README.md
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
# GBP
|
|
2
|
-
|
|
3
|
-
La libreria permite realizar operaciones en Global Blue Point (GBP)mediante HTTP. Sin utilizar interfaz gráfica (Scraping). Usando las credenciales de una cuenta, se obtiene las cookies de sesión y se pueden realizar operaciones. Se realiza todo de manera sincrona.
|
|
4
|
-
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"""Parsers y normalizacion para items y SKUs."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import re
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger(__name__)
|
|
7
|
-
logger.addHandler(logging.NullHandler())
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def extract_sku_tail(value: str) -> str:
|
|
11
|
-
"""Extrae el segmento final del SKU separado por '-'.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
value (str): Valor de entrada a normalizar o procesar.
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
str: Texto resultante del procesamiento.
|
|
18
|
-
"""
|
|
19
|
-
source = value.strip()
|
|
20
|
-
if not source:
|
|
21
|
-
return ""
|
|
22
|
-
|
|
23
|
-
hashtag_match = re.search(r"#([A-Za-z0-9-]+)", source)
|
|
24
|
-
if hashtag_match:
|
|
25
|
-
source = hashtag_match.group(1)
|
|
26
|
-
|
|
27
|
-
source = source.split("|")[-1].strip()
|
|
28
|
-
source = re.sub(r"[^A-Za-z0-9-]", "", source)
|
|
29
|
-
if not source:
|
|
30
|
-
return ""
|
|
31
|
-
|
|
32
|
-
parts = [part for part in source.split("-") if part]
|
|
33
|
-
return parts[-1] if parts else source
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def normalize_sku_token(value: str) -> str:
|
|
37
|
-
"""Normaliza un token de SKU para comparacion flexible.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
value (str): Valor de entrada a normalizar o procesar.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
str: Texto resultante del procesamiento.
|
|
44
|
-
"""
|
|
45
|
-
return re.sub(r"[^a-z0-9]", "", value.lower())
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def extract_item_code_from_row(raw_item: str) -> str:
|
|
49
|
-
"""Extrae el codigo de articulo desde una fila devuelta por itemGetText.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
raw_item (str): Fila cruda devuelta por itemGetText.
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
str: Texto resultante del procesamiento.
|
|
56
|
-
"""
|
|
57
|
-
parts = [part.strip() for part in raw_item.split("|")]
|
|
58
|
-
return parts[-1] if parts else raw_item
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def match_item_to_requested_sku(items: list[str], requested_sku: str) -> str | None:
|
|
62
|
-
"""Selecciona el item cuyo codigo coincide con el SKU solicitado.
|
|
63
|
-
|
|
64
|
-
Prioriza la coincidencia exacta del codigo de articulo para no confundir SKUs
|
|
65
|
-
con prefijo comun (por ejemplo `3NS-POS-SC415` contra `3NS-POS-SC415BT`).
|
|
66
|
-
|
|
67
|
-
Args:
|
|
68
|
-
items (list[str]): Items devueltos por el servicio de catalogo.
|
|
69
|
-
requested_sku (str): SKU solicitado originalmente por el usuario.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
str | None: Fila del item elegido o `None` cuando no hay coincidencia confiable.
|
|
73
|
-
"""
|
|
74
|
-
requested_code = re.sub(r"[^A-Za-z0-9-]", "", requested_sku.strip().lstrip("*"))
|
|
75
|
-
if not requested_code:
|
|
76
|
-
return None
|
|
77
|
-
|
|
78
|
-
rows = [raw_item for raw_item in items if isinstance(raw_item, str)]
|
|
79
|
-
requested_norm = normalize_sku_token(requested_code)
|
|
80
|
-
|
|
81
|
-
exact_matches = [row for row in rows if normalize_sku_token(extract_item_code_from_row(row)) == requested_norm]
|
|
82
|
-
if exact_matches:
|
|
83
|
-
return exact_matches[0]
|
|
84
|
-
|
|
85
|
-
requested_tail_norm = normalize_sku_token(extract_sku_tail(requested_code))
|
|
86
|
-
if requested_tail_norm:
|
|
87
|
-
tail_matches = [row for row in rows if normalize_sku_token(extract_sku_tail(extract_item_code_from_row(row))) == requested_tail_norm]
|
|
88
|
-
if tail_matches:
|
|
89
|
-
return tail_matches[0]
|
|
90
|
-
|
|
91
|
-
partial_matches = [row for row in rows if requested_norm in normalize_sku_token(row)]
|
|
92
|
-
if len(partial_matches) == 1:
|
|
93
|
-
return partial_matches[0]
|
|
94
|
-
|
|
95
|
-
if partial_matches:
|
|
96
|
-
codes = [extract_item_code_from_row(row) for row in partial_matches]
|
|
97
|
-
logger.error("SKU %s es ambiguo en el catalogo de GBP: %s", requested_sku, codes)
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
logger.error("SKU %s no coincide con ningun item del catalogo de GBP", requested_sku)
|
|
101
|
-
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|