Skip to main content

Requisitos — US 197021

User Story pai: US 197021 — Data de expiração opcional para vitrines
Spike relacionada: Spike 198299 — Resultado inicial
ADRs relacionados:

Status: refinado
Sessão de grilling: decisões D1–D8 em context/grill-decisions.md (2026-07-09)


Visão geral

Permitir que o vendedor crie e edite Vitrine de loja (Minhas vitrines) e Vitrine por histórico (Histórico de compras) com prazo de expiração ou no estado Sem data de expiração, reduzindo retrabalho de recriação sem alterar regras de catálogo, jobs de produto ou compartilhamento.

A implementação reutiliza o padrão já existente na Vitrine da marca: persistir DateTime.MaxValue no Elasticsearch e expor null na API/UI. Escopo técnico: coezzion-service-showcase e coezzion_vendas_app. Vitrine da marca (zzportal), coezzion-showcase-web e jobs de expiração de vendedor permanecem fora.


Papéis

PapelResponsabilidade
Vendedor (zzapp)Criar e editar vitrines Store e Recommend com ou sem dateExpiration
coezzion-service-showcaseNormalizar dateExpiration, persistir MaxValue, validar cap, mapear DTOs, unicidade Recommend
coezzion_vendas_appPreencher default na UI, campo clearable, labels CA-4, enviar null explícito quando sem prazo
Cliente final (link público)Acessar vitrine ativa sem prazo enquanto não soft-deletada

Glossário

TermoDefinição
Vitrine de loja (Store)Vitrine em Minhas vitrines; índice arezzo-showcase
Vitrine por histórico (Recommendation)Vitrine vinculada a cliente (CPF); índice arezzo-showcase-recommend
Sem data de expiraçãoVitrine ativa sem prazo por data; API/UI: dateExpiration = null
Vitrine ativaNão soft-deletada e não expirada (DateExpiration maior ou igual a agora, ou MaxValue)
Prazo padrãoData sugerida na criação: +31 dias no AS-IS (alinhamento PO para 30 vs 31 pendente)

Detalhe: context/CONTEXT.md.


Requisitos

RF-01 — Data de expiração opcional (CA-1)

User Story: Como vendedor, eu quero que o campo data de expiração não seja obrigatório na criação e edição de vitrines, para poder manter vitrines sem prazo quando fizer sentido.

Acceptance Criteria:

  1. WHEN o vendedor cria uma Vitrine de loja no zzapp THEN o campo dateExpiration SHALL ser opcional no formulário e no payload de create.
  2. WHEN o vendedor edita uma Vitrine de loja no zzapp THEN o campo dateExpiration SHALL ser opcional no formulário e no payload de update.
  3. WHEN o vendedor cria uma Vitrine por histórico no zzapp THEN o campo dateExpiration SHALL deixar de ser obrigatório na UI e no command CreateShowCaseRecommendCommand.
  4. WHEN o vendedor edita uma Vitrine por histórico no zzapp THEN o campo dateExpiration SHALL deixar de ser obrigatório na UI e no command UpdateShowCaseRecommendCommand.
  5. IF o fluxo envolve Vitrine da marca ou Vitrine marca–vendedor THEN o sistema SHALL manter o comportamento atual sem alteração desta US.

Edge Cases:

  • Recommend AS-IS rejeita create sem data via FluentValidation NotEmpty — deve ser removido.
  • Store AS-IS aceita DateTime? mas null vira +31d no handler — semântica deve mudar (ver RF-06).

RF-02 — Criação com default na UI (CA-2, D4)

User Story: Como vendedor, eu quero que novas vitrines já venham com o prazo padrão atual, mas possa limpar essa data, para manter o hábito atual e ainda optar por sem expiração.

