Pular para conteúdo

Discounts

Este documento descreve todas as operações disponíveis para o agregado Discounts e seus recursos, seguindo os padrões de Domain-Driven Design (DDD) e Command Query Responsibility Segregation (CQRS).

Índice


Discounts (Descontos)

Visão Geral

O agregado Discount representa cupons e promoções de desconto disponíveis no sistema. Os descontos podem ser: - Públicos: Disponíveis para todos os usuários - Privados: Associados a usuários específicos (UserDiscounts)

Os descontos são aplicados em pedidos (Orders) e possuem regras de validação complexas.

Commands (Comandos)

1. Criação de Desconto (CreateDiscount)

Contrato: CreateDiscountCommand

Campos Necessários:

  • Name (string, obrigatório) - Nome do desconto
  • Description (string, obrigatório) - Descrição do desconto
  • Code (string, obrigatório) - Código do cupom de desconto
  • Value (decimal, obrigatório) - Valor do desconto
  • Type (DiscountType enum, obrigatório) - Tipo do desconto
  • Percentage - Desconto em porcentagem (ex: 10 = 10%)
  • FixedAmount - Desconto em valor fixo (ex: 50.00 = R$ 50,00)
  • StartDt (DateTime, obrigatório) - Data de início da validade
  • EndDt (DateTime, obrigatório) - Data de término da validade
  • IsActive (bool, obrigatório) - Indica se o desconto está ativo
  • MaxUses (int?, opcional) - Número máximo de usos do desconto
  • MinOrderValue (int?, opcional) - Valor mínimo do pedido para aplicar o desconto
  • IsPublic (bool?, opcional) - Indica se o desconto é público (padrão: false)
  • Observation (string, opcional) - Observações adicionais
  • Author (string, opcional) - Autor da operação

Validações:

  • Name: Não pode ser vazio; Tamanho máximo: 100 caracteres
  • Description: Tamanho máximo: 255 caracteres
  • Code: Não pode ser vazio; Tamanho máximo: 50 caracteres
  • Value: Deve ser maior que 0
  • Type: Deve ser um valor válido do enum (Percentage ou FixedAmount)
  • StartDt: Não pode ser vazio; Deve ser uma data futura (com tolerância de 5 minutos)
  • EndDt: Não pode ser vazio; Deve ser posterior à data de início
  • MaxUses: Quando fornecido, deve ser maior que 0
  • MinOrderValue: Quando fornecido, deve ser maior que 0

Retorno: CreateDiscountResponse - Id (Guid) - Identificador do desconto criado

Handler: CreateDiscountHandler

Eventos de Domínio:

  • DiscountCreatedEvent - Disparado quando um desconto é criado (se implementado)

Regras de Negócio:

  • A data de término deve ser sempre posterior à data de início
  • Descontos podem ser públicos (disponíveis para todos) ou privados (apenas para usuários específicos via UserDiscount)
  • O código do cupom deve ser único (recomendado)
  • Se IsPublic = true, o desconto aparece em buscas públicas
  • Se IsPublic = false, o desconto só pode ser usado por usuários que tenham UserDiscount associado

Exemplo de Uso:

{
  "name": "Black Friday 2024",
  "description": "Desconto especial para Black Friday",
  "code": "BLACKFRIDAY2024",
  "value": 20,
  "type": "Percentage",
  "startDt": "2024-11-24T00:00:00",
  "endDt": "2024-11-30T23:59:59",
  "isActive": true,
  "maxUses": 1000,
  "minOrderValue": 100,
  "isPublic": true,
  "observation": "Válido apenas para novos clientes",
  "author": "marketing@sistema.com"
}

Exemplo de Resposta:

{
  "id": "123e4567-e89b-12d3-a456-426614174000"
}

2. Atualização de Desconto (UpdateDiscount)

Contrato: UpdateDiscountCommand

Campos Necessários:

  • Id (Guid, obrigatório, JsonIgnore) - Identificador do desconto

