Acesso Externo via API (Web Service)
Índice
- Apresentação
- Ambiente de Teste e Produção
- Autenticação
- Consultar Notas Fiscais
- Emitir Notas Fiscais
- Cancelar Notas Fiscais
- Consultar Faturas
- Gerar Boleto de Faturas Selecionadas
Apresentação
Visando atender a necessidade dos contribuintes que precisam enviar ou consultar muitos registros, ou que desejam automatizar o acesso fazendo integração nos seus próprios sistemas, a prefeitura disponibiliza um acesso externo via API (Web Service).
Ambiente de Teste e Produção
O usuário tem à disposição dois ambientes, um somente para realizar testes, chamado de Ambiente de Teste, acessível através da URL https://meu_municipio.teste.joinfiscal.com.br , e o outro chamado Ambiente de Produção, acessível através da URL https://meu_municipio.joinfiscal.com.br . Antes de submeter qualquer dado para o Ambiente de Produção, é altamente recomendável utilizar primeiro o Ambiente de Teste para entender como a API funciona.
Qualquer usuário cadastrado no sistema pode fazer o acesso via API, desde que tenha a devida permissão para fazer operações de consulta, criação, atualização ou cancelamento sobre o registro em questão. Se ainda não está cadastrado, faça o seu cadastro antes de continuar, bastando acessar os links postados anteriormente. Observe que no Ambiente de Produção, é necessário aguardar a aprovação do cadastro para acessar o sistema (e consequentemente a API).
Autenticação
Para fazer a comunicação com a API, o usuário pode usar qualquer linguagem de programação capaz de fazer chamadas XML-RPC. Também é necessário criar uma Chave de API, bastando acessar o menu Perfil > Preferências:
Acessando aba Segurança da Conta:
Clicando no botão para criar uma Nova Chave de API:
O usuário pode criar quantas chaves quiser, bem como pode revogar o acesso para as chaves existentes. Isso é muito útil para fornecer acesso à sistemas de terceiros, sem precisar informar a sua senha de acesso ao sistema (login).
De posse do nome de usuário (o mesmo usado para fazer login no sistema) e da chave de API (recém criada anteriormente), basta preencher os seguintes parâmetros:
# Ambiente de Produção
# url = 'https://meu_municipio.joinfiscal.com.br'
# Ambiente de Teste
url = 'https://meu_municipio.teste.joinfiscal.com.br'
banco = 'meu_municipio'
usuario = 'inserir nome de usuário'
chave = 'inserir chave da API'
// Ambiente de Produção
// final String url = "https://meu_municipio.joinfiscal.com.br";
// Ambiente de Teste
final String url = "https://meu_municipio.teste.joinfiscal.com.br";
final String db = "meu_municipio",
username = "nserir nome de usuário",
password = "inserir chave da API";
# Ambiente de Produção
# url = 'https://meu_municipio.joinfiscal.com.br'
# Ambiente de Teste
url = 'https://meu_municipio.teste.joinfiscal.com.br'
db = 'meu_municipio'
username = 'inserir nome de usuário'
password = 'inserir chave da API'
Utilize os parâmetros anteriores para autenticar:
import xmlrpc.client
# ==========================
# configuracoes
# ==========================
URL = 'https://teresina.joinfiscal.com.br' # endereco eletrônico do sistema
BANCO = 'teresina' # nome do municipio, de acordo com o endereco eletronico
USUARIO = '000.000.000-00' # login, CPF ou CNPJ
CHAVE = 'aafd1ed92dbe5274ceff4d0ef51e415645de9123' # exemplo de chave de API
def conectar():
common = xmlrpc.client.ServerProxy(f'{URL}/xmlrpc/2/common')
uid = common.authenticate(BANCO, USUARIO, CHAVE, {})
if not uid:
raise Exception('falha na autenticacao. verifique usuario ou chave')
models = xmlrpc.client.ServerProxy(f'{URL}/xmlrpc/2/object')
return uid, models
# ==========================
# funcao auxiliar
# ==========================
def call(models, uid, model, method, args=None, kwargs=None):
if args is None:
args = []
if kwargs is None:
kwargs = {}
return models.execute_kw(
BANCO,
uid,
CHAVE,
model,
method,
args,
kwargs
)
Note que a biblioteca xmlrpc já vem instalada por padrão em qualquer ambiente
Python, não sendo necessária nenhuma dependência externa para realizar a comunicação.
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.XmlRpcException;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
final XmlRpcClient client = new XmlRpcClient();
final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
int uid = (int)client.execute(
common_config, "authenticate",
Arrays.asList(db, username, password, Collections.emptyMap())
);
final XmlRpcClient models = new XmlRpcClient() {{
setConfig(new XmlRpcClientConfigImpl() {{
setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
}});
}};
require "xmlrpc/client"
common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
uid = common.call('authenticate', db, username, password, {})
models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
Note que a biblioteca xmlrpc já vem instalada por padrão em qualquer ambiente
Ruby, não sendo necessária nenhuma dependência externa para realizar a comunicação.
Consultar Notas Fiscais
Liste as dez primeiras identificações id, nome de notas fiscais do usuário:
def main():
uid, models = conectar()
# exemplo: buscar 10 notas fiscais
notas = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[[]],
{
'fields': ['id', 'name'],
'limit': 10
}
)
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "search",
Arrays.asList(Collections.emptyList()),
new HashMap() {{ put("limit", 10); }}
)));
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_read', [[]],
{limit: 10})
Exemplo de resultado:
{'id': 6207, 'name': 'NF-2024-933-00007'}
{'id': 6352, 'name': 'NF-2024-933-00009'}
{'id': 5668, 'name': 'NF-2024-933-00004'}
{'id': 4958, 'name': 'NF-2023-933-00002'}
{'id': 6848, 'name': 'NF-2024-933-00012'}
{'id': 7268, 'name': 'NF-2024-933-00015'}
{'id': 5884, 'name': 'NF-2024-933-00005'}
{'id': 5486, 'name': 'NF-2023-933-00006'}
{'id': 4820, 'name': 'NF-2023-933-00001'}
{'id': 9931, 'name': 'NF-2025-933-00009'}
Pula os três primeiros registros e lista as sete identificações id da sequência de notas
fiscais (utilize offset e limit para controlar a paginação):
notas = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[[]],
{
'fields': ['id', 'name'],
'offset': 3,
'limit': 7
}
)
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "search",
Arrays.asList(Collections.emptyList()),
new HashMap() {{ put("offset", 3); put("limit", 7); }}
)));
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_read', [[]],
{offset: 3, limit: 7})
Exemplo de resultado:
{'id': 11782, 'name': 'NF-2026-2060-00001'}
{'id': 11745, 'name': 'NF-2025-2060-00001'}
{'id': 11783, 'name': 'NF-2026-933-00001'}
{'id': 4958, 'name': 'NF-2023-933-00002'}
{'id': 5486, 'name': 'NF-2023-933-00006'}
{'id': 5668, 'name': 'NF-2024-933-00004'}
{'id': 5884, 'name': 'NF-2024-933-00005'}
Utilize filtros para listar as dez primeiras identificações id de notas fiscais já canceladas, em ordem decrescente pela data de cancelamento (data_hora_cancelamento):
notas = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[
[
['state', '=', 'CANCELADA']
]
],
{
'fields': ['id', 'name', 'state', 'data_hora_cancelamento'],
'order': 'data_hora_cancelamento DESC',
'limit': 10
}
)
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "search",
Arrays.asList(Arrays.asList(Arrays.asList("state", "=", "CANCELADA"))),
new HashMap() {{
put("limit", 10);
put("order", "data_hora_cancelamento DESC");
}}
)));
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_read',
[[['state', '=', 'CANCELADA']]],
{limit: 10, order: 'data_hora_cancelamento DESC'})
Exemplo de resultado:
{'id': 11773, 'name': 'NF-2025-1160-00037', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-30 12:39:08'}
{'id': 11648, 'name': 'NF-2025-2025-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-23 16:39:41'}
{'id': 11772, 'name': 'NF-2025-1160-00036', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-23 16:33:30'}
{'id': 11417, 'name': 'NF-2025-1808-00005', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-23 15:00:45'}
{'id': 11736, 'name': 'NF-2025-2050-00001', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-19 12:36:32'}
{'id': 11724, 'name': 'NF-2025-2015-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-18 13:33:13'}
{'id': 11697, 'name': 'NF-2025-1812-00016', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 13:36:11'}
{'id': 11695, 'name': 'NF-2025-416-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 12:35:28'}
{'id': 11555, 'name': 'NF-2025-204-00007', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 11:35:38'}
{'id': 11407, 'name': 'NF-2025-1214-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 11:32:53'}
Utilize o método search_count para contar a quantidade de registros de um determinado filtro:
notas = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_count',
[
[
['state', '=', 'CANCELADA']
]
]
)
(Integer)models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "search_count",
Arrays.asList(Arrays.asList(Arrays.asList("state", "=", "CANCELADA")))
));
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_count',
[[['state', '=', 'CANCELADA']]])
Exemplo de resultado:
23
Obtenha todos os nomes dos campos, tipos e descrições de ajuda de um determinado
model, sendo nesse caso prefeitura.nota_fiscal (funciona para inspecionar
qualquer model):
campos = call(
models,
uid,
'prefeitura.nota_fiscal',
'fields_get',
[],
{
'attributes': [
'string',
'help',
'type',
'required',
'selection',
'relation'
]
}
)
Exemplo de resultado:
'autenticidade_codigo': {'required': False, 'string': 'Código', 'type': 'char'}
'autenticidade_valida': {'required': False, 'string': 'É Válida', 'type': 'boolean'}
'legislacao_atual': {'required': False, 'selection': [['LEGISLACAO_I', 'CTM 2019'], ['LEGISLACAO_II', 'CTM 2023']], 'string': 'Legislação Atual', 'type': 'selection'}
'pix_foi_desativado': {'help': 'Utilizado para desativar funcionalidade do PIX (caso não exista convênio ou sistema esteja offline).', 'required': False, 'string': 'Pix Foi Desativado', 'type': 'boolean'}
(Map<String, Map<String, Object>>)models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "fields_get",
Collections.emptyList(),
new HashMap() {{
put("attributes", Arrays.asList("string", "help", "type", "required",
"selection", "relation"));
}}
));
Exemplo de resultado:
{
'name': {'type': 'char',
'required': False,
'string': 'Número da Nota'},
'state': {'type': 'selection',
'required': False,
'selection': [['RASCUNHO', 'Rascunho'],
['CONCLUIDO', 'Emitida'],
['CANCELADA', 'Cancelada']],
'string': 'Estado'},
'prestador_id': {'type': 'many2one',
'help': 'Identificação do PRESTADOR.\n É importante que a Atividade Econômica esteja previamente registrada no cadastro do Prestador.',
'required': True,
'string': 'Prestador de Serviço',
'relation': 'res.partner'},
'tomador_id': {'type': 'many2one',
'help': "Identificação do TOMADOR.\n Para criar um novo registro basta digitar o nome do prestador e teclar 'Enter'. \nLogo após, uma tela será exibida para completar o cadastro. \nDá próxima vez este tomador estará disponível para seleção.",
'required': True,
'string': 'Tomador de Serviço',
'relation': 'prefeitura.tomador'},
'city_id': {'type': 'many2one',
'required': True,
'string': 'Local de Prestação do Serviço',
'relation': 'res.city'},
'description': {'type': 'text',
'help': 'Escreva aqui informações sobre os serviços prestados.',
'required': False,
'string': 'Descrição'},
'competencia': {'type': 'date',
'help': 'Selecione uma data dentro do mês corrente. ',
'required': True,
'string': 'Competência'},
'emissao': {'type': 'datetime',
'required': False,
'string': 'Data de Emissão'},
'data_hora_cancelamento': {'type': 'datetime',
'required': False,
'string': 'Data de Cancelamento'},
'cnae_ids': {'type': 'many2many',
'help': 'Selecione entre os CNAEs informados no cadastro do prestador.',
'required': False,
'string': 'CNAE',
'relation': 'prefeitura.cnae'},
'servico_ids': {'type': 'many2many',
'help': 'Selecione entre os serviços informados no cadastro do prestador.',
'required': True,
'string': 'Serviços',
'relation': 'prefeitura.servico'},
'local_incidencia_iss': {'type': 'selection',
'required': False,
'selection': [['PRESTADOR', 'Local do Prestador'],
['TOMADOR', 'Local do Tomador'],
['SERVICO', 'Local da prestação de serviço']],
'string': 'Local de Incidência do ISS'},
'tipo_recolhimento': {'type': 'selection',
'required': False,
'selection': [['ISS_RETIDO', 'ISS retido.'],
['ISS_A_RECOLHER', 'ISS a recolher.'],
['SEM_INCIDENCIA', 'Sem incidência.']],
'string': 'Tipo de Recolhimento'},
'item_ids': {'type': 'one2many',
'help': 'Especifique os serviços prestados informando a quantidade e valor unitário para cada ítem.',
'required': False,
'string': 'Itens',
'relation': 'prefeitura.item_nota_fiscal'},
'item_total': {'type': 'float',
'required': False,
'string': 'Total dos Itens'},
'base_calculo': {'type': 'monetary',
'help': 'Valor utilizado para a base de cálculo do ISS.',
'required': False,
'string': 'Base de Cálculo'},
'aliquota_iss': {'type': 'float',
'help': 'Alíquota estabelecida pelo CTM.',
'required': False,
'string': 'Alíquota do ISS (%)'},
'valor_iss': {'type': 'monetary',
'required': False,
'string': 'Valor do ISS'},
'valor_total_deducoes': {'type': 'monetary',
'required': False,
'string': 'Valor Total das Retenções'},
'nome_arquivo_pdf': {'type': 'char',
'help': 'Nome do arquivo PDF.',
'required': False,
'string': 'Nome do Arquivo PDF'},
'arquivo_pdf': {'type': 'binary',
'help': 'Este arquivo permanecerá como RASCUNHO até a confirmação do pagamento da guia de ISS. \n Exceto para contribuintes optantes pelo Simples Nacional.',
'required': False,
'string': 'Arquivo PDF'},
'nome_arquivo_xml': {'type': 'char',
'help': 'Nome do arquivo XML.',
'required': False,
'string': 'Nome do Arquivo XML'},
'arquivo_xml': {'type': 'binary',
'help': 'Arquivo no formato XML.',
'required': False,
'string': 'Arquivo XML'},
'boleto_id': {'type': 'many2one', 'required': False, 'string': 'Boleto', 'relation': 'prefeitura.boleto'},
'boleto_state': {'type': 'selection',
'required': False,
'selection': [['RASCUNHO', 'Rascunho'],
['ABERTO', 'Emitido'],
['VENCIDO', 'Vencido'],
['PAGO', 'Pago'],
['CANCELADO', 'Cancelado']],
'string': 'Estado do Boleto'},
'possui_pagamento_pendente': {'type': 'boolean',
'required': False,
'string': 'Possui Pagamento Pendente'},
}
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal',
'fields_get', [],
{attributes: %w(string help type required selection relation)})
Exemplo de resultado:
{
"name" => {
"type" => "char",
"required" => false,
"string" => "Número da Nota"},
"state" => {
"type" => "selection",
"required" => false,
"selection" => [
["RASCUNHO", "Rascunho"],
["CONCLUIDO", "Emitida"],
["CANCELADA", "Cancelada"]
],
"string" => "Estado"},
"prestador_id" => {
"type" => "many2one",
"help" => "Identificação do PRESTADOR.\n É importante que a Atividade Econômica esteja previamente registrada no cadastro do Prestador.",
"required" => true,
"string" => "Prestador de Serviço",
"relation" => "res.partner"},
"tomador_id" => {
"type" => "many2one",
"help" => "Identificação do TOMADOR.\n Para criar um novo registro basta digitar o nome do prestador e teclar 'Enter'. \nLogo após, uma tela será exibida para completar o cadastro. \nDá próxima vez este tomador estará disponível para seleção.",
"required" => true,
"string" => "Tomador de Serviço",
"relation" => "prefeitura.tomador"},
"city_id" => {
"type" => "many2one",
"required" => true,
"string" => "Local de Prestação do Serviço",
"relation" => "res.city"},
"description" => {
"type" => "text",
"help" => "Escreva aqui informações sobre os serviços prestados.",
"required" => false,
"string" => "Descrição"},
"competencia" => {
"type" => "date",
"help" => "Selecione uma data dentro do mês corrente. ",
"required" => true,
"string" => "Competência"},
"emissao" => {
"type" => "datetime",
"required" => false,
"string" => "Data de Emissão"},
"data_hora_cancelamento" => {
"type" => "datetime",
"required" => false,
"string" => "Data de Cancelamento"},
"cnae_ids" => {
"type" => "many2many",
"help" => "Selecione entre os CNAEs informados no cadastro do prestador.",
"required" => false,
"string" => "CNAE",
"relation" => "prefeitura.cnae"},
"servico_ids" => {
"type" => "many2many",
"help" => "Selecione entre os serviços informados no cadastro do prestador.",
"required" => true,
"string" => "Serviços",
"relation" => "prefeitura.servico"},
"local_incidencia_iss" => {
"type" => "selection",
"required" => false,
"selection" => [
["PRESTADOR", "Local do Prestador"],
["TOMADOR", "Local do Tomador"],
["SERVICO", "Local da prestação de serviço"]
],
"string" => "Local de Incidência do ISS"},
"tipo_recolhimento" => {
"type" => "selection",
"required" => false,
"selection" => [
["ISS_RETIDO", "ISS retido."],
["ISS_A_RECOLHER", "ISS a recolher."],
["SEM_INCIDENCIA", "Sem incidência."]
],
"string" => "Tipo de Recolhimento"},
"item_ids" => {
"type" => "one2many",
"help" => "Especifique os serviços prestados informando a quantidade e valor unitário para cada ítem.",
"required" => false,
"string" => "Itens",
"relation" => "prefeitura.item_nota_fiscal"},
"item_total" => {
"type" => "float",
"required" => false,
"string" => "Total dos Itens"},
"base_calculo" => {
"type" => "monetary",
"help" => "Valor utilizado para a base de cálculo do ISS.",
"required" => false,
"string" => "Base de Cálculo"},
"aliquota_iss" => {
"type" => "float",
"help" => "Alíquota estabelecida pelo CTM.",
"required" => false,
"string" => "Alíquota do ISS (%)"},
"valor_iss" => {
"type" => "monetary",
"required" => false,
"string" => "Valor do ISS"},
"valor_total_deducoes" => {
"type" => "monetary",
"required" => false,
"string" => "Valor Total das Retenções"},
"nome_arquivo_pdf" => {
"type" => "char",
"help" => "Nome do arquivo PDF.",
"required" => false,
"string" => "Nome do Arquivo PDF"},
"arquivo_pdf" => {
"type" => "binary",
"help" => "Este arquivo permanecerá como RASCUNHO até a confirmação do pagamento da guia de ISS. \n Exceto para contribuintes optantes pelo Simples Nacional.",
"required" => false,
"string" => "Arquivo PDF"},
"nome_arquivo_xml" => {
"type" => "char",
"help" => "Nome do arquivo XML.",
"required" => false,
"string" => "Nome do Arquivo XML"},
"arquivo_xml" => {
"type" => "binary",
"help" => "Arquivo no formato XML.",
"required" => false,
"string" => "Arquivo XML"},
"boleto_id" => {
"type" => "many2one",
"required" => false,
"string" => "Boleto",
"relation" => "prefeitura.boleto"},
"boleto_state" => {
"type" => "selection",
"required" => false,
"selection" => [
["RASCUNHO", "Rascunho"],
["ABERTO", "Emitido"],
["VENCIDO", "Vencido"],
["PAGO", "Pago"],
["CANCELADO", "Cancelado"]
],
"string" => "Estado do Boleto"},
"possui_pagamento_pendente" => {
"type" => "boolean",
"required" => false,
"string" => "Possui Pagamento Pendente"
}
}
Emitir Notas Fiscais
O processo segue o mesmo procedimento de emissão de nota fiscal utilizando o formulário no
navegador, primeiro é necessário criar o registro, que por padrão estará no
estado RASCUNHO. Nessa fase, o usuário pode realizar alterações e correções
nos dados submetidos. Após tudo conferido, o usuário executa a ação de Emitir,
que mudará o estado do registro para CONCLUIDO (emitido).
Antes de realizar a criação da nota fiscal, é preciso definir os valores dos campos obrigatórios. Abaixo está uma lista completa dos campos necessários:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
prestador_id
|
Many2one | Padrão | Prestador de Serviço. O sistema obtém automaticamente baseado no usuário logado. Não é necessário informar via API. |
tomador_id
|
Many2one | Sim | Tomador de Serviço. ID do registro em prefeitura.tomador. Deve ser buscado antes da criação.
|
city_id
|
Many2one | Sim | Local da Prestação do Serviço. ID do registro em res.city. Deve ser buscado antes da criação.
|
competencia
|
Date | Sim | Data de competência da nota fiscal. Formato: 'YYYY-MM-DD'. Deve estar dentro do mês corrente (ou até 30 dias
retroativos).
|
servico_ids
|
Many2many | Padrão | Serviços. O sistema obtém automaticamente baseado no prestador. Para emissão, deve conter exatamente 1 serviço. |
servico_nbs_id
|
Many2one | Emissão | Serviço NBS (IBS). Obrigatório apenas na emissão da nota. Deve estar cadastrado no prestador. |
item_ids
|
One2many | Emissão | Itens da Nota Fiscal. Obrigatório ter pelo menos 1 item antes da emissão. Cada item deve ter: name
(descrição), price (preço unitário) e quantity (quantidade).
|
tipo_recolhimento
|
Selection | Emissão | Tipo de Recolhimento. Obrigatório na emissão. Valores: 'ISS_RETIDO', 'ISS_A_RECOLHER' ou
'SEM_INCIDENCIA'. Geralmente calculado automaticamente.
|
description
|
Text | Não | Descrição adicional sobre os serviços prestados. Campo opcional. |
cnae_ids
|
Many2many | Não | CNAEs. O sistema obtém automaticamente baseado no prestador. Campo opcional. |
base_calculo
|
Monetary | Não | Base de Cálculo. Calculado automaticamente como soma dos itens. Pode ser editado se permite_deducao for
True.
|
Notas importantes:
-
Campos com padrão automático:
prestador_ideservico_idssão obtidos automaticamente do usuário logado, não é necessário informá-los. -
Campos obrigatórios para criação:
Apenas
tomador_id,city_idecompetenciasão obrigatórios na criação do rascunho. -
Campos obrigatórios para emissão:
Além dos campos acima, na emissão também são necessários:
item_ids(pelo menos 1 item),servico_nbs_idetipo_recolhimento. -
Itens da Nota:
Cada item deve ter
name(descrição),price(preço unitário) equantity(quantidade, padrão: 1). O campototalé calculado automaticamente. -
Serviços:
Na emissão, deve haver exatamente 1 serviço selecionado em
servico_ids. - Competência: A data deve estar dentro do mês corrente ou até 30 dias retroativos (exceto para administradores que podem ir até 90 dias).
Exemplo de busca dos campos obrigatórios antes da criação:
uid, models = conectar()
# Buscar tomador
tomador_result = call(
models,
uid,
'prefeitura.tomador',
'search',
[[('name', 'ilike', 'peças')]],
{'limit': 1}
)
if not tomador_result:
raise Exception('nenhum tomador encontrado')
tomador_id = tomador_result[0]
# Buscar cidade
city_result = call(
models,
uid,
'res.city',
'search',
[[
('name', 'ilike', 'riachão'),
('state_id.name', 'ilike', 'maranhão'),
]],
{'limit': 1}
)
if not city_result:
raise Exception('nenhuma cidade encontrada')
city_id = city_result[0]
Object tomadorId = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.tomador", "search",
Arrays.asList(Arrays.asList(Arrays.asList("name", "ilike", "peças"))),
new HashMap() {{
put("limit", 1);
}}
))).get(0);
Object cityId = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"res.city", "search",
Arrays.asList(Arrays.asList(
Arrays.asList("name", "ilike", "riachão"),
Arrays.asList("state_id.name", "ilike", "maranhão")
)),
new HashMap() {{
put("limit", 1);
}}
))).get(0);
uid, models = conectar()
tomador_id = call(
models,
uid,
'prefeitura.tomador',
'search',
[[['name', 'ilike', 'peças']]],
{limit: 1}
)
if tomador_id.empty?
raise 'nenhum tomador encontrado'
end
city_id = call(
models,
uid,
'res.city',
'search',
[[
['name', 'ilike', 'riachão'],
['state_id.name', 'ilike', 'maranhão'],
]],
{limit: 1}
)
if city_id.empty?
raise 'nenhuma cidade encontrada'
end
Observe que os comandos anteriores estão realizando consulta de dados em diferentes tabelas.
A cidade está sendo consultada na tabela res.city, procurando o primeiro registro
que possua o nome riachão em que está localizado no estado maranhão.
O tomador é localizado na tabela prefeitura.tomador, a consulta procura o primeiro
registro que possua o nome peças. Note que foi utilizado o operador
ilike. É possível usar qualquer um dos seguintes operadores:
| Operador | Descrição |
|---|---|
=
|
Igual. |
!=
|
Diferente. |
>
|
Maior. |
>=
|
Maior ou igual. |
<
|
Menor. |
<=
|
Menor ou igual. |
=?
|
Indefinido ou igual (retorna true se valor é None ou False, do contrário se comporta
como =).
|
=like
|
Corresponde field_name no seguinte padrão de valor: um
sublinhado
_
no padrão significa (corresponde) qualquer caractere único; e um sinal de
porcentagem
%
corresponde qualquer string de zero ou mais caracteres.
|
like
|
Corresponde field_name no padrão %valor%. Parecido com =like mas embrulha o valor com
%
antes de corresponder.
|
not like
|
Não corresponde no padrão %value%.
|
ilike
|
Caso maúsculo ou minúsculo de like.
|
not ilike
|
Caso maúsculo ou minúsculo de not like.
|
=ilike
|
Caso maúsculo ou minúsculo de =like.
|
in
|
É igual a qualquer um dos itens da lista (nos casos do valor ser uma lista). |
not in
|
É diferente de qualquer um dos itens da lista (nos casos do valor ser uma lista). |
Exemplo completo de como criar uma nota fiscal(RASCUNHO)
uid, models = conectar()
# ==========================
# 1. Conectar ao sistema
# ==========================
uid, models = conectar()
# ==========================
# 2. Buscar tomador
# ==========================
tomador_result = call(
models,
uid,
'prefeitura.tomador',
'search',
[[('name', 'ilike', 'Prefeitura de Riachao ')]],
{'limit': 1}
)
if not tomador_result:
raise Exception('Tomador não encontrado')
tomador_id = tomador_result[0]
# ==========================
# 3. Buscar cidade
# ==========================
city_result = call(
models,
uid,
'res.city',
'search',
[[
('name', 'ilike', 'Riachão'),
('state_id.name', 'ilike', 'Maranhão'),
]],
{'limit': 1}
)
if not city_result:
raise Exception('Cidade não encontrada')
city_id = city_result[0]
# ==========================
# 4. Criar nota fiscal (rascunho)
# ==========================
from datetime import date
nota_id = call(
models,
uid,
'prefeitura.nota_fiscal',
'create',
[
{
'tomador_id': tomador_id,
'city_id': city_id,
'competencia': date.today().strftime('%Y-%m-%d'),
'description': 'Serviços de suporte técnico mensal.',
}
]
)
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'create', [[
{
tomador_id: tomador_id,
city_id: city_id,
competencia: '2023-02-10',
},
{
tomador_id: tomador_id,
city_id: city_id,
competencia: '2023-02-10',
},
]])
Observe que o registros está no estado de RASCUNHO. Significa que
ele podem ser alterado (ainda não foi emitido CONCLUIDO). Para
adicionar um ou mais Itens da Nota, execute:
nota = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[[('id', '=', '5669')]],
{
'fields': [
'id', 'name', 'state', 'servico_ids', 'servico_nbs_id', 'prestador_id'
],
'limit': 1
}
)[0]
# adiciona dois itens na nota fiscal
call(
models,
uid,
'prefeitura.nota_fiscal',
'write',
[
[nota.get('id')],
{
'item_ids': [
(0, 0, {
'name': 'Consultoria em Desenvolvimento de Software',
'price': 5000.00,
'quantity': 1,
}),
(0, 0, {
'name': 'Desenvolvimento de Software',
'price': 3000.00,
'quantity': 1,
}),
]
}
]
)
(Boolean) models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "write",
Arrays.asList(
Arrays.asList(notaId),
new HashMap() {{
put("item_ids", Arrays.asList(
Arrays.asList(0, 0, new HashMap() {{
put("name", "Primeiro Item");
put("price", 123.45);
put("quantity", 2);
}}),
Arrays.asList(0, 0, new HashMap() {{
put("name", "Segundo Item");
put("price", 321.54);
put("quantity", 1);
}})
));
}}
)
))
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'write', [[nota_id], {
item_ids: [
[0, 0, {
name: "Primeiro Item",
price: 123.45,
quantity: 2,
}],
[0, 0, {
name: "Segundo Item",
price: 321.54,
quantity: 1,
}],
]
}])
Para consultar os campos alterados dos Itens da Nota, execute:
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "read",
Arrays.asList(Arrays.asList(5669)),
new HashMap() {{
put("fields", Arrays.asList("id", "name", "state", "item_ids"));
}}
)))
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'read',
[[nota_id]],
{fields: %w(id name state item_ids)})
nota = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[[('id', '=', nota_id)]],
{
'fields': [
'id', 'name', 'state', 'item_ids'
],
'limit': 1
}
)[0]
Resultado:
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "read",
Arrays.asList(Arrays.asList(notaId)),
new HashMap() {{
put("fields", Arrays.asList("id", "name", "state", "item_ids"));
}}
)))
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'read',
[[nota_id]],
{fields: %w(id name state item_ids)})
[
{'id': 5669, 'name': 'NF-2024-1152-00001', 'state': 'RASCUNHO', 'item_ids': [11929, 11930, 5669, 5670]}
]
Observe que os comandos anteriores estão acessando os registros relacionados (itens da nota) usando o seguinte formato:
| Comando | Descrição |
|---|---|
(0, 0, valores)
|
Adiciona um novo registro, criado a partir do dicionário valores fornecido. |
(1, id, valores)
|
Atualiza um registro existente de id com os valores fornecidos. |
(2, id, 0)
|
Remove o registro de id da relação, e depois deleta da tabela de origem. |
(3, id, 0)
|
Remove o registro de id da relação, mas não deleta da tabela de origem. |
(4, id, 0)
|
Adiciona um registro existente de id na relação. |
(5, 0, 0)
|
Remove todos os registros da relação, e não os remove da tabela de origem. |
(6, 0, ids)
|
Substitui todos os registros existentes da relação pelos ids da lista, e não os remove da tabela de origem. |
Finalmente, para emitir a nota fiscal, execute:
result = call(
models,
uid,
'prefeitura.nota_fiscal',
'action_emitir_nota_fiscal',
[[nota.get('id')]]
)
(Object) models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "action_emitir_nota_fiscal",
Arrays.asList(Arrays.asList(notaId))
))
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal',
'action_emitir_nota_fiscal', [[nota_id]])
Exemplo como EMITIR nota fiscal
uid, models = conectar()
# ==========================
# EMITIR NOTA FISCAL
# ==========================
# ==========================
# 5. Adicionar itens à nota fiscal
# ==========================
call(
models,
uid,
'prefeitura.item_nota_fiscal',
'create',
[{
'nota_fiscal_id': nota_id,
'name': 'Consultoria em Desenvolvimento de Software',
'price': 5000.00,
'quantity': 1,
}]
)
# ==========================
# 6. Adicionar Serviço (IBS) à nota fiscal
# ==========================
nota = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[[('id', '=', '5669')]],
{
'fields': [
'id', 'name', 'state', 'servico_ids', 'servico_nbs_id', 'prestador_id'
],
'limit': 1
}
)[0]
prestador = call(
models,
uid,
'res.partner',
'read',
[[nota['prestador_id'][0]]],
{'fields': ['servico_nbs_ids','servico_ids','cnae_ids']}
)
# Prestador tem N serviços nbs, mas só é possível adicionar um serviço nbs
if prestador and prestador[0].get('servico_nbs_ids'):
servico_nbs_id = prestador[0].get('servico_nbs_ids')
call(
models,
uid,
'prefeitura.nota_fiscal',
'write',
[
[nota.get('id')],
{
'servico_nbs_id': servico_nbs_id[0]
}
]
)
# Adicione os serciços na nota, o servico_ids contem todos os serviços do prestador, então escolha apenas o que desejar.
# Aqui estou adicionando apenas o primeiro, mas poderia adicionar todos se fossem necessário
if prestador and prestador[0].get('servico_ids'):
servico_ids = prestador[0].get('servico_ids')
call(
models,
uid,
'prefeitura.nota_fiscal',
'write',
[
[nota.get('id')],
{
# adiciona apenas o primeiro
'servico_ids': [(6, 0, [servico_ids[0]])]
# adiciona todos
# 'servico_ids': [(6, 0, servico_ids)]
}
]
)
# Adicione os cnaes na nota, o cnae_ids todos os cnaes do prestador, então escolha apenas o que desejar.
# Aqui estou adicionando apenas o primeiro, mas poderia adicionar todos se fossem necessário
if prestador and prestador[0].get('cnae_ids'):
cnae_id = prestador[0].get('cnae_ids')
call(
models,
uid,
'prefeitura.nota_fiscal',
'write',
[
[nota.get('id')],
{
'cnae_ids': [(6, 0, [cnae_id[0]])]
}
]
)
try:
result = call(
models,
uid,
'prefeitura.nota_fiscal',
'action_emitir_nota_fiscal',
[[nota.get('id')]]
)
# Verificar resultado
nota_emitida = call(
models,
uid,
'prefeitura.nota_fiscal',
'read',
[[nota.get('id')]],
{
'fields': [
'id', 'name', 'state', 'emissao', 'valor_iss',
'boleto_id', 'possui_pagamento_pendente'
]
}
)[0]
print(f'\n✅ Nota fiscal emitida com sucesso!')
print(f' - Número: {nota_emitida["name"]}')
print(f' - Estado: {nota_emitida["state"]}')
print(f' - Data de emissão: {nota_emitida.get("emissao", "N/A")}')
print(f' - Valor do ISS: R$ {nota_emitida.get("valor_iss", 0):.2f}')
print(f' - Possui boleto: {bool(nota_emitida.get("boleto_id"))}')
except Exception as e:
print(f'\n❌ Erro ao emitir nota fiscal: {str(e)}')
print('Verifique se todos os campos obrigatórios foram preenchidos.')
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'create', [[
{
tomador_id: tomador_id,
city_id: city_id,
competencia: '2023-02-10',
},
{
tomador_id: tomador_id,
city_id: city_id,
competencia: '2023-02-10',
},
]])
Baixar PDF do Boleto da Nota Fiscal
Após emissão da nota fiscal (p.ex. id = 5669), caso exista algum débito a ser pago, você poderá baixar o PDF do boleto de cobrança utilizando a URL do arquivo:
URL = 'https://meu_municipio.teste.joinfiscal.com.br'
nota = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[[('id', '=', '5669')]],
{
'fields': [
'id', 'name', 'state', 'item_ids'
],
'limit': 1
}
)[0]
result = call(
models,
uid,
'prefeitura.nota_fiscal',
'action_baixar_arquivo_pdf',
[[nota.get('id')]]
)
download_url = f"{URL}{result['url']}"
print(download_url)
String baseUrl = "https://meu_municipio.teste.joinfiscal.com.br";
String path = ((Map) models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal", "action_baixar_arquivo_pdf",
Arrays.asList(Arrays.asList(4382))
))).get("url");
System.out.println(baseUrl + "/" + path);
url = 'https://meu_municipio.teste.joinfiscal.com.br'
path = models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal',
'action_baixar_arquivo_pdf', [[4382]])['url']
puts url + '/' + path
"https://meu_municipio.teste.joinfiscal.com.br/web/content/prefeitura.nota_fiscal/4382/arquivo_pdf/nota-fiscal-nf-2023-23-00001.pdf?download=true"
Cancelar Notas Fiscais
Para cancelar uma nota fiscal emitida, é necessário informar o motivo do cancelamento. A nota fiscal deve estar no estado EMITIDA para poder ser cancelada.
nota = call(
models,
uid,
'prefeitura.nota_fiscal',
'search_read',
[[('id', '=', '5669')]],
{
'fields': [
'id', 'name', 'state', 'item_ids'
],
'limit': 1
}
)[0]
# Criar registro do diálogo de cancelamento
dialogo_id = call(
models,
uid,
'prefeitura.nota_fiscal_dialogo',
'create',
[{
'nota_fiscal_id': nota.get('id'),
'motivo_cancelamento': 'Erro na emissão da nota fiscal. Necessário cancelar e reemitir.'
}]
)
# Confirmar o cancelamento
result = call(
models,
uid,
'prefeitura.nota_fiscal_dialogo',
'confirmar_cancelamento',
[[dialogo_id]]
)
// Criar registro do diálogo de cancelamento
Integer dialogoId = (Integer) models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal_dialogo", "create",
Arrays.asList(Arrays.asList(
new HashMap() {{
put("nota_fiscal_id", notaId);
put("motivo_cancelamento", "Erro na emissão da nota fiscal. Necessário cancelar e reemitir.");
}}
))
));
// Confirmar o cancelamento
(Boolean) models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.nota_fiscal_dialogo", "confirmar_cancelamento",
Arrays.asList(Arrays.asList(dialogoId))
))
# Criar registro do diálogo de cancelamento
dialogo_id = models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal_dialogo',
'create', [{
nota_fiscal_id: nota_id,
motivo_cancelamento: 'Erro na emissão da nota fiscal. Necessário cancelar e reemitir.'
}])
# Confirmar o cancelamento
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal_dialogo',
'confirmar_cancelamento', [[dialogo_id]])
Consultar Faturas
Para listar todas as faturas em aberto, execute:
faturas = call(
models,
uid,
'account.move',
'search_read',
[[
('state', '=', 'posted'),
('payment_state', '=', 'not_paid')
]]
)
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"account.move", "search",
Arrays.asList(Arrays.asList(
Arrays.asList("state", "=", "posted"),
Arrays.asList("payment_state", "=", "not_paid")
)),
new HashMap() {{ }}
)));
models.execute_kw(db, uid, password, 'account.move', 'search_read', [[
['state', '=', 'posted'],
['payment_state', '=', 'not_paid'],
]], {})
Exemplo de resultado:
[356, 341, 134]
Gerar Boleto de Faturas Selecionadas
É possível gerar um boleto para pagar uma ou mais faturas, nesse caso para as faturas ids = [356, 341, 134]. Mas primeiro, certifique-se que todas as faturas possuem o mesmo contribuinte associado:
contribuintes = call(
models,
uid,
'account.move',
'search_read',
[[('id', 'in', [356, 341, 134])]],
{'fields': ['partner_id']}
)
for contribuinte in contribuintes:
print(contribuinte)
List<Object> contribuintes = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"account.move", "search_read",
Arrays.asList(
Arrays.asList(Arrays.asList("id", "in", Arrays.asList(356, 341, 134))),
Arrays.asList("partner_id")
),
new HashMap() {{ }}
)));
for (Object contribuinte : contribuintes) {
System.out.println(contribuinte);
}
contribuintes = models.execute_kw(db, uid, password, 'account.move', 'search_read', [[
['id', 'in', [356, 341, 134]],
], ['partner_id']], {})
for contribuinte in contribuintes do
puts contribuinte
end
Exemplo de resultado:
{'id': 356, 'partner_id': [378, 'Felipe Silva']}
{'id': 341, 'partner_id': [378, 'Felipe Silva']}
{'id': 134, 'partner_id': [378, 'Felipe Silva']}
Após confirmar que todas as faturas ids = [356, 341, 134] pertencem ao mesmo contribuinte id = 378, basta criar o boleto:
result = call(
models,
uid,
'prefeitura.boleto',
'create',
[{
'contribuinte_id': 378,
'move_ids': [356, 341, 134],
}]
)
print(result)
List<Object> result = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.boleto", "create",
Arrays.asList(Arrays.asList(
new HashMap() {{
put("contribuinte_id", 378);
put("move_ids", Arrays.asList(356, 341, 134));
}}
))
)));
System.out.println(result);
result = models.execute_kw(db, uid, password, 'prefeitura.boleto', 'create', [[
{
contribuinte_id: 378,
'move_ids': [356, 341, 134],
},
]])
puts result
Exemplo de resultado:
[495]
O boleto recém-criado estará no estado RASCUNHO. Nessa fase, o usuário pode realizar
alterações e correções nos dados submetidos. Após tudo conferido, o usuário executa a ação
de Emitir Boleto:
result = call(
models,
uid,
'prefeitura.boleto',
'action_gerar_boleto',
[[boleto.get('id')]]
)
print(result)
Object result = (Object) models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.boleto", "action_gerar_boleto",
Arrays.asList(Arrays.asList(boletoId))
));
System.out.println(result);
result = models.execute_kw(db, uid, password, 'prefeitura.boleto',
'action_gerar_boleto', [[boleto_id]])
puts result
Exemplo de resultado:
True
Após emissão do boleto de cobrança id = 495, você poderá baixar o arquivo PDF utilizando a URL do arquivo:
URL = 'https://meu_municipio.teste.joinfiscal.com.br'
result = call(
models,
uid,
'prefeitura.boleto',
'action_baixar_arquivo_pdf',
[[boleto.get('id')]]
)
download_url = f"{URL}{result['url']}"
print(download_url)
String URL = "https://meu_municipio.teste.joinfiscal.com.br";
Map result = (Map) models.execute("execute_kw", Arrays.asList(
db, uid, password,
"prefeitura.boleto", "action_baixar_arquivo_pdf",
Arrays.asList(Arrays.asList(boletoId))
));
String downloadUrl = URL + result.get("url");
System.out.println(downloadUrl);
URL = 'https://meu_municipio.teste.joinfiscal.com.br'
result = models.execute_kw(db, uid, password, 'prefeitura.boleto',
'action_baixar_arquivo_pdf', [[boleto_id]])
download_url = "#{URL}#{result['url']}"
puts download_url
"https://meu_municipio.teste.joinfiscal.com.br/web/content/prefeitura.boleto/495/conteudo_pdf/boleto-495-felipe-silva-aberto-2023-04-10.pdf?download=true"
- Prefeitura de Lima Campos
- Praça Duque de Caxias, nº S/N
- Lima Campos - MA 65728-000
- (86) 9 9903-9494
- suporte@jointecdesenvolvimento.com.br
- Ver localização no Google Maps