Realne sluzby, OpenAI, odesilani e-mailu a helpdesk

Katalog srovnany s tim, co opravdu bezi na services.csbot.cz/apps:
trinact sluzeb dostalo pristupove udaje a levne cteci overeni, opravena
appId, ktera nikam nevedla (ppl, microsoft365, transcription), a GA4,
Search Console, Google Ads i Sklik ted stoji na aplikaci analytics,
kazda s vlastnimi udaji. Nove sluzby SAP Business One, Google Workspace
a Meta Ads. K tomu 23 skriptu, ktere s nimi opravdu neco delaji.

OpenAI jako prvni sluzba, ktera nebezi u nas: Service.baseUrl s absolutni
adresou, prepis pres <SLUZBA>_BASE_URL nebo adresu u konektoru, predpona
hlavicky u pole udaju (uzivatel vlepi holy klic, Bearer dopise runtime).
Dotaz na model, nahrani souboru, otazka nad souborem, prepis zvuku.
Skript umi odeslat soubor pres ctx.http.postForm (multipart, obsah Base64).

Sluzba E-mail pres SMTP. Neni to skript, ale vnitrni krok - SMTP neni HTTP.
Konektor nese schranku firmy, krok ma HTML telo, ve kterem se dosazene
hodnoty escapuji (znacky autora sablony jsou zamer, ostre zavorky od
zakaznika ne). Overeni konektoru se prihlasi na server a nic neodesle.

Helpdesk: Ticket.helpdeskSourceId drzi firmu, ktera pozadavek poslala,
vlastnikem zustava ta, ktera ho resi - jinak by ho resitel nemel ve sve
fronte. Komu pozadavek pripadne, urcuje Tenant.helpdeskProviderId.
Zadavatel vidi jen svoje pozadavky a smi k nim pripsat komentar.

Opravy v portalu:
- hlasky o ulozisti a odchozi IP vidi jen spravce platformy
- typ ticketu se v automatizaci vybira ze seznamu firmy, nebo dosadi z dat
- stav ticketu je otevreny naseptavac, ne ciselnik
- ticket jde zalozit rucne, zakaznik u nej neni povinny
- kanal se prejmenoval a parametry u webhooku jsou oznacene jako nepovinne
- srovnane markdown tabulky v cele dokumentaci