Campos Opcionais (todos podem ser atualizados individualmente): - Name (string, opcional) - Nome do desconto - Description (string, opcional) - Descrição do desconto - Code (string, opcional) - Código do cupom - Value (decimal?, opcional) - Valor do desconto - Type (DiscountType?, opcional) - Tipo do desconto - StartDt (DateTime?, opcional) - Data de início - EndDt (DateTime?, opcional) - Data de término - IsActive (bool?, opcional) - Status ativo/inativo - MaxUses (int?, opcional) - Número máximo de usos - MinOrderValue (int?, opcional) - Valor mínimo do pedido - IsPublic (bool?, opcional) - Indicador público/privado - Observation (string, opcional) - Observações - Author (string, opcional) - Autor da operação

Validações:

  • Id: Não pode ser vazio
  • Name: Quando fornecido, não pode ser vazio e deve ter no máximo 100 caracteres
  • Description: Quando fornecido, deve ter no máximo 255 caracteres
  • Code: Quando fornecido, não pode ser vazio e deve ter no máximo 50 caracteres
  • Value: Quando fornecido, deve ser maior que 0
  • Type: Quando fornecido, deve ser um valor válido do enum
  • StartDt: Quando fornecido, deve ser uma data futura (com tolerância de 5 minutos)
  • EndDt: Quando fornecido, deve ser posterior à data de início (considerando StartDt atual ou novo)
  • MaxUses: Quando fornecido, deve ser maior que 0
  • MinOrderValue: Quando fornecido, deve ser maior que 0

Retorno: Void

Handler: UpdateDiscountHandler

Regras de Negócio:

  • O desconto deve existir no sistema
  • Apenas os campos fornecidos são atualizados (atualização parcial/patch)
  • As validações de datas são aplicadas considerando os valores atuais e novos:
  • Se alterar apenas EndDt, valida contra StartDt atual
  • Se alterar apenas StartDt, valida contra EndDt atual
  • Se alterar ambos, valida novo EndDt contra novo StartDt
  • Cuidado ao atualizar descontos já em uso
  • Recomenda-se criar novo desconto em vez de alterar um ativo

Exemplo de Uso:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "isActive": false,
  "observation": "Desconto encerrado antes do previsto",
  "author": "admin@sistema.com"
}

3. Exclusão de Desconto (DeleteDiscount)

Contrato: DeleteDiscountCommand

Campos Necessários:

  • Id (Guid, obrigatório) - Identificador do desconto

Validações:

  • Id: Não pode ser vazio

Retorno: Void

Handler: DeleteDiscountHandler

Regras de Negócio:

  • O desconto deve existir no sistema
  • Verifica se há dependências antes de excluir:
  • UserDiscounts (descontos associados a usuários)
  • Orders (pedidos que usaram o desconto)
  • Se houver pedidos históricos, considere inativar em vez de excluir

Recomendação: Use UpdateDiscount com IsActive = false em vez de exclusão para manter histórico.


Queries (Consultas)

1. Buscar Desconto por ID (GetDiscountById)

Contrato: GetDiscountByIdQuery

Campos Necessários:

  • Id (Guid, obrigatório) - Identificador do desconto

Retorno: GetDiscountByIdResponse - Id (Guid) - Identificador do desconto - Name (string) - Nome do desconto - Description (string) - Descrição - Code (string) - Código do cupom - Value (decimal) - Valor do desconto - Type (DiscountType) - Tipo do desconto - StartDt (DateTime) - Data de início - EndDt (DateTime) - Data de término - IsActive (bool) - Status ativo/inativo - MaxUses (int?) - Máximo de usos - MinOrderValue (int?) - Valor mínimo do pedido - IsPublic (bool?) - Público/privado - Observation (string) - Observações - CreatedAt (DateTime) - Data de criação - UpdatedAt (DateTime?) - Data da última atualização - AuthorCreated (string) - Autor da criação - AuthorUpdated (string) - Autor da última atualização

Handler: GetDiscountByIdHandler

Mapper: GetDiscountByIdMapper

Descrição:

  • Retorna todos os dados do desconto especificado
  • Inclui informações completas de configuração e auditoria
  • Se o desconto não existir, lança exceção NotFound

