Changelog
O que há de novo.
Cada melhoria para a plataforma, como ele envia.
· kamo-shared-library
O domínio folha de pagamento-fornecedor por trás do motor de cartão de tempo
Adiciona a coluna de persistência para conectar os cartões de tempo de uma organização a um sistema de folha de pagamento de terceiros: a ligação e a sua configuração criptografada por org, a subvenção OAuth, a ligação membro-a-fornecedor-empregado, o mapa do código de ganho, e os dois registros somente de apêndice (tentativas de envio e problemas de sincronização) que fazem uma sincronização que se explica depois. Mantido dentro com.kamo.z. shared.hr.timecard em vez de um novo pacote em finalidade: um novo pacote de lib compartilhada tem que ser adicionado a três @EnableJpaRepositórios lista, e omitir um aborta um contexto de serviço — ou, em KamoInitializer, aborta a corrida enquanto ainda relata o SUCESSO DO BUILD. Notas sobre a forma: - PayrollProviderType reutiliza o vocabulário token já documentado em **************************** (ADP WFN, PAYCHEX, GUSTO, ...). Um segundo ortografia para o mesmo fornecedor iria dividir o histórico de exportação sem erro Em qualquer lugar. ADP RUN e QUICKBOOKS são mantidos porque a tela de exportação de folha de pagamento Ofereceu-lhes desde que foi enviado, por isso as fileiras vivas podem carregá-los. - Links de funcionários são chaveados pelo provedor, não por conexão, então comutação provedor e troca de volta não joga fora o trabalho de mapeamento. DOIS restrições únicas: um para um membro segurando dois links, o outro para dois membros que reclamam um empregado prestador — que um impede um pagamento duplo. - PayrollDispatch e PayrollSyncIssue são WORM. Uma transmissão de folha de pagamento tentativa é um evento de dinheiro; "nós enviamos, então nós enviamos novamente" é o fato de um As necessidades de reconciliação. - Credenciais ao vivo em ONE config json blob encriptado em vez de uma coluna por campo, então adicionar um fornecedor não é uma mudança de esquema que re-arma cada serviço. GESTÃO PAYROLL PROVIDER (199) — NÃO deliberadamente legado MANAGEM PAYROLL (40), que não porta nada hoje e já está detido por número desconhecido de membros; reutilizá-lo entregaria folha de pagamento de terceiros credenciais para todos os titulares de direitos de visualização de taxa na bota que o enviou. PlatformOAuthProviderType ganha os sete fornecedores de folha de pagamento onde Kamo detém um registo do parceiro (ids 11-17). Credenciais de emissão Dayforce, Workday e UKG por cliente e deliberadamente não obter constante: a tela da plataforma renderiza cartão para cada constante, então um para eles seria um formulário nenhum operador poderia Preencha. Também corrige uma asserção antiga em BillingDelegationModeTest: commit 9276487 REQUIRES APPROVAL deliberadamente feitos permitem grupos (a exclusão fez o caminho de aprovação inalcançável) mas deixou o teste afirmando o comportamento antigo, assim A suite está vermelha desde então. Esquema aplicado e verificado em Yugabyte antes deste push.
· kamo-internal
Uma parede no "agora", e um cartão que explica um soco cancelado
Ninguém consegue registar o tempo que ainda não trabalhou. Arrastar uma borda, deslizar um todo o espaço, mexendo com as setas, o ímã, o botão direito do mouse e o manual adicionar diálogo tudo para no minuto atual na linha de hoje, e um O futuro dia não aceita absolutamente nada. A parte de hoje que não aconteceu é chocou, porque "você não pode arrastar uma pílula aqui" tem que ser visível ANTES de alguém Controlo, não como regra. O servidor já recusou um soco inserido ou corrigido no passado agora, que é exatamente por que o cliente precisava disso: sem a parede o drag conseguiu, o comprimido sentado lá olhando encenado, e todo o salvamento falhou no final com um mensagem sobre um soco que o membro não conseguiu encontrar. O íman fica com a mesma parede. Um soco deixado no futuro por um dispositivo errado relógio era um alvo legítimo snap, então uma borda poderia ser puxada para a frente para ele e pousar em algum lugar o salvamento recusaria. E o cartão de confirmação agora permanece aberto quando um soco CANCELA o anterior, que TimecardService começou a fazer esta manhã para qualquer inversão dentro de um Um minuto. Sem isso, o membro pressiona um botão, observa o estado voltar para exatamente onde começou, e razoavelmente conclui que o botão está quebrado. In que o estado da carta deixa cair seu catador e sua cópia de ação, mostra uma frase e um botão, e pára de descartar no pano de fundo — o clique perdido que assim muitas vezes segue uma confirmação não deve fechar a única explicação do que aconteceu.
· kamo-internal
Dar suporte e janelas sociais ao compositor do chat do membro
Um bilhete de apoio e um DM social são a mesma coisa que um bate-papo de membro — um sessão de mídia com mensagens nele — mas eles foram respondidos através de um nu Campo de texto. Um agente poderia ser enviado por um cliente e não tinha como para enviar um de volta: sem botão anexar, sem drag-drop, sem pasta, sem emoji, e um entrada que não iria crescer além de uma linha. Ambos agora renderizam o Composer do chat do membro, então a máquina de upload, o bandeja de anexo, o painel de transferência, o coletor de emoji e Enter-to-send são uma implementação em todos os três. Os anexos recebidos e enviados são renderizados as bolhas através do mesmo apêndiceChip. Duas coisas que a janela de suporte de tabulação forçou a abrir: - Os rascunhos estão estacionados por conversação (useDraftParking). Um compositor serve vários tickets e os posts de envio para quem quer que esteja na tela, então um rascunho à esquerda na caixa estão as palavras de um cliente dirigidas a outro. Eliminação após envio toma a chave com a qual o envio começou, ou mudar as abas no meio do voo iria apagar a resposta sendo digitada para outra pessoa. Os anexos encenados também são mantidos por sessão. Uma lista plana coloca um cliente arquivo na bandeja de outro e enviou-o para lá; uploadTudo agora se dirige ao sessão os arquivos foram encenados para, qualquer que seja a aba em vista quando ele pousa. As respostas sociais são fechadas em um arquivo de 10 MB: uma mensagem carrega uma única MediaObjSocialRef, então um segundo anexo poderia ser armazenado e nunca retransmitido, e 10 MB é o teto mais apertado provedor que enviamos. Recusando-o na O compositor bate perdendo-o silenciosamente. Um envio falhou é agora reportado separadamente de um envio falhou — os arquivos são A mensagem não está, e o rascunho ainda está lá para tentar novamente.
· kamo-internal
A marcação rápida lista as extensões do departamento da org
A guia enviou seis entradas inventadas — Sales Team em 1001, uma Sala de Conferências permanentemente tocando — assim discando um atingiu o que aconteceu para responder que extensão, e os crachás BLF relataram presença nada foi subscrito. Ele agora lê /api/security/departments e lista todos os departamentos que o org tem dada uma extensão, ordenada por título, com o número exterior por baixo quando Um está pronto. O carregamento, o vazio e o erro de repetição substituem as peças que Nunca poderia estar vazio. O GET é org-scoped e não precisa de direito, e dispara somente uma vez que a aba é aberta pela primeira vez.
· kamo-internal
Conversa ampliada, social, ai-chat e compor janelas para o tamanho do suporte
A janela de ticket de suporte abre a 460px enquanto bate-papo de membro, bate-papo social, bate-papo de IA e o compositor de e-mail todos sentou em 360-420. Mesmo cais, mesmo tipo de conteúdo — eles agora compartilhe o padrão 460px da janela de suporte (e um piso 420px para o redimensionável ones), então uma linha de janelas de ferramentas abertas se alinha em vez de escada-passo.
· kamo-internal
Mostrar mensagens invisíveis em todas as filas de filas, não apenas as que se juntaram
O distintivo e a cor da fileira já estavam aqui — simplesmente nunca foram povoados para a maioria da lista. getUnreadPara ler apenas o mapa não lido ao vivo por sessão, que é derivado da própria linha de membros do espectador, então um ticket que eles não tinham A união nunca se acendeu. Nesta fila que é a maior parte, e o tickets não atribuídos que ninguém pegou são tanto a maioria e as linhas que A maioria precisa da bandeira. Ele agora prefere o mapa ao vivo, que é autoritário para conversas o espectador está e atualiza sobre STOMP no momento em que lê ou responde terras, e cai De volta à conta do servidor para tudo o resto. Os dois não podem ser fundida cegamente: o mapa ao vivo só carrega sessões que ainda não foram lidas mensagens, então "leia há um segundo" e "nunca contada" são indistinguíveis em — daí o pequeno conjunto de conversas abertas durante esta visita, que mantém uma linha que o membro está lendo atualmente de ficar aceso até a próxima recarga. A lista também se re-ler quando o suporte pendente conta se move. Para uma conversa o espectador não se juntou não há nenhum canal ao vivo que corrigir o instantâneo — o tópico não lido só atinge as pessoas na sessão, que em uma fila não está ninguém - e pendenteCount é o único sinal que atinge Todos os membros com capacidade de apoio da org.
· MediaService
Selo invisível conta na lista de tickets, e manter a fila ao vivo
getTickets agora carrega uma quantidade invisível de mensagens por linha para o chamador, ao lado o visitante rema já carrega dessa forma, e getTicketDetail calcula seu próprio antes da marca do leitorAsRead terras — para que a janela possa mostrar o que chegou enquanto eles estavam fora em vez de um número o ato de abri-lo já zeroed. Também fecha a lacuna de sinal que deixou a fila estagnada. Uma mensagem num suporte conversação move a fila para cada membro com suporte da org, não apenas as pessoas nessa sessão — que é exatamente quem por sessão não lido O tópico NÃO alcança. A transmissão pendente já foi disparada quando um visitante de chat público enviado e quando um agente marcado lido; um membro respondendo de dentro do aplicativo estava o buraco, então uma conversa de suporte no aplicativo poderia ganhar mensagens sem nada a dizer ao ecrã de qualquer agente para olhar novamente. O SupportUnteenCountSqlTest fixa a forma da consulta. Cada cláusula nela é carregar e nenhum deles é visível em uma construção passageira: obter um erro e a fila ainda renderiza, apenas com contagens que são silenciosamente falsas.
· kamo-shared-library
Conte mensagens invisíveis para um visualizador que não está na conversa
Não lido sempre foi derivado da linha do próprio visualizador MediaSessionMember, então uma conversa que eles não tinham se juntado não produziu nenhuma contagem. Numa fila de suporte que é a maior parte — 27 dos 37 bilhetes vivos não são atribuídos, e 26 dos ter exatamente um membro da sessão, a pessoa que pediu ajuda. Aqueles são... precisamente as fileiras que mais precisam de sinalização, e foram elas que estruturalmente não poderia carregar uma bandeira: ninguém tinha se juntado a eles, então não havia leitor para que não sejam lidos. O countUnseenBySessionForViewer responde à pergunta que uma fila faz. A A adesão esquerda à adesão é todo o truque: sem linha de adesão não há leia o ponteiro, então cada mensagem de entrada conta, que é a verdade — o visualizador Não viu nenhum deles. Uma consulta por página, combinando como as linhas do visitante ao lado Já estão carregados. Os eventos do sistema estão excluídos. Uma mudança de status ou uma reatribuição é contabilidade sobre a conversa, e contando-o ilumina filas onde ninguém tem realmente disse qualquer coisa — pior do que nenhum distintivo em uma tela cujo trabalho é decidir O que precisa de uma resposta. SuporteTicketDTO.unseenCount é preenchido na lista e detalhes lê e esquerda null em qualquer outro lugar, porque "invisível" é um fato sobre um visualizador e um número Inventado para um ouvinte que não perguntou sobre um seria simplesmente errado.
· kamo-internal
Apagar, ímãs, um menu com o botão direito do mouse e um relógio que pergunta qual
Seis adições ao gráfico do período e o relógio de navegação. DELETE remove a pílula focada. Backspace também, porque em um Mac a chave etiquetado "delete" É Backspace e oferecer apenas Apagar significa que o atalho faz não existe num portátil. Ele anula ambos os socos do span, por isso apagar uma refeição deixa o trabalho de cada lado do mesmo contínuo — que é o que significa para o Uma refeição que não aconteceu. Estágio como qualquer outra edição; nada é escrito até salvar. Uma ORIENTAÇÃO segue o ponteiro através de cada linha, com o minuto em que está em um Chip no topo. Seu trabalho é dizer onde um clique pousaria, e isso é o mesmo minuto todos os dias no período. Medido um elemento da faixa em vez que o contentor, cuja caixa inclui o nome do dia e as colunas totais — utilizando que o membro não pode ver Direito-clique em qualquer lugar em um dia abre um menu que deixa cair um bloco de 15 minutos de trabalhou tempo, refeição ou pausa onde o ponteiro estava. Dois socos, não um: um span É um par, e um abridor solitário desenha uma pílula correndo para a meia-noite. Note que uma refeição bloco ABERTA com MEAL OUT — uma refeição é gravada como o relógio fora do trabalho e para trás E ter esse par ao contrário inverte o dia. MAGNETS: uma borda arrastada dentro de cinco minutos de outro soco se encaixa em sua Minuto exato, ignorando deliberadamente o espaço de um minuto que a pinça mantém. Isso. gap pára uma borda atravessando seu vizinho; aqui a intenção toda é para os dois Encontrar, e uma borda que só pode pousar um minuto curto nunca pode fechar uma lacuna. Cinco minutos no zoom de quinze dias é um par de pixels, então sem um ímã "fazer esta ruptura terminar quando a próxima começar" é uma tarefa que ninguém pode completar com um ponteiro. E quando o ímã pousa dois vãos do mesmo tipo de borda à borda, eles MERGE no mesmo gesto: o relógio-out e o relógio-in que agora compartilham uma parada de minutos sendo verdade, e ambos são anulados. Tipos diferentes nunca se fundem — um final de mudança exatamente quando uma refeição começa não é um período, são as duas coisas que realmente Aconteceu. Isto precisava do tipo em paraGraphPunches para tie-break no original minuto: duas bordas coincidentes devem manter sua ordem cronológica ou emparelhamento troca-os, produz uma extensão de largura zero e um abridor perdido, ea pílula desaparece no momento em que os dois se encontram. TRÊS CORES DE PILL, e distinto por mais do que matiz — trabalhar sólido na marca primário, refeição com banda no âmbar HRS, quebra finamente listrado em violeta. Cor sozinho falha o ~8% dos homens com uma deficiência vermelho-verde e falha todos em um projetor; alguém deve ser capaz de distinguir uma refeição não remunerada de uma pausa paga em um Impressão em preto e branco. O relógio de navegação pergunta qual é o soco. "Clock out" são três atos diferentes, e o que é oferta depende de onde o membro está: trabalhando, eles podem terminar o mudar, ir a uma refeição ou fazer uma pausa; em uma refeição ou uma pausa, voltando é o padrão e terminando o turno ainda está lá porque as pessoas saem em linha reta do almoço. Colapso que em um botão é como alguém termina seu dia quando eles pretendiam ir para o almoço — uma hora não remunerada eles não voltar e um Excepções de soco perdido a caminho. A carta passa para a ação selecionada em as mesmas três cores que o gráfico usa, diz quanto tempo eles estiveram em seu o estado atual, e o nav dot agora relata qual dos três estados é. Um estado ausente do servidor é lido como "o booleano é tudo o que temos", não como OFF: lendo como OFF ofereceria "relógio" para alguém no meio do turno para o comprimento da janela de implantação.
· SecurityService
Retransmitir o vazio do soco
Passagem para POST /punches/{id}/void, a chave de exclusão do editor e a Máquinas por trás da junção de dois vãos adjacentes. A mesma autoridade que /correcto, e pela mesma razão: um EMPREGO pode anular o seu próprio soco, porque um membro que não pode dizer "que torneira dupla nunca aconteceu" tem Não há forma de corrigir um registo que seja a única testemunha. O que eles não podem fazer é fazer valer — o vazio pousa num cartão de tempo que ainda se move através do cadeia de aprovação, e a razão é obrigatória em todos os caminhos. O ator está carimbado da SESSÃO, nunca do corpo; um ator Assinatura fornecida pelo corpo no registo salarial de outra pessoa. Dois testes, puxando em direções opostas, porque este serviço funciona anyRequest().permitAll() com auth per-handler laminada à mão: deve recusar uma o soco do colega sem nunca chegar ao relé, e deve admitir o Não têm quaisquer direitos.
· kamo-shared-library
Um soco pode ser anulado sem ser excluído
Quatro colunas nulas e uma segunda transição de escrita no TimecardPunch. A void diz que o soco nunca deveria ter existido — uma batida dupla no relógio, e quebra abandonada, ou a costura entre dois espaços o editor de cartões de tempo tem apenas fundiram-se num. Isso não é uma correção ("isso aconteceu, em um diferente tempo", que deixa uma linha de substituição) nem uma exclusão: suplantadoByPunchId significa "ler esta outra linha em vez", e aqui não há outra linha. A fileira sobrevive, vazia, com seu autor e sua razão ao seu lado. Um salário recorde que pode ser feito para esquecer um soco é exatamente a inadequação que muda o fardo para o empregador sob Anderson v. Mt. Clemens Pottery, então o único algo que um vazio remove é o efeito do soco no cálculo. executeWorm () permite que seja anuladoAt nos mesmos termos que suplantadoByPunchId — null a um valor, uma vez — e recusa um vazio sem razão. Un-voiding no local é deliberadamente impossível: reinserir um punch significa inserir um, que deixa sua própria linha e sua própria explicação. ******************* não é um conveniência. A variante não filtrada está errada para decidir o estado do relógio: a a linha substituída mantém o seu instante ORIGINAL, de modo que um clock-out corrigido para trás deixa a linha morta mais recente por occutc, e uma linha vazia permanece mais recente até que algo mais novo seja perfurado. Qualquer um responde: "Estás a trabalhar?" de um registo que já não existe. Sequenciado pela regra de mudança de entidade: compartilhamento-lib instalado localmente, KI migração escrita e correr contra 192.168.4.22:5433 com todos os quatro ALTERS aplicado, e só então isso é empurrado — de modo que nenhum serviço pode construir contra esquema não migratório.
· InitializerService
As colunas vazias em hr timecard punch
Espelha quatro novas colunas nulas na entidade TimecardPunch. Um vazio é nem um delete nem uma correção: diz que o soco nunca deveria ter existido — uma torneira dupla sobre o relógio, uma pausa abandonada, ou a costura entre dois vãos o editor se fundiu em um. SUPERSEDED BY PUNCH ID não pode expressá-lo, porque essa coluna significa "leia esta outra linha em vez" e não existe outra Remar. Todos os quatro são NULLÁVEIS, que é o que torna isso seguro em uma tabela povoada: BarataDB rejeita ADD COLUNN ... NÃO NULL em uma tabela não vazia e rola o toda a declaração de volta, então um marcador vazio NÃO NULL seria registrado como um warn, Saltou, e silenciosamente ausente. Eles vão primeiro neste corredor, à frente dos índices e REVOKES-lo já carrega, pela razão que @Order(0) existe: o momento em que a lib partilhada mapeia uma coluna, cada corredor que carrega a entidade ponche emite-o em seu SELECT, e um coluna-add ordenado tarde nunca é executado em tudo — um leitor anterior morre em "coluna não existe" e leva o inicializador para baixo com ele. Correr contra 192.168.4.22:5433, todos os quatro aplicados.
· kamo-internal
Mostrar o painel de integração do mercado quando não existirem mercados de CRM
/leades renderizou a grade de lead com uma barra de tabulação de mercado vazia e desativada para uma org que nunca tinha configurado um mercado de CRM. Agora mostra o mesmo painel de bordo /commerce mostra para os mercados de comércio em falta — extraídos para MarketOnboardingEmptyState para que os dois permanecem idênticos — impulsionado pelo líder CRM mercados de Configurações > Gerente de Relacionamento com Clientes, não os mercados de comércio, e apontando para a aba Mercados & Campos CRM. Os passos e as configurações CTA são mostrados apenas para os membros que podem gerenciar CRM configurações; todos os outros recebem a cópia de check-back- posterior existente.
· kamo-internal
O gráfico de período, e um cartão de tempo que você editar como um rascunho
Substitui a linha do tempo de um dia em... período de pagamento inteiro, e muda o que a edição se sente. Os comprimidos não trazem mensagens. Eles costumavam carregar seus próprios tempos, e um rótulo é 60px de largura por mais longa que seja a coisa que descreve foi — assim uma pausa de dez minutos e um o turno de quatro horas desenhou a mesma pílula, e a uma propriedade uma linha temporal tem que um tabela não (largura significa duração) foi a propriedade que jogou fora. Ambos os tempos agora vivem numa dica que nomeia o início, o fim e o intervalo entre eles, e segue a borda sendo arrastado com uma leitura que atualiza como ele Mexe-te. Cantos são 2.5px: suficiente para tirar a dureza mecânica, não O suficiente para remendar um comprimido de cinco minutos numa pastilha. As etiquetas de cabeçalho a cada hora, cada um girando um quarto de volta no sentido anti- horário Todos os vinte e quatro cabem. Quatro rótulos com seis horas de diferença não podem dizer a ninguém se uma pílula começa em 9 ou em 10, que é a única questão que o cabeçalho está lá para Responde agora que os comprimidos não estão marcados. Todos os dias no período recebe uma linha, INCLUINDO dias que ainda não aconteceram. Um gráfico que apenas desenha dias com dados não pode mostrar um soco em falta, e o falta de ponche é a coisa mais comum errado com um cartão de tempo: o gap é o encontrar, e uma descoberta precisa de uma linha para aparecer. Dias futuros são eclodidos do que simplesmente escurecido — "ainda não" e "ninguém registrou nada" são diferentes factos. Arraste para o minuto. Cinco minutos snapping não poderia expressar 9:02, então um relógio que estava dois minutos fora não podia ser corrigido e cada vez corrigida adquiriu uma arredondamento que lia como uma estimativa. O arredondamento é uma política de pagamento O motor aplica-se com o relatório de neutralidade atrás do mesmo; uma barra deslizante não deve aplicar Segundo, invisível. As teclas de seta empurram por um minuto, Shift por quinze, Alt um ponteiro não pode expressar um minuto numa pista em que um O minuto está debaixo de um pixel. E a edição é agora um rascunho. Arrastar a janela de motivos obrigatórios em cada lançamento: fixar um dia com três socos errados significava escrever três explicações, cada uma variante de "o relógio estava errado", e uma proteção real sob Anderson v. Mt. Clemens Pottery transformou-se em uma caixa as pessoas digitadas "correcção" para fazê-lo desaparecer. Uma proteção que todos aprendem a derrotar Não protege ninguém. Edita agora o palco livremente e uma explicação é recolhida em salvar — ainda obrigatório, ainda gravado em cada soco o salvar toques, mas perguntou uma vez, quando alguém realmente sabe o que fez. Nada está escrito Até lá. Suportando alterações: - app/lib/timecardGraph.ts detém todas as decisões tomadas pelo gráfico — emparelhamento, aperto, snapping, o deslocamento instantâneo seguro de DST — como funções puras, porque um decisão sobre um registro salarial que só pode ser verificado arrastando algo com um rato é uma decisão que ninguém verifica. 34 testes. - fetchRange cai de volta para leituras por dia em um 404 então a aba não está quebrada para Quem o carregar entre o Serviço de Segurança e este. - as viagens de ida e volta através da parede local. Transformou um UTC cortar em uma entrada local datetime e analisar o resultado como local, então cada visita ao campo moveu o tempo pela zona offset em silêncio.
· SecurityService
Retransmitir o intervalo de período do editor
Passe para GET /members/{id}/range, o gráfico de período é lido. de/para são encaminhado apenas quando presente para que o TimecardService possa resolver o próprio pagamento do membro período — o encaminhamento "?from=&to=" ligar-se-ia a cadeias em branco a jusante e silenciosamente derrotar esse padrão para cada org com um calendário de pagamento. A identidade é verificada com o resolveRole e nenhum direito, exatamente como /day já faz. Os dois requisitos puxar em direções opostas e um portão à direita fica Só um deles: não deve tornar-se uma forma de ler as horas de um colega, e deve admitir-se o sujeito que não detém nada, porque um membro no A categoria RESTRICTED não tem quaisquer direitos e ainda tem direito estatutário aos seus O próprio recorde. TimecardRangeAuthTest cobre ambas as direções mais o edifício de consultas, porque este serviço executa qualquerRequest().permitAll() com auth per-handler laminada à mão — um mapeamento que esquece sua verificação é acessível a partir da internet pública através A APIService está à frente.
· kamo-internal
Criar uma conversa de apoio quando alguém responder nela
Fechar uma janela de suporte não está a sair da conversa, mas foi tratada como Se fosse. ChatNotificationListener retornou mais cedo em cada SUPPORT TICKET notificação — corretamente, uma vez que um ticket não deve abrir uma janela de bate-papo simples, mas Deixou suporte sem reabrir ao vivo. Quem fechou o painel, a maioria muitas vezes a pessoa que pediu ajuda uma vez que tinha pedido, simplesmente nunca viu o responder. Agora abre a janela de suporte, endereçada pelo ID do ticket notification carrega; a ferramenta store dedupes no ticketId, então uma janela já está acima é focado em vez de duplicado. AutoOpenUnreadSupportChats é a metade durável, para uma resposta que chegou enquanto foram assinados para fora — o impulso ao vivo é núcleo NATS sem repetição. Deliberadamente separado de AssignedTicketOpener: que um responde "este trabalho é agora seu", este responde "alguém lhe disse alguma coisa", e para um pedido ou o primeiro Nunca acontece. Ambos reconhecem através do mesmo registro auto-aberto o o caminho do chat usa, de modo que um segundo dispositivo não reproduz o que o primeiro mostrou. ChatsList agora leva ticketId direto para fora do quadro de atividade. Sintetizar linha de suporte sem um produziu uma linha que não pôde ser aberta, então a lista tinha para jogar o quadro fora e re-ler a página inteira para obtê-lo.
· MediaService
Uma conversa, muitas mãos — unam-se em aberto, nomeiem cada mensagem, reabrem uma resposta
Um ticket de apoio é trabalhado por quem está ajudando, não só por quem quer que seja atualmente carimbado com. A atribuição decide a quem o trabalho é oferecido e a quem lista em que aparece; nunca foi feito para ser um bloqueio na conversa. Abrir um ticket agora entra em sua sessão de mídia. Antes, um agente que abriu um o ticket da fila que não foi atribuído a eles poderia digitar nele — nada Rejeitaram a mensagem — mas não eram membros da sessão, pelo que a conversa nunca apareceu em seus chats -> Suporte, nenhuma resposta jamais alcançada Eles, e o solicitante "é o meu agente aqui" não os contou. Eram. A falar numa sala onde não estavam. Quem pode aderir é a mesma população que já pode ver o ticket na fila: as pessoas já na conversa, mais ACCEPT SUPPORT TICKETS ou GESTÃO SUPPORT na org atribuída. Todos os outros. é agora recusado — este endpoint anteriormente servido qualquer ticket na plataforma para qualquer membro autenticado que possa adivinhar um UUID, entre organizações, e o Juntar portão teria ampliado que de ler a conversa de um estranho para sendo escrito nele. Cada mensagem agora tem um nome. o remetenteName foi lido diretamente para fora Member.usernameAlias, um apelido opcional que está desligado em quase todos os membros, por isso serializou como null e o chat renderizado mensagens sem atribuição em Todos. Isso é pior em um ticket de suporte, onde mais de um agente pode ser responder e o cliente não tem outra maneira de dizer quem está falando com eles. Os Nomes do MemberDisplay — já utilizados noutros locais deste controlador — são O nome verdadeiro. A mesma leitura em bruto foi em branco nomes de agentes atribuídos, ticket atores da história, atores do sistema-evento, o agente-aviso de disponibilidade e o Quadro de relatórios. Uma janela fechada não significa mais uma resposta perdida. A notificação da mensagem de entrada carrega o ID do ticket, para que o cliente possa levantar a conversa que está dizendo você sobre, e /sessions/suporte/auto-aberto é a metade durável para uma resposta que chegou quando você foi assinado para fora — o par de mensagem de atribuído-não aberto, que só cobriu o trabalho mudando de mãos e assim nunca Despedido pela pessoa que pediu ajuda. Testes: ChatListNativeAliasCaseTest indica a citação da coluna de consultas de feed aliases, que nada mais pode pegar — o modo de falha é um nulo silencioso, não um erro de tipo ou uma exceção. SuporteAutoOpenHqlTest analisa a nova consulta contra o modelo de entidade real, uma vez que Spring Data só valida @Query no boot e esta vive na biblioteca compartilhada, onde um erro de digitação derruba todos os serviços.
· kamo-internal
Páginas de ligação, uma página de trabalho Adicionar Membro e uma página de histórico completo
Cada tabulação em /account/view/{id} é uma rota real agora — /members, /history and the descanso são links que alguém pode enviar, marcador, ou pousar. Antigo ?tab= redirecionamento de links Para o correspondente. Cada tabulação carrega seus próprios dados, então abrir uma conta não mais espera nos dados de cada tabulação antes de mostrar qualquer um deles. Adicionar Membro abriu um brinde "em breve". Agora abre o mesmo selecionador do Gestor usos de campo, procurando cada membro da organização em vez de apenas pessoal — A pessoa que paga uma conta frequentemente não está na folha de pagamento. Pessoas já na conta permanecer visível, mas incaptável com a razão, porque um nome que é simplesmente faltando de uma pesquisa lê-se como um catador quebrado. Definir como primário e remover trabalhar também, remover atrás de uma confirmação que diz o que faz e não apaga. Tanto a mesa como a marca de escolha que tipo de pessoa cada linha é. Sem ele membro e um membro da equipe ler de forma idêntica e a lista implica em silêncio todos em Funciona aqui. As linhas dos membros também renderizam agora: as teclas do tipo reivindicadas Endpoint nunca enviou, então cada nome mostrou "---" e nada pôde ser removido - a linha id das necessidades de remoção de chamadas foi um dos campos inventados. O painel histórico da view principal torna-se EntityHistoryPanel, compartilhado com o novo account History tab: mesmos filtros, mesmo paging, mesmos estados vazios. O que fica com Cada ouvinte é apenas de onde vem a alimentação e o que significa a sua identificação.
· InitializerService
Criar Conta HISTÓRIA
Espelha a entidade AccountHistory na biblioteca do kamo. Escrito explicitamente em vez da esquerda para ddl-auto porque os padrões importam: ID e DATE CREAted carrega o banco de dados padrão para que uma inserção SQL simples funcione sem nomeá- los, o que O DDL gerado pelo Hibernate não funciona. Nenhuma chave estrangeira, de propósito — o tronco sobrevive ao que descreve. Uma conta pode ser fundida e um membro pode deixar a org sem a exclusão falhar ou o registro do que eles fizeram desaparecendo com eles.
· SecurityService
Endpoints do histórico da conta, linhas de membros reais, um diretório de todos os membros
A visão de conta pode agora dizer o que aconteceu com uma conta e quem está nela. - Cada mutação na superfície da conta registra o histórico: criar, editar o campo, apagar soft, mesclar (que move a linha do tempo do perdedor para o sobrevivente em vez do que soltá-lo), endereços, notas, e o membro adicionar/remover/caminhos primários. GET clientes/{uid}/história e /história/filtros espelham os terminais principais pedido de solicitação, porque um componente no site interno lê ambos. - getCustomerMembers retorna o que uma linha de membros precisa ser renderizada: a exibição nome, avatar, cargo, e é membro da equipe. Ambos os tipos de pessoa pertencem a um conta — o que paga frequentemente não está na folha de pagamento — e sem a bandeira Os dois leram de forma idêntica. - updateCustomerMembers aceita ids como strings (um passado único rowid() MAX SAFE INTEGER chega arredondado de outra forma), limpa o membro primário quando o a linha que está sendo removida é deles, e não muta mais o órfão Remove-mapeado coleção ao lado do repositório escreve. - A pesquisa de diretórios leva escopo=all, sobre cada membro da org em vez de a lista de funcionários, e cada linha diz de que tipo é. A catraca final aprende a negarA menos: carrega a sessão e retorna 401 sem um, ou 403 sem o direito, então um manipulador que abre com ele fez Apenas mais do que resolver uma chamada. Vinte entradas iniciais foram listadas apenas porque o exame não podia ver o guarda que já lá estava.
· kamo-shared-library
Uma pista de auditoria para as contas e uma pasta que inclui não pessoal
CONTA HISTÓRIO registra o que acontece com uma conta da forma como LEAD HISTÓRIO registra o que acontece com um chumbo — deliberadamente as mesmas colunas, porque o interno site agora renderiza tanto através de um componente e uma segunda forma teria forçado Uma segunda. - AccountHistory + repositório + AccountHistoryService: snapshot/diff sobre um campo registro, mais eventos únicos para as coisas que não são edições de campo (um membro unindo, um líder ou ordem chegando, uma fusão). Gravação nunca lança: uma auditoria escrever que falhas não devem derrubar a operação que foi auditoria. - AccountHistoryFieldRegistry é o único local onde um campo rastreado é declarado. Os totais em execução estão deliberadamente ausentes — movem-se como efeito colateral das ordens, que já têm os seus próprios eventos. - O LeadService agora escreve metade de uma ligação. Anexar um lead a um conta é um ato em duas linhas do tempo, e gravá-lo em apenas um meio página que você está olhando é o que não menciona isso. Uma pista movida entre contas escreve para ambos. - CommerceService records ORDER PLACED / LINKED / LINKED / STATUS CHANGED contra a conta de compra. Silencioso quando uma ordem não tem comprador. - Está bem. a pesquisa de diretórios somente para funcionários alargada a cada membro de uma org, para os catadores que não estão escolhendo um colega. Mantido em passo com o termo TeamMemberRepo.searchDirectório para termo, incluindo o Equipe de adesão esquerdaMembro que mantém o cargo título e departamento pesquisável para a equipe nos resultados. ativoSó pode falar para linhas que têm um status de emprego, assim os membros sem um passam em vez de serem lidos como "não empregados". - Member.getPhoneExtension: getPhone() achata para o número, deixando a extensão inatingível de fora da classe.
· kamo-internal
Confirme um relógio de entrada/saída antes de escrever o soco
O relógio de navegação esmurrou o clique. Todos os outros controles naquele bar. é idempotente ou trivialmente impossível; um soco é um registro salarial que depois apenas um administrador de RH com uma razão escrita obrigatória pode corrigir — e o botão fica um pixel do Logout. Ele agora levanta TimecardPunchConfirmDialog, construído ao padrão de NewSupportRequestPopup e deliberadamente não uma cópia dele: esse cartão é um alerta que interrompe, assim que respira, recusa seu pano de fundo e arma seus botões tarde. Este foi pedido por um clique, então ele armas de uma vez e fecha em Escape e no fundo. Verde para dentro, âmbar para fora, porque os dois resultados têm de antes que uma palavra seja lida. Sua peça central é um relógio correndo para o segundo, e a cópia diz: que o tempo registado é o momento da confirmação e não o momento em tela ao decidir — o ponto é que hesitar é livre e nada é Até que o diga. O primeiro tique está alinhado com o segundo limite, então a leitura concorda com cada outro relógio na tela do membro em vez de Aterragem onde quer que o clique o tenha feito. Comportamento que mudou com ele: - a chave de idempotência é cunhada quando a janela OPENS e reutilizada para uma repetição. As teclas por tentativa foram o erro que isto previne: uma solicitação que cronometrada pode muito bem Aterrou, e tentar novamente sob uma chave fresca é um segundo soco em um recorde salarial. - a ação é congelada em tempo aberto, então uma pesquisa dos anos 60 girando o relógio por baixo o cartão (um soco de seu telefone) não pode mudar a pergunta no meio da resposta. - o salto optimista desapareceu. Existiu porque o soco disparou o clique; o membro agora observa um cartão que permanece até que o servidor responda, então um Supostamente o estado seria apenas uma oportunidade de mostrar a pessoa errada. - um soco falhado mantém o cartão aberto e diz isso. Reverter silenciosamente um ícone é nenhuma maneira de aprender sobre um soco que não aconteceu — que surge dias depois como uma exceção do soco perdido no cartão de tempo. - a mudança de identidade (sudo modo deus, interruptor de sessão) deixa cair uma confirmação aberta, que de outra forma escreveria o soco do membro anterior contra o novo.
· kamo-internal
Filtro de estado segmentado no lugar da seleção
Pendente/Completo/Todos são três pares curtos que cabem em uma linha, assim um dropdown escondeu dois deles atrás de um clique e gastou uma etiqueta e um 170px Bem para não dizer nada. Um select fechado também não pode responder "estou olhando tudo?" sem abrir. Segmentado Filtro é compartilhado: exclusivo ToggleButtonGroup, acento tomado como um CSS var para que cada superfície mantenha sua identidade, e o nulo que exclusivo o modo mãos de volta quando o segmento selecionado é re-clicado é descartado — a O filtro tem sempre um valor. O treinamento designado também o recebe, em --training-brand: os dois painéis compartilham uma faixa de tabulação, e uma delas manter um dropdown seria lido como dois produtos diferentes.
· kamo-internal
Deslize um navegador lotado em vez de esmagar suas opções
Pinting mais opções do que um navegador pode mostrar espremido as peças fora de forma: ambas as tiras foram caixas de transbordamento cruas e as telhas são crianças flex Então a barra superior os desvirtuou (e escondeu). barra de rolagem outright, engolir opções sem sinal de que existiam) enquanto o Rail desenhou uma barra de 12px sobre o seu próprio gradiente. Ambos os navegadores agora renderizam suas opções móveis através do NavOverflowScroller: azulejos manter sua pegada, setas finais e uma borda suave desvanecer apenas no lado que ainda tem algo para alcançar, e a tira pode ser bipada, realizada, rodado, lançado com impulso, ou arrastado. A opção da página atual é deslocou-se de volta para a vista na chegada, e a barra de topo mais tempo compartilha o déficit com a única criança que pode absorvê-lo. Arrastar uma opção e deslizar a tira são o mesmo gesto em um lotado Nav, então um nav lotado coloca rearranjo atrás de um curto espaço. Aquele porão. retém o mouse para baixo do dnd-kit em vez de usar seu `atraso` restrição: a restrição de atraso ativa-se no temporizador sozinho e no dnd-kit suprime o clique de qualquer prensa que atinja a ativação, que teria parou todos os cliques ligeiramente lentos em uma opção de navegação.
· kamo-marketing
Indice as páginas de detalhes da coleção em mais 19 idiomas
A sincronização terminou, então as ~2.400 páginas de detalhes que foram noindexadas por terem A cópia do corpo inglês agora tem traduções reais e pode ser indexada. Medida em todos os 21 locais: cada um tem entre 0,0% e 8,8% da coleta valores idênticos ao inglês, exceto sw e ta em exatamente 100,0% — aqueles dois não tem provedor, então o TranslateService devolveu o texto fonte e a execução escreveu inglês de volta sob a aparência de sucesso. COLECÇÃO CONTENT LOCALES é, portanto, derivado, não listado: cada anunciado locale menos o conjunto sem provedor. sw mantém seu noindex apenas em páginas detalhadas — seu pacote base é genuíno Suaíli, por isso suas páginas estáticas permanecem indexadas — e ta não recebe nenhuma diretiva robôs, porque já canônicos para os ingleses original e empilhamento noindex no topo iria enviar dois sinais contraditórios sobre Um pedaço de conteúdo.
· kamo-shared-library
Por-membro atrasou o rollup para a matriz de conformidade
Os ramos matriciais da HR em `row.overdue > 0`, mas o legal e treinamento rolagem enviado com esse campo codificado para 0 — uma ramificação que poderia nunca ser tomado, então esses dois domínios só poderia mostrar âmbar ou verde e Degenerado. O histograma (membro, estado) não pode respondê-lo: não carrega nenhum dueAt. Então, cada um repositório ganha uma contagem agrupada cujo predicado é caractere-para-caractere um na contagemOverdueForOrg, mais GRUPO POR equipeMembro. Partilhar o predicado é o que faz as figuras por membro somarem a contagem de org-nível renderizada acima Eles. Apenas métodos de consulta. Nenhuma alteração de entidade, nenhuma DDL, versão permanece 1.5.0. Chamadores continuam a possuir o conjunto aberto, e os dois não são os mesmos: passes legais OPEN STATES, treinamento passa REMINDABLE STATES. AWAITING GRADING está aberta, mas não deve nada ao membro, e contá-lo atrasado reproduziria o legal bug AWAITTING OTHERS do módulo que disse aos membros no tempo que eles estavam semanas atrasados, Para sempre.
· kamo-internal
Compila a aba Visão Geral como um relatório sobre os outros quatro
Consola de relatórios de largura total: leitura de masthead, quatro pilares de tabulação, o registro de necessidades-atenção, a matriz de conformidade da força de trabalho e uma atividade Trem. Apenas leitura — cada ação é um link profundo para a guia de posse.
· kamo-internal
Visão geral registro de atenção, matriz de conformidade e trilho de atividade
useMemberNames sources /api/security/members/names em vez de adivinhar o resumo .../org-config forma: org-config nunca retorna uma lista de membros (confirmada pela leitura LegalPackageDialog.tsx and TrainingAssignDialog.tsx, que analisa apenas departamentos/trabalhoTítulos a partir dele), enquanto /members/names é o endpoint já construído para resolução de id-to-name e já usado da mesma forma por hipotecaAppsApi.memberNames().
· kamo-internal
Visão geral masthead e os quatro pilares de tabulação
· kamo-internal
Visão geral gancho de dados e primitivos visuais compartilhados
useHrOverview obtém resumos de assiduidade + documentos com todosConfigurados para um o serviço morto degrada apenas as suas próprias secções; ele salta as buscas inteiramente para Seções visíveisOverviewSections() diz que o membro não pode ver. A primitivos (CountUp, ProgressRing, StackedBar, StatePip, Figura, SectionShell) são os blocos de construção compartilhados da renderização de seções de visão geral com, todo o movimento guardado pelo usoReducedMotion e EASE HOUSE.

Seu universo está esperando.
Comece gratuitamente em minutos.Adicione produtos sempre que estiver pronto — sem migração, sem novos logins.
Gratuito para sempre — não é necessário cartão de crédito