Acceptance Criteria:

  1. WHEN o vendedor abre o formulário de criação de Vitrine de loja ou Vitrine por histórico THEN o zzapp SHALL pré-preencher dateExpiration com o prazo padrão atual (+31 dias no AS-IS).
  2. WHEN o vendedor limpa o campo de data antes de salvar a criação THEN o zzapp SHALL enviar dateExpiration: null explicitamente no request.
  3. IF o request de create chega com dateExpiration: null THEN o coezzion-service-showcase SHALL gravar sem expiração (MaxValue) e SHALL NOT reaplicar o prazo padrão no servidor.
  4. WHEN o vendedor mantém o default pré-preenchido e salva THEN o zzapp SHALL enviar a data default no request e a vitrine SHALL expirar na data informada.
  5. IF o vendedor informa uma data de expiração na criação THEN o sistema SHALL validar que a data é futura e respeita o teto máximo de N dias (ver RF-08).

Edge Cases:

  • Create Minhas sem tocar no campo → envia default +N → expira normalmente.
  • Create Minhas com clear → null → sem expiração.

RF-03 — Edição da expiração (CA-3)

User Story: Como vendedor, eu quero adicionar, alterar ou remover a data de expiração de vitrines existentes, para ajustar a vigência conforme o uso.

Acceptance Criteria:

  1. WHEN o vendedor edita uma vitrine Store ou Recommend que possui dateExpiration definida THEN o zzapp SHALL permitir alterar a data respeitando o teto máximo (RF-08).
  2. WHEN o vendedor remove a data de expiração na edição THEN o zzapp SHALL enviar dateExpiration: null e a vitrine SHALL passar ao estado Sem data de expiração.
  3. WHEN o vendedor edita uma vitrine sem data de expiração THEN o zzapp SHALL exibir o campo de data e permitir informar um novo prazo.
  4. WHEN o update é processado com dateExpiration: null THEN o coezzion-service-showcase SHALL persistir DateTime.MaxValue e SHALL NOT converter null em +31 dias.
  5. IF o vendedor não possui permissão no fluxo de edição existente THEN o sistema SHALL manter as regras de autorização atuais sem regressão.

Edge Cases:

  • Edit: remove data de vitrine com prazo → MaxValue.
  • Edit: adiciona data em vitrine sem prazo → data informada com validação de cap.

RF-04 — Identificação da vigência (CA-4)

User Story: Como vendedor, eu quero ver claramente quais vitrines não têm prazo, para distinguir vigência indefinida de data fixa.

Acceptance Criteria:

  1. WHEN a listagem de Minhas vitrines exibe um item com dateExpiration: null na resposta da API THEN o zzapp SHALL exibir o texto "Sem data de expiração" no lugar da data formatada.
  2. WHEN o detalhe de uma Vitrine de loja possui dateExpiration: null THEN o zzapp SHALL exibir "Sem data de expiração".
  3. WHEN a listagem ou detalhe de Vitrine por histórico possui dateExpiration: null THEN o zzapp SHALL exibir "Sem data de expiração" (reutilizando nullExpirationLabel onde já existir).
  4. WHEN a API retorna dateExpiration com valor ISO válido THEN o zzapp SHALL continuar exibindo a data formatada como hoje.
  5. IF o documento no Elasticsearch contém DateExpiration = DateTime.MaxValue THEN a API SHALL mapear para null na resposta e SHALL NOT expor ano 9999 na UI.

Edge Cases:

  • MaxValue no ES nunca deve aparecer como data legível no app.

RF-05 — Compatibilidade com regras existentes (CA-5, D7)

User Story: Como vendedor, eu quero que estoque, preços, disponibilidade e compartilhamento continuem funcionando, para não perder confiança no catálogo compartilhado.