Exemplo de Resposta:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "Black Friday 2024",
  "description": "Desconto especial para Black Friday",
  "code": "BLACKFRIDAY2024",
  "value": 20,
  "type": "Percentage",
  "startDt": "2024-11-24T00:00:00Z",
  "endDt": "2024-11-30T23:59:59Z",
  "isActive": true,
  "maxUses": 1000,
  "minOrderValue": 100,
  "isPublic": true,
  "observation": "Válido apenas para novos clientes",
  "createdAt": "2024-11-01T10:00:00Z",
  "updatedAt": null,
  "authorCreated": "marketing@sistema.com",
  "authorUpdated": null
}

2. Pesquisar Descontos (SearchDiscounts)

Contrato: SearchDiscountsQuery

Campos Opcionais: - Type (DiscountType?, opcional) - Filtrar por tipo de desconto (Percentage ou FixedAmount) - IsActive (bool?, opcional) - Filtrar por status ativo/inativo - StartDt (DateTime?, opcional) - Filtrar por data de início - EndDt (DateTime?, opcional) - Filtrar por data de término - PageNumber (int) - Número da página (padrão: 1) - PageSize (int) - Tamanho da página (padrão: 10)

Retorno: SearchDiscountsResponse - Lista paginada de descontos que atendem aos critérios de filtro - Metadados de paginação (total de registros, total de páginas, etc.) Descrição:

  • Retorna uma lista paginada de descontos conforme os filtros aplicados
  • Ordenados por data de criação (mais recentes primeiro)
  • Suporta paginação para grandes volumes de dados

Handler: SearchDiscountsHandler

Mapper: SearchDiscountsMapper

Validações:

  • PageNumber: Deve ser maior que 0
  • PageSize: Deve estar entre 1 e 100

Regras de Negócio:

  • Retorna descontos ordenados por data de criação (mais recentes primeiro)
  • Suporta paginação para grandes volumes de dados
  • Filtros podem ser combinados

Exemplo de Uso:

{
  "type": "Percentage",
  "isActive": true,
  "pageNumber": 1,
  "pageSize": 20
}

Exemplo de Resposta:

{
  "discounts": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "name": "Black Friday 2024",
      "code": "BLACKFRIDAY2024",
      "value": 20,
      "type": "Percentage",
      "isActive": true,
      "startDt": "2024-11-24T00:00:00Z",
      "endDt": "2024-11-30T23:59:59Z"
    },
    {
      "id": "456e7890-e12b-34d5-b678-901234567890",
      "name": "Desconto Natal",
      "code": "NATAL2024",
      "value": 15,
      "type": "Percentage",
      "isActive": true,
      "startDt": "2024-12-01T00:00:00Z",
      "endDt": "2024-12-25T23:59:59Z"
    }
  ],
  "totalRecords": 2,
  "totalPages": 1,
  "currentPage": 1,
  "pageSize": 20
}

Tipos e Enums

DiscountType

public enum DiscountType
{
    Percentage,    // Desconto em porcentagem
    FixedAmount    // Desconto em valor fixo
}

Uso: - Percentage: O valor representa a porcentagem de desconto (ex: 15 = 15%) - FixedAmount: O valor representa o montante fixo de desconto (ex: 50.00 = R$ 50,00)


Conceitos de DDD e CQRS

Domain-Driven Design (DDD)

Aggregate Root: - Discount é um agregado raiz independente - Mantém consistência de suas propriedades e regras de negócio - Possui coleções de entidades relacionadas: - UserDiscounts - Descontos associados a usuários específicos - Orders - Pedidos que utilizaram o desconto - Constantes de domínio: - MaxLengthName = 100 - MaxLengthDescription = 255 - MaxLengthCode = 50

Value Objects: - Author - Representa o autor da operação - Observation - Observações sobre o desconto

Domain Methods:

  1. ValidateDiscountUsage(decimal totalPrice)
  2. Valida se o desconto pode ser usado para um pedido específico
  3. Verifica:
    • Status ativo
    • Período de validade (data atual entre StartDt e EndDt)
    • Limite de usos (MaxUses)
    • Valor mínimo do pedido (MinOrderValue)
  4. Lança RuleViolationDomainException se alguma regra for violada

  5. ValidateDiscountUsage()

  6. Versão sem validação de valor do pedido
  7. Valida apenas status, período e limite de usos

  8. Update(...)

  9. Atualiza propriedades do desconto
  10. Valida invariantes durante atualização
  11. Aplica regras de negócio (ex: EndDt > StartDt)

