# Integração Jasmin no ClientFlow

Esta versão adiciona a primeira integração Jasmin validada com testes reais em Jasmin 3.02.

## O que foi validado

- OAuth Client Credentials:
  - `https://identity.primaverabss.com/connect/token`
  - `grant_type=client_credentials`
  - `scope=application`
- `GET /businessCore/productInfos/getVersions`
- Clientes:
  - `GET /salesCore/customerParties/getCustomerByCompanyTaxId/{nif}`
  - `POST /salesCore/customerParties`
- Produtos:
  - `GET /salesCore/salesItems/extension/odata?$top=50`
- Orçamentos:
  - `POST /sales/quotations`
  - `documentType=ORC`
  - `serie=ORC2026`
- Faturas:
  - `POST /billing/invoices/fromQuotation/{quotationId}` com body `{}`

## Regras importantes descobertas

1. O NIF deve ser pesquisado no Jasmin sem prefixo `PT`.
2. O Jasmin rejeita `electronicMail` e `telephone` vazios; campos opcionais vazios são omitidos.
3. OData tem limite de `$top=100`.
4. Cliente novo pode usar `partyKey=CF{NIF}`.
5. Converter orçamento em fatura exige `json={}`; sem body pode devolver `411 Length Required`.

## Novas tabelas

- `customers`
- `commercial_documents`
- `commercial_document_lines`
- `shipments`

Estas tabelas permitem o pressuposto correto:

```text
Cliente
→ várias oportunidades
→ vários orçamentos
→ várias faturas
```

## Fluxo na oportunidade

A página da oportunidade tem dois botões simples:

```text
[Criar orçamento]
[Converter em fatura]
```

`Criar orçamento` faz internamente:

```text
find_or_create_customer
create_quotation
```

`Converter em fatura` faz internamente:

```text
pegar no orçamento ativo/mais recente
POST /billing/invoices/fromQuotation/{quotationId} com {}
grava a fatura ligada ao orçamento local
```

## Variáveis `.env`

```env
JASMIN_ENABLED=true
JASMIN_OUTBOX_ENABLED=true
JASMIN_BASE_URL=https://my.jasminsoftware.com
JASMIN_PUBLIC_URL=https://my.jasminsoftware.com
JASMIN_TOKEN_URL=https://identity.primaverabss.com/connect/token
JASMIN_SCOPE=application
JASMIN_ACCOUNT=...
JASMIN_SUBSCRIPTION=...
JASMIN_CLIENT_ID=...
JASMIN_CLIENT_SECRET=...

JASMIN_COMPANY_KEY=CTULDA
JASMIN_QUOTATION_TYPE=ORC
JASMIN_QUOTATION_SERIE=ORC2026
JASMIN_DEFAULT_PRICE_LIST=03
JASMIN_DEFAULT_PAYMENT_METHOD=TRA
JASMIN_DEFAULT_PAYMENT_TERM=00
JASMIN_DEFAULT_DELIVERY_TERM=TRANSP
JASMIN_DEFAULT_CURRENCY=EUR
JASMIN_DEFAULT_COUNTRY=PT
JASMIN_DEFAULT_CUSTOMER_GROUP=02
JASMIN_DEFAULT_PARTY_TAX_SCHEMA=CONTINENTE
JASMIN_DEFAULT_UNIT=UN
JASMIN_DEFAULT_ITEM_TAX_SCHEMA=NORMAL
JASMIN_DEFAULT_SALES_ITEM=CARREGADOR_MONO_7KW
```

## Teste não destrutivo

```bash
python scripts/test_jasmin_connection.py
```

## Processar outbox Jasmin

Modo seguro:

```bash
OUTBOX_TARGET_SYSTEM=jasmin JASMIN_OUTBOX_ENABLED=true OUTBOX_DRY_RUN=true python scripts/process_outbox.py
```

Modo real:

```bash
OUTBOX_TARGET_SYSTEM=jasmin JASMIN_OUTBOX_ENABLED=true OUTBOX_DRY_RUN=false python scripts/process_outbox.py
```

## Dados necessários na oportunidade

Para criar cliente Jasmin novo, a oportunidade precisa de:

- nome do cliente
- NIF em `metadata.customer_tax_id`, `metadata.nif` ou `metadata.customer.tax_id`
- morada/código postal/cidade se disponível

Para criar orçamento, as linhas da oportunidade precisam de mapear para artigos Jasmin. O serviço usa por ordem:

1. `opportunity_items.metadata.jasmin_sales_item`
2. `opportunity_items.metadata.jasmin_item_key`
3. `opportunity_items.sku`
4. `JASMIN_DEFAULT_SALES_ITEM`
