Guia de integração — Desconto avançado
Serviço: API principal (back-end Soba)
Autenticação: token Bearer (JWT) e contexto de tenant — ver Login Combinado
Introdução
Este guia descreve a integração com a API para gerir descontos avançados (regras com vários modelos: ofertas “leve X pague Y”, desconto sobre total do documento, desconto sobre linha, campanha).
O corpo do pedido é polimórfico: a propriedade discriminadora é templateType. A API exige templateType no JSON e interpreta o restante do corpo (em especial object) em função desse valor (ver Dependências condicionais do payload).
Após validação, o serviço normaliza targetObject e preenche target com o filtro de cliente já processado; em geral envie targetObject no pedido e trate target como saída derivada.
Endpoints disponíveis
| Método | URL | Descrição resumida |
|---|---|---|
| GET | /gateway/advanced-discount | Lista descontos com paginação opcional (page, pageSize); ver regras de paginação. |
| GET | /gateway/advanced-discount/{id} | Obtém um desconto pelo identificador único (UUID). |
| GET | /gateway/advanced-discount/next-order | Devolve o próximo número de ordem sugerido. |
| POST | /gateway/advanced-discount | Cria um desconto; resposta 200 OK com o UUID criado no corpo. |
| PUT | /gateway/advanced-discount/{id} | Atualiza; id na rota e no corpo coerente; resposta 204 No Content. |
| DELETE | /gateway/advanced-discount/{id} | Elimina pelo identificador único (UUID); resposta 204 No Content. |
Listar
URL: /gateway/advanced-discount — Método: GET
Obter por identificador
URL: /gateway/advanced-discount/{id} — Método: GET
Próxima ordem
URL: /gateway/advanced-discount/next-order — Método: GET
Criar
URL: /gateway/advanced-discount — Método: POST
Atualizar
URL: /gateway/advanced-discount/{id} — Método: PUT
Eliminar
URL: /gateway/advanced-discount/{id} — Método: DELETE
Estrutura do corpo do pedido
Os nomes das propriedades seguem camelCase no JSON. O discriminador templateType é obrigatório; sem ele o pedido é rejeitado na deserialização.
| Campo | Tipo | Descrição |
|---|---|---|
id | string (UUID) | Em atualização, identificador existente; em criação, pode ser vazio — o servidor gera um novo identificador. |
description | string | Obrigatório. |
active | bool | Ativo. |
includedTerminals | int[] | Terminais onde a regra se aplica; ver validação Terminais. |
discountOrder | int? | Ordem de aplicação. |
cumulative | bool | Permitir cumular com outros descontos. |
startDate | date-time? | Início de vigência (data; ver validação de “passado”). |
endDate | date-time? | Fim de vigência. |
appliedTo | int | Onde a regra se aplica: linha (0) ou documento (1); ver tabela em Enums. |
weekDaysAvailability | int (bitmask) | Dias da semana em que a regra vigora; ver Dependências condicionais e tabela de valores. |
targetObject | objeto? | Critérios de filtro do cliente alvo (estrutura aninhada com operadores lógicos e critérios). |
target | string? | Preenchido pelo servidor a partir de targetObject; não é o foco do pedido de criação/edição. |
templateType | int | Discriminador do modelo; ver tabela abaixo. |
object | objeto | Conteúdo específico do modelo; estrutura depende de templateType. |
templateType e forma de object
templateType | Modelo |
|---|---|
0 | Ofertas (leve X pague Y) |
1 | Desconto sobre o total do documento |
2 | Desconto sobre linha do documento |
3 | Campanha (várias linhas-promoção) |
object — modelo Offers (templateType === 0)
| Campo | Tipo | Descrição |
|---|---|---|
eligibility | int | 0 = artigo, 1 = família — interpreta o significado dos IDs em eligibleIdList. |
eligibleIdList | string[] | Referências elegíveis (códigos de artigo ou identificadores de família, conforme eligibility). |
triggerAmount | decimal | Quantidade “por cada” (gatilho). |
offerAmount | decimal | Quantidade oferecida. |
selectedOfferItemKeyId | string? | Artigo oferta (opcional). |
offerItemMayNotBePresent | bool | Se o artigo oferta pode não existir na venda. |
object — total do documento (templateType === 1)
| Campo | Tipo | Descrição |
|---|---|---|
selectedCalcType | int | Modo de cálculo no produto (ex.: total superior a vs por cada); o servidor não valida este inteiro contra o conjunto de tipos de oferta usado na linha. |
amount | decimal | Limiar / montante associado ao modo de cálculo. |
offerType | int | Tipo de oferta no total do documento; tem de ser um valor válido segundo a tabela Tipos de oferta / cálculo na linha. |
discount | decimal | Valor do desconto (montante ou percentagem conforme offerType). |
activeAfterNumberOfDays | int | Dias até vigor do voucher/desconto (semântica de produto). |
expiresAfterNumberOfDays | int | Validade em dias (semântica de produto). |
object — linha do documento (templateType === 2)
| Campo | Tipo | Descrição |
|---|---|---|
eligibility | int | Artigo (0) ou família (1) para eligibleIdList. |
eligibleIdList | string[] | Lista de referências; o serviço não exige não vazio neste modelo, mas o motor de negócio pode exigi-lo. |
selectedCalcType | int | Tipo de cálculo na linha; tem de ser um valor válido segundo a tabela Tipos de oferta / cálculo na linha. |
discount | decimal | Desconto (valor efetivo validado como > 0). |
activeAfterNumberOfDays | int | Igual ao modelo de total, à escala de linha. |
expiresAfterNumberOfDays | int | Idem. |
object — campanha (templateType === 3)
| Campo | Tipo | Descrição |
|---|---|---|
campaignItems | array | Lista de linhas da campanha; ver propriedades na tabela seguinte. |
Elementos de campaignItems
| Campo | Tipo | Notas |
|---|---|---|
promoId | int? | Opcional. |
id | string | Identificador da linha de campanha; obrigatório não vazio na validação do servidor. |
type | int | 0 = artigo, 1 = família / grupo. |
percentage, retailPriceHolder, fixedPrice, newPriceHolder, quantity, priceLine, … | numéricos / string | Campos de suporte ao tipo de desconto da campanha. |
discountType | int | Tipo de desconto da campanha; obrigatório e tem de ser um valor da tabela Tipos de desconto em campanha. |
afterQuantity, afterQuantityHolder, descriptionHolder | vários | Conforme cenário de campanha. |
Enums (valores numéricos no JSON)
templateType
| Valor | Significado |
|---|---|
0 | Ofertas (leve X pague Y) |
1 | Desconto no total do documento |
2 | Desconto na linha do documento |
3 | Campanha |
appliedTo
| Valor | Significado |
|---|---|
0 | Linha |
1 | Documento |
Recomendação de consistência com o produto: com templateType 0 ou 1 use appliedTo = 1 (documento); com templateType 2 ou 3 use appliedTo = 0 (linha). O servidor aceita qualquer valor definido na tabela acima; combinações incoerentes podem não fazer sentido no runtime.
weekDaysAvailability (bitmask de dias)
| Valor | Dia |
|---|---|
1 | Segunda |
2 | Terça |
4 | Quarta |
8 | Quinta |
16 | Sexta |
32 | Sábado |
64 | Domingo |
Combine com soma OR bit a bit (ex.: dias úteis = 31; todos os dias = 127).
Elegibilidade em ofertas e na linha (eligibility)
| Valor | Significado |
|---|---|
0 | Artigo |
1 | Família |
Tipo de oferta e cálculo na linha
Usado em object.offerType (total do documento) e em object.selectedCalcType (linha do documento). Os valores válidos são os mesmos códigos numéricos:
| Valor | Significado |
|---|---|
0 | Oferta por quantidade |
1 | Desconto em percentagem |
2 | Desconto em valor |
3 | Pontos (montante) |
4 | Voucher em valor |
5 | Voucher em percentagem |
6 | Preço |
7 | Oferta por quantidade (novo artigo) |
Tipos de desconto em campanha
Valor de discountType em cada elemento de campaignItems:
| Valor | Significado |
|---|---|
0 | Desconto global |
1 | Preço fixo |
2 | Desconto de linha / tabela de preço |
Dependências condicionais do payload
| Condição | O que muda no JSON / semântica |
|---|---|
templateType | Define a estrutura esperada em object. Valor desconhecido → código AdvancedDiscount.InvalidTemplateType. Se a forma de object não corresponder ao templateType, pode ocorrer AdvancedDiscount.InvalidObjectPayload. |
object.eligibility (modelo de ofertas, templateType 0) | Determina o tipo de IDs em eligibleIdList (artigo vs família). Tem de ser 0 ou 1 conforme a tabela de elegibilidade. |
object em ofertas | eligibleIdList tem de existir e ter pelo menos um elemento. offerAmount ≤ triggerAmount; ambos > 0. |
object em total documento | amount e discount têm de ser > 0 em conjunto. offerType tem de ser um código válido na tabela Tipos de oferta / cálculo na linha. |
object em linha documento | discount > 0. eligibility e selectedCalcType têm de ser valores definidos nas tabelas de enums acima. |
object em campanha | campaignItems obrigatório e não vazio. Cada item: id não vazio; discountType válido segundo Tipos de desconto em campanha. |
startDate e endDate ambos preenchidos | weekDaysAvailability não pode incluir um dia da semana que nunca ocorre no intervalo de calendário entre as duas datas (código AdvancedDiscount.WeekDaysOutsideDateRange). Se o intervalo cobre 6 dias ou mais de diferença de calendário, todos os dias da semana são permitidos no bitmask. |
includedTerminals | Sempre necessário: array não nulo e não vazio, com IDs que existam na base. |
Validações: servidor
Regras gerais (antes do ramo object)
| Regra | Código de erro |
|---|---|
startDate definida e data anterior a hoje (comparação só à parte da data) | AdvancedDiscount.StartDateInPast |
startDate e endDate definidas e startDate > endDate | AdvancedDiscount.InvalidDateRange |
appliedTo não é um valor conhecido (0 ou 1) | AdvancedDiscount.InvalidAppliedTo |
templateType não é um valor conhecido (0–3) | AdvancedDiscount.InvalidTemplateType |
weekDaysAvailability é 0 ou contém bits fora dos sete dias | AdvancedDiscount.InvalidWeekDaysAvailability |
Com startDate e endDate, bitmask de dias inclui dia fora do intervalo de calendário | AdvancedDiscount.WeekDaysOutsideDateRange |
includedTerminals nulo ou vazio | AdvancedDiscount.TerminalsRequired |
includedTerminals com IDs inexistentes | AdvancedDiscount.InvalidTerminalIds |
Forma de object incompatível com templateType | AdvancedDiscount.InvalidObjectPayload |
Por tipo de object
| Contexto | Regra | Código |
|---|---|---|
| Ofertas | triggerAmount ≤ 0 | AdvancedDiscount.OffersTriggerAmountRequired |
| Ofertas | offerAmount ≤ 0 | AdvancedDiscount.OffersOfferAmountRequired |
| Ofertas | offerAmount > triggerAmount | AdvancedDiscount.OffersOfferExceedsTrigger |
| Ofertas | eligibility inválido | AdvancedDiscount.InvalidEligibleType |
| Ofertas | eligibleIdList nulo ou vazio | AdvancedDiscount.OffersEligibleListRequired |
| Total documento | amount ≤ 0 ou discount ≤ 0 | AdvancedDiscount.DocTotalAmountAndDiscountRequired |
| Total documento | offerType inválido | AdvancedDiscount.InvalidOfferType |
| Linha documento | discount ≤ 0 | AdvancedDiscount.DocLineDiscountRequired |
| Linha documento | eligibility inválido | AdvancedDiscount.InvalidEligibleType |
| Linha documento | selectedCalcType inválido | AdvancedDiscount.InvalidSelectedCalcType |
| Campanha | campaignItems nulo ou vazio | AdvancedDiscount.CampaignItemsRequired |
| Campanha | id de um item em branco | AdvancedDiscount.CampaignItemIdRequired |
| Campanha | discountType inválido | AdvancedDiscount.InvalidCampaignDiscountType |
Permissões e persistência
| Código | Quando |
|---|---|
AdvancedDiscount.PermissionDenied | Utilizador sem permissão para criar, editar ou eliminar descontos avançados. |
AdvancedDiscount.NotFound | GET/PUT/DELETE com id inexistente. |
AdvancedDiscount.CreateFail | Falha ao persistir na criação. |
AdvancedDiscount.UpdateFail | Falha ao persistir na atualização. |
AdvancedDiscount.DeleteFail | Falha ao eliminar. |
Validações de listagem (GET)
| Parâmetro | Comportamento |
|---|---|
page | Se omitido ou < 1, usa-se página 1. |
pageSize | Se omitido ou ≤ 0, usa-se 20; caso contrário aplica-se o pedido até ao máximo 100. |
Exemplos JSON por cenário
Os exemplos usam camelCase e valores de enum em número. Ajuste includedTerminals a terminais existentes no tenant, startDate a uma data não anterior a hoje (validação do servidor) e weekDaysAvailability a um bitmask compatível com o intervalo startDate–endDate quando ambas estão definidas (ver Dependências condicionais do payload).
Ofertas — artigos elegíveis (templateType 0, eligibility 0)
{
"description": "Compre 5 unidades, oferta de 2 (artigos elegíveis)",
"active": true,
"includedTerminals": [1],
"discountOrder": 1,
"cumulative": false,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 1,
"weekDaysAvailability": 127,
"templateType": 0,
"targetObject": null,
"object": {
"eligibility": 0,
"eligibleIdList": ["ART001", "ART002"],
"triggerAmount": 5,
"offerAmount": 2,
"selectedOfferItemKeyId": "ART-GIFT",
"offerItemMayNotBePresent": false
}
}
Ofertas — famílias elegíveis (templateType 0, eligibility 1)
{
"description": "Leve 10 pague 8 em famílias selecionadas",
"active": true,
"includedTerminals": [1, 2],
"discountOrder": 2,
"cumulative": false,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-09-30T23:59:59",
"appliedTo": 1,
"weekDaysAvailability": 127,
"templateType": 0,
"targetObject": null,
"object": {
"eligibility": 1,
"eligibleIdList": ["10", "20"],
"triggerAmount": 10,
"offerAmount": 2,
"selectedOfferItemKeyId": null,
"offerItemMayNotBePresent": true
}
}
Total do documento — percentagem acima de limiar (templateType 1)
selectedCalcType 0 = “total superior a” (semântica de produto; o servidor valida sobretudo amount e discount > 0 e offerType).
{
"description": "10% de desconto em compras acima de 100 €",
"active": true,
"includedTerminals": [1],
"discountOrder": 3,
"cumulative": false,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 1,
"weekDaysAvailability": 127,
"templateType": 1,
"targetObject": null,
"object": {
"selectedCalcType": 0,
"amount": 100,
"offerType": 1,
"discount": 10,
"activeAfterNumberOfDays": 0,
"expiresAfterNumberOfDays": 0
}
}
Total do documento — valor fixo de desconto (templateType 1)
{
"description": "15 € de desconto acima de 200 € de total",
"active": true,
"includedTerminals": [1],
"discountOrder": 4,
"cumulative": true,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 1,
"weekDaysAvailability": 127,
"templateType": 1,
"targetObject": null,
"object": {
"selectedCalcType": 0,
"amount": 200,
"offerType": 2,
"discount": 15,
"activeAfterNumberOfDays": 0,
"expiresAfterNumberOfDays": 0
}
}
Total do documento — voucher percentagem com vigência em dias (templateType 1)
offerType 5 = voucher em percentagem (ver tabela de tipos de oferta). Os campos activeAfterNumberOfDays e expiresAfterNumberOfDays fazem parte do modelo; o servidor não aplica regras extra a estes inteiros além das gerais de object.
{
"description": "Voucher 5% válido após 7 dias, expira ao fim de 30 dias",
"active": true,
"includedTerminals": [1],
"discountOrder": 5,
"cumulative": false,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 1,
"weekDaysAvailability": 127,
"templateType": 1,
"targetObject": null,
"object": {
"selectedCalcType": 0,
"amount": 50,
"offerType": 5,
"discount": 5,
"activeAfterNumberOfDays": 7,
"expiresAfterNumberOfDays": 30
}
}
Linha do documento — desconto percentual por família (templateType 2)
selectedCalcType deve ser um código válido na tabela Tipos de oferta / cálculo na linha (ex.: 1 = desconto em percentagem).
{
"description": "5% em linhas de famílias de eletrónica",
"active": true,
"includedTerminals": [1],
"discountOrder": 6,
"cumulative": true,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 0,
"weekDaysAvailability": 31,
"templateType": 2,
"targetObject": null,
"object": {
"eligibility": 1,
"eligibleIdList": ["FAM001"],
"selectedCalcType": 1,
"discount": 5,
"activeAfterNumberOfDays": 0,
"expiresAfterNumberOfDays": 0
}
}
Linha do documento — desconto em valor por artigos (templateType 2)
{
"description": "2 € de desconto por linha em artigos-chave",
"active": true,
"includedTerminals": [1],
"discountOrder": 7,
"cumulative": false,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 0,
"weekDaysAvailability": 127,
"templateType": 2,
"targetObject": null,
"object": {
"eligibility": 0,
"eligibleIdList": ["SKU-A", "SKU-B"],
"selectedCalcType": 2,
"discount": 2,
"activeAfterNumberOfDays": 0,
"expiresAfterNumberOfDays": 0
}
}
Linha do documento — targetObject com filtro de cliente
{
"description": "Desconto em linha só para clientes de Lisboa",
"active": true,
"includedTerminals": [1],
"discountOrder": 8,
"cumulative": false,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 0,
"weekDaysAvailability": 127,
"templateType": 2,
"targetObject": {
"logicOperator": "And",
"criterias": [
{
"variableName": "City",
"operation": "==",
"value": "Lisboa"
}
]
},
"object": {
"eligibility": 0,
"eligibleIdList": ["PROMO-LINE-01"],
"selectedCalcType": 1,
"discount": 3,
"activeAfterNumberOfDays": 0,
"expiresAfterNumberOfDays": 0
}
}
Campanha — vários itens e tipos de desconto (templateType 3)
{
"description": "Campanha de verão — múltiplas linhas",
"active": true,
"includedTerminals": [1],
"discountOrder": 9,
"cumulative": false,
"startDate": "2026-07-01T00:00:00",
"endDate": "2026-08-31T23:59:59",
"appliedTo": 0,
"weekDaysAvailability": 127,
"templateType": 3,
"targetObject": null,
"object": {
"campaignItems": [
{
"promoId": null,
"id": "ART100",
"type": 0,
"quantity": 2,
"percentage": 10,
"retailPriceHolder": 0,
"fixedPrice": 0,
"newPriceHolder": 0,
"descriptionHolder": null,
"afterQuantity": false,
"afterQuantityHolder": null,
"discountType": 0,
"priceLine": 1
},
{
"promoId": null,
"id": "ART200",
"type": 0,
"quantity": 1,
"percentage": 0,
"retailPriceHolder": 0,
"fixedPrice": 9.99,
"newPriceHolder": 0,
"descriptionHolder": null,
"afterQuantity": false,
"afterQuantityHolder": null,
"discountType": 1,
"priceLine": 1
},
{
"promoId": null,
"id": "GRP01",
"type": 1,
"quantity": 1,
"percentage": 0,
"retailPriceHolder": 0,
"fixedPrice": 0,
"newPriceHolder": 0,
"descriptionHolder": null,
"afterQuantity": false,
"afterQuantityHolder": null,
"discountType": 2,
"priceLine": 2
}
]
}
}
Atualização — corpo com id
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"description": "Regra atualizada",
"active": true,
"includedTerminals": [1],
"discountOrder": 10,
"cumulative": false,
"startDate": "2026-06-01T00:00:00",
"endDate": "2026-12-31T23:59:59",
"appliedTo": 1,
"weekDaysAvailability": 127,
"templateType": 0,
"targetObject": null,
"object": {
"eligibility": 0,
"eligibleIdList": ["ART001"],
"triggerAmount": 4,
"offerAmount": 1,
"selectedOfferItemKeyId": null,
"offerItemMayNotBePresent": false
}
}
Para outras combinações, consulte os exemplos da operação Criar na documentação OpenAPI (Swagger) exposta pelo gateway.
Notas de integração
- Use
GET /gateway/advanced-discount/next-orderpara sugerirdiscountOrderem novas regras. - Em atualização, envie o
idcoerente com a rotaPUT /gateway/advanced-discount/{id}. - Alguns códigos de erro adicionais existem na lista interna da API (ex.:
DuplicateId,TemplateTypeRequired,ObjectRequired) mas não são devolvidos pelo fluxo atual de validação e persistência deste recurso; em caso de dúvida, baseie-se no código de problema devolvido na resposta.
Recursos relacionados
Última atualização: 14 Maio de 2026