diff --git a/src/packages/administrativo/components/TPessoa/TPessoaTableFormSubview.tsx b/src/packages/administrativo/components/TPessoa/TPessoaTableFormSubview.tsx index a756b94..04ad22f 100644 --- a/src/packages/administrativo/components/TPessoa/TPessoaTableFormSubview.tsx +++ b/src/packages/administrativo/components/TPessoa/TPessoaTableFormSubview.tsx @@ -64,7 +64,7 @@ export default function TPessoaTableFormSubview({ params, servico, selectedTPess return ( -
+
diff --git a/src/packages/servicos/actions/TServicoPedido/HandleSelectTServicoTipoAction.ts b/src/packages/servicos/actions/TServicoPedido/HandleSelectTServicoTipoAction.ts index 531d24b..c569337 100644 --- a/src/packages/servicos/actions/TServicoPedido/HandleSelectTServicoTipoAction.ts +++ b/src/packages/servicos/actions/TServicoPedido/HandleSelectTServicoTipoAction.ts @@ -1,8 +1,15 @@ -import HandleSelectTServicoTipoInterface from "./HandleSelectTServicoTipoInterface"; + export default function HandleSelectTServicoTipoAction({ servico, emolumento, onOpenPessoaForm, onAddItem }: HandleSelectTServicoTipoInterface) { - if (!servico || !emolumento) return; + if (!servico || !emolumento) { + + return { + 'status': 422, + 'detail': 'Serviço e emolumento devem ser selecionados' + }; + + }; if (servico?.tipo_pessoa) { diff --git a/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoPayload.ts b/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoPayload.ts index 5f25e93..524e76b 100644 --- a/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoPayload.ts +++ b/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoPayload.ts @@ -6,7 +6,7 @@ export default function PrepareTServicoItemPedidoPayload(data: TServicoItemPedid data.valor_documento = 0 // Verifica dados obrigatórios de serviço e emolumento - if (!data?.emolumentoSelecionado?.emolumento_id || !data?.servicoSelecionado?.servico_tipo_id) { + if (!data?.emolumento?.emolumento_id || !data?.servico_tipo?.servico_tipo_id) { return { status: 400, message: 'Dados informados inválidos: serviço ou emolumento não informado.' @@ -14,7 +14,7 @@ export default function PrepareTServicoItemPedidoPayload(data: TServicoItemPedid } // Valida sistema_id (padrão 2, mas ainda assim precisa ser válido) - if (!data?.emolumentoSelecionado.sistema_id || data.emolumentoSelecionado.sistema_id <= 0) { + if (!data?.emolumento.sistema_id || data.servico_tipo.sistema_id <= 0) { return { status: 400, message: 'Sistema inválido ou não informado.' @@ -30,10 +30,10 @@ export default function PrepareTServicoItemPedidoPayload(data: TServicoItemPedid } return { - sistema_id: data.emolumentoSelecionado.sistema_id, + sistema_id: data.emolumento.sistema_id, valor_documento: data.valor_documento, quantidade: data.quantidade, - emolumento_id: data.emolumentoSelecionado.emolumento_id, + emolumento_id: data.emolumento.emolumento_id, }; } diff --git a/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoResponseItem.ts b/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoResponseItem.ts index dadf841..985c7ac 100644 --- a/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoResponseItem.ts +++ b/src/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoResponseItem.ts @@ -44,10 +44,10 @@ export function PrepareTServicoItemPedidoCalculoResponse( const item: TServicoItemPedidoAddResponseInterface = { emolumento_id: result.emolumento_id, emolumento_item_id: result.emolumento_item_id ?? null, - servico_tipo_id: data.servicoSelecionado.servico_tipo_id ?? 0, - tipo_item: data.servicoSelecionado.tipo_item ?? "", - descricao: data.servicoSelecionado.descricao ?? "", - tabela: data.emolumentoSelecionado?.descricao ?? "", + servico_tipo_id: data.servico_tipo.servico_tipo_id ?? 0, + tipo_item: data.servico_tipo.tipo_item ?? "", + descricao: data.servico_tipo.descricao ?? "", + tabela: data.servico_tipo?.descricao ?? "", situacao: "F", qtd: 1, valor: result.valor_total ?? 0, diff --git a/src/packages/servicos/components/TServicoPedido/TServicoPedidoForm.tsx b/src/packages/servicos/components/TServicoPedido/TServicoPedidoForm.tsx index 191117d..ef44577 100644 --- a/src/packages/servicos/components/TServicoPedido/TServicoPedidoForm.tsx +++ b/src/packages/servicos/components/TServicoPedido/TServicoPedidoForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { CreditCard, Package, PlusIcon, UserSquare2 } from 'lucide-react'; +import { CreditCard, Package, UserSquare2 } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -38,9 +38,12 @@ import { useTServicoPedidoSaveHook } from '@/packages/servicos/hooks/TServicoPed import { useTServicoPedidoShowHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoShowHook'; import { TServicoPedidoFormInterface } from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoFormInterface'; import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface'; +import { FormatCPFCNPJForm } from '@/shared/actions/CPF/FormatCPFCNPJForm'; +import { UnmaskCPFCNPJForm } from '@/shared/actions/CPF/UnmaskCPFCNPJForm'; import { parseNumberInput } from '@/shared/actions/form/parseNumberInput'; import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog'; import LoadingButton from '@/shared/components/loadingButton/LoadingButton'; +import { useResponse } from '@/shared/components/response/ResponseContext'; import { StepNavigator, StepNavigatorRef, @@ -58,6 +61,7 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido const { setValue, reset, watch } = form; const [isSaving, setIsSaving] = useState(false); + const [isAdding, setIsAdding] = useState(false); const [isPessoaFormOpen, setIsPessoaFormOpen] = useState(false); const [isSaveConfirmOpen, setIsSaveConfirmOpen] = useState(false); const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false) @@ -72,6 +76,7 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido const handleCloseSaveConfirm = useCallback(() => setIsSaveConfirmOpen(false), []); // Hooks + const { response, setResponse } = useResponse() const { saveTServicoPedido } = useTServicoPedidoSaveHook(); const { showTServicoPedido } = useTServicoPedidoShowHook(); const { TServicoItemPedidoLocal, localAddTServicoItemPedido, setLocalTServicoItemPedido } = useTServicoItemPedidoLocalAddHook(setValue); @@ -158,6 +163,8 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido const handleAddItemWithPessoa = useCallback(async (selectedTPessoa: any) => { + setIsAdding(true) + // Constroi um novo item const newItem: TServicoSubviewInterface = await addTServicoItemPedido({ servico_tipo: selectedServicoTipo, @@ -190,6 +197,8 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido // Atualiza os itens do formulário form.setValue(`itens.${index}`, newItem); + setIsAdding(false) + }, [ addTServicoItemPedido, selectedServicoTipo, @@ -239,10 +248,12 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido // Controle de itens const handleAddItemBasic = useCallback(async () => { + setIsAdding(true) + // Prepara e valida os dados de item do pedido const payload = { - servicoSelecionado: selectedServicoTipo, - emolumentoSelecionado: selectedEmolumento + servico_tipo: selectedServicoTipo, + emolumento: selectedEmolumento } // Verifica se os dados foram criados corretamente @@ -254,6 +265,8 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido // Se tiver um novo item, adiciona o mesmo na tela if (newItem) localAddTServicoItemPedido(newItem); + setIsAdding(false) + }, [addTServicoItemPedido, selectedServicoTipo, selectedEmolumento, localAddTServicoItemPedido]); // Habilita o formulário de pessoas @@ -267,15 +280,42 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido // Adiciona o item a tabela e verifica se deve ou não montar a subview da linha da tabela const handleSelectServicoTipo = useCallback(() => { - HandleSelectTServicoTipoAction({ + const response = HandleSelectTServicoTipoAction({ servico: selectedServicoTipo, emolumento: selectedEmolumento, onOpenPessoaForm: handleOpenPessoaForm, onAddItem: handleAddItemBasic }) + // Verifica se existem erros + if (response?.status) { + setResponse(response) + } + }, [selectedServicoTipo, selectedEmolumento, handleOpenPessoaForm, handleAddItemBasic]); + // Cálculo automático dos totais + const calcularTotais = useCallback(() => { + if (!TServicoItemPedidoLocal || !TServicoItemPedidoLocal.length) { + setValue("valor_pedido", 0); + setValue("valor_pago", 0); + return; + } + + const total = TServicoItemPedidoLocal.reduce((acc, item) => { + const valor = Number(item.valor_total ?? item.valor ?? 0); + return acc + valor; + }, 0); + + setValue("valor_pedido", total, { shouldDirty: true }); + + // opcional: manter valor pago igual ao pedido + const valorPagoAtual = watch("valor_pago"); + if (!valorPagoAtual || valorPagoAtual === 0) { + setValue("valor_pago", total, { shouldDirty: true }); + } + }, [TServicoItemPedidoLocal, setValue, watch]); + // Dispara a busca do pedido useEffect(() => { @@ -297,6 +337,11 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido }, []); + // Monitora mudanças na lista de itens + useEffect(() => { + calcularTotais(); + }, [TServicoItemPedidoLocal, calcularTotais]); + // Memoriza os dados para não renderizar novamente const sections: StepSection[] = useMemo(() => [ { @@ -375,8 +420,18 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido name="cpfcnpj_apresentante" render={({ field }) => ( - CPF - + CPF/CNPJ + + { + const raw = UnmaskCPFCNPJForm(e.target.value); + field.onChange(raw); + }} + maxLength={14} + /> + )} @@ -403,8 +458,18 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido name="selo_pessoa_cpfcnpj" render={({ field }) => ( - CPF - + CPF/CNPJ + + { + const raw = UnmaskCPFCNPJForm(e.target.value); + field.onChange(raw); + }} + maxLength={14} + /> + )} @@ -449,9 +514,12 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido />
- +
@@ -486,8 +554,17 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido name="pagador_cpfcnpj" render={({ field }) => ( - CPF/CNPJ Requerente - + CPF/CNPJ + + { + const raw = UnmaskCPFCNPJForm(e.target.value); + field.onChange(raw); + }} + maxLength={14} + /> )} @@ -502,7 +579,9 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido Valor do Pedido field.onChange(parseNumberInput(e))} /> + onChange={(e) => field.onChange(parseNumberInput(e))} + readOnly={true} + /> diff --git a/src/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoAddHook.ts b/src/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoAddHook.ts index 273bdd8..6a87816 100644 --- a/src/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoAddHook.ts +++ b/src/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoAddHook.ts @@ -7,10 +7,13 @@ import { GCalculoServicoService } from '@/packages/administrativo/services/GCalc import PrepareTServicoItemPedidoPayload from '@/packages/servicos/actions/TServicoPedido/PrepareTServicoItemPedidoPayload'; import TServicoItemPedidoAddInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface'; import { default as TServicoItemPedidoIndexResponseInterface } from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddResponseInterface'; +import { useResponse } from '@/shared/components/response/ResponseContext'; export const useTServicoItemPedidoAddHook = (setValue?: UseFormSetValue) => { + const { setResponse } = useResponse(); + const [TServicoItemPedido, setTServicoItemPedido] = useState([]); const addTServicoItemPedido = async (data: TServicoItemPedidoAddInterface) => { @@ -18,6 +21,14 @@ export const useTServicoItemPedidoAddHook = (setValue?: UseFormSetValue({ resolver: zodResolver(TServicoPedidoFormSchema), defaultValues: { + escrevente_id: 0, + apresentante: "", + cpfcnpj_apresentante: "", + selo_pessoa_nome: "", + selo_pessoa_cpfcnpj: "", + servico_tipo: null, + emolumento: null, + itens: [], + pagador_nome: "", + pagador_cpfcnpj: "", + valor_pedido: 0, + valor_pago: 0, + tipo_pagamento: null, ...defaults, }, }); diff --git a/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoSaveHook.ts b/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoSaveHook.ts index 6500b27..e72dd90 100644 --- a/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoSaveHook.ts +++ b/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoSaveHook.ts @@ -2,10 +2,9 @@ import { useState } from 'react'; -import { useResponse } from '@/shared/components/response/ResponseContext'; - import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface'; import { TServicoPedidoSaveService } from '@/packages/servicos/services/TServicoPedido/TServicoPedidoSaveService'; +import { useResponse } from '@/shared/components/response/ResponseContext'; export const useTServicoPedidoSaveHook = () => { @@ -17,6 +16,7 @@ export const useTServicoPedidoSaveHook = () => { const [isOpen, setIsOpen] = useState(false); const saveTServicoPedido = async (data: TServicoPedidoInterface) => { + const response = await TServicoPedidoSaveService(data); // Armazena os dados da resposta @@ -30,6 +30,7 @@ export const useTServicoPedidoSaveHook = () => { // Retorna os valores de forma imediata return response.data; + }; return { TServicoPedido, saveTServicoPedido, isOpen, setIsOpen }; diff --git a/src/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface.ts b/src/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface.ts index 2c6ece1..1269f8f 100644 --- a/src/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface.ts +++ b/src/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface.ts @@ -2,8 +2,8 @@ import TServicoTipoInterface from "@/app/(protected)/(cadastros)/cadastros/_inte import GEmolumentoInterface from "@/packages/administrativo/interfaces/GEmolumento/GEmolumentoInterface"; export default interface TServicoItemPedidoAddInterface { - emolumentoSelecionado: GEmolumentoInterface; - servicoSelecionado: TServicoTipoInterface; + emolumento: GEmolumentoInterface; + servico_tipo: TServicoTipoInterface; sistema_id?: number; valor_documento?: number; quantidade?: number; diff --git a/src/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema.ts b/src/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema.ts index e17eb78..e9e92c4 100644 --- a/src/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema.ts +++ b/src/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema.ts @@ -5,39 +5,54 @@ export const TServicoPedidoFormSchema = z.object({ .number() .int() .positive("Escrevente é obrigatório."), + apresentante: z .string() - .optional(), + .min(1, "Apresentante é obrigatório."), + cpfcnpj_apresentante: z .string() - .optional(), + .min(1, "CPF/CNPJ do apresentante é obrigatório."), + selo_pessoa_nome: z .string() - .optional(), + .min(1, "Nome da pessoa do selo é obrigatório."), + selo_pessoa_cpfcnpj: z .string() - .optional(), + .min(1, "CPF/CNPJ da pessoa do selo é obrigatório."), + servico_tipo: z .any() .refine((v) => !!v, "Selecione um serviço."), + emolumento: z .any() .refine((v) => !!v, "Selecione um emolumento."), - itens: z.array(z.any()).optional(), - pagador_nome: z.string().optional(), - pagador_cpfcnpj: z.string().optional(), + + itens: z + .array(z.any()) + .min(1, "Adicione ao menos um item ao pedido."), + + pagador_nome: z + .string() + .min(1, "Nome do requerente é obrigatório."), + + pagador_cpfcnpj: z + .string() + .min(1, "CPF/CNPJ do requerente é obrigatório."), + valor_pedido: z .number() - .nonnegative("Valor do pedido inválido.") - .optional(), + .nonnegative("Valor do pedido inválido."), + valor_pago: z .number() - .nonnegative("Valor pago inválido.") - .optional(), + .nonnegative("Valor pago inválido."), + tipo_pagamento: z .any() - .refine((v) => !!v, "Selecione a forma de pagamento.") - .optional(), + .refine((v) => !!v, "Selecione a forma de pagamento."), }); export type TServicoPedidoFormValues = z.infer; \ No newline at end of file diff --git a/src/shared/actions/CPF/FormatCPFCNPJForm.ts b/src/shared/actions/CPF/FormatCPFCNPJForm.ts new file mode 100644 index 0000000..1fa91e1 --- /dev/null +++ b/src/shared/actions/CPF/FormatCPFCNPJForm.ts @@ -0,0 +1,25 @@ +import { UnmaskCPFCNPJForm } from "./UnmaskCPFCNPJForm"; + +export function isCpf(value: string): boolean { + return UnmaskCPFCNPJForm(value).length <= 11; +} + +export function FormatCPFCNPJForm(value: string = ''): string { + const digits = UnmaskCPFCNPJForm(value); + + // CPF — 11 dígitos + if (digits.length <= 11) { + return digits + .replace(/^(\d{3})(\d)/, "$1.$2") + .replace(/^(\d{3})\.(\d{3})(\d)/, "$1.$2.$3") + .replace(/^(\d{3})\.(\d{3})\.(\d{3})(\d{1,2})$/, "$1.$2.$3-$4"); + } + + // CNPJ — 14 dígitos + return digits + .slice(0, 14) + .replace(/^(\d{2})(\d)/, "$1.$2") + .replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3") + .replace(/^(\d{2})\.(\d{3})\.(\d{3})(\d)/, "$1.$2.$3/$4") + .replace(/^(\d{2})\.(\d{3})\.(\d{3})\/(\d{4})(\d{1,2})$/, "$1.$2.$3/$4-$5"); +} diff --git a/src/shared/actions/CPF/UnmaskCPFCNPJForm.ts b/src/shared/actions/CPF/UnmaskCPFCNPJForm.ts new file mode 100644 index 0000000..462ca38 --- /dev/null +++ b/src/shared/actions/CPF/UnmaskCPFCNPJForm.ts @@ -0,0 +1,3 @@ +export function UnmaskCPFCNPJForm(value: string = ""): string { + return value.replace(/\D/g, "").slice(0, 11); +}