Changelog
O que há de novo.
Cada melhoria para a plataforma, como ele envia.
· SecurityService
Servir o pedido público de hipoteca, endereçado por produto
Três terminais não autenticados em ******************* mais o CRUD por trás das licenças do próprio estado da organização. ** A organização é nomeada pelo PATH, nunca pelo host.** Um credor que tem não apontou um domínio para nós ainda precisa de uma aplicação marcada em branco para enviar Os tomadores de empréstimos a, e ************* é essa ligação. Resolver o inquilino do nome do anfitrião pintaria cada um desses credores no branding da plataforma e arquivar cada pista sob a organização da plataforma — que é precisamente o que acontece com qualquer coisa proxied através do compartilhado `api.kamocrm.com` gateway, onde `X-Forwarded-Host` não sobrevive. `GET /{productId}` retorna branding mais as divulgações um emprestador já publica ao pé das suas próprias páginas — não o registo da organização; Endpoint catchet já tinha razão em recusar isso uma vez. `GET * *******************estão sujeitos a um membro da equipa activa. ORGANIZAÇÃO, então um ID de membro adivinhado não pode publicar a fotografia de outra pessoa e linha directa. `POST /{productId}` é defeituosa em Capcha (ao contrário de login, que permanece permissive para um cliente móvel que não envia nenhum), taxa limitada por IP em Redis, requer a caixa de consentimento, e cria seu chumbo através «LeadService.createLead» — que verifica novamente o proprietário do produto antes de carimbar e que todos os outros caminhos de entrada na plataforma já passam. Um produto cujo mercado não é RESIDENCIAL MORTGAGE ou COMERCIAL MORTGAGE respostas 404, idênticamente a uma suprimida. Todos os três estão na base da catraca. com o raciocínio escrito. ** `OrgStateLicenseController`** é como um credor entra no que realmente é licenciado para fazer, por estado, fechado em CONFIGURE SYSTEM e escopo na consulta sobre cada escrita — um ID de licença é um UUID em um URL, e uma atualização que resolve o linha antes de verificar a organização é uma edição da regulamentação de outro inquilino divulgação. Além disso: `aplicationBaseUrl` na resposta dos fornecedores, para que a tela de configurações possa oferecer um link copiável por produto construído em `verifiedRootDomain` — o mesmo predicar as rotas do caminho sign-in em, decidido aqui porque uma frontend derivando ele da lista de domínios entregaria a um operador um nome de máquina cujo DNS nunca foi apontado para nós. E `appsLogoOverlayColor` cozido no tema config.json, que é a única rota que a marca do site de aplicativos toma.
· kamo-shared-library
Registre se um mutuário diz que esta é sua primeira casa
Um comprador pela primeira vez é elegível para programas que ninguém mais pode utilizar, por isso um líder que não carrega a resposta custa ao oficial uma chamada para pedir. O público formulário de aplicação pede; não havia lugar para colocá-lo. Nulable e tri-valorizado deliberadamente. Null significa "não perguntado", que é o que cada lead criado antes desta coluna e cada caminho de entrada que não recolher deve ler-se como — o incumprimento de false diria a um oficial o O mutuário respondeu que não. Reivindicado, como tudo no LeadClaimMortgage: ninguém verificou um título gravar, e o firewall HMDA nessa entidade aplica-se inalterado.
· kamo-apps
Reconstruir o pedido de hipoteca como uma forma real, branco-marcado
A página que substitui era um protótipo usando uma URL de produção. Foi postado para `http://localhost:8082`, então nenhuma aplicação que ele coletava chegou a nada. Ele nomeou "Mark Johnson com American Lendering Group, NMLS 343298954" como cada Oficial de empréstimos do visitante. Seu rodapé alegou uma hipoteca residencial da Califórnia Licença de Lei de Crédito, um escritório na "123 Hipoteca Lane", e uma linha de reclamações lendo "1-800-XXX-XXXX" — sob o logotipo do inquilino que aconteceu para carregar. A o texto de consentimento pediu aos mutuários que concordassem com os termos de "[Nome Broker]". **Endereçado por produto, não por máquina. ** `/empréstimo/mortgage/{productId}`, com Opcional `/{memberId}` para o link de referência de um agente de empréstimo. Os nomes de identificação do produto a organização, de modo que a página funciona de forma idêntica no próprio credor «apps.their domain.com» e em «apps.kamocrm.com» — mesmo logótipo, cores, favicon, Informações e divulgações de qualquer forma. É isso que faz a rotulagem branca possível para um credor que não configurou DNS, e é por isso que nada aqui resolve um inquilino do nome da máquina: um proxy para a máquina de API compartilhada perde `X-Forwarded-Host', e cada um desses credores teria resolvido para a plataforma. Um produto cujo mercado não é um mercado hipotecário responde 404, idêntico a um excluído um, e a página diz que o aplicativo não existe. **O rodapé é agora o próprio registro da organização** — nome legal, NMLS com um Ligação de acesso ao consumidor, endereço real e contacto, as licenças estatais que introduziu, a sua marca de alojamento justo e as suas ligações políticas publicadas. Um valor que o credor tem não entrar não faz absolutamente nada. Um rodapé vazio é uma lacuna que alguém pode preencher; A anterior era uma falsa declaração regulamentar que ninguém conseguia detectar. **Design.** Duas colunas: uma coluna de cor fixa com a marca, oficial e um trilho de progresso contínuo, contra papel. A espinha é o todo idéia — responde "a quem estou dando isso" e "quanto mais há" permanentemente, quais são as duas ansiedades desta forma. Frauntes para o declarações, Inter Tight para o trabalho, tanto auto-hospedado assim nem a imagem construir nem a primeira pintura de um mutuário depende do Google. Figuras tabulares em todos os lugares um número é digitado. Um momento orquestrado sobre carga; depois disso, movimento só responde a uma ação, e `MotionConfig reduxMotion="user" 'faz que uma verdadeira preferência em vez de um framer ignora. ** Submissão** vai para o endpoint público do SecurityService, Capcha ***, e pousa em uma página de confirmação que é uma rota real — ele sobrevive a uma actualização e a uma ligação transmitida. O e- mail do recibo é enviado para o servidor a partir do mesmo perfil de divulgação que o rodapé usa, então os dois não podem derivar. Verificado em um navegador sem cabeça em 1440 e 390: cinco etapas, validação, erro afirma, a confirmação, a página não encontrada, e machado limpo (WCAG 2.1 AA) em todos os quatro — uma falha real de contraste encontrada e fixada no trilho.
· kamo-login
Assentar cada tela em um painel arredondado sob uma fita de canto
Cada rota agora renderiza dentro .kamo-pane: um cartão inserido 15px do navegador edge com um raio de 14px, uma sombra suave, e overflow: escondido, então uma página própria O conteúdo é cortado para o retângulo arredondado. O painel também carrega um translateZ(0), que é o que o torna o bloco contendo para `posição: fixo` descendentes — sem ele a pílula Powered-by e a âncora de fundo Capcha para o viewport e escapar do clipe inteiramente, que é exatamente o caso Foi pedido recorte. A fita "Bem-vindo de volta!" é de três camadas ao redor desse painel, ordenado dentro .kamo-shell: as caudas mais escuras atrás dele, o painel, em seguida, a faixa vermelha na frente. A banda é girada 45 graus no sentido horário sobre o canto superior direito diagonal e cortado por uma caixa ancorada 8px fora desse canto, por isso pende o topo e bordas direitas por exatamente 8px, corte flush com o espaço. O corte do flush não é um atalho, é a única maneira de atingir 8px. Uma banda com as extremidades quadradas perpendiculares a si não podem pendurar uma borda por menos do que a sua espessura própria sobre a raiz dois — 24px em uma faixa legível 34px, muito mais do que um 15px gap tem espaço para. Cortando as extremidades paralelas às bordas que atravessam desacopla a penugem da espessura da banda, e dá ambas as bordas a mesma 8px. As caudas começam a largura de uma banda para fora do canto, para que seus próximos fins terminam por baixo dela, correr ao longo de cada borda através da abertura, e depois são engolidos por O painel. Só que... a costela de cada cauda limpa o painel. o entalhe de rabo de andorinha é cortado dentro dessa tira; nada mais profundo nunca mostra. Cada dimensão é uma propriedade personalizada em :root, e escalas de montagem inteiras abaixo de 640px. Cada rota dimensionou-se para o viewport, e assim transbordaram o novo painel por 30px. Cada raiz agora preenche o painel em vez disso — 100% no lugar de --app-dvh, 100dvh, 100vh e min-h-screen — e o revestimento de área segura move-se até ao palco, que é onde o inset é agora aplicado. O palco recua para 100dvh para as rotas que não medem visualViewport em -- app-dvh eles mesmos. Verificado em um navegador em 1440x900 e 390x844: o painel mede 15px de cada borda, a caixa de clipe fica 8px fora do canto, ambas as caudas caem onde o geometria prediz, e a pílula Powered-by está contida 16px dentro do painel em vez do viewport.
· kamo-shared-library
Dar um credor em algum lugar para colocar a verdade sobre si mesmo
A página de pedido público de hipotecas compôs o seu rodapé legal do nada — uma licença da Califórnia codificada, um número NMLS inventado, um endereço fictício — e serviu-o sob o logotipo de cada inquilino. Não havia fonte de dados que pudesse tornaram-no verdade. Esta é a fonte, mais as duas políticas aborda o mesmo Footer precisa e não tinha onde ler. **Organização** ganhos «privacy PolicyUrl», «termsOfServiceUrl» e `appsLogoOverlay`, tudo nulo e tudo em `patchOrganização `'s allow-list - que não é opcional: uma coluna que o método não nome é aceito com um 200 e silenciosamente nunca armazenado, e o único sintoma é uma configuração que reverte sobre o seu Próximo carregamento. ** `OrgStateLicense`** é o registro de licença própria da empresa por estado. Distinto de `mlos.legal. StewardLicense ', que é por member e responde "que isso oficial trabalhar este arquivo " — nenhuma licença de oficial está em para a empresa. A entidade, o seu repositório (em `dao.repos`, que todos os serviços já verificam; um novo o pacote falha na inicialização do contexto em cada serviço que não o adicionou) e «LenderDisclosureProfile», que reduz uma organização mais as suas licenças activas para as frases que um rodapé imprime. Sua regra de governo: um valor em falta produz NO LINE, nunca um padrão plausível. Um rodapé vazio é uma lacuna que alguém pode enchimento; um fabricado não é detectável. **`WebAddress.normalize`** transforma `www.example.com/privacy.html` em um URL absoluta, e recusa `javascript:`/`dados:`. O valor cai em um 'href' em páginas não autenticadas, onde um host sem esquema resolve como um caminho RELATIVO — presente, estilo, clicável, e 404 para cada visitante. **`LOAN APLICATION RECEBIDO`** é o recibo do mutuário, o mesmo perfil de divulgação que a página, de modo que os dois não podem derivar — ninguém lê o seu próprio email de confirmação, para que uma licença removida do site seria Caso contrário, continue a sair por correio durante meses. Seus blocos são pré-renderizados em `LoanApplicationRecebidoEmail` porque o renderizador não tem condições, e o Enviar a chave é o DOTKED ************* não o canónico keys constante — a divisão que enviou QUOOTE SENT não entregue. 18 novos testes, incluindo um afirmando que uma organização que entrou no A licença não estabelece nenhuma licença.
· kamo-internal
Publicar os endereços da política, as licenças do estado real, e uma ligação por produto
Três coisas que o site de pedido de empréstimo público não poderia ser dito anteriormente, e um que foi dito erradamente. **A Informação da Empresa ganha Política de Privacidade Endereço e Termos de Serviço Endereço.** Ambas opcionais. Ambos normalizados para um URL absoluto no borrão e novamente no servidor, porque o valor pousa em um `href` em páginas que qualquer um pode abrir, e máquina nua sem esquema resolve lá como um caminho RELATIVO — o link permanece presente, estilo e clicável, e 404s para cada visitante. «app/lib/webAddress.ts» espelhos da biblioteca compartilhada `WebAddress.normalize` para que a caixa mostre o que vai realmente ser armazenado em vez de surpreender o operador após uma recarga. **Um painel de licenciamento estatal, sob informações da empresa.** A página da aplicação hipotecária enviou uma divulgação codificada da Califórnia — "Licenciada pelo Departamento de Supervisão empresarial sob a lei de empréstimos de hipotecas residenciais da Califórnia" servido sob o logotipo de cada inquilino, incluindo inquilinos nunca licenciados em Califórnia. Essa é uma declaração regulatória falsa feita em nome de um cliente. Não havia nenhuma fonte de dados que pudesse torná-la verdadeira, então esta é a fonte: nome da licença por Estado, número, regulador, linha de reclamações e uma opção O texto exato que imprime verbatim. Uma organização que não entra nada imprime nada; o painel mostra a frase composta antes de salvá-la, porque o alternativa para lê-lo aqui é ouvir sobre isso de um mutuário. **Vendors & Products carrega um link de aplicação copiável por produto.** `https://apps.<verified Regressar ao apps.kamocrm.com — e o alerta acima da tabela diz claramente que a plataforma host não é um link menor, porque um operador que assume que mostra A marca não a enviará. Não: a página resolve o credor do ID do produto no caminho. A base vem do servidor (`verifiedRootDomain`, as mesmas rotas de entrada predicadas em), nunca a partir do nome de máquina do navegador, então Ninguém recebe um endereço cujo DNS nunca foi apontado para nós. Apenas mercados cujos produtos realmente têm uma página obter a coluna; os outros seriam 404. 'npm teste' verde: 447 arquivos, 5525 testes, e todos os quatorze guardas.
· kamo-internal
Iniciar uma sessão de codificação a partir do menu Linux Desktop, em um modelo e esforço que você mantém
O ícone já abriu uma área de trabalho remota e um terminal remoto. A terceira coisa as pessoas realmente ir para aquela máquina para era alcançável apenas abrindo o Centro de Interação, mudando para Sessões e encontrando a Nova linha -- três movimentos para fazer o que o ícone é para. antes que seja um destino, então a linha dos pais não lança nada e seus filhos são as respostas. Modelo e esforço carregam seu valor atual na etiqueta -- a membro não deve ter que expandir uma linha para saber o que sua sessão irá executar como - e expandir para grupos de rádio lidos de AI MODELS e AI EFFORTS, então um modelo adicionado para o vocabulário aparece aqui sem que ninguém se lembre que este arquivo existe. A regra sob Esforço separa o que a sessão será executada como da linha que corre o mesmo dispositivo que a lista de tamanhos usa. As duas configurações NÃO são locais para este menu. Eles são a posição do membro escolha, leitura e escrita através do usoAiRunSettings -- o mesmo gancho do Enviar para IA modelo escolhido em qualquer um dos três está na tela nos outros dois antes do menu terminou de fechar, e ainda está lá no próximo navegador. Ele monta o membro IU preferencia map, que já é lido uma vez por carga de página, então isso custa não Viagem de ida e volta. Três coisas tiveram de mudar por baixo: * NavToolItem ganha 'mantenha aberto'. Cada linha usada para fechar o menu, que está certo para uma linha que vai em algum lugar e errado para uma que ajusta uma configuração: modelo e o esforço são duas metades de uma decisão com o lançamento sob eles, então fechar na primeira escolha fez escolher tanto uma missão de três visitas -- e Derrubou o grupo, escondendo a marca que tinha acabado de se mover. * opencoderSession junta openTerminal em app/lib/terminal. A Interacção Centro inicia as sessões em si e tem que, porque possui uma linha inicial, e linha de erro e uma lista de terminal para atualizar; cada outra superfície tem uma linha de menu E nada mais. Ele nunca rejeita -- uma linha de menu não tem onde colocar um rejeitado promete -- e entrega a própria recusa da máquina ao brinde, verbatim, o caminho O painel imprime-o. * A placa de lançamento home renderiza crianças. Desenhou apenas o nível superior de cada seção, que silenciosamente deixou cair cada 'crianças' array em toolSections: Remote Área de trabalho, cada tópico Configurações e agora este são linhas que um membro pode clicar para sempre sem nada acontecer. Eles nunca foram linhas quebradas -- eles são perguntas cujas respostas nunca estavam no ecrã. Agora expande-os com o regra própria do nav (uma linha com um destino vai lá, uma linha que é apenas um pai expande, uma linha que é tanto vai lá e alterna em seu chevron), assim as duas superfícies que lêem uma lista de secções não podem mais discordar do que click significa. A dica dizia "Remote Desktop or Terminal" e agora nomeia todos os três. Removido de todos os 21 arquivos locais no dicionário no mesmo push, porque editar O inglês sob uma chave traduzida não muda nada, mas o inglês.
· kamo-internal
Terminar o painel de calendários pessoais e colocá-lo onde um membro pode alcançá-lo
PersonalProviderSection estava andaimes que nada renderizado, e não iria funcionou se tivesse: leu GET /member/{id}, uma rota que não existia, então falhou ao abrir; ofereceu um botão Adicionar para Google e Microsoft que POSTOU um linha sem credenciais, produzindo uma conexão que nunca poderia sincronizar; sua alternância bidirecional enviada em que a coluna é bidirecional, assim o comutar movido e nada foi armazenado; e tinha um "Unificar com a organização" mudar para um campo ContactIntegration não tem, que o serviço deixou cair Cada escrita. Agora ele lista o que um membro tem conectado com seu estado de sincronização real, conecta o Google e Microsoft através do fluxo OAuth, toma uma URL e senha para iCloud e servidores personalizados, e diz quando uma conexão não possui credencial -- qual o muda sozinho não pode, e que é a diferença entre "off" e "not can trabalho". O interruptor Unify desapareceu em vez de ser falsificado: não há onde guardá-lo, e um interruptor que silenciosamente persiste nada é pior do que nenhum interruptor. DavOAuthConnect ganha um escopo em vez de ser copiado. Tudo o que tem de difícil -- um callback sobre outra origem, um popup que não pode relatar seu próprio resultado, o re-read que é o único sinal confiável -- é idêntico para um membro e para uma organização, e uma segunda cópia iria derivar exatamente sobre as partes ninguém Reler. No escopo do membro ele adiciona ?member=true e o servidor leva o membro da sessão, então o painel não pode se dirigir a mais ninguém. Vive na gaveta de preferências do calendário. O tópico de configurações de email é fechado em MANAGEM EMAIL SETTINGS, então colocando calendários pessoais lá teria Escondi-os de quase todos os que têm calendários. Último na gaveta e atrás de um divisor: cada bloco existe uma preferência que o painel possui, enquanto este autoriza um terceiro e pode falhar de forma que o membro tenha que agir. Caminhos chave são literais e o espaço de nomes está em linha. check-i18n-keys resolve useTraduções('<literal>') e desista do usoTraduções(SOMETHING) -- e desistir significa ignorar cada chave no arquivo, não apenas uma, então um const lá teria deixado os trinta sem controlo. Alcançar t através de um árbitro para mantê-lo fora de as dependências do carregador escondem um local de chamada do guarda da mesma forma, então o O carregador define uma bandeira e o renderizador traduz- a.
· EmailService
Deixe um membro conectar seus próprios calendários e contatos
O painel de calendários pessoais pediu-me para ir embora. que nunca foi mapeado -- então falhou em cada aberto -- e para o Google e Microsoft não tinha como autorizar nada. Este é o serviço metade de A fazer funcionar. O membro viaja no estado de OAuth, e essa é a única coisa que separa Os dois fluxos. O registro do aplicativo, os escopos e o redirecionamento de URI registrado são todos org-scoped e byte-identical para uma conexão pessoal e um organização-wide um, e o callback é sem sessão -- então sem um ID de membro em `state` uma pessoa que autorizasse sua própria conta do Google a linha da ORGANIZAÇÃO e remarcar a sincronização de cada colega em uma pessoa calendário. Ausente ainda significa a organização, que é o que cada estado escreveu Antes que isto pareça. /oauth/url, /oauth/status e DELETE/oauth take ?member=true, e o membro vem da SESSION em vez da consulta -- um cliente nomeando um membro id seria escolher o calendário para se conectar. salvar os upserts do MemberOuth por (membro, provedor) em vez de inserir como salvar a integração de membros faz. Inserir é certo para um servidor DAV digitado, uma vez que pessoa pode manter dois, e errado para uma bolsa: há uma conta do Google conectada em um momento, e re-consenting tem que substituí-lo em vez de deixar uma segunda linha para Os trabalhos de sincronização para ambos. GET /member/{memberId} agora existe e recusa qualquer ID exceto o do chamador. Largando o id e a leitura da sessão teriam sido mais simples e errados: Para que os calendários de outra pessoa fossem respondidos com os seus. Não existe direito que o abre -- uma bolsa privada do Google não é um ativo da organização, então um O administrador também não tem leitura.
· kamo-internal
Gate a conta de usuário em direitos de plataforma, não em que org possui
Autor numa sessão concorrente nesta máquina e pedido do usuário; gravado para que a proveniência não seja adivinhada posteriormente. A conta de usuário é a PESSOA, e editá-la usada para exigir o atual organização para possuir a conta. Isso foi errado duas vezes. O proprietário de uma conta é uma pessoa, não uma organização, então a condição respondeu a uma pergunta sobre administradores e estava a ser aplicado a alguém que editava os seus próprios detalhes. E era um beco sem saída na prática: a plataforma fornece a conta para quase cada membro do inquilino — 77 dos 141 membros em 2026-09-04 — e 37 dos membros as pessoas não são membros da organização que possui sua conta em tudo, assim não existia espaço de trabalho em que pudessem editar o seu próprio nome preferido, site ou links. O portão é agora o proprietário, um operador idóneo, o usuário do sistema, ou um titular dos direitos de plataforma que administram contas de usuário (MANAGE USERS / SYSTEM USER — a guia Usuários na /conta). A organização de topo pré-condition passeios ao longo em vez de ser verificado separadamente, porque um sessão dentro de um inquilino resolve nenhuma plataforma direito em tudo. Os direitos dos inquilinos permanecem deliberadamente ausentes. GESTÃO MEMBROS E SECURIDADE MEMBER não chegar à conta do utilizador: um administrador de inquilinos pode editar a sua própria modelos de segurança da organização, de modo que um inquilino que detém a capacidade de reescrever a identidade de cada outra organização que pertence ao "consegue ver". Esse é o mesmo raciocínio que os mantém fora da senha. Com a condição de proprietário-org se foi, a "sua conta é gerenciada em outro lugar" o estandarte e o link de entrada do cross-org que carrega não têm mais nada para explicar, Então eles e a canalização são removidos.
· EmailService
Dar a mesma forma à árvore de pastas de cada provedor antes que qualquer cliente a veja
Reportar o atributo SPECIAL-USE de uma pasta não foi suficiente, porque apenas um servidor envia um se estiver configurado para — e o Dovecot KamoMail na produção envia nenhum em tudo: seu papel de respostas listando: null para Sent, Lixo e Lixo. Então... Google caixa de correio ainda mostrou "[Gmail]" entre as pastas do próprio membro com o seu real Enviado enterrado dentro dele, e um "Enviado" vazio preso acima apontando para uma pasta O Gmail não tem. FolderRoleMapper resolve cada função de, por ordem: o atributo que o servidor enviou, que é a única fonte que conhece um "Gesendet" localizado é a pasta Enviada; os caminhos que este provedor é conhecido por usar; e o nome da própria pasta, que é onde cada servidor sem SPECIAL-USE termina e é exatamente o que o cliente Costumava fazer por si mesma. Um papel é reivindicado uma vez, então o próprio membro "Enviado" no Gmail não pode pegar o slot de [Gmail]/Enviado. Assinala também os contentores em que um fornecedor envolve as suas pastas — [Gmail] e [Google Mail] — como escondido. Ainda estão listados, porque seus filhos nomeiam eles como seus pais, mas nenhum cliente os mostra. Adicionar um provedor é adicionar uma linha ao mapa em vez de outra ramificação em um componente, e tanto a caixa de correio do membro e um compartilhado passam por ele.
· kamo-internal
Conectar via OAuth na aba CalDAV/CardDAV realmente conecta
O botão de alerta levantado (' Fluxo OAuth ainda não implementado - configure credenciais manualmente através da API'). Agora abre o verdadeiro Google Workspace / Microsoft 365 fluxo de consentimento e relata o que voltou. O limite da confiança é a parte interessante. O popup completa na chamada de retorno registrado no próprio console do provedor, que é servido no host API -- a origem diferente desta janela -- por isso a sua pós-Mensagem normalmente falha na origem verificar e o resultado é aprendido com o popup simplesmente fechando. Um sinal que carrega nenhum resultado nunca deve ser lido como um sucesso, então cada caminho termina perguntando o servidor o que ele realmente armazenado: um membro que fecha a tela de consentimento é não mostrou nenhuma ligação. O painel também incorpora OrgOAuthAppSection, então o aplicativo conecta através é visível e editável aqui, e é o registro SAME a aba Configuração do Provedor lê. Três correções encontradas durante a fiação, todas nesta aba: - Um laço de renderização. O carregador de status dependia de `t`; useTranslations() não promete uma identidade estável entre renders, de modo que o carregador foi recriado, Efeito redisposto, definir estado, e girar novamente. Nada aqui guarda 't' através de um callback now -- um problema é transportado quer como formulação o serviço escolhido ou como uma das quatro chaves chamadas. - A guia errou em todas as cargas. GET /org respostas 204 para uma org sem integração, e contact integrations é um cluster vazio, então res.json() jogou no corpo vazio e em todas as visitas levantadas "Falha em carregar configurações de integração". - O interruptor de duas vias de sincronização nunca reflectiu o que foi armazenado: leu 'bidirecionalSync' onde a linha carrega 'bidirecional'. Lê ambos, envia o Uma coluna tem o seu nome. A barra de sincronização ao lado imprimiu seu serviço em bruto (IDLE / SYNCING / ERROR) com uma "Última Sincronização:" codificada e nunca mostrou a mensagem de erro da linha -- então um chip ERROR disse a um membro que algo estava errado e não lhes deu nada para agir. Ele agora nomeia os estados e mostra a razão, e Sync agora re-leia a linha Só começou. O inglês da aba que permanece em código rígido também está traduzido. Caminhos chave são soletrados na íntegra em cada local de chamada em vez de construído a partir de um prefixo compartilhado const: scripts/check-i18n-keys.mjs resolve um t literal('a.b.c') e um modelo cuja parte estática vem em primeiro lugar, mas não consegue ver através t(`${KEYS}.c`) -- verificado quebrando uma chave atrás de tal prefixo e observando o O guarda ainda se apresenta limpo. Atrás de um desses, todas as 15 novas chaves teriam enviado sem proteção, e um erro de digitação renderiza como um caminho de chave cru na tela.
· EmailService
Conecte calendários Google e Microsoft sobre OAuth, no aplicativo que a caixa de correio já usa
A guia CalDAV/CardDAV Connect via botão OAuth levantou um alerta dizendo o o fluxo não foi implementado e configurar credenciais através da API. Agora. executa uma conexão real, e a restrição que o moldou é que um redirecionamento OAuth URI vive em um console de uma TERCEIRA PARTE: Google e Entra cada segurar exatamente um endereço por inscrição do aplicativo, digitado por quem o registrou, então um segundo O fluxo não pode ter uma segunda chamada. Por conseguinte, ambos os fluxos caem no endereço já registrados e são contados separadamente por um marcador `flow` dentro de `state` -- OAuthFlowKinnd, ausente significando EMAIL assim callbacks no voo continuam completando onde Sempre o fizeram. Sem alteração da consola, sem nova rota de gateway, sem nova sessão isenção de uma via pública. O que o novo fluxo compartilha com o fluxo de caixa de correio que ele compartilha deliberadamente: - O registo da aplicação. GroupwareOAuthService pergunta PlatformOAuthClientResolver a mesma pergunta que a guia Configuração do Provedor pergunta -- Google Próprio da organização ou aplicativo Entra se ele registrou um lá, senão aplicativo de plataforma de Kamo -- assim credenciais introduzidas uma vez servir ambos, e uma org que não tem nenhum é dito assim em vez de ser enviado para uma tela de consentimento que rejeita um cliente id em branco. - Redireccionar o URI, ler de... Reafirmado. Uma cópia, porque um segundo seria um redirecionamento uri missmatch esperando para alguém mover uma chamada de volta, e já está coberto por OAuthRedirectUriTest contra a tabela OAuthCallbackPaths partilhada. O que não partilha é o subsídio. Pede Calendário e Contactos que o Gmail e o Admin SDK, e armazena seus tokens no org's ContatoIntegração em vez de sua linha de provedor de e-mail, então conectando calendários não pode perturbar uma caixa de correio que funcione. escopos de leitura-escrita, não .readonly: two-way sync é um interruptor que o membro muda após o consentimento, e os escopos são fixados no consentimento. O connect prova-se antes de relatar o sucesso -- ele lê a conta que acesso concedido e lista um calendário. Uma subvenção que não possa ler calendários, ou que voltou sem nenhum token de atualização, é armazenado e sinalizado com o que fazer sobre isso; ambos são invisíveis até uma sincronização dias depois. Credenciais são escritos na forma das quatro actualizações existentes já lidas pela tecla de texto (accessToken / re updateToken / tokenExpirar como epoch milis / clientId / clienteSecret / locatárioId), então uma conexão permanece viva sem mais trabalho, e GroupwareOAuthServiceTest fixa esses nomes: renomeando um compila, implementa, conecta-se, e pára de trabalhar uma hora depois. Ao lado, três fixações na mesma superfície: - GET /settings/integrations/org devolveu a entidade, que colocou o org's Credenciais criptografadas por AES blob em um corpo de resposta, um cache de navegador e cada log proxy entre aqui e o membro, em cada carga de uma tela que nunca lê o campo. Ambos os terminais de org agora retornam uma visão explícita da linha, informando apenas se uma credencial está em arquivo. - applyConfig leia 'bidirecional' enquanto cada tela de configurações sempre enviou 'bidirecionalSync', então o interruptor de duas vias nunca persistiu. Aceita ambos. - clearOrgCredentials, por isso desconecte esquece o subsídio sem redefinir o opções de sincronização que o membro escolheu -- que deleteOrgIntegration faria. A sincronização calendários e contatos em si é inalterada e ainda não construída: o Google e Microsoft GroupwareProviders são stubs delegando para o DB local, ProviderRegistry vincula KamoGroupwareProvider qualquer tipo de provedor, e As tarefas de sincronização do DaemonService só batem uma sincronizaçãoToken. Isto aterra a credibilidade daqueles vai precisar, e nada ainda lê.
· kamo-shared-library
Tente novamente o membro lê que falha primeiro após uma DDL
A carga membro-a-ID está perto da primeira instrução uma solicitação autenticada Então, uma versão de catálogo bate-lhe antes de atingir qualquer outra coisa. Isso. é por isso que a última janela foi reportada como "o widget de placar não pode ser atingido": a consulta que realmente falhou foi uma carga de identidade chata compartilhada por quase todos os endpoints, e qualquer que seja a característica anunciada sua própria falha mais alta Tenho a culpa. Anotado aqui em vez de no 96 membroRepositório. sites de chamadas findById em Serviço de Segurança, nenhum dos quais é um ponto de estrangulamento e a maioria dos quais senta-se dentro @Métodos transacionais onde uma repetição não faria nada. Estas quatro leituras estão na biblioteca compartilhada, então um lugar cobre a frota. A repetição é significativa em exatamente estes métodos porque eles não são @Transactional: cada chamada de repositório é executada em sua própria transação implícita, então a a segunda tentativa recebe genuinamente uma nova — a mesma razão em que trabalha Serviço de Resolução de Org. Adicionando @Transactional a qualquer um deles mais tarde sem se mover A repetição com ela transformaria estes em no-ops. Só lê. Repetir uma leitura é seguro por inspeção; os caminhos de escrita nesta classe são deixados em paz. O teste afirma que a anotação está em vigor na classe real através de um real proxy, não que o assistente de retentação funcione. Falhou com o conflito bruto até o contexto registrou um criador de auto-proxy, que vale a pena saber: sem um, o feijão sai sem aproximação e cada anotação sobre ele é inerte enquanto Parece perfeitamente correcto. Não coberto: member.getOrganização(), que é uma deferência de LAZY resolvida mais tarde sob open-in-view, fora de qualquer método uma anotação pode embrulhar.
· MediaService
Enviar o avatar do remetente com a mensagem e o seu push
A notificação de chat realizada `senderAvatar: ""` com o comentário "avatar resolvido no frontend", e o push carregou nenhum avatar em tudo. Um navegador pode resolver um — tem o diretório membro carregado. Um telefone a levantar uma flutuação cabeça de um PUSH não pode: o aplicativo pode não estar rodando, então não há nada para procurar qualquer coisa dentro, e a cabeça mostrou iniciais até que o membro abriu o conversar e minimizá-lo novamente. membroAvatarUrl é o gêmeo de um membro do solicitanteAvatarUrls e re-leituras o avatar linha por id pela mesma razão: a associação é preguiçosa e Avatar é uma hierarquia CONJUNTA, então o proxy off getAvatar() reporta o tipo BASE e cada foto carregada seria procurada no caminho do vetor. Melhor esforço em todo — um rosto ausente degrada-se para as iniciais, que é o que 404 na imagem teria produzido de qualquer maneira, e não deve custar a mensagem.
· kamo-shared-library
Tente novamente um método transacional em uma transação NEW, e pare de abrir os direitos
Duas soluções que só fazem sentido juntos. @RetryOnDbConflito repete um método FORA de sua transação, então cada tentativa Começa um novo. Tentar novamente dentro da transação — que é o que você obtém se nada disto é sutilmente errado — não é um benefício menor, mas nenhum: transação que viu SQLState 40001 falha em cada instrução subsequente, então cada tentativa extra re-runs trabalho que não pode commit. Também parece que é. O trabalho, que é a parte perigosa. O log preenche com repetições, o método realmente é chamado três vezes, e o erro no final é o que você teria Tinha mesmo. Assim, o teste conta TRANSACÇÕES INÍCIO, não tentativas. Esses dois números são idêntico no caso de trabalho e diferente no quebrado, e nada mais Separa-os. Pegou três defeitos reais enquanto isto estava sendo escrito: 1. Um conselheiro simples @Component é invisível para o criador auto-proxy que @EnableTransactionManagement instala, que considera apenas os assessores marcados ROLE INFRAESTRUTURA. Cada repetição silenciosamente não fez nada. cadeia interceptor, então um segundo proceed() retomado após a transação interceptor e reinvocou o alvo diretamente, dentro da falha transacção. Cada tentativa agora recebe um invocávelClone(). 3. O conselho não deve alterar o contrato de um método, então uma exceção declarada verificada é transportado através do limite de reteste e retrown inalterado. Construído em simples Primavera AOP, não @Aspect, para evitar colocar aspectjweaver em 60 imagens para um interceptor. Segundo: Direitos de MembroAplicadoServiço não continua mais quando o modelo de segurança os modelos não podem ser lidos. Registrar um aviso e continuar soa como o escolha cuidadosa e é o seu oposto — modelos não são apenas uma fonte de extra direitos, e um valor NUKE é como um modelo diz "desligado por padrão", então pulá-los deixa uma posição certa que a organização pretende reter. O resultado foi então persistido. Um soluço de banco de dados transitório poderia, portanto, entregar um membro acesso ninguém concedido, com uma linha warn como o único vestígio. Agora joga com a causa anexada, e os dois pontos de entrada carregam @RetryOnDbConflito — o causa realista é um bump catálogo-versão, e uma repetição só pode acontecer se o O fracasso é permitido alcançá-lo. 2757 testes verdes.
· kamo-marketing
Diga o que o plano gratuito não cobra, além do que os pagos fazem
Agora que Starter e Business cada um carrega uma taxa única no cartão, um Free cartão que não diz nada sobre ele convida exatamente a inferência errada — o leitor que acabou de ler "$99 por assento" duas vezes não tem razão para assumir o nível acima A dobra é diferente. O único plano onde "sem taxa de inicialização" ainda é verdade agora diz assim, na mesma posição e a mesma voz que seus vizinhos, e responde Pergunta de cartão de crédito no mesmo fôlego. Escopo ao mensalPreço === 0, que é Livre sozinho. Enterprise é -1 e negocia os seus próprios termos, por isso não afirma nem uma taxa nem a ausência de um.
· SecurityService
Expor o que uma mudança de assento custará antes de ser feita
Aumentar o número de lugares de uma subscrição aumenta agora uma factura — uma startup por banco taxa segue a pessoa, então dez contratações em um plano de $99 é $990 e uma conta grande corre para quatro dígitos. O console precisa de algo para perguntar antes de commits um operador para isso, e tem que ser o servidor que responde: acordo pode substituir a taxa, e uma figura calculada no navegador seria uma Em segundo lugar, a aplicação dessa regra sem nada a manter honesta. Apenas leitura, fechado em MANAGEM SUBSCRIÇÕES como a alteração que ele pré-visualiza.
· EmailService
Envie o e-mail de uma organização como seu próprio domínio quando o SPF diz que podemos
O endereço De foi escolhido perguntando quem hospeda o MAILBOXES da organização. Essa é uma pergunta diferente da que importa, e foi errado em ambos instruções imediatas. chinilaw.com roda no Google Workspace e tinha adicionado incluem:spf.kamocrm.com seu SPF — autorizando nosso relé explicitamente, por escrito, em DNS — e ainda tem "Ron Chini, PC" <NoReply@kamocrm.com> em cada verificação e reset de senha Correio. Enquanto isso, seis organizações cujo SPF nunca mencionou nós estavam enviando como seus próprios domínios, porque hospedamos suas caixas de correio: correio não autenticado reivindicando um domínio que não enviamos, que é os receptores de configuração spam-pasta e rejeitar totalmente sob uma política rigorosa DMARC. Então a regra agora é o registro DNS: possuído, e autorizando-nos. Ambas as metades são requerido. Propriedade sem SPF é exatamente o correio não autenticado acima; SPF sem propriedade deixaria qualquer um que adicionasse um mecanismo include emprestado um domínio. Desconhecido lê como não — um domínio que a varredura ainda não atingiu o endereço da plataforma, porque adivinhar errado dessa forma custa a marca enquanto adivinhando errado a outra maneira custa entrega, silenciosamente, na mensagem que a maioria Tem de chegar. Uma linha de seleção alimenta tanto o endereço quanto a decisão. A ler as bandeiras um domínio ao enviar como outro autorizaria algo que ninguém verificou, e nada pareceria errado: a mensagem ainda envia. Uma recusa dura no relé agora volta uma vez do endereço da plataforma em vez do que perder o correio. Isso não é deliberadamente a proteção contra um domínio não autorizado, e não pode ser: uma falha SPF não é uma falha de envio — o relé aceita a mensagem, o servidor recebe-a, e é silenciosamente spam-dobrado ou saltado mais tarde. O pré-controlo é a protecção. Caso mais estreito onde o envio realmente jogou.
· kamo-shared-library
Deixe o console perguntar o que custa uma mudança de assento antes de fazê-lo
changeSeats agora levanta uma fatura para a taxa de inicialização em novas cabeças, que gira um número em um diálogo em uma cobrança — dez contratações em um plano de $99 é $990, e em um grande conta são quatro figuras. Um operador deve concordar com isso em vez de aprender sobre isso com o cliente, então há algo para perguntar. Apenas leitura, e resolvido através exatamente o código que irá carregá-lo. Computação a mesma figura no console seria uma segunda implementação de uma regra que acordo negociado pode substituir, e o número na janela tem que ser o número da factura.
· SecurityService
Registre quais domínios nos autorizam, para que seu e-mail possa sair como eles
E- mail transacional atualmente escolhe seu endereço de From perguntando quem hospeda um OS MAILBOXES DA organização. Essa é a pergunta errada. Se podemos enviar como chinilaw.com é decidido por chinilaw. com registro SPF, e uma organização em Google Workspace pode autorizar o nosso relé perfeitamente bem — chinilaw.com já tem, e tem recebido NoReply@kamocrm.com de qualquer maneira. Então a resposta é lida do DNS e anotada, ao lado da propriedade e SSL bandeiras fica ao lado conceptualmente. EmailService então lê uma coluna em vez de resolução de DNS no caminho que oferece uma redefinição de senha. A sua própria varredura, não a SSL. Isso varre os domínios SSL-CONFIRMED, e SPF Não tem nada a ver com certificados: chinilaw. o ssl confirmado do com é reaberto sobre um certificado de carreira em falta enquanto o seu SPF é perfeitamente bom. Pendurar isto. Ele teria ignorado a única organização para a qual foi escrito. Uma pesquisa falhada do DNS não escreve nenhum veredicto, apenas um timetamp. As duas formas de ser errado aqui não são simétricos: registrando um domínio não autorizado como autorizado envia correio não autenticado que recebe pasta de spam, e a senha da org resets param de chegar sem nada para mostrar para ele; gravando um domínio autorizado como custos não autorizados branding apenas. Um blip de resolução não é evidência sobre qualquer um registro SPF, e não deve trocar de uma organização de endereço até que o A próxima varredura vem em volta. O conselho final também escreve o veredicto, então um proprietário que acabou de corrigir seu registro vê que faz efeito na página que eles estão olhando, em vez de um 15 minutos depois. Nada lê a bandeira ainda; a mudança do EmailService segue.
· kamo-shared-library
Registre se o SPF de um domínio nos autoriza a enviar como ele
Duas colunas ao lado da propriedade e bandeiras SSL, respondendo à terceira pergunta DNS sobre um domínio: podemos colocar seu nome na linha From do correio transacional Mandamos buscá-lo. As colunas já existem na base de dados — desembarcadas antes disso, deliberadamente. Mapeando uma coluna que a tabela não possui quebras em todas as consultas org domains em toda a frota, e os 500 aparecem em qualquer repositório a seguir lê um domínio em vez de neste. Ambos nulláveis e sem DEFAULT. NULL significa "nunca verificado", o que não é a mesma declaração que "verificado, e não autorizado": o primeiro é o silêncio, o A segunda é a prova. Mantê-los separados é o que permite que uma procura DNS falhou deixar um veredito existente em pé em vez de discretamente rebaixar o correio de uma organização para O endereço da plataforma até à próxima varredura.
· kamo-internal
O Hexhead de um terminal diz se te quer
Um membro com seis terminais minimizados tem uma pergunta sobre cada: À minha espera? A cabeça agora responde em uma cor. trabalho verde — nada é desejado de você âmbar esperando por você, e você não olhou desde que começou a esperar espera cinza, e você viu Três estados e nada mais. Um quarto teria que ser lido em vez de olhar para, e um olhar é o ponto de uma cor. Verde e âmbar são do próprio emulador - o mesmo verde a lista de terminais já usa para "este shell é anexado" — então alguém que aprendeu um tem Aprendi o outro. Cinzento não é deliberadamente uma cor: um terminal que você tem já visto deve retroceder, e a maneira de retroceder é parar de ser colorido. A metade ocupada/espera vem da máquina (comando de primeiro plano de uma shell, um máquina pode dizer que um terminal está esperando, mas só este navegador sabe se o a pessoa já olhou para ele — e sem essa distinção seis terminais ociosos brilho para a atenção para sempre e a cor deixa de significar qualquer coisa. Revisado significa visto desde que começou a esperar, nunca visto. Um terminal você aberto, ler e guardar, que então saiu e fez mais trabalho e parou Outra vez, é pedir por você uma segunda vez; uma regra que só lembrou "você tem Nunca poderia dizer isso. Assim que um terminal deixa de ser ocupado é registrado e comparado contra. A TRANSIÇÃO é o que está gravado, nunca o estado — um terminal já esperando quando a página carregada não tinha nenhum, e carimbar "agora" marcaria cada terminal na máquina sem revisão após cada refrescar. Teclado no ID da sessão da shell, não nos IDs da janela da ferramenta: contador que reinicia em cada carga de página, para que uma revisão gravada contra um seria siga o terminal que aconteceu para abrir primeiro após uma atualização. Pesquisas a 3s em vez dos 10s do painel - uma lista é algo que você lê, mas uma cor é algo que você percebe, e um que leva dez segundos para ir âmbar depois de uma construção terminar é uma cor que você aprende a não confiar. Apenas enquanto algo é realmente minimizado, então um membro sem cabeças para fora não paga nada. A cor é empurrado somente quando mudou, assim uma cabeça que foi âmbar por dez minutos não re-render a cada três segundos. HexHeadTerminalStateSync fica ao lado do HexHeadUnreadSync e existe para o mesmo razão: é a única coisa que pode ver tanto a lista de janelas como uma fonte de dados O motor Hexhead não sabe de nada. 13 guardas e 5381 testes. check-i18n-keys permanece vermelho em outra sessão trabalho de assinatura, nada aqui.
· SecurityService
Transportar o estado de atividade de um terminal para o console
TerminalGanhos de resumo `state`: ocupado, ou esperando por seu membro. Ler o transcrição da sessão — nenhum dos quais é conhecido daqui, por isso viaja em vez de ser derivado. Null de um agente muito velho para enviá-lo, que o console lê como esperando: a "ocupado" errado esconde que um terminal precisa de alguém, uma "espera" errada apenas desenha O olho.
· kamo-internal
Dar uma cabeça dica do próprio aplicativo, colocado de onde ele se senta
A etiqueta era a nativa do navegador `title` box: unstyled, e colocar onde quer que o outros 291 alvos pairando neste uso cromado, seta e tudo. Uma cabeça não é um alvo hover normal, por isso a dica é dirigida em vez de Acionado. Os próprios ouvintes da MUI estão todos desligados: o elemento que os ligaria é um invólucro transformado de tamanho zero nada pode pairar, e seu toque de longa pressão é o mesmo gesto que abre o menu de contexto da cabeça. O palco possui um compartilhado popper, abre-o do ponteiro do HexHead entrar / sair, e fecha-o em qualquer imprensa - uma etiqueta pendurada numa cabeça a ser atirada pelo ecrã não é uma etiqueta. A 400ms trilha de etiquetas piscando. A âncora é um elemento virtual lendo os valores de movimento da cabeça em cada chamada, não o nó DOM. Cabeças se movem escrevendo sua transformação diretamente para o DOM com nenhum React render no meio, para que um rect medido em aberto fique obsoleto no momento em que um refluxos de cluster em torno de uma janela recém- aberta; Popper é empurrado para re- execução sempre que esses valores mudam. Colocação é decidida de onde a cabeça está, por placeTooltip: abra longe do borda horizontal mais próxima, e para uma cabeça no terço exterior da tela alinhar a dica está perto da borda para que cresça para dentro em vez de ser empurrado de volta ecrã. Popper's flip and preventOverflow fica por baixo como a rede de segurança, ligado o viewport com a mesma banda 8px keep-out que o menu de contexto usa — eles cobrem o que a geometria por si só não pode, um título maior do que o espaço que a colocação deixa. Verificado no Chrome, não só em jsdom: oito posições de cabeça (quatro cantos, o centro, um título de 90 caracteres no canto, e duas cabeças arrastadas para fora da borda) através de um 1200x800 e um viewport 480x900, todos os dezesseis rótulos totalmente dentro do margem; e uma cabeça movido 250x120px pelo valor de movimento sozinho, com o rótulo Estou a segui-lo até ao pixel.
· kamo-marketing
Declarar a taxa de inicialização, e parar de alegar que não há um
O site disse "$0 taxas de configuração" três vezes nos metadados da página de preços e listado "Nenhuma taxa de embarque obrigatória" como uma vantagem sobre a HubSpot. Tudo isto é Agora falso: planos pagos têm uma única vez $99 por assento. Uma página de preços que omite um a taxa de quatro dígitos sobre uma ordem de 25 lugares não é meramente incompleta, é a razão alguém encontra o número pela primeira vez no checkout. A comparação HubSpot sobrevive honestamente. Os seus $1.500-$3.000 Professional onboarding recurs por Hub; o nosso é uma vez por assento e uma ordem de magnitude menor. Esse sempre foi o ponto real — a velha bala acabou de chegar A reclamar um zero que já não cobramos. A taxa é lida fora do catálogo em vez de escrito na cópia, para o mesmo razão para o teto do assento é: é um número as contas do servidor em, e uma frase dizendo que iria continuar a afirmar o antigo no dia em que muda. A aritmética vive em plan-pricing.ts ao lado do termo escada, espelhando SetupFees na servidor, e o guarda que existe para pegar um número errado renderizando perfeitamente agora Verifica. Uma assimetria deliberada no limite do catálogo: um modelo ausente PER SEAT aqui, onde o servidor é padrão para FLAT. Este site renderiza exatamente um catálogo e cada plano pago nele é por- assento; a API relata o valor sem modelo até que os serviços sejam reconstruídos contra a biblioteca compartilhada que introduziu Ele. Predefinição para FLAT nessa janela iria imprimir "$99" contra uma ordem realmente faturado $2,475, e subestimando uma carga em público é o valor do fracasso a falhar. Um FLAT explícito é sempre acreditado. A calculadora carrega a taxa em sua própria linha em vez de no total, que é um número mensal recorrente que perUser divide — somando-os Uma carga única de doze vezes.
· kamo-internal
Mostrar a taxa de inicialização antes de ser carregada, e onde um acordo o substitui
calcular o preço sempre devolveu uma configuraçãoFee e o assistente de ordem imprimiu em nenhum lugar, então uma carga que corre para quatro figuras em uma grande ordem atingiu o A primeira fatura do cliente sem nunca aparecer na citação que eles concordaram. Ele senta-se agora ao lado do subtotal, no degrau em que é escolhida a contagem do assento. O chip de pagamento leu "AUTO BILL ? Auto-Bill : Factura", que rotulou um comped conta e um fio limpo como uma conta pendente — os dois estados que mais precisam A dizer à parte de um. É uma tabela agora, e tanto WIRE e COMP são selecionáveis onde o método é alterado, o que não foi. Os editores do plano ganham o modelo ao lado do valor, porque $99 na configuraçãoFee é Ou $99 ou $2.475 em uma ordem de 25 lugares e o número sozinho não pode dizer qual. Termos negociados obter um painel na gaveta de org do console da plataforma, ao lado do números comerciais em vez de enterrados, uma vez que são a razão desses números discordar da lista de preços. Um acordo caducado permanece listado, mas diminuído: deve não lido como aquele em vigor, e também não deve desaparecer, porque é o que explica uma factura já apresentada.
· InitializerService
Semente a taxa de inicialização de $99 por assento em cada nível pago
O carregador passou nulo para setupFee em todos os quatro planos, então um recém-semeado ambiente a preços de onboarding em nada enquanto a produção cobra $99 por assento o tipo de lacuna que só é encontrada por alguém testando uma citação e acreditando nela. O modelo é passado em cada local de chamada ao lado da quantidade, porque a quantidade sozinho não diz o que significa, e a taxa é nomeada uma vez assim os três pagos As camadas não podem separar-se. Livre não tem nenhuma taxa.
· BillingService
Bill a taxa de inicialização em ambas as rotas para um plano, e registrar termos negociados
Duas rotas alcançam o mesmo plano e apenas uma delas iria cobrar o novo Taxa de arranque por assento. Uma encomenda escrita através do serviço de assinatura, que nunca toca em Stripe; auto-servir checkout vai direto para um Checkout Sessão e teria acusado ninguém que se inscreveu. O mesmo plano teria custado dinheiro diferente, dependendo de como foi comprado, então a taxa fica um preço Stripe não recorrente e um item de linha, ignorado quando um acordo renuncia e pulou em vez de fatal quando ainda não existe preço — um preço em falta deve não parar um cliente assinando, e uma taxa é recuperável em uma fatura de uma forma Um checkout abandonado não é. A API de acordos é apenas administração de plataforma e deliberadamente: um acordo move dinheiro em favor do cliente contra a lista publicada, que é Kamo decisão e não do proprietário do cliente. Nada exclui — retirar desactivações e mantém a linha, porque uma fatura levantada enquanto estava vivo tem que ficar Explicável. Duas maneiras de registrar um erro são recusadas: um FLAT acordo sem preços de montante a zero e lê-se como uma renúncia que ninguém concordou, e um acordo editado através da página errada da organização re-preços alguém que nunca fez parte da conversa. A DDL é à mão, como os scripts COMP ao lado foram: InicializerService não é implantado, o Hibernate não reedita um CHECK numa tabela existente, e setup fee model é deliberadamente nulável, então nenhum plano existente é cobrado é multiplicado por um padrão que ninguém escolheu.
· kamo-shared-library
Cobrar a taxa de inicialização, por assento, e deixar um cliente fora dele
A taxa de inicialização foi semi-construída desde que o modelo existiu. SubscriptionPlan carregava uma configuraçãoFee, SubscriptionOrder carregava uma configuraçãoFee, OrderLineType e InvoiceLineType ambos tinham SETUP FEE - e nada disso nunca Chegou a uma conta. calcularPreço adicionado o montante do plano ao total exatamente uma vez, e gerar as linhas de PLAN e ADDON emitidosFatura e parou, então uma taxa que foi citado, acordado e armazenado foi cobrado a ninguém. KamoCRM agora cobra $99 por assento. Um único valor não pode dizer que: $99 em setup fee é $99 ou $2.475 em uma ordem de 25 lugares, e nada na linha Distinguem-nos. Assim, o significado é indicado ao lado do montante, com FLAT como o padrão — cada plano que a plataforma hosts para um terceiro foi Uma taxa única e não deve ser multiplicada retroactivamente. Três pessoas precisam da resposta e uma delas é nova. Uma taxa por lugar segue o pessoa, assim crescendo a equipe deve-o novamente na diferença; sem que um Conta de 5 lugares poderia chegar a 500 tendo pago configuração em cinco. mudar os assentos agora levanta a sua própria factura de uma linha para as novas cabeças, em vez de enterrar uma uma vez, uma carga dentro de uma renovação que pode estar a onze meses de distância. Harmony Home Empréstimos concordaram um plano de 2.000 dólares cobrindo cada assento que eles vão adicionar, Antes de assinar. As vendas tiveram uma promoção (um código, aberto a quem o digitar, expresso em percentagem) e os totais de encomendas editadas à mão, e nenhum dos dois grupos considera que: uma promoção não tem escopo por conta, e uma ordem editada é um fato sobre uma ordem, esquecido da próxima vez que a conta é preço. OrgBilling Acordo registra-o contra a organização em vez, onde preços lê-lo todas as vezes. "$2.000 em vez de $99 um assento" e "$2.000 e nunca mais" são diferentes promessas, assim cobreFutureSeats é solicitado por si só em vez de inferido — a cliente que comprou o primeiro seria cobrado corretamente hoje e erroneamente o Mês em que contratam. WIRE junta-se aos métodos de pagamento porque o dinheiro da Harmony já tinha Não havia onde dizer. Não é INVOCAÇÃO (nada é notável, dunning Não se deve persegui-lo) e, enfaticamente, não COMP, que é o que eles foram levados como durante quinze dias, enquanto o fio se sentava no banco — uma conta como Complementar em cada relatório que conta com receitas.

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