Acceptance Criteria:

  1. WHEN uma vitrine Store ou Recommend está no estado Sem data de expiração e ativa THEN os jobs de sincronização de produto (ShowCaseProductJob, listeners de pedido) SHALL continuar atualizando estoque, preço e disponibilidade sem alteração de comportamento.
  2. WHEN o vendedor compartilha ou copia o link de uma vitrine sem expiração THEN o zzapp SHALL manter o fluxo de share/copy existente.
  3. WHEN um cliente acessa o link público de vitrine sem expiração THEN o coezzion-showcase-web SHALL exibir a vitrine enquanto a API retornar documento ativo.
  4. WHEN jobs de status de Vitrine da marca executam THEN o sistema SHALL manter comportamento atual sem impacto em Store/Recommend.
  5. IF a vitrine possui prazo definido e a data passou THEN o sistema SHALL manter o comportamento AS-IS de exclusão da listagem e do GET público ativo.

Edge Cases:

  • Link público em vitrine sem prazo após meses → continua acessível se não deletada e produtos ok.
  • Link público após expiração de vitrine com prazo → "Vitrine não encontrada" (AS-IS).

RF-06 — Persistência sem expiração (D1)

User Story: Como time de plataforma, eu quero persistir "sem expiração" de forma compatível com Elasticsearch e filtros ativos, para evitar regressão em listagens e acesso público.

Acceptance Criteria:

  1. WHEN o handler de create ou update Store ou Recommend recebe dateExpiration: null THEN o coezzion-service-showcase SHALL persistir DateExpiration = DateTime.MaxValue no documento Elasticsearch.
  2. WHEN o handler persiste qualquer vitrine Store ou Recommend THEN o sistema SHALL NOT gravar null no campo de data do Elasticsearch.
  3. WHEN SetDateExpiration é invocado para Store com argumento null THEN o método SHALL NOT aplicar DateCreated + 31 dias como fallback.
  4. IF possível sem alterar handlers de Brand THEN o serviço SHALL extrair ou reutilizar helper de normalização never-expire alinhado ao padrão Brand.

Edge Cases:

  • Null no ES em campo date_range pode sumir da listagem — mitigação obrigatória via MaxValue.

RF-07 — Contrato API de leitura (D1)

User Story: Como consumidor da API (zzapp), eu quero receber null quando a vitrine não expira, para renderizar a UI corretamente.

Acceptance Criteria:

  1. WHEN qualquer DTO de leitura Store ou Recommend serializa um documento com DateExpiration = DateTime.MaxValue THEN a API SHALL retornar dateExpiration: null.
  2. WHEN qualquer DTO de leitura serializa documento com data finita THEN a API SHALL retornar a data em formato ISO consistente com o contrato atual.
  3. FOR ALL endpoints de listagem e detalhe Store/Recommend usados pelo zzapp THEN o mapeamento MaxValue→null SHALL ser aplicado de forma uniforme.

Contrato de referência:

Request dateExpiration:
- iso-date = expira nessa data
- null = sem expiração (preferir null explícito)
Response dateExpiration:
- null = sem data de expiração
- iso-date = data

RF-08 — Validação de teto de dias (D8)

User Story: Como produto, eu quero que datas informadas respeitem o máximo permitido, para manter a política de prazo mesmo com expiração opcional.

Acceptance Criteria:

  1. IF o request de create ou update Store ou Recommend informa dateExpiration com valor não-nulo THEN o coezzion-service-showcase SHALL validar que a data é estritamente futura.
  2. IF o request informa dateExpiration não-nulo THEN o validator SHALL rejeitar datas além do teto máximo N dias a partir de agora (constante única; AS-IS +31 até alinhamento PO).
  3. WHEN a validação de teto falha THEN a API SHALL retornar erro de validação sem persistir alteração.
  4. WHEN o zzapp exibe o seletor de data THEN o componente SHALL manter limite máximo na UI coerente com N dias.

Edge Cases:

  • Divergência 30 (SP) vs 31 (AS-IS) — pendência PO; usar constante única documentada em código.

RF-09 — Unicidade de Recommendation (D6)

User Story: Como vendedor, eu quero que apenas uma vitrine ativa exista por cliente na loja, inclusive quando sem prazo, para evitar conflito de recomendações.