Invariantes: - Nome não pode exceder 100 caracteres - Descrição não pode exceder 255 caracteres - Código não pode exceder 50 caracteres - Data de término deve ser posterior à data de início - Valor deve ser maior que 0 - MaxUses, quando definido, deve ser maior que 0 - MinOrderValue, quando definido, deve ser maior que 0

Domain Exceptions: - RuleViolationDomainException - Quando regras de negócio são violadas - Desconto inativo - Fora do período de validade - Limite de usos atingido - Valor do pedido abaixo do mínimo - Validações de campos

CQRS (Command Query Responsibility Segregation)

Commands (Comandos): - CreateDiscountCommand - Cria um novo desconto - UpdateDiscountCommand - Atualiza um desconto existente - DeleteDiscountCommand - Remove um desconto - Todos os comandos passam por validação (FluentValidation) - Cada comando tem um handler dedicado - Comandos modificam o estado do sistema

Queries (Consultas): - GetDiscountByIdQuery - Busca um desconto específico - SearchDiscountsQuery - Pesquisa descontos com filtros e paginação - Queries são otimizadas para leitura - Não modificam o estado do sistema - Cada query tem um handler e mapper dedicados

Validators: - CreateDiscountValidator - Valida criação de descontos - UpdateDiscountValidator - Valida atualização de descontos - DeleteDiscountValidator - Valida exclusão de descontos - SearchDiscountsValidator - Valida parâmetros de pesquisa - Utilizam FluentValidation para regras declarativas


Relacionamentos

UserDiscount

Representa a associação entre um desconto e um usuário específico. - Permite descontos personalizados/exclusivos - Controla quantas vezes cada usuário usou o desconto - Usado para descontos privados (não públicos)

Order

Pedidos que utilizaram o desconto. - Mantém histórico de uso - Usado para calcular limite de usos - Permite auditoria e análise de efetividade


Validações de Domínio

Exemplo de Validação de Uso

public void ValidateDiscountUsage(decimal totalPrice)
{
    if (!IsActive)
        throw new RuleViolationDomainException("Discount is not active");

    if (DateTime.Now < StartDt)
        throw new RuleViolationDomainException($"Discount will only be valid from {StartDt:MM/dd/yyyy}");

    if (DateTime.Now > EndDt)
        throw new RuleViolationDomainException("Discount has expired");

    if (MaxUses.HasValue && UserDiscounts.Count >= MaxUses.Value)
        throw new RuleViolationDomainException("Discount usage limit has been reached");

    if (totalPrice < MinOrderValue)
        throw new RuleViolationDomainException($"Minimum order value to use this discount is {MinOrderValue.Value:C}");
}

Observações Importantes

  1. Período de Validade:
  2. StartDt e EndDt controlam quando o desconto pode ser usado
  3. Tolerância de 5 minutos na criação permite ajustes de última hora

  4. Tipos de Desconto:

  5. Percentage: Útil para promoções proporcionais (ex: "20% off")
  6. FixedAmount: Útil para valores fixos (ex: "R$ 50 de desconto")

  7. Limite de Usos:

  8. MaxUses = null: Ilimitado
  9. MaxUses = 1: Uso único (cupom single-use)
  10. MaxUses > 1: Limite específico

  11. Valor Mínimo:

  12. Evita uso de descontos em pedidos pequenos
  13. Protege margem de lucro

  14. Público vs Privado:

  15. Público: Código disponível para todos, basta conhecer
  16. Privado: Requer associação UserDiscount

  17. Auditoria: Todas as operações registram autor e data

  18. Atualização Parcial:

  19. UpdateDiscount permite alterar apenas campos necessários
  20. Facilita ativação/desativação rápida

  21. Códigos de Cupom:

  22. Use códigos únicos e descritivos
  23. Evite códigos facilmente adivinháveis para descontos privados
  24. Considere prefixos por campanha (ex: BF2024-, NATAL-)

  25. Estratégias Recomendadas:

  26. Descontos de boas-vindas: MaxUses=1, IsPublic=false, via UserDiscount
  27. Promoções sazonais: IsPublic=true, período limitado
  28. Descontos VIP: IsPublic=false, sem MaxUses, MinOrderValue alto

