Pular para o conteúdo principal

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étodoURLDescrição resumida
GET/gateway/advanced-discountLista 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-orderDevolve o próximo número de ordem sugerido.
POST/gateway/advanced-discountCria 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-discountMétodo: GET

Ver mais →

Obter por identificador

URL: /gateway/advanced-discount/{id}Método: GET

Ver mais →

Próxima ordem

URL: /gateway/advanced-discount/next-orderMétodo: GET

Ver mais →

Criar

URL: /gateway/advanced-discountMétodo: POST

Ver mais →

Atualizar

URL: /gateway/advanced-discount/{id}Método: PUT

Ver mais →

Eliminar

URL: /gateway/advanced-discount/{id}Método: DELETE

Ver mais →


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.

CampoTipoDescrição
idstring (UUID)Em atualização, identificador existente; em criação, pode ser vazio — o servidor gera um novo identificador.
descriptionstringObrigatório.
activeboolAtivo.
includedTerminalsint[]Terminais onde a regra se aplica; ver validação Terminais.
discountOrderint?Ordem de aplicação.
cumulativeboolPermitir cumular com outros descontos.
startDatedate-time?Início de vigência (data; ver validação de “passado”).
endDatedate-time?Fim de vigência.
appliedTointOnde a regra se aplica: linha (0) ou documento (1); ver tabela em Enums.
weekDaysAvailabilityint (bitmask)Dias da semana em que a regra vigora; ver Dependências condicionais e tabela de valores.
targetObjectobjeto?Critérios de filtro do cliente alvo (estrutura aninhada com operadores lógicos e critérios).
targetstring?Preenchido pelo servidor a partir de targetObject; não é o foco do pedido de criação/edição.
templateTypeintDiscriminador do modelo; ver tabela abaixo.
objectobjetoConteúdo específico do modelo; estrutura depende de templateType.

templateType e forma de object

templateTypeModelo
0Ofertas (leve X pague Y)
1Desconto sobre o total do documento
2Desconto sobre linha do documento
3Campanha (várias linhas-promoção)

object — modelo Offers (templateType === 0)

CampoTipoDescrição
eligibilityint0 = artigo, 1 = família — interpreta o significado dos IDs em eligibleIdList.
eligibleIdListstring[]Referências elegíveis (códigos de artigo ou identificadores de família, conforme eligibility).
triggerAmountdecimalQuantidade “por cada” (gatilho).
offerAmountdecimalQuantidade oferecida.
selectedOfferItemKeyIdstring?Artigo oferta (opcional).
offerItemMayNotBePresentboolSe o artigo oferta pode não existir na venda.

objecttotal do documento (templateType === 1)

CampoTipoDescrição
selectedCalcTypeintModo 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.
amountdecimalLimiar / montante associado ao modo de cálculo.
offerTypeintTipo de oferta no total do documento; tem de ser um valor válido segundo a tabela Tipos de oferta / cálculo na linha.
discountdecimalValor do desconto (montante ou percentagem conforme offerType).
activeAfterNumberOfDaysintDias até vigor do voucher/desconto (semântica de produto).
expiresAfterNumberOfDaysintValidade em dias (semântica de produto).

objectlinha do documento (templateType === 2)

CampoTipoDescrição
eligibilityintArtigo (0) ou família (1) para eligibleIdList.
eligibleIdListstring[]Lista de referências; o serviço não exige não vazio neste modelo, mas o motor de negócio pode exigi-lo.
selectedCalcTypeintTipo de cálculo na linha; tem de ser um valor válido segundo a tabela Tipos de oferta / cálculo na linha.
discountdecimalDesconto (valor efetivo validado como > 0).
activeAfterNumberOfDaysintIgual ao modelo de total, à escala de linha.
expiresAfterNumberOfDaysintIdem.

objectcampanha (templateType === 3)

CampoTipoDescrição
campaignItemsarrayLista de linhas da campanha; ver propriedades na tabela seguinte.

Elementos de campaignItems

CampoTipoNotas
promoIdint?Opcional.
idstringIdentificador da linha de campanha; obrigatório não vazio na validação do servidor.
typeint0 = artigo, 1 = família / grupo.
percentage, retailPriceHolder, fixedPrice, newPriceHolder, quantity, priceLine, …numéricos / stringCampos de suporte ao tipo de desconto da campanha.
discountTypeintTipo de desconto da campanha; obrigatório e tem de ser um valor da tabela Tipos de desconto em campanha.
afterQuantity, afterQuantityHolder, descriptionHolderváriosConforme cenário de campanha.

