@fayz-ai/plugin-fiscal-br 0.3.4 → 0.3.5
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 +75 -0
- package/dist/data/inbox.d.ts.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2688 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/nfe-xml.d.ts +154 -0
- package/dist/lib/nfe-xml.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +3 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/ComingSoon.d.ts +2 -0
- package/dist/views/ComingSoon.d.ts.map +1 -0
- package/dist/views/GapScreens.d.ts +17 -0
- package/dist/views/GapScreens.d.ts.map +1 -0
- package/dist/views/InboundNotesPage.d.ts +13 -0
- package/dist/views/InboundNotesPage.d.ts.map +1 -0
- package/dist/views/InboundNotesView.d.ts +8 -0
- package/dist/views/InboundNotesView.d.ts.map +1 -0
- package/dist/views/ManifestView.d.ts +6 -0
- package/dist/views/ManifestView.d.ts.map +1 -0
- package/dist/views/NoteReconcileView.d.ts +9 -0
- package/dist/views/NoteReconcileView.d.ts.map +1 -0
- package/dist/views/inventoryView.d.ts +14 -0
- package/dist/views/inventoryView.d.ts.map +1 -0
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { recordRailFields, RECORD_GROUP, activeFacet, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus } from '@fayz-ai/core';
|
|
1
|
+
import { recordRailFields, RECORD_GROUP, activeFacet, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
2
|
+
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
3
|
+
import { Inbox, Loader2, UploadCloud, Trash2, FileDown, Gift, RefreshCw, Check, Plus, AlertTriangle, X, ShieldCheck, FileText, ExternalLink } from 'lucide-react';
|
|
4
|
+
import { PageHeaderActions, Button, Card, ListFilterBar, Skeleton, SubpageHeader, EmBreve, SearchSelect, Input, DatePicker, Hint, ComingSoon } from '@fayz-ai/ui';
|
|
5
|
+
export { ComingSoon, EmBreve } from '@fayz-ai/ui';
|
|
6
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
7
|
|
|
3
8
|
// src/index.ts
|
|
4
9
|
var PLUGNOTAS_CONNECTOR_ID = "plugnotas";
|
|
@@ -1833,6 +1838,999 @@ ALTER TABLE public.plg_fiscalbr_documents ENABLE ROW LEVEL SECURITY;
|
|
|
1833
1838
|
ALTER TABLE public.plg_fiscalbr_document_events ENABLE ROW LEVEL SECURITY;
|
|
1834
1839
|
ALTER TABLE public.plg_fiscalbr_document_orders ENABLE ROW LEVEL SECURITY;
|
|
1835
1840
|
`;
|
|
1841
|
+
var MIGRATION_008_A_NOTA_RECEBIDA_GUARDA_A_DECISAO_DE_ENTRADA = `-- ---------------------------------------------------------------------------
|
|
1842
|
+
-- 008_a_nota_recebida_guarda_a_decisao_de_entrada.sql
|
|
1843
|
+
--
|
|
1844
|
+
-- A 002 trouxe a nota; esta guarda o que se DECIDE sobre ela.
|
|
1845
|
+
--
|
|
1846
|
+
-- O inbox do V2 j\xE1 sabe tudo que o XML diz: fornecedor, item, NCM, CFOP, os 30
|
|
1847
|
+
-- campos de tributo. O que n\xE3o tem onde morar \xE9 a decis\xE3o que o operador toma
|
|
1848
|
+
-- em cima da nota, e \xE9 ela que transforma documento em estoque e em dinheiro:
|
|
1849
|
+
--
|
|
1850
|
+
-- a qual PRODUTO esta linha corresponde (product_id, j\xE1 existia)
|
|
1851
|
+
-- que TIPO de entrada ela \xE9 (entry_type)
|
|
1852
|
+
-- em que UNIDADE de estoque, com que FATOR (stock_unit_id, conversion_factor)
|
|
1853
|
+
-- em que PLANO DE CONTAS ela cai (chart_account_id)
|
|
1854
|
+
-- com que CUSTO o produto fica (cost_mode, unit_cost_new)
|
|
1855
|
+
--
|
|
1856
|
+
-- Sem essas colunas a tela de concilia\xE7\xE3o n\xE3o tem onde salvar, e "importar nota
|
|
1857
|
+
-- fiscal" viraria um leitor de XML \u2014 que \xE9 exatamente o que o V2 tinha.
|
|
1858
|
+
--
|
|
1859
|
+
-- \u2500\u2500 O vocabul\xE1rio vem do V1 medido, n\xE3o de um enum inventado \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1860
|
+
--
|
|
1861
|
+
-- \`danfe_import_items.entry_type\` no V1 de produ\xE7\xE3o (29-09-2026), 3.402 linhas:
|
|
1862
|
+
--
|
|
1863
|
+
-- insumo 2.303 revenda 546 patrimonio 280
|
|
1864
|
+
-- despesa 262 servico 11 (null) 5
|
|
1865
|
+
--
|
|
1866
|
+
-- S\xE3o cinco, e duas delas \u2014 \`despesa\` e \`servico\` \u2014 N\xC3O viram estoque. \xC9 por
|
|
1867
|
+
-- isso que o tipo \xE9 coluna da linha e n\xE3o da nota: a mesma NF-e traz o insumo
|
|
1868
|
+
-- que entra no estoque e a taxa de entrega que s\xF3 vira despesa.
|
|
1869
|
+
--
|
|
1870
|
+
-- \`cost_mode\` tem UM valor em 3.407 linhas: \`novo\`. A m\xE9dia ponderada existe na
|
|
1871
|
+
-- tela do V1 e ningu\xE9m nunca escolheu. Fica no CHECK por paridade \u2014 tirar a
|
|
1872
|
+
-- op\xE7\xE3o \xE9 decis\xE3o de produto, e o dado n\xE3o autoriza ningu\xE9m a tom\xE1-la por aqui.
|
|
1873
|
+
--
|
|
1874
|
+
-- \u2500\u2500 Por que \`stock_movement_id\` na linha, e n\xE3o s\xF3 na nota \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1875
|
+
--
|
|
1876
|
+
-- Uma nota pode virar estoque PARCIALMENTE: o V1 tem os status \`partial\` e
|
|
1877
|
+
-- \`partial_stocked\` justamente porque o operador concilia 1 de 6 itens hoje e o
|
|
1878
|
+
-- resto amanh\xE3. Guardar o movimento s\xF3 no cabe\xE7alho responderia "esta nota
|
|
1879
|
+
-- entrou?" e nunca "esta LINHA entrou?" \u2014 e \xE9 a segunda pergunta que decide se
|
|
1880
|
+
-- aceitar de novo duplica saldo.
|
|
1881
|
+
--
|
|
1882
|
+
-- \u2500\u2500 O de-para \xE9 o que faz a segunda nota ser r\xE1pida \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1883
|
+
--
|
|
1884
|
+
-- \`supplier_product_mappings\` no V1: (CNPJ do fornecedor, c\xF3digo do produto no
|
|
1885
|
+
-- fornecedor) \u2192 produto, tipo, plano, unidade, fator. 66 linhas na Artorius, e \xE9
|
|
1886
|
+
-- o que faz a nota seguinte do mesmo fornecedor chegar j\xE1 vinculada. Sem ele,
|
|
1887
|
+
-- toda importa\xE7\xE3o recome\xE7a do zero e o ganho de importar XML desaparece.
|
|
1888
|
+
--
|
|
1889
|
+
-- A chave \xE9 (tenant, CNPJ, c\xF3digo) e N\xC3O inclui unidade: o de-para \xE9
|
|
1890
|
+
-- conhecimento sobre o cat\xE1logo do fornecedor, n\xE3o sobre uma loja.
|
|
1891
|
+
--
|
|
1892
|
+
-- Aditivo e idempotente. Nada aqui apaga, renomeia ou reescreve dado existente.
|
|
1893
|
+
-- ---------------------------------------------------------------------------
|
|
1894
|
+
|
|
1895
|
+
-- \u2500\u2500 A decis\xE3o, linha a linha \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1896
|
+
|
|
1897
|
+
ALTER TABLE public.plg_fiscalbr_inbox_items
|
|
1898
|
+
ADD COLUMN IF NOT EXISTS entry_type text,
|
|
1899
|
+
ADD COLUMN IF NOT EXISTS chart_account_id uuid,
|
|
1900
|
+
ADD COLUMN IF NOT EXISTS stock_unit_id uuid,
|
|
1901
|
+
-- A quantidade que entra no estoque, j\xE1 convertida. Derivada
|
|
1902
|
+
-- (quantity_commercial \xD7 conversion_factor), mas GRAVADA: \xE9 o n\xFAmero que o
|
|
1903
|
+
-- operador viu na tela antes de aceitar, e recalcular depois com um fator que
|
|
1904
|
+
-- algu\xE9m editou daria outra resposta para a mesma entrada.
|
|
1905
|
+
ADD COLUMN IF NOT EXISTS stock_quantity numeric(18,4),
|
|
1906
|
+
ADD COLUMN IF NOT EXISTS conversion_factor numeric(14,4) NOT NULL DEFAULT 1,
|
|
1907
|
+
ADD COLUMN IF NOT EXISTS cost_mode text,
|
|
1908
|
+
ADD COLUMN IF NOT EXISTS unit_cost_new numeric(18,6),
|
|
1909
|
+
-- O movimento que ESTA linha produziu. Nulo = n\xE3o entrou.
|
|
1910
|
+
ADD COLUMN IF NOT EXISTS stock_movement_id uuid,
|
|
1911
|
+
-- Diverg\xEAncia: chegou menos do que a nota diz. O V1 guarda quantidade, motivo
|
|
1912
|
+
-- e observa\xE7\xE3o, e a nota continua v\xE1lida \u2014 quem responde pela diferen\xE7a \xE9 o
|
|
1913
|
+
-- fornecedor, n\xE3o o estoque.
|
|
1914
|
+
ADD COLUMN IF NOT EXISTS shortage_quantity numeric(18,4),
|
|
1915
|
+
ADD COLUMN IF NOT EXISTS shortage_reason text,
|
|
1916
|
+
ADD COLUMN IF NOT EXISTS shortage_note text;
|
|
1917
|
+
|
|
1918
|
+
DO $$
|
|
1919
|
+
BEGIN
|
|
1920
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_entry_type_chk') THEN
|
|
1921
|
+
ALTER TABLE public.plg_fiscalbr_inbox_items
|
|
1922
|
+
ADD CONSTRAINT plg_fiscalbr_inbox_items_entry_type_chk
|
|
1923
|
+
CHECK (entry_type IS NULL OR entry_type IN ('insumo','revenda','patrimonio','despesa','servico'));
|
|
1924
|
+
END IF;
|
|
1925
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_cost_mode_chk') THEN
|
|
1926
|
+
ALTER TABLE public.plg_fiscalbr_inbox_items
|
|
1927
|
+
ADD CONSTRAINT plg_fiscalbr_inbox_items_cost_mode_chk
|
|
1928
|
+
CHECK (cost_mode IS NULL OR cost_mode IN ('novo','media'));
|
|
1929
|
+
END IF;
|
|
1930
|
+
-- Um fator zero ou negativo n\xE3o converte nada: transformaria a quantidade da
|
|
1931
|
+
-- nota em zero estoque sem ningu\xE9m acusar. \xC9 o defeito silencioso que o
|
|
1932
|
+
-- FLOW.md do V1 nomeia (\xABfator de convers\xE3o errado \xE9 silencioso\xBB), e aqui ele
|
|
1933
|
+
-- deixa de ser poss\xEDvel.
|
|
1934
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_factor_chk') THEN
|
|
1935
|
+
ALTER TABLE public.plg_fiscalbr_inbox_items
|
|
1936
|
+
ADD CONSTRAINT plg_fiscalbr_inbox_items_factor_chk CHECK (conversion_factor > 0);
|
|
1937
|
+
END IF;
|
|
1938
|
+
END $$;
|
|
1939
|
+
|
|
1940
|
+
-- FK l\xF3gica para o plano de contas e a unidade: as duas s\xE3o de OUTROS plugins
|
|
1941
|
+
-- (financial e inventory), e a 000 deste plugin j\xE1 estabelece que FK f\xEDsica
|
|
1942
|
+
-- entre plugins \xE9 proibida \u2014 instalar ou remover um addon n\xE3o reescreve o
|
|
1943
|
+
-- schema do vizinho. A integridade \xE9 verificada na RPC, que \xE9 quem escreve.
|
|
1944
|
+
COMMENT ON COLUMN public.plg_fiscalbr_inbox_items.chart_account_id IS
|
|
1945
|
+
'Logical reference to plg_financial_chart_of_accounts.id \u2014 validated by the accept RPC, not by a cross-plugin FK.';
|
|
1946
|
+
COMMENT ON COLUMN public.plg_fiscalbr_inbox_items.stock_unit_id IS
|
|
1947
|
+
'Logical reference to plg_inventory_measurement_units.id \u2014 validated by the accept RPC.';
|
|
1948
|
+
COMMENT ON COLUMN public.plg_fiscalbr_inbox_items.stock_quantity IS
|
|
1949
|
+
'quantity_commercial x conversion_factor, stored as shown on screen before accepting.';
|
|
1950
|
+
|
|
1951
|
+
CREATE INDEX IF NOT EXISTS plg_fiscalbr_inbox_items_pending_idx
|
|
1952
|
+
ON public.plg_fiscalbr_inbox_items (tenant_id, inbox_document_id)
|
|
1953
|
+
WHERE reconciled IS NOT TRUE;
|
|
1954
|
+
|
|
1955
|
+
-- \u2500\u2500 A decis\xE3o no cabe\xE7alho da nota \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1956
|
+
|
|
1957
|
+
ALTER TABLE public.plg_fiscalbr_inbox_documents
|
|
1958
|
+
-- \xABitens sem plano herdar\xE3o este\xBB \u2014 o plano da nota \xE9 padr\xE3o com HERAN\xC7A, n\xE3o
|
|
1959
|
+
-- obriga\xE7\xE3o por item. A frase \xE9 do V1 e a coluna existe para ela.
|
|
1960
|
+
ADD COLUMN IF NOT EXISTS default_chart_account_id uuid,
|
|
1961
|
+
-- O t\xEDtulo a pagar que esta nota gerou. Mesma ideia do inventory_operation_id
|
|
1962
|
+
-- que a 002 j\xE1 trouxe para o estoque: a nota aponta para as duas travessias.
|
|
1963
|
+
ADD COLUMN IF NOT EXISTS invoice_id uuid,
|
|
1964
|
+
ADD COLUMN IF NOT EXISTS stocked_at timestamptz,
|
|
1965
|
+
-- A natureza da opera\xE7\xE3o declarada na NF-e (\xABREM BONIFICACAO NAO
|
|
1966
|
+
-- CONTRIBUINTE\xBB). O bloco de situa\xE7\xE3o fiscal da concilia\xE7\xE3o mostra isso, e
|
|
1967
|
+
-- sem a coluna a tela mostraria um campo vazio.
|
|
1968
|
+
ADD COLUMN IF NOT EXISTS nature_operation text,
|
|
1969
|
+
ADD COLUMN IF NOT EXISTS default_location_id uuid;
|
|
1970
|
+
|
|
1971
|
+
COMMENT ON COLUMN public.plg_fiscalbr_inbox_documents.invoice_id IS
|
|
1972
|
+
'Logical reference to plg_financial_invoices.id created by fiscalbr_accept_inbox_document.';
|
|
1973
|
+
|
|
1974
|
+
-- \u2500\u2500 A parcela sabe como se paga \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1975
|
+
--
|
|
1976
|
+
-- A duplicata do XML traz n\xFAmero, vencimento e valor. O que o operador acrescenta
|
|
1977
|
+
-- na tela \xE9 COMO ela se paga \u2014 e o boleto que ele anexa carrega o c\xF3digo de
|
|
1978
|
+
-- barras, que \xE9 o que a automa\xE7\xE3o do V1 l\xEA para preencher a parcela.
|
|
1979
|
+
|
|
1980
|
+
ALTER TABLE public.plg_fiscalbr_inbox_duplicates
|
|
1981
|
+
ADD COLUMN IF NOT EXISTS payment_type text,
|
|
1982
|
+
ADD COLUMN IF NOT EXISTS barcode text,
|
|
1983
|
+
ADD COLUMN IF NOT EXISTS pix_key text,
|
|
1984
|
+
ADD COLUMN IF NOT EXISTS payment_method_id uuid;
|
|
1985
|
+
|
|
1986
|
+
-- O vocabul\xE1rio \xE9 o do V1, medido em \`danfe_payments.payment_type\` (1.069 linhas):
|
|
1987
|
+
-- boleto 660 \xB7 sem_pagamento 108 \xB7 outro 107 \xB7 dinheiro 65 \xB7
|
|
1988
|
+
-- duplicata_mercantil 45 \xB7 pix 35 \xB7 cartao_credito 35 \xB7 cartao_debito 9 \xB7
|
|
1989
|
+
-- credito_loja 4 \xB7 vale_presente 1
|
|
1990
|
+
DO $$
|
|
1991
|
+
BEGIN
|
|
1992
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_duplicates_payment_type_chk') THEN
|
|
1993
|
+
ALTER TABLE public.plg_fiscalbr_inbox_duplicates
|
|
1994
|
+
ADD CONSTRAINT plg_fiscalbr_inbox_duplicates_payment_type_chk
|
|
1995
|
+
CHECK (payment_type IS NULL OR payment_type IN (
|
|
1996
|
+
'boleto','pix','dinheiro','cartao_credito','cartao_debito',
|
|
1997
|
+
'duplicata_mercantil','credito_loja','vale_presente','sem_pagamento','outro'));
|
|
1998
|
+
END IF;
|
|
1999
|
+
END $$;
|
|
2000
|
+
|
|
2001
|
+
-- \u2500\u2500 O de-para fornecedor \xD7 produto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2002
|
+
|
|
2003
|
+
CREATE TABLE IF NOT EXISTS public.plg_fiscalbr_supplier_product_map (
|
|
2004
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
2005
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
2006
|
+
-- S\xF3 d\xEDgitos, normalizado na escrita: o XML traz o CNPJ cru e a tela do V1
|
|
2007
|
+
-- aceitava pontua\xE7\xE3o, ent\xE3o duas grafias do mesmo fornecedor viravam dois
|
|
2008
|
+
-- de-para e a segunda nota n\xE3o achava o primeiro.
|
|
2009
|
+
supplier_cnpj text NOT NULL,
|
|
2010
|
+
supplier_product_code text NOT NULL,
|
|
2011
|
+
product_id uuid NOT NULL REFERENCES public.products(id) ON DELETE CASCADE,
|
|
2012
|
+
entry_type text,
|
|
2013
|
+
chart_account_id uuid,
|
|
2014
|
+
stock_unit_id uuid,
|
|
2015
|
+
conversion_factor numeric(14,4) NOT NULL DEFAULT 1,
|
|
2016
|
+
cost_mode text,
|
|
2017
|
+
-- Quantas vezes este de-para acertou. Serve para a tela mostrar de onde veio o
|
|
2018
|
+
-- v\xEDnculo (\xABj\xE1 usado 12 vezes\xBB) em vez de um produto que apareceu sozinho.
|
|
2019
|
+
hit_count integer NOT NULL DEFAULT 0,
|
|
2020
|
+
last_used_at timestamptz,
|
|
2021
|
+
owner_id uuid,
|
|
2022
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
2023
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
2024
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
2025
|
+
CONSTRAINT plg_fiscalbr_supplier_product_map_factor_chk CHECK (conversion_factor > 0),
|
|
2026
|
+
CONSTRAINT plg_fiscalbr_supplier_product_map_entry_type_chk
|
|
2027
|
+
CHECK (entry_type IS NULL OR entry_type IN ('insumo','revenda','patrimonio','despesa','servico')),
|
|
2028
|
+
CONSTRAINT plg_fiscalbr_supplier_product_map_cost_mode_chk
|
|
2029
|
+
CHECK (cost_mode IS NULL OR cost_mode IN ('novo','media'))
|
|
2030
|
+
);
|
|
2031
|
+
|
|
2032
|
+
CREATE UNIQUE INDEX IF NOT EXISTS plg_fiscalbr_supplier_product_map_uq
|
|
2033
|
+
ON public.plg_fiscalbr_supplier_product_map (tenant_id, supplier_cnpj, supplier_product_code);
|
|
2034
|
+
|
|
2035
|
+
ALTER TABLE public.plg_fiscalbr_supplier_product_map ENABLE ROW LEVEL SECURITY;
|
|
2036
|
+
|
|
2037
|
+
-- Mesmas pol\xEDticas das tabelas do inbox (002): o de-para \xE9 conhecimento do
|
|
2038
|
+
-- financeiro sobre o fornecedor, ent\xE3o l\xEA com \`financial.read\` e escreve com
|
|
2039
|
+
-- \`financial.edit\`. N\xE3o tem unit_id \u2014 \xE9 cat\xE1logo do fornecedor, n\xE3o da loja.
|
|
2040
|
+
DROP POLICY IF EXISTS plg_fiscalbr_supplier_product_map_authz_select ON public.plg_fiscalbr_supplier_product_map;
|
|
2041
|
+
CREATE POLICY plg_fiscalbr_supplier_product_map_authz_select
|
|
2042
|
+
ON public.plg_fiscalbr_supplier_product_map FOR SELECT TO authenticated
|
|
2043
|
+
USING (
|
|
2044
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
2045
|
+
AND (SELECT app.has_permission('financial.read', NULL::uuid)));
|
|
2046
|
+
|
|
2047
|
+
DROP POLICY IF EXISTS plg_fiscalbr_supplier_product_map_authz_write ON public.plg_fiscalbr_supplier_product_map;
|
|
2048
|
+
CREATE POLICY plg_fiscalbr_supplier_product_map_authz_write
|
|
2049
|
+
ON public.plg_fiscalbr_supplier_product_map FOR ALL TO authenticated
|
|
2050
|
+
USING (
|
|
2051
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
2052
|
+
AND (SELECT app.has_permission('financial.edit', NULL::uuid)))
|
|
2053
|
+
WITH CHECK (
|
|
2054
|
+
tenant_id = (SELECT app.current_tenant_id())
|
|
2055
|
+
AND (SELECT app.has_permission('financial.edit', NULL::uuid)));
|
|
2056
|
+
|
|
2057
|
+
COMMENT ON TABLE public.plg_fiscalbr_supplier_product_map IS
|
|
2058
|
+
'Supplier catalogue memory: (CNPJ, supplier product code) -> the entry decision, so the next note from the same supplier arrives already bound. Mirrors V1 supplier_product_mappings.';
|
|
2059
|
+
`;
|
|
2060
|
+
var MIGRATION_009_ACEITAR_A_NOTA_ESCREVE_ESTOQUE_E_TITULO_DE_UMA_VEZ = `-- ---------------------------------------------------------------------------
|
|
2061
|
+
-- 009_aceitar_a_nota_escreve_estoque_e_titulo_de_uma_vez.sql
|
|
2062
|
+
--
|
|
2063
|
+
-- \xC9 o arquivo que fecha o porte: \xABAceitar\xBB na concilia\xE7\xE3o d\xE1 entrada no estoque
|
|
2064
|
+
-- E cria a conta a pagar, numa transa\xE7\xE3o s\xF3.
|
|
2065
|
+
--
|
|
2066
|
+
-- Medido no V1 de produ\xE7\xE3o (tenant 12 da Artorius, 29-09-2026): **72 de 72**
|
|
2067
|
+
-- movimentos de estoque nasceram com \`danfe_import_item_id\`. Nenhum foi digitado.
|
|
2068
|
+
-- Dar ao V2 uma tela de movimenta\xE7\xE3o manual e chamar isso de paridade seria
|
|
2069
|
+
-- entregar a tela que eles nunca abriram.
|
|
2070
|
+
--
|
|
2071
|
+
-- \u2500\u2500 N\xE3o reimplementa estoque nem t\xEDtulo. CHAMA quem j\xE1 faz \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2072
|
+
--
|
|
2073
|
+
-- O V2 j\xE1 tem as duas primitivas, e as duas j\xE1 s\xE3o idempotentes:
|
|
2074
|
+
--
|
|
2075
|
+
-- public.inventory_receive(location, lines, document, idempotency_key)
|
|
2076
|
+
-- public.financial_create_invoice(direction, total, counterparty, \u2026, key)
|
|
2077
|
+
--
|
|
2078
|
+
-- Reescrever INSERT em \`plg_inventory_stock_movements\` aqui daria uma segunda
|
|
2079
|
+
-- resposta para \xABcomo entra estoque\xBB \u2014 e a posi\xE7\xE3o \xE9 derivada por gatilho, ent\xE3o
|
|
2080
|
+
-- a segunda resposta sairia com saldo certo e sem ningu\xE9m acusar. A entrada de
|
|
2081
|
+
-- nota \xE9 mais um CHAMADOR dessas portas, como a requisi\xE7\xE3o e a contagem.
|
|
2082
|
+
--
|
|
2083
|
+
-- Idempot\xEAncia vem de gra\xE7a da\xED: a chave \xE9 derivada da chave de acesso da NF-e,
|
|
2084
|
+
-- que \xE9 \xFAnica por documento fiscal no Brasil. Aceitar duas vezes devolve o mesmo
|
|
2085
|
+
-- resultado sem escrever de novo \u2014 o AC7 do plano.
|
|
2086
|
+
--
|
|
2087
|
+
-- \u2500\u2500 O total do t\xEDtulo \xE9 a soma das PARCELAS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2088
|
+
--
|
|
2089
|
+
-- Regra herdada da \`plugin-financial/084\`, que mediu: em 6,3% das notas a soma
|
|
2090
|
+
-- das duplicatas n\xE3o bate com o total da nota. \`plg_financial_invoices\` \xE9
|
|
2091
|
+
-- imut\xE1vel depois de emitido, ent\xE3o um t\xEDtulo aberto com o total errado fica
|
|
2092
|
+
-- com saldo errado para sempre em toda tela que faz \xABtotal \u2212 pago\xBB.
|
|
2093
|
+
-- O total da nota continua leg\xEDvel no \`metadata\` e na caixa de entrada.
|
|
2094
|
+
--
|
|
2095
|
+
-- E a MESMA chave de idempot\xEAncia da 084 (\`danfe:<chave>\`): uma nota que o porte
|
|
2096
|
+
-- hist\xF3rico j\xE1 transformou em t\xEDtulo n\xE3o pode ganhar um segundo aqui.
|
|
2097
|
+
--
|
|
2098
|
+
-- \u2500\u2500 Despesa e servi\xE7o n\xE3o viram estoque \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2099
|
+
--
|
|
2100
|
+
-- \`entry_type\` decide por LINHA. A mesma NF-e traz o insumo que entra no estoque
|
|
2101
|
+
-- e a taxa de entrega que s\xF3 vira despesa \u2014 e as duas precisam ir para o mesmo
|
|
2102
|
+
-- t\xEDtulo a pagar. Por isso a lista de linhas do estoque \xE9 um FILTRO sobre os
|
|
2103
|
+
-- itens, n\xE3o os itens.
|
|
2104
|
+
--
|
|
2105
|
+
-- Idempotente. S\xF3 cria fun\xE7\xE3o.
|
|
2106
|
+
-- ---------------------------------------------------------------------------
|
|
2107
|
+
|
|
2108
|
+
-- \u2500\u2500 Ajudantes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2109
|
+
|
|
2110
|
+
-- O XML traz o CNPJ cru e a tela aceita pontua\xE7\xE3o. Duas grafias do mesmo
|
|
2111
|
+
-- fornecedor viravam dois de-para no V1, e a segunda nota n\xE3o achava o primeiro.
|
|
2112
|
+
CREATE OR REPLACE FUNCTION public.plg_fiscalbr_digits(p_value text)
|
|
2113
|
+
RETURNS text LANGUAGE sql IMMUTABLE SET search_path TO '' AS $$
|
|
2114
|
+
SELECT regexp_replace(coalesce(p_value, ''), '[^0-9]', '', 'g')
|
|
2115
|
+
$$;
|
|
2116
|
+
|
|
2117
|
+
-- \u2500\u2500 Receber a nota: o XML j\xE1 lido vira linhas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2118
|
+
--
|
|
2119
|
+
-- O XML \xE9 decomposto no NAVEGADOR e chega aqui como jsonb. Parsear NF-e em
|
|
2120
|
+
-- PL/pgSQL seria escrever um leitor de XML dentro do banco, sem teste unit\xE1rio
|
|
2121
|
+
-- poss\xEDvel e sem a mensagem de erro que a tela precisa dar para um arquivo que
|
|
2122
|
+
-- n\xE3o \xE9 uma nota.
|
|
2123
|
+
|
|
2124
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_stage_inbox_document(p_note jsonb)
|
|
2125
|
+
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
|
|
2126
|
+
DECLARE
|
|
2127
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
2128
|
+
v_key text := btrim(coalesce(p_note->>'access_key', ''));
|
|
2129
|
+
v_cnpj text := public.plg_fiscalbr_digits(p_note->'supplier'->>'cnpj');
|
|
2130
|
+
v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
|
|
2131
|
+
v_unit uuid := nullif(p_note->>'unit_id', '')::uuid;
|
|
2132
|
+
v_party uuid;
|
|
2133
|
+
v_item jsonb;
|
|
2134
|
+
v_map public.plg_fiscalbr_supplier_product_map%ROWTYPE;
|
|
2135
|
+
v_n integer := 0;
|
|
2136
|
+
v_matched integer := 0;
|
|
2137
|
+
BEGIN
|
|
2138
|
+
PERFORM public.plg_financial_require_permission('financial.create', v_unit);
|
|
2139
|
+
|
|
2140
|
+
IF length(v_key) <> 44 THEN
|
|
2141
|
+
RAISE EXCEPTION 'fiscalbr: chave de acesso inv\xE1lida (esperado 44 d\xEDgitos, veio %)', length(v_key)
|
|
2142
|
+
USING ERRCODE = '22023';
|
|
2143
|
+
END IF;
|
|
2144
|
+
|
|
2145
|
+
SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
|
|
2146
|
+
WHERE tenant_id = v_tenant AND access_key = v_key;
|
|
2147
|
+
|
|
2148
|
+
-- Nota j\xE1 aceita n\xE3o \xE9 reimportada: o XML \xE9 o mesmo documento fiscal, e
|
|
2149
|
+
-- reescrever os itens apagaria a decis\xE3o que virou estoque e t\xEDtulo.
|
|
2150
|
+
IF FOUND AND (v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL) THEN
|
|
2151
|
+
RETURN jsonb_build_object(
|
|
2152
|
+
'document_id', v_doc.id, 'access_key', v_key, 'already_accepted', true,
|
|
2153
|
+
'invoice_id', v_doc.invoice_id, 'items', 0, 'matched', 0);
|
|
2154
|
+
END IF;
|
|
2155
|
+
|
|
2156
|
+
-- O fornecedor \xE9 uma PESSOA, n\xE3o um nome. A 094 do financial documenta o custo
|
|
2157
|
+
-- de n\xE3o ter o ponteiro: praticamente todo t\xEDtulo nascido de DANFE ficou sem
|
|
2158
|
+
-- contraparte. Casar por CNPJ acerta; casar por nome acertaria 137 de 554.
|
|
2159
|
+
IF v_cnpj <> '' THEN
|
|
2160
|
+
SELECT id INTO v_party FROM public.people
|
|
2161
|
+
WHERE tenant_id = v_tenant AND public.plg_fiscalbr_digits(document_number) = v_cnpj
|
|
2162
|
+
LIMIT 1;
|
|
2163
|
+
END IF;
|
|
2164
|
+
|
|
2165
|
+
INSERT INTO public.plg_fiscalbr_inbox_documents (
|
|
2166
|
+
tenant_id, unit_id, access_key, nfe_number, nfe_series, nfe_model, issue_date,
|
|
2167
|
+
received_at, status, nature_operation, supplier_party_id, supplier_cnpj,
|
|
2168
|
+
supplier_name, supplier_ie, total_value, total_products, total_discount,
|
|
2169
|
+
total_freight, total_insurance, total_other, additional_info, metadata)
|
|
2170
|
+
VALUES (
|
|
2171
|
+
v_tenant, v_unit, v_key, p_note->>'nfe_number', p_note->>'nfe_series',
|
|
2172
|
+
coalesce(p_note->>'nfe_model', '55'), nullif(p_note->>'issue_date', '')::date,
|
|
2173
|
+
now(), 'pending', p_note->>'nature_operation', v_party, nullif(v_cnpj, ''),
|
|
2174
|
+
p_note->'supplier'->>'name', p_note->'supplier'->>'ie',
|
|
2175
|
+
(p_note->'totals'->>'total')::numeric, (p_note->'totals'->>'products')::numeric,
|
|
2176
|
+
(p_note->'totals'->>'discount')::numeric, (p_note->'totals'->>'freight')::numeric,
|
|
2177
|
+
(p_note->'totals'->>'insurance')::numeric, (p_note->'totals'->>'other')::numeric,
|
|
2178
|
+
p_note->>'additional_info',
|
|
2179
|
+
jsonb_build_object('source', coalesce(p_note->>'source', 'xml_upload')))
|
|
2180
|
+
-- O \xEDndice \xFAnico da 002 \xE9 PARCIAL (\`WHERE access_key IS NOT NULL\`), e a
|
|
2181
|
+
-- infer\xEAncia do ON CONFLICT s\xF3 o alcan\xE7a repetindo o predicado. Sem ele o
|
|
2182
|
+
-- INSERT levanta \xABno unique or exclusion constraint matching\xBB \u2014 e levantaria
|
|
2183
|
+
-- na primeira reimporta\xE7\xE3o, n\xE3o na aplica\xE7\xE3o.
|
|
2184
|
+
ON CONFLICT (tenant_id, access_key) WHERE access_key IS NOT NULL DO UPDATE SET
|
|
2185
|
+
-- Reimportar o mesmo XML corrige o cabe\xE7alho; a contraparte s\xF3 \xE9 preenchida
|
|
2186
|
+
-- quando est\xE1 vazia, para n\xE3o desfazer uma costura feita \xE0 m\xE3o.
|
|
2187
|
+
nfe_number = EXCLUDED.nfe_number,
|
|
2188
|
+
nature_operation = coalesce(EXCLUDED.nature_operation, public.plg_fiscalbr_inbox_documents.nature_operation),
|
|
2189
|
+
supplier_party_id = coalesce(public.plg_fiscalbr_inbox_documents.supplier_party_id, EXCLUDED.supplier_party_id),
|
|
2190
|
+
supplier_name = EXCLUDED.supplier_name,
|
|
2191
|
+
total_value = EXCLUDED.total_value,
|
|
2192
|
+
updated_at = now()
|
|
2193
|
+
RETURNING * INTO v_doc;
|
|
2194
|
+
|
|
2195
|
+
-- Os itens e as duplicatas s\xE3o do DOCUMENTO, n\xE3o do operador: v\xEAm do XML e
|
|
2196
|
+
-- podem ser reescritos enquanto a nota n\xE3o foi aceita.
|
|
2197
|
+
DELETE FROM public.plg_fiscalbr_inbox_items WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
2198
|
+
DELETE FROM public.plg_fiscalbr_inbox_duplicates WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
2199
|
+
DELETE FROM public.plg_fiscalbr_inbox_payments WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
2200
|
+
|
|
2201
|
+
FOR v_item IN SELECT * FROM jsonb_array_elements(coalesce(p_note->'items', '[]'::jsonb)) LOOP
|
|
2202
|
+
v_n := v_n + 1;
|
|
2203
|
+
|
|
2204
|
+
-- O de-para: a segunda nota do mesmo fornecedor chega j\xE1 vinculada. \xC9 o que
|
|
2205
|
+
-- faz importar XML valer a pena \u2014 sem ele toda nota recome\xE7a do zero.
|
|
2206
|
+
SELECT * INTO v_map FROM public.plg_fiscalbr_supplier_product_map
|
|
2207
|
+
WHERE tenant_id = v_tenant AND supplier_cnpj = v_cnpj
|
|
2208
|
+
AND supplier_product_code = coalesce(v_item->>'supplier_product_code', '');
|
|
2209
|
+
IF FOUND THEN v_matched := v_matched + 1; END IF;
|
|
2210
|
+
|
|
2211
|
+
INSERT INTO public.plg_fiscalbr_inbox_items (
|
|
2212
|
+
tenant_id, inbox_document_id, item_number, supplier_product_code, ean_barcode,
|
|
2213
|
+
description, ncm, cest, cfop, unit_commercial, quantity_commercial,
|
|
2214
|
+
unit_price_commercial, total_price, discount, freight, other_expenses,
|
|
2215
|
+
icms_cst, icms_csosn, icms_base, icms_rate, icms_value,
|
|
2216
|
+
pis_value, cofins_value, ipi_value, raw_xml_item,
|
|
2217
|
+
product_id, entry_type, chart_account_id, stock_unit_id, conversion_factor,
|
|
2218
|
+
cost_mode, reconciled, stock_quantity, unit_cost_new)
|
|
2219
|
+
VALUES (
|
|
2220
|
+
v_tenant, v_doc.id, coalesce((v_item->>'item_number')::int, v_n),
|
|
2221
|
+
v_item->>'supplier_product_code', v_item->>'ean_barcode',
|
|
2222
|
+
v_item->>'description', v_item->>'ncm', v_item->>'cest', v_item->>'cfop',
|
|
2223
|
+
v_item->>'unit', (v_item->>'quantity')::numeric,
|
|
2224
|
+
(v_item->>'unit_price')::numeric, (v_item->>'total')::numeric,
|
|
2225
|
+
(v_item->>'discount')::numeric, (v_item->>'freight')::numeric,
|
|
2226
|
+
(v_item->>'other')::numeric,
|
|
2227
|
+
v_item->>'icms_cst', v_item->>'icms_csosn', (v_item->>'icms_base')::numeric,
|
|
2228
|
+
(v_item->>'icms_rate')::numeric, (v_item->>'icms_value')::numeric,
|
|
2229
|
+
(v_item->>'pis_value')::numeric, (v_item->>'cofins_value')::numeric,
|
|
2230
|
+
(v_item->>'ipi_value')::numeric, v_item,
|
|
2231
|
+
v_map.product_id, v_map.entry_type, v_map.chart_account_id, v_map.stock_unit_id,
|
|
2232
|
+
coalesce(v_map.conversion_factor, 1), coalesce(v_map.cost_mode, 'novo'),
|
|
2233
|
+
v_map.product_id IS NOT NULL,
|
|
2234
|
+
-- A quantidade de estoque j\xE1 sai calculada quando o de-para deu o fator: \xE9
|
|
2235
|
+
-- o n\xFAmero que a tela mostra antes de aceitar.
|
|
2236
|
+
CASE WHEN v_map.product_id IS NOT NULL
|
|
2237
|
+
THEN (v_item->>'quantity')::numeric * coalesce(v_map.conversion_factor, 1) END,
|
|
2238
|
+
CASE WHEN v_map.product_id IS NOT NULL AND coalesce(v_map.conversion_factor, 1) > 0
|
|
2239
|
+
THEN (v_item->>'unit_price')::numeric / coalesce(v_map.conversion_factor, 1) END);
|
|
2240
|
+
END LOOP;
|
|
2241
|
+
|
|
2242
|
+
INSERT INTO public.plg_fiscalbr_inbox_duplicates (
|
|
2243
|
+
tenant_id, inbox_document_id, duplicate_index, number, due_date, value, payment_type)
|
|
2244
|
+
SELECT v_tenant, v_doc.id, coalesce((d->>'index')::int, ord), d->>'number',
|
|
2245
|
+
nullif(d->>'due_date', '')::date, (d->>'value')::numeric,
|
|
2246
|
+
nullif(d->>'payment_type', '')
|
|
2247
|
+
FROM jsonb_array_elements(coalesce(p_note->'duplicates', '[]'::jsonb)) WITH ORDINALITY AS t(d, ord);
|
|
2248
|
+
|
|
2249
|
+
INSERT INTO public.plg_fiscalbr_inbox_payments (
|
|
2250
|
+
tenant_id, inbox_document_id, payment_index, payment_indicator,
|
|
2251
|
+
payment_type_code, payment_type_label, value)
|
|
2252
|
+
SELECT v_tenant, v_doc.id, coalesce((p->>'index')::int, ord), p->>'indicator',
|
|
2253
|
+
p->>'code', p->>'label', (p->>'value')::numeric
|
|
2254
|
+
FROM jsonb_array_elements(coalesce(p_note->'payments', '[]'::jsonb)) WITH ORDINALITY AS t(p, ord);
|
|
2255
|
+
|
|
2256
|
+
RETURN jsonb_build_object(
|
|
2257
|
+
'document_id', v_doc.id, 'access_key', v_key, 'already_accepted', false,
|
|
2258
|
+
'supplier_party_id', v_party, 'items', v_n, 'matched', v_matched);
|
|
2259
|
+
END $function$;
|
|
2260
|
+
|
|
2261
|
+
REVOKE ALL ON FUNCTION public.fiscalbr_stage_inbox_document(jsonb) FROM PUBLIC;
|
|
2262
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_stage_inbox_document(jsonb) TO authenticated;
|
|
2263
|
+
|
|
2264
|
+
-- \u2500\u2500 Salvar a concilia\xE7\xE3o sem aceitar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2265
|
+
--
|
|
2266
|
+
-- Conciliar e estocar s\xE3o duas coisas no V1 \u2014 da\xED as duas colunas na lista, e
|
|
2267
|
+
-- da\xED a fila de 55 notas conciliadas e n\xE3o estocadas. Quem salva o v\xEDnculo n\xE3o
|
|
2268
|
+
-- est\xE1 mandando dar entrada.
|
|
2269
|
+
|
|
2270
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_save_inbox_decision(
|
|
2271
|
+
p_document_id uuid,
|
|
2272
|
+
p_items jsonb,
|
|
2273
|
+
p_default_chart_account_id uuid DEFAULT NULL,
|
|
2274
|
+
p_default_location_id uuid DEFAULT NULL,
|
|
2275
|
+
p_duplicates jsonb DEFAULT NULL)
|
|
2276
|
+
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
|
|
2277
|
+
DECLARE
|
|
2278
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
2279
|
+
v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
|
|
2280
|
+
v_row jsonb;
|
|
2281
|
+
v_done integer := 0;
|
|
2282
|
+
v_total integer;
|
|
2283
|
+
BEGIN
|
|
2284
|
+
SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
|
|
2285
|
+
WHERE tenant_id = v_tenant AND id = p_document_id;
|
|
2286
|
+
IF NOT FOUND THEN
|
|
2287
|
+
RAISE EXCEPTION 'fiscalbr: nota % n\xE3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';
|
|
2288
|
+
END IF;
|
|
2289
|
+
PERFORM public.plg_financial_require_permission('financial.edit', v_doc.unit_id);
|
|
2290
|
+
|
|
2291
|
+
IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN
|
|
2292
|
+
RAISE EXCEPTION 'fiscalbr: nota % j\xE1 foi aceita \u2014 a decis\xE3o n\xE3o muda depois do estoque', p_document_id
|
|
2293
|
+
USING ERRCODE = '23514';
|
|
2294
|
+
END IF;
|
|
2295
|
+
|
|
2296
|
+
UPDATE public.plg_fiscalbr_inbox_documents SET
|
|
2297
|
+
default_chart_account_id = coalesce(p_default_chart_account_id, default_chart_account_id),
|
|
2298
|
+
default_location_id = coalesce(p_default_location_id, default_location_id),
|
|
2299
|
+
reviewed_at = now(), updated_at = now()
|
|
2300
|
+
WHERE id = v_doc.id;
|
|
2301
|
+
|
|
2302
|
+
FOR v_row IN SELECT * FROM jsonb_array_elements(coalesce(p_items, '[]'::jsonb)) LOOP
|
|
2303
|
+
UPDATE public.plg_fiscalbr_inbox_items i SET
|
|
2304
|
+
product_id = nullif(v_row->>'product_id', '')::uuid,
|
|
2305
|
+
entry_type = nullif(v_row->>'entry_type', ''),
|
|
2306
|
+
chart_account_id = nullif(v_row->>'chart_account_id', '')::uuid,
|
|
2307
|
+
stock_unit_id = nullif(v_row->>'stock_unit_id', '')::uuid,
|
|
2308
|
+
conversion_factor = coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1),
|
|
2309
|
+
cost_mode = coalesce(nullif(v_row->>'cost_mode', ''), 'novo'),
|
|
2310
|
+
shortage_quantity = nullif(v_row->>'shortage_quantity', '')::numeric,
|
|
2311
|
+
shortage_reason = nullif(v_row->>'shortage_reason', ''),
|
|
2312
|
+
shortage_note = nullif(v_row->>'shortage_note', ''),
|
|
2313
|
+
-- Derivadas, gravadas: \xE9 o que o operador viu.
|
|
2314
|
+
stock_quantity = i.quantity_commercial * coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1)
|
|
2315
|
+
- coalesce(nullif(v_row->>'shortage_quantity', '')::numeric, 0),
|
|
2316
|
+
unit_cost_new = CASE WHEN coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1) > 0
|
|
2317
|
+
THEN i.unit_price_commercial / coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1) END,
|
|
2318
|
+
-- Uma linha est\xE1 conciliada quando sabe o que \xE9. Despesa e servi\xE7o n\xE3o
|
|
2319
|
+
-- pedem produto: elas nunca v\xE3o virar estoque.
|
|
2320
|
+
reconciled = CASE
|
|
2321
|
+
WHEN nullif(v_row->>'entry_type', '') IN ('despesa','servico') THEN true
|
|
2322
|
+
ELSE nullif(v_row->>'product_id', '') IS NOT NULL
|
|
2323
|
+
END,
|
|
2324
|
+
updated_at = now()
|
|
2325
|
+
WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id
|
|
2326
|
+
AND i.id = (v_row->>'id')::uuid;
|
|
2327
|
+
v_done := v_done + 1;
|
|
2328
|
+
END LOOP;
|
|
2329
|
+
|
|
2330
|
+
IF p_duplicates IS NOT NULL THEN
|
|
2331
|
+
DELETE FROM public.plg_fiscalbr_inbox_duplicates
|
|
2332
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
2333
|
+
INSERT INTO public.plg_fiscalbr_inbox_duplicates (
|
|
2334
|
+
tenant_id, inbox_document_id, duplicate_index, number, due_date, value,
|
|
2335
|
+
payment_type, barcode, pix_key, payment_method_id)
|
|
2336
|
+
SELECT v_tenant, v_doc.id, coalesce((d->>'index')::int, ord), d->>'number',
|
|
2337
|
+
nullif(d->>'due_date', '')::date, (d->>'value')::numeric,
|
|
2338
|
+
nullif(d->>'payment_type', ''), nullif(d->>'barcode', ''),
|
|
2339
|
+
nullif(d->>'pix_key', ''), nullif(d->>'payment_method_id', '')::uuid
|
|
2340
|
+
FROM jsonb_array_elements(p_duplicates) WITH ORDINALITY AS t(d, ord);
|
|
2341
|
+
END IF;
|
|
2342
|
+
|
|
2343
|
+
SELECT count(*) INTO v_total FROM public.plg_fiscalbr_inbox_items
|
|
2344
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
2345
|
+
|
|
2346
|
+
UPDATE public.plg_fiscalbr_inbox_documents SET status = CASE
|
|
2347
|
+
WHEN (SELECT count(*) FROM public.plg_fiscalbr_inbox_items
|
|
2348
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id AND reconciled) = v_total
|
|
2349
|
+
THEN 'accepted' ELSE 'partial' END
|
|
2350
|
+
WHERE id = v_doc.id;
|
|
2351
|
+
|
|
2352
|
+
RETURN jsonb_build_object('document_id', v_doc.id, 'saved', v_done, 'items', v_total,
|
|
2353
|
+
'reconciled', (SELECT count(*) FROM public.plg_fiscalbr_inbox_items
|
|
2354
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id AND reconciled));
|
|
2355
|
+
END $function$;
|
|
2356
|
+
|
|
2357
|
+
REVOKE ALL ON FUNCTION public.fiscalbr_save_inbox_decision(uuid, jsonb, uuid, uuid, jsonb) FROM PUBLIC;
|
|
2358
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_save_inbox_decision(uuid, jsonb, uuid, uuid, jsonb) TO authenticated;
|
|
2359
|
+
|
|
2360
|
+
-- \u2500\u2500 Aceitar: estoque e t\xEDtulo, de uma vez \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2361
|
+
|
|
2362
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_accept_inbox_document(
|
|
2363
|
+
p_document_id uuid,
|
|
2364
|
+
p_location_id uuid DEFAULT NULL)
|
|
2365
|
+
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
|
|
2366
|
+
DECLARE
|
|
2367
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
2368
|
+
v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
|
|
2369
|
+
v_loc uuid;
|
|
2370
|
+
v_lines jsonb := '[]'::jsonb;
|
|
2371
|
+
v_insts jsonb := '[]'::jsonb;
|
|
2372
|
+
v_recv jsonb;
|
|
2373
|
+
v_inv jsonb;
|
|
2374
|
+
v_it record;
|
|
2375
|
+
v_no integer := 0;
|
|
2376
|
+
v_total numeric := 0;
|
|
2377
|
+
v_stock_n integer := 0;
|
|
2378
|
+
v_key text;
|
|
2379
|
+
v_invoice uuid;
|
|
2380
|
+
-- Posi\xE7\xE3o na lista de linhas \u2192 id do item da nota. \`line_no\` do movimento
|
|
2381
|
+
-- indexa este array direto.
|
|
2382
|
+
v_item_ids uuid[] := '{}';
|
|
2383
|
+
BEGIN
|
|
2384
|
+
SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
|
|
2385
|
+
WHERE tenant_id = v_tenant AND id = p_document_id;
|
|
2386
|
+
IF NOT FOUND THEN
|
|
2387
|
+
RAISE EXCEPTION 'fiscalbr: nota % n\xE3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';
|
|
2388
|
+
END IF;
|
|
2389
|
+
PERFORM public.plg_financial_require_permission('financial.create', v_doc.unit_id);
|
|
2390
|
+
|
|
2391
|
+
-- J\xE1 aceita devolve o que aceitou. N\xE3o \xE9 erro: \xE9 a segunda vez que o operador
|
|
2392
|
+
-- clicou, e o AC7 diz que isso n\xE3o pode dobrar nada.
|
|
2393
|
+
IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN
|
|
2394
|
+
RETURN jsonb_build_object('document_id', v_doc.id, 'already_accepted', true,
|
|
2395
|
+
'invoice_id', v_doc.invoice_id, 'operation_id', v_doc.inventory_operation_id);
|
|
2396
|
+
END IF;
|
|
2397
|
+
|
|
2398
|
+
v_loc := coalesce(p_location_id, v_doc.default_location_id);
|
|
2399
|
+
|
|
2400
|
+
-- Toda linha tem de saber o que \xE9. Aceitar uma nota com item sem tipo gravaria
|
|
2401
|
+
-- um t\xEDtulo cujo rateio ningu\xE9m consegue explicar depois.
|
|
2402
|
+
IF EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items
|
|
2403
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id
|
|
2404
|
+
AND (entry_type IS NULL OR entry_type = '')) THEN
|
|
2405
|
+
RAISE EXCEPTION 'fiscalbr: h\xE1 item sem tipo de registro \u2014 conciliar antes de aceitar'
|
|
2406
|
+
USING ERRCODE = '23514';
|
|
2407
|
+
END IF;
|
|
2408
|
+
|
|
2409
|
+
-- As linhas que viram estoque. Despesa e servi\xE7o ficam fora daqui e entram s\xF3
|
|
2410
|
+
-- no t\xEDtulo.
|
|
2411
|
+
FOR v_it IN
|
|
2412
|
+
SELECT * FROM public.plg_fiscalbr_inbox_items
|
|
2413
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id
|
|
2414
|
+
AND entry_type IN ('insumo','revenda','patrimonio')
|
|
2415
|
+
ORDER BY item_number
|
|
2416
|
+
LOOP
|
|
2417
|
+
IF v_it.product_id IS NULL THEN
|
|
2418
|
+
RAISE EXCEPTION 'fiscalbr: item % \xE9 % e n\xE3o tem produto vinculado', v_it.item_number, v_it.entry_type
|
|
2419
|
+
USING ERRCODE = '23514';
|
|
2420
|
+
END IF;
|
|
2421
|
+
v_no := v_no + 1;
|
|
2422
|
+
-- A ordem desta lista \xC9 o \`line_no\` que o movimento vai carregar. Guardo o
|
|
2423
|
+
-- id da linha na mesma posi\xE7\xE3o para devolver o movimento ao item sem
|
|
2424
|
+
-- reconsultar \u2014 e sem depender de casar por produto, que repete quando a
|
|
2425
|
+
-- mesma mercadoria vem em duas linhas da nota.
|
|
2426
|
+
v_item_ids := v_item_ids || v_it.id;
|
|
2427
|
+
v_lines := v_lines || jsonb_build_object(
|
|
2428
|
+
'product_id', v_it.product_id,
|
|
2429
|
+
'quantity', coalesce(v_it.stock_quantity, v_it.quantity_commercial * v_it.conversion_factor),
|
|
2430
|
+
'unit_cost', coalesce(v_it.unit_cost_new, v_it.unit_price_commercial / v_it.conversion_factor),
|
|
2431
|
+
'measurement_unit_id', v_it.stock_unit_id,
|
|
2432
|
+
'notes', v_it.description);
|
|
2433
|
+
END LOOP;
|
|
2434
|
+
v_stock_n := v_no;
|
|
2435
|
+
|
|
2436
|
+
IF v_stock_n > 0 THEN
|
|
2437
|
+
IF v_loc IS NULL THEN
|
|
2438
|
+
RAISE EXCEPTION 'fiscalbr: a nota tem % linha(s) de estoque e nenhum local de estoque escolhido', v_stock_n
|
|
2439
|
+
USING ERRCODE = '22023';
|
|
2440
|
+
END IF;
|
|
2441
|
+
-- A chave de acesso \xE9 \xFAnica por documento fiscal no Brasil: \xE9 a chave de
|
|
2442
|
+
-- idempot\xEAncia natural, e \xE9 ela que faz o segundo clique n\xE3o dobrar saldo.
|
|
2443
|
+
v_recv := public.inventory_receive(
|
|
2444
|
+
v_loc, v_lines,
|
|
2445
|
+
jsonb_build_object(
|
|
2446
|
+
'supplier_id', v_doc.supplier_party_id,
|
|
2447
|
+
'date', v_doc.issue_date,
|
|
2448
|
+
'type', 'nfe',
|
|
2449
|
+
'ref', 'NF-e ' || coalesce(v_doc.nfe_number, '?'),
|
|
2450
|
+
'notes', 'Entrada via DANFE NF-e ' || coalesce(v_doc.nfe_number, '?')),
|
|
2451
|
+
'nfe:' || v_doc.access_key);
|
|
2452
|
+
|
|
2453
|
+
-- Cada movimento volta para a SUA linha. Guardar s\xF3 no cabe\xE7alho responderia
|
|
2454
|
+
-- \xABesta nota entrou?\xBB e nunca \xABesta linha entrou?\xBB \u2014 e \xE9 a segunda pergunta
|
|
2455
|
+
-- que a concilia\xE7\xE3o parcial faz.
|
|
2456
|
+
--
|
|
2457
|
+
-- \`line_no\` do movimento \xE9 a posi\xE7\xE3o na lista que montei acima, e
|
|
2458
|
+
-- \`v_item_ids\` est\xE1 na mesma ordem: um join, n\xE3o uma varredura por posi\xE7\xE3o.
|
|
2459
|
+
UPDATE public.plg_fiscalbr_inbox_items i
|
|
2460
|
+
SET stock_movement_id = m.movement_id, updated_at = now()
|
|
2461
|
+
FROM (
|
|
2462
|
+
SELECT v_item_ids[(mov->>'line_no')::int] AS item_id, (mov->>'id')::uuid AS movement_id
|
|
2463
|
+
FROM jsonb_array_elements(coalesce(v_recv->'movements', '[]'::jsonb)) AS t(mov)
|
|
2464
|
+
WHERE (mov->>'line_no') IS NOT NULL
|
|
2465
|
+
) m
|
|
2466
|
+
WHERE i.tenant_id = v_tenant AND i.id = m.item_id;
|
|
2467
|
+
END IF;
|
|
2468
|
+
|
|
2469
|
+
-- \u2500\u2500 O t\xEDtulo \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2470
|
+
-- Total = soma das PARCELAS (regra da financial/084). Nota sem duplicata vira
|
|
2471
|
+
-- uma parcela s\xF3, no vencimento que a nota declarar ou na emiss\xE3o.
|
|
2472
|
+
SELECT coalesce(sum(value), 0),
|
|
2473
|
+
coalesce(jsonb_agg(jsonb_build_object(
|
|
2474
|
+
'number', coalesce(duplicate_index, 1),
|
|
2475
|
+
'due_date', coalesce(due_date, v_doc.issue_date, current_date),
|
|
2476
|
+
'amount', round(value, 2),
|
|
2477
|
+
'metadata', jsonb_strip_nulls(jsonb_build_object(
|
|
2478
|
+
'barcode', barcode, 'pixKey', pix_key, 'paymentType', payment_type,
|
|
2479
|
+
'danfeNumber', number))) ORDER BY duplicate_index), '[]'::jsonb)
|
|
2480
|
+
INTO v_total, v_insts
|
|
2481
|
+
FROM public.plg_fiscalbr_inbox_duplicates
|
|
2482
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
2483
|
+
|
|
2484
|
+
IF jsonb_array_length(v_insts) = 0 THEN
|
|
2485
|
+
v_total := round(coalesce(v_doc.total_value, 0), 2);
|
|
2486
|
+
v_insts := jsonb_build_array(jsonb_build_object(
|
|
2487
|
+
'number', 1, 'due_date', coalesce(v_doc.issue_date, current_date), 'amount', v_total));
|
|
2488
|
+
ELSE
|
|
2489
|
+
v_total := round(v_total, 2);
|
|
2490
|
+
END IF;
|
|
2491
|
+
|
|
2492
|
+
-- A MESMA chave da 084: nota j\xE1 transformada em t\xEDtulo pelo porte hist\xF3rico
|
|
2493
|
+
-- n\xE3o ganha um segundo aqui.
|
|
2494
|
+
v_key := 'danfe:' || v_doc.access_key;
|
|
2495
|
+
|
|
2496
|
+
v_inv := public.financial_create_invoice(
|
|
2497
|
+
'payable', v_total, v_doc.supplier_party_id, NULL, v_doc.unit_id, v_insts,
|
|
2498
|
+
v_doc.issue_date,
|
|
2499
|
+
'NF-e ' || coalesce(v_doc.nfe_number, '?') || ' \xB7 ' || coalesce(v_doc.supplier_name, 'fornecedor'),
|
|
2500
|
+
NULL,
|
|
2501
|
+
jsonb_build_object(
|
|
2502
|
+
'source', 'danfe',
|
|
2503
|
+
'danfe', jsonb_strip_nulls(jsonb_build_object(
|
|
2504
|
+
'accessKey', v_doc.access_key, 'number', v_doc.nfe_number,
|
|
2505
|
+
'series', v_doc.nfe_series, 'supplierName', v_doc.supplier_name,
|
|
2506
|
+
'supplierCnpj', v_doc.supplier_cnpj,
|
|
2507
|
+
-- O total da NOTA continua leg\xEDvel, mesmo quando difere do total do
|
|
2508
|
+
-- t\xEDtulo. \xC9 a diferen\xE7a que a 084 mediu em 6,3% das notas.
|
|
2509
|
+
'noteTotal', v_doc.total_value,
|
|
2510
|
+
'inboxDocumentId', v_doc.id))),
|
|
2511
|
+
v_key);
|
|
2512
|
+
v_invoice := (v_inv->>'invoice_id')::uuid;
|
|
2513
|
+
|
|
2514
|
+
-- As linhas do t\xEDtulo: TODOS os itens, inclusive despesa e servi\xE7o. \xC9 o que
|
|
2515
|
+
-- responde \xABde que essa conta \xE9 feita\xBB.
|
|
2516
|
+
INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)
|
|
2517
|
+
SELECT v_tenant, v_invoice, i.description, i.quantity_commercial, i.unit_price_commercial,
|
|
2518
|
+
round(coalesce(i.total_price, 0), 2), i.item_number,
|
|
2519
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
2520
|
+
'itemKind', i.entry_type, 'referenceId', i.product_id,
|
|
2521
|
+
'chartAccountId', i.chart_account_id, 'ncm', i.ncm, 'cfop', i.cfop,
|
|
2522
|
+
'supplierProductCode', i.supplier_product_code))
|
|
2523
|
+
FROM public.plg_fiscalbr_inbox_items i
|
|
2524
|
+
WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id
|
|
2525
|
+
ORDER BY i.item_number
|
|
2526
|
+
ON CONFLICT DO NOTHING;
|
|
2527
|
+
|
|
2528
|
+
UPDATE public.plg_fiscalbr_inbox_documents SET
|
|
2529
|
+
invoice_id = v_invoice,
|
|
2530
|
+
inventory_operation_id = coalesce((v_recv->>'operation_id')::uuid, inventory_operation_id),
|
|
2531
|
+
stocked_at = CASE WHEN v_stock_n > 0 THEN now() ELSE stocked_at END,
|
|
2532
|
+
status = 'accepted', updated_at = now()
|
|
2533
|
+
WHERE id = v_doc.id;
|
|
2534
|
+
|
|
2535
|
+
-- \u2500\u2500 O de-para aprende \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2536
|
+
-- A decis\xE3o desta nota \xE9 o que faz a pr\xF3xima do mesmo fornecedor chegar
|
|
2537
|
+
-- pronta. Guardar isto \xE9 o que separa \xABimportei XML\xBB de \xABo sistema sabe o meu
|
|
2538
|
+
-- fornecedor\xBB.
|
|
2539
|
+
INSERT INTO public.plg_fiscalbr_supplier_product_map (
|
|
2540
|
+
tenant_id, supplier_cnpj, supplier_product_code, product_id, entry_type,
|
|
2541
|
+
chart_account_id, stock_unit_id, conversion_factor, cost_mode, hit_count, last_used_at)
|
|
2542
|
+
SELECT v_tenant, v_doc.supplier_cnpj, i.supplier_product_code, i.product_id,
|
|
2543
|
+
i.entry_type, coalesce(i.chart_account_id, v_doc.default_chart_account_id),
|
|
2544
|
+
i.stock_unit_id, i.conversion_factor, i.cost_mode, 1, now()
|
|
2545
|
+
FROM public.plg_fiscalbr_inbox_items i
|
|
2546
|
+
WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id
|
|
2547
|
+
AND i.product_id IS NOT NULL
|
|
2548
|
+
AND coalesce(v_doc.supplier_cnpj, '') <> ''
|
|
2549
|
+
AND coalesce(i.supplier_product_code, '') <> ''
|
|
2550
|
+
ON CONFLICT (tenant_id, supplier_cnpj, supplier_product_code) DO UPDATE SET
|
|
2551
|
+
product_id = EXCLUDED.product_id,
|
|
2552
|
+
entry_type = EXCLUDED.entry_type,
|
|
2553
|
+
chart_account_id = coalesce(EXCLUDED.chart_account_id, public.plg_fiscalbr_supplier_product_map.chart_account_id),
|
|
2554
|
+
stock_unit_id = coalesce(EXCLUDED.stock_unit_id, public.plg_fiscalbr_supplier_product_map.stock_unit_id),
|
|
2555
|
+
conversion_factor = EXCLUDED.conversion_factor,
|
|
2556
|
+
cost_mode = EXCLUDED.cost_mode,
|
|
2557
|
+
hit_count = public.plg_fiscalbr_supplier_product_map.hit_count + 1,
|
|
2558
|
+
last_used_at = now(),
|
|
2559
|
+
updated_at = now();
|
|
2560
|
+
|
|
2561
|
+
RETURN jsonb_build_object(
|
|
2562
|
+
'document_id', v_doc.id, 'already_accepted', false,
|
|
2563
|
+
'invoice_id', v_invoice, 'invoice_total', v_total,
|
|
2564
|
+
'installments', jsonb_array_length(v_insts),
|
|
2565
|
+
'operation_id', v_recv->>'operation_id',
|
|
2566
|
+
'stock_lines', v_stock_n,
|
|
2567
|
+
'movements', coalesce(v_recv->'movements', '[]'::jsonb));
|
|
2568
|
+
END $function$;
|
|
2569
|
+
|
|
2570
|
+
REVOKE ALL ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) FROM PUBLIC;
|
|
2571
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) TO authenticated;
|
|
2572
|
+
|
|
2573
|
+
-- \u2500\u2500 Rejeitar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2574
|
+
-- O rodap\xE9 do di\xE1logo do V1 tem dois bot\xF5es, e o outro tamb\xE9m precisa existir:
|
|
2575
|
+
-- recusar a nota \xE9 dizer que ela n\xE3o vira nada aqui \u2014 sem apagar o documento,
|
|
2576
|
+
-- que \xE9 fiscal e tem de continuar leg\xEDvel.
|
|
2577
|
+
|
|
2578
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_reject_inbox_document(
|
|
2579
|
+
p_document_id uuid, p_reason text DEFAULT NULL)
|
|
2580
|
+
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
|
|
2581
|
+
DECLARE
|
|
2582
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
2583
|
+
v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
|
|
2584
|
+
BEGIN
|
|
2585
|
+
SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
|
|
2586
|
+
WHERE tenant_id = v_tenant AND id = p_document_id;
|
|
2587
|
+
IF NOT FOUND THEN
|
|
2588
|
+
RAISE EXCEPTION 'fiscalbr: nota % n\xE3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';
|
|
2589
|
+
END IF;
|
|
2590
|
+
PERFORM public.plg_financial_require_permission('financial.edit', v_doc.unit_id);
|
|
2591
|
+
|
|
2592
|
+
IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN
|
|
2593
|
+
RAISE EXCEPTION 'fiscalbr: nota % j\xE1 virou estoque ou t\xEDtulo \u2014 recusar n\xE3o desfaz', p_document_id
|
|
2594
|
+
USING ERRCODE = '23514';
|
|
2595
|
+
END IF;
|
|
2596
|
+
|
|
2597
|
+
UPDATE public.plg_fiscalbr_inbox_documents SET
|
|
2598
|
+
status = 'rejected',
|
|
2599
|
+
metadata = metadata || jsonb_strip_nulls(jsonb_build_object('rejectReason', p_reason)),
|
|
2600
|
+
reviewed_at = now(), updated_at = now()
|
|
2601
|
+
WHERE id = v_doc.id;
|
|
2602
|
+
|
|
2603
|
+
RETURN jsonb_build_object('document_id', v_doc.id, 'status', 'rejected');
|
|
2604
|
+
END $function$;
|
|
2605
|
+
|
|
2606
|
+
REVOKE ALL ON FUNCTION public.fiscalbr_reject_inbox_document(uuid, text) FROM PUBLIC;
|
|
2607
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_reject_inbox_document(uuid, text) TO authenticated;
|
|
2608
|
+
`;
|
|
2609
|
+
var MIGRATION_010_A_DATA_DE_EMISSAO_E_INSTANTE_E_A_COMPETENCIA_E_DIA = `-- ---------------------------------------------------------------------------
|
|
2610
|
+
-- 010_a_data_de_emissao_e_instante_e_a_competencia_e_dia.sql
|
|
2611
|
+
--
|
|
2612
|
+
-- A 009 n\xE3o conseguia aceitar nota nenhuma. Descoberto no primeiro teste com
|
|
2613
|
+
-- usu\xE1rio logado de verdade, que \xE9 onde ela ia ser usada:
|
|
2614
|
+
--
|
|
2615
|
+
-- POST rpc/fiscalbr_accept_inbox_document \u2192 404
|
|
2616
|
+
-- function public.financial_create_invoice(unknown, numeric, uuid, unknown,
|
|
2617
|
+
-- uuid, jsonb, timestamp with time zone, text, unknown, jsonb, text)
|
|
2618
|
+
-- does not exist
|
|
2619
|
+
--
|
|
2620
|
+
-- \`plg_fiscalbr_inbox_documents.issue_date\` \xE9 **timestamptz** \u2014 a NF-e declara um
|
|
2621
|
+
-- INSTANTE (\`dhEmi\`, com fuso), e a 002 preservou isso. Eu escrevi a chamada
|
|
2622
|
+
-- supondo \`date\`, e \`financial_create_invoice\` pede \`p_competence_date date\`.
|
|
2623
|
+
--
|
|
2624
|
+
-- Nenhum teste pegou porque nenhum podia: o SQL aplica, a fun\xE7\xE3o compila (o corpo
|
|
2625
|
+
-- de PL/pgSQL n\xE3o resolve assinatura em CREATE), e o erro s\xF3 existe na chamada.
|
|
2626
|
+
-- Foi preciso um usu\xE1rio logado percorrendo o caminho.
|
|
2627
|
+
--
|
|
2628
|
+
-- \u2500\u2500 Duas corre\xE7\xF5es, n\xE3o uma \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2629
|
+
--
|
|
2630
|
+
-- 1. \`issue_date::date\` onde a compet\xEAncia \xE9 pedida. Instante \xE9 quando a nota foi
|
|
2631
|
+
-- emitida; compet\xEAncia \xE9 o DIA a que o t\xEDtulo pertence, e o m\xEAs do t\xEDtulo vem
|
|
2632
|
+
-- dela \u2014 um \`timestamptz\` viajando para l\xE1 levaria fuso para dentro de uma
|
|
2633
|
+
-- conta cont\xE1bil.
|
|
2634
|
+
--
|
|
2635
|
+
-- 2. **Todo argumento tipado na chamada.** A chamada posicional passava \`'payable'\`
|
|
2636
|
+
-- e \`NULL\` sem tipo, e o Postgres tem de adivinhar entre as sobrecargas. Adivinhar
|
|
2637
|
+
-- certo hoje e errado quando algu\xE9m acrescentar uma sobrecarga \xE9 o defeito que
|
|
2638
|
+
-- volta meses depois sem ningu\xE9m mexer neste arquivo. Agora vai \`::text\`,
|
|
2639
|
+
-- \`::uuid\`, \`::date\` em tudo.
|
|
2640
|
+
--
|
|
2641
|
+
-- Idempotente: s\xF3 substitui o corpo de uma fun\xE7\xE3o.
|
|
2642
|
+
-- ---------------------------------------------------------------------------
|
|
2643
|
+
|
|
2644
|
+
CREATE OR REPLACE FUNCTION public.fiscalbr_accept_inbox_document(
|
|
2645
|
+
p_document_id uuid,
|
|
2646
|
+
p_location_id uuid DEFAULT NULL)
|
|
2647
|
+
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
|
|
2648
|
+
DECLARE
|
|
2649
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
2650
|
+
v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
|
|
2651
|
+
v_loc uuid;
|
|
2652
|
+
v_lines jsonb := '[]'::jsonb;
|
|
2653
|
+
v_insts jsonb := '[]'::jsonb;
|
|
2654
|
+
v_recv jsonb;
|
|
2655
|
+
v_inv jsonb;
|
|
2656
|
+
v_it record;
|
|
2657
|
+
v_no integer := 0;
|
|
2658
|
+
v_total numeric := 0;
|
|
2659
|
+
v_stock_n integer := 0;
|
|
2660
|
+
v_key text;
|
|
2661
|
+
v_invoice uuid;
|
|
2662
|
+
v_item_ids uuid[] := '{}';
|
|
2663
|
+
-- A compet\xEAncia: o DIA da emiss\xE3o, n\xE3o o instante.
|
|
2664
|
+
v_competence date;
|
|
2665
|
+
BEGIN
|
|
2666
|
+
SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
|
|
2667
|
+
WHERE tenant_id = v_tenant AND id = p_document_id;
|
|
2668
|
+
IF NOT FOUND THEN
|
|
2669
|
+
RAISE EXCEPTION 'fiscalbr: nota % n\xE3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';
|
|
2670
|
+
END IF;
|
|
2671
|
+
PERFORM public.plg_financial_require_permission('financial.create', v_doc.unit_id);
|
|
2672
|
+
|
|
2673
|
+
IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN
|
|
2674
|
+
RETURN jsonb_build_object('document_id', v_doc.id, 'already_accepted', true,
|
|
2675
|
+
'invoice_id', v_doc.invoice_id, 'operation_id', v_doc.inventory_operation_id);
|
|
2676
|
+
END IF;
|
|
2677
|
+
|
|
2678
|
+
v_loc := coalesce(p_location_id, v_doc.default_location_id);
|
|
2679
|
+
v_competence := (v_doc.issue_date)::date;
|
|
2680
|
+
|
|
2681
|
+
IF EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items
|
|
2682
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id
|
|
2683
|
+
AND (entry_type IS NULL OR entry_type = '')) THEN
|
|
2684
|
+
RAISE EXCEPTION 'fiscalbr: h\xE1 item sem tipo de registro \u2014 conciliar antes de aceitar'
|
|
2685
|
+
USING ERRCODE = '23514';
|
|
2686
|
+
END IF;
|
|
2687
|
+
|
|
2688
|
+
FOR v_it IN
|
|
2689
|
+
SELECT * FROM public.plg_fiscalbr_inbox_items
|
|
2690
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id
|
|
2691
|
+
AND entry_type IN ('insumo','revenda','patrimonio')
|
|
2692
|
+
ORDER BY item_number
|
|
2693
|
+
LOOP
|
|
2694
|
+
IF v_it.product_id IS NULL THEN
|
|
2695
|
+
RAISE EXCEPTION 'fiscalbr: item % \xE9 % e n\xE3o tem produto vinculado', v_it.item_number, v_it.entry_type
|
|
2696
|
+
USING ERRCODE = '23514';
|
|
2697
|
+
END IF;
|
|
2698
|
+
v_no := v_no + 1;
|
|
2699
|
+
v_item_ids := v_item_ids || v_it.id;
|
|
2700
|
+
v_lines := v_lines || jsonb_build_object(
|
|
2701
|
+
'product_id', v_it.product_id,
|
|
2702
|
+
'quantity', coalesce(v_it.stock_quantity, v_it.quantity_commercial * v_it.conversion_factor),
|
|
2703
|
+
'unit_cost', coalesce(v_it.unit_cost_new, v_it.unit_price_commercial / v_it.conversion_factor),
|
|
2704
|
+
'measurement_unit_id', v_it.stock_unit_id,
|
|
2705
|
+
'notes', v_it.description);
|
|
2706
|
+
END LOOP;
|
|
2707
|
+
v_stock_n := v_no;
|
|
2708
|
+
|
|
2709
|
+
IF v_stock_n > 0 THEN
|
|
2710
|
+
IF v_loc IS NULL THEN
|
|
2711
|
+
RAISE EXCEPTION 'fiscalbr: a nota tem % linha(s) de estoque e nenhum local de estoque escolhido', v_stock_n
|
|
2712
|
+
USING ERRCODE = '22023';
|
|
2713
|
+
END IF;
|
|
2714
|
+
v_recv := public.inventory_receive(
|
|
2715
|
+
v_loc::uuid,
|
|
2716
|
+
v_lines::jsonb,
|
|
2717
|
+
jsonb_build_object(
|
|
2718
|
+
'supplier_id', v_doc.supplier_party_id,
|
|
2719
|
+
-- O documento do estoque tamb\xE9m quer o DIA.
|
|
2720
|
+
'date', v_competence,
|
|
2721
|
+
'type', 'nfe',
|
|
2722
|
+
'ref', 'NF-e ' || coalesce(v_doc.nfe_number, '?'),
|
|
2723
|
+
'notes', 'Entrada via DANFE NF-e ' || coalesce(v_doc.nfe_number, '?')),
|
|
2724
|
+
('nfe:' || v_doc.access_key)::text);
|
|
2725
|
+
|
|
2726
|
+
UPDATE public.plg_fiscalbr_inbox_items i
|
|
2727
|
+
SET stock_movement_id = m.movement_id, updated_at = now()
|
|
2728
|
+
FROM (
|
|
2729
|
+
SELECT v_item_ids[(mov->>'line_no')::int] AS item_id, (mov->>'id')::uuid AS movement_id
|
|
2730
|
+
FROM jsonb_array_elements(coalesce(v_recv->'movements', '[]'::jsonb)) AS t(mov)
|
|
2731
|
+
WHERE (mov->>'line_no') IS NOT NULL
|
|
2732
|
+
) m
|
|
2733
|
+
WHERE i.tenant_id = v_tenant AND i.id = m.item_id;
|
|
2734
|
+
END IF;
|
|
2735
|
+
|
|
2736
|
+
SELECT coalesce(sum(value), 0),
|
|
2737
|
+
coalesce(jsonb_agg(jsonb_build_object(
|
|
2738
|
+
'number', coalesce(duplicate_index, 1),
|
|
2739
|
+
'due_date', coalesce(due_date, v_competence, current_date),
|
|
2740
|
+
'amount', round(value, 2),
|
|
2741
|
+
'metadata', jsonb_strip_nulls(jsonb_build_object(
|
|
2742
|
+
'barcode', barcode, 'pixKey', pix_key, 'paymentType', payment_type,
|
|
2743
|
+
'danfeNumber', number))) ORDER BY duplicate_index), '[]'::jsonb)
|
|
2744
|
+
INTO v_total, v_insts
|
|
2745
|
+
FROM public.plg_fiscalbr_inbox_duplicates
|
|
2746
|
+
WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;
|
|
2747
|
+
|
|
2748
|
+
IF jsonb_array_length(v_insts) = 0 THEN
|
|
2749
|
+
v_total := round(coalesce(v_doc.total_value, 0), 2);
|
|
2750
|
+
v_insts := jsonb_build_array(jsonb_build_object(
|
|
2751
|
+
'number', 1, 'due_date', coalesce(v_competence, current_date), 'amount', v_total));
|
|
2752
|
+
ELSE
|
|
2753
|
+
v_total := round(v_total, 2);
|
|
2754
|
+
END IF;
|
|
2755
|
+
|
|
2756
|
+
v_key := 'danfe:' || v_doc.access_key;
|
|
2757
|
+
|
|
2758
|
+
-- TODO argumento tipado. A chamada posicional sem tipo obrigava o Postgres a
|
|
2759
|
+
-- adivinhar entre sobrecargas, e foi assim que esta fun\xE7\xE3o nasceu quebrada.
|
|
2760
|
+
v_inv := public.financial_create_invoice(
|
|
2761
|
+
'payable'::text,
|
|
2762
|
+
v_total::numeric,
|
|
2763
|
+
v_doc.supplier_party_id::uuid,
|
|
2764
|
+
NULL::uuid,
|
|
2765
|
+
v_doc.unit_id::uuid,
|
|
2766
|
+
v_insts::jsonb,
|
|
2767
|
+
v_competence::date,
|
|
2768
|
+
('NF-e ' || coalesce(v_doc.nfe_number, '?') || ' \xB7 ' || coalesce(v_doc.supplier_name, 'fornecedor'))::text,
|
|
2769
|
+
NULL::text,
|
|
2770
|
+
jsonb_build_object(
|
|
2771
|
+
'source', 'danfe',
|
|
2772
|
+
'danfe', jsonb_strip_nulls(jsonb_build_object(
|
|
2773
|
+
'accessKey', v_doc.access_key, 'number', v_doc.nfe_number,
|
|
2774
|
+
'series', v_doc.nfe_series, 'supplierName', v_doc.supplier_name,
|
|
2775
|
+
'supplierCnpj', v_doc.supplier_cnpj,
|
|
2776
|
+
'noteTotal', v_doc.total_value,
|
|
2777
|
+
'inboxDocumentId', v_doc.id)))::jsonb,
|
|
2778
|
+
v_key::text);
|
|
2779
|
+
v_invoice := (v_inv->>'invoice_id')::uuid;
|
|
2780
|
+
|
|
2781
|
+
INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)
|
|
2782
|
+
SELECT v_tenant, v_invoice, i.description, i.quantity_commercial, i.unit_price_commercial,
|
|
2783
|
+
round(coalesce(i.total_price, 0), 2), i.item_number,
|
|
2784
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
2785
|
+
'itemKind', i.entry_type, 'referenceId', i.product_id,
|
|
2786
|
+
'chartAccountId', i.chart_account_id, 'ncm', i.ncm, 'cfop', i.cfop,
|
|
2787
|
+
'supplierProductCode', i.supplier_product_code))
|
|
2788
|
+
FROM public.plg_fiscalbr_inbox_items i
|
|
2789
|
+
WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id
|
|
2790
|
+
ORDER BY i.item_number
|
|
2791
|
+
ON CONFLICT DO NOTHING;
|
|
2792
|
+
|
|
2793
|
+
UPDATE public.plg_fiscalbr_inbox_documents SET
|
|
2794
|
+
invoice_id = v_invoice,
|
|
2795
|
+
inventory_operation_id = coalesce((v_recv->>'operation_id')::uuid, inventory_operation_id),
|
|
2796
|
+
stocked_at = CASE WHEN v_stock_n > 0 THEN now() ELSE stocked_at END,
|
|
2797
|
+
status = 'accepted', updated_at = now()
|
|
2798
|
+
WHERE id = v_doc.id;
|
|
2799
|
+
|
|
2800
|
+
INSERT INTO public.plg_fiscalbr_supplier_product_map (
|
|
2801
|
+
tenant_id, supplier_cnpj, supplier_product_code, product_id, entry_type,
|
|
2802
|
+
chart_account_id, stock_unit_id, conversion_factor, cost_mode, hit_count, last_used_at)
|
|
2803
|
+
SELECT v_tenant, v_doc.supplier_cnpj, i.supplier_product_code, i.product_id,
|
|
2804
|
+
i.entry_type, coalesce(i.chart_account_id, v_doc.default_chart_account_id),
|
|
2805
|
+
i.stock_unit_id, i.conversion_factor, i.cost_mode, 1, now()
|
|
2806
|
+
FROM public.plg_fiscalbr_inbox_items i
|
|
2807
|
+
WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id
|
|
2808
|
+
AND i.product_id IS NOT NULL
|
|
2809
|
+
AND coalesce(v_doc.supplier_cnpj, '') <> ''
|
|
2810
|
+
AND coalesce(i.supplier_product_code, '') <> ''
|
|
2811
|
+
ON CONFLICT (tenant_id, supplier_cnpj, supplier_product_code) DO UPDATE SET
|
|
2812
|
+
product_id = EXCLUDED.product_id,
|
|
2813
|
+
entry_type = EXCLUDED.entry_type,
|
|
2814
|
+
chart_account_id = coalesce(EXCLUDED.chart_account_id, public.plg_fiscalbr_supplier_product_map.chart_account_id),
|
|
2815
|
+
stock_unit_id = coalesce(EXCLUDED.stock_unit_id, public.plg_fiscalbr_supplier_product_map.stock_unit_id),
|
|
2816
|
+
conversion_factor = EXCLUDED.conversion_factor,
|
|
2817
|
+
cost_mode = EXCLUDED.cost_mode,
|
|
2818
|
+
hit_count = public.plg_fiscalbr_supplier_product_map.hit_count + 1,
|
|
2819
|
+
last_used_at = now(),
|
|
2820
|
+
updated_at = now();
|
|
2821
|
+
|
|
2822
|
+
RETURN jsonb_build_object(
|
|
2823
|
+
'document_id', v_doc.id, 'already_accepted', false,
|
|
2824
|
+
'invoice_id', v_invoice, 'invoice_total', v_total,
|
|
2825
|
+
'installments', jsonb_array_length(v_insts),
|
|
2826
|
+
'operation_id', v_recv->>'operation_id',
|
|
2827
|
+
'stock_lines', v_stock_n,
|
|
2828
|
+
'movements', coalesce(v_recv->'movements', '[]'::jsonb));
|
|
2829
|
+
END $function$;
|
|
2830
|
+
|
|
2831
|
+
REVOKE ALL ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) FROM PUBLIC;
|
|
2832
|
+
GRANT EXECUTE ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) TO authenticated;
|
|
2833
|
+
`;
|
|
1836
2834
|
var MIGRATIONS = [
|
|
1837
2835
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
1838
2836
|
{ id: "001_preserve_terminal_results_and_recover_leases", sql: MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES },
|
|
@@ -1841,7 +2839,10 @@ var MIGRATIONS = [
|
|
|
1841
2839
|
{ id: "004_o_grupo_fiscal_do_v1_repete_nome", sql: MIGRATION_004_O_GRUPO_FISCAL_DO_V1_REPETE_NOME },
|
|
1842
2840
|
{ id: "005_a_nota_emitida_e_um_documento", sql: MIGRATION_005_A_NOTA_EMITIDA_E_UM_DOCUMENTO },
|
|
1843
2841
|
{ id: "006_o_grupo_fiscal_ganha_o_rail", sql: MIGRATION_006_O_GRUPO_FISCAL_GANHA_O_RAIL },
|
|
1844
|
-
{ id: "007_a_rls_do_documento_passa_a_ser_legivel", sql: MIGRATION_007_A_RLS_DO_DOCUMENTO_PASSA_A_SER_LEGIVEL }
|
|
2842
|
+
{ id: "007_a_rls_do_documento_passa_a_ser_legivel", sql: MIGRATION_007_A_RLS_DO_DOCUMENTO_PASSA_A_SER_LEGIVEL },
|
|
2843
|
+
{ id: "008_a_nota_recebida_guarda_a_decisao_de_entrada", sql: MIGRATION_008_A_NOTA_RECEBIDA_GUARDA_A_DECISAO_DE_ENTRADA },
|
|
2844
|
+
{ 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 }
|
|
1845
2846
|
];
|
|
1846
2847
|
var taxGroupEntity = {
|
|
1847
2848
|
name: "Grupo fiscal",
|
|
@@ -1929,6 +2930,1591 @@ var fiscalRegistries = [
|
|
|
1929
2930
|
}
|
|
1930
2931
|
];
|
|
1931
2932
|
|
|
2933
|
+
// src/lib/nfe-xml.ts
|
|
2934
|
+
var NfeParseError = class extends Error {
|
|
2935
|
+
constructor(message) {
|
|
2936
|
+
super(message);
|
|
2937
|
+
this.name = "NfeParseError";
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
var ENTITIES = {
|
|
2941
|
+
amp: "&",
|
|
2942
|
+
lt: "<",
|
|
2943
|
+
gt: ">",
|
|
2944
|
+
quot: '"',
|
|
2945
|
+
apos: "'"
|
|
2946
|
+
};
|
|
2947
|
+
function decode(raw) {
|
|
2948
|
+
return raw.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body) => {
|
|
2949
|
+
if (body.startsWith("#x") || body.startsWith("#X")) return String.fromCodePoint(parseInt(body.slice(2), 16));
|
|
2950
|
+
if (body.startsWith("#")) return String.fromCodePoint(parseInt(body.slice(1), 10));
|
|
2951
|
+
return ENTITIES[body] ?? whole;
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
var bare = (name) => {
|
|
2955
|
+
const i = name.indexOf(":");
|
|
2956
|
+
return i < 0 ? name : name.slice(i + 1);
|
|
2957
|
+
};
|
|
2958
|
+
function parseXml(xml) {
|
|
2959
|
+
const src = xml.replace(/<\?[\s\S]*?\?>/g, "").replace(/<!--[\s\S]*?-->/g, "").replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, (_m, inner) => inner).replace(/<!DOCTYPE[^>]*>/gi, "");
|
|
2960
|
+
const root = { name: "#root", attrs: {}, children: [], text: "" };
|
|
2961
|
+
const stack = [root];
|
|
2962
|
+
const tag = /<\s*(\/?)\s*([A-Za-z_][\w.:-]*)((?:\s+[\w.:-]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(\/?)\s*>/g;
|
|
2963
|
+
let cursor = 0;
|
|
2964
|
+
let m;
|
|
2965
|
+
while ((m = tag.exec(src)) !== null) {
|
|
2966
|
+
const [whole, closing, rawName, rawAttrs, selfClosing] = m;
|
|
2967
|
+
const top = stack[stack.length - 1];
|
|
2968
|
+
const between = src.slice(cursor, m.index);
|
|
2969
|
+
if (between.trim()) top.text += decode(between);
|
|
2970
|
+
cursor = m.index + whole.length;
|
|
2971
|
+
if (closing) {
|
|
2972
|
+
if (stack.length > 1) stack.pop();
|
|
2973
|
+
continue;
|
|
2974
|
+
}
|
|
2975
|
+
const attrs = {};
|
|
2976
|
+
const attrRe = /([\w.:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
2977
|
+
let a;
|
|
2978
|
+
while ((a = attrRe.exec(rawAttrs ?? "")) !== null) {
|
|
2979
|
+
attrs[bare(a[1])] = decode(a[2] ?? a[3] ?? "");
|
|
2980
|
+
}
|
|
2981
|
+
const node = { name: bare(rawName), attrs, children: [], text: "" };
|
|
2982
|
+
top.children.push(node);
|
|
2983
|
+
if (!selfClosing) stack.push(node);
|
|
2984
|
+
}
|
|
2985
|
+
return root;
|
|
2986
|
+
}
|
|
2987
|
+
var kids = (node, name) => node ? node.children.filter((c) => c.name === name) : [];
|
|
2988
|
+
var kid = (node, name) => kids(node, name)[0];
|
|
2989
|
+
function at(node, path) {
|
|
2990
|
+
let cur = node;
|
|
2991
|
+
for (const step of path.split(".")) {
|
|
2992
|
+
cur = kid(cur, step);
|
|
2993
|
+
if (!cur) return void 0;
|
|
2994
|
+
}
|
|
2995
|
+
return cur;
|
|
2996
|
+
}
|
|
2997
|
+
var str = (node, path) => {
|
|
2998
|
+
const target = path ? at(node, path) : node;
|
|
2999
|
+
const value = target?.text.trim();
|
|
3000
|
+
return value ? value : void 0;
|
|
3001
|
+
};
|
|
3002
|
+
var num = (node, path) => {
|
|
3003
|
+
const value = str(node, path);
|
|
3004
|
+
if (value === void 0) return void 0;
|
|
3005
|
+
const parsed = Number(value);
|
|
3006
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3007
|
+
};
|
|
3008
|
+
var zero = (node, path) => num(node, path) ?? 0;
|
|
3009
|
+
var PAYMENT_BY_CODE = {
|
|
3010
|
+
"01": "dinheiro",
|
|
3011
|
+
"02": "outro",
|
|
3012
|
+
// cheque
|
|
3013
|
+
"03": "cartao_credito",
|
|
3014
|
+
"04": "cartao_debito",
|
|
3015
|
+
"05": "credito_loja",
|
|
3016
|
+
"10": "outro",
|
|
3017
|
+
// vale alimentação
|
|
3018
|
+
"11": "outro",
|
|
3019
|
+
// vale refeição
|
|
3020
|
+
"12": "vale_presente",
|
|
3021
|
+
"13": "outro",
|
|
3022
|
+
// vale combustível
|
|
3023
|
+
"14": "duplicata_mercantil",
|
|
3024
|
+
"15": "boleto",
|
|
3025
|
+
"16": "outro",
|
|
3026
|
+
// depósito bancário
|
|
3027
|
+
"17": "pix",
|
|
3028
|
+
"18": "outro",
|
|
3029
|
+
// transferência bancária
|
|
3030
|
+
"19": "outro",
|
|
3031
|
+
// programa de fidelidade
|
|
3032
|
+
"90": "sem_pagamento",
|
|
3033
|
+
"99": "outro"
|
|
3034
|
+
};
|
|
3035
|
+
var PAYMENT_LABEL = {
|
|
3036
|
+
dinheiro: "Dinheiro",
|
|
3037
|
+
cartao_credito: "Cart\xE3o de cr\xE9dito",
|
|
3038
|
+
cartao_debito: "Cart\xE3o de d\xE9bito",
|
|
3039
|
+
credito_loja: "Cr\xE9dito na loja",
|
|
3040
|
+
vale_presente: "Vale presente",
|
|
3041
|
+
duplicata_mercantil: "Duplicata mercantil",
|
|
3042
|
+
boleto: "Boleto",
|
|
3043
|
+
pix: "PIX",
|
|
3044
|
+
sem_pagamento: "Sem pagamento",
|
|
3045
|
+
outro: "Outro"
|
|
3046
|
+
};
|
|
3047
|
+
function paymentTypeFromCode(code) {
|
|
3048
|
+
if (!code) return "outro";
|
|
3049
|
+
return PAYMENT_BY_CODE[code.padStart(2, "0")] ?? "outro";
|
|
3050
|
+
}
|
|
3051
|
+
function paymentLabel(type) {
|
|
3052
|
+
return PAYMENT_LABEL[type] ?? "Outro";
|
|
3053
|
+
}
|
|
3054
|
+
var onlyDigits = (value) => (value ?? "").replace(/\D+/g, "");
|
|
3055
|
+
function parseNfeXml(xml) {
|
|
3056
|
+
if (!xml || !xml.trim()) throw new NfeParseError("O arquivo est\xE1 vazio.");
|
|
3057
|
+
const root = parseXml(xml);
|
|
3058
|
+
const proc = kid(root, "nfeProc");
|
|
3059
|
+
const nfe = kid(proc ?? root, "NFe") ?? kid(root, "NFe");
|
|
3060
|
+
const inf = kid(nfe, "infNFe");
|
|
3061
|
+
if (!inf) {
|
|
3062
|
+
throw new NfeParseError(
|
|
3063
|
+
"Este arquivo n\xE3o \xE9 uma NF-e: n\xE3o encontrei <infNFe>. Se veio do fornecedor, pe\xE7a o XML da nota, n\xE3o o DANFE em PDF."
|
|
3064
|
+
);
|
|
3065
|
+
}
|
|
3066
|
+
const accessKey = onlyDigits(inf.attrs.Id ?? inf.attrs.id ?? "");
|
|
3067
|
+
if (accessKey.length !== 44) {
|
|
3068
|
+
throw new NfeParseError(
|
|
3069
|
+
`A chave de acesso tem ${accessKey.length} d\xEDgitos e deveria ter 44. O arquivo pode estar truncado.`
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
const ide = kid(inf, "ide");
|
|
3073
|
+
const model = str(ide, "mod") ?? "55";
|
|
3074
|
+
if (model !== "55") {
|
|
3075
|
+
throw new NfeParseError(
|
|
3076
|
+
`Este XML \xE9 modelo ${model}${model === "65" ? " (NFC-e, nota de venda no balc\xE3o)" : ""}, e a entrada de notas recebe NF-e modelo 55 de fornecedor.`
|
|
3077
|
+
);
|
|
3078
|
+
}
|
|
3079
|
+
const emit = kid(inf, "emit");
|
|
3080
|
+
const ender = kid(emit, "enderEmit");
|
|
3081
|
+
const total = at(inf, "total.ICMSTot");
|
|
3082
|
+
const dhEmi = str(ide, "dhEmi") ?? str(ide, "dEmi") ?? "";
|
|
3083
|
+
const items = kids(inf, "det").map((det, i) => {
|
|
3084
|
+
const prod = kid(det, "prod");
|
|
3085
|
+
const imposto = kid(det, "imposto");
|
|
3086
|
+
const icms = kid(imposto, "ICMS")?.children[0];
|
|
3087
|
+
const pis = kid(imposto, "PIS")?.children[0];
|
|
3088
|
+
const cofins = kid(imposto, "COFINS")?.children[0];
|
|
3089
|
+
const ipi = kid(kid(imposto, "IPI"), "IPITrib");
|
|
3090
|
+
return {
|
|
3091
|
+
itemNumber: Number(det.attrs.nItem ?? i + 1) || i + 1,
|
|
3092
|
+
supplierProductCode: str(prod, "cProd") ?? "",
|
|
3093
|
+
eanBarcode: str(prod, "cEAN"),
|
|
3094
|
+
description: str(prod, "xProd") ?? "(sem descri\xE7\xE3o)",
|
|
3095
|
+
ncm: str(prod, "NCM"),
|
|
3096
|
+
cest: str(prod, "CEST"),
|
|
3097
|
+
cfop: str(prod, "CFOP"),
|
|
3098
|
+
unit: str(prod, "uCom"),
|
|
3099
|
+
quantity: zero(prod, "qCom"),
|
|
3100
|
+
unitPrice: zero(prod, "vUnCom"),
|
|
3101
|
+
total: zero(prod, "vProd"),
|
|
3102
|
+
discount: num(prod, "vDesc"),
|
|
3103
|
+
freight: num(prod, "vFrete"),
|
|
3104
|
+
other: num(prod, "vOutro"),
|
|
3105
|
+
icmsCst: str(icms, "CST"),
|
|
3106
|
+
icmsCsosn: str(icms, "CSOSN"),
|
|
3107
|
+
icmsOrigin: str(icms, "orig"),
|
|
3108
|
+
icmsBase: num(icms, "vBC"),
|
|
3109
|
+
icmsRate: num(icms, "pICMS"),
|
|
3110
|
+
icmsValue: num(icms, "vICMS"),
|
|
3111
|
+
pisValue: num(pis, "vPIS"),
|
|
3112
|
+
cofinsValue: num(cofins, "vCOFINS"),
|
|
3113
|
+
ipiValue: num(ipi, "vIPI")
|
|
3114
|
+
};
|
|
3115
|
+
});
|
|
3116
|
+
if (items.length === 0) {
|
|
3117
|
+
throw new NfeParseError("A nota n\xE3o tem nenhum item \u2014 n\xE3o h\xE1 o que dar entrada.");
|
|
3118
|
+
}
|
|
3119
|
+
const cobr = kid(inf, "cobr");
|
|
3120
|
+
const duplicates = kids(cobr, "dup").map((dup, i) => ({
|
|
3121
|
+
index: i + 1,
|
|
3122
|
+
number: str(dup, "nDup"),
|
|
3123
|
+
dueDate: str(dup, "dVenc"),
|
|
3124
|
+
value: zero(dup, "vDup")
|
|
3125
|
+
}));
|
|
3126
|
+
const payments = kids(kid(inf, "pag"), "detPag").map((det, i) => {
|
|
3127
|
+
const code = str(det, "tPag");
|
|
3128
|
+
const type = paymentTypeFromCode(code);
|
|
3129
|
+
return {
|
|
3130
|
+
index: i + 1,
|
|
3131
|
+
indicator: str(det, "indPag"),
|
|
3132
|
+
code,
|
|
3133
|
+
label: paymentLabel(type),
|
|
3134
|
+
value: zero(det, "vPag")
|
|
3135
|
+
};
|
|
3136
|
+
});
|
|
3137
|
+
const prot = at(proc, "protNFe.infProt");
|
|
3138
|
+
return {
|
|
3139
|
+
accessKey,
|
|
3140
|
+
nfeNumber: str(ide, "nNF") ?? "",
|
|
3141
|
+
nfeSeries: str(ide, "serie") ?? "",
|
|
3142
|
+
nfeModel: model,
|
|
3143
|
+
// The layout writes an ISO instant with offset; the inbox stores a date.
|
|
3144
|
+
issueDate: dhEmi.slice(0, 10),
|
|
3145
|
+
natureOperation: str(ide, "natOp"),
|
|
3146
|
+
supplier: {
|
|
3147
|
+
cnpj: onlyDigits(str(emit, "CNPJ") ?? str(emit, "CPF")),
|
|
3148
|
+
name: str(emit, "xNome") ?? "",
|
|
3149
|
+
tradeName: str(emit, "xFant"),
|
|
3150
|
+
ie: str(emit, "IE"),
|
|
3151
|
+
city: str(ender, "xMun"),
|
|
3152
|
+
state: str(ender, "UF")
|
|
3153
|
+
},
|
|
3154
|
+
totals: {
|
|
3155
|
+
total: zero(total, "vNF"),
|
|
3156
|
+
products: zero(total, "vProd"),
|
|
3157
|
+
discount: zero(total, "vDesc"),
|
|
3158
|
+
freight: zero(total, "vFrete"),
|
|
3159
|
+
insurance: zero(total, "vSeg"),
|
|
3160
|
+
other: zero(total, "vOutro"),
|
|
3161
|
+
icmsBase: num(total, "vBC"),
|
|
3162
|
+
icmsValue: num(total, "vICMS"),
|
|
3163
|
+
pisValue: num(total, "vPIS"),
|
|
3164
|
+
cofinsValue: num(total, "vCOFINS"),
|
|
3165
|
+
ipiValue: num(total, "vIPI")
|
|
3166
|
+
},
|
|
3167
|
+
items,
|
|
3168
|
+
duplicates,
|
|
3169
|
+
payments,
|
|
3170
|
+
additionalInfo: str(at(inf, "infAdic"), "infCpl"),
|
|
3171
|
+
protocol: str(prot, "nProt"),
|
|
3172
|
+
authorizedAt: str(prot, "dhRecbto")
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
function toStagePayload(note, options = {}) {
|
|
3176
|
+
return {
|
|
3177
|
+
access_key: note.accessKey,
|
|
3178
|
+
nfe_number: note.nfeNumber,
|
|
3179
|
+
nfe_series: note.nfeSeries,
|
|
3180
|
+
nfe_model: note.nfeModel,
|
|
3181
|
+
issue_date: note.issueDate,
|
|
3182
|
+
nature_operation: note.natureOperation ?? null,
|
|
3183
|
+
additional_info: note.additionalInfo ?? null,
|
|
3184
|
+
unit_id: options.unitId ?? null,
|
|
3185
|
+
source: options.source ?? "xml_upload",
|
|
3186
|
+
supplier: {
|
|
3187
|
+
cnpj: note.supplier.cnpj,
|
|
3188
|
+
name: note.supplier.name,
|
|
3189
|
+
ie: note.supplier.ie ?? null
|
|
3190
|
+
},
|
|
3191
|
+
totals: {
|
|
3192
|
+
total: note.totals.total,
|
|
3193
|
+
products: note.totals.products,
|
|
3194
|
+
discount: note.totals.discount,
|
|
3195
|
+
freight: note.totals.freight,
|
|
3196
|
+
insurance: note.totals.insurance,
|
|
3197
|
+
other: note.totals.other
|
|
3198
|
+
},
|
|
3199
|
+
items: note.items.map((i) => ({
|
|
3200
|
+
item_number: i.itemNumber,
|
|
3201
|
+
supplier_product_code: i.supplierProductCode,
|
|
3202
|
+
ean_barcode: i.eanBarcode ?? null,
|
|
3203
|
+
description: i.description,
|
|
3204
|
+
ncm: i.ncm ?? null,
|
|
3205
|
+
cest: i.cest ?? null,
|
|
3206
|
+
cfop: i.cfop ?? null,
|
|
3207
|
+
unit: i.unit ?? null,
|
|
3208
|
+
quantity: i.quantity,
|
|
3209
|
+
unit_price: i.unitPrice,
|
|
3210
|
+
total: i.total,
|
|
3211
|
+
discount: i.discount ?? null,
|
|
3212
|
+
freight: i.freight ?? null,
|
|
3213
|
+
other: i.other ?? null,
|
|
3214
|
+
icms_cst: i.icmsCst ?? null,
|
|
3215
|
+
icms_csosn: i.icmsCsosn ?? null,
|
|
3216
|
+
icms_base: i.icmsBase ?? null,
|
|
3217
|
+
icms_rate: i.icmsRate ?? null,
|
|
3218
|
+
icms_value: i.icmsValue ?? null,
|
|
3219
|
+
pis_value: i.pisValue ?? null,
|
|
3220
|
+
cofins_value: i.cofinsValue ?? null,
|
|
3221
|
+
ipi_value: i.ipiValue ?? null
|
|
3222
|
+
})),
|
|
3223
|
+
// The instalment carries the payment form the note declared, so the
|
|
3224
|
+
// reconciliation dialog opens with `Boleto` already chosen instead of blank.
|
|
3225
|
+
duplicates: note.duplicates.map((d) => ({
|
|
3226
|
+
index: d.index,
|
|
3227
|
+
number: d.number ?? null,
|
|
3228
|
+
due_date: d.dueDate ?? null,
|
|
3229
|
+
value: d.value,
|
|
3230
|
+
payment_type: note.payments.length === 1 ? paymentTypeFromCode(note.payments[0].code) : null
|
|
3231
|
+
})),
|
|
3232
|
+
payments: note.payments.map((p) => ({
|
|
3233
|
+
index: p.index,
|
|
3234
|
+
indicator: p.indicator ?? null,
|
|
3235
|
+
code: p.code ?? null,
|
|
3236
|
+
label: p.label ?? null,
|
|
3237
|
+
value: p.value
|
|
3238
|
+
}))
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
// src/data/inbox.ts
|
|
3243
|
+
function sb() {
|
|
3244
|
+
const supabase = getSupabaseClientOptional();
|
|
3245
|
+
if (!supabase) throw new Error("Supabase not initialized");
|
|
3246
|
+
return supabase;
|
|
3247
|
+
}
|
|
3248
|
+
var NOTE_COLUMNS = `
|
|
3249
|
+
id, access_key, nfe_number, nfe_series, issue_date, received_at, status,
|
|
3250
|
+
manifestation_status, manifested_at, manifestation_attempts,
|
|
3251
|
+
nature_operation, supplier_name, supplier_cnpj, supplier_party_id,
|
|
3252
|
+
total_value, total_products, total_discount, total_freight,
|
|
3253
|
+
invoice_id, inventory_operation_id, stocked_at, reviewed_at,
|
|
3254
|
+
default_chart_account_id, default_location_id, unit_id, document_id, metadata,
|
|
3255
|
+
items:plg_fiscalbr_inbox_items(id, reconciled, entry_type),
|
|
3256
|
+
duplicates:plg_fiscalbr_inbox_duplicates(id, value)
|
|
3257
|
+
`;
|
|
3258
|
+
function rowToNote(row) {
|
|
3259
|
+
const items = row.items ?? [];
|
|
3260
|
+
const duplicates = row.duplicates ?? [];
|
|
3261
|
+
return {
|
|
3262
|
+
id: String(row.id),
|
|
3263
|
+
accessKey: String(row.access_key ?? ""),
|
|
3264
|
+
number: String(row.nfe_number ?? ""),
|
|
3265
|
+
series: String(row.nfe_series ?? ""),
|
|
3266
|
+
issueDate: row.issue_date ?? null,
|
|
3267
|
+
receivedAt: row.received_at ?? null,
|
|
3268
|
+
status: row.status ?? "pending",
|
|
3269
|
+
manifestationStatus: row.manifestation_status ?? null,
|
|
3270
|
+
manifestedAt: row.manifested_at ?? null,
|
|
3271
|
+
manifestationAttempts: Number(row.manifestation_attempts ?? 0),
|
|
3272
|
+
natureOperation: row.nature_operation ?? null,
|
|
3273
|
+
supplierName: row.supplier_name ?? "",
|
|
3274
|
+
supplierCnpj: row.supplier_cnpj ?? null,
|
|
3275
|
+
supplierPartyId: row.supplier_party_id ?? null,
|
|
3276
|
+
total: Number(row.total_value ?? 0),
|
|
3277
|
+
itemCount: items.length,
|
|
3278
|
+
reconciledCount: items.filter((i) => i.reconciled).length,
|
|
3279
|
+
instalmentCount: duplicates.length,
|
|
3280
|
+
// The sum of instalments, because that is the total the payable is opened
|
|
3281
|
+
// with (financial/084) — showing the note total here would make the list and
|
|
3282
|
+
// Contas a Pagar disagree on 6,3% of notes.
|
|
3283
|
+
instalmentTotal: duplicates.reduce((acc, d) => acc + Number(d.value ?? 0), 0),
|
|
3284
|
+
invoiceId: row.invoice_id ?? null,
|
|
3285
|
+
operationId: row.inventory_operation_id ?? null,
|
|
3286
|
+
stockedAt: row.stocked_at ?? null,
|
|
3287
|
+
documentId: row.document_id ?? null,
|
|
3288
|
+
unitId: row.unit_id ?? null
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
async function listInboxNotes(options = {}) {
|
|
3292
|
+
const { filter = "all", search, limit = 200 } = options;
|
|
3293
|
+
let query = sb().from("plg_fiscalbr_inbox_documents").select(NOTE_COLUMNS);
|
|
3294
|
+
if (filter === "stocked") query = query.not("stocked_at", "is", null);
|
|
3295
|
+
if (filter === "pending") query = query.is("stocked_at", null);
|
|
3296
|
+
if (filter === "rejected") query = query.eq("status", "rejected");
|
|
3297
|
+
if (filter === "to_manifest") query = query.not("manifestation_status", "eq", "confirmada");
|
|
3298
|
+
if (search?.trim()) {
|
|
3299
|
+
const term = search.trim();
|
|
3300
|
+
query = query.or(
|
|
3301
|
+
`supplier_name.ilike.%${term}%,nfe_number.ilike.%${term}%,access_key.ilike.%${term}%`
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
const { data, error } = await query.order("issue_date", { ascending: false, nullsFirst: false }).order("received_at", { ascending: false }).limit(limit);
|
|
3305
|
+
if (error) throw error;
|
|
3306
|
+
return (data ?? []).map(rowToNote);
|
|
3307
|
+
}
|
|
3308
|
+
var ITEM_COLUMNS = `
|
|
3309
|
+
id, item_number, supplier_product_code, ean_barcode, description, ncm, cest, cfop,
|
|
3310
|
+
unit_commercial, quantity_commercial, unit_price_commercial, total_price, discount,
|
|
3311
|
+
icms_cst, icms_csosn, icms_base, icms_rate, icms_value, pis_value, cofins_value, ipi_value,
|
|
3312
|
+
product_id, entry_type, chart_account_id, stock_unit_id, stock_quantity,
|
|
3313
|
+
conversion_factor, cost_mode, unit_cost_new, reconciled, stock_movement_id,
|
|
3314
|
+
shortage_quantity, shortage_reason, shortage_note
|
|
3315
|
+
`;
|
|
3316
|
+
function rowToItem(row) {
|
|
3317
|
+
const quantity = Number(row.quantity_commercial ?? 0);
|
|
3318
|
+
const factor = Number(row.conversion_factor ?? 1) || 1;
|
|
3319
|
+
return {
|
|
3320
|
+
id: String(row.id),
|
|
3321
|
+
itemNumber: Number(row.item_number ?? 0),
|
|
3322
|
+
supplierProductCode: row.supplier_product_code ?? "",
|
|
3323
|
+
eanBarcode: row.ean_barcode ?? null,
|
|
3324
|
+
description: row.description ?? "",
|
|
3325
|
+
ncm: row.ncm ?? null,
|
|
3326
|
+
cest: row.cest ?? null,
|
|
3327
|
+
cfop: row.cfop ?? null,
|
|
3328
|
+
unit: row.unit_commercial ?? null,
|
|
3329
|
+
quantity,
|
|
3330
|
+
unitPrice: Number(row.unit_price_commercial ?? 0),
|
|
3331
|
+
total: Number(row.total_price ?? 0),
|
|
3332
|
+
discount: row.discount == null ? null : Number(row.discount),
|
|
3333
|
+
icmsCst: row.icms_cst ?? null,
|
|
3334
|
+
icmsCsosn: row.icms_csosn ?? null,
|
|
3335
|
+
icmsValue: row.icms_value == null ? null : Number(row.icms_value),
|
|
3336
|
+
productId: row.product_id ?? null,
|
|
3337
|
+
entryType: row.entry_type ?? null,
|
|
3338
|
+
chartAccountId: row.chart_account_id ?? null,
|
|
3339
|
+
stockUnitId: row.stock_unit_id ?? null,
|
|
3340
|
+
conversionFactor: factor,
|
|
3341
|
+
// Derived AND stored by the RPC. Reading the stored value keeps the screen
|
|
3342
|
+
// showing the number the operator accepted, and falls back to the product of
|
|
3343
|
+
// the two only while the decision has not been saved yet.
|
|
3344
|
+
stockQuantity: row.stock_quantity == null ? quantity * factor : Number(row.stock_quantity),
|
|
3345
|
+
costMode: row.cost_mode ?? "novo",
|
|
3346
|
+
unitCostNew: row.unit_cost_new == null ? null : Number(row.unit_cost_new),
|
|
3347
|
+
reconciled: Boolean(row.reconciled),
|
|
3348
|
+
stockMovementId: row.stock_movement_id ?? null,
|
|
3349
|
+
shortageQuantity: row.shortage_quantity == null ? null : Number(row.shortage_quantity),
|
|
3350
|
+
shortageReason: row.shortage_reason ?? null,
|
|
3351
|
+
shortageNote: row.shortage_note ?? null
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
async function getInboxNote(id) {
|
|
3355
|
+
const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select(NOTE_COLUMNS).eq("id", id).maybeSingle();
|
|
3356
|
+
if (error) throw error;
|
|
3357
|
+
if (!data) return null;
|
|
3358
|
+
const [items, instalments, payments] = await Promise.all([
|
|
3359
|
+
sb().from("plg_fiscalbr_inbox_items").select(ITEM_COLUMNS).eq("inbox_document_id", id).order("item_number"),
|
|
3360
|
+
sb().from("plg_fiscalbr_inbox_duplicates").select("id, duplicate_index, number, due_date, value, payment_type, barcode, pix_key, payment_method_id").eq("inbox_document_id", id).order("duplicate_index"),
|
|
3361
|
+
sb().from("plg_fiscalbr_inbox_payments").select("id, payment_index, payment_type_code, payment_type_label, value").eq("inbox_document_id", id).order("payment_index")
|
|
3362
|
+
]);
|
|
3363
|
+
if (items.error) throw items.error;
|
|
3364
|
+
if (instalments.error) throw instalments.error;
|
|
3365
|
+
return {
|
|
3366
|
+
...rowToNote(data),
|
|
3367
|
+
natureOperation: data.nature_operation ?? null,
|
|
3368
|
+
defaultChartAccountId: data.default_chart_account_id ?? null,
|
|
3369
|
+
defaultLocationId: data.default_location_id ?? null,
|
|
3370
|
+
items: (items.data ?? []).map(rowToItem),
|
|
3371
|
+
instalments: (instalments.data ?? []).map((d) => ({
|
|
3372
|
+
id: String(d.id),
|
|
3373
|
+
index: Number(d.duplicate_index ?? 0),
|
|
3374
|
+
number: d.number ?? null,
|
|
3375
|
+
dueDate: d.due_date ?? null,
|
|
3376
|
+
amount: Number(d.value ?? 0),
|
|
3377
|
+
paymentType: d.payment_type ?? null,
|
|
3378
|
+
barcode: d.barcode ?? null,
|
|
3379
|
+
pixKey: d.pix_key ?? null,
|
|
3380
|
+
paymentMethodId: d.payment_method_id ?? null
|
|
3381
|
+
})),
|
|
3382
|
+
declaredPayments: (payments.data ?? []).map((p) => ({
|
|
3383
|
+
index: Number(p.payment_index ?? 0),
|
|
3384
|
+
code: p.payment_type_code ?? null,
|
|
3385
|
+
label: p.payment_type_label ?? null,
|
|
3386
|
+
value: Number(p.value ?? 0)
|
|
3387
|
+
}))
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
async function stageNoteFromXml(xml, options = {}) {
|
|
3391
|
+
const parsed = parseNfeXml(xml);
|
|
3392
|
+
const { data, error } = await sb().rpc("fiscalbr_stage_inbox_document", {
|
|
3393
|
+
p_note: toStagePayload(parsed, { unitId: options.unitId ?? null })
|
|
3394
|
+
});
|
|
3395
|
+
if (error) throw error;
|
|
3396
|
+
const res = data ?? {};
|
|
3397
|
+
return {
|
|
3398
|
+
documentId: String(res.document_id ?? ""),
|
|
3399
|
+
accessKey: String(res.access_key ?? parsed.accessKey),
|
|
3400
|
+
alreadyAccepted: Boolean(res.already_accepted),
|
|
3401
|
+
supplierPartyId: res.supplier_party_id ?? null,
|
|
3402
|
+
itemCount: Number(res.items ?? 0),
|
|
3403
|
+
matchedCount: Number(res.matched ?? 0),
|
|
3404
|
+
parsed
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
async function saveNoteDecision(input) {
|
|
3408
|
+
const { data, error } = await sb().rpc("fiscalbr_save_inbox_decision", {
|
|
3409
|
+
p_document_id: input.documentId,
|
|
3410
|
+
p_items: input.items.map((i) => ({
|
|
3411
|
+
id: i.id,
|
|
3412
|
+
product_id: i.productId ?? null,
|
|
3413
|
+
entry_type: i.entryType ?? null,
|
|
3414
|
+
chart_account_id: i.chartAccountId ?? null,
|
|
3415
|
+
stock_unit_id: i.stockUnitId ?? null,
|
|
3416
|
+
conversion_factor: i.conversionFactor ?? 1,
|
|
3417
|
+
cost_mode: i.costMode ?? "novo",
|
|
3418
|
+
shortage_quantity: i.shortageQuantity ?? null,
|
|
3419
|
+
shortage_reason: i.shortageReason ?? null,
|
|
3420
|
+
shortage_note: i.shortageNote ?? null
|
|
3421
|
+
})),
|
|
3422
|
+
p_default_chart_account_id: input.defaultChartAccountId ?? null,
|
|
3423
|
+
p_default_location_id: input.defaultLocationId ?? null,
|
|
3424
|
+
p_duplicates: input.instalments ? input.instalments.map((d) => ({
|
|
3425
|
+
index: d.index,
|
|
3426
|
+
number: d.number ?? null,
|
|
3427
|
+
due_date: d.dueDate ?? null,
|
|
3428
|
+
value: d.amount,
|
|
3429
|
+
payment_type: d.paymentType ?? null,
|
|
3430
|
+
barcode: d.barcode ?? null,
|
|
3431
|
+
pix_key: d.pixKey ?? null,
|
|
3432
|
+
payment_method_id: d.paymentMethodId ?? null
|
|
3433
|
+
})) : null
|
|
3434
|
+
});
|
|
3435
|
+
if (error) throw error;
|
|
3436
|
+
const res = data ?? {};
|
|
3437
|
+
return {
|
|
3438
|
+
saved: Number(res.saved ?? 0),
|
|
3439
|
+
items: Number(res.items ?? 0),
|
|
3440
|
+
reconciled: Number(res.reconciled ?? 0)
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
async function acceptNote(documentId, locationId) {
|
|
3444
|
+
const { data, error } = await sb().rpc("fiscalbr_accept_inbox_document", {
|
|
3445
|
+
p_document_id: documentId,
|
|
3446
|
+
p_location_id: locationId ?? null
|
|
3447
|
+
});
|
|
3448
|
+
if (error) throw error;
|
|
3449
|
+
const res = data ?? {};
|
|
3450
|
+
return {
|
|
3451
|
+
documentId: String(res.document_id ?? documentId),
|
|
3452
|
+
alreadyAccepted: Boolean(res.already_accepted),
|
|
3453
|
+
invoiceId: res.invoice_id ?? null,
|
|
3454
|
+
invoiceTotal: Number(res.invoice_total ?? 0),
|
|
3455
|
+
instalments: Number(res.installments ?? 0),
|
|
3456
|
+
operationId: res.operation_id ?? null,
|
|
3457
|
+
stockLines: Number(res.stock_lines ?? 0)
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
async function rejectNote(documentId, reason) {
|
|
3461
|
+
const { error } = await sb().rpc("fiscalbr_reject_inbox_document", {
|
|
3462
|
+
p_document_id: documentId,
|
|
3463
|
+
p_reason: reason ?? null
|
|
3464
|
+
});
|
|
3465
|
+
if (error) throw error;
|
|
3466
|
+
}
|
|
3467
|
+
async function searchProducts(term, limit = 20) {
|
|
3468
|
+
let query = sb().from("products").select("id, name, sku, unit, cost, kind").eq("is_active", true).limit(limit);
|
|
3469
|
+
if (term.trim()) query = query.or(`name.ilike.%${term.trim()}%,sku.ilike.%${term.trim()}%`);
|
|
3470
|
+
const { data, error } = await query.order("name");
|
|
3471
|
+
if (error) throw error;
|
|
3472
|
+
return (data ?? []).map((p) => ({
|
|
3473
|
+
id: String(p.id),
|
|
3474
|
+
name: p.name ?? "",
|
|
3475
|
+
sku: p.sku ?? null,
|
|
3476
|
+
unit: p.unit ?? null,
|
|
3477
|
+
cost: p.cost == null ? null : Number(p.cost),
|
|
3478
|
+
kind: p.kind ?? null
|
|
3479
|
+
}));
|
|
3480
|
+
}
|
|
3481
|
+
async function listMeasurementUnits() {
|
|
3482
|
+
const { data, error } = await sb().from("plg_inventory_measurement_units").select("id, name, abbreviation, is_base").eq("is_active", true).order("name");
|
|
3483
|
+
if (error) throw error;
|
|
3484
|
+
return (data ?? []).map((u) => ({
|
|
3485
|
+
id: String(u.id),
|
|
3486
|
+
name: u.name ?? "",
|
|
3487
|
+
abbreviation: u.abbreviation ?? "",
|
|
3488
|
+
isBase: Boolean(u.is_base)
|
|
3489
|
+
}));
|
|
3490
|
+
}
|
|
3491
|
+
async function listChartAccounts() {
|
|
3492
|
+
const { data, error } = await sb().from("plg_financial_chart_of_accounts").select("id, code, name, kind, is_active").order("code");
|
|
3493
|
+
if (error) throw error;
|
|
3494
|
+
return (data ?? []).filter((a) => a.is_active !== false).map((a) => ({
|
|
3495
|
+
id: String(a.id),
|
|
3496
|
+
code: a.code ?? "",
|
|
3497
|
+
name: a.name ?? "",
|
|
3498
|
+
kind: a.kind ?? null
|
|
3499
|
+
}));
|
|
3500
|
+
}
|
|
3501
|
+
async function listStockLocations() {
|
|
3502
|
+
const { data, error } = await sb().from("plg_inventory_stock_locations").select("id, name, unit_id, is_active").order("name");
|
|
3503
|
+
if (error) throw error;
|
|
3504
|
+
return (data ?? []).filter((l) => l.is_active !== false).map((l) => ({ id: String(l.id), name: l.name ?? "", unitId: l.unit_id ?? null }));
|
|
3505
|
+
}
|
|
3506
|
+
async function listSupplierMap(supplierCnpj) {
|
|
3507
|
+
if (!supplierCnpj) return [];
|
|
3508
|
+
const { data, error } = await sb().from("plg_fiscalbr_supplier_product_map").select("supplier_product_code, product_id, hit_count, last_used_at").eq("supplier_cnpj", supplierCnpj);
|
|
3509
|
+
if (error) throw error;
|
|
3510
|
+
return (data ?? []).map((m) => ({
|
|
3511
|
+
supplierProductCode: m.supplier_product_code ?? "",
|
|
3512
|
+
productId: String(m.product_id),
|
|
3513
|
+
hitCount: Number(m.hit_count ?? 0),
|
|
3514
|
+
lastUsedAt: m.last_used_at ?? null
|
|
3515
|
+
}));
|
|
3516
|
+
}
|
|
3517
|
+
var money = (value) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(value || 0);
|
|
3518
|
+
var day = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
|
|
3519
|
+
var cnpj = (digits2) => {
|
|
3520
|
+
const d = (digits2 ?? "").replace(/\D/g, "");
|
|
3521
|
+
if (d.length !== 14) return digits2 ?? "\u2014";
|
|
3522
|
+
return `${d.slice(0, 2)}.${d.slice(2, 5)}.${d.slice(5, 8)}/${d.slice(8, 12)}-${d.slice(12)}`;
|
|
3523
|
+
};
|
|
3524
|
+
function InboundNotesView({ onOpenNote, onOpenManifest }) {
|
|
3525
|
+
const [notes, setNotes] = useState([]);
|
|
3526
|
+
const [loading, setLoading] = useState(true);
|
|
3527
|
+
const [filter, setFilter] = useState("all");
|
|
3528
|
+
const [busy, setBusy] = useState(false);
|
|
3529
|
+
const [report, setReport] = useState(null);
|
|
3530
|
+
const [dragging, setDragging] = useState(false);
|
|
3531
|
+
const fileInput = useRef(null);
|
|
3532
|
+
const reload = useCallback(async () => {
|
|
3533
|
+
setLoading(true);
|
|
3534
|
+
try {
|
|
3535
|
+
setNotes(await listInboxNotes({ filter }));
|
|
3536
|
+
} finally {
|
|
3537
|
+
setLoading(false);
|
|
3538
|
+
}
|
|
3539
|
+
}, [filter]);
|
|
3540
|
+
useEffect(() => {
|
|
3541
|
+
void reload();
|
|
3542
|
+
}, [reload]);
|
|
3543
|
+
const ingest = useCallback(async (files) => {
|
|
3544
|
+
const xmls = files.filter((f) => /\.xml$/i.test(f.name));
|
|
3545
|
+
if (xmls.length === 0) {
|
|
3546
|
+
setReport({ ok: [], failed: [{ file: "\u2014", message: "Nenhum arquivo .xml no que foi solto." }] });
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
setBusy(true);
|
|
3550
|
+
const next = { ok: [], failed: [] };
|
|
3551
|
+
for (const file of xmls) {
|
|
3552
|
+
try {
|
|
3553
|
+
const res = await stageNoteFromXml(await file.text());
|
|
3554
|
+
next.ok.push({
|
|
3555
|
+
file: file.name,
|
|
3556
|
+
number: res.parsed.nfeNumber,
|
|
3557
|
+
items: res.itemCount,
|
|
3558
|
+
matched: res.matchedCount,
|
|
3559
|
+
repeated: res.alreadyAccepted
|
|
3560
|
+
});
|
|
3561
|
+
} catch (error) {
|
|
3562
|
+
next.failed.push({ file: file.name, message: error instanceof Error ? error.message : String(error) });
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
setBusy(false);
|
|
3566
|
+
setReport(next);
|
|
3567
|
+
await reload();
|
|
3568
|
+
}, [reload]);
|
|
3569
|
+
const remove = useCallback(async (note) => {
|
|
3570
|
+
await rejectNote(note.id, "Recusada na lista de entrada");
|
|
3571
|
+
await reload();
|
|
3572
|
+
}, [reload]);
|
|
3573
|
+
const filters = useMemo(() => [{
|
|
3574
|
+
id: "estado",
|
|
3575
|
+
label: "Estado",
|
|
3576
|
+
allLabel: "Todos",
|
|
3577
|
+
value: filter === "all" ? void 0 : filter,
|
|
3578
|
+
onChange: (value) => setFilter(value ?? "all"),
|
|
3579
|
+
options: [
|
|
3580
|
+
{ value: "pending", label: "Estoque pendente" },
|
|
3581
|
+
{ value: "stocked", label: "Estocado" },
|
|
3582
|
+
{ value: "to_manifest", label: "A manifestar" },
|
|
3583
|
+
{ value: "rejected", label: "Recusada" }
|
|
3584
|
+
]
|
|
3585
|
+
}], [filter]);
|
|
3586
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3587
|
+
/* @__PURE__ */ jsxs(PageHeaderActions, { children: [
|
|
3588
|
+
onOpenManifest && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onOpenManifest, children: [
|
|
3589
|
+
/* @__PURE__ */ jsx(Inbox, { className: "h-4 w-4 mr-1.5" }),
|
|
3590
|
+
"Manifesto de Notas"
|
|
3591
|
+
] }),
|
|
3592
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => fileInput.current?.click(), disabled: busy, children: [
|
|
3593
|
+
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" }),
|
|
3594
|
+
"Importar XML"
|
|
3595
|
+
] })
|
|
3596
|
+
] }),
|
|
3597
|
+
/* @__PURE__ */ jsxs(
|
|
3598
|
+
Card,
|
|
3599
|
+
{
|
|
3600
|
+
className: `border-dashed p-6 text-center transition-colors ${dragging ? "border-primary bg-primary/5" : ""}`,
|
|
3601
|
+
onDragOver: (e) => {
|
|
3602
|
+
e.preventDefault();
|
|
3603
|
+
setDragging(true);
|
|
3604
|
+
},
|
|
3605
|
+
onDragLeave: () => setDragging(false),
|
|
3606
|
+
onDrop: (e) => {
|
|
3607
|
+
e.preventDefault();
|
|
3608
|
+
setDragging(false);
|
|
3609
|
+
void ingest(Array.from(e.dataTransfer.files));
|
|
3610
|
+
},
|
|
3611
|
+
children: [
|
|
3612
|
+
/* @__PURE__ */ jsx(UploadCloud, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
3613
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Arraste arquivos XML aqui ou clique para selecionar" }),
|
|
3614
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Suporta m\xFAltiplos arquivos XML de NF-e. O XML do fornecedor d\xE1 entrada no estoque e cria a conta a pagar." }),
|
|
3615
|
+
/* @__PURE__ */ jsx(
|
|
3616
|
+
"input",
|
|
3617
|
+
{
|
|
3618
|
+
ref: fileInput,
|
|
3619
|
+
type: "file",
|
|
3620
|
+
accept: ".xml,text/xml,application/xml",
|
|
3621
|
+
multiple: true,
|
|
3622
|
+
className: "hidden",
|
|
3623
|
+
onChange: (e) => {
|
|
3624
|
+
void ingest(Array.from(e.target.files ?? []));
|
|
3625
|
+
e.target.value = "";
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
)
|
|
3629
|
+
]
|
|
3630
|
+
}
|
|
3631
|
+
),
|
|
3632
|
+
report && /* @__PURE__ */ jsxs(Card, { className: "p-4 space-y-2", children: [
|
|
3633
|
+
report.ok.map((r) => /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
|
|
3634
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
3635
|
+
"NF-e ",
|
|
3636
|
+
r.number
|
|
3637
|
+
] }),
|
|
3638
|
+
" ",
|
|
3639
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: r.repeated ? "\u2014 j\xE1 tinha sido aceita, nada mudou" : `\u2014 ${r.items} ${r.items === 1 ? "item" : "itens"}, ${r.matched} j\xE1 ${r.matched === 1 ? "vinculado" : "vinculados"}` })
|
|
3640
|
+
] }, r.file)),
|
|
3641
|
+
report.failed.map((r) => /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
|
|
3642
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: r.file }),
|
|
3643
|
+
" \u2014 ",
|
|
3644
|
+
r.message
|
|
3645
|
+
] }, r.file)),
|
|
3646
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setReport(null), children: "Fechar" })
|
|
3647
|
+
] }),
|
|
3648
|
+
/* @__PURE__ */ jsx(ListFilterBar, { filters, onClear: () => setFilter("all"), activeCount: filter === "all" ? 0 : 1 }),
|
|
3649
|
+
loading ? /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-14 w-full" }, i)) }) : notes.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "p-10 text-center", children: [
|
|
3650
|
+
/* @__PURE__ */ jsx(Inbox, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
3651
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota de entrada" }),
|
|
3652
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "As notas chegam da SEFAZ pelo Manifesto, ou voc\xEA arrasta o XML aqui." })
|
|
3653
|
+
] }) : /* @__PURE__ */ jsx(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
3654
|
+
/* @__PURE__ */ jsx("thead", { className: "border-b bg-muted/40 text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
3655
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Emiss\xE3o" }),
|
|
3656
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "N\xBA" }),
|
|
3657
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Fornecedor" }),
|
|
3658
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Itens" }),
|
|
3659
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center font-medium", children: "Concilia\xE7\xE3o" }),
|
|
3660
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Valor" }),
|
|
3661
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center font-medium", children: "Estoque" }),
|
|
3662
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "A\xE7\xF5es" })
|
|
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,
|
|
3685
|
+
{
|
|
3686
|
+
size: "sm",
|
|
3687
|
+
variant: "ghost",
|
|
3688
|
+
"aria-label": `Recusar a nota ${note.number}`,
|
|
3689
|
+
onClick: () => {
|
|
3690
|
+
void remove(note);
|
|
3691
|
+
},
|
|
3692
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
3693
|
+
}
|
|
3694
|
+
)
|
|
3695
|
+
] }) })
|
|
3696
|
+
] }, note.id)) })
|
|
3697
|
+
] }) }) })
|
|
3698
|
+
] });
|
|
3699
|
+
}
|
|
3700
|
+
function ReconcileBadge({ done, total }) {
|
|
3701
|
+
const full = total > 0 && done === total;
|
|
3702
|
+
return /* @__PURE__ */ jsxs(
|
|
3703
|
+
"span",
|
|
3704
|
+
{
|
|
3705
|
+
className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium tabular-nums ${full ? "bg-success-soft text-success-soft-foreground" : "bg-muted text-muted-foreground"}`,
|
|
3706
|
+
children: [
|
|
3707
|
+
done,
|
|
3708
|
+
"/",
|
|
3709
|
+
total
|
|
3710
|
+
]
|
|
3711
|
+
}
|
|
3712
|
+
);
|
|
3713
|
+
}
|
|
3714
|
+
function StockBadge({ stockedAt, status }) {
|
|
3715
|
+
if (status === "rejected") {
|
|
3716
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground line-through", children: "Recusada" });
|
|
3717
|
+
}
|
|
3718
|
+
return stockedAt ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-full bg-success-soft px-2 py-0.5 text-[11px] font-medium text-success-soft-foreground", children: [
|
|
3719
|
+
/* @__PURE__ */ jsx(FileDown, { className: "mr-1 h-3 w-3", "aria-hidden": true }),
|
|
3720
|
+
"Estocado"
|
|
3721
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-warning-soft px-2 py-0.5 text-[11px] font-medium text-warning-soft-foreground", children: "Pendente" });
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
// src/types.ts
|
|
3725
|
+
var STOCK_BEARING_ENTRY_TYPES = ["insumo", "revenda", "patrimonio"];
|
|
3726
|
+
var isStockBearing = (type) => STOCK_BEARING_ENTRY_TYPES.includes(type);
|
|
3727
|
+
var money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
|
|
3728
|
+
var qty = (v) => new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 4 }).format(v || 0);
|
|
3729
|
+
var day2 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
|
|
3730
|
+
var ENTRY_TYPES = [
|
|
3731
|
+
{ value: "insumo", label: "Insumo" },
|
|
3732
|
+
{ value: "revenda", label: "Produto Revenda" },
|
|
3733
|
+
{ value: "patrimonio", label: "Patrim\xF4nio" },
|
|
3734
|
+
{ value: "despesa", label: "Despesa", hint: "n\xE3o vira estoque" },
|
|
3735
|
+
{ value: "servico", label: "Servi\xE7o", hint: "n\xE3o vira estoque" }
|
|
3736
|
+
];
|
|
3737
|
+
var localSearch = (options) => (query) => {
|
|
3738
|
+
const q = query.trim().toLowerCase();
|
|
3739
|
+
return (q ? options.filter((o) => o.label.toLowerCase().includes(q)) : options).slice(0, 40);
|
|
3740
|
+
};
|
|
3741
|
+
var labelOf = (options, id) => options.find((o) => o.id === id)?.label;
|
|
3742
|
+
var draftOf = (item) => ({
|
|
3743
|
+
productId: item.productId,
|
|
3744
|
+
productLabel: "",
|
|
3745
|
+
entryType: item.entryType ?? null,
|
|
3746
|
+
chartAccountId: item.chartAccountId,
|
|
3747
|
+
stockUnitId: item.stockUnitId,
|
|
3748
|
+
conversionFactor: String(item.conversionFactor ?? 1),
|
|
3749
|
+
costMode: item.costMode === "media" ? "media" : "novo",
|
|
3750
|
+
shortageQuantity: item.shortageQuantity == null ? "" : String(item.shortageQuantity)
|
|
3751
|
+
});
|
|
3752
|
+
function NoteReconcileView({ noteId, onBack, onOpenInvoice }) {
|
|
3753
|
+
const [note, setNote] = useState(null);
|
|
3754
|
+
const [loading, setLoading] = useState(true);
|
|
3755
|
+
const [drafts, setDrafts] = useState({});
|
|
3756
|
+
const [instalments, setInstalments] = useState([]);
|
|
3757
|
+
const [defaultAccount, setDefaultAccount] = useState(null);
|
|
3758
|
+
const [location, setLocation] = useState(null);
|
|
3759
|
+
const [units, setUnits] = useState([]);
|
|
3760
|
+
const [accounts, setAccounts] = useState([]);
|
|
3761
|
+
const [locations, setLocations] = useState([]);
|
|
3762
|
+
const [saving, setSaving] = useState(false);
|
|
3763
|
+
const [accepting, setAccepting] = useState(false);
|
|
3764
|
+
const [error, setError] = useState(null);
|
|
3765
|
+
const [accepted, setAccepted] = useState(null);
|
|
3766
|
+
const load = useCallback(async () => {
|
|
3767
|
+
setLoading(true);
|
|
3768
|
+
try {
|
|
3769
|
+
const detail = await getInboxNote(noteId);
|
|
3770
|
+
setNote(detail);
|
|
3771
|
+
const [u, a, l] = await Promise.all([
|
|
3772
|
+
listMeasurementUnits().catch(() => []),
|
|
3773
|
+
listChartAccounts().catch(() => []),
|
|
3774
|
+
listStockLocations().catch(() => [])
|
|
3775
|
+
]);
|
|
3776
|
+
setUnits(u);
|
|
3777
|
+
setAccounts(a);
|
|
3778
|
+
setLocations(l);
|
|
3779
|
+
if (detail) {
|
|
3780
|
+
setDrafts(Object.fromEntries(detail.items.map((i) => [i.id, draftOf(i)])));
|
|
3781
|
+
setInstalments(detail.instalments.map((d) => ({
|
|
3782
|
+
index: d.index,
|
|
3783
|
+
number: d.number,
|
|
3784
|
+
dueDate: d.dueDate,
|
|
3785
|
+
amount: d.amount,
|
|
3786
|
+
paymentType: d.paymentType,
|
|
3787
|
+
barcode: d.barcode,
|
|
3788
|
+
pixKey: d.pixKey
|
|
3789
|
+
})));
|
|
3790
|
+
setDefaultAccount(detail.defaultChartAccountId);
|
|
3791
|
+
setLocation(detail.defaultLocationId ?? l[0]?.id ?? null);
|
|
3792
|
+
}
|
|
3793
|
+
} catch (e) {
|
|
3794
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
3795
|
+
} finally {
|
|
3796
|
+
setLoading(false);
|
|
3797
|
+
}
|
|
3798
|
+
}, [noteId]);
|
|
3799
|
+
useEffect(() => {
|
|
3800
|
+
void load();
|
|
3801
|
+
}, [load]);
|
|
3802
|
+
const patch = (id, change) => setDrafts((prev) => ({ ...prev, [id]: { ...prev[id], ...change } }));
|
|
3803
|
+
const accountOptions = useMemo(
|
|
3804
|
+
() => accounts.map((a) => ({ id: a.id, label: `${a.code} \xB7 ${a.name}` })),
|
|
3805
|
+
[accounts]
|
|
3806
|
+
);
|
|
3807
|
+
const unitOptions = useMemo(
|
|
3808
|
+
() => units.map((u) => ({ id: u.id, label: u.abbreviation, subtitle: u.name })),
|
|
3809
|
+
[units]
|
|
3810
|
+
);
|
|
3811
|
+
const locationOptions = useMemo(
|
|
3812
|
+
() => locations.map((l) => ({ id: l.id, label: l.name })),
|
|
3813
|
+
[locations]
|
|
3814
|
+
);
|
|
3815
|
+
const decisions = useMemo(() => {
|
|
3816
|
+
if (!note) return [];
|
|
3817
|
+
return note.items.map((item) => {
|
|
3818
|
+
const d = drafts[item.id] ?? draftOf(item);
|
|
3819
|
+
return {
|
|
3820
|
+
id: item.id,
|
|
3821
|
+
productId: isStockBearing(d.entryType) ? d.productId : null,
|
|
3822
|
+
entryType: d.entryType,
|
|
3823
|
+
// A herança que a legenda do V1 promete: «itens sem plano herdarão este».
|
|
3824
|
+
chartAccountId: d.chartAccountId ?? defaultAccount,
|
|
3825
|
+
stockUnitId: d.stockUnitId,
|
|
3826
|
+
conversionFactor: Number(d.conversionFactor.replace(",", ".")) || 1,
|
|
3827
|
+
costMode: d.costMode,
|
|
3828
|
+
shortageQuantity: d.shortageQuantity === "" ? null : Number(d.shortageQuantity.replace(",", "."))
|
|
3829
|
+
};
|
|
3830
|
+
});
|
|
3831
|
+
}, [note, drafts, defaultAccount]);
|
|
3832
|
+
const stockLines = decisions.filter((d) => isStockBearing(d.entryType));
|
|
3833
|
+
const missingProduct = stockLines.filter((d) => !d.productId).length;
|
|
3834
|
+
const missingType = decisions.filter((d) => !d.entryType).length;
|
|
3835
|
+
const instalmentSum = instalments.reduce((acc, d) => acc + (Number(d.amount) || 0), 0);
|
|
3836
|
+
const readOnly = Boolean(note?.stockedAt || note?.invoiceId);
|
|
3837
|
+
const save = async () => {
|
|
3838
|
+
if (!note) return;
|
|
3839
|
+
setSaving(true);
|
|
3840
|
+
setError(null);
|
|
3841
|
+
try {
|
|
3842
|
+
await saveNoteDecision({
|
|
3843
|
+
documentId: note.id,
|
|
3844
|
+
items: decisions,
|
|
3845
|
+
defaultChartAccountId: defaultAccount,
|
|
3846
|
+
defaultLocationId: location,
|
|
3847
|
+
instalments
|
|
3848
|
+
});
|
|
3849
|
+
await load();
|
|
3850
|
+
} catch (e) {
|
|
3851
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
3852
|
+
} finally {
|
|
3853
|
+
setSaving(false);
|
|
3854
|
+
}
|
|
3855
|
+
};
|
|
3856
|
+
const accept = async () => {
|
|
3857
|
+
if (!note) return;
|
|
3858
|
+
setAccepting(true);
|
|
3859
|
+
setError(null);
|
|
3860
|
+
try {
|
|
3861
|
+
await saveNoteDecision({
|
|
3862
|
+
documentId: note.id,
|
|
3863
|
+
items: decisions,
|
|
3864
|
+
defaultChartAccountId: defaultAccount,
|
|
3865
|
+
defaultLocationId: location,
|
|
3866
|
+
instalments
|
|
3867
|
+
});
|
|
3868
|
+
setAccepted(await acceptNote(note.id, location));
|
|
3869
|
+
await load();
|
|
3870
|
+
} catch (e) {
|
|
3871
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
3872
|
+
} finally {
|
|
3873
|
+
setAccepting(false);
|
|
3874
|
+
}
|
|
3875
|
+
};
|
|
3876
|
+
if (loading) {
|
|
3877
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3878
|
+
/* @__PURE__ */ jsx(SubpageHeader, { title: "Concilia\xE7\xE3o", onBack, parentLabel: "Entrada de Notas" }),
|
|
3879
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full" }),
|
|
3880
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-48 w-full" })
|
|
3881
|
+
] });
|
|
3882
|
+
}
|
|
3883
|
+
if (!note) {
|
|
3884
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3885
|
+
/* @__PURE__ */ jsx(SubpageHeader, { title: "Concilia\xE7\xE3o", onBack, parentLabel: "Entrada de Notas" }),
|
|
3886
|
+
/* @__PURE__ */ jsx(Card, { className: "p-8 text-center text-sm", children: error ? /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
|
|
3887
|
+
"N\xE3o consegui carregar a nota: ",
|
|
3888
|
+
error
|
|
3889
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Nota n\xE3o encontrada." }) })
|
|
3890
|
+
] });
|
|
3891
|
+
}
|
|
3892
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 pb-24", children: [
|
|
3893
|
+
/* @__PURE__ */ jsx(
|
|
3894
|
+
SubpageHeader,
|
|
3895
|
+
{
|
|
3896
|
+
title: `Concilia\xE7\xE3o \xB7 NF-e ${note.number}`,
|
|
3897
|
+
onBack,
|
|
3898
|
+
parentLabel: "Entrada de Notas"
|
|
3899
|
+
}
|
|
3900
|
+
),
|
|
3901
|
+
accepted && /* @__PURE__ */ jsxs(Card, { className: "border-success bg-success-soft p-4", children: [
|
|
3902
|
+
/* @__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
|
+
accepted.invoiceId && onOpenInvoice && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => onOpenInvoice(accepted.invoiceId), children: "Abrir a conta a pagar" })
|
|
3904
|
+
] }),
|
|
3905
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
3906
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Situa\xE7\xE3o fiscal" }),
|
|
3907
|
+
/* @__PURE__ */ jsxs("dl", { className: "mt-3 grid gap-x-6 gap-y-2 text-sm sm:grid-cols-2 lg:grid-cols-3", children: [
|
|
3908
|
+
/* @__PURE__ */ jsx(Field, { label: "Fornecedor", value: note.supplierName || "\u2014" }),
|
|
3909
|
+
/* @__PURE__ */ jsx(Field, { label: "CNPJ", value: note.supplierCnpj ?? "\u2014", mono: true }),
|
|
3910
|
+
/* @__PURE__ */ jsx(Field, { label: "Natureza da opera\xE7\xE3o", value: note.natureOperation ?? "\u2014" }),
|
|
3911
|
+
/* @__PURE__ */ jsx(
|
|
3912
|
+
Field,
|
|
3913
|
+
{
|
|
3914
|
+
label: "Manifesta\xE7\xE3o",
|
|
3915
|
+
value: `${note.manifestationStatus ?? "n\xE3o manifestada"}${note.manifestationAttempts ? ` \xB7 ${note.manifestationAttempts} tentativa(s)` : ""}`
|
|
3916
|
+
}
|
|
3917
|
+
),
|
|
3918
|
+
/* @__PURE__ */ jsx(Field, { label: "Manifestada em", value: day2(note.manifestedAt) }),
|
|
3919
|
+
/* @__PURE__ */ jsx(Field, { label: "Recebida em", value: day2(note.receivedAt) })
|
|
3920
|
+
] }),
|
|
3921
|
+
!note.supplierPartyId && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-md border border-warning bg-warning-soft px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning-soft-foreground", children: [
|
|
3922
|
+
"Este fornecedor n\xE3o est\xE1 no cadastro de pessoas, ent\xE3o o t\xEDtulo a pagar vai nascer",
|
|
3923
|
+
" ",
|
|
3924
|
+
/* @__PURE__ */ jsx("strong", { children: "sem contraparte" }),
|
|
3925
|
+
". Cadastre o CNPJ antes de aceitar para a conta saber a quem se deve."
|
|
3926
|
+
] }) }),
|
|
3927
|
+
/bonific|brinde|doacao|doação/i.test(note.natureOperation ?? "") && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-md border border-dashed border-input px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
3928
|
+
/* @__PURE__ */ jsx(Gift, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
|
|
3929
|
+
"A natureza desta nota \xE9 ",
|
|
3930
|
+
/* @__PURE__ */ jsx("strong", { children: "bonifica\xE7\xE3o" }),
|
|
3931
|
+
". Ela deveria entrar no estoque",
|
|
3932
|
+
" ",
|
|
3933
|
+
/* @__PURE__ */ jsx("strong", { children: "sem" }),
|
|
3934
|
+
" abrir conta a pagar \u2014 hoje ainda abre.",
|
|
3935
|
+
" ",
|
|
3936
|
+
/* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 py-0.5 text-[10px]", children: "Em breve" })
|
|
3937
|
+
] }) }),
|
|
3938
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [
|
|
3939
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
3940
|
+
void load();
|
|
3941
|
+
}, children: [
|
|
3942
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "mr-1.5 h-4 w-4" }),
|
|
3943
|
+
"Atualizar status"
|
|
3944
|
+
] }),
|
|
3945
|
+
/* @__PURE__ */ jsx(
|
|
3946
|
+
EmBreve,
|
|
3947
|
+
{
|
|
3948
|
+
label: "Cadastrar fornecedor",
|
|
3949
|
+
o_que: "Criar a pessoa do fornecedor aqui, sem sair da nota \u2014 \xE9 o que preenche a contraparte do t\xEDtulo",
|
|
3950
|
+
quantas: "a Laissa cadastrou 37 fornecedores no V1 enquanto importava"
|
|
3951
|
+
}
|
|
3952
|
+
)
|
|
3953
|
+
] })
|
|
3954
|
+
] }),
|
|
3955
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
3956
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Padr\xF5es desta nota" }),
|
|
3957
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 grid gap-4 sm:grid-cols-2", children: [
|
|
3958
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
3959
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Plano de contas padr\xE3o" }),
|
|
3960
|
+
/* @__PURE__ */ jsx(
|
|
3961
|
+
SearchSelect,
|
|
3962
|
+
{
|
|
3963
|
+
onSearch: localSearch(accountOptions),
|
|
3964
|
+
value: defaultAccount ?? void 0,
|
|
3965
|
+
displayValue: labelOf(accountOptions, defaultAccount),
|
|
3966
|
+
onChange: (id) => setDefaultAccount(id || null),
|
|
3967
|
+
minChars: 0,
|
|
3968
|
+
placeholder: "Escolher plano de contas",
|
|
3969
|
+
noResultsLabel: "Nenhum plano de contas",
|
|
3970
|
+
searchingLabel: "Buscando\u2026",
|
|
3971
|
+
disabled: readOnly
|
|
3972
|
+
}
|
|
3973
|
+
),
|
|
3974
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-[11px] text-muted-foreground", children: "Itens sem plano herdar\xE3o este." })
|
|
3975
|
+
] }),
|
|
3976
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
3977
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Local de estoque" }),
|
|
3978
|
+
/* @__PURE__ */ jsx(
|
|
3979
|
+
SearchSelect,
|
|
3980
|
+
{
|
|
3981
|
+
onSearch: localSearch(locationOptions),
|
|
3982
|
+
value: location ?? void 0,
|
|
3983
|
+
displayValue: labelOf(locationOptions, location),
|
|
3984
|
+
onChange: (id) => setLocation(id || null),
|
|
3985
|
+
minChars: 0,
|
|
3986
|
+
placeholder: "Escolher local",
|
|
3987
|
+
noResultsLabel: "Nenhum local de estoque",
|
|
3988
|
+
searchingLabel: "Buscando\u2026",
|
|
3989
|
+
disabled: readOnly
|
|
3990
|
+
}
|
|
3991
|
+
),
|
|
3992
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-[11px] text-muted-foreground", children: "Onde as linhas de estoque desta nota entram." })
|
|
3993
|
+
] })
|
|
3994
|
+
] })
|
|
3995
|
+
] }),
|
|
3996
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3997
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold", children: [
|
|
3998
|
+
"Itens ",
|
|
3999
|
+
/* @__PURE__ */ jsxs("span", { className: "font-normal text-muted-foreground", children: [
|
|
4000
|
+
"(",
|
|
4001
|
+
note.items.length,
|
|
4002
|
+
")"
|
|
4003
|
+
] })
|
|
4004
|
+
] }),
|
|
4005
|
+
note.items.map((item) => {
|
|
4006
|
+
const d = drafts[item.id] ?? draftOf(item);
|
|
4007
|
+
const factor = Number(d.conversionFactor.replace(",", ".")) || 1;
|
|
4008
|
+
const shortage = d.shortageQuantity === "" ? 0 : Number(d.shortageQuantity.replace(",", ".")) || 0;
|
|
4009
|
+
const stockQty = item.quantity * factor - shortage;
|
|
4010
|
+
const unitCost = factor > 0 ? item.unitPrice / factor : item.unitPrice;
|
|
4011
|
+
const bearing = isStockBearing(d.entryType);
|
|
4012
|
+
return /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
|
|
4013
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4014
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Item na nota" }),
|
|
4015
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1 font-medium leading-tight", children: [
|
|
4016
|
+
item.itemNumber,
|
|
4017
|
+
". ",
|
|
4018
|
+
item.description
|
|
4019
|
+
] }),
|
|
4020
|
+
/* @__PURE__ */ jsxs("dl", { className: "mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs sm:grid-cols-4", children: [
|
|
4021
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "Qtd", value: `${qty(item.quantity)} ${item.unit ?? ""}` }),
|
|
4022
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "Vl. unit.", value: money2(item.unitPrice) }),
|
|
4023
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "Total", value: money2(item.total) }),
|
|
4024
|
+
/* @__PURE__ */ jsx(Field, { small: true, label: "C\xF3d", value: item.supplierProductCode || "\u2014", mono: true }),
|
|
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" }),
|
|
4056
|
+
/* @__PURE__ */ jsx(
|
|
4057
|
+
SearchSelect,
|
|
4058
|
+
{
|
|
4059
|
+
value: d.productId ?? void 0,
|
|
4060
|
+
displayValue: d.productLabel || void 0,
|
|
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
|
|
4075
|
+
}
|
|
4076
|
+
)
|
|
4077
|
+
] }),
|
|
4078
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
4079
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4080
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Fator de convers\xE3o" }),
|
|
4081
|
+
/* @__PURE__ */ jsx(
|
|
4082
|
+
Input,
|
|
4083
|
+
{
|
|
4084
|
+
value: d.conversionFactor,
|
|
4085
|
+
onChange: (e) => patch(item.id, { conversionFactor: e.target.value }),
|
|
4086
|
+
inputMode: "decimal",
|
|
4087
|
+
disabled: readOnly
|
|
4088
|
+
}
|
|
4089
|
+
)
|
|
4090
|
+
] }),
|
|
4091
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4092
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Unidade de estoque" }),
|
|
4093
|
+
/* @__PURE__ */ jsx(
|
|
4094
|
+
SearchSelect,
|
|
4095
|
+
{
|
|
4096
|
+
onSearch: localSearch(unitOptions),
|
|
4097
|
+
value: d.stockUnitId ?? void 0,
|
|
4098
|
+
displayValue: labelOf(unitOptions, d.stockUnitId),
|
|
4099
|
+
onChange: (id) => patch(item.id, { stockUnitId: id || null }),
|
|
4100
|
+
minChars: 0,
|
|
4101
|
+
placeholder: "Unidade",
|
|
4102
|
+
noResultsLabel: "Nenhuma unidade",
|
|
4103
|
+
searchingLabel: "Buscando\u2026",
|
|
4104
|
+
disabled: readOnly
|
|
4105
|
+
}
|
|
4106
|
+
)
|
|
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
|
+
] }),
|
|
4114
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [
|
|
4115
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Novo custo unit\xE1rio" }),
|
|
4116
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money2(unitCost) })
|
|
4117
|
+
] })
|
|
4118
|
+
] }),
|
|
4119
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
4120
|
+
/* @__PURE__ */ jsx(
|
|
4121
|
+
EmBreve,
|
|
4122
|
+
{
|
|
4123
|
+
label: "Criar produto daqui",
|
|
4124
|
+
o_que: "Cadastrar o produto a partir desta linha da nota, com nome, c\xF3digo e unidade j\xE1 preenchidos",
|
|
4125
|
+
quantas: "ela criou 33 produtos no V1 no meio de importa\xE7\xF5es"
|
|
4126
|
+
}
|
|
4127
|
+
),
|
|
4128
|
+
/* @__PURE__ */ jsx(
|
|
4129
|
+
EmBreve,
|
|
4130
|
+
{
|
|
4131
|
+
label: "Lote e validade",
|
|
4132
|
+
o_que: "Informar lote e vencimento desta entrada \u2014 a coluna j\xE1 existe no movimento de estoque",
|
|
4133
|
+
quantas: "usado 5 vezes no V1; numa churrascaria \xE9 validade de carne"
|
|
4134
|
+
}
|
|
4135
|
+
)
|
|
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
|
+
] })
|
|
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
|
+
] })
|
|
4188
|
+
] })
|
|
4189
|
+
] }) }, item.id);
|
|
4190
|
+
})
|
|
4191
|
+
] }),
|
|
4192
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
4193
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
|
|
4194
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados de pagamento" }),
|
|
4195
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
4196
|
+
instalments.length,
|
|
4197
|
+
" ",
|
|
4198
|
+
instalments.length === 1 ? "parcela" : "parcelas",
|
|
4199
|
+
" \xB7",
|
|
4200
|
+
" ",
|
|
4201
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money2(instalmentSum) })
|
|
4202
|
+
] })
|
|
4203
|
+
] }),
|
|
4204
|
+
note.declaredPayments.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
|
|
4205
|
+
"A nota declarou: ",
|
|
4206
|
+
note.declaredPayments.map((p) => `${p.label ?? p.code} ${money2(p.value)}`).join(" \xB7 ")
|
|
4207
|
+
] }),
|
|
4208
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 space-y-2", children: instalments.map((inst, i) => /* @__PURE__ */ jsxs("div", { className: "grid items-end gap-2 sm:grid-cols-[2rem_1fr_1fr_1fr_2rem]", children: [
|
|
4209
|
+
/* @__PURE__ */ jsx("span", { className: "pb-2 text-xs tabular-nums text-muted-foreground", children: inst.index }),
|
|
4210
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4211
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Vencimento" }),
|
|
4212
|
+
readOnly ? /* @__PURE__ */ jsx("p", { className: "py-2 text-sm tabular-nums", children: day2(inst.dueDate) }) : /* @__PURE__ */ jsx(
|
|
4213
|
+
DatePicker,
|
|
4214
|
+
{
|
|
4215
|
+
value: inst.dueDate ?? "",
|
|
4216
|
+
onChange: (v) => setInstalments((prev) => prev.map((p, j) => j === i ? { ...p, dueDate: v || null } : p))
|
|
4217
|
+
}
|
|
4218
|
+
)
|
|
4219
|
+
] }),
|
|
4220
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4221
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Valor" }),
|
|
4222
|
+
/* @__PURE__ */ jsx(
|
|
4223
|
+
Input,
|
|
4224
|
+
{
|
|
4225
|
+
value: String(inst.amount),
|
|
4226
|
+
inputMode: "decimal",
|
|
4227
|
+
disabled: readOnly,
|
|
4228
|
+
onChange: (e) => setInstalments((prev) => prev.map((p, j) => j === i ? { ...p, amount: Number(e.target.value.replace(",", ".")) || 0 } : p))
|
|
4229
|
+
}
|
|
4230
|
+
)
|
|
4231
|
+
] }),
|
|
4232
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
4233
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "C\xF3d. barras / PIX" }),
|
|
4234
|
+
/* @__PURE__ */ jsx(
|
|
4235
|
+
Input,
|
|
4236
|
+
{
|
|
4237
|
+
value: inst.barcode ?? inst.pixKey ?? "",
|
|
4238
|
+
disabled: readOnly,
|
|
4239
|
+
onChange: (e) => setInstalments((prev) => prev.map((p, j) => j === i ? { ...p, barcode: e.target.value } : p))
|
|
4240
|
+
}
|
|
4241
|
+
)
|
|
4242
|
+
] }),
|
|
4243
|
+
!readOnly && /* @__PURE__ */ jsx(
|
|
4244
|
+
Button,
|
|
4245
|
+
{
|
|
4246
|
+
variant: "ghost",
|
|
4247
|
+
size: "sm",
|
|
4248
|
+
"aria-label": `Remover a parcela ${inst.index}`,
|
|
4249
|
+
onClick: () => setInstalments((prev) => prev.filter((_, j) => j !== i)),
|
|
4250
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
4251
|
+
}
|
|
4252
|
+
)
|
|
4253
|
+
] }, i)) }),
|
|
4254
|
+
!readOnly && /* @__PURE__ */ jsxs(
|
|
4255
|
+
Button,
|
|
4256
|
+
{
|
|
4257
|
+
variant: "outline",
|
|
4258
|
+
size: "sm",
|
|
4259
|
+
className: "mt-3",
|
|
4260
|
+
onClick: () => setInstalments((prev) => [...prev, {
|
|
4261
|
+
index: prev.length + 1,
|
|
4262
|
+
number: null,
|
|
4263
|
+
dueDate: note.issueDate,
|
|
4264
|
+
amount: 0,
|
|
4265
|
+
paymentType: "boleto"
|
|
4266
|
+
}]),
|
|
4267
|
+
children: [
|
|
4268
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
4269
|
+
"Adicionar parcela"
|
|
4270
|
+
]
|
|
4271
|
+
}
|
|
4272
|
+
),
|
|
4273
|
+
Math.abs(instalmentSum - note.total) > 0.01 && /* @__PURE__ */ jsx(Hint, { className: "mt-3", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
4274
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
|
|
4275
|
+
"A nota diz ",
|
|
4276
|
+
money2(note.total),
|
|
4277
|
+
" e as parcelas somam ",
|
|
4278
|
+
money2(instalmentSum),
|
|
4279
|
+
". O t\xEDtulo vai nascer com ",
|
|
4280
|
+
/* @__PURE__ */ jsx("strong", { children: money2(instalmentSum) }),
|
|
4281
|
+
" \u2014 \xE9 a soma das parcelas que define o saldo a pagar."
|
|
4282
|
+
] }) })
|
|
4283
|
+
] }),
|
|
4284
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
4285
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Anexos da conta" }),
|
|
4286
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
|
|
4287
|
+
"Boleto, comprovante, foto da nota. No sistema antigo, anexar o boleto tamb\xE9m",
|
|
4288
|
+
" ",
|
|
4289
|
+
/* @__PURE__ */ jsx("strong", { children: "preenchia as parcelas sozinho" }),
|
|
4290
|
+
" \u2014 \xE9 a automa\xE7\xE3o mais \xFAtil e a mais invis\xEDvel de l\xE1."
|
|
4291
|
+
] }),
|
|
4292
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
|
|
4293
|
+
/* @__PURE__ */ jsx(
|
|
4294
|
+
EmBreve,
|
|
4295
|
+
{
|
|
4296
|
+
label: "Anexar boleto",
|
|
4297
|
+
o_que: "Guardar o arquivo no t\xEDtulo que esta nota cria",
|
|
4298
|
+
quantas: "25 anexos no V1, o \xFAltimo hoje"
|
|
4299
|
+
}
|
|
4300
|
+
),
|
|
4301
|
+
/* @__PURE__ */ jsx(
|
|
4302
|
+
EmBreve,
|
|
4303
|
+
{
|
|
4304
|
+
label: "Ler o c\xF3digo de barras do boleto",
|
|
4305
|
+
o_que: "Preencher vencimento e valor da parcela a partir do boleto anexado",
|
|
4306
|
+
quantas: "automa\xE7\xE3o do V1 que ningu\xE9m documentou"
|
|
4307
|
+
}
|
|
4308
|
+
)
|
|
4309
|
+
] })
|
|
4310
|
+
] }),
|
|
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
|
+
!readOnly && /* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 -mx-4 flex items-center justify-between gap-3 border-t bg-card/95 px-4 py-3 pr-16 backdrop-blur", children: [
|
|
4313
|
+
/* @__PURE__ */ jsxs(
|
|
4314
|
+
Button,
|
|
4315
|
+
{
|
|
4316
|
+
variant: "ghost",
|
|
4317
|
+
onClick: async () => {
|
|
4318
|
+
await rejectNote(note.id);
|
|
4319
|
+
onBack();
|
|
4320
|
+
},
|
|
4321
|
+
disabled: saving || accepting,
|
|
4322
|
+
children: [
|
|
4323
|
+
/* @__PURE__ */ jsx(X, { className: "mr-1.5 h-4 w-4" }),
|
|
4324
|
+
"Rejeitar"
|
|
4325
|
+
]
|
|
4326
|
+
}
|
|
4327
|
+
),
|
|
4328
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4329
|
+
(missingType > 0 || missingProduct > 0) && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
4330
|
+
missingType > 0 && `${missingType} sem tipo`,
|
|
4331
|
+
missingType > 0 && missingProduct > 0 && " \xB7 ",
|
|
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
|
+
] });
|
|
4356
|
+
}
|
|
4357
|
+
function Field({ label, value, mono, small }) {
|
|
4358
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
4359
|
+
/* @__PURE__ */ jsx("dt", { className: `text-muted-foreground ${small ? "text-[10px]" : "text-xs"}`, children: label }),
|
|
4360
|
+
/* @__PURE__ */ jsx("dd", { className: `${mono ? "tabular-nums" : ""} ${small ? "text-xs" : "text-sm"}`, children: value })
|
|
4361
|
+
] });
|
|
4362
|
+
}
|
|
4363
|
+
var money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
|
|
4364
|
+
var day3 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
|
|
4365
|
+
var MANIFEST_TONE = {
|
|
4366
|
+
confirmada: "bg-success-soft text-success-soft-foreground",
|
|
4367
|
+
processando: "bg-info-soft text-info-soft-foreground",
|
|
4368
|
+
erro: "bg-destructive/10 text-destructive"
|
|
4369
|
+
};
|
|
4370
|
+
function ManifestView({ onOpenNote }) {
|
|
4371
|
+
const [notes, setNotes] = useState([]);
|
|
4372
|
+
const [loading, setLoading] = useState(true);
|
|
4373
|
+
const [tab, setTab] = useState("pendentes");
|
|
4374
|
+
const reload = useCallback(async () => {
|
|
4375
|
+
setLoading(true);
|
|
4376
|
+
try {
|
|
4377
|
+
setNotes(await listInboxNotes({ filter: "all", limit: 300 }));
|
|
4378
|
+
} finally {
|
|
4379
|
+
setLoading(false);
|
|
4380
|
+
}
|
|
4381
|
+
}, []);
|
|
4382
|
+
useEffect(() => {
|
|
4383
|
+
void reload();
|
|
4384
|
+
}, [reload]);
|
|
4385
|
+
const shown = useMemo(() => notes.filter((n) => {
|
|
4386
|
+
const status = n.manifestationStatus ?? "";
|
|
4387
|
+
if (tab === "todas") return true;
|
|
4388
|
+
if (tab === "confirmadas") return status === "confirmada";
|
|
4389
|
+
if (tab === "erro") return status === "erro";
|
|
4390
|
+
return status !== "confirmada";
|
|
4391
|
+
}), [notes, tab]);
|
|
4392
|
+
const pending = notes.filter((n) => (n.manifestationStatus ?? "") !== "confirmada").length;
|
|
4393
|
+
const filters = useMemo(() => [{
|
|
4394
|
+
id: "manifestacao",
|
|
4395
|
+
label: "Manifesta\xE7\xE3o",
|
|
4396
|
+
allLabel: "Todas",
|
|
4397
|
+
value: tab === "todas" ? void 0 : tab,
|
|
4398
|
+
onChange: (v) => setTab(v ?? "todas"),
|
|
4399
|
+
options: [
|
|
4400
|
+
{ value: "pendentes", label: "Pendentes" },
|
|
4401
|
+
{ value: "confirmadas", label: "Confirmadas" },
|
|
4402
|
+
{ value: "erro", label: "Com erro" }
|
|
4403
|
+
]
|
|
4404
|
+
}], [tab]);
|
|
4405
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
4406
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
4407
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
4408
|
+
/* @__PURE__ */ jsx(ShieldCheck, { className: "mt-0.5 h-5 w-5 text-muted-foreground", "aria-hidden": true }),
|
|
4409
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4410
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
|
|
4411
|
+
pending,
|
|
4412
|
+
" ",
|
|
4413
|
+
pending === 1 ? "nota aguardando" : "notas aguardando",
|
|
4414
|
+
" manifesta\xE7\xE3o \xE0 SEFAZ"
|
|
4415
|
+
] }),
|
|
4416
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Manifestar responde \xE0 SEFAZ e tem prazo. Dar entrada responde ao seu estoque e ao financeiro \u2014 s\xE3o duas filas." })
|
|
4417
|
+
] })
|
|
4418
|
+
] }),
|
|
4419
|
+
/* @__PURE__ */ jsx(Hint, { className: "mt-3", children: /* @__PURE__ */ jsx("span", { children: "A manifesta\xE7\xE3o continua sendo feita no sistema antigo por enquanto. Aqui voc\xEA l\xEA a fila e o que a SEFAZ respondeu; a entrada no estoque e a conta a pagar j\xE1 s\xE3o feitas nesta tela nova." }) }),
|
|
4420
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
|
|
4421
|
+
/* @__PURE__ */ jsx(EmBreve, { label: "Aceitar / Recusar", o_que: "Responder \xE0 SEFAZ \u2014 \xE9 obriga\xE7\xE3o com prazo. Hoje a manifesta\xE7\xE3o \xE9 feita no sistema antigo" }),
|
|
4422
|
+
/* @__PURE__ */ jsx(EmBreve, { label: "Puxar por chave", o_que: "Buscar uma NF-e espec\xEDfica pela chave de acesso de 44 d\xEDgitos" }),
|
|
4423
|
+
/* @__PURE__ */ jsx(EmBreve, { label: "Sincronizar agora", o_que: "Pedir \xE0 SEFAZ o que chegou desde a \xFAltima varredura" }),
|
|
4424
|
+
/* @__PURE__ */ jsx(EmBreve, { label: "Resincronizar tudo", o_que: "Refazer a varredura inteira do per\xEDodo" }),
|
|
4425
|
+
/* @__PURE__ */ jsx(EmBreve, { label: "Diagnosticar", o_que: "Por que uma nota n\xE3o chegou: certificado, credencial ou cursor parado" })
|
|
4426
|
+
] })
|
|
4427
|
+
] }),
|
|
4428
|
+
/* @__PURE__ */ jsx(ListFilterBar, { filters, onClear: () => setTab("todas"), activeCount: tab === "todas" ? 0 : 1 }),
|
|
4429
|
+
loading ? /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full" }, i)) }) : shown.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "p-10 text-center", children: [
|
|
4430
|
+
/* @__PURE__ */ jsx(FileText, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
4431
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota nesta fila" })
|
|
4432
|
+
] }) : /* @__PURE__ */ jsx(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
4433
|
+
/* @__PURE__ */ jsx("thead", { className: "border-b bg-muted/40 text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
4434
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Emiss\xE3o" }),
|
|
4435
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "N\xBA" }),
|
|
4436
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Fornecedor" }),
|
|
4437
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Valor" }),
|
|
4438
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Parcelas" }),
|
|
4439
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center font-medium", children: "Manifesta\xE7\xE3o" }),
|
|
4440
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "A\xE7\xF5es" })
|
|
4441
|
+
] }) }),
|
|
4442
|
+
/* @__PURE__ */ jsx("tbody", { children: shown.map((note) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/30", children: [
|
|
4443
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums text-muted-foreground", children: day3(note.issueDate) }),
|
|
4444
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums font-medium", children: note.number }),
|
|
4445
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: note.supplierName || "\u2014" }),
|
|
4446
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums", children: money3(note.total) }),
|
|
4447
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums text-muted-foreground", children: note.instalmentCount }),
|
|
4448
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-center", children: /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${MANIFEST_TONE[note.manifestationStatus ?? ""] ?? "bg-muted text-muted-foreground"}`, children: note.manifestationStatus ?? "n\xE3o manifestada" }) }),
|
|
4449
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
|
|
4450
|
+
/* @__PURE__ */ jsx(EmBreve, { label: "XML", o_que: "Ver o XML bruto autorizado desta nota" }),
|
|
4451
|
+
/* @__PURE__ */ jsx(EmBreve, { label: "PDF", o_que: "Abrir o DANFE desta nota" }),
|
|
4452
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: () => onOpenNote(note.id), children: [
|
|
4453
|
+
"Abrir concilia\xE7\xE3o",
|
|
4454
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "ml-1.5 h-3.5 w-3.5" })
|
|
4455
|
+
] })
|
|
4456
|
+
] }) })
|
|
4457
|
+
] }, note.id)) })
|
|
4458
|
+
] }) }) })
|
|
4459
|
+
] });
|
|
4460
|
+
}
|
|
4461
|
+
var HASH_PREFIX = "#/inbound-notes";
|
|
4462
|
+
function viewFromLocation() {
|
|
4463
|
+
if (typeof window === "undefined") return { kind: "list" };
|
|
4464
|
+
const hash = window.location.hash;
|
|
4465
|
+
const note = hash.match(/\/inbound-notes\/note\/([0-9a-f-]{36})/i);
|
|
4466
|
+
if (note) return { kind: "note", id: note[1] };
|
|
4467
|
+
if (/\/inbound-notes\/manifest/.test(hash)) return { kind: "manifest" };
|
|
4468
|
+
return { kind: "list" };
|
|
4469
|
+
}
|
|
4470
|
+
var pathOf = (view) => view.kind === "note" ? `${HASH_PREFIX}/note/${view.id}` : view.kind === "manifest" ? `${HASH_PREFIX}/manifest` : HASH_PREFIX;
|
|
4471
|
+
function InboundNotesPage({ payableRoute } = {}) {
|
|
4472
|
+
const [view, setView] = useState(viewFromLocation);
|
|
4473
|
+
const go = useCallback((next) => {
|
|
4474
|
+
setView(next);
|
|
4475
|
+
if (typeof window !== "undefined") {
|
|
4476
|
+
window.history.replaceState(null, "", pathOf(next));
|
|
4477
|
+
}
|
|
4478
|
+
}, []);
|
|
4479
|
+
useEffect(() => {
|
|
4480
|
+
if (typeof window === "undefined") return;
|
|
4481
|
+
const onPop = () => setView(viewFromLocation());
|
|
4482
|
+
window.addEventListener("popstate", onPop);
|
|
4483
|
+
window.addEventListener("hashchange", onPop);
|
|
4484
|
+
return () => {
|
|
4485
|
+
window.removeEventListener("popstate", onPop);
|
|
4486
|
+
window.removeEventListener("hashchange", onPop);
|
|
4487
|
+
};
|
|
4488
|
+
}, []);
|
|
4489
|
+
if (view.kind === "note") {
|
|
4490
|
+
return /* @__PURE__ */ jsx(
|
|
4491
|
+
NoteReconcileView,
|
|
4492
|
+
{
|
|
4493
|
+
noteId: view.id,
|
|
4494
|
+
onBack: () => go({ kind: "list" }),
|
|
4495
|
+
onOpenInvoice: payableRoute ? (invoiceId) => {
|
|
4496
|
+
window.location.hash = payableRoute(invoiceId);
|
|
4497
|
+
} : void 0
|
|
4498
|
+
}
|
|
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
|
+
);
|
|
4511
|
+
}
|
|
4512
|
+
function makeInboundNotesRoute(options = {}) {
|
|
4513
|
+
return function InboundNotesRoute() {
|
|
4514
|
+
return /* @__PURE__ */ jsx(InboundNotesPage, { ...options });
|
|
4515
|
+
};
|
|
4516
|
+
}
|
|
4517
|
+
|
|
1932
4518
|
// src/lib/financial-provider.ts
|
|
1933
4519
|
function fiscalReader(provider) {
|
|
1934
4520
|
if (!provider.getNotaById) throw new Error("PlugNotas requires fiscal document reads from Financial.");
|
|
@@ -2020,6 +4606,84 @@ function createPlugNotasFinancialProvider(options) {
|
|
|
2020
4606
|
}
|
|
2021
4607
|
});
|
|
2022
4608
|
}
|
|
4609
|
+
function TaxGroupsView({ onBack } = {}) {
|
|
4610
|
+
return /* @__PURE__ */ jsx(
|
|
4611
|
+
ComingSoon,
|
|
4612
|
+
{
|
|
4613
|
+
title: "Grupos Fiscais",
|
|
4614
|
+
summary: "Perfis tribut\xE1rios reutiliz\xE1veis \u2014 CFOP, CSOSN/CST, PIS, COFINS, ICMS \u2014 que os produtos usam na emiss\xE3o.",
|
|
4615
|
+
v1Path: "Estoque \u2192 Notas Fiscais \u2192 Grupos Fiscais",
|
|
4616
|
+
measured: "Vazio no V1 da Artorius (\xABNenhum grupo fiscal cadastrado\xBB), coerente com eles n\xE3o emitirem nota. O Pertinho do C\xE9u e o Espa\xE7o Facial t\xEAm 11 entre os dois.",
|
|
4617
|
+
missing: [
|
|
4618
|
+
"Trazer o cadastro para dentro do grupo: a tabela plg_fiscalbr_tax_groups j\xE1 existe e j\xE1 recebeu os 11 grupos do V1",
|
|
4619
|
+
"Hoje ele s\xF3 aparece na lista gen\xE9rica de cadastros, longe de onde a nota \xE9 usada",
|
|
4620
|
+
"S\xF3 faz sentido junto com Notas Emitidas \u2014 grupo fiscal \xE9 insumo de emiss\xE3o"
|
|
4621
|
+
],
|
|
4622
|
+
onBack,
|
|
4623
|
+
backTo: "Notas Fiscais"
|
|
4624
|
+
}
|
|
4625
|
+
);
|
|
4626
|
+
}
|
|
4627
|
+
function IssuedNotesView({ onBack } = {}) {
|
|
4628
|
+
return /* @__PURE__ */ jsx(
|
|
4629
|
+
ComingSoon,
|
|
4630
|
+
{
|
|
4631
|
+
title: "Notas Emitidas",
|
|
4632
|
+
summary: "As NF-e, NFS-e e NFC-e que a casa emitiu, com o estado de cada uma na SEFAZ \u2014 autorizada, processando, rejeitada ou cancelada \u2014 e a comanda ou fatura que deu origem a ela.",
|
|
4633
|
+
v1Path: "Estoque \u2192 Notas Fiscais \u2192 Notas Emitidas",
|
|
4634
|
+
measured: "A Artorius n\xE3o emite nota hoje: em 80 documentos recebidos, zero emitidos. Quem emite \xE9 o Pertinho do C\xE9u e o Espa\xE7o Facial.",
|
|
4635
|
+
missing: [
|
|
4636
|
+
"Ligar o emissor da PlugNotas a este CNPJ (o certificado A1 j\xE1 est\xE1 l\xE1 desde maio; falta a credencial chegar neste ambiente)",
|
|
4637
|
+
"A lista com o estado na SEFAZ e o rastro de tentativas \u2014 as tabelas j\xE1 existem (plg_fiscalbr_nfce_jobs, _events)",
|
|
4638
|
+
"Abrir o XML autorizado e o DANFE de cada nota",
|
|
4639
|
+
"Cancelar uma nota emitida, com justificativa"
|
|
4640
|
+
],
|
|
4641
|
+
onBack,
|
|
4642
|
+
backTo: "Notas Fiscais"
|
|
4643
|
+
}
|
|
4644
|
+
);
|
|
4645
|
+
}
|
|
4646
|
+
function ReturnedNotesView({ onBack } = {}) {
|
|
4647
|
+
return /* @__PURE__ */ jsx(
|
|
4648
|
+
ComingSoon,
|
|
4649
|
+
{
|
|
4650
|
+
title: "Notas Devolvidas",
|
|
4651
|
+
summary: "As notas fiscais de devolu\xE7\xE3o: o que voltou para o fornecedor, e o que isso desfaz no estoque e na conta a pagar.",
|
|
4652
|
+
v1Path: "Estoque \u2192 Notas Fiscais \u2192 Notas Devolvidas",
|
|
4653
|
+
v1AlsoMissing: true,
|
|
4654
|
+
missing: [
|
|
4655
|
+
"Decidir o gesto: devolver a nota INTEIRA ou s\xF3 algumas linhas dela",
|
|
4656
|
+
"A devolu\xE7\xE3o ESTORNA o estoque que a entrada criou \u2014 e o movimento de estorno j\xE1 existe no V2 (reverses_movement_id)",
|
|
4657
|
+
"E abate o t\xEDtulo a pagar, ou gera um cr\xE9dito com o fornecedor. \xC9 decis\xE3o de produto, n\xE3o de c\xF3digo",
|
|
4658
|
+
"Emitir a NF-e de devolu\xE7\xE3o exige emissor ligado \u2014 mesma depend\xEAncia de Notas Emitidas"
|
|
4659
|
+
],
|
|
4660
|
+
onBack,
|
|
4661
|
+
backTo: "Notas Fiscais"
|
|
4662
|
+
}
|
|
4663
|
+
);
|
|
4664
|
+
}
|
|
4665
|
+
function inboundNotesInventoryView(options = {}) {
|
|
4666
|
+
return {
|
|
4667
|
+
id: "inbound-notes",
|
|
4668
|
+
label: "Notas Fiscais",
|
|
4669
|
+
icon: "FileText",
|
|
4670
|
+
render: () => /* @__PURE__ */ jsx(InboundNotesPage, { ...options }),
|
|
4671
|
+
// A ordem é a do V1: Grupos Fiscais vem antes das duas de emissão, que é
|
|
4672
|
+
// onde ele é usado.
|
|
4673
|
+
//
|
|
4674
|
+
// Bonificação NÃO é aba. Ela chegou aqui por engano meu: no V1 não existe
|
|
4675
|
+
// esse menu — a bonificação é uma NATUREZA DE OPERAÇÃO que vem escrita na
|
|
4676
|
+
// nota (57 delas, em 11 grafias), e o que ela muda é o comportamento da
|
|
4677
|
+
// entrada: a mercadoria soma ao estoque e NÃO abre conta a pagar. O aviso
|
|
4678
|
+
// continua onde serve, na conciliação da nota. Uma tela vazia para um
|
|
4679
|
+
// comportamento é um menu que promete o que não vai entregar.
|
|
4680
|
+
children: [
|
|
4681
|
+
{ id: "tax-groups", label: "Grupos Fiscais", render: () => /* @__PURE__ */ jsx(TaxGroupsView, {}) },
|
|
4682
|
+
{ id: "issued-notes", label: "Notas Emitidas", render: () => /* @__PURE__ */ jsx(IssuedNotesView, {}) },
|
|
4683
|
+
{ id: "returned-notes", label: "Notas Devolvidas", render: () => /* @__PURE__ */ jsx(ReturnedNotesView, {}) }
|
|
4684
|
+
]
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
2023
4687
|
|
|
2024
4688
|
// src/index.ts
|
|
2025
4689
|
function createPlugNotasPlugin(options = {}) {
|
|
@@ -2032,8 +4696,28 @@ function createPlugNotasPlugin(options = {}) {
|
|
|
2032
4696
|
scope: "addon",
|
|
2033
4697
|
defaultEnabled: true,
|
|
2034
4698
|
dependencies: ["financial"],
|
|
4699
|
+
// SEM linha no rail, de propósito.
|
|
4700
|
+
//
|
|
4701
|
+
// Entrada de Notas é assunto de ESTOQUE, não assunto do menu: no V1 o caminho
|
|
4702
|
+
// é `Estoque → Notas Fiscais → Entrada de Notas`. Montei como linha solta na
|
|
4703
|
+
// primeira tentativa e estava errado — quem vem do V1 procura dentro do
|
|
4704
|
+
// Estoque e não acha. O app entrega a tela ao inventário por
|
|
4705
|
+
// `createInventoryPlugin({ extraViews })`, e ela aparece como aba irmã de
|
|
4706
|
+
// Produtos, Movimentações e Contagens.
|
|
4707
|
+
//
|
|
4708
|
+
// A rota própria FICA: ela é o deep link (`#/inbound-notes`), e é por onde o
|
|
4709
|
+
// agente e um link de e-mail chegam sem passar pela faixa de abas.
|
|
2035
4710
|
navigation: [],
|
|
2036
|
-
routes: [
|
|
4711
|
+
routes: [
|
|
4712
|
+
{
|
|
4713
|
+
path: "/inbound-notes",
|
|
4714
|
+
// O registro de rotas monta componentes sem prop. A opção do host entra
|
|
4715
|
+
// por fechamento, e a fábrica mora no `.tsx` para este manifesto seguir
|
|
4716
|
+
// sendo TypeScript sem JSX.
|
|
4717
|
+
component: makeInboundNotesRoute({ payableRoute: options.payableRoute }),
|
|
4718
|
+
permission: { feature: "financial", action: "read" }
|
|
4719
|
+
}
|
|
4720
|
+
],
|
|
2037
4721
|
// O grupo fiscal é CADASTRO, e por isso entra como registry e não como
|
|
2038
4722
|
// rota própria: ele aparece onde os outros cadastros do produto aparecem.
|
|
2039
4723
|
registries: fiscalRegistries,
|
|
@@ -2108,6 +4792,6 @@ function createPlugNotasPlugin(options = {}) {
|
|
|
2108
4792
|
});
|
|
2109
4793
|
}
|
|
2110
4794
|
|
|
2111
|
-
export { PLUGNOTAS_CONNECTOR_ID, PLUGNOTAS_FIELDS, PLUGNOTAS_SETTINGS, createPlugNotasConnector, createPlugNotasFinancialProvider, createPlugNotasPlugin, isValidCnpj, markPlugNotasEmitterReady };
|
|
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 };
|
|
2112
4796
|
//# sourceMappingURL=index.js.map
|
|
2113
4797
|
//# sourceMappingURL=index.js.map
|