DiscountEligibilities (Elegibilidades de Desconto)

Visão Geral

O recurso DiscountEligibility gerencia quais usuários são elegíveis para usar descontos privados. Quando um desconto não é público (IsPublic = false), apenas usuários com elegibilidade associada podem utilizá-lo.

Commands (Comandos)

1. Adicionar Usuário Elegível (AddEligibleUser)

Contrato: AddEligibleUserCommand

Campos Necessários:

  • DiscountId (Guid, obrigatório) - Identificador do desconto
  • UserId (Guid, obrigatório) - Identificador do usuário
  • ExpiresAt (DateTime?, opcional) - Data de expiração da elegibilidade
  • Observation (string, opcional) - Observações adicionais
  • Author (string, opcional) - Autor da operação

Validações:

  • DiscountId: Não pode ser vazio; Desconto deve existir
  • UserId: Não pode ser vazio; Usuário deve existir
  • ExpiresAt: Quando fornecido, deve ser uma data futura

Retorno: AddEligibleUserResponse - Id (Guid) - Identificador da elegibilidade criada

Regras de Negócio:

  • Um usuário não pode ter elegibilidade duplicada para o mesmo desconto
  • A elegibilidade pode ter data de expiração independente do desconto
  • Elegibilidade é criada com IsActive = true

Exemplo de Uso:

{
  "discountId": "123e4567-e89b-12d3-a456-426614174000",
  "userId": "456e7890-e12b-34d5-b678-901234567890",
  "expiresAt": "2024-12-31T23:59:59",
  "observation": "Cliente VIP - acesso antecipado",
  "author": "admin@sistema.com"
}

2. Remover Usuário Elegível (RemoveEligibleUser)

Contrato: RemoveEligibleUserCommand

Campos Necessários:

  • DiscountId (Guid, obrigatório) - Identificador do desconto
  • UserId (Guid, obrigatório) - Identificador do usuário

Validações:

  • DiscountId: Não pode ser vazio
  • UserId: Não pode ser vazio
  • Elegibilidade deve existir para a combinação desconto/usuário

Retorno: Void

Regras de Negócio:

  • Remove a elegibilidade do usuário para o desconto especificado
  • Operação é permanente

Queries (Consultas)

1. Buscar Elegibilidade por ID (GetDiscountEligibilityById)

Contrato: GetDiscountEligibilityByIdQuery

Parâmetros:

  • DiscountId (Guid, obrigatório) - Identificador do desconto
  • UserId (Guid, obrigatório) - Identificador do usuário

Retorno: GetDiscountEligibilityByIdResponse - DiscountId (Guid) - Identificador do desconto - Name (string) - Nome do desconto - Code (string) - Código do desconto - User (object) - Dados do usuário - Id (Guid) - Identificador do usuário - Name (string) - Nome completo do usuário - Email (string) - Email do usuário - Phone (string) - Telefone do usuário - IsActive (bool) - Status ativo/inativo - ExpiresAt (DateTime?) - Data de expiração - IsValid (bool) - Se a elegibilidade é válida (ativa e não expirada) - Created (DateTime) - Data de criação - Updated (DateTime?) - Data da última atualização - Observation (string) - Observações - AuthorCreated (string) - Autor da criação - AuthorUpdated (string) - Autor da última atualização

Exemplo de Resposta:

{
  "discountId": "123e4567-e89b-12d3-a456-426614174000",
  "name": "Desconto VIP",
  "code": "VIP2024",
  "user": {
    "id": "456e7890-e12b-34d5-b678-901234567890",
    "name": "João Silva",
    "email": "joao@email.com",
    "phone": "+5511999999999"
  },
  "isActive": true,
  "expiresAt": "2024-12-31T23:59:59Z",
  "isValid": true,
  "created": "2024-01-15T10:00:00Z",
  "updated": null,
  "observation": "Cliente VIP - acesso antecipado",
  "authorCreated": "admin@sistema.com",
  "authorUpdated": null
}

