Visita AdhocAdhoc VisitVisita Adhoc
Cria uma visita não planejada a um varejo, na hora, direto da Lista de varejos. Uma única chamada gRPC (getAdhocVisit) traz de volta tudo o que aquela visita precisa — visita, call tasks, pedidos, estoque, financeiro, pesquisas, promoções, merchandising, calculadora de margem e catálogo — e o app funde aditivamente esse reply em 11 caches locais. Feature online (sem mock), remote-first, sem tela própria: é um botão no card do varejo + um modal de confirmação.
Creates an unplanned visit to a retail, on the spot, straight from the Retails list. A single gRPC call (getAdhocVisit) brings back everything that visit needs — visit, call tasks, orders, stock, financials, surveys, promotions, merchandising, margin calculator and catalog — and the app additively merges that reply into 11 local caches. An online feature (no mock), remote-first, with no screen of its own: a button on the retail card + a confirmation modal.
Crea una visita no planificada a un punto de venta, al instante, directo desde la Lista de puntos de venta. Una única llamada gRPC (getAdhocVisit) trae de vuelta todo lo que esa visita necesita — visita, call tasks, pedidos, stock, financiero, encuestas, promociones, merchandising, calculadora de margen y catálogo — y la app fusiona aditivamente ese reply en 11 cachés locales. Feature online (sin mock), remote-first, sin pantalla propia: es un botón en la tarjeta del punto de venta + un modal de confirmación.
O que é e para que serveWhat it is and what it's forQué es y para qué sirve
Nem toda visita está no roteiro. Quando o representante de vendas passa por um varejo que não estava planejado para o dia, a Visita Adhoc cria essa visita na hora: o app pede ao backend uma "fotografia" completa daquele varejo e a incorpora ao que já está no aparelho, para que o representante possa trabalhar a visita normalmente — abrir pedidos, checar estoque, responder call tasks, ver o financeiro etc. Not every visit is on the route. When the sales rep passes by a retail that wasn't planned for the day, the Adhoc Visit creates that visit on the spot: the app asks the backend for a full "snapshot" of that retail and merges it into whatever is already on the device, so the rep can work the visit normally — place orders, check stock, answer call tasks, see financials, etc. No toda visita está en la ruta. Cuando el representante de ventas pasa por un punto de venta que no estaba planificado para el día, la Visita Adhoc crea esa visita al instante: la app le pide al backend una "fotografía" completa de ese punto de venta y la incorpora a lo que ya está en el dispositivo, para que el representante trabaje la visita con normalidad — abrir pedidos, revisar stock, responder call tasks, ver el financiero, etc.
Na horaOn the spotAl instante
Um toque no botão ADHOC do card do varejo cria a visita — sem sair da Lista de varejos.One tap on the retail card's ADHOC button creates the visit — without leaving the Retails list.Un toque en el botón ADHOC de la tarjeta crea la visita — sin salir de la Lista de puntos de venta.
Traz tudoBrings everythingTrae todo
Uma só chamada devolve visita, pedidos, estoque, call tasks, financeiro, pesquisas, promoções, merchandising, margem e catálogo.A single call returns visit, orders, stock, call tasks, financials, surveys, promotions, merchandising, margin and catalog.Una sola llamada devuelve visita, pedidos, stock, call tasks, financiero, encuestas, promociones, merchandising, margen y catálogo.
Não apaga nadaErases nothingNo borra nada
O reply é fundido aditivamente: substitui só os dados daquele varejo/visita e preserva o resto do cache.The reply merges additively: it replaces only that retail/visit's data and keeps the rest of the cache.El reply se fusiona aditivamente: reemplaza solo los datos de ese punto/visita y conserva el resto del caché.
Adhoc ≠ Visita planejadaAdhoc ≠ Planned visitAdhoc ≠ Visita planificada
A visita planejada já vem no roteiro sincronizado. A adhoc é criada em runtime para um varejo que não estava no roteiro — o VisitType.adhoc marca essas visitas. O botão só aparece nos varejos elegíveis (isAdhocVisitCreationAvailable).
The planned visit already comes in the synced route. The adhoc one is created at runtime for a retail that wasn't on the route — VisitType.adhoc tags those visits. The button only shows on eligible retails (isAdhocVisitCreationAvailable).
La visita planificada ya viene en la ruta sincronizada. La adhoc se crea en runtime para un punto de venta que no estaba en la ruta — VisitType.adhoc etiqueta esas visitas. El botón solo aparece en los puntos elegibles (isAdhocVisitCreationAvailable).
Como acessarHow to openCómo acceder
- Abra a Lista de varejosOpen the Retails listAbra la Lista de puntos de ventaNo menu lateral, entre em Varejos e ache o varejo desejado.From the drawer, open Retails and find the target retail.Desde el menú lateral, abra Puntos de venta y busque el punto deseado.
- Toque em ADHOCTap ADHOCToque ADHOCO botão ADHOC aparece no card do varejo somente quando ele é elegível. Toque para começar.The ADHOC button shows on the retail card only when it's eligible. Tap it to start.El botón ADHOC aparece en la tarjeta solo cuando es elegible. Tóquelo para empezar.
- Confirme no modalConfirm in the modalConfirme en el modalUm modal pede confirmação (nome do varejo). Ao confirmar, o app cria a visita e mostra um aviso verde de sucesso.A modal asks for confirmation (retail name). On confirm, the app creates the visit and shows a green success notice.Un modal pide confirmación (nombre del punto). Al confirmar, la app crea la visita y muestra un aviso verde de éxito.
Estrutura do fluxoFlow structureEstructura del flujo
A Visita Adhoc não tem tela própria. Ela é um botão + um modal na Lista de varejos, e o resultado alimenta as outras telas (Visitas, Detalhe da visita, Pedidos…). Três superfícies visíveis:The Adhoc Visit has no screen of its own. It's a button + a modal in the Retails list, and its result feeds the other screens (Visits, Visit detail, Orders…). Three visible surfaces:La Visita Adhoc no tiene pantalla propia. Es un botón + un modal en la Lista de puntos de venta, y su resultado alimenta las otras pantallas (Visitas, Detalle de la visita, Pedidos…). Tres superficies visibles:
- Botão ADHOCADHOC buttonBotón ADHOC
- Um badge/botão no card do varejo, visível só quando
isAdhocVisitCreationAvailable. Enquanto cria, mostra estado de carregamento.A badge/button on the retail card, visible only whenisAdhocVisitCreationAvailable. While creating, it shows a loading state.Un badge/botón en la tarjeta, visible solo cuandoisAdhocVisitCreationAvailable. Mientras crea, muestra estado de carga. - Modal de confirmaçãoConfirmation modalModal de confirmación
- Título "criar visita adhoc" com o nome do varejo, botão de confirmar e de cancelar."Create adhoc visit" title with the retail name, a confirm and a cancel button.Título "crear visita adhoc" con el nombre del punto, botón de confirmar y de cancelar.
- Aviso de resultadoResult noticeAviso de resultado
- Sucesso: aviso verde com nome + SAP do varejo. Falha (ex.: reply sem visita): aviso vermelho.Success: green notice with the retail name + SAP. Failure (e.g. a reply with no visit): red notice.Éxito: aviso verde con nombre + SAP del punto. Falla (ej.: reply sin visita): aviso rojo.
StatusStatusEstado
O fluxo tem quatro fases (AdhocVisitCreationPhase), por varejo:The flow has four phases (AdhocVisitCreationPhase), per retail:El flujo tiene cuatro fases (AdhocVisitCreationPhase), por punto de venta:
Anti reentrânciaNo re-entrySin reentrada
Enquanto a fase for creating, um novo toque é ignorado — não dispara uma segunda chamada para o mesmo varejo. Ao completar, o app invalida retailsProvider e visitsProvider para as listas se atualizarem.
While the phase is creating, a new tap is ignored — it won't fire a second call for the same retail. On completion, the app invalidates retailsProvider and visitsProvider so the lists refresh.
Mientras la fase sea creating, un nuevo toque se ignora — no dispara una segunda llamada para el mismo punto. Al completar, la app invalida retailsProvider y visitsProvider para que las listas se actualicen.
Ações: criar visita adhocActions: create adhoc visitAcciones: crear visita adhoc
- CriarCreateCrear
- Confirmar no modal dispara a criação: o app lê a posição (geolocalização, quando disponível) e chama o backend com o varejo escolhido.Confirming in the modal fires creation: the app reads the position (geolocation, when available) and calls the backend with the chosen retail.Confirmar en el modal dispara la creación: la app lee la posición (geolocalización, cuando esté disponible) y llama al backend con el punto elegido.
- FeedbackFeedbackFeedback
- Sucesso mostra um aviso verde interpolando nome e SAP do varejo; falha mostra um aviso vermelho.Success shows a green notice interpolating the retail name and SAP; failure shows a red notice.El éxito muestra un aviso verde interpolando nombre y SAP del punto; la falla muestra un aviso rojo.
- DepoisAfterDespués
- A visita passa a existir em Visitas e nas demais telas — como qualquer visita — a partir dos caches recém-fundidos.The visit then exists in Visits and the other screens — like any visit — from the freshly merged caches.La visita pasa a existir en Visitas y en las demás pantallas — como cualquier visita — a partir de los cachés recién fusionados.
Precisa de redeNeeds networkNecesita red A Visita Adhoc é online-only: não há mock nem fallback de cache. Sem conexão (ou se o reply vier sem nenhuma visita), a criação falha com aviso vermelho — nada é gravado. The Adhoc Visit is online-only: there's no mock and no cache fallback. With no connection (or if the reply comes back with no visit at all), creation fails with a red notice — nothing is written. La Visita Adhoc es online-only: no hay mock ni fallback de caché. Sin conexión (o si el reply vuelve sin ninguna visita), la creación falla con aviso rojo — no se graba nada.
Arquitetura e fluxo de dadosArchitecture & data flowArquitectura y flujo de datos
Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. Um único fluxo remote-first de escrita-e-leitura: o botão dispara o provider, que chama o UseCase → Repository → RPC remoto getAdhocVisit. O reply vira AdhocVisitResultEntity (achatado) e o merge coordinator funde suas 15 listas em 11 datasources locais. Não há Local datasource nem Model próprios do agregado — ele é transiente e se dissolve nos caches de cada feature.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. A single remote-first read-and-write flow: the button fires the provider, which calls the UseCase → Repository → remote RPC getAdhocVisit. The reply becomes an AdhocVisitResultEntity (flattened) and the merge coordinator merges its 15 lists into 11 local datasources. There is no Local datasource nor Model for the aggregate — it is transient and dissolves into each feature's caches.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. Un único flujo remote-first de escritura-y-lectura: el botón dispara el provider, que llama al UseCase → Repository → RPC remoto getAdhocVisit. El reply se vuelve AdhocVisitResultEntity (aplanado) y el merge coordinator fusiona sus 15 listas en 11 datasources locales. No hay Local datasource ni Model propios del agregado — es transitorio y se disuelve en los cachés de cada feature.
- RetailCardWidget · ADHOCUI
- create(retailName, accountSapCustomerId)AdhocVisitCreationprovider · family(accountSfid)
- execute(accountSfid, geo?)CreateAdhocVisitUseCase
- createAdhocVisitAdhocVisitRepositoryImplremote-first · sem fallback
- getAdhocVisit (gRPC)AdhocVisitRemoteDataSourceAdhocVisitReply → DTO
- toDomainAdhocVisitResultEntity15 listas achatadas
- merge(result, accountSfid)AdhocVisitMergeCoordinator→ 11 caches ObjectBox
- toDomainAdhocVisitResultEntity15 listas achatadas
- getAdhocVisit (gRPC)AdhocVisitRemoteDataSourceAdhocVisitReply → DTO
- createAdhocVisitAdhocVisitRepositoryImplremote-first · sem fallback
- execute(accountSfid, geo?)CreateAdhocVisitUseCase
- create(retailName, accountSapCustomerId)AdhocVisitCreationprovider · family(accountSfid)
Notas de implementaçãoImplementation notesNotas de implementación
O provider é family por accountSfid e guarda contra reentrância na fase creating. O Repository resolve o ResourceEntity via currentResourceProvider — envia username e repSfid = resource.sfid (escrita identifica o representante, §25). Cada merge roda em ErrorUtils.runIsolated: um merge que falha não aborta os outros. Após sucesso, invalida retailsProvider + visitsProvider e marca retailRepository.markAdhocVisitCreated(accountSfid).
The provider is family by accountSfid and guards re-entry in the creating phase. The Repository resolves the ResourceEntity via currentResourceProvider — sends username and repSfid = resource.sfid (a write identifies the sales rep, §25). Each merge runs in ErrorUtils.runIsolated: a failing merge does not abort the others. After success it invalidates retailsProvider + visitsProvider and marks retailRepository.markAdhocVisitCreated(accountSfid).
El provider es family por accountSfid y protege la reentrada en la fase creating. El Repository resuelve el ResourceEntity vía currentResourceProvider — envía username y repSfid = resource.sfid (una escritura identifica al representante, §25). Cada merge corre en ErrorUtils.runIsolated: un merge que falla no aborta los demás. Tras el éxito invalida retailsProvider + visitsProvider y marca retailRepository.markAdhocVisitCreated(accountSfid).
Modelo de dadosData modelModelo de datos
A Visita Adhoc é um agregado de fronteira transiente: existe em três representações — Proto (AdhocVisitReply, wire gRPC) → DTO (AdhocVisitResultDTO, Freezed) → Entity (AdhocVisitResultEntity, domínio) — ligadas por dois mappers. Não há Model ObjectBox nem JSON de mock para o agregado: ele nunca é persistido como uma unidade — cada uma de suas listas é fundida no cache da sua feature (a coluna Model de cada sub-estrutura vive no doc da feature dona).The Adhoc Visit is a transient boundary aggregate: it exists in three representations — Proto (AdhocVisitReply, gRPC wire) → DTO (AdhocVisitResultDTO, Freezed) → Entity (AdhocVisitResultEntity, domain) — linked by two mappers. There is no ObjectBox Model nor mock JSON for the aggregate: it is never persisted as one unit — each of its lists is merged into its own feature's cache (each sub-structure's Model column lives in that feature's doc).La Visita Adhoc es un agregado de frontera transitorio: existe en tres representaciones — Proto (AdhocVisitReply, wire gRPC) → DTO (AdhocVisitResultDTO, Freezed) → Entity (AdhocVisitResultEntity, dominio) — unidas por dos mappers. No hay Model ObjectBox ni JSON de mock para el agregado: nunca se persiste como una unidad — cada una de sus listas se fusiona en el caché de su feature (la columna Model de cada sub-estructura vive en el doc de esa feature).
O AdhocVisitReply tem 11 campos; três deles são mensagens-wrapper (AdhocSurveys, AdhocMerchandising, AdhocFinancialManagement) que o mapper achata — por isso o DTO/Entity terminam com 15 listas. O CallTask vem do TasksConectaRep.proto (mesma estrutura da feature Call Task). A seguir: o proto, as estruturas e os mappers.The AdhocVisitReply has 11 fields; three of them are wrapper messages (AdhocSurveys, AdhocMerchandising, AdhocFinancialManagement) that the mapper flattens — that's why the DTO/Entity end up with 15 lists. CallTask comes from TasksConectaRep.proto (same structure as the Call Task feature). Next: the proto, the structures and the mappers.El AdhocVisitReply tiene 11 campos; tres de ellos son mensajes-wrapper (AdhocSurveys, AdhocMerchandising, AdhocFinancialManagement) que el mapper aplana — por eso el DTO/Entity terminan con 15 listas. El CallTask viene de TasksConectaRep.proto (misma estructura que la feature Call Task). A continuación: el proto, las estructuras y los mappers.
Proto
AdhocVisitConectaRep.proto · proto3 · package mn.bat.conectarep.streambridge. Um único RPC unário que agrega toda a visita:A single unary RPC that aggregates the whole visit:Un único RPC unario que agrega toda la visita:
getAdhocVisitunaryrpc getAdhocVisit(AdhocVisitRequest) returns (AdhocVisitReply)
path /mn.bat.conectarep.streambridge.AdhocVisitConectaRepService/getAdhocVisit
AdhocVisitRequestusernamestring· #1 · representante logadologged-in reprepresentante logueadoaccountSfidstring· #2 · varejo alvotarget retailpunto de venta objetivorepSfidstring· #3 ·resource.sfidgeoLatitudedouble· #4 · optionalgeoLongitudedouble· #5 · optional
AdhocVisitReply11 campos agregados (as listas de uma visita completa):11 aggregated fields (the lists of a full visit):11 campos agregados (las listas de una visita completa):
visitsrepeated Visit· #1callTasksrepeated CallTask· #2surveysAdhocSurveys· #3 · wrapper (achatado)wrapper (flattened)wrapper (aplanado)stockControlrepeated Stock· #4promotionsrepeated Promotion· #5promotionSpotsrepeated SpotPromotion· #6ordersrepeated Order· #7merchandisingAdhocMerchandising· #8 · wrapper (achatado)wrapper (flattened)wrapper (aplanado)marginCalculatorrepeated MarginCalculatorProduct· #9financialManagementAdhocFinancialManagement· #10 · wrapper (achatado)wrapper (flattened)wrapper (aplanado)productCatalogrepeated Product· #11
Estruturas de dadosData structuresEstructuras de datos
Um dropdown por estrutura. Como o agregado não é persistido, a tabela tem três colunas de camada — Proto (AdhocVisitReply) · DTO · Entity; o texto azul marca onde o dado primeiro muda (o achatamento das mensagens-wrapper acontece no toDTO, no Proto→DTO). Cada linha aponta o doc da feature onde vive o Model daquela lista.One dropdown per structure. Since the aggregate is not persisted, the table has three layer columns — Proto (AdhocVisitReply) · DTO · Entity; the blue text marks where the data first changes (the flattening of the wrapper messages happens in toDTO, at Proto→DTO). Each row points to the feature doc where that list's Model lives.Un dropdown por estructura. Como el agregado no se persiste, la tabla tiene tres columnas de capa — Proto (AdhocVisitReply) · DTO · Entity; el texto azul marca dónde primero cambia el dato (el aplanamiento de los wrappers ocurre en toDTO, en Proto→DTO). Cada fila apunta al doc de la feature donde vive el Model de esa lista.
AdhocVisitRequest request 5 camposfieldscampos
Campo Proto DTO Entity usernamestring — (montado no datasource a partir do ResourceEntity)— (built in the datasource from theResourceEntity)— (armado en el datasource desde elResourceEntity)accountSfidstring — (parâmetro do UseCase)— (UseCase parameter)— (parámetro del UseCase) repSfidstring resource.sfidgeoLatitudedouble¹ — (posição do device)— (device position)— (posición del device) geoLongitudedouble¹ — (posição do device)— (device position)— (posición del device) AdhocVisitResult AdhocVisitReply → DTO/Entity 15 listaslistslistas
Campo Proto (Reply) DTO Entity visitsrepeated Visit List<VisitDTO> List<VisitEntity> callTasksrepeated CallTask List<CallTaskDTO> List<CallTaskEntity> surveyssurveys.surveys List<SurveyDTO>List<SurveyEntity> stockControlrepeated Stock List<StockDTO> List<StockEntity> promotionsrepeated Promotion List<PromotionDTO> List<PromotionEntity> promotionSpotsrepeated SpotPromotion List<SpotPromotionDTO> List<SpotPromotionEntity> ordersrepeated Order List<OrderDTO> List<OrderEntity> merchandisingAssetsmerchandising.assets List<MerchandisingAssetDTO>List<MerchandisingAssetEntity> merchandisingServiceOrdersmerchandising.serviceOrders List<MerchandisingServiceOrderDTO>List<MerchandisingServiceOrderEntity> marginCalculatorrepeated MarginCalculatorProduct List<MarginCalculatorProductDTO> List<MarginCalculatorProductEntity> debitOpenItemsfinancialManagement.debitOpenItems List<DebitOpenItemDTO>List<DebitOpenItemEntity> banksfinancialManagement.banks List<BankDTO>List<BankEntity> paymentsfinancialManagement.payments List<PaymentDTO>List<PaymentEntity> creditNotesfinancialManagement.creditNotes List<CreditNoteDTO>List<CreditNoteEntity> productCatalogrepeated Product List<ProductDTO> List<ProductEntity>
Campos do wrapper não usadosUnused wrapper fieldsCampos del wrapper no usados
O AdhocMerchandising traz 4 listas no proto (assets, audits, digitalContent, serviceOrders), mas o mapper só achata assets e serviceOrders — audits e digitalContent não são mapeados para o agregado adhoc hoje.
AdhocMerchandising carries 4 lists in the proto (assets, audits, digitalContent, serviceOrders), but the mapper only flattens assets and serviceOrders — audits and digitalContent are not mapped into the adhoc aggregate today.
AdhocMerchandising trae 4 listas en el proto (assets, audits, digitalContent, serviceOrders), pero el mapper solo aplana assets y serviceOrders — audits y digitalContent no se mapean al agregado adhoc hoy.
Mappers
O adhoc_visit_result_mapper.dart tem 2 direções (o agregado não tem Model, então não há toModel/Model→Entity); cada lista delega ao mapper da sua feature.The adhoc_visit_result_mapper.dart has 2 directions (the aggregate has no Model, so no toModel/Model→Entity); each list delegates to its feature's mapper.El adhoc_visit_result_mapper.dart tiene 2 direcciones (el agregado no tiene Model, así que no hay toModel/Model→Entity); cada lista delega al mapper de su feature.
| DireçãoDirectionDirección | Extensão · métodoExtension · methodExtensión · método | DelegaçãoDelegationDelegación |
|---|---|---|
| Proto → DTO | AdhocVisitReplyProtoMapper.toDTO() | achata os wrappers; p/ call tasks chama CallTaskProtoMapper.toDTO() por item.flattens the wrappers; for call tasks calls CallTaskProtoMapper.toDTO() per item.aplana los wrappers; p/ call tasks llama CallTaskProtoMapper.toDTO() por ítem. |
| DTO → Entity | AdhocVisitResultDTOMapper.toDomain() | p/ call tasks chama CallTaskDTOMapper.toDomain() por item.for call tasks calls CallTaskDTOMapper.toDomain() per item.p/ call tasks llama CallTaskDTOMapper.toDomain() por ítem. |
Os únicos deltasThe only deltasLos únicos deltas
- Achatamento de wrapper —
surveys,merchandisingAssets,merchandisingServiceOrders,debitOpenItems,banks,payments,creditNotessaem de dentro das 3 mensagens-wrapper notoDTO(Proto→DTO).Wrapper flattening —surveys,merchandisingAssets,merchandisingServiceOrders,debitOpenItems,banks,payments,creditNotescome out of the 3 wrapper messages intoDTO(Proto→DTO).Aplanamiento de wrapper —surveys,merchandisingAssets,merchandisingServiceOrders,debitOpenItems,banks,payments,creditNotessalen de los 3 wrappers entoDTO(Proto→DTO). - Sem Model / sem persistência do agregado — o
AdhocVisitResultEntitynunca vira um Model; cada lista é fundida no cache de outra feature pelo merge coordinator.No Model / no aggregate persistence — theAdhocVisitResultEntitynever becomes a Model; each list is merged into another feature's cache by the merge coordinator.Sin Model / sin persistencia del agregado — elAdhocVisitResultEntitynunca se vuelve Model; cada lista se fusiona en el caché de otra feature por el merge coordinator. - Delegação por item — os campos internos (enums, datas parseadas de
CallTasketc.) mudam dentro do mapper de cada feature, não aqui.Per-item delegation — inner fields (enums, parsedCallTaskdates etc.) change inside each feature's mapper, not here.Delegación por ítem — los campos internos (enums, fechas parseadas deCallTasketc.) cambian dentro del mapper de cada feature, no aquí.
Repository
O AdhocVisitRepositoryImpl tem um único método. Apesar do nome "create", é uma operação remote-first de leitura-e-cache: busca o reply no servidor e o funde nos caches locais. Não há caminho de fallback para cache — sem varejo no reply, falha.The AdhocVisitRepositoryImpl has a single method. Despite the "create" name, it is a remote-first read-and-cache operation: it fetches the reply from the server and merges it into the local caches. There is no cache fallback path — with no visit in the reply, it fails.El AdhocVisitRepositoryImpl tiene un único método. A pesar del nombre "create", es una operación remote-first de lectura-y-caché: busca el reply en el servidor y lo fusiona en los cachés locales. No hay camino de fallback a caché — sin visita en el reply, falla.
createAdhocVisit({accountSfid, geoLatitude?, geoLongitude?}) remote-first
RetornaReturnsDevuelve Future<Result<AdhocVisitResultEntity, Failure>>
- createAdhocVisit
currentResourceProvider == null→ Error(UnknownFailure)_useMock→ mock.getAdhocVisit() (vazio) → toDomain → merge- senãoelsesi no → remote.getAdhocVisit(username, accountSfid, repSfid, geo?) → toDomain
entity.visits.isEmpty→ log adhocReplyHadNoVisit · Error(BusinessFailure: retailsAdhocFailedToast)- senãoelsesi no → mergeCoordinator.merge(result, accountSfid) · log adhocCreated · Success(entity)
Erros mapeados por FailureMapper.fromException e logados em LogCategory.dataSync. O username e repSfid vêm do ResourceEntity (§25).Errors mapped via FailureMapper.fromException and logged under LogCategory.dataSync. username and repSfid come from the ResourceEntity (§25).Errores mapeados por FailureMapper.fromException y logueados en LogCategory.dataSync. El username y repSfid vienen del ResourceEntity (§25).
AdhocVisitMergeCoordinator.merge({result, accountSfid}) 11 merges aditivos11 additive merges11 merges aditivos
Funde as 15 listas em 11 datasources locais, cada merge dentro de ErrorUtils.runIsolated (um erro isolado não aborta os demais). Todos seguem o padrão aditivo escopado por chave: retêm no cache o que não pertence ao escopo e anexam o recebido (lastSyncAt preservado).Merges the 15 lists into 11 local datasources, each merge inside ErrorUtils.runIsolated (one isolated error doesn't abort the rest). All follow the additive, key-scoped pattern: retain in cache what does not belong to the scope and append the incoming (lastSyncAt preserved).Fusiona las 15 listas en 11 datasources locales, cada merge dentro de ErrorUtils.runIsolated (un error aislado no aborta los demás). Todos siguen el patrón aditivo por clave: retienen en caché lo que no pertenece al alcance y anexan lo recibido (lastSyncAt preservado).
| Merge | Escopo (chave de retenção)Scope (retention key)Alcance (clave de retención) |
|---|---|
mergeAdhocVisits | visitas com accountData.sfid ≠ accountSfidvisits with accountData.sfid ≠ accountSfidvisitas con accountData.sfid ≠ accountSfid |
mergeAdhocCallTasks | call tasks com visitSfid ∉ refreshedVisitSfids (Call Task)call tasks whose visitSfid ∉ refreshedVisitSfids (Call Task)call tasks con visitSfid ∉ refreshedVisitSfids (Call Task) |
mergeAdhocOrders | accountSfid ≠ accountSfid |
mergeAdhocStockControl | accountId ≠ accountSfid |
mergeAdhocFinancialManagement | por lista (debitOpenItems/payments por account; creditNotes por retailerId; banks dedup por sfid)per list (debitOpenItems/payments by account; creditNotes by retailerId; banks dedup by sfid)por lista (debitOpenItems/payments por account; creditNotes por retailerId; banks dedup por sfid) |
mergeAdhocMerchandisingServiceOrders | accountSfid ≠ accountSfid |
mergeAdhocSurveys | upsert por survey sfid; substitui só o accountData deste accountupsert by survey sfid; replaces only this account's accountDataupsert por survey sfid; reemplaza solo el accountData de este account |
mergeAdhocPromotionCatalog | upsert por promoção id; spots por accountSfidupsert by promotion id; spots by accountSfidupsert por promoción id; spots por accountSfid |
mergeAdhocMarginCalculator | upsert por produto sfid; anexa accountSfid à listaupsert by product sfid; appends accountSfid to the listupsert por producto sfid; anexa accountSfid a la lista |
mergeAdhocProductCatalog | upsert por productSfid; substitui soq/salesHistory deste accountupsert by productSfid; replaces this account's soq/salesHistoryupsert por productSfid; reemplaza soq/salesHistory de este account |
mergeAdhocMerchandisingAssets | upsert por asset sfid; substitui os items deste accountupsert by asset sfid; replaces this account's itemsupsert por asset sfid; reemplaza los items de este account |
refreshedVisitSfids = união de result.visits.map(sfid) ∪ result.callTasks.map(visitSfid), sem strings vazias — usado apenas no mergeAdhocCallTasks (pois CallTask não tem accountSfid, o escopo é por visita).union of result.visits.map(sfid) ∪ result.callTasks.map(visitSfid), empty strings removed — used only in mergeAdhocCallTasks (since CallTask has no accountSfid, the scope is per visit).unión de result.visits.map(sfid) ∪ result.callTasks.map(visitSfid), sin strings vacías — usado solo en mergeAdhocCallTasks (como CallTask no tiene accountSfid, el alcance es por visita).
Manager Tasks não são fundidas — a fiação de ManagerTaskLocalDataSource foi removida do adhoc; o slot de tarefas de uma visita adhoc é sempre Call Tasks.Manager Tasks are not merged — the ManagerTaskLocalDataSource wiring was removed from adhoc; an adhoc visit's task slot is always Call Tasks.Las Manager Tasks no se fusionan — la conexión de ManagerTaskLocalDataSource se quitó del adhoc; el slot de tareas de una visita adhoc es siempre Call Tasks.
Datasources
Só dois datasources próprios: Remote (o RPC) e Mock (stub vazio). Não há Local datasource nem JSON de mock do agregado — a persistência acontece nos 11 datasources locais das outras features, pelo merge coordinator (seção 08).Only two datasources of its own: Remote (the RPC) and Mock (empty stub). There is no Local datasource nor mock JSON for the aggregate — persistence happens in the 11 local datasources of the other features, via the merge coordinator (section 08).Solo dos datasources propios: Remote (el RPC) y Mock (stub vacío). No hay Local datasource ni JSON de mock del agregado — la persistencia ocurre en los 11 datasources locales de las otras features, vía el merge coordinator (sección 08).
Remote AdhocVisitRemoteDataSource gRPC
Envio / fluxo: AdhocVisitConectaRepServiceClient. Erro: GrpcError → GrpcExceptionHandler.handle; outros → ServerException.Sends / flow: AdhocVisitConectaRepServiceClient. Error: GrpcError → GrpcExceptionHandler.handle; others → ServerException.Envío / flujo: AdhocVisitConectaRepServiceClient. Error: GrpcError → GrpcExceptionHandler.handle; otros → ServerException.
getAdhocVisit({username, accountSfid, repSfid, geoLatitude?, geoLongitude?})
- RetornoReturnRetorno
Future<AdhocVisitResultDTO>- EnvioSendsEnvío
AdhocVisitRequest(username+accountSfid+repSfid;geoLatitude/geoLongitudesó quando presentes)- FluxoFlowFlujo
- stub
getAdhocVisit;response.toDTO().stubgetAdhocVisit;response.toDTO().stubgetAdhocVisit;response.toDTO().
Mock AdhocVisitMockDataSource stub vazioempty stubstub vacío
getAdhocVisit({accountSfid})
- RetornoReturnRetorno
Future<AdhocVisitResultDTO>- ComportamentoBehaviorComportamiento
- retorna
const AdhocVisitResultDTO()(todas as listas vazias) — online-only por design, não lê asset. No modo mock a criação falha emvisits.isEmpty.returnsconst AdhocVisitResultDTO()(all lists empty) — online-only by design, reads no asset. In mock mode creation fails atvisits.isEmpty.retornaconst AdhocVisitResultDTO()(todas las listas vacías) — online-only por diseño, no lee asset. En modo mock la creación falla envisits.isEmpty.
Enums e labelsEnums & labelsEnums y labels
AdhocVisitCreationPhase core/enums/adhoc_visit 4
| case | significadomeaningsignificado |
|---|---|
idle | estado inicial · nada em andamentoinitial · nothing in progressinicial · nada en curso |
creating | criando · loading; bloqueia reentrânciacreating · loading; blocks re-entrycreando · loading; bloquea reentrada |
completed | visita criada com sucessovisit created successfullyvisita creada con éxito |
failed | erro · carrega o Failureerror · carries the Failureerror · lleva el Failure |
VisitType core/enums/visits valor adhocadhoc valuevalor adhoc
A visita criada carrega VisitType.adhoc (value "adhoc") — distingue-a das planejadas nas listas e no fim de jornada.The created visit carries VisitType.adhoc (value "adhoc") — it sets it apart from planned ones in the lists and at journey end.La visita creada lleva VisitType.adhoc (value "adhoc") — la distingue de las planificadas en las listas y al fin de jornada.
Labels · TranslationConstants i18n 7
| key | usouseuso |
|---|---|
retailsAdhocBadge | badge/botão ADHOC no cardADHOC badge/button on the cardbadge/botón ADHOC en la tarjeta |
retailsCreateAdhocModalTitle | título do modalmodal titletítulo del modal |
retailsCreateAdhocOk | botão confirmarconfirm buttonbotón confirmar |
retailsCreateAdhocCancel | botão cancelarcancel buttonbotón cancelar |
retailsAdhocCreatedToast | aviso de sucesso (interpola {name}/{sap})success notice (interpolates {name}/{sap})aviso de éxito (interpola {name}/{sap}) |
retailsAdhocFailedToast | aviso de erroerror noticeaviso de error |
endJourneyVisitTypeAdhoc | rótulo do tipo no fim de jornadatype label at journey endetiqueta del tipo al fin de jornada |
UseCases
CreateAdhocVisitUseCase usecases/adhoc_visit
| MétodoMethodMétodo | RetornaReturnsDevuelve | UsoUseUso |
|---|---|---|
execute({accountSfid, geoLatitude?, geoLongitude?}) | Future<Result<AdhocVisitResultEntity, Failure>> | encaminha ao repository.createAdhocVisit(...). Provider keepAlive.forwards to repository.createAdhocVisit(...). keepAlive provider.reenvía a repository.createAdhocVisit(...). Provider keepAlive. |
Notifier & State
A Visita Adhoc é orquestrada por um provider cross-page (não um Notifier de tela): o AdhocVisitCreation, family por accountSfid e keepAlive, vive em presentation/shared/providers/visits/ porque é consumido pelos cards da Lista de varejos. O build devolve o State inicial; o método create conduz o fluxo.The Adhoc Visit is orchestrated by a cross-page provider (not a screen Notifier): AdhocVisitCreation, family by accountSfid and keepAlive, lives in presentation/shared/providers/visits/ because the Retails list cards consume it. build returns the initial State; the create method drives the flow.La Visita Adhoc es orquestada por un provider cross-page (no un Notifier de pantalla): el AdhocVisitCreation, family por accountSfid y keepAlive, vive en presentation/shared/providers/visits/ porque las tarjetas de la Lista de puntos de venta lo consumen. El build devuelve el State inicial; el método create conduce el flujo.
MétodosMethodsMétodos
create({retailName, accountSapCustomerId})
RetornoReturnRetorno Future<void>
Guarda contra reentrância (phase == creating → retorna). Seta creating com retailName/accountSapCustomerId, lê a posição via locationServiceProvider, chama createAdhocVisitUseCase.execute(accountSfid, geo?). Success → retailRepository.markAdhocVisitCreated(accountSfid), invalida retailsProvider + visitsProvider, fase completed, toast verde. Error → fase failed + failure, toast vermelho.Guards re-entry (phase == creating → returns). Sets creating with retailName/accountSapCustomerId, reads position via locationServiceProvider, calls createAdhocVisitUseCase.execute(accountSfid, geo?). Success → retailRepository.markAdhocVisitCreated(accountSfid), invalidates retailsProvider + visitsProvider, phase completed, green toast. Error → phase failed + failure, red toast.Protege la reentrada (phase == creating → retorna). Setea creating con retailName/accountSapCustomerId, lee la posición vía locationServiceProvider, llama createAdhocVisitUseCase.execute(accountSfid, geo?). Success → retailRepository.markAdhocVisitCreated(accountSfid), invalida retailsProvider + visitsProvider, fase completed, toast verde. Error → fase failed + failure, toast rojo.
State disponível para a PageState available to the PageState disponible para la Page
AdhocVisitCreationState Freezed 4 camposfieldscampos
| Campo | TipoTypeTipo | UsoUseUso |
|---|---|---|
phase | AdhocVisitCreationPhase | default idledefault idledefault idle |
retailName | String? | nome exibido nos avisosname shown in noticesnombre en los avisos |
accountSapCustomerId | String? | SAP exibido no aviso verdeSAP shown in the green noticeSAP en el aviso verde |
failure | Failure? | preenchido em failedset on failedseteado en failed |
Page e widgetsPage & widgetsPage y widgets
Sem page própria — a superfície é o card do varejo (na Lista de varejos) e o modal de confirmação.No page of its own — the surface is the retail card (in the Retails list) and the confirmation modal.Sin page propia — la superficie es la tarjeta del punto (en la Lista de puntos de venta) y el modal de confirmación.
- RetailsPage Lista de varejos (host)Retails list (host)Lista de puntos de venta (host)
- RetailCardWidget observa
adhocVisitCreationProvider(accountSfid:)watchesadhocVisitCreationProvider(accountSfid:)observaadhocVisitCreationProvider(accountSfid:)- _RetailActionBadge · ADHOC visível só se
isAdhocVisitCreationAvailable; loading na fasecreatingvisible only ifisAdhocVisitCreationAvailable; loading in thecreatingphasevisible solo siisAdhocVisitCreationAvailable; loading en la fasecreating - CreateAdhocVisitModalContent modal · confirma →
provider.create(retailName, accountSapCustomerId)modal · confirm →provider.create(retailName, accountSapCustomerId)modal · confirmar →provider.create(retailName, accountSapCustomerId)
- _RetailActionBadge · ADHOC visível só se
- RetailCardWidget observa
A RetailsPage passa o onAdhocTap que abre o CreateAdhocVisitModalContent via ConectaModal.show; ao confirmar, chama ref.read(adhocVisitCreationProvider(...).notifier).create(...). Abrir modal e navegar ficam no widget; o disparo é o método do provider (§39).The RetailsPage passes the onAdhocTap that opens CreateAdhocVisitModalContent via ConectaModal.show; on confirm it calls ref.read(adhocVisitCreationProvider(...).notifier).create(...). Opening the modal and navigating stay in the widget; the trigger is the provider method (§39).La RetailsPage pasa el onAdhocTap que abre el CreateAdhocVisitModalContent vía ConectaModal.show; al confirmar llama ref.read(adhocVisitCreationProvider(...).notifier).create(...). Abrir el modal y navegar quedan en el widget; el disparo es el método del provider (§39).
Notas por mercadoMarket notesNotas por mercado
A Visita Adhoc acompanha o ecossistema de Varejos/Visitas — ativa nos mercados vivos (BR/CL/ZA). O botão em si é por varejo (isAdhocVisitCreationAvailable, dado da entity), e o tipo adhoc é um valor de visita reconhecido no End Market Configuration.The Adhoc Visit follows the Retails/Visits ecosystem — active in the live markets (BR/CL/ZA). The button itself is per retail (isAdhocVisitCreationAvailable, entity data), and the adhoc type is a visit value recognized in the End Market Configuration.La Visita Adhoc acompaña el ecosistema de Puntos de venta/Visitas — activa en los mercados vivos (BR/CL/ZA). El botón en sí es por punto (isAdhocVisitCreationAvailable, dato de la entity), y el tipo adhoc es un valor de visita reconocido en el End Market Configuration.
AtivosActiveActivos
Mercados vivos com Varejos/Visitas. O reply do getAdhocVisit traz Call Tasks no slot de tarefas (não Manager Tasks). A geolocalização é enviada quando o device a disponibiliza.
Live markets with Retails/Visits. The getAdhocVisit reply carries Call Tasks in the task slot (not Manager Tasks). Geolocation is sent when the device provides it.
Mercados vivos con Puntos de venta/Visitas. El reply de getAdhocVisit trae Call Tasks en el slot de tareas (no Manager Tasks). La geolocalización se envía cuando el device la provee.
AR · PY · PE Mercados PANGEA de configuração mínima: o ecossistema de Varejos/Visitas não está ativo, então a Visita Adhoc não existe — nenhum card exibe o botão ADHOC. PANGEA minimal-config markets: the Retails/Visits ecosystem is not active, so the Adhoc Visit doesn't exist — no card shows the ADHOC button. Mercados PANGEA de configuración mínima: el ecosistema de Puntos de venta/Visitas no está activo, así que la Visita Adhoc no existe — ninguna tarjeta muestra el botón ADHOC.
Pendências / roadmapPending / roadmapPendientes / roadmap
(1) O agregado ignora merchandising.audits e merchandising.digitalContent do proto — só assets e serviceOrders são fundidos hoje. (2) Sem mock: em ambiente mock a criação sempre falha (reply vazio) — é online-only por design. (3) O merge coordinator é fire-and-persist sem transação atômica entre as 11 caches (cada merge é isolado por runIsolated).
(1) The aggregate ignores the proto's merchandising.audits and merchandising.digitalContent — only assets and serviceOrders are merged today. (2) No mock: in mock mode creation always fails (empty reply) — it is online-only by design. (3) The merge coordinator is fire-and-persist with no atomic transaction across the 11 caches (each merge is isolated by runIsolated).
(1) El agregado ignora merchandising.audits y merchandising.digitalContent del proto — solo assets y serviceOrders se fusionan hoy. (2) Sin mock: en modo mock la creación siempre falla (reply vacío) — es online-only por diseño. (3) El merge coordinator es fire-and-persist sin transacción atómica entre los 11 cachés (cada merge está aislado por runIsolated).