@fayz-ai/plugin-fiscal-br 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/inbox.d.ts +88 -0
- package/dist/data/inbox.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2218 -353
- package/dist/index.js.map +1 -1
- package/dist/lib/nfe-xml.d.ts +2 -0
- package/dist/lib/nfe-xml.d.ts.map +1 -1
- package/dist/lib/note-suggestion.d.ts +196 -0
- package/dist/lib/note-suggestion.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +7 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +42 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/InboundNotesPage.d.ts.map +1 -1
- package/dist/views/InboundNotesView.d.ts.map +1 -1
- package/dist/views/ManifestView.d.ts +3 -1
- package/dist/views/ManifestView.d.ts.map +1 -1
- package/dist/views/NoteReconcileView.d.ts +7 -1
- package/dist/views/NoteReconcileView.d.ts.map +1 -1
- package/dist/views/UnitFiscalWidgets.d.ts +21 -0
- package/dist/views/UnitFiscalWidgets.d.ts.map +1 -0
- package/dist/views/inventoryView.d.ts +31 -0
- package/dist/views/inventoryView.d.ts.map +1 -1
- package/functions/fiscalbr-suggest-note-items/index.ts +425 -0
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { recordRailFields, RECORD_GROUP, activeFacet, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus,
|
|
2
|
-
import { useState,
|
|
3
|
-
import {
|
|
4
|
-
import { PageHeaderActions, Button, Card, ListFilterBar, Skeleton, SubpageHeader, EmBreve, SearchSelect, Input, DatePicker, Hint, ComingSoon } from '@fayz-ai/ui';
|
|
1
|
+
import { recordRailFields, RECORD_GROUP, activeFacet, getSupabaseClientOptional, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus, maskCpfCnpj } from '@fayz-ai/core';
|
|
2
|
+
import React5, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { ModalHeader, ModalTitle, ModalBody, Skeleton, AiActionButton, Card, Button, EmBreve, SearchSelect, Input, DatePicker, Hint, ModalFooter, SubpageHeader, useFilterDraft, PageHeaderActions, FilterPanel, FilterPanelField, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SearchField, DataTable, ListFilterBar, Modal, ModalContent, ComingSoon, CardHeader, CardTitle, CardContent, toast, Badge, CardDescription, Switch } from '@fayz-ai/ui';
|
|
5
4
|
export { ComingSoon, EmBreve } from '@fayz-ai/ui';
|
|
6
|
-
import {
|
|
5
|
+
import { registerDocumentOpener, getActiveTenantId, useUnitFilter, UNIT_DETAIL_ZONES, adminNavigateTo } from '@fayz-ai/admin';
|
|
6
|
+
import { Gift, RefreshCw, Loader2, FileDown, AlertTriangle, ChevronRight, Check, Trash2, Plus, Inbox, UploadCloud, ShieldCheck, FileText, ExternalLink, X, Info } from 'lucide-react';
|
|
7
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
8
|
|
|
8
9
|
// src/index.ts
|
|
9
10
|
var PLUGNOTAS_CONNECTOR_ID = "plugnotas";
|
|
@@ -2831,6 +2832,821 @@ END $function$;
|
|
|
2831
2832
|
REVOKE ALL ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) FROM PUBLIC;
|
|
2832
2833
|
GRANT EXECUTE ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) TO authenticated;
|
|
2833
2834
|
`;
|
|
2835
|
+
var MIGRATION_011_O_IMPOSTO_DO_PRODUTO_E_DO_FISCAL = `-- O imposto do PRODUTO mora no fiscal, n\xE3o no estoque.
|
|
2836
|
+
--
|
|
2837
|
+
-- No V1 as dezessete colunas de tributo (NCM, CFOP, CST, PIS, COFINS, CEST, FCP
|
|
2838
|
+
-- e os dois campos novos da reforma) ficam numa tabela do m\xF3dulo de estoque,
|
|
2839
|
+
-- ao lado de quantidade m\xEDnima e temperatura de armazenamento. O sal\xE3o que
|
|
2840
|
+
-- nunca emitiu uma nota carrega as dezessete do mesmo jeito.
|
|
2841
|
+
--
|
|
2842
|
+
-- Aqui elas ficam onde o assunto \xE9: \`plg_fiscalbr_tax_groups\` j\xE1 guarda a
|
|
2843
|
+
-- mesma lista para o GRUPO fiscal, e esta tabela \xE9 o que um produto tem de
|
|
2844
|
+
-- diferente do grupo dele. Produto sem nada de fiscal n\xE3o tem linha nenhuma, e
|
|
2845
|
+
-- o estoque de quem n\xE3o emite nota n\xE3o muda de forma.
|
|
2846
|
+
--
|
|
2847
|
+
-- \`tax_group_id\` \xE9 a heran\xE7a e as outras colunas s\xE3o a exce\xE7\xE3o: NULL aqui quer
|
|
2848
|
+
-- dizer \xABvale o que o grupo diz\xBB, n\xE3o \xABzero\xBB. Por isso nenhuma tem DEFAULT 0 \u2014
|
|
2849
|
+
-- um zero gravado afirmaria al\xEDquota zero e sobrescreveria o grupo em sil\xEAncio.
|
|
2850
|
+
|
|
2851
|
+
CREATE TABLE IF NOT EXISTS public.plg_fiscalbr_product_tax_info (
|
|
2852
|
+
product_id uuid PRIMARY KEY REFERENCES public.products(id) ON DELETE CASCADE,
|
|
2853
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
2854
|
+
tax_group_id uuid REFERENCES public.plg_fiscalbr_tax_groups(id) ON DELETE SET NULL,
|
|
2855
|
+
|
|
2856
|
+
-- Nomenclatura Comum do Mercosul: \xE9 do PRODUTO, nunca do grupo. \xC9 o campo que
|
|
2857
|
+
-- o V1 preenche mesmo quando nenhum outro est\xE1 preenchido.
|
|
2858
|
+
ncm text,
|
|
2859
|
+
cest text,
|
|
2860
|
+
icms_origin text,
|
|
2861
|
+
icms_rate numeric(9,4),
|
|
2862
|
+
cfop text,
|
|
2863
|
+
cst text,
|
|
2864
|
+
pis_situation text,
|
|
2865
|
+
pis_rate numeric(9,4),
|
|
2866
|
+
cofins_situation text,
|
|
2867
|
+
cofins_rate numeric(9,4),
|
|
2868
|
+
base_calculation_mode text,
|
|
2869
|
+
benefit_code text,
|
|
2870
|
+
poverty_fund_rate numeric(9,4),
|
|
2871
|
+
|
|
2872
|
+
-- Reforma tribut\xE1ria. Entram agora pelo mesmo motivo que entraram no grupo:
|
|
2873
|
+
-- deixar de fora obrigaria a recadastrar o cat\xE1logo inteiro no dia em que
|
|
2874
|
+
-- IBS e CBS valerem.
|
|
2875
|
+
ibs_cbs_situation text,
|
|
2876
|
+
ibs_cbs_classification text,
|
|
2877
|
+
|
|
2878
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
2879
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
2880
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
2881
|
+
);
|
|
2882
|
+
|
|
2883
|
+
CREATE INDEX IF NOT EXISTS plg_fiscalbr_product_tax_info_tenant_idx
|
|
2884
|
+
ON public.plg_fiscalbr_product_tax_info (tenant_id);
|
|
2885
|
+
CREATE INDEX IF NOT EXISTS plg_fiscalbr_product_tax_info_group_idx
|
|
2886
|
+
ON public.plg_fiscalbr_product_tax_info (tax_group_id);
|
|
2887
|
+
|
|
2888
|
+
ALTER TABLE public.plg_fiscalbr_product_tax_info ENABLE ROW LEVEL SECURITY;
|
|
2889
|
+
|
|
2890
|
+
-- Quem l\xEA e escreve \xE9 quem cuida do CADASTRO do produto, n\xE3o quem emite a
|
|
2891
|
+
-- nota: a aba Fiscal do V1 vive dentro do formul\xE1rio do produto, e exigir
|
|
2892
|
+
-- permiss\xE3o de financeiro ali esconderia a se\xE7\xE3o do estoquista que digita o
|
|
2893
|
+
-- NCM da mercadoria que acabou de chegar.
|
|
2894
|
+
DROP POLICY IF EXISTS plg_fiscalbr_product_tax_info_authz_select ON public.plg_fiscalbr_product_tax_info;
|
|
2895
|
+
CREATE POLICY plg_fiscalbr_product_tax_info_authz_select
|
|
2896
|
+
ON public.plg_fiscalbr_product_tax_info FOR SELECT TO authenticated
|
|
2897
|
+
USING (
|
|
2898
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
2899
|
+
AND (SELECT app.has_permission('inventory.read', NULL::uuid)));
|
|
2900
|
+
|
|
2901
|
+
DROP POLICY IF EXISTS plg_fiscalbr_product_tax_info_authz_write ON public.plg_fiscalbr_product_tax_info;
|
|
2902
|
+
CREATE POLICY plg_fiscalbr_product_tax_info_authz_write
|
|
2903
|
+
ON public.plg_fiscalbr_product_tax_info FOR ALL TO authenticated
|
|
2904
|
+
USING (
|
|
2905
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
2906
|
+
AND (SELECT app.has_permission('inventory.edit', NULL::uuid)))
|
|
2907
|
+
WITH CHECK (
|
|
2908
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
2909
|
+
AND (SELECT app.has_permission('inventory.edit', NULL::uuid)));
|
|
2910
|
+
|
|
2911
|
+
GRANT ALL ON TABLE public.plg_fiscalbr_product_tax_info TO authenticated;
|
|
2912
|
+
GRANT ALL ON TABLE public.plg_fiscalbr_product_tax_info TO service_role;
|
|
2913
|
+
|
|
2914
|
+
COMMENT ON TABLE public.plg_fiscalbr_product_tax_info IS
|
|
2915
|
+
'Tributos do produto \u2014 a exce\xE7\xE3o ao grupo fiscal. Espelha a aba Fiscal do cadastro de produto do V1 (product_fiscal_info). NULL herda do grupo.';
|
|
2916
|
+
`;
|
|
2917
|
+
var MIGRATION_012_QUEM_E_O_PRODUTO_SE_PERGUNTA_AO_INDICE = `-- ---------------------------------------------------------------------------
|
|
2918
|
+
-- Quem \xE9 o produto desta linha da nota: pergunta-se ao \xEDndice, n\xE3o ao modelo.
|
|
2919
|
+
-- ---------------------------------------------------------------------------
|
|
2920
|
+
-- A sugest\xE3o de IA da entrada de notas mandava o CAT\xC1LOGO para o modelo. O V1
|
|
2921
|
+
-- mandava 400 produtos em todo turno (\`.slice(0, 400)\` sobre um \`.limit(800)\`
|
|
2922
|
+
-- sem \`order by\` \u2014 o corte era arbitr\xE1rio, e acima de 400 produtos a function
|
|
2923
|
+
-- respondia \xABcrie este produto\xBB para produto cadastrado, sem aviso).
|
|
2924
|
+
--
|
|
2925
|
+
-- O custo disso \xE9 \`cat\xE1logo \xD7 notas\`. Num tenant de 171 produtos s\xE3o ~3,5k
|
|
2926
|
+
-- tokens por nota; num distribuidor de 8.000 s\xE3o ~160k e n\xE3o cabe no turno. E a
|
|
2927
|
+
-- varredura de cat\xE1logo n\xE3o \xE9 trabalho de modelo: \xE9 trabalho de \xEDndice.
|
|
2928
|
+
--
|
|
2929
|
+
-- Esta fun\xE7\xE3o \xE9 a pr\xE9-sele\xE7\xE3o, em TR\xCAS fases, e a primeira que acerta encerra a
|
|
2930
|
+
-- linha:
|
|
2931
|
+
--
|
|
2932
|
+
-- supplier_map o de-para do fornecedor. O que aquele CNPJ j\xE1 ensinou n\xE3o se
|
|
2933
|
+
-- pergunta de novo \u2014 e numa nota repetida isso resolve a nota
|
|
2934
|
+
-- inteira, ent\xE3o a IA nem roda.
|
|
2935
|
+
-- ean c\xF3digo de barras contra o cadastro. Igualdade, n\xE3o palpite.
|
|
2936
|
+
-- text semelhan\xE7a de texto, com os candidatos que sobraram.
|
|
2937
|
+
--
|
|
2938
|
+
-- \`reason\` \xE9 o que a tela usa para decidir: \`supplier_map\` e \`ean\` s\xE3o V\xCDNCULO
|
|
2939
|
+
-- (liga sozinho), \`text\` \xE9 CANDIDATO (vai para o modelo desambiguar). Sem essa
|
|
2940
|
+
-- coluna a tela trataria um EAN exato como um chute de trigrama.
|
|
2941
|
+
--
|
|
2942
|
+
-- A fase de texto N\xC3O reimplementa busca: ela chama \`public.global_search\`, que
|
|
2943
|
+
-- j\xE1 tem \xEDndice GIN de trigrama, \`fayz_norm()\` (tira acento, min\xFAscula, colapsa
|
|
2944
|
+
-- pontua\xE7\xE3o) e a escada de pontua\xE7\xE3o da casa. Reescrever isso pior era o erro
|
|
2945
|
+
-- que o \`ILIKE\` da descri\xE7\xE3o fatiada em 24 caracteres j\xE1 estava cometendo do
|
|
2946
|
+
-- lado do cliente \u2014 \`name.ilike.%SACO PLASTICO 10X15 TRAN%\` n\xE3o casa acento,
|
|
2947
|
+
-- ordem de palavra nem abrevia\xE7\xE3o, e o resultado mais comum era zero candidatos.
|
|
2948
|
+
--
|
|
2949
|
+
-- O RPC foi renomeado na 137 (\`fayz_global_search\` \u2192 \`global_search\`) e h\xE1 pool
|
|
2950
|
+
-- em cada lado. Aqui se descobre o nome em tempo de execu\xE7\xE3o, como o cliente j\xE1
|
|
2951
|
+
-- faz em \`@fayz-ai/core/search\`.
|
|
2952
|
+
--
|
|
2953
|
+
-- N\xC3O \xE9 SECURITY DEFINER. Roda como quem chamou, a RLS de \`products\` vale, e o
|
|
2954
|
+
-- tenant vem de \`app.current_tenant_id()\` \u2014 o cliente n\xE3o passa tenant e por
|
|
2955
|
+
-- isso n\xE3o pode passar o de outra conta.
|
|
2956
|
+
--
|
|
2957
|
+
-- \`SET search_path\` \xE9 expl\xEDcito porque \`similarity\`/\`word_similarity\` moram em
|
|
2958
|
+
-- \`extensions\`: corpo com search_path vazio passa no DDL e estoura na primeira
|
|
2959
|
+
-- chamada.
|
|
2960
|
+
-- ---------------------------------------------------------------------------
|
|
2961
|
+
|
|
2962
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_match_product_candidates(
|
|
2963
|
+
-- [{ "ref": "<id do item>", "text": "SACO PLASTICO 10X15", "ean": "789\u2026", "code": "ABC1" }]
|
|
2964
|
+
p_queries jsonb,
|
|
2965
|
+
p_supplier_cnpj text DEFAULT NULL,
|
|
2966
|
+
p_per_query integer DEFAULT 8
|
|
2967
|
+
) RETURNS TABLE (
|
|
2968
|
+
ref text,
|
|
2969
|
+
product_id uuid,
|
|
2970
|
+
name text,
|
|
2971
|
+
sku text,
|
|
2972
|
+
unit text,
|
|
2973
|
+
cost numeric,
|
|
2974
|
+
score real,
|
|
2975
|
+
reason text
|
|
2976
|
+
)
|
|
2977
|
+
LANGUAGE plpgsql
|
|
2978
|
+
STABLE
|
|
2979
|
+
SET search_path TO 'public', 'extensions', 'pg_temp'
|
|
2980
|
+
AS $fn$
|
|
2981
|
+
DECLARE
|
|
2982
|
+
v_tenant uuid := (SELECT app.current_tenant_id());
|
|
2983
|
+
v_cnpj text := public.fayz_digits(coalesce(p_supplier_cnpj, ''));
|
|
2984
|
+
v_barcodes boolean := to_regclass('public.plg_inventory_product_barcodes') IS NOT NULL;
|
|
2985
|
+
v_map boolean := to_regclass('public.plg_fiscalbr_supplier_product_map') IS NOT NULL;
|
|
2986
|
+
v_search text;
|
|
2987
|
+
v_q record;
|
|
2988
|
+
v_found uuid;
|
|
2989
|
+
BEGIN
|
|
2990
|
+
IF v_tenant IS NULL THEN RETURN; END IF;
|
|
2991
|
+
IF p_queries IS NULL OR jsonb_typeof(p_queries) <> 'array' THEN RETURN; END IF;
|
|
2992
|
+
p_per_query := least(greatest(coalesce(p_per_query, 8), 1), 50);
|
|
2993
|
+
|
|
2994
|
+
v_search := CASE
|
|
2995
|
+
WHEN to_regprocedure('public.global_search(text,uuid,text[],integer,integer)') IS NOT NULL
|
|
2996
|
+
THEN 'public.global_search'
|
|
2997
|
+
WHEN to_regprocedure('public.fayz_global_search(text,uuid,text[],integer,integer)') IS NOT NULL
|
|
2998
|
+
THEN 'public.fayz_global_search'
|
|
2999
|
+
ELSE NULL
|
|
3000
|
+
END;
|
|
3001
|
+
|
|
3002
|
+
FOR v_q IN
|
|
3003
|
+
SELECT e->>'ref' AS ref,
|
|
3004
|
+
coalesce(e->>'text', '') AS text,
|
|
3005
|
+
public.fayz_digits(coalesce(e->>'ean', '')) AS ean,
|
|
3006
|
+
nullif(btrim(coalesce(e->>'code', '')), '') AS code
|
|
3007
|
+
FROM jsonb_array_elements(p_queries) AS e
|
|
3008
|
+
WHERE e->>'ref' IS NOT NULL
|
|
3009
|
+
LOOP
|
|
3010
|
+
v_found := NULL;
|
|
3011
|
+
|
|
3012
|
+
-- \u2460 O de-para do fornecedor. Aprendizado da casa, e ele ganha de tudo.
|
|
3013
|
+
IF v_map AND v_cnpj <> '' AND v_q.code IS NOT NULL THEN
|
|
3014
|
+
SELECT p.id INTO v_found
|
|
3015
|
+
FROM public.plg_fiscalbr_supplier_product_map m
|
|
3016
|
+
JOIN public.products p ON p.id = m.product_id
|
|
3017
|
+
WHERE m.supplier_cnpj = v_cnpj
|
|
3018
|
+
AND m.supplier_product_code = v_q.code
|
|
3019
|
+
AND p.is_active
|
|
3020
|
+
ORDER BY m.hit_count DESC NULLS LAST, m.last_used_at DESC NULLS LAST
|
|
3021
|
+
LIMIT 1;
|
|
3022
|
+
|
|
3023
|
+
IF v_found IS NOT NULL THEN
|
|
3024
|
+
RETURN QUERY
|
|
3025
|
+
SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'supplier_map'::text
|
|
3026
|
+
FROM public.products p WHERE p.id = v_found;
|
|
3027
|
+
CONTINUE;
|
|
3028
|
+
END IF;
|
|
3029
|
+
END IF;
|
|
3030
|
+
|
|
3031
|
+
-- \u2461 O c\xF3digo de barras. O produto guarda o principal em \`metadata->>barcode\`
|
|
3032
|
+
-- (no V1 era coluna) e os demais na tabela de c\xF3digos do estoque.
|
|
3033
|
+
IF length(v_q.ean) >= 8 THEN
|
|
3034
|
+
SELECT p.id INTO v_found
|
|
3035
|
+
FROM public.products p
|
|
3036
|
+
WHERE p.is_active
|
|
3037
|
+
AND (
|
|
3038
|
+
public.fayz_digits(coalesce(p.metadata->>'barcode', '')) = v_q.ean
|
|
3039
|
+
OR (v_barcodes AND EXISTS (
|
|
3040
|
+
SELECT 1 FROM public.plg_inventory_product_barcodes b
|
|
3041
|
+
WHERE b.product_id = p.id
|
|
3042
|
+
AND public.fayz_digits(coalesce(b.barcode, '')) = v_q.ean))
|
|
3043
|
+
)
|
|
3044
|
+
LIMIT 1;
|
|
3045
|
+
|
|
3046
|
+
IF v_found IS NOT NULL THEN
|
|
3047
|
+
RETURN QUERY
|
|
3048
|
+
SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'ean'::text
|
|
3049
|
+
FROM public.products p WHERE p.id = v_found;
|
|
3050
|
+
CONTINUE;
|
|
3051
|
+
END IF;
|
|
3052
|
+
END IF;
|
|
3053
|
+
|
|
3054
|
+
-- \u2462 Semelhan\xE7a de texto, pela busca indexada que j\xE1 existe.
|
|
3055
|
+
CONTINUE WHEN v_search IS NULL OR length(public.fayz_norm(v_q.text)) < 3;
|
|
3056
|
+
|
|
3057
|
+
RETURN QUERY EXECUTE format($q$
|
|
3058
|
+
SELECT $4::text, p.id, p.name, p.sku, p.unit, p.cost, g.score, 'text'::text
|
|
3059
|
+
FROM %s($1, $2, ARRAY['product']::text[], $3, $3) g
|
|
3060
|
+
JOIN public.products p ON p.id = g.record_id::uuid
|
|
3061
|
+
WHERE p.is_active
|
|
3062
|
+
ORDER BY g.score DESC
|
|
3063
|
+
$q$, v_search)
|
|
3064
|
+
USING v_q.text, v_tenant, p_per_query, v_q.ref;
|
|
3065
|
+
END LOOP;
|
|
3066
|
+
END
|
|
3067
|
+
$fn$;
|
|
3068
|
+
|
|
3069
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) TO authenticated;
|
|
3070
|
+
|
|
3071
|
+
COMMENT ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) IS
|
|
3072
|
+
'Pr\xE9-sele\xE7\xE3o de produtos para a sugest\xE3o de IA da entrada de notas: de-para do fornecedor e EAN resolvem sozinhos (reason supplier_map/ean, v\xEDnculo direto); o resto volta como candidatos de texto (reason text) para o modelo desambiguar. Uma viagem para a nota inteira \u2014 substitui o despejo de 400 produtos do V1 e o ILIKE da descri\xE7\xE3o fatiada.';
|
|
3073
|
+
`;
|
|
3074
|
+
var MIGRATION_013_A_SUGESTAO_SOBREVIVE_A_FECHAR_A_JANELA = `-- ---------------------------------------------------------------------------
|
|
3075
|
+
-- A sugest\xE3o sobrevive a fechar a janela \u2014 e diz QUEM escreveu no campo.
|
|
3076
|
+
-- ---------------------------------------------------------------------------
|
|
3077
|
+
-- A concilia\xE7\xE3o virou modal para a lista n\xE3o ser desmontada. O efeito colateral
|
|
3078
|
+
-- \xE9 que o modal fecha com clique fora e com Esc, e a sugest\xE3o s\xF3 existia em
|
|
3079
|
+
-- mem\xF3ria at\xE9 algu\xE9m clicar em \xABSalvar concilia\xE7\xE3o\xBB:
|
|
3080
|
+
--
|
|
3081
|
+
-- sugerir (30s de IA) \u2192 revisar 7 de 11 itens \u2192 clique fora \u2192 perdeu os 11
|
|
3082
|
+
--
|
|
3083
|
+
-- No V1 fechar n\xE3o custava nada, porque a edge function j\xE1 tinha gravado. Ent\xE3o
|
|
3084
|
+
-- a V2 grava tamb\xE9m, logo depois da sugest\xE3o, pelo caminho que j\xE1 existia
|
|
3085
|
+
-- (\`fiscalbr_save_inbox_decision\`, que n\xE3o marca \`reconciled\`) \u2014 nenhum segundo
|
|
3086
|
+
-- caminho de escrita, nenhuma coluna \`ai_*\` paralela aos campos de verdade.
|
|
3087
|
+
--
|
|
3088
|
+
-- O que faltava era a MEM\xD3RIA DE AUTORIA. Depois de recarregar, a tela via um
|
|
3089
|
+
-- fator 22,056 e n\xE3o sabia se foi a Laissa que digitou ou a m\xE1quina que chutou \u2014
|
|
3090
|
+
-- e as duas coisas se conferem de maneiras diferentes.
|
|
3091
|
+
--
|
|
3092
|
+
-- O V1 guardava um booleano, \`ai_suggested\`. Aqui \xE9 texto, porque a V2 tem TR\xCAS
|
|
3093
|
+
-- autores de m\xE1quina e eles n\xE3o merecem o mesmo selo:
|
|
3094
|
+
--
|
|
3095
|
+
-- supplier_map o de-para deste fornecedor. Igualdade, e aprendizado da casa.
|
|
3096
|
+
-- ean c\xF3digo de barras. Igualdade.
|
|
3097
|
+
-- ai semelhan\xE7a julgada por modelo. \xC9 o \xFAnico que se confere item
|
|
3098
|
+
-- a item, e \`suggestion_confidence\` diz o quanto.
|
|
3099
|
+
--
|
|
3100
|
+
-- NULL \xE9 a resposta mais importante: significa \xABuma pessoa escolheu isto\xBB. O
|
|
3101
|
+
-- cliente limpa a coluna em qualquer edi\xE7\xE3o do campo, ent\xE3o o selo nunca mente
|
|
3102
|
+
-- sobre um valor que algu\xE9m corrigiu \xE0 m\xE3o.
|
|
3103
|
+
-- ---------------------------------------------------------------------------
|
|
3104
|
+
|
|
3105
|
+
ALTER TABLE public.plg_fiscalbr_inbox_items
|
|
3106
|
+
ADD COLUMN IF NOT EXISTS suggested_by text,
|
|
3107
|
+
ADD COLUMN IF NOT EXISTS suggestion_confidence numeric;
|
|
3108
|
+
|
|
3109
|
+
DO $$
|
|
3110
|
+
BEGIN
|
|
3111
|
+
IF NOT EXISTS (
|
|
3112
|
+
SELECT 1 FROM pg_constraint
|
|
3113
|
+
WHERE conname = 'plg_fiscalbr_inbox_items_suggested_by_check'
|
|
3114
|
+
AND conrelid = 'public.plg_fiscalbr_inbox_items'::regclass
|
|
3115
|
+
) THEN
|
|
3116
|
+
ALTER TABLE public.plg_fiscalbr_inbox_items
|
|
3117
|
+
ADD CONSTRAINT plg_fiscalbr_inbox_items_suggested_by_check
|
|
3118
|
+
CHECK (suggested_by IS NULL OR suggested_by IN ('ai', 'ean', 'supplier_map'));
|
|
3119
|
+
END IF;
|
|
3120
|
+
END $$;
|
|
3121
|
+
|
|
3122
|
+
COMMENT ON COLUMN public.plg_fiscalbr_inbox_items.suggested_by IS
|
|
3123
|
+
'Quem escreveu a decis\xE3o desta linha: ai (modelo, confira), ean / supplier_map (igualdade), NULL (uma pessoa). O cliente limpa em qualquer edi\xE7\xE3o manual.';
|
|
3124
|
+
COMMENT ON COLUMN public.plg_fiscalbr_inbox_items.suggestion_confidence IS
|
|
3125
|
+
'0..1, s\xF3 para suggested_by = ai. Abaixo de 0,6 a tela marca a linha como \xABconfira\xBB.';
|
|
3126
|
+
|
|
3127
|
+
-- \u2500\u2500 A grava\xE7\xE3o da decis\xE3o passa a carregar a autoria \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3128
|
+
-- Mesma assinatura, ent\xE3o \xE9 \`CREATE OR REPLACE\` e nenhum chamador muda. O corpo
|
|
3129
|
+
-- \xE9 o da 009 com duas linhas a mais: o resto fica palavra por palavra, porque
|
|
3130
|
+
-- esta fun\xE7\xE3o \xE9 o \xFAnico caminho de escrita da decis\xE3o e reescrev\xEA-la de mem\xF3ria
|
|
3131
|
+
-- \xE9 como se perde uma regra (a de \`reconciled\`, por exemplo).
|
|
3132
|
+
|
|
3133
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_save_inbox_decision(
|
|
3134
|
+
p_document_id uuid,
|
|
3135
|
+
p_items jsonb,
|
|
3136
|
+
p_default_chart_account_id uuid DEFAULT NULL,
|
|
3137
|
+
p_default_location_id uuid DEFAULT NULL,
|
|
3138
|
+
p_duplicates jsonb DEFAULT NULL)
|
|
3139
|
+
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
|
|
3140
|
+
DECLARE
|
|
3141
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
3142
|
+
v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
|
|
3143
|
+
v_row jsonb;
|
|
3144
|
+
v_done integer := 0;
|
|
3145
|
+
v_total integer;
|
|
3146
|
+
BEGIN
|
|
3147
|
+
SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
|
|
3148
|
+
WHERE tenant_id = v_tenant AND id = p_document_id;
|
|
3149
|
+
IF NOT FOUND THEN
|
|
3150
|
+
RAISE EXCEPTION 'fiscalbr: nota % n\xE3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';
|
|
3151
|
+
END IF;
|
|
3152
|
+
PERFORM public.plg_financial_require_permission('financial.edit', v_doc.unit_id);
|
|
3153
|
+
|
|
3154
|
+
IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN
|
|
3155
|
+
RAISE EXCEPTION 'fiscalbr: nota % j\xE1 foi aceita \u2014 a decis\xE3o n\xE3o muda depois do estoque', p_document_id
|
|
3156
|
+
USING ERRCODE = '23514';
|
|
3157
|
+
END IF;
|
|
3158
|
+
|
|
3159
|
+
UPDATE public.plg_fiscalbr_inbox_documents SET
|
|
3160
|
+
default_chart_account_id = coalesce(p_default_chart_account_id, default_chart_account_id),
|
|
3161
|
+
default_location_id = coalesce(p_default_location_id, default_location_id),
|
|
3162
|
+
reviewed_at = now(), updated_at = now()
|
|
3163
|
+
WHERE id = v_doc.id;
|
|
3164
|
+
|
|
3165
|
+
FOR v_row IN SELECT * FROM jsonb_array_elements(coalesce(p_items, '[]'::jsonb)) LOOP
|
|
3166
|
+
UPDATE public.plg_fiscalbr_inbox_items i SET
|
|
3167
|
+
product_id = nullif(v_row->>'product_id', '')::uuid,
|
|
3168
|
+
entry_type = nullif(v_row->>'entry_type', ''),
|
|
3169
|
+
chart_account_id = nullif(v_row->>'chart_account_id', '')::uuid,
|
|
3170
|
+
stock_unit_id = nullif(v_row->>'stock_unit_id', '')::uuid,
|
|
3171
|
+
conversion_factor = coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1),
|
|
3172
|
+
cost_mode = coalesce(nullif(v_row->>'cost_mode', ''), 'novo'),
|
|
3173
|
+
shortage_quantity = nullif(v_row->>'shortage_quantity', '')::numeric,
|
|
3174
|
+
shortage_reason = nullif(v_row->>'shortage_reason', ''),
|
|
3175
|
+
shortage_note = nullif(v_row->>'shortage_note', ''),
|
|
3176
|
+
-- Quem escreveu. Ausente na chave \xE9 NULL, que \xE9 \xABuma pessoa escolheu\xBB \u2014 e \xE9
|
|
3177
|
+
-- o que acontece em todo salvamento comum, sem a IA ter passado por aqui.
|
|
3178
|
+
suggested_by = nullif(v_row->>'suggested_by', ''),
|
|
3179
|
+
suggestion_confidence = nullif(v_row->>'suggestion_confidence', '')::numeric,
|
|
3180
|
+
-- Derivadas, gravadas: \xE9 o que o operador viu.
|
|
3181
|
+
stock_quantity = i.quantity_commercial * coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1)
|
|
3182
|
+
- coalesce(nullif(v_row->>'shortage_quantity', '')::numeric, 0),
|
|
3183
|
+
unit_cost_new = CASE WHEN coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1) > 0
|
|
3184
|
+
THEN i.unit_price_commercial / coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1) END,
|
|
3185
|
+
-- Uma linha est\xE1 conciliada quando sabe o que \xE9. Despesa e servi\xE7o n\xE3o
|
|
3186
|
+
-- pedem produto: elas nunca v\xE3o virar estoque.
|
|
3187
|
+
reconciled = CASE
|
|
3188
|
+
WHEN nullif(v_row->>'entry_type', '') IN ('despesa','servico') THEN true
|
|
3189
|
+
ELSE nullif(v_row->>'product_id', '') IS NOT NULL
|
|
3190
|
+
END,
|
|
3191
|
+
updated_at = now()
|
|
3192
|
+
WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id
|
|
3193
|
+
AND i.id = (v_row->>'id')::uuid;
|
|
3194
|
+
v_done := v_done + 1;
|
|
3195
|
+
END LOOP;
|
|
3196
|
+
|
|
3197
|
+
IF p_duplicates IS NOT NULL THEN
|
|
3198
|
+
DELETE FROM public.plg_fiscalbr_inbox_duplicates
|
|
3199
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
3200
|
+
INSERT INTO public.plg_fiscalbr_inbox_duplicates (
|
|
3201
|
+
tenant_id, inbox_document_id, duplicate_index, number, due_date, value,
|
|
3202
|
+
payment_type, barcode, pix_key, payment_method_id)
|
|
3203
|
+
SELECT v_tenant, v_doc.id, coalesce((d->>'index')::int, ord), d->>'number',
|
|
3204
|
+
nullif(d->>'due_date', '')::date, (d->>'value')::numeric,
|
|
3205
|
+
nullif(d->>'payment_type', ''), nullif(d->>'barcode', ''),
|
|
3206
|
+
nullif(d->>'pix_key', ''), nullif(d->>'payment_method_id', '')::uuid
|
|
3207
|
+
FROM jsonb_array_elements(p_duplicates) WITH ORDINALITY AS t(d, ord);
|
|
3208
|
+
END IF;
|
|
3209
|
+
|
|
3210
|
+
SELECT count(*) INTO v_total FROM public.plg_fiscalbr_inbox_items
|
|
3211
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
3212
|
+
|
|
3213
|
+
UPDATE public.plg_fiscalbr_inbox_documents SET status = CASE
|
|
3214
|
+
WHEN (SELECT count(*) FROM public.plg_fiscalbr_inbox_items
|
|
3215
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id AND reconciled) = v_total
|
|
3216
|
+
THEN 'accepted' ELSE 'partial' END
|
|
3217
|
+
WHERE id = v_doc.id;
|
|
3218
|
+
|
|
3219
|
+
RETURN jsonb_build_object('document_id', v_doc.id, 'saved', v_done, 'items', v_total,
|
|
3220
|
+
'reconciled', (SELECT count(*) FROM public.plg_fiscalbr_inbox_items
|
|
3221
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id AND reconciled));
|
|
3222
|
+
END $function$;
|
|
3223
|
+
|
|
3224
|
+
REVOKE ALL ON FUNCTION public.fiscalbr_save_inbox_decision(uuid, jsonb, uuid, uuid, jsonb) FROM PUBLIC;
|
|
3225
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_save_inbox_decision(uuid, jsonb, uuid, uuid, jsonb) TO authenticated;
|
|
3226
|
+
`;
|
|
3227
|
+
var MIGRATION_014_A_DESCRICAO_DA_NOTA_TEM_LIXO_NO_MEIO = `-- ---------------------------------------------------------------------------
|
|
3228
|
+
-- A descri\xE7\xE3o da nota tem lixo no meio, e um token de lixo matava a busca.
|
|
3229
|
+
-- ---------------------------------------------------------------------------
|
|
3230
|
+
-- Caso real, primeira nota de verdade: o item \xAB20X35 AR VACUO\xBB voltou SEM
|
|
3231
|
+
-- candidato, e o produto \`EMBALAGEM VACUO 20X35\` est\xE1 cadastrado.
|
|
3232
|
+
--
|
|
3233
|
+
-- \`public.global_search\` exige que TODOS os tokens apare\xE7am no haystack \u2014 \xE9 a
|
|
3234
|
+
-- regra certa para gente digitando (\xABjo\xE3o silva\xBB n\xE3o deve trazer todos os jo\xE3os)
|
|
3235
|
+
-- e \xE9 a regra errada para descri\xE7\xE3o de NF-e. Os tokens de \`20X35 AR VACUO\` s\xE3o
|
|
3236
|
+
-- \`20x35\`, \`ar\`, \`vacuo\`; o produto \xE9 \`embalagem vacuo 20x35\`. Dois casam. \`ar\`
|
|
3237
|
+
-- n\xE3o aparece em lugar nenhum, e o AND derruba a linha inteira.
|
|
3238
|
+
--
|
|
3239
|
+
-- A segunda fase (\`word_similarity\`) tamb\xE9m n\xE3o salva: ela compara a frase
|
|
3240
|
+
-- inteira contra o haystack, e com um ter\xE7o da frase sendo ru\xEDdo a semelhan\xE7a
|
|
3241
|
+
-- fica abaixo do limiar.
|
|
3242
|
+
--
|
|
3243
|
+
-- Descri\xE7\xE3o de nota fiscal \xE9 assim por natureza: abreviada, em ordem pr\xF3pria, com
|
|
3244
|
+
-- sigla de unidade, micragem e palavra solta no meio. Ent\xE3o o que entra na busca
|
|
3245
|
+
-- deixa de ser a frase e passa a ser O QUE NELA DISTINGUE:
|
|
3246
|
+
--
|
|
3247
|
+
-- \u2460 tokens de 3 letras ou mais. \xABar\xBB, \xABkg\xBB, \xABun\xBB, \xABc/\xBB, \xABp/\xBB saem. \xAB20x35\xBB
|
|
3248
|
+
-- e \xABvacuo\xBB ficam \u2014 e s\xE3o eles que identificam o produto.
|
|
3249
|
+
-- \u2461 se mesmo assim n\xE3o vier nada, UMA \xFAltima tentativa com o token mais longo
|
|
3250
|
+
-- sozinho. \xC9 o recuo que acha \xABembalagem a v\xE1cuo 15 x 25\xBB a partir de
|
|
3251
|
+
-- \xABVACUO\xBB, para a pessoa escolher entre poucos em vez de nenhum.
|
|
3252
|
+
--
|
|
3253
|
+
-- N\xE3o se mexe em \`global_search\`: l\xE1 a regra est\xE1 certa para quem ela serve.
|
|
3254
|
+
-- ---------------------------------------------------------------------------
|
|
3255
|
+
|
|
3256
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_match_product_candidates(
|
|
3257
|
+
p_queries jsonb,
|
|
3258
|
+
p_supplier_cnpj text DEFAULT NULL,
|
|
3259
|
+
p_per_query integer DEFAULT 8
|
|
3260
|
+
) RETURNS TABLE (
|
|
3261
|
+
ref text,
|
|
3262
|
+
product_id uuid,
|
|
3263
|
+
name text,
|
|
3264
|
+
sku text,
|
|
3265
|
+
unit text,
|
|
3266
|
+
cost numeric,
|
|
3267
|
+
score real,
|
|
3268
|
+
reason text
|
|
3269
|
+
)
|
|
3270
|
+
LANGUAGE plpgsql
|
|
3271
|
+
STABLE
|
|
3272
|
+
SET search_path TO 'public', 'extensions', 'pg_temp'
|
|
3273
|
+
AS $fn$
|
|
3274
|
+
DECLARE
|
|
3275
|
+
v_tenant uuid := (SELECT app.current_tenant_id());
|
|
3276
|
+
v_cnpj text := public.fayz_digits(coalesce(p_supplier_cnpj, ''));
|
|
3277
|
+
v_barcodes boolean := to_regclass('public.plg_inventory_product_barcodes') IS NOT NULL;
|
|
3278
|
+
v_map boolean := to_regclass('public.plg_fiscalbr_supplier_product_map') IS NOT NULL;
|
|
3279
|
+
v_search text;
|
|
3280
|
+
v_q record;
|
|
3281
|
+
v_found uuid;
|
|
3282
|
+
v_tokens text[];
|
|
3283
|
+
v_strong text[];
|
|
3284
|
+
v_busca text;
|
|
3285
|
+
v_recuo text;
|
|
3286
|
+
BEGIN
|
|
3287
|
+
IF v_tenant IS NULL THEN RETURN; END IF;
|
|
3288
|
+
IF p_queries IS NULL OR jsonb_typeof(p_queries) <> 'array' THEN RETURN; END IF;
|
|
3289
|
+
p_per_query := least(greatest(coalesce(p_per_query, 8), 1), 50);
|
|
3290
|
+
|
|
3291
|
+
v_search := CASE
|
|
3292
|
+
WHEN to_regprocedure('public.global_search(text,uuid,text[],integer,integer)') IS NOT NULL
|
|
3293
|
+
THEN 'public.global_search'
|
|
3294
|
+
WHEN to_regprocedure('public.fayz_global_search(text,uuid,text[],integer,integer)') IS NOT NULL
|
|
3295
|
+
THEN 'public.fayz_global_search'
|
|
3296
|
+
ELSE NULL
|
|
3297
|
+
END;
|
|
3298
|
+
|
|
3299
|
+
FOR v_q IN
|
|
3300
|
+
SELECT e->>'ref' AS ref,
|
|
3301
|
+
coalesce(e->>'text', '') AS text,
|
|
3302
|
+
public.fayz_digits(coalesce(e->>'ean', '')) AS ean,
|
|
3303
|
+
nullif(btrim(coalesce(e->>'code', '')), '') AS code
|
|
3304
|
+
FROM jsonb_array_elements(p_queries) AS e
|
|
3305
|
+
WHERE e->>'ref' IS NOT NULL
|
|
3306
|
+
LOOP
|
|
3307
|
+
v_found := NULL;
|
|
3308
|
+
|
|
3309
|
+
-- \u2460 O de-para do fornecedor. Aprendizado da casa, e ele ganha de tudo.
|
|
3310
|
+
IF v_map AND v_cnpj <> '' AND v_q.code IS NOT NULL THEN
|
|
3311
|
+
SELECT p.id INTO v_found
|
|
3312
|
+
FROM public.plg_fiscalbr_supplier_product_map m
|
|
3313
|
+
JOIN public.products p ON p.id = m.product_id
|
|
3314
|
+
WHERE m.supplier_cnpj = v_cnpj
|
|
3315
|
+
AND m.supplier_product_code = v_q.code
|
|
3316
|
+
AND p.is_active
|
|
3317
|
+
ORDER BY m.hit_count DESC NULLS LAST, m.last_used_at DESC NULLS LAST
|
|
3318
|
+
LIMIT 1;
|
|
3319
|
+
|
|
3320
|
+
IF v_found IS NOT NULL THEN
|
|
3321
|
+
RETURN QUERY
|
|
3322
|
+
SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'supplier_map'::text
|
|
3323
|
+
FROM public.products p WHERE p.id = v_found;
|
|
3324
|
+
CONTINUE;
|
|
3325
|
+
END IF;
|
|
3326
|
+
END IF;
|
|
3327
|
+
|
|
3328
|
+
-- \u2461 O c\xF3digo de barras. \xABSEM GTIN\xBB \xE9 o que a NF-e escreve quando n\xE3o h\xE1 \u2014 e
|
|
3329
|
+
-- \`fayz_digits\` o reduz a vazio, ent\xE3o o teste de comprimento j\xE1 o barra.
|
|
3330
|
+
IF length(v_q.ean) >= 8 THEN
|
|
3331
|
+
SELECT p.id INTO v_found
|
|
3332
|
+
FROM public.products p
|
|
3333
|
+
WHERE p.is_active
|
|
3334
|
+
AND (
|
|
3335
|
+
public.fayz_digits(coalesce(p.metadata->>'barcode', '')) = v_q.ean
|
|
3336
|
+
OR (v_barcodes AND EXISTS (
|
|
3337
|
+
SELECT 1 FROM public.plg_inventory_product_barcodes b
|
|
3338
|
+
WHERE b.product_id = p.id
|
|
3339
|
+
AND public.fayz_digits(coalesce(b.barcode, '')) = v_q.ean))
|
|
3340
|
+
)
|
|
3341
|
+
LIMIT 1;
|
|
3342
|
+
|
|
3343
|
+
IF v_found IS NOT NULL THEN
|
|
3344
|
+
RETURN QUERY
|
|
3345
|
+
SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'ean'::text
|
|
3346
|
+
FROM public.products p WHERE p.id = v_found;
|
|
3347
|
+
CONTINUE;
|
|
3348
|
+
END IF;
|
|
3349
|
+
END IF;
|
|
3350
|
+
|
|
3351
|
+
-- \u2462 Semelhan\xE7a de texto, com o que DISTINGUE em vez da frase inteira.
|
|
3352
|
+
CONTINUE WHEN v_search IS NULL;
|
|
3353
|
+
|
|
3354
|
+
v_tokens := array_remove(string_to_array(public.fayz_norm(v_q.text), ' '), '');
|
|
3355
|
+
SELECT array_agg(t ORDER BY length(t) DESC) INTO v_strong
|
|
3356
|
+
FROM unnest(v_tokens) t WHERE length(t) >= 3;
|
|
3357
|
+
|
|
3358
|
+
v_busca := array_to_string(coalesce(v_strong, v_tokens), ' ');
|
|
3359
|
+
-- O recuo \xE9 o token mais longo sozinho: numa descri\xE7\xE3o de nota ele \xE9 quase
|
|
3360
|
+
-- sempre o nome da coisa, e os outros s\xE3o medida, micragem ou conjun\xE7\xE3o.
|
|
3361
|
+
v_recuo := coalesce(v_strong[1], '');
|
|
3362
|
+
|
|
3363
|
+
CONTINUE WHEN length(v_busca) < 3;
|
|
3364
|
+
|
|
3365
|
+
RETURN QUERY EXECUTE format($q$
|
|
3366
|
+
WITH forte AS (
|
|
3367
|
+
SELECT g.record_id, g.score FROM %1$s($1, $2, ARRAY['product']::text[], $3, $3) g
|
|
3368
|
+
),
|
|
3369
|
+
-- S\xF3 quando a busca forte n\xE3o achou nada: um recuo que rodasse sempre
|
|
3370
|
+
-- encheria a lista de parecidos e empurraria o certo para fora do teto.
|
|
3371
|
+
fraco AS (
|
|
3372
|
+
SELECT g.record_id, g.score * 0.7 AS score
|
|
3373
|
+
FROM %1$s($4, $2, ARRAY['product']::text[], $3, $3) g
|
|
3374
|
+
WHERE $4 <> '' AND $4 <> $1 AND NOT EXISTS (SELECT 1 FROM forte)
|
|
3375
|
+
)
|
|
3376
|
+
SELECT $5::text, p.id, p.name, p.sku, p.unit, p.cost, g.score::real, 'text'::text
|
|
3377
|
+
FROM (SELECT * FROM forte UNION ALL SELECT * FROM fraco) g
|
|
3378
|
+
JOIN public.products p ON p.id = g.record_id::uuid
|
|
3379
|
+
WHERE p.is_active
|
|
3380
|
+
ORDER BY g.score DESC
|
|
3381
|
+
$q$, v_search)
|
|
3382
|
+
USING v_busca, v_tenant, p_per_query, v_recuo, v_q.ref;
|
|
3383
|
+
END LOOP;
|
|
3384
|
+
END
|
|
3385
|
+
$fn$;
|
|
3386
|
+
|
|
3387
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) TO authenticated;
|
|
3388
|
+
|
|
3389
|
+
COMMENT ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) IS
|
|
3390
|
+
'Pr\xE9-sele\xE7\xE3o de produtos para a sugest\xE3o de IA da entrada de notas: de-para do fornecedor e EAN resolvem sozinhos (reason supplier_map/ean); o resto volta como candidatos de texto. A busca usa os tokens de 3+ letras da descri\xE7\xE3o, n\xE3o a frase \u2014 \xAB20X35 AR VACUO\xBB achava nada porque o AND de global_search exige o token \xABar\xBB, que n\xE3o existe em \xABembalagem vacuo 20x35\xBB.';
|
|
3391
|
+
`;
|
|
3392
|
+
var MIGRATION_015_O_MOVIMENTO_DIZ_QUE_VEIO_DA_NOTA = `-- ---------------------------------------------------------------------------
|
|
3393
|
+
-- O movimento de estoque diz, por escrito, que veio desta nota.
|
|
3394
|
+
-- ---------------------------------------------------------------------------
|
|
3395
|
+
-- A aceita\xE7\xE3o j\xE1 mandava a frase para \`inventory_receive\`, na chave \`notes\` do
|
|
3396
|
+
-- cabe\xE7alho \u2014 e ela nunca chegava: l\xE1 dentro o \`coalesce(v_ln.notes, \u2026)\` d\xE1 a vez
|
|
3397
|
+
-- \xE0 anota\xE7\xE3o DA LINHA, que aqui \xE9 sempre a descri\xE7\xE3o do item. A tela de
|
|
3398
|
+
-- Movimenta\xE7\xF5es ent\xE3o escrevia \xABSem motivo registrado\xBB sobre a entrada mais
|
|
3399
|
+
-- rastre\xE1vel que existe.
|
|
3400
|
+
--
|
|
3401
|
+
-- \`inventory/021\` abriu a chave \`reason\` no cabe\xE7alho. Esta migration passa a
|
|
3402
|
+
-- us\xE1-la, com a mesma frase que as linhas migradas do V1 carregam \u2014 para que a
|
|
3403
|
+
-- entrada de ontem e a de hoje se leiam igual no mesmo raz\xE3o.
|
|
3404
|
+
--
|
|
3405
|
+
-- S\xF3 a chamada muda. O resto do corpo \xE9 o da 009, palavra por palavra: esta
|
|
3406
|
+
-- fun\xE7\xE3o escreve estoque e t\xEDtulo na mesma transa\xE7\xE3o, e reescrev\xEA-la de mem\xF3ria
|
|
3407
|
+
-- \xE9 como se perde uma regra.
|
|
3408
|
+
-- ---------------------------------------------------------------------------
|
|
3409
|
+
|
|
3410
|
+
DO $m$
|
|
3411
|
+
DECLARE
|
|
3412
|
+
v_def text;
|
|
3413
|
+
v_antes text := E' ''ref'', ''NF-e '' || coalesce(v_doc.nfe_number, ''?''),\\n ''notes'', ''Entrada via DANFE NF-e '' || coalesce(v_doc.nfe_number, ''?'')),';
|
|
3414
|
+
v_depois text := E' ''ref'', ''NF-e '' || coalesce(v_doc.nfe_number, ''?''),\\n ''notes'', ''Entrada via DANFE NF-e '' || coalesce(v_doc.nfe_number, ''?''),\\n ''reason'', ''Entrada via DANFE NF-e '' || coalesce(v_doc.nfe_number, ''?'')),';
|
|
3415
|
+
BEGIN
|
|
3416
|
+
SELECT pg_get_functiondef(p.oid) INTO v_def
|
|
3417
|
+
FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
3418
|
+
WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_accept_inbox_document';
|
|
3419
|
+
|
|
3420
|
+
IF v_def IS NULL THEN
|
|
3421
|
+
RAISE NOTICE 'fiscalbr: accept ainda n\xE3o existe neste pool \u2014 009 roda antes e j\xE1 traz o reason';
|
|
3422
|
+
RETURN;
|
|
3423
|
+
END IF;
|
|
3424
|
+
|
|
3425
|
+
-- J\xE1 aplicada: sair sem tocar em nada \xE9 o que torna esta migration repet\xEDvel.
|
|
3426
|
+
IF position('''reason'', ''Entrada via DANFE' in v_def) > 0 THEN RETURN; END IF;
|
|
3427
|
+
|
|
3428
|
+
IF position(v_antes in v_def) = 0 THEN
|
|
3429
|
+
RAISE EXCEPTION 'fiscalbr/015: o corpo de fiscalbr_accept_inbox_document n\xE3o \xE9 o esperado \u2014 confira \xE0 m\xE3o em vez de deixar passar calado';
|
|
3430
|
+
END IF;
|
|
3431
|
+
|
|
3432
|
+
EXECUTE replace(v_def, v_antes, v_depois);
|
|
3433
|
+
END $m$;
|
|
3434
|
+
`;
|
|
3435
|
+
var MIGRATION_016_A_NOTA_GUARDA_O_PROPRIO_XML = `-- ---------------------------------------------------------------------------
|
|
3436
|
+
-- A nota guarda o pr\xF3prio XML, em vez de l\xEA-lo e jogar fora.
|
|
3437
|
+
-- ---------------------------------------------------------------------------
|
|
3438
|
+
-- O V1 abre o XML e o DANFE direto da lista de notas. A V2 mostrava os dois
|
|
3439
|
+
-- bot\xF5es desligados porque n\xE3o tinha o arquivo: das 889 notas deste cluster,
|
|
3440
|
+
-- ZERO t\xEAm \`document_id\`, zero t\xEAm bytes em \`documents\`, zero t\xEAm nada no
|
|
3441
|
+
-- metadata.
|
|
3442
|
+
--
|
|
3443
|
+
-- E n\xE3o \xE9 que o arquivo nunca tenha passado por aqui \u2014 ele passa INTEIRO pela
|
|
3444
|
+
-- m\xE3o do est\xE1gio: o navegador l\xEA o \`.xml\` que a pessoa arrastou, o parser extrai
|
|
3445
|
+
-- itens, parcelas e totais, e os bytes s\xE3o descartados no fim da fun\xE7\xE3o. A nota
|
|
3446
|
+
-- fiscal \xE9 documento com guarda legal de cinco anos, e a \xFAnica c\xF3pia ficava na
|
|
3447
|
+
-- pasta de downloads de quem importou.
|
|
3448
|
+
--
|
|
3449
|
+
-- \`xml_content text\` \xE9 o mesmo nome e o mesmo tipo do \`dfe_inbox.xml_content\` do
|
|
3450
|
+
-- V1 \u2014 a ponte da 003 j\xE1 citava essa coluna, e repetir o nome \xE9 o que faz uma
|
|
3451
|
+
-- futura migra\xE7\xE3o de dados ser um \`INSERT ... SELECT\` em vez de um de-para.
|
|
3452
|
+
--
|
|
3453
|
+
-- Guardar no banco e n\xE3o no bucket, de prop\xF3sito: s\xE3o 10\u201350 KB de texto por nota
|
|
3454
|
+
-- e s\xE3o o \xDANICO conte\xFAdo que torna a linha audit\xE1vel. Storage \xE9 outro sistema,
|
|
3455
|
+
-- com outra vida \xFAtil e outra pol\xEDtica de acesso, e um XML fiscal que vive em
|
|
3456
|
+
-- bucket \xE9 um XML que algu\xE9m apaga limpando bucket.
|
|
3457
|
+
--
|
|
3458
|
+
-- Isto N\xC3O preenche as 889 notas antigas: o que n\xE3o foi guardado n\xE3o se inventa.
|
|
3459
|
+
-- A tela desliga o bot\xE3o por nota, n\xE3o por feature.
|
|
3460
|
+
-- ---------------------------------------------------------------------------
|
|
3461
|
+
|
|
3462
|
+
ALTER TABLE public.plg_fiscalbr_inbox_documents
|
|
3463
|
+
ADD COLUMN IF NOT EXISTS xml_content text;
|
|
3464
|
+
|
|
3465
|
+
COMMENT ON COLUMN public.plg_fiscalbr_inbox_documents.xml_content IS
|
|
3466
|
+
'O XML autorizado da NF-e, como chegou. Nulo nas notas importadas antes desta coluna existir \u2014 o que n\xE3o foi guardado n\xE3o se inventa.';
|
|
3467
|
+
|
|
3468
|
+
-- \u2500\u2500 O est\xE1gio passa a guardar o que j\xE1 tinha em m\xE3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3469
|
+
-- Patch sobre o corpo que est\xE1 no cluster, e n\xE3o reescrita: esta fun\xE7\xE3o insere
|
|
3470
|
+
-- documento, itens, duplicatas e pagamentos numa transa\xE7\xE3o s\xF3, e reescrev\xEA-la de
|
|
3471
|
+
-- mem\xF3ria \xE9 como se perde uma regra.
|
|
3472
|
+
|
|
3473
|
+
DO $m$
|
|
3474
|
+
DECLARE
|
|
3475
|
+
v_def text;
|
|
3476
|
+
v_cols_antes text := E' total_freight, total_insurance, total_other, additional_info, metadata)';
|
|
3477
|
+
v_cols_depois text := E' total_freight, total_insurance, total_other, additional_info, metadata, xml_content)';
|
|
3478
|
+
v_vals_antes text := E' jsonb_build_object(''source'', coalesce(p_note->>''source'', ''xml_upload'')))';
|
|
3479
|
+
v_vals_depois text := E' jsonb_build_object(''source'', coalesce(p_note->>''source'', ''xml_upload'')),' || E'\\n' ||
|
|
3480
|
+
E' nullif(p_note->>''xml'', ''''))';
|
|
3481
|
+
v_upd_antes text := E' total_value = EXCLUDED.total_value,';
|
|
3482
|
+
v_upd_depois text := E' total_value = EXCLUDED.total_value,' || E'\\n' ||
|
|
3483
|
+
E' -- Reimportar com o arquivo preenche o que faltava; reimportar sem ele' || E'\\n' ||
|
|
3484
|
+
E' -- n\xE3o apaga o que j\xE1 estava guardado.' || E'\\n' ||
|
|
3485
|
+
E' xml_content = coalesce(EXCLUDED.xml_content, public.plg_fiscalbr_inbox_documents.xml_content),';
|
|
3486
|
+
BEGIN
|
|
3487
|
+
SELECT pg_get_functiondef(p.oid) INTO v_def
|
|
3488
|
+
FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
3489
|
+
WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_stage_inbox_document';
|
|
3490
|
+
|
|
3491
|
+
IF v_def IS NULL THEN
|
|
3492
|
+
RAISE NOTICE 'fiscalbr/016: o est\xE1gio ainda n\xE3o existe neste pool \u2014 a 009 roda antes';
|
|
3493
|
+
RETURN;
|
|
3494
|
+
END IF;
|
|
3495
|
+
|
|
3496
|
+
-- J\xE1 aplicada: sair sem tocar em nada \xE9 o que torna esta migration repet\xEDvel.
|
|
3497
|
+
IF position('xml_content' in v_def) > 0 THEN RETURN; END IF;
|
|
3498
|
+
|
|
3499
|
+
IF position(v_cols_antes in v_def) = 0
|
|
3500
|
+
OR position(v_vals_antes in v_def) = 0
|
|
3501
|
+
OR position(v_upd_antes in v_def) = 0 THEN
|
|
3502
|
+
RAISE EXCEPTION 'fiscalbr/016: o corpo de fiscalbr_stage_inbox_document n\xE3o \xE9 o esperado \u2014 confira \xE0 m\xE3o em vez de deixar passar calado';
|
|
3503
|
+
END IF;
|
|
3504
|
+
|
|
3505
|
+
v_def := replace(v_def, v_cols_antes, v_cols_depois);
|
|
3506
|
+
v_def := replace(v_def, v_vals_antes, v_vals_depois);
|
|
3507
|
+
v_def := replace(v_def, v_upd_antes, v_upd_depois);
|
|
3508
|
+
EXECUTE v_def;
|
|
3509
|
+
END $m$;
|
|
3510
|
+
`;
|
|
3511
|
+
var MIGRATION_017_QUEM_EMITE_E_O_CNPJ_NAO_A_LOJA = `-- Quem emite \xE9 o CNPJ, n\xE3o a loja.
|
|
3512
|
+
--
|
|
3513
|
+
-- As abas "Certificado" e "Emiss\xE3o Fiscal" do cadastro de empresa do V1
|
|
3514
|
+
-- guardam, em companies, como aquela pessoa jur\xEDdica emite NFS-e e NFC-e e se
|
|
3515
|
+
-- recebe DANFes da SEFAZ. No V2 a pessoa jur\xEDdica \xE9
|
|
3516
|
+
-- public.company_legal_entities (base), e a filial chega nela por
|
|
3517
|
+
-- app.units.company_legal_entity_id (168). Esta tabela \xE9 uma linha por pessoa
|
|
3518
|
+
-- jur\xEDdica:
|
|
3519
|
+
--
|
|
3520
|
+
-- unidade \u2192 app.units.company_legal_entity_id \u2192 company_legal_entities.id
|
|
3521
|
+
-- \u2192 plg_fiscalbr_emitter_settings.legal_entity_id (UNIQUE)
|
|
3522
|
+
--
|
|
3523
|
+
-- Duas filiais com o mesmo CNPJ dividem a mesma configura\xE7\xE3o, como no V1.
|
|
3524
|
+
--
|
|
3525
|
+
-- O certificado A1 N\xC3O mora aqui. O .pfx e a senha v\xE3o direto ao provedor
|
|
3526
|
+
-- (PlugNotas) e o que fica \xE9 o recibo: id no provedor, titular e validade. O
|
|
3527
|
+
-- CSC da NFC-e \xE9 segredo \u2014 o navegador grava, mas n\xE3o l\xEA de volta: a coluna
|
|
3528
|
+
-- fica fora do GRANT SELECT e a tela v\xEA s\xF3 \`nfce_csc_token_set\`.
|
|
3529
|
+
--
|
|
3530
|
+
-- Plano aprovado em docs/port/empresa.schema.json. Idempotente.
|
|
3531
|
+
|
|
3532
|
+
CREATE TABLE IF NOT EXISTS public.plg_fiscalbr_emitter_settings (
|
|
3533
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
3534
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
3535
|
+
legal_entity_id uuid NOT NULL,
|
|
3536
|
+
|
|
3537
|
+
nfse_enabled boolean NOT NULL DEFAULT false,
|
|
3538
|
+
nfse_production boolean NOT NULL DEFAULT false,
|
|
3539
|
+
nfse_national boolean NOT NULL DEFAULT false,
|
|
3540
|
+
nfse_service_code_national text,
|
|
3541
|
+
nfse_service_code_municipal text,
|
|
3542
|
+
nfse_nbs_code text,
|
|
3543
|
+
nfse_cnae text,
|
|
3544
|
+
nfse_iss_rate numeric(5,2) CHECK (nfse_iss_rate IS NULL OR (nfse_iss_rate >= 0 AND nfse_iss_rate <= 100)),
|
|
3545
|
+
nfse_iss_taxation smallint,
|
|
3546
|
+
nfse_iss_exigibility smallint,
|
|
3547
|
+
nfse_ibscbs_cst text,
|
|
3548
|
+
nfse_ibscbs_classification text,
|
|
3549
|
+
nfse_simples_regime smallint,
|
|
3550
|
+
nfse_net_value boolean NOT NULL DEFAULT false,
|
|
3551
|
+
nfse_additional_info text,
|
|
3552
|
+
nfse_city_features jsonb,
|
|
3553
|
+
|
|
3554
|
+
nfce_enabled boolean NOT NULL DEFAULT false,
|
|
3555
|
+
nfce_production boolean NOT NULL DEFAULT false,
|
|
3556
|
+
nfce_series integer CHECK (nfce_series IS NULL OR nfce_series > 0),
|
|
3557
|
+
nfce_csc_id text,
|
|
3558
|
+
nfce_csc_token text,
|
|
3559
|
+
nfce_csc_token_set boolean GENERATED ALWAYS AS (nfce_csc_token IS NOT NULL AND nfce_csc_token <> '') STORED,
|
|
3560
|
+
nfce_generic_item jsonb,
|
|
3561
|
+
|
|
3562
|
+
nfe_enabled boolean NOT NULL DEFAULT false,
|
|
3563
|
+
|
|
3564
|
+
certificate_provider_id text,
|
|
3565
|
+
certificate_cn text,
|
|
3566
|
+
certificate_expires_at timestamptz,
|
|
3567
|
+
certificate_uploaded_at timestamptz,
|
|
3568
|
+
|
|
3569
|
+
dfe_sync_enabled boolean NOT NULL DEFAULT false,
|
|
3570
|
+
dfe_sync_cursor text,
|
|
3571
|
+
dfe_last_sync_at timestamptz,
|
|
3572
|
+
provider_synced_at timestamptz,
|
|
3573
|
+
|
|
3574
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
3575
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
3576
|
+
UNIQUE (tenant_id, id)
|
|
3577
|
+
);
|
|
3578
|
+
|
|
3579
|
+
DO $$
|
|
3580
|
+
BEGIN
|
|
3581
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_emitter_settings_legal_entity_fkey') THEN
|
|
3582
|
+
-- company_legal_entities \xE9 da base, n\xE3o de outro plugin: a FK f\xEDsica \xE9
|
|
3583
|
+
-- permitida (a regra do 000 pro\xEDbe FK entre PLUGINS).
|
|
3584
|
+
ALTER TABLE ONLY public.plg_fiscalbr_emitter_settings
|
|
3585
|
+
ADD CONSTRAINT plg_fiscalbr_emitter_settings_legal_entity_fkey
|
|
3586
|
+
FOREIGN KEY (legal_entity_id) REFERENCES public.company_legal_entities(id) ON DELETE CASCADE;
|
|
3587
|
+
END IF;
|
|
3588
|
+
END $$;
|
|
3589
|
+
|
|
3590
|
+
CREATE UNIQUE INDEX IF NOT EXISTS plg_fiscalbr_emitter_settings_legal_entity_uidx
|
|
3591
|
+
ON public.plg_fiscalbr_emitter_settings USING btree (legal_entity_id);
|
|
3592
|
+
|
|
3593
|
+
COMMENT ON TABLE public.plg_fiscalbr_emitter_settings IS
|
|
3594
|
+
'V1: companies (abas Certificado + Emiss\xE3o Fiscal). Como uma pessoa jur\xEDdica emite NFS-e/NFC-e e recebe DANFes. Uma linha por CNPJ; a filial chega por app.units.company_legal_entity_id.';
|
|
3595
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.legal_entity_id IS 'A chave que conecta: company_legal_entities.id. UNIQUE.';
|
|
3596
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_production IS 'V1: companies.nfse_producao \u2014 false = homologa\xE7\xE3o.';
|
|
3597
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_national IS 'V1: companies.nfse_nacional \u2014 padr\xE3o nacional \xD7 municipal.';
|
|
3598
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_service_code_national IS 'V1: companies.nfse_codigo_tributacao_nacional';
|
|
3599
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_service_code_municipal IS 'V1: companies.nfse_codigo_tributacao_municipio';
|
|
3600
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_nbs_code IS 'V1: companies.nfse_codigo_nbs';
|
|
3601
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_cnae IS 'V1: companies.nfse_cnae \u2014 CNAE do servi\xE7o, pode diferir do principal.';
|
|
3602
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_iss_rate IS 'V1: companies.nfse_iss_aliquota (%)';
|
|
3603
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_iss_taxation IS 'V1: companies.nfse_iss_tipo_tributacao';
|
|
3604
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_iss_exigibility IS 'V1: companies.nfse_iss_exigibilidade';
|
|
3605
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_simples_regime IS 'V1: companies.nfse_regime_apuracao_sn \u2014 s\xF3 para optante do Simples.';
|
|
3606
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_net_value IS 'V1: companies.nfse_valor_liquido \u2014 emitir pelo valor l\xEDquido.';
|
|
3607
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_city_features IS 'V1: companies.nfse_city_features \u2014 o que a prefeitura aceita, devolvido pelo provedor.';
|
|
3608
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfce_csc_token IS 'V1: companies.nfce_csc_token \u2014 SEGREDO. Fora do GRANT SELECT de authenticated.';
|
|
3609
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfce_generic_item IS 'V1: companies.nfce_item_generico \u2014 {ncm, cfop, csosn}.';
|
|
3610
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.certificate_provider_id IS 'V1: companies.certificate_plugnotas_id \u2014 recibo; o .pfx n\xE3o fica no banco.';
|
|
3611
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.dfe_sync_enabled IS 'V1: companies.dfe_sync_enabled \u2014 receber DANFes automaticamente.';
|
|
3612
|
+
COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.dfe_sync_cursor IS 'V1: companies.dfe_sync_cursor \u2014 NSU onde a busca na SEFAZ parou.';
|
|
3613
|
+
|
|
3614
|
+
ALTER TABLE public.plg_fiscalbr_emitter_settings ENABLE ROW LEVEL SECURITY;
|
|
3615
|
+
ALTER TABLE public.plg_fiscalbr_emitter_settings FORCE ROW LEVEL SECURITY;
|
|
3616
|
+
|
|
3617
|
+
-- O segredo fica fora do SELECT: grava-se, n\xE3o se l\xEA.
|
|
3618
|
+
REVOKE ALL ON TABLE public.plg_fiscalbr_emitter_settings FROM anon, authenticated;
|
|
3619
|
+
GRANT INSERT, UPDATE, DELETE ON TABLE public.plg_fiscalbr_emitter_settings TO authenticated;
|
|
3620
|
+
GRANT SELECT (
|
|
3621
|
+
id, tenant_id, legal_entity_id,
|
|
3622
|
+
nfse_enabled, nfse_production, nfse_national, nfse_service_code_national, nfse_service_code_municipal,
|
|
3623
|
+
nfse_nbs_code, nfse_cnae, nfse_iss_rate, nfse_iss_taxation, nfse_iss_exigibility, nfse_ibscbs_cst,
|
|
3624
|
+
nfse_ibscbs_classification, nfse_simples_regime, nfse_net_value, nfse_additional_info, nfse_city_features,
|
|
3625
|
+
nfce_enabled, nfce_production, nfce_series, nfce_csc_id, nfce_csc_token_set, nfce_generic_item,
|
|
3626
|
+
nfe_enabled, certificate_provider_id, certificate_cn, certificate_expires_at, certificate_uploaded_at,
|
|
3627
|
+
dfe_sync_enabled, dfe_sync_cursor, dfe_last_sync_at, provider_synced_at, created_at, updated_at
|
|
3628
|
+
) ON public.plg_fiscalbr_emitter_settings TO authenticated;
|
|
3629
|
+
GRANT ALL ON TABLE public.plg_fiscalbr_emitter_settings TO service_role;
|
|
3630
|
+
|
|
3631
|
+
DROP POLICY IF EXISTS plg_fiscalbr_emitter_settings_authz_select ON public.plg_fiscalbr_emitter_settings;
|
|
3632
|
+
CREATE POLICY plg_fiscalbr_emitter_settings_authz_select ON public.plg_fiscalbr_emitter_settings
|
|
3633
|
+
FOR SELECT TO authenticated USING (
|
|
3634
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
3635
|
+
AND (SELECT app.has_permission('financial.read', NULL::uuid)));
|
|
3636
|
+
|
|
3637
|
+
DROP POLICY IF EXISTS plg_fiscalbr_emitter_settings_authz_write ON public.plg_fiscalbr_emitter_settings;
|
|
3638
|
+
CREATE POLICY plg_fiscalbr_emitter_settings_authz_write ON public.plg_fiscalbr_emitter_settings
|
|
3639
|
+
FOR ALL TO authenticated
|
|
3640
|
+
USING (
|
|
3641
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
3642
|
+
AND (SELECT app.has_permission('settings.manage', NULL::uuid)))
|
|
3643
|
+
WITH CHECK (
|
|
3644
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
3645
|
+
AND (SELECT app.has_permission('settings.manage', NULL::uuid))
|
|
3646
|
+
AND EXISTS (SELECT 1 FROM public.company_legal_entities le
|
|
3647
|
+
WHERE le.id = plg_fiscalbr_emitter_settings.legal_entity_id
|
|
3648
|
+
AND le.tenant_id = plg_fiscalbr_emitter_settings.tenant_id));
|
|
3649
|
+
`;
|
|
2834
3650
|
var MIGRATIONS = [
|
|
2835
3651
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
2836
3652
|
{ id: "001_preserve_terminal_results_and_recover_leases", sql: MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES },
|
|
@@ -2842,7 +3658,14 @@ var MIGRATIONS = [
|
|
|
2842
3658
|
{ id: "007_a_rls_do_documento_passa_a_ser_legivel", sql: MIGRATION_007_A_RLS_DO_DOCUMENTO_PASSA_A_SER_LEGIVEL },
|
|
2843
3659
|
{ id: "008_a_nota_recebida_guarda_a_decisao_de_entrada", sql: MIGRATION_008_A_NOTA_RECEBIDA_GUARDA_A_DECISAO_DE_ENTRADA },
|
|
2844
3660
|
{ id: "009_aceitar_a_nota_escreve_estoque_e_titulo_de_uma_vez", sql: MIGRATION_009_ACEITAR_A_NOTA_ESCREVE_ESTOQUE_E_TITULO_DE_UMA_VEZ },
|
|
2845
|
-
{ id: "010_a_data_de_emissao_e_instante_e_a_competencia_e_dia", sql: MIGRATION_010_A_DATA_DE_EMISSAO_E_INSTANTE_E_A_COMPETENCIA_E_DIA }
|
|
3661
|
+
{ id: "010_a_data_de_emissao_e_instante_e_a_competencia_e_dia", sql: MIGRATION_010_A_DATA_DE_EMISSAO_E_INSTANTE_E_A_COMPETENCIA_E_DIA },
|
|
3662
|
+
{ id: "011_o_imposto_do_produto_e_do_fiscal", sql: MIGRATION_011_O_IMPOSTO_DO_PRODUTO_E_DO_FISCAL },
|
|
3663
|
+
{ id: "012_quem_e_o_produto_se_pergunta_ao_indice", sql: MIGRATION_012_QUEM_E_O_PRODUTO_SE_PERGUNTA_AO_INDICE },
|
|
3664
|
+
{ id: "013_a_sugestao_sobrevive_a_fechar_a_janela", sql: MIGRATION_013_A_SUGESTAO_SOBREVIVE_A_FECHAR_A_JANELA },
|
|
3665
|
+
{ id: "014_a_descricao_da_nota_tem_lixo_no_meio", sql: MIGRATION_014_A_DESCRICAO_DA_NOTA_TEM_LIXO_NO_MEIO },
|
|
3666
|
+
{ id: "015_o_movimento_diz_que_veio_da_nota", sql: MIGRATION_015_O_MOVIMENTO_DIZ_QUE_VEIO_DA_NOTA },
|
|
3667
|
+
{ id: "016_a_nota_guarda_o_proprio_xml", sql: MIGRATION_016_A_NOTA_GUARDA_O_PROPRIO_XML },
|
|
3668
|
+
{ id: "017_quem_emite_e_o_cnpj_nao_a_loja", sql: MIGRATION_017_QUEM_EMITE_E_O_CNPJ_NAO_A_LOJA }
|
|
2846
3669
|
];
|
|
2847
3670
|
var taxGroupEntity = {
|
|
2848
3671
|
name: "Grupo fiscal",
|
|
@@ -3174,6 +3997,10 @@ function parseNfeXml(xml) {
|
|
|
3174
3997
|
}
|
|
3175
3998
|
function toStagePayload(note, options = {}) {
|
|
3176
3999
|
return {
|
|
4000
|
+
// O ARQUIVO VIAJA JUNTO. Ele já está na mão aqui — o parser acabou de lê-lo —
|
|
4001
|
+
// e sem isto os bytes morriam no fim da função: nota fiscal tem guarda legal
|
|
4002
|
+
// de cinco anos e a única cópia ficava na pasta de downloads de quem importou.
|
|
4003
|
+
...options.xml ? { xml: options.xml } : {},
|
|
3177
4004
|
access_key: note.accessKey,
|
|
3178
4005
|
nfe_number: note.nfeNumber,
|
|
3179
4006
|
nfe_series: note.nfeSeries,
|
|
@@ -3248,7 +4075,7 @@ function sb() {
|
|
|
3248
4075
|
var NOTE_COLUMNS = `
|
|
3249
4076
|
id, access_key, nfe_number, nfe_series, issue_date, received_at, status,
|
|
3250
4077
|
manifestation_status, manifested_at, manifestation_attempts,
|
|
3251
|
-
nature_operation, supplier_name, supplier_cnpj, supplier_party_id,
|
|
4078
|
+
nature_operation, supplier_name, supplier_cnpj, supplier_party_id, xml_content,
|
|
3252
4079
|
total_value, total_products, total_discount, total_freight,
|
|
3253
4080
|
invoice_id, inventory_operation_id, stocked_at, reviewed_at,
|
|
3254
4081
|
default_chart_account_id, default_location_id, unit_id, document_id, metadata,
|
|
@@ -3285,12 +4112,16 @@ function rowToNote(row) {
|
|
|
3285
4112
|
operationId: row.inventory_operation_id ?? null,
|
|
3286
4113
|
stockedAt: row.stocked_at ?? null,
|
|
3287
4114
|
documentId: row.document_id ?? null,
|
|
4115
|
+
hasXml: Boolean(row.xml_content),
|
|
3288
4116
|
unitId: row.unit_id ?? null
|
|
3289
4117
|
};
|
|
3290
4118
|
}
|
|
3291
4119
|
async function listInboxNotes(options = {}) {
|
|
3292
|
-
const { filter = "all", search, limit = 200 } = options;
|
|
4120
|
+
const { filter = "all", search, unitId, from, to, limit = 200 } = options;
|
|
3293
4121
|
let query = sb().from("plg_fiscalbr_inbox_documents").select(NOTE_COLUMNS);
|
|
4122
|
+
if (unitId) query = query.eq("unit_id", unitId);
|
|
4123
|
+
if (from) query = query.or(`issue_date.gte.${from},issue_date.is.null`);
|
|
4124
|
+
if (to) query = query.or(`issue_date.lte.${to},issue_date.is.null`);
|
|
3294
4125
|
if (filter === "stocked") query = query.not("stocked_at", "is", null);
|
|
3295
4126
|
if (filter === "pending") query = query.is("stocked_at", null);
|
|
3296
4127
|
if (filter === "rejected") query = query.eq("status", "rejected");
|
|
@@ -3308,10 +4139,11 @@ async function listInboxNotes(options = {}) {
|
|
|
3308
4139
|
var ITEM_COLUMNS = `
|
|
3309
4140
|
id, item_number, supplier_product_code, ean_barcode, description, ncm, cest, cfop,
|
|
3310
4141
|
unit_commercial, quantity_commercial, unit_price_commercial, total_price, discount,
|
|
4142
|
+
unit_taxable, quantity_taxable,
|
|
3311
4143
|
icms_cst, icms_csosn, icms_base, icms_rate, icms_value, pis_value, cofins_value, ipi_value,
|
|
3312
4144
|
product_id, entry_type, chart_account_id, stock_unit_id, stock_quantity,
|
|
3313
4145
|
conversion_factor, cost_mode, unit_cost_new, reconciled, stock_movement_id,
|
|
3314
|
-
shortage_quantity, shortage_reason, shortage_note
|
|
4146
|
+
shortage_quantity, shortage_reason, shortage_note, suggested_by, suggestion_confidence
|
|
3315
4147
|
`;
|
|
3316
4148
|
function rowToItem(row) {
|
|
3317
4149
|
const quantity = Number(row.quantity_commercial ?? 0);
|
|
@@ -3329,6 +4161,8 @@ function rowToItem(row) {
|
|
|
3329
4161
|
quantity,
|
|
3330
4162
|
unitPrice: Number(row.unit_price_commercial ?? 0),
|
|
3331
4163
|
total: Number(row.total_price ?? 0),
|
|
4164
|
+
unitTaxable: row.unit_taxable ?? null,
|
|
4165
|
+
quantityTaxable: row.quantity_taxable == null ? null : Number(row.quantity_taxable),
|
|
3332
4166
|
discount: row.discount == null ? null : Number(row.discount),
|
|
3333
4167
|
icmsCst: row.icms_cst ?? null,
|
|
3334
4168
|
icmsCsosn: row.icms_csosn ?? null,
|
|
@@ -3345,6 +4179,8 @@ function rowToItem(row) {
|
|
|
3345
4179
|
costMode: row.cost_mode ?? "novo",
|
|
3346
4180
|
unitCostNew: row.unit_cost_new == null ? null : Number(row.unit_cost_new),
|
|
3347
4181
|
reconciled: Boolean(row.reconciled),
|
|
4182
|
+
suggestedBy: row.suggested_by === "ai" || row.suggested_by === "ean" || row.suggested_by === "supplier_map" ? row.suggested_by : null,
|
|
4183
|
+
suggestionConfidence: row.suggestion_confidence == null ? null : Number(row.suggestion_confidence),
|
|
3348
4184
|
stockMovementId: row.stock_movement_id ?? null,
|
|
3349
4185
|
shortageQuantity: row.shortage_quantity == null ? null : Number(row.shortage_quantity),
|
|
3350
4186
|
shortageReason: row.shortage_reason ?? null,
|
|
@@ -3390,7 +4226,7 @@ async function getInboxNote(id) {
|
|
|
3390
4226
|
async function stageNoteFromXml(xml, options = {}) {
|
|
3391
4227
|
const parsed = parseNfeXml(xml);
|
|
3392
4228
|
const { data, error } = await sb().rpc("fiscalbr_stage_inbox_document", {
|
|
3393
|
-
p_note: toStagePayload(parsed, { unitId: options.unitId ?? null })
|
|
4229
|
+
p_note: toStagePayload(parsed, { unitId: options.unitId ?? null, xml })
|
|
3394
4230
|
});
|
|
3395
4231
|
if (error) throw error;
|
|
3396
4232
|
const res = data ?? {};
|
|
@@ -3415,6 +4251,8 @@ async function saveNoteDecision(input) {
|
|
|
3415
4251
|
stock_unit_id: i.stockUnitId ?? null,
|
|
3416
4252
|
conversion_factor: i.conversionFactor ?? 1,
|
|
3417
4253
|
cost_mode: i.costMode ?? "novo",
|
|
4254
|
+
suggested_by: i.suggestedBy ?? null,
|
|
4255
|
+
suggestion_confidence: i.suggestionConfidence ?? null,
|
|
3418
4256
|
shortage_quantity: i.shortageQuantity ?? null,
|
|
3419
4257
|
shortage_reason: i.shortageReason ?? null,
|
|
3420
4258
|
shortage_note: i.shortageNote ?? null
|
|
@@ -3478,24 +4316,77 @@ async function searchProducts(term, limit = 20) {
|
|
|
3478
4316
|
kind: p.kind ?? null
|
|
3479
4317
|
}));
|
|
3480
4318
|
}
|
|
3481
|
-
async function
|
|
3482
|
-
const { data, error } = await sb().from("
|
|
4319
|
+
async function getInboxNoteXml(id) {
|
|
4320
|
+
const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select("xml_content").eq("id", id).maybeSingle();
|
|
3483
4321
|
if (error) throw error;
|
|
3484
|
-
return
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
4322
|
+
return data?.xml_content ?? null;
|
|
4323
|
+
}
|
|
4324
|
+
async function findInboxNoteByNumber(nfeNumber) {
|
|
4325
|
+
const n = (nfeNumber ?? "").replace(/\D/g, "");
|
|
4326
|
+
if (!n) return null;
|
|
4327
|
+
const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select("id").eq("nfe_number", n).limit(2);
|
|
4328
|
+
if (error) throw error;
|
|
4329
|
+
const linhas = data ?? [];
|
|
4330
|
+
return linhas.length === 1 ? linhas[0].id : null;
|
|
4331
|
+
}
|
|
4332
|
+
async function findInboxNoteByOperation(operationId) {
|
|
4333
|
+
if (!operationId) return null;
|
|
4334
|
+
const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select("id").eq("inventory_operation_id", operationId).limit(1).maybeSingle();
|
|
4335
|
+
if (error) throw error;
|
|
4336
|
+
return data?.id ?? null;
|
|
4337
|
+
}
|
|
4338
|
+
async function suggestNoteItems(documentId, reevaluate = false) {
|
|
4339
|
+
const { data, error } = await sb().functions.invoke("fiscalbr-suggest-note-items", {
|
|
4340
|
+
body: { documentId, reevaluate, tenantId: getActiveTenantId() ?? null }
|
|
4341
|
+
});
|
|
4342
|
+
if (error) {
|
|
4343
|
+
const corpo = await error.context?.json?.().catch(() => null);
|
|
4344
|
+
throw new Error(corpo?.error ?? error.message);
|
|
4345
|
+
}
|
|
4346
|
+
const res = data ?? {};
|
|
4347
|
+
if (res.ok === false) throw new Error(String(res.error ?? "A sugest\xE3o falhou."));
|
|
4348
|
+
return {
|
|
4349
|
+
applied: Number(res.applied ?? 0),
|
|
4350
|
+
typedOnly: Number(res.typedOnly ?? 0),
|
|
4351
|
+
bound: Number(res.bound ?? 0),
|
|
4352
|
+
boundByEan: Number(res.boundByEan ?? 0),
|
|
4353
|
+
newProductNames: Array.isArray(res.newProductNames) ? res.newProductNames.filter((n) => typeof n === "string") : [],
|
|
4354
|
+
warnings: Array.isArray(res.warnings) ? res.warnings.filter((w) => typeof w === "string") : []
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
async function listProductsByIds(ids) {
|
|
4358
|
+
if (ids.length === 0) return [];
|
|
4359
|
+
const { data, error } = await sb().from("products").select("id, name, sku, unit, cost, kind").in("id", ids);
|
|
4360
|
+
if (error) throw error;
|
|
4361
|
+
return (data ?? []).map((p) => ({
|
|
4362
|
+
id: String(p.id),
|
|
4363
|
+
name: p.name ?? "",
|
|
4364
|
+
sku: p.sku ?? null,
|
|
4365
|
+
unit: p.unit ?? null,
|
|
4366
|
+
cost: p.cost == null ? null : Number(p.cost),
|
|
4367
|
+
kind: p.kind ?? null
|
|
4368
|
+
}));
|
|
4369
|
+
}
|
|
4370
|
+
async function listMeasurementUnits() {
|
|
4371
|
+
const { data, error } = await sb().from("plg_inventory_measurement_units").select("id, name, abbreviation, is_base").eq("is_active", true).order("name");
|
|
4372
|
+
if (error) throw error;
|
|
4373
|
+
return (data ?? []).map((u) => ({
|
|
4374
|
+
id: String(u.id),
|
|
4375
|
+
name: u.name ?? "",
|
|
4376
|
+
abbreviation: u.abbreviation ?? "",
|
|
4377
|
+
isBase: Boolean(u.is_base)
|
|
3489
4378
|
}));
|
|
3490
4379
|
}
|
|
3491
4380
|
async function listChartAccounts() {
|
|
3492
|
-
const { data, error } = await sb().from("plg_financial_chart_of_accounts").select("id, code, name,
|
|
4381
|
+
const { data, error } = await sb().from("plg_financial_chart_of_accounts").select("id, code, name, account_type, node_type, parent_id, is_active").order("code");
|
|
3493
4382
|
if (error) throw error;
|
|
3494
|
-
|
|
4383
|
+
const rows = (data ?? []).filter((a) => a.is_active !== false);
|
|
4384
|
+
const parents = new Set(rows.map((a) => a.parent_id).filter(Boolean).map(String));
|
|
4385
|
+
return rows.filter((a) => a.node_type !== "group" && !parents.has(String(a.id))).map((a) => ({
|
|
3495
4386
|
id: String(a.id),
|
|
3496
4387
|
code: a.code ?? "",
|
|
3497
4388
|
name: a.name ?? "",
|
|
3498
|
-
kind: a.
|
|
4389
|
+
kind: a.account_type ?? null
|
|
3499
4390
|
}));
|
|
3500
4391
|
}
|
|
3501
4392
|
async function listStockLocations() {
|
|
@@ -3521,22 +4412,53 @@ var cnpj = (digits2) => {
|
|
|
3521
4412
|
if (d.length !== 14) return digits2 ?? "\u2014";
|
|
3522
4413
|
return `${d.slice(0, 2)}.${d.slice(2, 5)}.${d.slice(5, 8)}/${d.slice(8, 12)}-${d.slice(12)}`;
|
|
3523
4414
|
};
|
|
4415
|
+
var CAMPO = "h-9 w-full rounded-md border border-input bg-background px-2 text-sm";
|
|
4416
|
+
var STATE_OPTIONS = [
|
|
4417
|
+
{ value: "all", label: "Todos" },
|
|
4418
|
+
{ value: "pending", label: "Estoque pendente" },
|
|
4419
|
+
{ value: "stocked", label: "Estocado" },
|
|
4420
|
+
{ value: "to_manifest", label: "A manifestar" },
|
|
4421
|
+
{ value: "rejected", label: "Recusada" }
|
|
4422
|
+
];
|
|
4423
|
+
var isoDay = (d) => d.toISOString().slice(0, 10);
|
|
3524
4424
|
function InboundNotesView({ onOpenNote, onOpenManifest }) {
|
|
3525
4425
|
const [notes, setNotes] = useState([]);
|
|
3526
4426
|
const [loading, setLoading] = useState(true);
|
|
3527
|
-
const [
|
|
4427
|
+
const [search, setSearch] = useState("");
|
|
3528
4428
|
const [busy, setBusy] = useState(false);
|
|
3529
4429
|
const [report, setReport] = useState(null);
|
|
3530
4430
|
const [dragging, setDragging] = useState(false);
|
|
3531
4431
|
const fileInput = useRef(null);
|
|
4432
|
+
const unidades = useUnitFilter({ scope: "list" });
|
|
4433
|
+
const { draft, applied, patch, setDraft, dirty, apply, reset } = useFilterDraft({
|
|
4434
|
+
filter: "all",
|
|
4435
|
+
unitId: unidades.value ?? "",
|
|
4436
|
+
// Sem período por padrão: o V1 abre em «Todos os registros», e uma janela
|
|
4437
|
+
// de 30 dias esconderia justamente a nota velha que nunca foi estocada.
|
|
4438
|
+
from: "",
|
|
4439
|
+
to: ""
|
|
4440
|
+
});
|
|
4441
|
+
const ancora = unidades.value ?? "";
|
|
4442
|
+
const ancoraAnterior = useRef(ancora);
|
|
4443
|
+
useEffect(() => {
|
|
4444
|
+
if (ancoraAnterior.current === ancora) return;
|
|
4445
|
+
ancoraAnterior.current = ancora;
|
|
4446
|
+
setDraft((atual) => ({ ...atual, unitId: ancora }));
|
|
4447
|
+
apply();
|
|
4448
|
+
}, [ancora, setDraft, apply]);
|
|
3532
4449
|
const reload = useCallback(async () => {
|
|
3533
4450
|
setLoading(true);
|
|
3534
4451
|
try {
|
|
3535
|
-
setNotes(await listInboxNotes({
|
|
4452
|
+
setNotes(await listInboxNotes({
|
|
4453
|
+
filter: applied.filter,
|
|
4454
|
+
unitId: applied.unitId || void 0,
|
|
4455
|
+
from: applied.from || void 0,
|
|
4456
|
+
to: applied.to || void 0
|
|
4457
|
+
}));
|
|
3536
4458
|
} finally {
|
|
3537
4459
|
setLoading(false);
|
|
3538
4460
|
}
|
|
3539
|
-
}, [
|
|
4461
|
+
}, [applied]);
|
|
3540
4462
|
useEffect(() => {
|
|
3541
4463
|
void reload();
|
|
3542
4464
|
}, [reload]);
|
|
@@ -3570,24 +4492,117 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
|
|
|
3570
4492
|
await rejectNote(note.id, "Recusada na lista de entrada");
|
|
3571
4493
|
await reload();
|
|
3572
4494
|
}, [reload]);
|
|
3573
|
-
const
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
4495
|
+
const activeCount = (applied.filter === "all" ? 0 : 1) + (applied.unitId ? 1 : 0) + (applied.from || applied.to ? 1 : 0);
|
|
4496
|
+
const limpar = useCallback(() => {
|
|
4497
|
+
reset({ filter: "all", unitId: ancora, from: "", to: "" });
|
|
4498
|
+
}, [reset, ancora]);
|
|
4499
|
+
const setFrom = (from) => patch({ from, to: draft.to && from > draft.to ? from : draft.to });
|
|
4500
|
+
const setTo = (to) => patch({ to, from: draft.from && to < draft.from ? to : draft.from });
|
|
4501
|
+
const query = search.trim().toLowerCase();
|
|
4502
|
+
const shown = useMemo(() => {
|
|
4503
|
+
if (!query) return notes;
|
|
4504
|
+
const digits2 = query.replace(/\D/g, "");
|
|
4505
|
+
return notes.filter((note) => {
|
|
4506
|
+
if (`${note.number} ${note.series} ${note.supplierName}`.toLowerCase().includes(query)) return true;
|
|
4507
|
+
if (digits2.length < 2) return false;
|
|
4508
|
+
if ((note.supplierCnpj ?? "").replace(/\D/g, "").includes(digits2)) return true;
|
|
4509
|
+
if ((note.accessKey ?? "").includes(digits2)) return true;
|
|
4510
|
+
return day(note.issueDate).replace(/\D/g, "").includes(digits2);
|
|
4511
|
+
});
|
|
4512
|
+
}, [notes, query]);
|
|
4513
|
+
const columns = useMemo(() => [
|
|
4514
|
+
{
|
|
4515
|
+
id: "issueDate",
|
|
4516
|
+
header: "Emiss\xE3o",
|
|
4517
|
+
accessorFn: (note) => note.issueDate ?? "",
|
|
4518
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: day(row.original.issueDate) })
|
|
4519
|
+
},
|
|
4520
|
+
{
|
|
4521
|
+
id: "number",
|
|
4522
|
+
header: "N\xBA",
|
|
4523
|
+
accessorFn: (note) => Number(note.number) || note.number,
|
|
4524
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "tabular-nums font-medium", children: row.original.number })
|
|
4525
|
+
},
|
|
4526
|
+
{
|
|
4527
|
+
id: "supplier",
|
|
4528
|
+
header: "Fornecedor",
|
|
4529
|
+
accessorFn: (note) => note.supplierName,
|
|
4530
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
4531
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium leading-tight", children: row.original.supplierName || "\u2014" }),
|
|
4532
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground tabular-nums", children: cnpj(row.original.supplierCnpj) })
|
|
4533
|
+
] })
|
|
4534
|
+
},
|
|
4535
|
+
{
|
|
4536
|
+
id: "items",
|
|
4537
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Itens" }),
|
|
4538
|
+
accessorFn: (note) => note.itemCount,
|
|
4539
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums text-muted-foreground", children: row.original.itemCount })
|
|
4540
|
+
},
|
|
4541
|
+
{
|
|
4542
|
+
id: "reconciled",
|
|
4543
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-center", children: "Concilia\xE7\xE3o" }),
|
|
4544
|
+
accessorFn: (note) => note.itemCount > 0 ? note.reconciledCount / note.itemCount : -1,
|
|
4545
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(ReconcileBadge, { done: row.original.reconciledCount, total: row.original.itemCount }) })
|
|
4546
|
+
},
|
|
4547
|
+
{
|
|
4548
|
+
id: "total",
|
|
4549
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Valor" }),
|
|
4550
|
+
accessorFn: (note) => note.total,
|
|
4551
|
+
cell: ({ row }) => {
|
|
4552
|
+
const note = row.original;
|
|
4553
|
+
return /* @__PURE__ */ jsxs("div", { className: "text-right tabular-nums", children: [
|
|
4554
|
+
money(note.total),
|
|
4555
|
+
note.instalmentCount > 0 && Math.abs(note.instalmentTotal - note.total) > 0.01 && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-warning-soft-foreground", children: [
|
|
4556
|
+
"parcelas: ",
|
|
4557
|
+
money(note.instalmentTotal)
|
|
4558
|
+
] })
|
|
4559
|
+
] });
|
|
4560
|
+
}
|
|
4561
|
+
},
|
|
4562
|
+
{
|
|
4563
|
+
id: "stock",
|
|
4564
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-center", children: "Estoque" }),
|
|
4565
|
+
accessorFn: (note) => note.stockedAt ? 2 : note.status === "rejected" ? 0 : 1,
|
|
4566
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(StockBadge, { stockedAt: row.original.stockedAt, status: row.original.status }) })
|
|
4567
|
+
},
|
|
4568
|
+
{
|
|
4569
|
+
id: "actions",
|
|
4570
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "A\xE7\xF5es" }),
|
|
4571
|
+
enableSorting: false,
|
|
4572
|
+
cell: ({ row }) => {
|
|
4573
|
+
const note = row.original;
|
|
4574
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", children: [
|
|
4575
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: note.stockedAt ? "ghost" : "outline", onClick: () => onOpenNote(note.id), children: note.stockedAt ? "Ver" : "Conciliar" }),
|
|
4576
|
+
!note.stockedAt && !note.invoiceId && /* @__PURE__ */ jsx(
|
|
4577
|
+
Button,
|
|
4578
|
+
{
|
|
4579
|
+
size: "sm",
|
|
4580
|
+
variant: "ghost",
|
|
4581
|
+
"aria-label": `Recusar a nota ${note.number}`,
|
|
4582
|
+
onClick: () => {
|
|
4583
|
+
void remove(note);
|
|
4584
|
+
},
|
|
4585
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
4586
|
+
}
|
|
4587
|
+
)
|
|
4588
|
+
] });
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
], [onOpenNote, remove]);
|
|
4592
|
+
const emptyMessage = query || activeCount > 0 ? /* @__PURE__ */ jsxs("div", { children: [
|
|
4593
|
+
/* @__PURE__ */ jsx(Inbox, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
4594
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota encontrada" }),
|
|
4595
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Tente outro n\xFAmero, fornecedor ou CNPJ." })
|
|
4596
|
+
] }) : /* @__PURE__ */ jsxs("div", { children: [
|
|
4597
|
+
/* @__PURE__ */ jsx(Inbox, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
4598
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota de entrada" }),
|
|
4599
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "As notas chegam da SEFAZ pela fila de manifesta\xE7\xE3o, ou voc\xEA arrasta o XML aqui." })
|
|
4600
|
+
] });
|
|
3586
4601
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3587
4602
|
/* @__PURE__ */ jsxs(PageHeaderActions, { children: [
|
|
3588
4603
|
onOpenManifest && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onOpenManifest, children: [
|
|
3589
4604
|
/* @__PURE__ */ jsx(Inbox, { className: "h-4 w-4 mr-1.5" }),
|
|
3590
|
-
"
|
|
4605
|
+
"Fila da SEFAZ"
|
|
3591
4606
|
] }),
|
|
3592
4607
|
/* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => fileInput.current?.click(), disabled: busy, children: [
|
|
3593
4608
|
busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 mr-1.5 animate-spin" }) : /* @__PURE__ */ jsx(UploadCloud, { className: "h-4 w-4 mr-1.5" }),
|
|
@@ -3645,56 +4660,64 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
|
|
|
3645
4660
|
] }, r.file)),
|
|
3646
4661
|
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setReport(null), children: "Fechar" })
|
|
3647
4662
|
] }),
|
|
3648
|
-
/* @__PURE__ */
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
] }) }),
|
|
3664
|
-
/* @__PURE__ */ jsx("tbody", { children: notes.map((note) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/30", children: [
|
|
3665
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums text-muted-foreground", children: day(note.issueDate) }),
|
|
3666
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums font-medium", children: note.number }),
|
|
3667
|
-
/* @__PURE__ */ jsxs("td", { className: "px-3 py-2", children: [
|
|
3668
|
-
/* @__PURE__ */ jsx("p", { className: "font-medium leading-tight", children: note.supplierName || "\u2014" }),
|
|
3669
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground tabular-nums", children: cnpj(note.supplierCnpj) })
|
|
3670
|
-
] }),
|
|
3671
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums text-muted-foreground", children: note.itemCount }),
|
|
3672
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-center", children: /* @__PURE__ */ jsx(ReconcileBadge, { done: note.reconciledCount, total: note.itemCount }) }),
|
|
3673
|
-
/* @__PURE__ */ jsxs("td", { className: "px-3 py-2 text-right tabular-nums", children: [
|
|
3674
|
-
money(note.total),
|
|
3675
|
-
note.instalmentCount > 0 && Math.abs(note.instalmentTotal - note.total) > 0.01 && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-warning-soft-foreground", children: [
|
|
3676
|
-
"parcelas: ",
|
|
3677
|
-
money(note.instalmentTotal)
|
|
3678
|
-
] })
|
|
3679
|
-
] }),
|
|
3680
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-center", children: /* @__PURE__ */ jsx(StockBadge, { stockedAt: note.stockedAt, status: note.status }) }),
|
|
3681
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", children: [
|
|
3682
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: note.stockedAt ? "ghost" : "outline", onClick: () => onOpenNote(note.id), children: note.stockedAt ? "Ver" : "Conciliar" }),
|
|
3683
|
-
!note.stockedAt && !note.invoiceId && /* @__PURE__ */ jsx(
|
|
3684
|
-
Button,
|
|
4663
|
+
/* @__PURE__ */ jsxs(
|
|
4664
|
+
FilterPanel,
|
|
4665
|
+
{
|
|
4666
|
+
title: "Filtros",
|
|
4667
|
+
dirty,
|
|
4668
|
+
onApply: apply,
|
|
4669
|
+
applyLabel: "Buscar",
|
|
4670
|
+
pendingTitle: "Filtros n\xE3o aplicados",
|
|
4671
|
+
pendingHint: "A lista ainda mostra o recorte anterior.",
|
|
4672
|
+
onClear: activeCount > 0 ? limpar : void 0,
|
|
4673
|
+
clearLabel: "Limpar",
|
|
4674
|
+
busy: loading,
|
|
4675
|
+
children: [
|
|
4676
|
+
unidades.visible && /* @__PURE__ */ jsx(FilterPanelField, { label: unidades.label, children: /* @__PURE__ */ jsxs(
|
|
4677
|
+
Select,
|
|
3685
4678
|
{
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
4679
|
+
value: draft.unitId || "__all__",
|
|
4680
|
+
onValueChange: (v) => patch({ unitId: v === "__all__" ? "" : v }),
|
|
4681
|
+
children: [
|
|
4682
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
4683
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
4684
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "__all__", children: unidades.allLabel }),
|
|
4685
|
+
unidades.options.map((u) => /* @__PURE__ */ jsx(SelectItem, { value: u.id, children: u.name }, u.id))
|
|
4686
|
+
] })
|
|
4687
|
+
]
|
|
3693
4688
|
}
|
|
3694
|
-
)
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
4689
|
+
) }),
|
|
4690
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: "Estado", children: /* @__PURE__ */ jsxs(Select, { value: draft.filter, onValueChange: (v) => patch({ filter: v }), children: [
|
|
4691
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
4692
|
+
/* @__PURE__ */ jsx(SelectContent, { children: STATE_OPTIONS.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
|
|
4693
|
+
] }) }),
|
|
4694
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: "Emiss\xE3o de", children: /* @__PURE__ */ jsx("input", { type: "date", value: draft.from, max: isoDay(/* @__PURE__ */ new Date()), onChange: (e) => setFrom(e.target.value), className: CAMPO }) }),
|
|
4695
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: "Emiss\xE3o at\xE9", children: /* @__PURE__ */ jsx("input", { type: "date", value: draft.to, max: isoDay(/* @__PURE__ */ new Date()), onChange: (e) => setTo(e.target.value), className: CAMPO }) })
|
|
4696
|
+
]
|
|
4697
|
+
}
|
|
4698
|
+
),
|
|
4699
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
4700
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: /* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsx(
|
|
4701
|
+
SearchField,
|
|
4702
|
+
{
|
|
4703
|
+
value: search,
|
|
4704
|
+
onChange: setSearch,
|
|
4705
|
+
placeholder: "Buscar por n\xFAmero, fornecedor, CNPJ\u2026",
|
|
4706
|
+
"data-testid": "inbound-notes-search"
|
|
4707
|
+
}
|
|
4708
|
+
) }) }),
|
|
4709
|
+
/* @__PURE__ */ jsx(
|
|
4710
|
+
DataTable,
|
|
4711
|
+
{
|
|
4712
|
+
variant: "bare",
|
|
4713
|
+
columns,
|
|
4714
|
+
data: loading ? [] : shown,
|
|
4715
|
+
loading,
|
|
4716
|
+
getRowId: (note) => note.id,
|
|
4717
|
+
emptyMessage
|
|
4718
|
+
}
|
|
4719
|
+
)
|
|
4720
|
+
] })
|
|
3698
4721
|
] });
|
|
3699
4722
|
}
|
|
3700
4723
|
function ReconcileBadge({ done, total }) {
|
|
@@ -3724,6 +4747,24 @@ function StockBadge({ stockedAt, status }) {
|
|
|
3724
4747
|
// src/types.ts
|
|
3725
4748
|
var STOCK_BEARING_ENTRY_TYPES = ["insumo", "revenda", "patrimonio"];
|
|
3726
4749
|
var isStockBearing = (type) => STOCK_BEARING_ENTRY_TYPES.includes(type);
|
|
4750
|
+
|
|
4751
|
+
// src/lib/note-suggestion.ts
|
|
4752
|
+
var PACKAGING_COUNT = {
|
|
4753
|
+
MI: 1e3,
|
|
4754
|
+
MIL: 1e3,
|
|
4755
|
+
MLH: 1e3,
|
|
4756
|
+
MILHEIRO: 1e3,
|
|
4757
|
+
"MI/1000": 1e3,
|
|
4758
|
+
DZ: 12,
|
|
4759
|
+
DUZIA: 12,
|
|
4760
|
+
"D\xDAZIA": 12,
|
|
4761
|
+
CENTO: 100,
|
|
4762
|
+
CENT: 100
|
|
4763
|
+
};
|
|
4764
|
+
var norm = (u) => (u ?? "").trim().toUpperCase().replace(/\.$/, "");
|
|
4765
|
+
function packagingCount(commercialUnit) {
|
|
4766
|
+
return PACKAGING_COUNT[norm(commercialUnit)] ?? null;
|
|
4767
|
+
}
|
|
3727
4768
|
var money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
|
|
3728
4769
|
var qty = (v) => new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 4 }).format(v || 0);
|
|
3729
4770
|
var day2 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
|
|
@@ -3747,9 +4788,15 @@ var draftOf = (item) => ({
|
|
|
3747
4788
|
stockUnitId: item.stockUnitId,
|
|
3748
4789
|
conversionFactor: String(item.conversionFactor ?? 1),
|
|
3749
4790
|
costMode: item.costMode === "media" ? "media" : "novo",
|
|
3750
|
-
shortageQuantity: item.shortageQuantity == null ? "" : String(item.shortageQuantity)
|
|
4791
|
+
shortageQuantity: item.shortageQuantity == null ? "" : String(item.shortageQuantity),
|
|
4792
|
+
// Vem do SERVIDOR: é o que faz o selo sobreviver a recarregar a página.
|
|
4793
|
+
suggestedBy: item.suggestedBy,
|
|
4794
|
+
suggestionConfidence: item.suggestionConfidence
|
|
3751
4795
|
});
|
|
3752
|
-
function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
4796
|
+
function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
|
|
4797
|
+
const emModal = chrome === "modal";
|
|
4798
|
+
const sangria = emModal ? "-mx-6 px-6" : "-mx-4 px-4";
|
|
4799
|
+
const cabecalho = (titulo) => emModal ? null : /* @__PURE__ */ jsx(SubpageHeader, { title: titulo, onBack, parentLabel: "Manifesto de Notas" });
|
|
3753
4800
|
const [note, setNote] = useState(null);
|
|
3754
4801
|
const [loading, setLoading] = useState(true);
|
|
3755
4802
|
const [drafts, setDrafts] = useState({});
|
|
@@ -3761,23 +4808,48 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3761
4808
|
const [locations, setLocations] = useState([]);
|
|
3762
4809
|
const [saving, setSaving] = useState(false);
|
|
3763
4810
|
const [accepting, setAccepting] = useState(false);
|
|
4811
|
+
const [openIds, setOpenIds] = useState([]);
|
|
4812
|
+
const [checked, setChecked] = useState({});
|
|
4813
|
+
const [registryWarning, setRegistryWarning] = useState(null);
|
|
3764
4814
|
const [error, setError] = useState(null);
|
|
3765
4815
|
const [accepted, setAccepted] = useState(null);
|
|
4816
|
+
const [baixandoXml, setBaixandoXml] = useState(false);
|
|
4817
|
+
const [suggesting, setSuggesting] = useState(false);
|
|
4818
|
+
const [suggestionNote, setSuggestionNote] = useState(null);
|
|
3766
4819
|
const load = useCallback(async () => {
|
|
3767
4820
|
setLoading(true);
|
|
3768
4821
|
try {
|
|
3769
4822
|
const detail = await getInboxNote(noteId);
|
|
3770
4823
|
setNote(detail);
|
|
4824
|
+
const failed = [];
|
|
3771
4825
|
const [u, a, l] = await Promise.all([
|
|
3772
|
-
listMeasurementUnits().catch(() =>
|
|
3773
|
-
|
|
3774
|
-
|
|
4826
|
+
listMeasurementUnits().catch(() => {
|
|
4827
|
+
failed.push("unidades");
|
|
4828
|
+
return [];
|
|
4829
|
+
}),
|
|
4830
|
+
listChartAccounts().catch(() => {
|
|
4831
|
+
failed.push("plano de contas");
|
|
4832
|
+
return [];
|
|
4833
|
+
}),
|
|
4834
|
+
listStockLocations().catch(() => {
|
|
4835
|
+
failed.push("locais de estoque");
|
|
4836
|
+
return [];
|
|
4837
|
+
})
|
|
3775
4838
|
]);
|
|
3776
4839
|
setUnits(u);
|
|
3777
4840
|
setAccounts(a);
|
|
3778
4841
|
setLocations(l);
|
|
4842
|
+
setRegistryWarning(failed.length ? `N\xE3o consegui carregar ${failed.join(", ")}. Estes seletores v\xE3o aparecer vazios.` : null);
|
|
3779
4843
|
if (detail) {
|
|
3780
|
-
|
|
4844
|
+
const vinculados = [...new Set(detail.items.map((i) => i.productId).filter((id) => !!id))];
|
|
4845
|
+
const nomes = vinculados.length ? new Map((await listProductsByIds(vinculados).catch(() => [])).map((p) => [p.id, p.name])) : /* @__PURE__ */ new Map();
|
|
4846
|
+
setDrafts(Object.fromEntries(detail.items.map((i) => [
|
|
4847
|
+
i.id,
|
|
4848
|
+
{ ...draftOf(i), productLabel: i.productId && nomes.get(i.productId) || "" }
|
|
4849
|
+
])));
|
|
4850
|
+
setChecked(Object.fromEntries(detail.items.filter((i) => i.reconciled).map((i) => [i.id, true])));
|
|
4851
|
+
const pending = detail.items.find((i) => !i.reconciled);
|
|
4852
|
+
setOpenIds(pending ? [pending.id] : []);
|
|
3781
4853
|
setInstalments(detail.instalments.map((d) => ({
|
|
3782
4854
|
index: d.index,
|
|
3783
4855
|
number: d.number,
|
|
@@ -3799,7 +4871,14 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3799
4871
|
useEffect(() => {
|
|
3800
4872
|
void load();
|
|
3801
4873
|
}, [load]);
|
|
3802
|
-
const patch = (id, change) => setDrafts((prev) => ({
|
|
4874
|
+
const patch = (id, change) => setDrafts((prev) => ({
|
|
4875
|
+
...prev,
|
|
4876
|
+
[id]: {
|
|
4877
|
+
...prev[id],
|
|
4878
|
+
..."suggestedBy" in change ? {} : { suggestedBy: null, suggestionConfidence: null },
|
|
4879
|
+
...change
|
|
4880
|
+
}
|
|
4881
|
+
}));
|
|
3803
4882
|
const accountOptions = useMemo(
|
|
3804
4883
|
() => accounts.map((a) => ({ id: a.id, label: `${a.code} \xB7 ${a.name}` })),
|
|
3805
4884
|
[accounts]
|
|
@@ -3812,10 +4891,10 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3812
4891
|
() => locations.map((l) => ({ id: l.id, label: l.name })),
|
|
3813
4892
|
[locations]
|
|
3814
4893
|
);
|
|
3815
|
-
const
|
|
4894
|
+
const buildDecisions = useCallback((rascunhos) => {
|
|
3816
4895
|
if (!note) return [];
|
|
3817
4896
|
return note.items.map((item) => {
|
|
3818
|
-
const d =
|
|
4897
|
+
const d = rascunhos[item.id] ?? draftOf(item);
|
|
3819
4898
|
return {
|
|
3820
4899
|
id: item.id,
|
|
3821
4900
|
productId: isStockBearing(d.entryType) ? d.productId : null,
|
|
@@ -3825,15 +4904,131 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3825
4904
|
stockUnitId: d.stockUnitId,
|
|
3826
4905
|
conversionFactor: Number(d.conversionFactor.replace(",", ".")) || 1,
|
|
3827
4906
|
costMode: d.costMode,
|
|
4907
|
+
suggestedBy: d.suggestedBy,
|
|
4908
|
+
suggestionConfidence: d.suggestionConfidence,
|
|
3828
4909
|
shortageQuantity: d.shortageQuantity === "" ? null : Number(d.shortageQuantity.replace(",", "."))
|
|
3829
4910
|
};
|
|
3830
4911
|
});
|
|
3831
|
-
}, [note,
|
|
4912
|
+
}, [note, defaultAccount]);
|
|
4913
|
+
const decisions = useMemo(() => buildDecisions(drafts), [buildDecisions, drafts]);
|
|
3832
4914
|
const stockLines = decisions.filter((d) => isStockBearing(d.entryType));
|
|
3833
4915
|
const missingProduct = stockLines.filter((d) => !d.productId).length;
|
|
3834
4916
|
const missingType = decisions.filter((d) => !d.entryType).length;
|
|
4917
|
+
const suspectFactor = (item, d) => packagingCount(item.unit) !== null && isStockBearing(d.entryType) && ((Number(d.conversionFactor.replace(",", ".")) || 1) === 1 || !d.stockUnitId);
|
|
4918
|
+
const unverifiedFactor = note ? note.items.filter((i) => suspectFactor(i, drafts[i.id] ?? draftOf(i))).length : 0;
|
|
3835
4919
|
const instalmentSum = instalments.reduce((acc, d) => acc + (Number(d.amount) || 0), 0);
|
|
3836
4920
|
const readOnly = Boolean(note?.stockedAt || note?.invoiceId);
|
|
4921
|
+
const isReady = (item, d) => Boolean(d.entryType) && (!isStockBearing(d.entryType) || Boolean(d.productId)) && !suspectFactor(item, d);
|
|
4922
|
+
const doneCount = note ? note.items.filter((i) => checked[i.id]).length : 0;
|
|
4923
|
+
const toggleItem = (id) => setOpenIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]);
|
|
4924
|
+
const confirmItem = (id) => {
|
|
4925
|
+
setChecked((prev) => ({ ...prev, [id]: true }));
|
|
4926
|
+
setOpenIds((prev) => {
|
|
4927
|
+
const rest = prev.filter((x) => x !== id);
|
|
4928
|
+
const next = note?.items.find((i) => i.id !== id && !checked[i.id]);
|
|
4929
|
+
return next && !rest.includes(next.id) ? [...rest, next.id] : rest;
|
|
4930
|
+
});
|
|
4931
|
+
};
|
|
4932
|
+
const persistir = useCallback(async (rascunhos) => {
|
|
4933
|
+
if (!note) return;
|
|
4934
|
+
await saveNoteDecision({
|
|
4935
|
+
documentId: note.id,
|
|
4936
|
+
items: buildDecisions(rascunhos),
|
|
4937
|
+
defaultChartAccountId: defaultAccount,
|
|
4938
|
+
defaultLocationId: location,
|
|
4939
|
+
instalments
|
|
4940
|
+
});
|
|
4941
|
+
}, [note, buildDecisions, defaultAccount, location, instalments]);
|
|
4942
|
+
const pendingItems = useMemo(
|
|
4943
|
+
() => (note?.items ?? []).filter((item) => {
|
|
4944
|
+
const d = drafts[item.id] ?? draftOf(item);
|
|
4945
|
+
return !d.entryType || isStockBearing(d.entryType) && !d.productId;
|
|
4946
|
+
}),
|
|
4947
|
+
[note, drafts]
|
|
4948
|
+
);
|
|
4949
|
+
const machineItems = useMemo(
|
|
4950
|
+
() => (note?.items ?? []).filter((item) => (drafts[item.id] ?? draftOf(item)).suggestedBy !== null),
|
|
4951
|
+
[note, drafts]
|
|
4952
|
+
);
|
|
4953
|
+
const sugerir = useCallback(async (reavaliar2 = false) => {
|
|
4954
|
+
if (!note) return;
|
|
4955
|
+
setSuggesting(true);
|
|
4956
|
+
setSuggestionNote(null);
|
|
4957
|
+
setError(null);
|
|
4958
|
+
try {
|
|
4959
|
+
await persistir(drafts);
|
|
4960
|
+
const res = await suggestNoteItems(note.id, reavaliar2);
|
|
4961
|
+
await load();
|
|
4962
|
+
if (res.applied === 0 && res.bound === 0 && res.typedOnly === 0) {
|
|
4963
|
+
setSuggestionNote(res.warnings[0] ?? "N\xE3o achei nada para preencher nestes itens.");
|
|
4964
|
+
return;
|
|
4965
|
+
}
|
|
4966
|
+
const semIA = res.bound ? `Sem IA: ${res.boundByEan ? `${res.boundByEan} pelo c\xF3digo de barras` : ""}` + (res.boundByEan && res.bound - res.boundByEan ? " e " : "") + (res.bound - res.boundByEan ? `${res.bound - res.boundByEan} pelo cat\xE1logo deste fornecedor` : "") + "." : "";
|
|
4967
|
+
setSuggestionNote([
|
|
4968
|
+
semIA,
|
|
4969
|
+
res.applied ? `${res.applied} ${res.applied === 1 ? "item preenchido" : "itens preenchidos"} pela IA.` : "",
|
|
4970
|
+
res.typedOnly ? `${res.typedOnly} ${res.typedOnly === 1 ? "item ficou" : "itens ficaram"} sem produto no cat\xE1logo` + (res.newProductNames.length ? ` (${res.newProductNames.slice(0, 3).join(", ")})` : "") + " \u2014 escolha o produto ou cadastre antes de aceitar." : "",
|
|
4971
|
+
res.applied || res.bound ? "Salvo." : "",
|
|
4972
|
+
...res.warnings
|
|
4973
|
+
].filter(Boolean).join(" "));
|
|
4974
|
+
} catch (e) {
|
|
4975
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
4976
|
+
} finally {
|
|
4977
|
+
setSuggesting(false);
|
|
4978
|
+
}
|
|
4979
|
+
}, [note, drafts, persistir, load]);
|
|
4980
|
+
const reavaliar = pendingItems.length === 0 && machineItems.length > 0;
|
|
4981
|
+
const botaoIA = readOnly || pendingItems.length === 0 && !reavaliar ? null : /* @__PURE__ */ jsx(
|
|
4982
|
+
AiActionButton,
|
|
4983
|
+
{
|
|
4984
|
+
size: "sm",
|
|
4985
|
+
className: "shrink-0",
|
|
4986
|
+
tone: reavaliar ? "soft" : "solid",
|
|
4987
|
+
busy: suggesting,
|
|
4988
|
+
busyLabel: "Pensando\u2026",
|
|
4989
|
+
onClick: () => {
|
|
4990
|
+
void sugerir(reavaliar);
|
|
4991
|
+
},
|
|
4992
|
+
children: reavaliar ? "Rodar IA novamente" : "Sugerir com IA"
|
|
4993
|
+
}
|
|
4994
|
+
);
|
|
4995
|
+
const moldura = (node, titulo, rodape2) => emModal ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4996
|
+
/* @__PURE__ */ jsxs(ModalHeader, { className: "shrink-0 flex-row items-center justify-between gap-3 space-y-0 border-b px-6 py-3 pr-14 text-left sm:flex-row", children: [
|
|
4997
|
+
/* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 truncate", children: titulo }),
|
|
4998
|
+
botaoIA
|
|
4999
|
+
] }),
|
|
5000
|
+
/* @__PURE__ */ jsx(ModalBody, { className: "px-6 py-4", children: node }),
|
|
5001
|
+
rodape2 && /* @__PURE__ */ jsx(ModalFooter, { className: "shrink-0 flex-row items-center justify-between gap-3 space-x-0 border-t px-6 py-3 pr-16", children: rodape2 })
|
|
5002
|
+
] }) : (
|
|
5003
|
+
// Em tela cheia não há moldura onde pendurar o rodapé: ele volta para dentro
|
|
5004
|
+
// do conteúdo, grudado na base da janela enquanto a página rola.
|
|
5005
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5006
|
+
node,
|
|
5007
|
+
rodape2 && /* @__PURE__ */ jsx("div", { className: `sticky bottom-0 ${sangria} flex items-center justify-between gap-3 border-t bg-card/95 py-3 pr-16 backdrop-blur`, children: rodape2 })
|
|
5008
|
+
] })
|
|
5009
|
+
);
|
|
5010
|
+
const baixarXml = useCallback(async () => {
|
|
5011
|
+
if (!note) return;
|
|
5012
|
+
setBaixandoXml(true);
|
|
5013
|
+
setError(null);
|
|
5014
|
+
try {
|
|
5015
|
+
const xml = await getInboxNoteXml(note.id);
|
|
5016
|
+
if (!xml) {
|
|
5017
|
+
setError("O arquivo desta nota n\xE3o est\xE1 guardado.");
|
|
5018
|
+
return;
|
|
5019
|
+
}
|
|
5020
|
+
const url = URL.createObjectURL(new Blob([xml], { type: "application/xml" }));
|
|
5021
|
+
const a = document.createElement("a");
|
|
5022
|
+
a.href = url;
|
|
5023
|
+
a.download = `${note.accessKey || note.number || "nfe"}.xml`;
|
|
5024
|
+
a.click();
|
|
5025
|
+
URL.revokeObjectURL(url);
|
|
5026
|
+
} catch (e) {
|
|
5027
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
5028
|
+
} finally {
|
|
5029
|
+
setBaixandoXml(false);
|
|
5030
|
+
}
|
|
5031
|
+
}, [note]);
|
|
3837
5032
|
const save = async () => {
|
|
3838
5033
|
if (!note) return;
|
|
3839
5034
|
setSaving(true);
|
|
@@ -3874,30 +5069,89 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3874
5069
|
}
|
|
3875
5070
|
};
|
|
3876
5071
|
if (loading) {
|
|
3877
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3878
|
-
|
|
3879
|
-
/* @__PURE__ */
|
|
3880
|
-
|
|
3881
|
-
|
|
5072
|
+
return moldura(/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
5073
|
+
cabecalho("Concilia\xE7\xE3o"),
|
|
5074
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
5075
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
|
|
5076
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-x-6 gap-y-3 sm:grid-cols-2 lg:grid-cols-3", children: [0, 1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsxs("div", { children: [
|
|
5077
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" }),
|
|
5078
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-4 w-36" })
|
|
5079
|
+
] }, i)) })
|
|
5080
|
+
] }),
|
|
5081
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
5082
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-36" }),
|
|
5083
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-4 sm:grid-cols-2", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("div", { children: [
|
|
5084
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-28" }),
|
|
5085
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-9 w-full" })
|
|
5086
|
+
] }, i)) })
|
|
5087
|
+
] }),
|
|
5088
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }),
|
|
5089
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs(Card, { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
5090
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-full" }),
|
|
5091
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
5092
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-2/3" }),
|
|
5093
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-1/3" })
|
|
5094
|
+
] }),
|
|
5095
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
|
|
5096
|
+
] }, i)) })
|
|
5097
|
+
] }), "Concilia\xE7\xE3o");
|
|
3882
5098
|
}
|
|
3883
5099
|
if (!note) {
|
|
3884
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3885
|
-
|
|
5100
|
+
return moldura(/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
5101
|
+
cabecalho("Concilia\xE7\xE3o"),
|
|
3886
5102
|
/* @__PURE__ */ jsx(Card, { className: "p-8 text-center text-sm", children: error ? /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
|
|
3887
5103
|
"N\xE3o consegui carregar a nota: ",
|
|
3888
5104
|
error
|
|
3889
5105
|
] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Nota n\xE3o encontrada." }) })
|
|
3890
|
-
] });
|
|
5106
|
+
] }), "Concilia\xE7\xE3o");
|
|
3891
5107
|
}
|
|
3892
|
-
|
|
3893
|
-
/* @__PURE__ */
|
|
3894
|
-
|
|
5108
|
+
const rodape = readOnly ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5109
|
+
/* @__PURE__ */ jsxs(
|
|
5110
|
+
Button,
|
|
3895
5111
|
{
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
5112
|
+
variant: "ghost",
|
|
5113
|
+
onClick: async () => {
|
|
5114
|
+
await rejectNote(note.id);
|
|
5115
|
+
onBack();
|
|
5116
|
+
},
|
|
5117
|
+
disabled: saving || accepting,
|
|
5118
|
+
children: [
|
|
5119
|
+
/* @__PURE__ */ jsx(X, { className: "mr-1.5 h-4 w-4" }),
|
|
5120
|
+
"Rejeitar"
|
|
5121
|
+
]
|
|
3899
5122
|
}
|
|
3900
5123
|
),
|
|
5124
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5125
|
+
(missingType > 0 || missingProduct > 0) && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
5126
|
+
unverifiedFactor > 0 && `${unverifiedFactor} com fator por conferir`,
|
|
5127
|
+
unverifiedFactor > 0 && (missingType > 0 || missingProduct > 0) && " \xB7 ",
|
|
5128
|
+
missingType > 0 && `${missingType} sem tipo`,
|
|
5129
|
+
missingType > 0 && missingProduct > 0 && " \xB7 ",
|
|
5130
|
+
missingProduct > 0 && `${missingProduct} sem produto`
|
|
5131
|
+
] }),
|
|
5132
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => {
|
|
5133
|
+
void save();
|
|
5134
|
+
}, disabled: saving || accepting, children: [
|
|
5135
|
+
saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }),
|
|
5136
|
+
"Salvar concilia\xE7\xE3o"
|
|
5137
|
+
] }),
|
|
5138
|
+
/* @__PURE__ */ jsxs(
|
|
5139
|
+
Button,
|
|
5140
|
+
{
|
|
5141
|
+
onClick: () => {
|
|
5142
|
+
void accept();
|
|
5143
|
+
},
|
|
5144
|
+
disabled: accepting || saving || missingType > 0 || missingProduct > 0 || unverifiedFactor > 0,
|
|
5145
|
+
children: [
|
|
5146
|
+
accepting ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-4 w-4" }),
|
|
5147
|
+
"Aceitar"
|
|
5148
|
+
]
|
|
5149
|
+
}
|
|
5150
|
+
)
|
|
5151
|
+
] })
|
|
5152
|
+
] });
|
|
5153
|
+
return moldura(/* @__PURE__ */ jsxs("div", { className: `space-y-4 ${emModal ? "" : "pb-24"}`, children: [
|
|
5154
|
+
cabecalho(`Concilia\xE7\xE3o \xB7 NF-e ${note.number}`),
|
|
3901
5155
|
accepted && /* @__PURE__ */ jsxs(Card, { className: "border-success bg-success-soft p-4", children: [
|
|
3902
5156
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-success-soft-foreground", children: accepted.alreadyAccepted ? "Esta nota j\xE1 havia sido aceita \u2014 nada foi lan\xE7ado de novo." : `Aceita: ${accepted.stockLines} ${accepted.stockLines === 1 ? "linha" : "linhas"} de estoque e um t\xEDtulo de ${money2(accepted.invoiceTotal)} em ${accepted.instalments} ${accepted.instalments === 1 ? "parcela" : "parcelas"}.` }),
|
|
3903
5157
|
accepted.invoiceId && onOpenInvoice && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => onOpenInvoice(accepted.invoiceId), children: "Abrir a conta a pagar" })
|
|
@@ -3942,6 +5196,25 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3942
5196
|
/* @__PURE__ */ jsx(RefreshCw, { className: "mr-1.5 h-4 w-4" }),
|
|
3943
5197
|
"Atualizar status"
|
|
3944
5198
|
] }),
|
|
5199
|
+
note.hasXml ? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", disabled: baixandoXml, onClick: () => {
|
|
5200
|
+
void baixarXml();
|
|
5201
|
+
}, children: [
|
|
5202
|
+
baixandoXml ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(FileDown, { className: "mr-1.5 h-4 w-4" }),
|
|
5203
|
+
"Baixar XML"
|
|
5204
|
+
] }) : /* @__PURE__ */ jsx(
|
|
5205
|
+
EmBreve,
|
|
5206
|
+
{
|
|
5207
|
+
label: "Baixar XML",
|
|
5208
|
+
o_que: "O arquivo desta nota n\xE3o foi guardado \u2014 ela entrou antes de o est\xE1gio passar a gravar o XML. Reimportar o .xml preenche"
|
|
5209
|
+
}
|
|
5210
|
+
),
|
|
5211
|
+
/* @__PURE__ */ jsx(
|
|
5212
|
+
EmBreve,
|
|
5213
|
+
{
|
|
5214
|
+
label: "DANFE (PDF)",
|
|
5215
|
+
o_que: "O espelho impresso da nota. Tem layout fixado pela SEFAZ e vinha renderizado da PlugNotas no V1 \u2014 n\xE3o sai do XML sozinho"
|
|
5216
|
+
}
|
|
5217
|
+
),
|
|
3945
5218
|
/* @__PURE__ */ jsx(
|
|
3946
5219
|
EmBreve,
|
|
3947
5220
|
{
|
|
@@ -3954,6 +5227,10 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3954
5227
|
] }),
|
|
3955
5228
|
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
3956
5229
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Padr\xF5es desta nota" }),
|
|
5230
|
+
registryWarning && /* @__PURE__ */ jsx("div", { className: "mt-2 rounded-md border border-warning bg-warning-soft px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning-soft-foreground", children: [
|
|
5231
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
|
|
5232
|
+
registryWarning
|
|
5233
|
+
] }) }),
|
|
3957
5234
|
/* @__PURE__ */ jsxs("div", { className: "mt-3 grid gap-4 sm:grid-cols-2", children: [
|
|
3958
5235
|
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
3959
5236
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Plano de contas padr\xE3o" }),
|
|
@@ -3968,6 +5245,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3968
5245
|
placeholder: "Escolher plano de contas",
|
|
3969
5246
|
noResultsLabel: "Nenhum plano de contas",
|
|
3970
5247
|
searchingLabel: "Buscando\u2026",
|
|
5248
|
+
clearLabel: "Limpar",
|
|
3971
5249
|
disabled: readOnly
|
|
3972
5250
|
}
|
|
3973
5251
|
),
|
|
@@ -3986,6 +5264,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3986
5264
|
placeholder: "Escolher local",
|
|
3987
5265
|
noResultsLabel: "Nenhum local de estoque",
|
|
3988
5266
|
searchingLabel: "Buscando\u2026",
|
|
5267
|
+
clearLabel: "Limpar",
|
|
3989
5268
|
disabled: readOnly
|
|
3990
5269
|
}
|
|
3991
5270
|
),
|
|
@@ -3993,14 +5272,51 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
3993
5272
|
] })
|
|
3994
5273
|
] })
|
|
3995
5274
|
] }),
|
|
3996
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-
|
|
3997
|
-
/* @__PURE__ */ jsxs("
|
|
3998
|
-
"
|
|
3999
|
-
|
|
4000
|
-
|
|
5275
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
5276
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
5277
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold", children: [
|
|
5278
|
+
"Itens ",
|
|
5279
|
+
/* @__PURE__ */ jsxs("span", { className: "font-normal text-muted-foreground", children: [
|
|
5280
|
+
"(",
|
|
5281
|
+
note.items.length,
|
|
5282
|
+
")"
|
|
5283
|
+
] })
|
|
5284
|
+
] }),
|
|
5285
|
+
/* @__PURE__ */ jsx("div", { className: "h-1.5 min-w-[6rem] flex-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
|
|
5286
|
+
"div",
|
|
5287
|
+
{
|
|
5288
|
+
className: "h-full rounded-full bg-primary transition-all",
|
|
5289
|
+
style: { width: `${note.items.length ? doneCount / note.items.length * 100 : 0}%` }
|
|
5290
|
+
}
|
|
5291
|
+
) }),
|
|
5292
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs tabular-nums text-muted-foreground", children: [
|
|
5293
|
+
doneCount,
|
|
5294
|
+
"/",
|
|
4001
5295
|
note.items.length,
|
|
4002
|
-
"
|
|
4003
|
-
] })
|
|
5296
|
+
" conferidos"
|
|
5297
|
+
] }),
|
|
5298
|
+
/* @__PURE__ */ jsx(
|
|
5299
|
+
Button,
|
|
5300
|
+
{
|
|
5301
|
+
variant: "ghost",
|
|
5302
|
+
size: "sm",
|
|
5303
|
+
onClick: () => setOpenIds(openIds.length ? [] : note.items.map((i) => i.id)),
|
|
5304
|
+
children: openIds.length ? "Recolher todos" : "Abrir todos"
|
|
5305
|
+
}
|
|
5306
|
+
),
|
|
5307
|
+
!emModal && botaoIA
|
|
5308
|
+
] }),
|
|
5309
|
+
suggestionNote && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-md border border-magic/30 bg-magic-soft px-3 py-2", children: [
|
|
5310
|
+
/* @__PURE__ */ jsx("p", { className: "flex-1 text-xs text-magic-soft-foreground", children: suggestionNote }),
|
|
5311
|
+
/* @__PURE__ */ jsx(
|
|
5312
|
+
"button",
|
|
5313
|
+
{
|
|
5314
|
+
type: "button",
|
|
5315
|
+
className: "shrink-0 text-xs text-magic-soft-foreground/70 hover:underline",
|
|
5316
|
+
onClick: () => setSuggestionNote(null),
|
|
5317
|
+
children: "Fechar"
|
|
5318
|
+
}
|
|
5319
|
+
)
|
|
4004
5320
|
] }),
|
|
4005
5321
|
note.items.map((item) => {
|
|
4006
5322
|
const d = drafts[item.id] ?? draftOf(item);
|
|
@@ -4009,184 +5325,264 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
4009
5325
|
const stockQty = item.quantity * factor - shortage;
|
|
4010
5326
|
const unitCost = factor > 0 ? item.unitPrice / factor : item.unitPrice;
|
|
4011
5327
|
const bearing = isStockBearing(d.entryType);
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
/* @__PURE__ */ jsx(Field, { small: true, label: "EAN", value: item.eanBarcode ?? "\u2014", mono: true }),
|
|
4026
|
-
/* @__PURE__ */ jsx(Field, { small: true, label: "NCM", value: item.ncm ?? "\u2014", mono: true }),
|
|
4027
|
-
/* @__PURE__ */ jsx(Field, { small: true, label: "CFOP", value: item.cfop ?? "\u2014", mono: true }),
|
|
4028
|
-
/* @__PURE__ */ jsx(Field, { small: true, label: "CST/CSOSN", value: item.icmsCst ?? item.icmsCsosn ?? "\u2014", mono: true })
|
|
4029
|
-
] })
|
|
4030
|
-
] }),
|
|
4031
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-3 lg:border-l lg:pl-4", children: [
|
|
4032
|
-
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Item no estoque" }),
|
|
4033
|
-
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4034
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Tipo do registro" }),
|
|
4035
|
-
/* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: ENTRY_TYPES.map((t) => /* @__PURE__ */ jsxs(
|
|
4036
|
-
"button",
|
|
4037
|
-
{
|
|
4038
|
-
type: "button",
|
|
4039
|
-
disabled: readOnly,
|
|
4040
|
-
onClick: () => patch(item.id, { entryType: t.value }),
|
|
4041
|
-
className: `rounded-full border px-2.5 py-1 text-[11px] transition-colors disabled:opacity-60 ${d.entryType === t.value ? "border-primary bg-primary text-primary-foreground" : "border-input hover:bg-muted"}`,
|
|
4042
|
-
children: [
|
|
4043
|
-
t.label,
|
|
4044
|
-
t.hint && /* @__PURE__ */ jsxs("span", { className: "ml-1 opacity-70", children: [
|
|
4045
|
-
"\xB7 ",
|
|
4046
|
-
t.hint
|
|
4047
|
-
] })
|
|
4048
|
-
]
|
|
4049
|
-
},
|
|
4050
|
-
t.value
|
|
4051
|
-
)) })
|
|
4052
|
-
] }),
|
|
4053
|
-
bearing && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4054
|
-
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4055
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Produto vinculado" }),
|
|
5328
|
+
const open = openIds.includes(item.id);
|
|
5329
|
+
const done = Boolean(checked[item.id]);
|
|
5330
|
+
const ready = isReady(item, d);
|
|
5331
|
+
const typeLabel = ENTRY_TYPES.find((t) => t.value === d.entryType)?.label;
|
|
5332
|
+
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
5333
|
+
/* @__PURE__ */ jsxs(
|
|
5334
|
+
"button",
|
|
5335
|
+
{
|
|
5336
|
+
type: "button",
|
|
5337
|
+
onClick: () => toggleItem(item.id),
|
|
5338
|
+
"aria-expanded": open,
|
|
5339
|
+
className: "flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/30",
|
|
5340
|
+
children: [
|
|
4056
5341
|
/* @__PURE__ */ jsx(
|
|
4057
|
-
|
|
5342
|
+
ChevronRight,
|
|
4058
5343
|
{
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
onChange: (id, option) => patch(item.id, { productId: id || null, productLabel: option?.label ?? "" }),
|
|
4062
|
-
onSearch: async (term) => {
|
|
4063
|
-
const found = await searchProducts(term || item.description.slice(0, 24));
|
|
4064
|
-
return found.map((prod) => ({
|
|
4065
|
-
id: prod.id,
|
|
4066
|
-
label: prod.name,
|
|
4067
|
-
subtitle: prod.sku ?? void 0
|
|
4068
|
-
}));
|
|
4069
|
-
},
|
|
4070
|
-
minChars: 0,
|
|
4071
|
-
placeholder: "Buscar produto do cat\xE1logo",
|
|
4072
|
-
noResultsLabel: "Nenhum produto encontrado",
|
|
4073
|
-
searchingLabel: "Buscando\u2026",
|
|
4074
|
-
disabled: readOnly
|
|
5344
|
+
className: `h-4 w-4 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-90" : ""}`,
|
|
5345
|
+
"aria-hidden": true
|
|
4075
5346
|
}
|
|
4076
|
-
)
|
|
5347
|
+
),
|
|
5348
|
+
/* @__PURE__ */ jsx("span", { className: "w-5 shrink-0 text-xs tabular-nums text-muted-foreground", children: item.itemNumber }),
|
|
5349
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
5350
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium leading-tight", children: item.description }),
|
|
5351
|
+
/* @__PURE__ */ jsxs("span", { className: "block truncate text-xs text-muted-foreground", children: [
|
|
5352
|
+
qty(item.quantity),
|
|
5353
|
+
" ",
|
|
5354
|
+
item.unit ?? "",
|
|
5355
|
+
" \xB7 ",
|
|
5356
|
+
money2(item.unitPrice),
|
|
5357
|
+
typeLabel ? ` \xB7 ${typeLabel}` : " \xB7 sem tipo"
|
|
5358
|
+
] })
|
|
5359
|
+
] }),
|
|
5360
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm tabular-nums", children: money2(item.total) }),
|
|
5361
|
+
d.suggestedBy && !done && /* @__PURE__ */ jsx(AuthorBadge, { draft: d }),
|
|
5362
|
+
/* @__PURE__ */ jsx(ItemState, { done: done && ready, ready })
|
|
5363
|
+
]
|
|
5364
|
+
}
|
|
5365
|
+
),
|
|
5366
|
+
open && /* @__PURE__ */ jsxs("div", { className: "grid gap-4 border-t p-4 lg:grid-cols-2", children: [
|
|
5367
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5368
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Item na nota" }),
|
|
5369
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1 font-medium leading-tight", children: [
|
|
5370
|
+
item.itemNumber,
|
|
5371
|
+
". ",
|
|
5372
|
+
item.description
|
|
4077
5373
|
] }),
|
|
4078
|
-
/* @__PURE__ */ jsxs("
|
|
5374
|
+
/* @__PURE__ */ jsxs("dl", { className: "mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs sm:grid-cols-4", children: [
|
|
5375
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "Qtd", value: `${qty(item.quantity)} ${item.unit ?? ""}` }),
|
|
5376
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "Vl. unit.", value: money2(item.unitPrice) }),
|
|
5377
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "Total", value: money2(item.total) }),
|
|
5378
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "C\xF3d", value: item.supplierProductCode || "\u2014", mono: true }),
|
|
5379
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "EAN", value: item.eanBarcode ?? "\u2014", mono: true }),
|
|
5380
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "NCM", value: item.ncm ?? "\u2014", mono: true }),
|
|
5381
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "CFOP", value: item.cfop ?? "\u2014", mono: true }),
|
|
5382
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "CST/CSOSN", value: item.icmsCst ?? item.icmsCsosn ?? "\u2014", mono: true })
|
|
5383
|
+
] })
|
|
5384
|
+
] }),
|
|
5385
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 lg:border-l lg:pl-4", children: [
|
|
5386
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Item no estoque" }),
|
|
5387
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
5388
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Tipo do registro" }),
|
|
5389
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: ENTRY_TYPES.map((t) => /* @__PURE__ */ jsxs(
|
|
5390
|
+
"button",
|
|
5391
|
+
{
|
|
5392
|
+
type: "button",
|
|
5393
|
+
disabled: readOnly,
|
|
5394
|
+
onClick: () => patch(item.id, { entryType: t.value }),
|
|
5395
|
+
className: `rounded-full border px-2.5 py-1 text-[11px] transition-colors disabled:opacity-60 ${d.entryType === t.value ? "border-primary bg-primary text-primary-foreground" : "border-input hover:bg-muted"}`,
|
|
5396
|
+
children: [
|
|
5397
|
+
t.label,
|
|
5398
|
+
t.hint && /* @__PURE__ */ jsxs("span", { className: "ml-1 opacity-70", children: [
|
|
5399
|
+
"\xB7 ",
|
|
5400
|
+
t.hint
|
|
5401
|
+
] })
|
|
5402
|
+
]
|
|
5403
|
+
},
|
|
5404
|
+
t.value
|
|
5405
|
+
)) })
|
|
5406
|
+
] }),
|
|
5407
|
+
bearing && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4079
5408
|
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4080
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "
|
|
5409
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Produto vinculado" }),
|
|
4081
5410
|
/* @__PURE__ */ jsx(
|
|
4082
|
-
|
|
5411
|
+
SearchSelect,
|
|
4083
5412
|
{
|
|
4084
|
-
value: d.
|
|
4085
|
-
|
|
4086
|
-
|
|
5413
|
+
value: d.productId ?? void 0,
|
|
5414
|
+
displayValue: d.productLabel || void 0,
|
|
5415
|
+
onChange: (id, option) => patch(item.id, { productId: id || null, productLabel: option?.label ?? "" }),
|
|
5416
|
+
onSearch: async (term) => {
|
|
5417
|
+
const found = await searchProducts(term || item.description.slice(0, 24));
|
|
5418
|
+
return found.map((prod) => ({
|
|
5419
|
+
id: prod.id,
|
|
5420
|
+
label: prod.name,
|
|
5421
|
+
subtitle: prod.sku ?? void 0
|
|
5422
|
+
}));
|
|
5423
|
+
},
|
|
5424
|
+
minChars: 0,
|
|
5425
|
+
placeholder: "Buscar produto do cat\xE1logo",
|
|
5426
|
+
noResultsLabel: "Nenhum produto encontrado",
|
|
5427
|
+
searchingLabel: "Buscando\u2026",
|
|
5428
|
+
clearLabel: "Limpar",
|
|
4087
5429
|
disabled: readOnly
|
|
4088
5430
|
}
|
|
4089
5431
|
)
|
|
4090
5432
|
] }),
|
|
4091
|
-
/* @__PURE__ */ jsxs("
|
|
4092
|
-
/* @__PURE__ */
|
|
5433
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
5434
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
5435
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Fator de convers\xE3o" }),
|
|
5436
|
+
/* @__PURE__ */ jsx(
|
|
5437
|
+
Input,
|
|
5438
|
+
{
|
|
5439
|
+
value: d.conversionFactor,
|
|
5440
|
+
onChange: (e) => patch(item.id, { conversionFactor: e.target.value }),
|
|
5441
|
+
inputMode: "decimal",
|
|
5442
|
+
disabled: readOnly
|
|
5443
|
+
}
|
|
5444
|
+
)
|
|
5445
|
+
] }),
|
|
5446
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
5447
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Unidade de estoque" }),
|
|
5448
|
+
/* @__PURE__ */ jsx(
|
|
5449
|
+
SearchSelect,
|
|
5450
|
+
{
|
|
5451
|
+
onSearch: localSearch(unitOptions),
|
|
5452
|
+
value: d.stockUnitId ?? void 0,
|
|
5453
|
+
displayValue: labelOf(unitOptions, d.stockUnitId),
|
|
5454
|
+
onChange: (id) => patch(item.id, { stockUnitId: id || null }),
|
|
5455
|
+
minChars: 0,
|
|
5456
|
+
placeholder: "Unidade",
|
|
5457
|
+
noResultsLabel: "Nenhuma unidade",
|
|
5458
|
+
searchingLabel: "Buscando\u2026",
|
|
5459
|
+
clearLabel: "Limpar",
|
|
5460
|
+
disabled: readOnly
|
|
5461
|
+
}
|
|
5462
|
+
)
|
|
5463
|
+
] })
|
|
5464
|
+
] }),
|
|
5465
|
+
suspectFactor(item, d) && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-warning bg-warning-soft px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning-soft-foreground", children: [
|
|
5466
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
|
|
5467
|
+
"Este item vem em ",
|
|
5468
|
+
/* @__PURE__ */ jsx("strong", { children: item.unit }),
|
|
5469
|
+
", que s\xE3o",
|
|
5470
|
+
" ",
|
|
5471
|
+
qty(packagingCount(item.unit) ?? 0),
|
|
5472
|
+
" por embalagem.",
|
|
5473
|
+
" ",
|
|
5474
|
+
!d.stockUnitId ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5475
|
+
"Escolha a ",
|
|
5476
|
+
/* @__PURE__ */ jsx("strong", { children: "unidade de estoque" }),
|
|
5477
|
+
" para confirmar o fator \u2014 o produto est\xE1 cadastrado sem unidade."
|
|
5478
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5479
|
+
"O fator ainda \xE9 1: do jeito que est\xE1 entrariam ",
|
|
5480
|
+
qty(item.quantity),
|
|
5481
|
+
" ",
|
|
5482
|
+
"em vez de ",
|
|
5483
|
+
qty(item.quantity * (packagingCount(item.unit) ?? 1)),
|
|
5484
|
+
"."
|
|
5485
|
+
] })
|
|
5486
|
+
] }) }),
|
|
5487
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted/50 px-3 py-2", children: [
|
|
5488
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [
|
|
5489
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Qtd estoque (calc.)" }),
|
|
5490
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", children: qty(stockQty) })
|
|
5491
|
+
] }),
|
|
5492
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [
|
|
5493
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Novo custo unit\xE1rio" }),
|
|
5494
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money2(unitCost) })
|
|
5495
|
+
] })
|
|
5496
|
+
] }),
|
|
5497
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
4093
5498
|
/* @__PURE__ */ jsx(
|
|
4094
|
-
|
|
5499
|
+
EmBreve,
|
|
4095
5500
|
{
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
5501
|
+
label: "Criar produto daqui",
|
|
5502
|
+
o_que: "Cadastrar o produto a partir desta linha da nota, com nome, c\xF3digo e unidade j\xE1 preenchidos",
|
|
5503
|
+
quantas: "ela criou 33 produtos no V1 no meio de importa\xE7\xF5es"
|
|
5504
|
+
}
|
|
5505
|
+
),
|
|
5506
|
+
/* @__PURE__ */ jsx(
|
|
5507
|
+
EmBreve,
|
|
5508
|
+
{
|
|
5509
|
+
label: "Lote e validade",
|
|
5510
|
+
o_que: "Informar lote e vencimento desta entrada \u2014 a coluna j\xE1 existe no movimento de estoque",
|
|
5511
|
+
quantas: "usado 5 vezes no V1; numa churrascaria \xE9 validade de carne"
|
|
4105
5512
|
}
|
|
4106
5513
|
)
|
|
4107
|
-
] })
|
|
4108
|
-
] }),
|
|
4109
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted/50 px-3 py-2", children: [
|
|
4110
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [
|
|
4111
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Qtd estoque (calc.)" }),
|
|
4112
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", children: qty(stockQty) })
|
|
4113
5514
|
] }),
|
|
4114
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
4115
|
-
/* @__PURE__ */
|
|
4116
|
-
|
|
5515
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
5516
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
5517
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Modo de custo" }),
|
|
5518
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 flex gap-1", children: ["novo", "media"].map((mode) => /* @__PURE__ */ jsx(
|
|
5519
|
+
"button",
|
|
5520
|
+
{
|
|
5521
|
+
type: "button",
|
|
5522
|
+
disabled: readOnly,
|
|
5523
|
+
onClick: () => patch(item.id, { costMode: mode }),
|
|
5524
|
+
className: `flex-1 rounded-md border px-2 py-1 text-[11px] disabled:opacity-60 ${d.costMode === mode ? "border-primary bg-primary/10" : "border-input hover:bg-muted"}`,
|
|
5525
|
+
children: mode === "novo" ? "Novo" : "M\xE9dia"
|
|
5526
|
+
},
|
|
5527
|
+
mode
|
|
5528
|
+
)) })
|
|
5529
|
+
] }),
|
|
5530
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
5531
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Falta (qtd)" }),
|
|
5532
|
+
/* @__PURE__ */ jsx(
|
|
5533
|
+
Input,
|
|
5534
|
+
{
|
|
5535
|
+
value: d.shortageQuantity,
|
|
5536
|
+
onChange: (e) => patch(item.id, { shortageQuantity: e.target.value }),
|
|
5537
|
+
inputMode: "decimal",
|
|
5538
|
+
placeholder: "0",
|
|
5539
|
+
disabled: readOnly
|
|
5540
|
+
}
|
|
5541
|
+
)
|
|
5542
|
+
] })
|
|
4117
5543
|
] })
|
|
4118
5544
|
] }),
|
|
4119
|
-
/* @__PURE__ */ jsxs("
|
|
5545
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
5546
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Plano de contas" }),
|
|
4120
5547
|
/* @__PURE__ */ jsx(
|
|
4121
|
-
|
|
5548
|
+
SearchSelect,
|
|
4122
5549
|
{
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
5550
|
+
onSearch: localSearch(accountOptions),
|
|
5551
|
+
value: d.chartAccountId ?? defaultAccount ?? void 0,
|
|
5552
|
+
displayValue: labelOf(accountOptions, d.chartAccountId ?? defaultAccount),
|
|
5553
|
+
onChange: (id) => patch(item.id, { chartAccountId: id || null }),
|
|
5554
|
+
minChars: 0,
|
|
5555
|
+
placeholder: "Herda o padr\xE3o da nota",
|
|
5556
|
+
noResultsLabel: "Nenhum plano de contas",
|
|
5557
|
+
searchingLabel: "Buscando\u2026",
|
|
5558
|
+
clearLabel: "Limpar",
|
|
5559
|
+
disabled: readOnly
|
|
4126
5560
|
}
|
|
4127
|
-
)
|
|
4128
|
-
|
|
4129
|
-
|
|
5561
|
+
)
|
|
5562
|
+
] }),
|
|
5563
|
+
item.stockMovementId && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-success-soft-foreground", children: [
|
|
5564
|
+
/* @__PURE__ */ jsx(Check, { className: "mr-1 inline h-3 w-3", "aria-hidden": true }),
|
|
5565
|
+
"Esta linha j\xE1 deu entrada no estoque."
|
|
5566
|
+
] }),
|
|
5567
|
+
!readOnly && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 border-t pt-3", children: [
|
|
5568
|
+
!ready && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: !d.entryType ? "Escolha o tipo do registro" : "Vincule o produto" }),
|
|
5569
|
+
/* @__PURE__ */ jsxs(
|
|
5570
|
+
Button,
|
|
4130
5571
|
{
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
5572
|
+
size: "sm",
|
|
5573
|
+
variant: done ? "ghost" : "outline",
|
|
5574
|
+
disabled: !ready,
|
|
5575
|
+
onClick: () => done ? setChecked((prev) => ({ ...prev, [item.id]: false })) : confirmItem(item.id),
|
|
5576
|
+
children: [
|
|
5577
|
+
/* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-4 w-4" }),
|
|
5578
|
+
done ? "Conferido" : "Conferir item"
|
|
5579
|
+
]
|
|
4134
5580
|
}
|
|
4135
5581
|
)
|
|
4136
|
-
] }),
|
|
4137
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
4138
|
-
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4139
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Modo de custo" }),
|
|
4140
|
-
/* @__PURE__ */ jsx("div", { className: "mt-1 flex gap-1", children: ["novo", "media"].map((mode) => /* @__PURE__ */ jsx(
|
|
4141
|
-
"button",
|
|
4142
|
-
{
|
|
4143
|
-
type: "button",
|
|
4144
|
-
disabled: readOnly,
|
|
4145
|
-
onClick: () => patch(item.id, { costMode: mode }),
|
|
4146
|
-
className: `flex-1 rounded-md border px-2 py-1 text-[11px] disabled:opacity-60 ${d.costMode === mode ? "border-primary bg-primary/10" : "border-input hover:bg-muted"}`,
|
|
4147
|
-
children: mode === "novo" ? "Novo" : "M\xE9dia"
|
|
4148
|
-
},
|
|
4149
|
-
mode
|
|
4150
|
-
)) })
|
|
4151
|
-
] }),
|
|
4152
|
-
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4153
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Falta (qtd)" }),
|
|
4154
|
-
/* @__PURE__ */ jsx(
|
|
4155
|
-
Input,
|
|
4156
|
-
{
|
|
4157
|
-
value: d.shortageQuantity,
|
|
4158
|
-
onChange: (e) => patch(item.id, { shortageQuantity: e.target.value }),
|
|
4159
|
-
inputMode: "decimal",
|
|
4160
|
-
placeholder: "0",
|
|
4161
|
-
disabled: readOnly
|
|
4162
|
-
}
|
|
4163
|
-
)
|
|
4164
|
-
] })
|
|
4165
5582
|
] })
|
|
4166
|
-
] }),
|
|
4167
|
-
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4168
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Plano de contas" }),
|
|
4169
|
-
/* @__PURE__ */ jsx(
|
|
4170
|
-
SearchSelect,
|
|
4171
|
-
{
|
|
4172
|
-
onSearch: localSearch(accountOptions),
|
|
4173
|
-
value: d.chartAccountId ?? defaultAccount ?? void 0,
|
|
4174
|
-
displayValue: labelOf(accountOptions, d.chartAccountId ?? defaultAccount),
|
|
4175
|
-
onChange: (id) => patch(item.id, { chartAccountId: id || null }),
|
|
4176
|
-
minChars: 0,
|
|
4177
|
-
placeholder: "Herda o padr\xE3o da nota",
|
|
4178
|
-
noResultsLabel: "Nenhum plano de contas",
|
|
4179
|
-
searchingLabel: "Buscando\u2026",
|
|
4180
|
-
disabled: readOnly
|
|
4181
|
-
}
|
|
4182
|
-
)
|
|
4183
|
-
] }),
|
|
4184
|
-
item.stockMovementId && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-success-soft-foreground", children: [
|
|
4185
|
-
/* @__PURE__ */ jsx(Check, { className: "mr-1 inline h-3 w-3", "aria-hidden": true }),
|
|
4186
|
-
"Esta linha j\xE1 deu entrada no estoque."
|
|
4187
5583
|
] })
|
|
4188
5584
|
] })
|
|
4189
|
-
] }
|
|
5585
|
+
] }, item.id);
|
|
4190
5586
|
})
|
|
4191
5587
|
] }),
|
|
4192
5588
|
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
@@ -4308,51 +5704,27 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
|
4308
5704
|
)
|
|
4309
5705
|
] })
|
|
4310
5706
|
] }),
|
|
4311
|
-
error && /* @__PURE__ */ jsx(Card, { className: "border-destructive bg-destructive/5 p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }) })
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
),
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
missingProduct > 0 && `${missingProduct} sem produto`
|
|
4333
|
-
] }),
|
|
4334
|
-
/* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => {
|
|
4335
|
-
void save();
|
|
4336
|
-
}, disabled: saving || accepting, children: [
|
|
4337
|
-
saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }),
|
|
4338
|
-
"Salvar concilia\xE7\xE3o"
|
|
4339
|
-
] }),
|
|
4340
|
-
/* @__PURE__ */ jsxs(
|
|
4341
|
-
Button,
|
|
4342
|
-
{
|
|
4343
|
-
onClick: () => {
|
|
4344
|
-
void accept();
|
|
4345
|
-
},
|
|
4346
|
-
disabled: accepting || saving || missingType > 0 || missingProduct > 0,
|
|
4347
|
-
children: [
|
|
4348
|
-
accepting ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-4 w-4" }),
|
|
4349
|
-
"Aceitar"
|
|
4350
|
-
]
|
|
4351
|
-
}
|
|
4352
|
-
)
|
|
4353
|
-
] })
|
|
4354
|
-
] })
|
|
4355
|
-
] });
|
|
5707
|
+
error && /* @__PURE__ */ jsx(Card, { className: "border-destructive bg-destructive/5 p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }) })
|
|
5708
|
+
] }), `Concilia\xE7\xE3o \xB7 NF-e ${note.number}`, rodape);
|
|
5709
|
+
}
|
|
5710
|
+
function AuthorBadge({ draft }) {
|
|
5711
|
+
if (draft.suggestedBy === "ai") {
|
|
5712
|
+
const confira = draft.suggestionConfidence != null && draft.suggestionConfidence < 0.6;
|
|
5713
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center rounded-full bg-magic-soft px-2 py-0.5 text-[11px] font-medium text-magic-soft-foreground", children: [
|
|
5714
|
+
"IA",
|
|
5715
|
+
confira ? " \xB7 confira" : ""
|
|
5716
|
+
] });
|
|
5717
|
+
}
|
|
5718
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0 items-center rounded-full bg-info-soft px-2 py-0.5 text-[11px] font-medium text-info-soft-foreground", children: draft.suggestedBy === "ean" ? "EAN" : "fornecedor" });
|
|
5719
|
+
}
|
|
5720
|
+
function ItemState({ done, ready }) {
|
|
5721
|
+
if (done) {
|
|
5722
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center rounded-full bg-success-soft px-2 py-0.5 text-[11px] font-medium text-success-soft-foreground", children: [
|
|
5723
|
+
/* @__PURE__ */ jsx(Check, { className: "mr-1 h-3 w-3", "aria-hidden": true }),
|
|
5724
|
+
"Conferido"
|
|
5725
|
+
] });
|
|
5726
|
+
}
|
|
5727
|
+
return /* @__PURE__ */ jsx("span", { className: `inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${ready ? "bg-muted text-muted-foreground" : "bg-warning-soft text-warning-soft-foreground"}`, children: ready ? "A conferir" : "Falta decidir" });
|
|
4356
5728
|
}
|
|
4357
5729
|
function Field({ label, value, mono, small }) {
|
|
4358
5730
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -4367,7 +5739,7 @@ var MANIFEST_TONE = {
|
|
|
4367
5739
|
processando: "bg-info-soft text-info-soft-foreground",
|
|
4368
5740
|
erro: "bg-destructive/10 text-destructive"
|
|
4369
5741
|
};
|
|
4370
|
-
function ManifestView({ onOpenNote }) {
|
|
5742
|
+
function ManifestView({ onOpenNote, onBack }) {
|
|
4371
5743
|
const [notes, setNotes] = useState([]);
|
|
4372
5744
|
const [loading, setLoading] = useState(true);
|
|
4373
5745
|
const [tab, setTab] = useState("pendentes");
|
|
@@ -4403,6 +5775,7 @@ function ManifestView({ onOpenNote }) {
|
|
|
4403
5775
|
]
|
|
4404
5776
|
}], [tab]);
|
|
4405
5777
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
5778
|
+
onBack && /* @__PURE__ */ jsx(SubpageHeader, { title: "Fila da SEFAZ", onBack, parentLabel: "Manifesto de Notas" }),
|
|
4406
5779
|
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
4407
5780
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
4408
5781
|
/* @__PURE__ */ jsx(ShieldCheck, { className: "mt-0.5 h-5 w-5 text-muted-foreground", "aria-hidden": true }),
|
|
@@ -4468,17 +5841,33 @@ function viewFromLocation() {
|
|
|
4468
5841
|
return { kind: "list" };
|
|
4469
5842
|
}
|
|
4470
5843
|
var pathOf = (view) => view.kind === "note" ? `${HASH_PREFIX}/note/${view.id}` : view.kind === "manifest" ? `${HASH_PREFIX}/manifest` : HASH_PREFIX;
|
|
5844
|
+
var STACK_MARK = { inboundNotes: true };
|
|
4471
5845
|
function InboundNotesPage({ payableRoute } = {}) {
|
|
4472
5846
|
const [view, setView] = useState(viewFromLocation);
|
|
5847
|
+
const [background, setBackground] = useState(
|
|
5848
|
+
() => viewFromLocation().kind === "manifest" ? "manifest" : "list"
|
|
5849
|
+
);
|
|
4473
5850
|
const go = useCallback((next) => {
|
|
4474
5851
|
setView(next);
|
|
4475
|
-
if (
|
|
4476
|
-
|
|
5852
|
+
if (next.kind !== "note") setBackground(next.kind);
|
|
5853
|
+
if (typeof window === "undefined") return;
|
|
5854
|
+
const path = pathOf(next);
|
|
5855
|
+
if (window.location.hash === path) return;
|
|
5856
|
+
const empilhado = window.history.state?.inboundNotes;
|
|
5857
|
+
if (next.kind !== "note" && empilhado) {
|
|
5858
|
+
window.history.back();
|
|
5859
|
+
return;
|
|
4477
5860
|
}
|
|
5861
|
+
if (next.kind === "list") window.history.replaceState(null, "", path);
|
|
5862
|
+
else window.history.pushState(STACK_MARK, "", path);
|
|
4478
5863
|
}, []);
|
|
4479
5864
|
useEffect(() => {
|
|
4480
5865
|
if (typeof window === "undefined") return;
|
|
4481
|
-
const onPop = () =>
|
|
5866
|
+
const onPop = () => {
|
|
5867
|
+
const next = viewFromLocation();
|
|
5868
|
+
setView(next);
|
|
5869
|
+
if (next.kind !== "note") setBackground(next.kind);
|
|
5870
|
+
};
|
|
4482
5871
|
window.addEventListener("popstate", onPop);
|
|
4483
5872
|
window.addEventListener("hashchange", onPop);
|
|
4484
5873
|
return () => {
|
|
@@ -4486,34 +5875,436 @@ function InboundNotesPage({ payableRoute } = {}) {
|
|
|
4486
5875
|
window.removeEventListener("hashchange", onPop);
|
|
4487
5876
|
};
|
|
4488
5877
|
}, []);
|
|
4489
|
-
|
|
4490
|
-
|
|
5878
|
+
const fecharNota = useCallback(() => go({ kind: background }), [go, background]);
|
|
5879
|
+
useEffect(() => {
|
|
5880
|
+
if (typeof window === "undefined") return;
|
|
5881
|
+
const op = window.location.hash.match(/[?&]operation=([0-9a-f-]{36})/i)?.[1];
|
|
5882
|
+
if (!op) return;
|
|
5883
|
+
let vivo = true;
|
|
5884
|
+
findInboxNoteByOperation(op).then((id) => {
|
|
5885
|
+
if (vivo && id) go({ kind: "note", id });
|
|
5886
|
+
}).catch(() => {
|
|
5887
|
+
});
|
|
5888
|
+
return () => {
|
|
5889
|
+
vivo = false;
|
|
5890
|
+
};
|
|
5891
|
+
}, [go]);
|
|
5892
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5893
|
+
background === "manifest" ? /* @__PURE__ */ jsx(ManifestView, { onOpenNote: (id) => go({ kind: "note", id }), onBack: () => go({ kind: "list" }) }) : /* @__PURE__ */ jsx(
|
|
5894
|
+
InboundNotesView,
|
|
5895
|
+
{
|
|
5896
|
+
onOpenNote: (id) => go({ kind: "note", id }),
|
|
5897
|
+
onOpenManifest: () => go({ kind: "manifest" })
|
|
5898
|
+
}
|
|
5899
|
+
),
|
|
5900
|
+
/* @__PURE__ */ jsx(Modal, { open: view.kind === "note", onOpenChange: (open) => {
|
|
5901
|
+
if (!open) fecharNota();
|
|
5902
|
+
}, children: view.kind === "note" && // `noPadding`: a conciliação desenha o próprio cabeçalho e o próprio
|
|
5903
|
+
// corpo — o título e o «Sugerir com IA» ficam fora da área que rola, e o
|
|
5904
|
+
// rodapé «Aceitar» precisa encostar nas bordas do modal.
|
|
5905
|
+
/* @__PURE__ */ jsx(ModalContent, { size: "5xl", noPadding: true, className: "sm:max-h-[90vh]", children: /* @__PURE__ */ jsx(
|
|
4491
5906
|
NoteReconcileView,
|
|
4492
5907
|
{
|
|
5908
|
+
chrome: "modal",
|
|
4493
5909
|
noteId: view.id,
|
|
4494
|
-
onBack:
|
|
5910
|
+
onBack: fecharNota,
|
|
4495
5911
|
onOpenInvoice: payableRoute ? (invoiceId) => {
|
|
4496
5912
|
window.location.hash = payableRoute(invoiceId);
|
|
4497
5913
|
} : void 0
|
|
4498
5914
|
}
|
|
4499
|
-
)
|
|
4500
|
-
}
|
|
4501
|
-
if (view.kind === "manifest") {
|
|
4502
|
-
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(ManifestView, { onOpenNote: (id) => go({ kind: "note", id }) }) });
|
|
4503
|
-
}
|
|
4504
|
-
return /* @__PURE__ */ jsx(
|
|
4505
|
-
InboundNotesView,
|
|
4506
|
-
{
|
|
4507
|
-
onOpenNote: (id) => go({ kind: "note", id }),
|
|
4508
|
-
onOpenManifest: () => go({ kind: "manifest" })
|
|
4509
|
-
}
|
|
4510
|
-
);
|
|
5915
|
+
) }) })
|
|
5916
|
+
] });
|
|
4511
5917
|
}
|
|
4512
5918
|
function makeInboundNotesRoute(options = {}) {
|
|
4513
5919
|
return function InboundNotesRoute() {
|
|
4514
5920
|
return /* @__PURE__ */ jsx(InboundNotesPage, { ...options });
|
|
4515
5921
|
};
|
|
4516
5922
|
}
|
|
5923
|
+
var COLUMNS = [
|
|
5924
|
+
"id",
|
|
5925
|
+
"legal_entity_id",
|
|
5926
|
+
"nfse_enabled",
|
|
5927
|
+
"nfse_production",
|
|
5928
|
+
"nfse_national",
|
|
5929
|
+
"nfse_service_code_national",
|
|
5930
|
+
"nfse_service_code_municipal",
|
|
5931
|
+
"nfse_nbs_code",
|
|
5932
|
+
"nfse_cnae",
|
|
5933
|
+
"nfse_iss_rate",
|
|
5934
|
+
"nfse_iss_taxation",
|
|
5935
|
+
"nfse_ibscbs_cst",
|
|
5936
|
+
"nfse_ibscbs_classification",
|
|
5937
|
+
"nfse_simples_regime",
|
|
5938
|
+
"nfse_net_value",
|
|
5939
|
+
"nfse_additional_info",
|
|
5940
|
+
"nfce_enabled",
|
|
5941
|
+
"nfce_production",
|
|
5942
|
+
"nfce_series",
|
|
5943
|
+
"nfce_csc_id",
|
|
5944
|
+
"nfce_csc_token_set",
|
|
5945
|
+
"nfce_generic_item",
|
|
5946
|
+
"certificate_cn",
|
|
5947
|
+
"certificate_expires_at",
|
|
5948
|
+
"certificate_uploaded_at",
|
|
5949
|
+
"dfe_sync_enabled",
|
|
5950
|
+
"dfe_last_sync_at",
|
|
5951
|
+
"provider_synced_at"
|
|
5952
|
+
].join(", ");
|
|
5953
|
+
var sb2 = () => getSupabaseClientOptional();
|
|
5954
|
+
function useEmitterSettings(legalEntityId) {
|
|
5955
|
+
const [row, setRow] = React5.useState(null);
|
|
5956
|
+
const [loading, setLoading] = React5.useState(true);
|
|
5957
|
+
React5.useEffect(() => {
|
|
5958
|
+
if (!legalEntityId) {
|
|
5959
|
+
setLoading(false);
|
|
5960
|
+
return;
|
|
5961
|
+
}
|
|
5962
|
+
let alive = true;
|
|
5963
|
+
void sb2()?.from("plg_fiscalbr_emitter_settings").select(COLUMNS).eq("legal_entity_id", legalEntityId).maybeSingle().then(({ data, error }) => {
|
|
5964
|
+
if (!alive) return;
|
|
5965
|
+
if (error) toast.error("N\xE3o foi poss\xEDvel ler a configura\xE7\xE3o fiscal", { description: error.message });
|
|
5966
|
+
setRow(data ?? {});
|
|
5967
|
+
setLoading(false);
|
|
5968
|
+
});
|
|
5969
|
+
return () => {
|
|
5970
|
+
alive = false;
|
|
5971
|
+
};
|
|
5972
|
+
}, [legalEntityId]);
|
|
5973
|
+
const save = React5.useCallback(async (tenantId, patch) => {
|
|
5974
|
+
if (!legalEntityId) return false;
|
|
5975
|
+
const payload = { ...patch, tenant_id: tenantId, legal_entity_id: legalEntityId, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5976
|
+
const { data, error } = await sb2().from("plg_fiscalbr_emitter_settings").upsert(payload, { onConflict: "legal_entity_id" }).select(COLUMNS).single();
|
|
5977
|
+
if (error) {
|
|
5978
|
+
toast.error("N\xE3o foi poss\xEDvel salvar", { description: error.message });
|
|
5979
|
+
return false;
|
|
5980
|
+
}
|
|
5981
|
+
setRow(data);
|
|
5982
|
+
return true;
|
|
5983
|
+
}, [legalEntityId]);
|
|
5984
|
+
return { row, loading, save };
|
|
5985
|
+
}
|
|
5986
|
+
function NeedsLegalEntity({ item, what }) {
|
|
5987
|
+
return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col items-start gap-3 py-6", children: [
|
|
5988
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Falta o CNPJ desta unidade" }),
|
|
5989
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
5990
|
+
what,
|
|
5991
|
+
" pertence \xE0 pessoa jur\xEDdica. Preencha a raz\xE3o social e o CNPJ em Dados da empresa para configurar aqui."
|
|
5992
|
+
] }),
|
|
5993
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => adminNavigateTo(`/settings/locations/${item.id}/edit`), children: "Preencher CNPJ" })
|
|
5994
|
+
] }) });
|
|
5995
|
+
}
|
|
5996
|
+
function LoadingCard() {
|
|
5997
|
+
return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "space-y-3 py-6", children: [
|
|
5998
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-48" }),
|
|
5999
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" }),
|
|
6000
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" })
|
|
6001
|
+
] }) });
|
|
6002
|
+
}
|
|
6003
|
+
function ToggleRow({ title, description, checked, onChange, disabled, children }) {
|
|
6004
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
|
|
6005
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
6006
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: title }),
|
|
6007
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: description })
|
|
6008
|
+
] }),
|
|
6009
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
6010
|
+
children,
|
|
6011
|
+
/* @__PURE__ */ jsx(Switch, { checked, disabled, onCheckedChange: onChange })
|
|
6012
|
+
] })
|
|
6013
|
+
] });
|
|
6014
|
+
}
|
|
6015
|
+
function Field2({ label, hint, children }) {
|
|
6016
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
6017
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: label }),
|
|
6018
|
+
children,
|
|
6019
|
+
hint && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hint })
|
|
6020
|
+
] });
|
|
6021
|
+
}
|
|
6022
|
+
function SummaryCard({ title, lines, badge, onConfigure }) {
|
|
6023
|
+
return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-start justify-between gap-3 space-y-0", children: [
|
|
6024
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
6025
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: title }),
|
|
6026
|
+
/* @__PURE__ */ jsx("dl", { className: "mt-2 grid gap-x-6 gap-y-1 text-sm", style: { gridTemplateColumns: "auto 1fr" }, children: lines.map((l) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
|
|
6027
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: l.label }),
|
|
6028
|
+
/* @__PURE__ */ jsx("dd", { className: "text-foreground", children: l.value })
|
|
6029
|
+
] }, l.label)) })
|
|
6030
|
+
] }),
|
|
6031
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
6032
|
+
badge && /* @__PURE__ */ jsx(
|
|
6033
|
+
Badge,
|
|
6034
|
+
{
|
|
6035
|
+
variant: badge.tone === "bad" ? "destructive" : "outline",
|
|
6036
|
+
className: badge.tone === "ok" ? "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400" : badge.tone === "muted" ? "text-muted-foreground" : void 0,
|
|
6037
|
+
children: badge.text
|
|
6038
|
+
}
|
|
6039
|
+
),
|
|
6040
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onConfigure, children: "Configurar" })
|
|
6041
|
+
] })
|
|
6042
|
+
] }) });
|
|
6043
|
+
}
|
|
6044
|
+
var fmtDateTime = (v) => v ? new Date(v).toLocaleString("pt-BR") : null;
|
|
6045
|
+
function UnitCertificateWidget({ item }) {
|
|
6046
|
+
const { row, loading, save } = useEmitterSettings(item?.legalEntityId);
|
|
6047
|
+
const [open, setOpen] = React5.useState(false);
|
|
6048
|
+
if (!item?.id) return null;
|
|
6049
|
+
if (!item.legalEntityId) return /* @__PURE__ */ jsx(NeedsLegalEntity, { item, what: "O certificado digital" });
|
|
6050
|
+
if (loading || !row) return /* @__PURE__ */ jsx(LoadingCard, {});
|
|
6051
|
+
const expires = row.certificate_expires_at ? new Date(row.certificate_expires_at) : null;
|
|
6052
|
+
const expired = expires ? expires.getTime() < Date.now() : false;
|
|
6053
|
+
const hasCertificate = !!row.certificate_uploaded_at;
|
|
6054
|
+
if (!open) {
|
|
6055
|
+
return /* @__PURE__ */ jsx(
|
|
6056
|
+
SummaryCard,
|
|
6057
|
+
{
|
|
6058
|
+
title: "Certificado digital e DANFes",
|
|
6059
|
+
lines: [
|
|
6060
|
+
{ label: "Certificado A1", value: hasCertificate ? [row.certificate_cn, expires ? `v\xE1lido at\xE9 ${expires.toLocaleDateString("pt-BR")}` : null].filter(Boolean).join(" \xB7 ") || "Enviado" : "Nenhum certificado enviado" },
|
|
6061
|
+
{ label: "Receber DANFes", value: row.dfe_sync_enabled ? `Ligado \xB7 \xFAltima sincroniza\xE7\xE3o ${fmtDateTime(row.dfe_last_sync_at) ?? "nunca"}` : "Desligado" }
|
|
6062
|
+
],
|
|
6063
|
+
badge: hasCertificate ? { text: expired ? "Vencido" : "Ativo", tone: expired ? "bad" : "ok" } : { text: "Sem certificado", tone: "muted" },
|
|
6064
|
+
onConfigure: () => setOpen(true)
|
|
6065
|
+
}
|
|
6066
|
+
);
|
|
6067
|
+
}
|
|
6068
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
6069
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-start justify-between gap-3 space-y-0", children: [
|
|
6070
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Certificado digital e DANFes" }),
|
|
6071
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setOpen(false), children: "Fechar" })
|
|
6072
|
+
] }),
|
|
6073
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "space-y-5", children: [
|
|
6074
|
+
/* @__PURE__ */ jsx(
|
|
6075
|
+
ToggleRow,
|
|
6076
|
+
{
|
|
6077
|
+
title: "Receber DANFes automaticamente",
|
|
6078
|
+
description: "Busca por hora as NF-e emitidas contra o CNPJ desta empresa.",
|
|
6079
|
+
checked: !!row.dfe_sync_enabled,
|
|
6080
|
+
onChange: (v) => void save(item.tenantId, { dfe_sync_enabled: v }).then((ok) => ok && toast.success(v ? "Recep\xE7\xE3o ligada" : "Recep\xE7\xE3o desligada"))
|
|
6081
|
+
}
|
|
6082
|
+
),
|
|
6083
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/40 p-3 text-sm", children: [
|
|
6084
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-1.5 flex items-center gap-1.5 font-medium", children: [
|
|
6085
|
+
/* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }),
|
|
6086
|
+
" Como funciona"
|
|
6087
|
+
] }),
|
|
6088
|
+
/* @__PURE__ */ jsxs("ul", { className: "ml-5 list-disc space-y-1 text-muted-foreground", children: [
|
|
6089
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
6090
|
+
/* @__PURE__ */ jsx("strong", { className: "text-foreground", children: "O que faz:" }),
|
|
6091
|
+
" consulta a SEFAZ e lista as NF-e emitidas contra o CNPJ, para voc\xEA aceitar ou rejeitar e gerar entrada de estoque ou conta a pagar."
|
|
6092
|
+
] }),
|
|
6093
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
6094
|
+
/* @__PURE__ */ jsx("strong", { className: "text-foreground", children: "Requer certificado digital A1" }),
|
|
6095
|
+
" (.pfx) v\xE1lido desta empresa. O arquivo vai direto ao servi\xE7o fiscal e n\xE3o fica armazenado no nosso sistema."
|
|
6096
|
+
] }),
|
|
6097
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
6098
|
+
/* @__PURE__ */ jsx("strong", { className: "text-foreground", children: "N\xE3o faz manifesta\xE7\xE3o na SEFAZ" }),
|
|
6099
|
+
" (ci\xEAncia, confirma\xE7\xE3o, desconhecimento) nem emite NF-e."
|
|
6100
|
+
] })
|
|
6101
|
+
] })
|
|
6102
|
+
] }),
|
|
6103
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [
|
|
6104
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
6105
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
6106
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-sm font-medium", children: [
|
|
6107
|
+
/* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4" }),
|
|
6108
|
+
" Certificado digital A1"
|
|
6109
|
+
] }),
|
|
6110
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hasCertificate ? [row.certificate_cn, expires ? `v\xE1lido at\xE9 ${expires.toLocaleDateString("pt-BR")}` : null].filter(Boolean).join(" \xB7 ") : "Nenhum certificado enviado" })
|
|
6111
|
+
] }),
|
|
6112
|
+
hasCertificate && /* @__PURE__ */ jsx(Badge, { variant: expired ? "destructive" : "secondary", children: expired ? "Vencido" : "Ativo" })
|
|
6113
|
+
] }),
|
|
6114
|
+
/* @__PURE__ */ jsx("div", { className: "fz-container", children: /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
|
|
6115
|
+
/* @__PURE__ */ jsx(Field2, { label: "Arquivo (.pfx)", children: /* @__PURE__ */ jsx(Input, { type: "file", accept: ".pfx,.p12", disabled: true }) }),
|
|
6116
|
+
/* @__PURE__ */ jsx(Field2, { label: "Senha do certificado", children: /* @__PURE__ */ jsx(Input, { type: "password", disabled: true }) })
|
|
6117
|
+
] }) }),
|
|
6118
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "O envio do certificado ainda n\xE3o est\xE1 dispon\xEDvel nesta vers\xE3o: falta a fun\xE7\xE3o no servidor que repassa o arquivo ao PlugNotas sem guard\xE1-lo." })
|
|
6119
|
+
] }),
|
|
6120
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
6121
|
+
"\xDAltima sincroniza\xE7\xE3o: ",
|
|
6122
|
+
fmtDateTime(row.dfe_last_sync_at) ?? "nunca"
|
|
6123
|
+
] })
|
|
6124
|
+
] })
|
|
6125
|
+
] });
|
|
6126
|
+
}
|
|
6127
|
+
var ISS_TAXATION = [
|
|
6128
|
+
{ value: "6", label: "Tribut\xE1vel no munic\xEDpio" },
|
|
6129
|
+
{ value: "7", label: "Tribut\xE1vel fora do munic\xEDpio" },
|
|
6130
|
+
{ value: "1", label: "Isento" },
|
|
6131
|
+
{ value: "2", label: "Imune" }
|
|
6132
|
+
];
|
|
6133
|
+
var SIMPLES_REGIME = [
|
|
6134
|
+
{ value: "auto", label: "Autom\xE1tico (1 \u2014 tudo pelo Simples; excesso \u2192 3)" },
|
|
6135
|
+
{ value: "1", label: "1 \u2014 Federais e ISS pelo Simples (DAS)" },
|
|
6136
|
+
{ value: "2", label: "2 \u2014 Federais pelo Simples; ISS pela NFS-e (lei municipal)" },
|
|
6137
|
+
{ value: "3", label: "3 \u2014 Federais e ISS fora do Simples (sublimite)" }
|
|
6138
|
+
];
|
|
6139
|
+
function EnvSelect({ value, onChange, disabled }) {
|
|
6140
|
+
return /* @__PURE__ */ jsxs(Select, { value: value ? "prod" : "hom", onValueChange: (v) => onChange(v === "prod"), disabled, children: [
|
|
6141
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-36", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
6142
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
6143
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "hom", children: "Homologa\xE7\xE3o" }),
|
|
6144
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "prod", children: "Produ\xE7\xE3o" })
|
|
6145
|
+
] })
|
|
6146
|
+
] });
|
|
6147
|
+
}
|
|
6148
|
+
function UnitFiscalWidget({ item }) {
|
|
6149
|
+
const { row, loading, save } = useEmitterSettings(item?.legalEntityId);
|
|
6150
|
+
const [draft, setDraft] = React5.useState({});
|
|
6151
|
+
const [cscToken, setCscToken] = React5.useState("");
|
|
6152
|
+
const [saving, setSaving] = React5.useState(false);
|
|
6153
|
+
const [open, setOpen] = React5.useState(false);
|
|
6154
|
+
React5.useEffect(() => {
|
|
6155
|
+
if (row) setDraft(row);
|
|
6156
|
+
}, [row]);
|
|
6157
|
+
if (!item?.id) return null;
|
|
6158
|
+
if (!item.legalEntityId) return /* @__PURE__ */ jsx(NeedsLegalEntity, { item, what: "A emiss\xE3o fiscal" });
|
|
6159
|
+
if (loading || !row) return /* @__PURE__ */ jsx(LoadingCard, {});
|
|
6160
|
+
const set = (patch) => setDraft((d) => ({ ...d, ...patch }));
|
|
6161
|
+
const generic = draft.nfce_generic_item ?? {};
|
|
6162
|
+
const setGeneric = (patch) => set({ nfce_generic_item: { ...generic, ...patch } });
|
|
6163
|
+
const isSimples = (item.taxRegime ?? "").startsWith("simples");
|
|
6164
|
+
const checks = [
|
|
6165
|
+
{ ok: !!row.certificate_uploaded_at && !(row.certificate_expires_at && Date.parse(row.certificate_expires_at) < Date.now()), label: "Certificado A1 v\xE1lido" },
|
|
6166
|
+
// Only what the switched-on documents need: NFS-e off does not block a restaurant that issues NFC-e.
|
|
6167
|
+
...draft.nfse_enabled ? [
|
|
6168
|
+
{ ok: !!draft.nfse_service_code_national, label: "C\xF3digo de tributa\xE7\xE3o do servi\xE7o" },
|
|
6169
|
+
{ ok: draft.nfse_iss_rate != null && draft.nfse_iss_rate !== "", label: "Al\xEDquota de ISS" }
|
|
6170
|
+
] : [],
|
|
6171
|
+
...draft.nfce_enabled ? [
|
|
6172
|
+
{ ok: !!item.stateRegistration && !!draft.nfce_csc_id && (!!row.nfce_csc_token_set || !!cscToken), label: "IE + CSC (NFC-e)" }
|
|
6173
|
+
] : [],
|
|
6174
|
+
{ ok: !!row.provider_synced_at, label: "Empresa registrada para emitir" }
|
|
6175
|
+
];
|
|
6176
|
+
const ready = (draft.nfse_enabled || draft.nfce_enabled) && checks.every((c) => c.ok);
|
|
6177
|
+
const onSave = async () => {
|
|
6178
|
+
setSaving(true);
|
|
6179
|
+
const {
|
|
6180
|
+
id: _id,
|
|
6181
|
+
legal_entity_id: _le,
|
|
6182
|
+
nfce_csc_token_set: _t,
|
|
6183
|
+
certificate_cn: _cn,
|
|
6184
|
+
certificate_expires_at: _e,
|
|
6185
|
+
certificate_uploaded_at: _u,
|
|
6186
|
+
dfe_last_sync_at: _d,
|
|
6187
|
+
provider_synced_at: _p,
|
|
6188
|
+
...patch
|
|
6189
|
+
} = draft;
|
|
6190
|
+
const issRate = patch.nfse_iss_rate === "" || patch.nfse_iss_rate == null ? null : Number(String(patch.nfse_iss_rate).replace(",", "."));
|
|
6191
|
+
const ok = await save(item.tenantId, {
|
|
6192
|
+
...patch,
|
|
6193
|
+
nfse_iss_rate: Number.isFinite(issRate) ? issRate : null,
|
|
6194
|
+
nfse_iss_taxation: patch.nfse_iss_taxation ? Number(patch.nfse_iss_taxation) : null,
|
|
6195
|
+
nfse_simples_regime: patch.nfse_simples_regime && patch.nfse_simples_regime !== "auto" ? Number(patch.nfse_simples_regime) : null,
|
|
6196
|
+
nfce_series: patch.nfce_series ? Number(patch.nfce_series) : null,
|
|
6197
|
+
...cscToken ? { nfce_csc_token: cscToken } : {}
|
|
6198
|
+
});
|
|
6199
|
+
setSaving(false);
|
|
6200
|
+
if (ok) {
|
|
6201
|
+
setCscToken("");
|
|
6202
|
+
setOpen(false);
|
|
6203
|
+
toast.success("Configura\xE7\xE3o fiscal salva");
|
|
6204
|
+
}
|
|
6205
|
+
};
|
|
6206
|
+
if (!open) {
|
|
6207
|
+
const env = (prod) => prod ? "Produ\xE7\xE3o" : "Homologa\xE7\xE3o";
|
|
6208
|
+
return /* @__PURE__ */ jsx(
|
|
6209
|
+
SummaryCard,
|
|
6210
|
+
{
|
|
6211
|
+
title: "Emiss\xE3o fiscal (NFS-e / NFC-e)",
|
|
6212
|
+
lines: [
|
|
6213
|
+
{ label: "NFS-e", value: row.nfse_enabled ? `Ligada \xB7 ${env(!!row.nfse_production)}` : "Desligada" },
|
|
6214
|
+
{ label: "NFC-e", value: row.nfce_enabled ? `Ligada \xB7 ${env(!!row.nfce_production)}${row.nfce_series ? ` \xB7 s\xE9rie ${row.nfce_series}` : ""}` : "Desligada" }
|
|
6215
|
+
],
|
|
6216
|
+
badge: ready ? { text: "Pronto para emitir", tone: "ok" } : { text: "Configura\xE7\xE3o incompleta", tone: "muted" },
|
|
6217
|
+
onConfigure: () => setOpen(true)
|
|
6218
|
+
}
|
|
6219
|
+
);
|
|
6220
|
+
}
|
|
6221
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
6222
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-start justify-between gap-3 space-y-0", children: [
|
|
6223
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
6224
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Emiss\xE3o fiscal (NFS-e / NFC-e)" }),
|
|
6225
|
+
/* @__PURE__ */ jsxs(CardDescription, { children: [
|
|
6226
|
+
item.legalName ?? "Pessoa jur\xEDdica",
|
|
6227
|
+
item.cnpj ? ` \xB7 CNPJ ${maskCpfCnpj(item.cnpj)}` : ""
|
|
6228
|
+
] })
|
|
6229
|
+
] }),
|
|
6230
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
6231
|
+
/* @__PURE__ */ jsx(Badge, { variant: ready ? "default" : "secondary", children: ready ? "Pronto para emitir" : "Configura\xE7\xE3o incompleta" }),
|
|
6232
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
6233
|
+
setDraft(row);
|
|
6234
|
+
setCscToken("");
|
|
6235
|
+
setOpen(false);
|
|
6236
|
+
}, children: "Cancelar" })
|
|
6237
|
+
] })
|
|
6238
|
+
] }),
|
|
6239
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "space-y-5", children: [
|
|
6240
|
+
/* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-md border p-4", children: [
|
|
6241
|
+
/* @__PURE__ */ jsx(
|
|
6242
|
+
ToggleRow,
|
|
6243
|
+
{
|
|
6244
|
+
title: "NFS-e \u2014 Nota de Servi\xE7o",
|
|
6245
|
+
description: "Padr\xE3o nacional (RJ, Vit\xF3ria e demais aderentes)",
|
|
6246
|
+
checked: !!draft.nfse_enabled,
|
|
6247
|
+
onChange: (v) => set({ nfse_enabled: v }),
|
|
6248
|
+
children: /* @__PURE__ */ jsx(EnvSelect, { value: !!draft.nfse_production, onChange: (v) => set({ nfse_production: v }), disabled: !draft.nfse_enabled })
|
|
6249
|
+
}
|
|
6250
|
+
),
|
|
6251
|
+
draft.nfse_enabled && /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
|
|
6252
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
|
|
6253
|
+
/* @__PURE__ */ jsx(Field2, { label: "C\xF3d. tributa\xE7\xE3o nacional", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_service_code_national ?? "", placeholder: "ex.: 06.02.01", onChange: (e) => set({ nfse_service_code_national: e.target.value }) }) }),
|
|
6254
|
+
/* @__PURE__ */ jsx(Field2, { label: "C\xF3d. tributa\xE7\xE3o municipal", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_service_code_municipal ?? "", placeholder: "ex.: 001", onChange: (e) => set({ nfse_service_code_municipal: e.target.value }) }) }),
|
|
6255
|
+
/* @__PURE__ */ jsx(Field2, { label: "C\xF3digo NBS", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_nbs_code ?? "", placeholder: "ex.: 1.2602.90.00", onChange: (e) => set({ nfse_nbs_code: e.target.value }) }) }),
|
|
6256
|
+
/* @__PURE__ */ jsx(Field2, { label: "CNAE do servi\xE7o", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_cnae ?? "", placeholder: "ex.: 9602-5/02", onChange: (e) => set({ nfse_cnae: e.target.value }) }) }),
|
|
6257
|
+
/* @__PURE__ */ jsx(Field2, { label: "Al\xEDquota ISS (%)", children: /* @__PURE__ */ jsx(Input, { inputMode: "decimal", value: draft.nfse_iss_rate ?? "", placeholder: "ex.: 5", onChange: (e) => set({ nfse_iss_rate: e.target.value }) }) }),
|
|
6258
|
+
/* @__PURE__ */ jsx(Field2, { label: "Tributa\xE7\xE3o ISS", children: /* @__PURE__ */ jsxs(Select, { value: draft.nfse_iss_taxation != null ? String(draft.nfse_iss_taxation) : void 0, onValueChange: (v) => set({ nfse_iss_taxation: v }), children: [
|
|
6259
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Selecione" }) }),
|
|
6260
|
+
/* @__PURE__ */ jsx(SelectContent, { children: ISS_TAXATION.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
|
|
6261
|
+
] }) }),
|
|
6262
|
+
/* @__PURE__ */ jsx(Field2, { label: "IBS/CBS \u2014 CST", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_ibscbs_cst ?? "", placeholder: "ex.: 000", onChange: (e) => set({ nfse_ibscbs_cst: e.target.value }) }) }),
|
|
6263
|
+
/* @__PURE__ */ jsx(Field2, { label: "IBS/CBS \u2014 Classifica\xE7\xE3o", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_ibscbs_classification ?? "", placeholder: "ex.: 000001", onChange: (e) => set({ nfse_ibscbs_classification: e.target.value }) }) }),
|
|
6264
|
+
isSimples && /* @__PURE__ */ jsx(Field2, { label: "Apura\xE7\xE3o dos tributos no Simples", children: /* @__PURE__ */ jsxs(Select, { value: draft.nfse_simples_regime != null ? String(draft.nfse_simples_regime) : "auto", onValueChange: (v) => set({ nfse_simples_regime: v }), children: [
|
|
6265
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
6266
|
+
/* @__PURE__ */ jsx(SelectContent, { children: SIMPLES_REGIME.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
|
|
6267
|
+
] }) })
|
|
6268
|
+
] }),
|
|
6269
|
+
/* @__PURE__ */ jsx(ToggleRow, { title: "Emitir pelo valor l\xEDquido", checked: !!draft.nfse_net_value, onChange: (v) => set({ nfse_net_value: v }) }),
|
|
6270
|
+
/* @__PURE__ */ jsx(Field2, { label: "Informa\xE7\xF5es complementares da nota", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_additional_info ?? "", placeholder: "ex.: Totais Aproximados dos Tributos cfe. Lei n\xBA 12.741/2012\u2026", onChange: (e) => set({ nfse_additional_info: e.target.value }) }) })
|
|
6271
|
+
] })
|
|
6272
|
+
] }),
|
|
6273
|
+
/* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-md border p-4", children: [
|
|
6274
|
+
/* @__PURE__ */ jsx(
|
|
6275
|
+
ToggleRow,
|
|
6276
|
+
{
|
|
6277
|
+
title: "NFC-e \u2014 Cupom do Consumidor",
|
|
6278
|
+
description: "Produtos / comanda",
|
|
6279
|
+
checked: !!draft.nfce_enabled,
|
|
6280
|
+
onChange: (v) => set({ nfce_enabled: v }),
|
|
6281
|
+
children: /* @__PURE__ */ jsx(EnvSelect, { value: !!draft.nfce_production, onChange: (v) => set({ nfce_production: v }), disabled: !draft.nfce_enabled })
|
|
6282
|
+
}
|
|
6283
|
+
),
|
|
6284
|
+
draft.nfce_enabled && /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
|
|
6285
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
|
|
6286
|
+
/* @__PURE__ */ jsx(Field2, { label: "S\xE9rie", children: /* @__PURE__ */ jsx(Input, { inputMode: "numeric", value: draft.nfce_series ?? "", onChange: (e) => set({ nfce_series: e.target.value.replace(/\D/g, "") }) }) }),
|
|
6287
|
+
/* @__PURE__ */ jsx(Field2, { label: "CSC \u2014 ID", children: /* @__PURE__ */ jsx(Input, { value: draft.nfce_csc_id ?? "", onChange: (e) => set({ nfce_csc_id: e.target.value }) }) })
|
|
6288
|
+
] }),
|
|
6289
|
+
/* @__PURE__ */ jsx(Field2, { label: "CSC \u2014 Token", hint: "O CSC (id num\xE9rico + token) \xE9 gerado no portal da SEFAZ do estado \u2014 n\xE3o \xE9 o login de nenhum sistema.", children: /* @__PURE__ */ jsx(Input, { type: "password", value: cscToken, placeholder: row.nfce_csc_token_set ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (configurado \u2014 digite para trocar)" : "", onChange: (e) => setCscToken(e.target.value) }) }),
|
|
6290
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
|
|
6291
|
+
/* @__PURE__ */ jsx(Field2, { label: "Item gen\xE9rico \u2014 NCM", children: /* @__PURE__ */ jsx(Input, { value: generic.ncm ?? "", onChange: (e) => setGeneric({ ncm: e.target.value }) }) }),
|
|
6292
|
+
/* @__PURE__ */ jsx(Field2, { label: "Item gen\xE9rico \u2014 CFOP", children: /* @__PURE__ */ jsx(Input, { value: generic.cfop ?? "", onChange: (e) => setGeneric({ cfop: e.target.value }) }) }),
|
|
6293
|
+
/* @__PURE__ */ jsx(Field2, { label: "Item gen\xE9rico \u2014 CSOSN/CST", children: /* @__PURE__ */ jsx(Input, { value: generic.csosn ?? "", onChange: (e) => setGeneric({ csosn: e.target.value }) }) })
|
|
6294
|
+
] })
|
|
6295
|
+
] })
|
|
6296
|
+
] }),
|
|
6297
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-2", children: checks.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: c.ok ? "gap-1 border-emerald-500/40 text-emerald-700 dark:text-emerald-400" : "gap-1 text-muted-foreground", children: [
|
|
6298
|
+
c.ok ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(X, { className: "h-3 w-3" }),
|
|
6299
|
+
c.label
|
|
6300
|
+
] }) }, c.label)) }),
|
|
6301
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-3", children: [
|
|
6302
|
+
/* @__PURE__ */ jsx("p", { className: "mr-auto text-xs text-muted-foreground", children: "Registrar a empresa no emissor ainda n\xE3o est\xE1 dispon\xEDvel nesta vers\xE3o \u2014 a configura\xE7\xE3o fica salva para quando estiver." }),
|
|
6303
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void onSave(), disabled: saving, children: saving ? "Salvando\u2026" : "Salvar configura\xE7\xE3o" })
|
|
6304
|
+
] })
|
|
6305
|
+
] })
|
|
6306
|
+
] });
|
|
6307
|
+
}
|
|
4517
6308
|
|
|
4518
6309
|
// src/lib/financial-provider.ts
|
|
4519
6310
|
function fiscalReader(provider) {
|
|
@@ -4521,13 +6312,13 @@ function fiscalReader(provider) {
|
|
|
4521
6312
|
return provider.getNotaById.bind(provider);
|
|
4522
6313
|
}
|
|
4523
6314
|
async function committed(read, id) {
|
|
4524
|
-
const
|
|
4525
|
-
if (!
|
|
4526
|
-
return
|
|
6315
|
+
const document2 = await read(id);
|
|
6316
|
+
if (!document2) throw new Error("Fiscal document was not found after the server operation.");
|
|
6317
|
+
return document2;
|
|
4527
6318
|
}
|
|
4528
|
-
function resultError(result,
|
|
6319
|
+
function resultError(result, document2, fallback) {
|
|
4529
6320
|
if (result.status !== "error") return;
|
|
4530
|
-
throw new Error(result.message?.trim() ||
|
|
6321
|
+
throw new Error(result.message?.trim() || document2.errorMessage?.trim() || fallback);
|
|
4531
6322
|
}
|
|
4532
6323
|
function cancellationPending(metadata) {
|
|
4533
6324
|
const provider = metadata?.plugnotas;
|
|
@@ -4549,11 +6340,11 @@ function createPlugNotasFinancialProvider(options) {
|
|
|
4549
6340
|
const { base, gateway } = options;
|
|
4550
6341
|
const issueNota = async (id) => {
|
|
4551
6342
|
const read = fiscalReader(base);
|
|
4552
|
-
const
|
|
4553
|
-
if (!
|
|
4554
|
-
if (
|
|
4555
|
-
if (!
|
|
4556
|
-
if (!["draft", "error"].includes(
|
|
6343
|
+
const document2 = await read(id);
|
|
6344
|
+
if (!document2) throw new Error("Fiscal document not found.");
|
|
6345
|
+
if (document2.model !== "nfce") throw new Error("PlugNotas currently authorizes NFC-e only.");
|
|
6346
|
+
if (!document2.orderId?.trim()) throw new Error("PlugNotas NFC-e must be created from an order.");
|
|
6347
|
+
if (!["draft", "error"].includes(document2.status)) return document2;
|
|
4557
6348
|
const result = await gateway.issue({ notaId: id, idempotencyKey: `nfce:${id}` });
|
|
4558
6349
|
const saved = await committed(read, id);
|
|
4559
6350
|
resultError(result, saved, "PlugNotas could not issue this NFC-e.");
|
|
@@ -4561,9 +6352,9 @@ function createPlugNotasFinancialProvider(options) {
|
|
|
4561
6352
|
};
|
|
4562
6353
|
const refreshNota = async (id) => {
|
|
4563
6354
|
const read = fiscalReader(base);
|
|
4564
|
-
const
|
|
4565
|
-
if (!
|
|
4566
|
-
if (!["processing", "error"].includes(
|
|
6355
|
+
const document2 = await read(id);
|
|
6356
|
+
if (!document2) throw new Error("Fiscal document not found.");
|
|
6357
|
+
if (!["processing", "error"].includes(document2.status) && !cancellationPending(document2.metadata)) return document2;
|
|
4567
6358
|
const result = await gateway.refresh({ notaId: id });
|
|
4568
6359
|
const saved = await committed(read, id);
|
|
4569
6360
|
resultError(result, saved, "PlugNotas could not refresh this NFC-e.");
|
|
@@ -4571,10 +6362,10 @@ function createPlugNotasFinancialProvider(options) {
|
|
|
4571
6362
|
};
|
|
4572
6363
|
const cancelNota = async (id, reason) => {
|
|
4573
6364
|
const read = fiscalReader(base);
|
|
4574
|
-
const
|
|
4575
|
-
if (!
|
|
4576
|
-
if (
|
|
4577
|
-
if (cancellationPending(
|
|
6365
|
+
const document2 = await read(id);
|
|
6366
|
+
if (!document2) throw new Error("Fiscal document not found.");
|
|
6367
|
+
if (document2.status !== "issued") throw new Error("Only an issued NFC-e can be cancelled.");
|
|
6368
|
+
if (cancellationPending(document2.metadata)) throw new Error("NFC-e cancellation is already in progress.");
|
|
4578
6369
|
const normalized = reason?.trim() ?? "";
|
|
4579
6370
|
if (normalized.length < 15 || normalized.length > 255) {
|
|
4580
6371
|
throw new Error("Cancellation reason must contain between 15 and 255 characters.");
|
|
@@ -4586,12 +6377,12 @@ function createPlugNotasFinancialProvider(options) {
|
|
|
4586
6377
|
};
|
|
4587
6378
|
const getNotaArtifactUrl = async (id, kind) => {
|
|
4588
6379
|
const read = fiscalReader(base);
|
|
4589
|
-
const
|
|
4590
|
-
if (!
|
|
4591
|
-
if (!["issued", "cancelled"].includes(
|
|
6380
|
+
const document2 = await read(id);
|
|
6381
|
+
if (!document2) throw new Error("Fiscal document not found.");
|
|
6382
|
+
if (!["issued", "cancelled"].includes(document2.status)) {
|
|
4592
6383
|
throw new Error("Fiscal artifacts are available only after authorization.");
|
|
4593
6384
|
}
|
|
4594
|
-
if (kind === "cancellation_xml" &&
|
|
6385
|
+
if (kind === "cancellation_xml" && document2.status !== "cancelled") {
|
|
4595
6386
|
throw new Error("Cancellation XML is available only after cancellation.");
|
|
4596
6387
|
}
|
|
4597
6388
|
return artifactUrl((await gateway.artifact({ notaId: id, kind })).url);
|
|
@@ -4663,10 +6454,14 @@ function ReturnedNotesView({ onBack } = {}) {
|
|
|
4663
6454
|
);
|
|
4664
6455
|
}
|
|
4665
6456
|
function inboundNotesInventoryView(options = {}) {
|
|
6457
|
+
registerDocumentOpener(NFE_OPENER);
|
|
4666
6458
|
return {
|
|
4667
6459
|
id: "inbound-notes",
|
|
4668
6460
|
label: "Notas Fiscais",
|
|
4669
6461
|
icon: "FileText",
|
|
6462
|
+
// O nome do V1 para a fila de notas de compra. «Entrada» era o nome genérico
|
|
6463
|
+
// que o Estoque dá a qualquer tela emprestada, e dizia menos do que a tela faz.
|
|
6464
|
+
defaultChildLabel: "Manifesto de Notas",
|
|
4670
6465
|
render: () => /* @__PURE__ */ jsx(InboundNotesPage, { ...options }),
|
|
4671
6466
|
// A ordem é a do V1: Grupos Fiscais vem antes das duas de emissão, que é
|
|
4672
6467
|
// onde ele é usado.
|
|
@@ -4684,6 +6479,60 @@ function inboundNotesInventoryView(options = {}) {
|
|
|
4684
6479
|
]
|
|
4685
6480
|
};
|
|
4686
6481
|
}
|
|
6482
|
+
function InboundNoteDocumentModal({ movement, onClose }) {
|
|
6483
|
+
const [noteId, setNoteId] = React5.useState(null);
|
|
6484
|
+
const [erro, setErro] = React5.useState(null);
|
|
6485
|
+
React5.useEffect(() => {
|
|
6486
|
+
let vivo = true;
|
|
6487
|
+
const numero = nfeNumberOf(movement);
|
|
6488
|
+
const busca = movement.operationId ? findInboxNoteByOperation(movement.operationId).then((id) => id ?? (numero ? findInboxNoteByNumber(numero) : null)) : numero ? findInboxNoteByNumber(numero) : Promise.resolve(null);
|
|
6489
|
+
busca.then((id) => {
|
|
6490
|
+
if (!vivo) return;
|
|
6491
|
+
if (id) setNoteId(id);
|
|
6492
|
+
else setErro("N\xE3o achei a nota desta entrada nesta conta.");
|
|
6493
|
+
}).catch((e) => {
|
|
6494
|
+
if (vivo) setErro(e instanceof Error ? e.message : String(e));
|
|
6495
|
+
});
|
|
6496
|
+
return () => {
|
|
6497
|
+
vivo = false;
|
|
6498
|
+
};
|
|
6499
|
+
}, [movement.operationId, movement.documentNumber, movement.reason]);
|
|
6500
|
+
if (erro) {
|
|
6501
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6502
|
+
/* @__PURE__ */ jsx(ModalHeader, { className: "shrink-0 border-b px-6 py-3 pr-14 text-left", children: /* @__PURE__ */ jsx(ModalTitle, { children: "Nota de entrada" }) }),
|
|
6503
|
+
/* @__PURE__ */ jsx(ModalBody, { className: "px-6 py-8", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: erro }) })
|
|
6504
|
+
] });
|
|
6505
|
+
}
|
|
6506
|
+
if (!noteId) {
|
|
6507
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6508
|
+
/* @__PURE__ */ jsx(ModalHeader, { className: "shrink-0 border-b px-6 py-3 pr-14 text-left", children: /* @__PURE__ */ jsx(ModalTitle, { children: "Nota de entrada" }) }),
|
|
6509
|
+
/* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3 px-6 py-4", children: [
|
|
6510
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-48" }),
|
|
6511
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full" }),
|
|
6512
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full" })
|
|
6513
|
+
] })
|
|
6514
|
+
] });
|
|
6515
|
+
}
|
|
6516
|
+
return /* @__PURE__ */ jsx(NoteReconcileView, { chrome: "modal", noteId, onBack: onClose });
|
|
6517
|
+
}
|
|
6518
|
+
function inboundNoteDocumentModal(movement, close) {
|
|
6519
|
+
if (movement.documentType !== "nfe" || !movement.operationId) return void 0;
|
|
6520
|
+
return /* @__PURE__ */ jsx(InboundNoteDocumentModal, { movement, onClose: close });
|
|
6521
|
+
}
|
|
6522
|
+
var nfeNumberOf = (ref) => (ref.documentNumber ?? ref.reason ?? "").match(/NF-e\s*([0-9]{2,})/i)?.[1] ?? null;
|
|
6523
|
+
var NFE_OPENER = {
|
|
6524
|
+
documentType: "nfe",
|
|
6525
|
+
// O razão é APPEND-ONLY — um gatilho recusa `UPDATE`, e com razão — então as
|
|
6526
|
+
// 3.088 linhas que a importação do V1 trouxe sem `document_type` nunca vão
|
|
6527
|
+
// ganhá-lo. O que elas têm é «Entrada via DANFE NF-e 10592» no motivo, frase
|
|
6528
|
+
// que este mesmo sistema escreveu. Reconhecê-la é ler o que se escreveu.
|
|
6529
|
+
claims: (ref) => Boolean(nfeNumberOf(ref)),
|
|
6530
|
+
// Const de módulo e não arrow inline: o registro compara por identidade para
|
|
6531
|
+
// distinguir recarga quente de dois plugins disputando o mesmo tipo, e uma
|
|
6532
|
+
// função nova a cada chamada pareceria disputa.
|
|
6533
|
+
render: (ref, close) => /* @__PURE__ */ jsx(InboundNoteDocumentModal, { movement: ref, onClose: close })
|
|
6534
|
+
};
|
|
6535
|
+
registerDocumentOpener(NFE_OPENER);
|
|
4687
6536
|
|
|
4688
6537
|
// src/index.ts
|
|
4689
6538
|
function createPlugNotasPlugin(options = {}) {
|
|
@@ -4721,6 +6570,22 @@ function createPlugNotasPlugin(options = {}) {
|
|
|
4721
6570
|
// O grupo fiscal é CADASTRO, e por isso entra como registry e não como
|
|
4722
6571
|
// rota própria: ele aparece onde os outros cadastros do produto aparecem.
|
|
4723
6572
|
registries: fiscalRegistries,
|
|
6573
|
+
widgets: [
|
|
6574
|
+
{
|
|
6575
|
+
id: "fiscal-br-unit-certificate",
|
|
6576
|
+
zone: UNIT_DETAIL_ZONES.certificate,
|
|
6577
|
+
component: UnitCertificateWidget,
|
|
6578
|
+
order: 0,
|
|
6579
|
+
permission: { feature: "financial", action: "read" }
|
|
6580
|
+
},
|
|
6581
|
+
{
|
|
6582
|
+
id: "fiscal-br-unit-fiscal",
|
|
6583
|
+
zone: UNIT_DETAIL_ZONES.fiscal,
|
|
6584
|
+
component: UnitFiscalWidget,
|
|
6585
|
+
order: 0,
|
|
6586
|
+
permission: { feature: "financial", action: "read" }
|
|
6587
|
+
}
|
|
6588
|
+
],
|
|
4724
6589
|
connectors: [createPlugNotasConnector(options)],
|
|
4725
6590
|
functions: [{
|
|
4726
6591
|
slug: "plugnotas-bridge",
|
|
@@ -4792,6 +6657,6 @@ function createPlugNotasPlugin(options = {}) {
|
|
|
4792
6657
|
});
|
|
4793
6658
|
}
|
|
4794
6659
|
|
|
4795
|
-
export { InboundNotesPage, InboundNotesView, IssuedNotesView, ManifestView, NfeParseError, NoteReconcileView, PLUGNOTAS_CONNECTOR_ID, PLUGNOTAS_FIELDS, PLUGNOTAS_SETTINGS, ReturnedNotesView, STOCK_BEARING_ENTRY_TYPES, TaxGroupsView, acceptNote, createPlugNotasConnector, createPlugNotasFinancialProvider, createPlugNotasPlugin, getInboxNote, inboundNotesInventoryView, isStockBearing, isValidCnpj, listChartAccounts, listInboxNotes, listMeasurementUnits, listStockLocations, listSupplierMap, markPlugNotasEmitterReady, onlyDigits, parseNfeXml, paymentLabel, paymentTypeFromCode, rejectNote, saveNoteDecision, searchProducts, stageNoteFromXml, toStagePayload };
|
|
6660
|
+
export { InboundNoteDocumentModal, InboundNotesPage, InboundNotesView, IssuedNotesView, ManifestView, NfeParseError, NoteReconcileView, PLUGNOTAS_CONNECTOR_ID, PLUGNOTAS_FIELDS, PLUGNOTAS_SETTINGS, ReturnedNotesView, STOCK_BEARING_ENTRY_TYPES, TaxGroupsView, acceptNote, createPlugNotasConnector, createPlugNotasFinancialProvider, createPlugNotasPlugin, findInboxNoteByNumber, findInboxNoteByOperation, getInboxNote, getInboxNoteXml, inboundNoteDocumentModal, inboundNotesInventoryView, isStockBearing, isValidCnpj, listChartAccounts, listInboxNotes, listMeasurementUnits, listProductsByIds, listStockLocations, listSupplierMap, markPlugNotasEmitterReady, onlyDigits, parseNfeXml, paymentLabel, paymentTypeFromCode, rejectNote, saveNoteDecision, searchProducts, stageNoteFromXml, suggestNoteItems, toStagePayload };
|
|
4796
6661
|
//# sourceMappingURL=index.js.map
|
|
4797
6662
|
//# sourceMappingURL=index.js.map
|