Enums (valores numéricos no JSON)

templateType

ValorSignificado
0Ofertas (leve X pague Y)
1Desconto no total do documento
2Desconto na linha do documento
3Campanha

appliedTo

ValorSignificado
0Linha
1Documento

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)

ValorDia
1Segunda
2Terça
4Quarta
8Quinta
16Sexta
32Sábado
64Domingo

Combine com soma OR bit a bit (ex.: dias úteis = 31; todos os dias = 127).

Elegibilidade em ofertas e na linha (eligibility)

ValorSignificado
0Artigo
1Famí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:

ValorSignificado
0Oferta por quantidade
1Desconto em percentagem
2Desconto em valor
3Pontos (montante)
4Voucher em valor
5Voucher em percentagem
6Preço
7Oferta por quantidade (novo artigo)

Tipos de desconto em campanha

Valor de discountType em cada elemento de campaignItems:

ValorSignificado
0Desconto global
1Preço fixo
2Desconto de linha / tabela de preço

Dependências condicionais do payload

CondiçãoO que muda no JSON / semântica
templateTypeDefine 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 ofertaseligibleIdList tem de existir e ter pelo menos um elemento. offerAmounttriggerAmount; ambos > 0.
object em total documentoamount 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 documentodiscount > 0. eligibility e selectedCalcType têm de ser valores definidos nas tabelas de enums acima.
object em campanhacampaignItems 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 preenchidosweekDaysAvailability 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.
includedTerminalsSempre 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)

RegraCódigo de erro
startDate definida e data anterior a hoje (comparação só à parte da data)AdvancedDiscount.StartDateInPast
startDate e endDate definidas e startDate > endDateAdvancedDiscount.InvalidDateRange
appliedTo não é um valor conhecido (0 ou 1)AdvancedDiscount.InvalidAppliedTo
templateType não é um valor conhecido (03)AdvancedDiscount.InvalidTemplateType
weekDaysAvailability é 0 ou contém bits fora dos sete diasAdvancedDiscount.InvalidWeekDaysAvailability
Com startDate e endDate, bitmask de dias inclui dia fora do intervalo de calendárioAdvancedDiscount.WeekDaysOutsideDateRange
includedTerminals nulo ou vazioAdvancedDiscount.TerminalsRequired
includedTerminals com IDs inexistentesAdvancedDiscount.InvalidTerminalIds
Forma de object incompatível com templateTypeAdvancedDiscount.InvalidObjectPayload

Por tipo de object

ContextoRegraCódigo
OfertastriggerAmount0AdvancedDiscount.OffersTriggerAmountRequired
OfertasofferAmount0AdvancedDiscount.OffersOfferAmountRequired
OfertasofferAmount > triggerAmountAdvancedDiscount.OffersOfferExceedsTrigger
Ofertaseligibility inválidoAdvancedDiscount.InvalidEligibleType
OfertaseligibleIdList nulo ou vazioAdvancedDiscount.OffersEligibleListRequired
Total documentoamount0 ou discount0AdvancedDiscount.DocTotalAmountAndDiscountRequired
Total documentoofferType inválidoAdvancedDiscount.InvalidOfferType
Linha documentodiscount0AdvancedDiscount.DocLineDiscountRequired
Linha documentoeligibility inválidoAdvancedDiscount.InvalidEligibleType
Linha documentoselectedCalcType inválidoAdvancedDiscount.InvalidSelectedCalcType
CampanhacampaignItems nulo ou vazioAdvancedDiscount.CampaignItemsRequired
Campanhaid de um item em brancoAdvancedDiscount.CampaignItemIdRequired
CampanhadiscountType inválidoAdvancedDiscount.InvalidCampaignDiscountType

Permissões e persistência

CódigoQuando
AdvancedDiscount.PermissionDeniedUtilizador sem permissão para criar, editar ou eliminar descontos avançados.
AdvancedDiscount.NotFoundGET/PUT/DELETE com id inexistente.
AdvancedDiscount.CreateFailFalha ao persistir na criação.
AdvancedDiscount.UpdateFailFalha ao persistir na atualização.
AdvancedDiscount.DeleteFailFalha ao eliminar.

Validações de listagem (GET)

ParâmetroComportamento
pageSe omitido ou < 1, usa-se página 1.
pageSizeSe 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 startDateendDate 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-order para sugerir discountOrder em novas regras.
  • Em atualização, envie o id coerente com a rota PUT /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