Backport de mejoras genéricas de backend (desde cliente/tagumedica) + limpieza de datos de cliente en main

Mejoras genéricas traídas:
- core/crud_router.py: el PATCH genérico reenvía cualquier header B1S-* recibido del caller en vez de decidirlo el backend.
- routers/catalogs.py: nuevo endpoint /api/Catalogs/UdfValidValues (valores válidos de cualquier UDF vía Service Layer).
- routers/batch.py: /api/batch/stock (stock de un lote por almacén) y /api/batch/lot-source-warehouse (resuelve contra HANA de qué almacén salió un lote, repartiendo por origen).
- routers/inventory_transfer_requests.py + core/inventory_transfer_requests.py: CRUD genérico de Solicitudes de Transferencia de Inventario.
- routers/series.py + core/series.py: series activas de un tipo de documento.
- routers/user_tables.py: catálogo Code/Name genérico de tablas de usuario (mapeo USER_TABLES vacío, cada cliente completa el suyo).
- routers/reports.py / templates/reports/stickers.html: fix de márgenes/alto de página del reporte de etiquetas.
- mini-backend/test/ -> mini-backend/tools/ (rename organizativo).

Limpieza de datos de Tagumedica que ya estaban en el commit base de main (anteriores a la branch cliente/tagumedica, no relacionados con el backport):
- tools/sap-tagumedica/sl.http: eliminado (tenía CompanyDB y password reales).
- tools/ApiGateway/Login.yml: CompanyDB/host/password reemplazados por placeholders.
- templates/reports/stickers.html: título hardcodeado "TAGUMEDICA" reemplazado por "EMPRESA".
- menu_permissions.json: reemplazado por un ejemplo demo (usuarios/módulos ficticios) en vez de emails reales.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5 committed 2026-09-28 14:18:42 -05:00
1 parent b2fb4e2d0a
commit fef21898f9
30 files changed
+461 -111

No files matched your search

