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.
Files changed (44) hide show
  1. clientgbp-2.2.3/PKG-INFO +61 -0
  2. clientgbp-2.2.3/README.md +36 -0
  3. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/__init__.py +0 -2
  4. clientgbp-2.2.3/gbp/parsers/items.py +58 -0
  5. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/ws.py +6 -9
  6. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/base.py +1 -3
  7. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/orders.py +2 -0
  8. {clientgbp-2.2.2 → clientgbp-2.2.3}/pyproject.toml +1 -1
  9. clientgbp-2.2.2/PKG-INFO +0 -29
  10. clientgbp-2.2.2/README.md +0 -4
  11. clientgbp-2.2.2/gbp/parsers/items.py +0 -101
  12. {clientgbp-2.2.2 → clientgbp-2.2.3}/LICENSE +0 -0
  13. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/__init__.py +0 -0
  14. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/client.py +0 -0
  15. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/decorator/__init__.py +0 -0
  16. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/decorator/require_login.py +0 -0
  17. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/domain/__init__.py +0 -0
  18. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/domain/enums.py +0 -0
  19. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/domain/models.py +0 -0
  20. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/__init__.py +0 -0
  21. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/headers.py +0 -0
  22. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/http.py +0 -0
  23. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/http/urls.py +0 -0
  24. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/customers.py +0 -0
  25. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/entities.py +0 -0
  26. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/forms.py +0 -0
  27. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/html.py +0 -0
  28. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/parsers/orders.py +0 -0
  29. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/__init__.py +0 -0
  30. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/_constants.py +0 -0
  31. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/_form_utils.py +0 -0
  32. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/_viewstate_utils.py +0 -0
  33. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/auth.py +0 -0
  34. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/contacts.py +0 -0
  35. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/customers.py +0 -0
  36. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/payloads/orders.py +0 -0
  37. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/__init__.py +0 -0
  38. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/auth.py +0 -0
  39. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/contacts.py +0 -0
  40. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/customers.py +0 -0
  41. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/gbp_service.py +0 -0
  42. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/services/reports.py +0 -0
  43. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/validators/__init__.py +0 -0
  44. {clientgbp-2.2.2 → clientgbp-2.2.3}/gbp/validators/identifiers.py +0 -0
@@ -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
- clean_sku = sku.strip().lstrip("*")
33
- clean_sku = re.sub(r"[^A-Za-z0-9-]", "", clean_sku)
34
- if "-" in clean_sku:
35
- clean_sku = clean_sku.rsplit("-", 1)[-1]
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"**{clean_sku}",
40
- "count": 20,
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
- search_term = extract_sku_tail(sku)
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.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