Acceptance Criteria:

  1. WHEN existe Vitrine por histórico ativa com DateExpiration = DateTime.MaxValue para a combinação userId + customerCpf + storeId THEN o sistema SHALL bloquear criação de segunda vitrine ativa para o mesmo cliente.
  2. WHEN a verificação de unicidade avalia vitrines ativas THEN vitrine com MaxValue SHALL ser considerada vitrine ativa.
  3. WHEN a vitrine sem expiração é soft-deletada ou passa a ter data expirada THEN o sistema SHALL permitir nova criação para o mesmo cliente conforme regras atuais.

Edge Cases:

  • Recommend sem prazo "prende" cliente para sempre enquanto ativa — vendedor pode editar ou excluir.

RF-10 — Vitrines expiradas e migração (D5)

User Story: Como produto, eu quero limitar o escopo do MVP, para entregar valor sem reabrir fluxos de reativação em massa.

Acceptance Criteria:

  1. WHEN uma vitrine Store ou Recommend já expirou antes desta US THEN o sistema SHALL continuar excluindo-a das listagens e gets ativos sem oferecer reativação no zzapp.
  2. WHEN esta US é implantada THEN o sistema SHALL NOT executar migração em massa de vitrines existentes para sem expiração.
  3. WHEN esta US é implantada THEN o sistema SHALL NOT introduzir job de notificação "vai expirar" nem hard-delete de documentos expirados no ES.

RF-11 — Testes e deploy coordenado

User Story: Como desenvolvedor, eu quero cobertura de testes e deploy alinhado, para evitar regressão de contrato entre API e app.

Acceptance Criteria:

  1. WHEN a suíte de testes unitários do coezzion-service-showcase executa após as mudanças THEN os casos null=never, data válida, data passada, max excedido e unicidade com MaxValue SHALL passar.
  2. WHEN testes de serialização no coezzion_vendas_app cobrem payloads com dateExpiration: null THEN os testes SHALL passar sem regressão.
  3. IF versões antigas do zzapp enviam null esperando +31 no create Store THEN o time SHALL coordenar deploy API + app ou avaliar feature flag antes do merge.
  4. WHEN QA valida cenários da seção 8 do resultado inicial THEN todos os critérios funcionais e técnicos listados SHALL ser atendidos.

Fora de Escopo

  • Alterar regras ou UX de Vitrine da marca no zzportal (coezzion-portal).
  • Exibir estado de expiração no coezzion-showcase-web ("expirou em…").
  • Job de notificação de vitrine prestes a expirar.
  • Listar ou reativar vitrines já expiradas no zzapp.
  • Migração em massa de vitrines existentes para sem expiração.
  • Hard-delete de documentos expirados no Elasticsearch.

Dependências

DependênciaDescriçãoStatus
Spike 198299Plano técnico e decisões D1–D8Concluída
Alinhamento PO30 vs 31 dias (default e máximo) — D3Pendente (Fase 0)
Copy POTexto "Sem data de expiração"Confirmado AS-IS app/portal
PR1 coezzion-service-showcaseContrato API fixo (RF-06, RF-07, RF-08)Bloqueante recomendado para PR2
PR2 coezzion_vendas_appUI Minhas + HistóricoDepende de PR1 ou contrato acordado
Brand handlersNão alterar paths; helper compartilhado com cuidadoDecidido D7
Feature 196064Parent no Azure DevOps (notificações vitrine)Relacionada, fora do escopo desta US

Rastreabilidade

RFCA / DecisãoRepo principal
RF-01CA-1, D2showcase API + zzapp
RF-02CA-2, D4zzapp + showcase API
RF-03CA-3zzapp + showcase API
RF-04CA-4zzapp + showcase API
RF-05CA-5, D7jobs (regressão), zzapp, showcase-web
RF-06D1showcase API
RF-07D1showcase API
RF-08D8showcase API + zzapp
RF-09D6showcase API
RF-10D5
RF-11Plano spike §6–8showcase API + zzapp