+11 -1
View File
@@ -113,11 +113,21 @@ def make_crud_router(
@router.patch(f"/api/{resource}/{{key}}", status_code=204, name=f"update_{resource}")
async def update_resource(key: str, request: Request):
data = await request.json()
# Las cabeceras de Service Layer (B1S-*) las decide quien llama
# (el frontend), no el backend: cualquier header "B1S-*" que
# venga en el request se reenvía tal cual a SAP. Ej.:
# B1S-ReplaceCollectionsOnPatch para reemplazar una colección
# hija (StockTransferLines) en vez de mergearla por LineNum,
# necesario solo cuando ese PATCH agrega o quita líneas.
headers = dict(patch_headers or {})
headers.update(
{k: v for k, v in request.headers.items() if k.lower().startswith("b1s-")}
)
forward_to_sapb1(
method="PATCH",
endpoint_path=key_expr(key),
json_data=data,
extra_headers=patch_headers,
extra_headers=headers or None,
)
return router
@@ -0,0 +1,4 @@
# Objeto Service Layer para la Solicitud de Transferencia de Inventario
# (BoObjectTypes "1250000001"). Centralizado acá para que el router de
# routers/inventory_transfer_requests.py no repita el nombre del endpoint.
INVENTORY_TRANSFER_REQUESTS_RESOURCE = "InventoryTransferRequests"
+51
View File
@@ -0,0 +1,51 @@
import logging
from typing import List, Optional
from core.sap import forward_to_sapb1
logger = logging.getLogger(__name__)
def get_default_series(document_type: str) -> Optional[int]:
"""
Consulta a Service Layer (SeriesService_GetDefaultSeries) la serie activa
por defecto para un tipo de documento de SAP B1 (código BoObjectTypes,
ej. "1250000001" para Solicitud de Transferencia). Si la llamada falla o
la respuesta no trae el campo esperado, se registra en el log y se
devuelve None; quien llama decide cómo seguir sin una serie por defecto.
"""
try:
response = forward_to_sapb1(
method="POST",
endpoint_path="SeriesService_GetDefaultSeries",
json_data={"DocumentTypeParams": {"Document": document_type}},
)
except Exception as exc:
logger.warning(
f"No se pudo obtener la serie por defecto de SAP (documento {document_type}): {exc}"
)
return None
data = response.json()
series = data.get("Series")
if series is None:
logger.warning(
f"SeriesService_GetDefaultSeries no devolvió 'Series' (documento {document_type}): {data}"
)
return series
def get_active_series(document_type: str) -> List[dict]:
"""
Lista las series no bloqueadas de un tipo de documento de SAP B1, vía
SeriesService_GetDocumentSeries. Confirmado contra Service Layer real:
la respuesta viene en "value", cada item con "Series" (código) y
"Name"; "Locked" en "tYES"/"tNO".
"""
response = forward_to_sapb1(
method="POST",
endpoint_path="SeriesService_GetDocumentSeries",
json_data={"DocumentTypeParams": {"Document": document_type}},
)
series_list = response.json().get("value", [])
return [s for s in series_list if str(s.get("Locked", "")).lower() != "tyes"]
+6
View File
@@ -22,6 +22,9 @@ from routers.batch import router as batch_router
from routers.permissions import router as permissions_router
from routers.apigateway import router as apigateway_router
from routers.reports import router as reports_router
from routers.series import router as series_router
from routers.user_tables import router as user_tables_router
from routers.inventory_transfer_requests import router as inventory_transfer_requests_router
# ============================================================================
load_dotenv(dotenv_path=".env")
@@ -54,6 +57,9 @@ app.include_router(batch_router)
app.include_router(permissions_router)
app.include_router(apigateway_router)
app.include_router(reports_router)
app.include_router(series_router)
app.include_router(user_tables_router)
app.include_router(inventory_transfer_requests_router)
# ============================================================================
@app.middleware("http")
+5 -14
View File
@@ -1,22 +1,13 @@
{
"renato.ortega@tagumedica.com": [
"admin.demo@empresa.com": [
"*"
],
"rodrigo.catari@tagumedica.com": [
"/inventario/*"
],
"jose.huayta@tagumedica.com": [
"/inventario/articulos",
"ventas.demo@empresa.com": [
"/ventas/*",
"/compras/*"
"/socios/clientes"
],
"ricardo.merino@tagumedica.com": [
"/socios/*"
],
"renato.bustamante@tagumedica.com": [
"inventario.demo@empresa.com": [
"/inventario/articulos",
"/inventario/lotes",
"/ventas/*",
"/compras/*"
"/inventario/lotes"
]
}
+235
View File
@@ -1,4 +1,12 @@
import logging
from typing import Dict, List, Optional
from fastapi import HTTPException
from hdbcli import dbapi
from pydantic import BaseModel, Field
from core.crud_router import make_crud_router
from core.hana import hana_connection, HANA_SCHEMA
router = make_crud_router(
"BatchNumberDetails",
@@ -6,3 +14,230 @@ router = make_crud_router(
supports_orderby=True,
supports_create=False,
)
logger = logging.getLogger(__name__)
# ============================================================================
# Stock de un lote por almacén. OBTN es el maestro de lotes: cada DistNumber
# (número de lote) tiene un AbsEntry propio. OBTQ guarda la cantidad actual
# por almacén, pero se enlaza a OBTN por "MdAbsEntry" = OBTN."AbsEntry" (no
# por DistNumber ni ItemCode directamente). Confirmado contra HANA real.
BATCH_STOCK_QUERY = f"""
SELECT
T1."WhsCode" AS "WarehouseCode",
T2."WhsName" AS "WarehouseName",
T1."Quantity" AS "Quantity",
T1."CommitQty" AS "CommitQty",
T1."CountQty" AS "CountQty",
T1."ItemCode" AS "ItemCode",
T3."ItemName" AS "ItemDescription"
FROM "{HANA_SCHEMA}".OBTN T0
INNER JOIN "{HANA_SCHEMA}".OBTQ T1 ON T1."MdAbsEntry" = T0."AbsEntry"
INNER JOIN "{HANA_SCHEMA}".OWHS T2 ON T2."WhsCode" = T1."WhsCode"
INNER JOIN "{HANA_SCHEMA}".OITM T3 ON T3."ItemCode" = T1."ItemCode"
WHERE T0."DistNumber" = ? AND T1."Quantity" > 0
ORDER BY T3."ItemName", T2."WhsName"
"""
# ============================================================================
class WarehouseStock(BaseModel):
WarehouseCode: str
WarehouseName: str
Quantity: float
CommitQty: float
CountQty: float
ItemCode: str
ItemDescription: Optional[str] = None
# ============================================================================
class BatchStockResponse(BaseModel):
Batch: str
Warehouses: List[WarehouseStock]
# ============================================================================
@router.get("/api/batch/stock", response_model=BatchStockResponse)
async def batch_stock(batch: str):
batch = batch.strip()
if not batch:
raise HTTPException(status_code=400, detail="Debe indicar el número de lote")
try:
with hana_connection() as conn:
cursor = conn.cursor()
cursor.execute(BATCH_STOCK_QUERY, (batch,))
rows = cursor.fetchall()
columns = [d[0] for d in cursor.description]
except dbapi.Error as exc:
logger.error(f"Error al consultar stock del lote {batch}: {exc}")
raise HTTPException(status_code=500, detail=f"Error al consultar el stock del lote: {exc}")
warehouses = [dict(zip(columns, row)) for row in rows]
if not warehouses:
raise HTTPException(status_code=404, detail=f"No se encontró stock para el lote '{batch}'")
return {"Batch": batch, "Warehouses": warehouses}
# ============================================================================
# Para "Confirmar Liberación Lote": un lote inmovilizado (llevado a un
# almacén como 302) puede haber llegado ahí en más de una transferencia y
# desde más de un almacén de origen distinto (confirmado con datos reales:
# ej. el lote 51232313 llegó a 302 con 2 unidades desde el almacén 207 y 1
# desde el 208). Por eso la línea de la Solicitud de Liberación (que sale
# con el total del lote desde 302) puede tener que dividirse en una línea
# por cada almacén de origen encontrado, repartiendo la cantidad entre ellos
# a prorrata de cuánto trajo cada transferencia históricamente.
#
# Se buscan todas las transferencias ejecutadas (OWTR, DocType 67 en OITL)
# que ingresaron el lote al almacén dado; de cada una (mismo DocEntry) se
# toma la línea hermana de salida (cantidad negativa) para saber de qué
# almacén salió. OITL/ITL1 es el log de transacciones de lote/serie de SAP
# (mismo que usa el reporte estándar "Transacciones de Números de Serie y
# Lote"), unidas por "LogEntry"; OBTN da el AbsEntry del lote a partir de
# ItemCode+DistNumber.
INBOUND_TRANSFERS_QUERY = f"""
SELECT DISTINCT T1."DocEntry", T1."DocDate"
FROM "{HANA_SCHEMA}".ITL1 T0
INNER JOIN "{HANA_SCHEMA}".OITL T1 ON T1."LogEntry" = T0."LogEntry"
INNER JOIN "{HANA_SCHEMA}".OBTN T2 ON T2."AbsEntry" = T0."MdAbsEntry"
WHERE T2."ItemCode" = ? AND T2."DistNumber" = ?
AND T1."LocCode" = ? AND T1."StockEff" = 1
AND T0."Quantity" > 0 AND T1."DocType" = 67
ORDER BY T1."DocDate", T1."DocEntry"
"""
INBOUND_QUANTITY_QUERY = f"""
SELECT T0."Quantity"
FROM "{HANA_SCHEMA}".ITL1 T0
INNER JOIN "{HANA_SCHEMA}".OITL T1 ON T1."LogEntry" = T0."LogEntry"
INNER JOIN "{HANA_SCHEMA}".OBTN T2 ON T2."AbsEntry" = T0."MdAbsEntry"
WHERE T1."DocEntry" = ? AND T2."ItemCode" = ? AND T2."DistNumber" = ?
AND T1."LocCode" = ? AND T0."Quantity" > 0
LIMIT 1
"""
TRANSFER_SOURCE_WAREHOUSE_QUERY = f"""
SELECT T1."LocCode"
FROM "{HANA_SCHEMA}".ITL1 T0
INNER JOIN "{HANA_SCHEMA}".OITL T1 ON T1."LogEntry" = T0."LogEntry"
INNER JOIN "{HANA_SCHEMA}".OBTN T2 ON T2."AbsEntry" = T0."MdAbsEntry"
WHERE T1."DocEntry" = ? AND T2."ItemCode" = ? AND T2."DistNumber" = ?
AND T0."Quantity" < 0
LIMIT 1
"""
class LotSourceWarehouseLine(BaseModel):
ItemCode: str
DistNumber: str
# Almacén donde está inmovilizado hoy el lote (ej. 302): se busca de qué
# almacén(es) vino, entre las transferencias que lo trajeron A este.
Warehouse: str
# Cantidad de la línea a liberar: se reparte entre los almacenes de
# origen encontrados, a prorrata de cuánto trajo cada uno.
Quantity: float
class LotSourceWarehouseRequest(BaseModel):
Lines: List[LotSourceWarehouseLine] = Field(min_length=1)
class LotSourceAllocation(BaseModel):
WarehouseCode: str
Quantity: float
class LotSourceWarehouseResult(BaseModel):
ItemCode: str
DistNumber: str
Allocations: List[LotSourceAllocation] = []
def _allocate_by_origin(
origin_totals: Dict[str, float], release_qty: float
) -> List[LotSourceAllocation]:
"""
Reparte release_qty entre los almacenes de origin_totals a prorrata de
su participación histórica, redondeando cada asignación a un entero (los
artículos se mueven en unidades completas). El último almacén se ajusta
para que la suma coincida exactamente con release_qty (evita descuadres
de redondeo, ya que el histórico puede no calzar 1 a 1 con la cantidad
actual si parte del lote salió de 302 por otra vía mientras tanto).
"""
total_historical = sum(origin_totals.values())
if total_historical <= 0:
return []
origins = list(origin_totals.items())
allocations: List[LotSourceAllocation] = []
allocated_so_far = 0.0
for index, (warehouse_code, historical_qty) in enumerate(origins):
is_last = index == len(origins) - 1
allocated_qty = (
round(release_qty - allocated_so_far)
if is_last
else round(release_qty * (historical_qty / total_historical))
)
allocated_qty = max(allocated_qty, 0)
allocated_so_far += allocated_qty
if allocated_qty > 0:
allocations.append(
LotSourceAllocation(WarehouseCode=warehouse_code, Quantity=allocated_qty)
)
return allocations
@router.post(
"/api/batch/lot-source-warehouse",
response_model=List[LotSourceWarehouseResult],
)
async def lot_source_warehouse(payload: LotSourceWarehouseRequest):
results: List[LotSourceWarehouseResult] = []
try:
with hana_connection() as conn:
cursor = conn.cursor()
for line in payload.Lines:
cursor.execute(
INBOUND_TRANSFERS_QUERY,
(line.ItemCode, line.DistNumber, line.Warehouse),
)
doc_entries = [row[0] for row in cursor.fetchall()]
origin_totals: Dict[str, float] = {}
for doc_entry in doc_entries:
cursor.execute(
INBOUND_QUANTITY_QUERY,
(doc_entry, line.ItemCode, line.DistNumber, line.Warehouse),
)
inbound_row = cursor.fetchone()
if not inbound_row:
continue
cursor.execute(
TRANSFER_SOURCE_WAREHOUSE_QUERY,
(doc_entry, line.ItemCode, line.DistNumber),
)
source_row = cursor.fetchone()
if not source_row:
continue
warehouse_code = source_row[0]
origin_totals[warehouse_code] = origin_totals.get(
warehouse_code, 0
) + float(inbound_row[0])
results.append(
LotSourceWarehouseResult(
ItemCode=line.ItemCode,
DistNumber=line.DistNumber,
Allocations=_allocate_by_origin(origin_totals, line.Quantity),
)
)
except dbapi.Error as exc:
logger.error(f"Error al buscar el almacén de origen de lotes: {exc}")
raise HTTPException(
status_code=500,
detail=f"Error al buscar el almacén de origen de los lotes: {exc}",
)
return results
+43
View File
@@ -58,6 +58,49 @@ async def get_sunat_businesspartners_document_type():
extra_headers=base_headers
).json()
@router.get("/api/Catalogs/UdfValidValues")
async def get_udf_valid_values(table_name: str, field_name: str):
"""
Genérico para cualquier campo de usuario (UDF) con lista de valores
válidos: recibe la tabla SAP (ej. 'OWTQ') y el nombre del campo sin el
prefijo 'U_' (ej. 'SIS_TIPDESP'), y devuelve sus valores válidos vía
Service Layer (UserFieldsMD.ValidValuesMD), normalizados a {Code, Name}
para poder usarse con useSapCatalog igual que las tablas de usuario.
Equivale a lo que en HANA sería CUFD (por AliasID/TableID) + UFD1
(por FieldID), pero sin necesidad de acceso directo a HANA.
"""
def _escape(value: str) -> str:
return value.replace("'", "''")
sap_params = {
"$skip": 0,
"$top": 200,
"$inlinecount": "allpages",
}
sap_params["$filter"] = (
f"TableName eq '{_escape(table_name)}' and Name eq '{_escape(field_name)}'"
)
base_headers = {"B1S-PageSize": "200"}
response = forward_to_sapb1(
method="GET",
endpoint_path="UserFieldsMD",
params=sap_params,
extra_headers=base_headers,
).json()
fields = response.get("value", [])
valid_values = fields[0].get("ValidValuesMD", []) if fields else []
return {
"value": [
{"Code": v["Value"], "Name": v["Description"]} for v in valid_values
]
}
@router.get("/api/ItemGroups")
async def get_item_groups():
@@ -0,0 +1,15 @@
from core.crud_router import make_crud_router
from core.inventory_transfer_requests import INVENTORY_TRANSFER_REQUESTS_RESOURCE
# CRUD genérico de Solicitudes de Transferencia (list/get/create). El
# frontend arma acá el JSON completo que espera SAP (StockTransferLines,
# BatchNumbers, campos U_* de usuario, etc.) y lo manda directo a este
# endpoint; el backend no valida stock ni transforma nada, es SAP quien
# rechaza el documento si no hay stock suficiente (ver "Inmovilizar Lotes"
# en mini-frontend/src/views/inventario/InmovilizarLotesView.tsx).
router = make_crud_router(
INVENTORY_TRANSFER_REQUESTS_RESOURCE,
tags=["Solicitudes de Transferencia"],
key_field="DocEntry",
supports_orderby=True,
)
+4 -3
View File
@@ -26,9 +26,10 @@ logger = logging.getLogger(__name__)
# Tamaño de papel que usa cada reporte al renderizar el PDF (CSS @page.size).
PAPER_SIZES = {"A4": "A4", "Carta": "letter"}
# Alto útil de la página en orientación horizontal (tamaño de papel menos los
# 0.3in de margen superior e inferior definidos en el @page de stickers.html).
PAGINA_ALTO_LANDSCAPE = {"A4": "7.67in", "Carta": "7.9in"}
# Alto útil de la página en orientación horizontal (tamaño de papel menos el
# 1.6cm de margen superior + 1cm de margen inferior definidos en el @page de
# stickers.html).
PAGINA_ALTO_LANDSCAPE = {"A4": "18.4cm", "Carta": "18.99cm"}
# Query original de Crystal Reports para DLN20015, adaptada: el placeholder
# de Crystal `{?Dockey@}` se reemplaza por un bind param `?` de hdbcli, y cada
+34
View File
@@ -0,0 +1,34 @@
from typing import List, Optional
from fastapi import APIRouter
from pydantic import BaseModel
from core.series import get_active_series, get_default_series
router = APIRouter(tags=["Series"])
class SeriesOption(BaseModel):
Series: int
Name: str
class SeriesListResponse(BaseModel):
Default: Optional[int] = None
Options: List[SeriesOption]
@router.get("/api/series", response_model=SeriesListResponse)
async def list_series(document: str):
"""
Series activas (no bloqueadas) y serie por defecto para un tipo de
documento de SAP B1. `document` es el código BoObjectTypes del
documento (ej. "1250000001" para Solicitud de Transferencia); lo define
cada módulo que lo consuma, no este router.
"""
active_series = get_active_series(document)
options = [
{"Series": s["Series"], "Name": s.get("Name") or str(s["Series"])}
for s in active_series
]
return {"Default": get_default_series(document), "Options": options}
+38
View File
@@ -0,0 +1,38 @@
from typing import Dict
from fastapi import APIRouter, HTTPException
from core.sap import forward_to_sapb1
router = APIRouter(tags=["Tablas de Usuario"])
# Mapea un nombre amigable de tabla de usuario (@...) a su nombre real de
# entidad en Service Layer. La convención de nombre no es uniforme entre
# tablas de un mismo cliente (una tabla de usuario puede exponerse con
# prefijo "U_" y otra sin él), así que se centraliza acá en vez de que cada
# módulo tenga que adivinar o repetir el nombre de endpoint real. Cada
# cliente completa este mapeo según sus propias tablas de usuario en SAP.
USER_TABLES: Dict[str, str] = {}
@router.get("/api/UserTables/{name}")
async def get_user_table(name: str):
"""
Catálogo Code/Name de una tabla de usuario de SAP B1, identificada por un
nombre amigable (ver USER_TABLES) en vez del nombre real de tabla/entidad.
Pensado para reutilizarse en cualquier formulario que necesite un select
cargado desde una tabla de usuario de este tipo.
"""
entity = USER_TABLES.get(name)
if not entity:
raise HTTPException(status_code=404, detail=f"Tabla de usuario '{name}' no está registrada")
# Service Layer limita el tamaño de página por defecto (server-side, no
# depende solo de "$top") salvo que se indique explícitamente el header
# B1S-PageSize; sin él, catálogos con más de ~20 filas quedaban truncados.
return forward_to_sapb1(
method="GET",
endpoint_path=entity,
params={"$select": "Code,Name", "$top": 200},
extra_headers={"B1S-PageSize": "200"},
).json()
+11 -7
View File
@@ -10,7 +10,7 @@
@page {
size: {{ paper_size }} landscape;
margin: 0.3in;
margin: 1.6cm 3cm 1cm 2.2cm;
}
body {
@@ -33,8 +33,9 @@
}
.sticker {
height: 100%;
border: 2px solid #000;
padding: 0.3in 0.35in;
padding: 0.15in 0.35in;
box-sizing: border-box;
position: relative;
display: flex;
@@ -47,15 +48,15 @@
font-size: 20pt;
font-weight: bold;
letter-spacing: 1px;
margin-bottom: 16px;
margin-bottom: 10px;
}
.campo {
margin-bottom: 14px;
margin-bottom: 8px;
}
.campo-direccion {
min-height: 0.65in;
min-height: 0.5in;
}
.campo-label {
@@ -77,10 +78,13 @@
}
.documento-linea {
position: absolute;
left: 0.35in;
right: 0.35in;
bottom: 0.15in;
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: auto;
}
.documento-linea .campo {
@@ -94,7 +98,7 @@
<div class="pagina">
{% for sticker in pagina %}
<div class="sticker">
<div class="titulo">TAGUMEDICA</div>
<div class="titulo">EMPRESA</div>
<div class="campo">
<div class="campo-label">DATOS DEL CLIENTE:</div>
-82
View File
@@ -1,82 +0,0 @@
@url=https://192.168.1.175:50000/b1s/v2
###
# @noRejectUnauthorized
POST {{url}}/Login HTTP/1.1
Content-Type: application/json
{
"CompanyDB": "SBO_TAGUMEDICA_21",
"UserName": "manager",
"Password": "m1r1"
}
###
# @noRejectUnauthorized
GET {{url}}/$metadata
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/EmployeesInfo?$select=EmployeeID,FirstName,LastName,Active,U_WEB_USER,U_WEB_PASS&$filter=U_WEB_USER eq 'renato.ortega@tagumedica.com'
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/Items?$top=20&$orderby=ItemCode desc
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/DeliveryNotes(29360)
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/UserFieldsMD?$filter=TableName eq 'OCRD' and Name eq 'EXX_TIPODOCU'
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/Warehouses
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/PurchaseInvoices(135235)
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/Orders?$filter=DocumentStatus eq 'bost_Open'
Content-Type: application/json
###
# @noRejectUnauthorized
POST {{url}}/MessagesService_GetInbox HTTP/1.1
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/Messages(173810)
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/ItemGroups?$select=Number,GroupName
Content-Type: application/json
###
# @noRejectUnauthorized
POST {{url}}/FactoringIndicators?$select=IndicatorCode,IndicatorName&$filter=U_EXX_REGVEN eq 'Y'
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/Currencies
Content-Type: application/json
###
# @noRejectUnauthorized
GET {{url}}/BatchNumberDetails
Content-Type: application/json
@@ -4,15 +4,15 @@ info:
http:
method: POST
url: https://192.168.1.175:60000/login
url: https://<host>:60000/login
body:
type: json
data: |-
{
"CompanyDB":"SBO_TAGUMEDICA_21",
"CompanyDB":"<CompanyDB>",
"UserName": "manager",
"Password": "m1r1",
"DBInstance":"NDB@192.168.1.175:30013"
"Password": "<password>",
"DBInstance":"NDB@<host>:30013"
}
auth: inherit
File renamed without changes.
File renamed without changes.
File renamed without changes.