This commit is contained in:
JiriUhlir
2026-07-15 09:03:28 +02:00
parent 4b98f37407
commit fea25d7429
31 changed files with 4906 additions and 30 deletions
@@ -0,0 +1,70 @@
# SAP Business One Service Layer Connector (POLSTRIN)
Connector komunikuje výhradně přes SAP Business One Service Layer `/b1s/v1` instalace POLSTRIN
(`https://ws.polstrin.cz:50000`). Specifika instalace jsou v [polstrin-specifika.md](polstrin-specifika.md).
Hlavní vlastnosti:
- login/logout přes Service Layer
- správa `B1SESSION` a `ROUTEID`, jedna sdílená session mezi requesty
- automatický re-login po expiraci session
- SAP credentials výhradně z environment variables (AppFactory secrets), ne z requestů
- volitelná ochrana `/api` rout hlavičkou `X-Api-Key` (secret `API_KEY`)
- OData parametry `$select`, `$filter`, `$top`, `$skip`, `$orderby`
- stránkování přes `odata.nextLink` a `@odata.nextLink`
- generický přístup k libovolnému entity setu přes `/api/entities/...` (POLSTRIN UDO tabulky `U_*`)
- retry pro dočasné chyby
- zod validace konfigurace a hlavních response tvarů
Bezpečnostní pravidla:
- konfigurace se čte výhradně z environment variables (`SAP_B1_*`, `API_KEY`)
- SAP credentials se nikdy nepřijímají v requestech, nelogují a nevrací v response
- hesla, cookies a session tokeny se nelogují
- testy nepoužívají reálný SAP přístup
- pro self-signed certifikáty je dostupné `rejectUnauthorized`, ale produkčně se doporučuje důvěryhodný certifikát
## Detaily komunikace se Service Layer
- **Login** `POST /b1s/v1/Login` s `{ CompanyDB, UserName, Password, Language? }`.
`Language` se posílá jako celé číslo (Service Layer očekává `Edm.Int32`); nenumerická
hodnota se vynechá. Z odpovědi se čtou cookies `B1SESSION` a `ROUTEID` (s fallbackem na
`SessionId` v body) a `SessionTimeout` (minuty) pro výpočet expirace.
- **Autentizované requesty** posílají `Cookie: B1SESSION=…; ROUTEID=…`. Při `401`
connector jednou provede re-login a request zopakuje; re-login nesnižuje retry budget,
takže funguje i při `SAP_B1_RETRY_COUNT=0`.
- **Logout** `POST /b1s/v1/Logout` se posílá **s aktivní session cookie**, jinak by
Service Layer nevěděl, kterou session ukončit, a nechal by ji běžet až do timeoutu.
- **Verze a system info** login response obsahuje `Version` (např. `1000230` = SAP B1
10.0 PL 23) a `SessionTimeout`; connector si je ukládá (`client.loginInfo`, přežije
logout) a HTTP endpoint `POST /api/session/login` je vrací v odpovědi.
`GET /api/system/info` navíc vrátí přehled instalace: service document (`GET /b1s/v1/`
→ dostupné entity sety), `UserFieldsMD` (UDF), `UserTablesMD` (UDT), `UserObjectsMD`
(UDO) a `CompanyService_GetAdminInfo`. Sekce, na kterou SAP uživatel nemá práva, se
vrátí jako `{ "error": "…" }`, aby jedno chybějící oprávnění neshodilo celý přehled
(chyba je v odpovědi vidět, nejde o tiché selhání).
- **Stránkování** následuje `odata.nextLink` i `@odata.nextLink`. U absolutních
nextLinků se zachová i query string (`$skip` apod.), takže `listAll` nezacyklí.
## Reverse proxy a Swagger
Aplikace běží za AppFactory proxy na `/apps/<app-id>` (Caddy `handle_path` prefix před
předáním do containeru odstraní, takže container vidí routy bez prefixu a z requestu
veřejnou cestu nelze odvodit). Stejný přístup jako sousední služba `google-service`:
- `/docs` se servíruje **přímo jako HTML** (`GET /docs`), bez `swagger-ui-express` a bez
statického middleware proto **nevzniká žádný redirect `/docs` → `/docs/`**, který by za
proxy zahodil prefix `/apps/<app-id>` (to byla příčina bílé stránky).
- Swagger UI assety se načítají z CDN (`unpkg.com/swagger-ui-dist@5`).
- Spec URL je `ROOT_PATH + /openapi.json`, takže odkazuje na veřejné
`/apps/<app-id>/openapi.json`.
- `servers[0].url` se nastaví z `ROOT_PATH` (jinak `/`), takže Swagger `Try it out` volá
`…/apps/<app-id>/api/<resource>`.
| Kontrola | Lokálně (bez ROOT_PATH) | S `ROOT_PATH=/apps/sap-bo` |
|---|---|---|
| `GET /health` | 200 | 200 |
| `GET /docs` (Swagger UI) | 200, žádný redirect | 200, žádný redirect |
| `GET /openapi.json` | 200 | 200 |
| spec URL v `/docs` | `/openapi.json` | `/apps/sap-bo/openapi.json` |
| `servers[0].url` v OpenAPI | `/` | `/apps/sap-bo` |