Co z teto davky jeste neni: prepinac firmy je porad jen stav uvnitr stranky
Prehled, takze se prepnuti neprojevi v Lidech ani jinde.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-26 07:40:16 +02:00
co-authored by Claude Opus 5
parent b25a149574
commit a771834e57
78 changed files with 7021 additions and 932 deletions
+152
View File
@@ -0,0 +1,152 @@
/**
* PPL CPL: zalozeni zasilky.
*
* Sluzba: https://services.csbot.cz/apps/pplcplapi
* Endpoint: POST /shipments/create-and-wait
*
* CPL zpracovava zasilky **davkove**: zalozi se batch, pak se ceka na jeho
* zpracovani a teprve pak je znamé cislo zasilky. Sluzba na to ma synchronni
* obalku, ktera cely tok provede za nas - proto se vola ona a ne
* `/shipments/batch`. Krok stromu, ktery by musel sam pollovat, by potreboval
* runtime s cekanim, a ten zatim neni.
*
* Idempotence: hlavicku `Idempotency-Key` doplnuje runtime a je stabilni pres
* vsechny pokusy tehoz kroku, takze druhy pokus po timeoutu neposle druhy balik.
*/
export const manifest = {
id: 'ppl.create-shipment',
name: 'Vytvořit zásilku',
description:
'Založí zásilku v PPL a počká na její zpracování. Vrací číslo zásilky ' +
'a volitelně i etiketu k tisku.',
inputs: [
{
id: 'referenceId',
label: 'Reference',
type: 'string',
required: true,
hint: 'Vaše číslo, podle kterého zásilku poznáte. Typicky číslo objednávky.',
},
{
id: 'productType',
label: 'Produkt',
type: 'string',
required: false,
default: 'BUSS',
hint: 'Kód produktu PPL, například BUSS (Business) nebo PRIV (Private). Číselník vrací /codelists/product.',
},
{ id: 'recipientName', label: 'Příjemce', type: 'string', required: true },
{ id: 'recipientStreet', label: 'Ulice a číslo', type: 'string', required: true },
{ id: 'recipientCity', label: 'Město', type: 'string', required: true },
{
id: 'recipientZip',
label: 'PSČ',
type: 'string',
required: true,
hint: 'Bez mezer, například 60200.',
},
{
id: 'recipientCountry',
label: 'Země',
type: 'string',
required: false,
default: 'CZ',
hint: 'Dvoupísmenný kód země.',
},
{ id: 'recipientPhone', label: 'Telefon příjemce', type: 'string', required: false },
{ id: 'recipientEmail', label: 'E-mail příjemce', type: 'string', required: false },
{
id: 'note',
label: 'Poznámka',
type: 'string',
required: false,
hint: 'Text pro kurýra. Diakritiku odstraní služba sama, CPL ji nepřijímá.',
},
{
id: 'withLabel',
label: 'Vrátit i etiketu',
type: 'boolean',
required: false,
default: false,
hint: 'Etiketa je binární PDF, takže se vrací jen odkaz na ni, ne obsah.',
},
],
outputs: [
{ id: 'shipmentNumber', label: 'Číslo zásilky', type: 'string', required: false },
{ id: 'batchId', label: 'ID dávky', type: 'string', required: false },
{ id: 'completed', label: 'Zpracováno', type: 'boolean', required: true },
{ id: 'result', label: 'Celá odpověď PPL', type: 'object', required: true },
],
// Sluzba uvnitr ceka na zpracovani davky. Kratsi limit by utnul i uspech.
timeoutMs: 90000,
};
/**
* Cislo zasilky z odpovedi.
*
* CPL ho vraci na ruznych mistech podle toho, kolik zasilek davka mela.
* Hledani do hloubky je levnejsi nez tri varianty `pick` za sebou, ktere by
* stejne nepokryly dalsi tvar.
*/
function findShipmentNumber(value, depth = 0) {
if (value === null || typeof value !== 'object' || depth > 5) return null;
if (!Array.isArray(value)) {
for (const [key, item] of Object.entries(value)) {
if (/^shipmentnumber$/i.test(key) && typeof item === 'string' && item !== '') return item;
}
}
for (const item of Array.isArray(value) ? value : Object.values(value)) {
const found = findShipmentNumber(item, depth + 1);
if (found !== null) return found;
}
return null;
}
export async function run(inputs, ctx) {
const { pick, text, bool } = ctx.util;
const recipient = {
name: inputs.recipientName,
street: inputs.recipientStreet,
city: inputs.recipientCity,
zipCode: inputs.recipientZip.replace(/\s+/g, ''),
country: inputs.recipientCountry,
};
if (inputs.recipientPhone) recipient.phone = inputs.recipientPhone;
if (inputs.recipientEmail) recipient.email = inputs.recipientEmail;
const shipment = {
referenceId: inputs.referenceId,
productType: inputs.productType,
recipient,
};
if (inputs.note) shipment.note = inputs.note;
const { body } = await ctx.http.post(
'/shipments/create-and-wait',
{ shipments: [shipment] },
{ query: { include_labels: inputs.withLabel } },
);
const completed = bool(pick(body, 'completed'));
const shipmentNumber = findShipmentNumber(body);
if (!completed) {
// Neni to chyba: davka bezi dal a stav se da dotahnout pres batchId.
// Vyhodit chybu by znamenalo, ze uz zalozena zasilka vypadne ze stromu.
ctx.log('PPL dávku ještě nedokončil, stav zjistíte podle ID dávky.');
}
return {
shipmentNumber,
batchId: text(pick(body, 'batchId', 'batch_id')),
completed,
result: body,
};
}