2. Pesquisar Elegibilidades (SearchDiscountEligibilities)

Contrato: SearchDiscountEligibilitiesQuery

Parâmetros:

  • DiscountId (Guid?, opcional) - Filtrar por desconto
  • UserId (Guid?, opcional) - Filtrar por usuário
  • IsActive (bool?, opcional) - Filtrar por status
  • PageNumber (int, padrão: 1) - Número da página
  • PageSize (int, padrão: 10) - Tamanho da página

Retorno: SearchDiscountEligibilitiesResponse - Lista paginada de elegibilidades com os mesmos campos do GetById (exceto Observation)

Exemplo de Uso:

{
  "discountId": "123e4567-e89b-12d3-a456-426614174000",
  "isActive": true,
  "pageNumber": 1,
  "pageSize": 20
}

DiscountUsages (Uso de Descontos)

Visão Geral

O recurso DiscountUsage rastreia o histórico de uso de descontos por usuários. Cada vez que um usuário utiliza um desconto em um pedido, o registro de uso é atualizado.

Queries (Consultas)

1. Buscar Uso por ID (GetDiscountUsageById)

Contrato: GetDiscountUsageByIdQuery

Parâmetros:

  • DiscountId (Guid, obrigatório) - Identificador do desconto
  • UserId (Guid, obrigatório) - Identificador do usuário

Retorno: GetDiscountUsageByIdResponse - DiscountId (Guid) - Identificador do desconto - Name (string) - Nome do desconto - Code (string) - Código do desconto - User (object) - Dados do usuário - Id (Guid) - Identificador do usuário - Name (string) - Nome completo do usuário - Email (string) - Email do usuário - Phone (string) - Telefone do usuário - UsageCount (int) - Quantidade de vezes que o desconto foi usado - LastUsedAt (DateTime?) - Data do último uso - LastOrderId (Guid?) - ID do último pedido que usou o desconto - Created (DateTime) - Data de criação do registro - Updated (DateTime?) - Data da última atualização - Observation (string) - Observações - AuthorCreated (string) - Autor da criação - AuthorUpdated (string) - Autor da última atualização

Exemplo de Resposta:

{
  "discountId": "123e4567-e89b-12d3-a456-426614174000",
  "name": "Black Friday 2024",
  "code": "BLACKFRIDAY2024",
  "user": {
    "id": "456e7890-e12b-34d5-b678-901234567890",
    "name": "Maria Santos",
    "email": "maria@email.com",
    "phone": "+5511888888888"
  },
  "usageCount": 3,
  "lastUsedAt": "2024-11-29T15:30:00Z",
  "lastOrderId": "789a0123-b45c-67d8-e901-234567890abc",
  "created": "2024-11-24T10:00:00Z",
  "updated": "2024-11-29T15:30:00Z",
  "observation": null,
  "authorCreated": "system",
  "authorUpdated": "system"
}

2. Pesquisar Usos (SearchDiscountUsages)

Contrato: SearchDiscountUsagesQuery

Parâmetros:

  • DiscountId (Guid?, opcional) - Filtrar por desconto
  • UserId (Guid?, opcional) - Filtrar por usuário
  • UsedAfter (DateTime?, opcional) - Filtrar usos após esta data
  • UsedBefore (DateTime?, opcional) - Filtrar usos antes desta data
  • PageNumber (int, padrão: 1) - Número da página
  • PageSize (int, padrão: 10) - Tamanho da página

Retorno: SearchDiscountUsagesResponse - Lista paginada de usos com os mesmos campos do GetById (exceto Observation)

Exemplo de Uso:

{
  "discountId": "123e4567-e89b-12d3-a456-426614174000",
  "usedAfter": "2024-11-01T00:00:00",
  "usedBefore": "2024-11-30T23:59:59",
  "pageNumber": 1,
  "pageSize": 50
}

Descrição:

  • Permite análise de uso de descontos por período
  • Útil para relatórios de efetividade de campanhas
  • Suporta filtros combinados para análises específicas