Este artigo visa orientar sobre o desenho e implementação de APIs TOTVS respeitando as orientações do guia de implementação de APIs, bem como o modelo de conteúdo definido nas transações de mensagem padronizada TOTVS.

As orientações presentes neste documento estão em constante ajuste. Por isso, consulte sempre esta página quando for modelar uma nova transação.

Orientações gerais

  1. Verifique se a entidade sobre a qual a API atuará possui uma transação já definida no repositório de mensagens padronizadas TOTVS. No TFS, acesse a Project Collection TOTVSMSGXML (http://tfs2015.totvs.com.br:8080/tfs/TOTVSMSGXML).
    1. Consulte o repositório de mensagens padronizadas em formato JSON. Primeiramente, a pasta STABLE/messages-json/jsonschema, depois a pasta DEV/messages-json/jsonschema.
    2. Se não encontrar uma transação, consulte o repositório de mensagens em formato XML, verificando as pastas STABLE/messages-xml/xmlschema e DEV/messages-xml/xmlschema.
    3. Selecione, sempre que possível, a maior versão da transação.
  2. Existindo apenas no repositório XML uma transação compatível, deve-se criar o documento OpenAPI (antigo Swagger) equivalente ao documento XSD.
    1. O documento OpenAPI deve ser armazenado no TFS, na pasta DEV/messages-json/jsonschema.
    2. O modelo de dados pode ser segmentado em blocos menores, desde que obedeçam a estrutura hierárquica e tenham apenas os atributos previstos no documento XSD. Caso a hierarquia seja alterada ou seja retirado ou adicionado campos no modelo, isso implica em uma nova versão da transação. Exemplos de segmentação serão mostrados adiante neste documento.
  3. Havendo necessidade de se criar uma nova versão da transação, esta pode ser criada apenas no repositório JSON, desde que se siga as orientações previstas na documentação de elaboração de mensagem padronizada SOAP/XML, realizando os devidos ajustes para o formato JSON.
    1. Lembrando que as versões devem começar de 2.000 pois, por convenção, foi a partir desta que se iniciou o uso de InternalId.
  4. Modelar o documento OpenAPI seguindo as orientações para elaboração de mensagem padronizada REST/JSON.
    1. A versão da transação fará parte do endpoint da API. Assim, será possível indicar aos clientes das APIs qual o modelo de dados em uso.
      Modelo: http://<servidor>:<porta>/api/<produto>/<modulo>/<versao_transacao>/<nome_transacao>.
      Exemplo: http://api.totvs.com.br:8080/api/datasul/financeiro/v2.004/accountantAccounts.
    2. Conforme visto acima, o nome da transação deve ser no plural, para manter consistência com o guia de implementação de APIs.
  5. Submeter o documento OpenAPI ao comitê de integrações para análise e aprovação.
    1. Enviar e-mail para [email protected] contendo as seguintes informações:
      1. Nome e versão da transação.
      2. Caminho, no TFS, do documento que modela a transação, seja ele em formato XSD ou OpenAPI.
      3. Data prevista para a reunião de homologação, que deve ser marcada com, no mínimo, 1 semana de antecedência, para que o comitê tenha tempo para avaliar a mensagem proposta.
    2. Assim que a prática de modelar mensagens em formato JSON estiver consolidada, pode-se adotar, futuramente, a homologação apenas por e-mail, como acontece atualmente com mensagens em formato XML.
  6. Após aprovação da mensagem pelo comitê, o responsável pela modelagem deve mover o documento, no TFS, para a pasta STABLE/messages-json/jsonschema.

Segmentação do modelo de dados

Partindo de um modelo XML como base, veremos a seguir um exemplo de uma possível implementação de API usando segmentação do modelo de dados.

O modelo XML utilizado será Contract_2_000.xsd que está representado graficamente a seguir (clique na imagem para expandir).

Convertendo este modelo "como ele é" para o formato OpenAPI 3.0, teríamos o seguinte documento (alguns elementos previstos no guia de APIs e na elaboração de mensagem padronizada - REST/JSON foram omitidos para melhor compreensão):

{
  "openapi": "3.0.0",
  (...),
  "components": {
    "schemas": {
      "Contract": {
        "type": "object",
        "properties": {
          "CompanyId": {
            "type": "string"
          },
          "BranchId": {
            "type": "string"
          },
          "CompanyInternalId": {
            "type": "string"
          },
          "InternalId": {
            "type": "string"
          },
          "ContractNumber": {
            "type": "string"
          },
          "ContractReview": {
            "type": "string"
          },
          "ProjectInternalId": {
            "type": "string"
          },
          "BeginDate": {
            "type": "string",
            "format": "date-time"
          },
          "FinalDate": {
            "type": "string",
            "format": "date-time"
          },
          "CustomerCode": {
            "type": "string"
          },
          "CustomerInternalId": {
            "type": "string"
          },
          "ContractTotalValue": {
            "type": "number",
            "format": "float"
          },
          "ContractTypeCode": {
            "type": "string"
          },
          "ContractTypeInternalId": {
            "type": "string"
          },
          "ListOfSheet": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "SheetNumber": {
                  "type": "string"
                },
                "SheetTypePoperty": {
                  "type": "string"
                },
                "UnitPrice": {
                  "type": "number",
                  "format": "float"
                },
                "SheetTotalValue": {
                  "type": "number",
                  "format": "float"
                },
                "ListOfItem": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "ItemCode": {
                        "type": "string"
                      },
                      "ItemInternalId": {
                        "type": "string"
                      },
                      "AccountantAcountCode": {
                        "type": "string"
                      },
                      "AccountantAcountInternalId": {
                        "type": "string"
                      },
                      "CostCenterCode": {
                        "type": "string"
                      },
                      "CostCenterInternalId": {
                        "type": "string"
                      },
                      "AccountingItemCode": {
                        "type": "string"
                      },
                      "AccountingItemInternalId": {
                        "type": "string"
                      },
                      "ClassValueCode": {
                        "type": "string"
                      },
                      "ClassValueInternalId": {
                        "type": "string"
                      },
                      "ItemQuantity": {
                        "type": "number",
                        "format": "float"
                      },
                      "ItemUnitPrice": {
                        "type": "number",
                        "format": "float"
                      },
                      "ItemTotalValue": {
                        "type": "number",
                        "format": "float"
                      },
                      "PercentageOfDiscount": {
                        "type": "number",
                        "format": "float"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "description": "Contrato"
      }
    }
  }
}

Entretanto, utilizar o modelo desta forma tem vários problemas como, por exemplo, na modificação do contrato, onde teríamos que enviar também as páginas (Sheet) do contrato e os itens das páginas.

Por isso, a segmentação do modelo de dados é permitida, desde que mantenha a estrutura e atributos do modelo XML original.

Nosso modelo OpenAPI poderia ser segmentado em 3 submodelos:

  • ContractModel, correspondente ao cabeçalho do contrato.
  • SheetModel, correspondente às folhas do contrato.
  • ItemModel, correspondente aos itens vinculados às folhas do contrato.

Convertendo isso para o modelo OpenAPI, teríamos o seguinte, lembrando que elementos como as tags de documentação foram omitidos por questões didáticas.

{
  "openapi": "3.0.0",
  (...),
  "components": {
    "schemas": {
      "ContractModel": {
        "type": "object",
        "properties": {
          "CompanyId": {
            "type": "string"
          },
          "BranchId": {
            "type": "string"
          },
          "CompanyInternalId": {
            "type": "string"
          },
          "InternalId": {
            "type": "string"
          },
          "ContractNumber": {
            "type": "string"
          },
          "ContractReview": {
            "type": "string"
          },
          "ProjectInternalId": {
            "type": "string"
          },
          "BeginDate": {
            "type": "string",
            "format": "date-time"
          },
          "FinalDate": {
            "type": "string",
            "format": "date-time"
          },
          "CustomerCode": {
            "type": "string"
          },
          "CustomerInternalId": {
            "type": "string"
          },
          "ContractTotalValue": {
            "type": "number",
            "format": "float"
          },
          "ContractTypeCode": {
            "type": "string"
          },
          "ContractTypeInternalId": {
            "type": "string"
          },
          "ListOfSheet": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SheetModel"
            }
          }
        },
        "description": "Contrato"
      },
      "SheetModel": {
        "type": "object",
        "properties": {
          "SheetNumber": {
            "type": "string"
          },
          "SheetTypePoperty": {
            "type": "string"
          },
          "UnitPrice": {
            "type": "number",
            "format": "float"
          },
          "SheetTotalValue": {
            "type": "number",
            "format": "float"
          },
          "ListOfItem": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ItemModel"
            }
          }
        }
      },
      "ItemModel": {
        "type": "object",
        "properties": {
          "ItemCode": {
            "type": "string"
          },
          "ItemInternalId": {
            "type": "string"
          },
          "AccountantAcountCode": {
            "type": "string"
          },
          "AccountantAcountInternalId": {
            "type": "string"
          },
          "CostCenterCode": {
            "type": "string"
          },
          "CostCenterInternalId": {
            "type": "string"
          },
          "AccountingItemCode": {
            "type": "string"
          },
          "AccountingItemInternalId": {
            "type": "string"
          },
          "ClassValueCode": {
            "type": "string"
          },
          "ClassValueInternalId": {
            "type": "string"
          },
          "ItemQuantity": {
            "type": "number",
            "format": "float"
          },
          "ItemUnitPrice": {
            "type": "number",
            "format": "float"
          },
          "ItemTotalValue": {
            "type": "number",
            "format": "float"
          },
          "PercentageOfDiscount": {
            "type": "number",
            "format": "float"
          }
        }
      }
    }
  }
}

Consequentemente, esta segmentação será refletida nos endpoints das APIs. Tomando por base a divisão realizada, teríamos o seguinte template:

  • /v2.000/contracts/{ContractNumber}/sheets/{SheetNumber}/items/{ItemCode}

Neste template, temos as seguintes considerações:

  • No nível de contracts utilizamos o submodelo ContractModel. Nos atributos ListOfSheet e ListOfItem, utilizamos os submodelos SheetModel e ItemModel quando os mesmos forem expandidos.
  • No nível de sheets, utilizamos apenas o submodelo SheetModel. O modelo ItemModel será utilizado quando o atributo ListOfItem for expandido.
  • No nível de items, utilizamos apenas o submodelo ItemModel.
  • Observe a equivalencia entre o item do modelo e o item de path correspondente:
    • ListOfSheet (atributo de ContractModel) equivale a sheets.
    • ListOfItem (atributo de SheetModel) equivale a items.

Nos exemplos a seguir veremos a utilização dos possíveis endpoints e seus respectivos modelos:

Inclusão de um contrato de forma completa

{
  "CompanyId": "1",
  "BranchId": "1",
  "CompanyInternalId": "1",
  "InternalId": "1|1|1",
  "ContractNumber": "1",
  "ContractReview": "1",
  "ProjectInternalId": "1|1",
  "BeginDate": "2018-07-25T14:24:00",
  "FinalDate": "2019-07-25T14:24:00",
  "CustomerCode": "1",
  "CustomerInternalId": "1",
  "ContractTotalValue": 1.0,
  "ContractTypeCode": "1",
  "ContractTypeInternalId": "1",
  "ListOfSheet": [{
      "SheetNumber": "1",
      "SheetTypePoperty": "1",
      "UnitPrice": 1.0,
      "SheetTotalValue": 1.0,
      "ListOfItem": [{
          "ItemCode": "1",
          "ItemInternalId": "1|1",
          "AccountantAcountCode": "0001",
          "AccountantAcountInternalId": "1|0001",
          "CostCenterCode": "1",
          "CostCenterInternalId": "1|1",
          "AccountingItemCode": "111",
          "AccountingItemInternalId": "1|111",
          "ClassValueCode": "001",
          "ClassValueInternalId": "1|001",
          "ItemQuantity": 1.0,
          "ItemUnitPrice": 1.0,
          "ItemTotalValue": 1.0,
          "PercentageOfDiscount": 0.0
        }
      ]
    }
  ]
}

Inclusão de um contrato sem folhas (se a regra de negócio permitir)

{
  "CompanyId": "1",
  "BranchId": "1",
  "CompanyInternalId": "1",
  "InternalId": "1|1|1",
  "ContractNumber": "1",
  "ContractReview": "1",
  "ProjectInternalId": "1|1",
  "BeginDate": "2018-07-25T14:24:00",
  "FinalDate": "2019-07-25T14:24:00",
  "CustomerCode": "1",
  "CustomerInternalId": "1",
  "ContractTotalValue": 1.0,
  "ContractTypeCode": "1",
  "ContractTypeInternalId": "1",
  "ListOfSheet": []
}

Recuperando contratos (apenas cabeçalho, 1a página, até o limite de uma página)

{
  "hasNext": false,
  "items": [{
      "_expandables": ["ListOfSheet"],
      "CompanyId": "1",
      "BranchId": "1",
      "CompanyInternalId": "1",
      "InternalId": "1|1|1",
      "ContractNumber": "1",
      "ContractReview": "1",
      "ProjectInternalId": "1|1",
      "BeginDate": "2018-07-25T14:24:00",
      "FinalDate": "2019-07-25T14:24:00",
      "CustomerCode": "1",
      "CustomerInternalId": "1",
      "ContractTotalValue": 1.0,
      "ContractTypeCode": "1",
      "ContractTypeInternalId": "1",
      "ListOfSheet": []
    }
  ]
}

Recuperando um contrato, expandindo os atributos ListOfSheet e ListOfItem

{
  "_expandables": ["ListOfSheet"],
  "CompanyId": "1",
  "BranchId": "1",
  "CompanyInternalId": "1",
  "InternalId": "1|1|1",
  "ContractNumber": "1",
  "ContractReview": "1",
  "ProjectInternalId": "1|1",
  "BeginDate": "2018-07-25T14:24:00",
  "FinalDate": "2019-07-25T14:24:00",
  "CustomerCode": "1",
  "CustomerInternalId": "1",
  "ContractTotalValue": 1.0,
  "ContractTypeCode": "1",
  "ContractTypeInternalId": "1",
  "ListOfSheet": [{
      "_expandable": ["ListOfItem"],
      "SheetNumber": "1",
      "SheetTypePoperty": "1",
      "UnitPrice": 1.0,
      "SheetTotalValue": 1.0,
      "ListOfItem": [{
          "ItemCode": "1",
          "ItemInternalId": "1|1",
          "AccountantAcountCode": "0001",
          "AccountantAcountInternalId": "1|0001",
          "CostCenterCode": "1",
          "CostCenterInternalId": "1|1",
          "AccountingItemCode": "111",
          "AccountingItemInternalId": "1|111",
          "ClassValueCode": "001",
          "ClassValueInternalId": "1|001",
          "ItemQuantity": 1.0,
          "ItemUnitPrice": 1.0,
          "ItemTotalValue": 1.0,
          "PercentageOfDiscount": 0.0
        }
      ]
    }
  ]
}

Incluindo uma folha no contrato

{
  "SheetNumber": "1",
  "SheetTypePoperty": "1",
  "UnitPrice": 1.0,
  "SheetTotalValue": 1.0,
  "ListOfItem": [{
      "ItemCode": "1",
      "ItemInternalId": "1|1",
      "AccountantAcountCode": "0001",
      "AccountantAcountInternalId": "1|0001",
      "CostCenterCode": "1",
      "CostCenterInternalId": "1|1",
      "AccountingItemCode": "111",
      "AccountingItemInternalId": "1|111",
      "ClassValueCode": "001",
      "ClassValueInternalId": "1|001",
      "ItemQuantity": 1.0,
      "ItemUnitPrice": 1.0,
      "ItemTotalValue": 1.0,
      "PercentageOfDiscount": 0.0
    }
  ]
}

Incluindo um item de uma folha do contrato

{
  "ItemCode": "1",
  "ItemInternalId": "1|1",
  "AccountantAcountCode": "0001",
  "AccountantAcountInternalId": "1|0001",
  "CostCenterCode": "1",
  "CostCenterInternalId": "1|1",
  "AccountingItemCode": "111",
  "AccountingItemInternalId": "1|111",
  "ClassValueCode": "001",
  "ClassValueInternalId": "1|001",
  "ItemQuantity": 1.0,
  "ItemUnitPrice": 1.0,
  "ItemTotalValue": 1.0,
  "PercentageOfDiscount": 0.0
}

Eliminando um item de uma folha

  • DELETE /v2.000/contracts/1/sheets/1/items/1

Eliminando uma folha de um contrato

  • DELETE /v2.000/contracts/1/sheets/1

Links relacionados

Guia de implementação de APIs TOTVS

Elaboração de mensagem padronizada REST/JSON

Elaboração de mensagem padronizada SOAP/XML

Repositório de mensagens padronizadas (TFS) - STABLE - requer login TOTVS para acesso

Repositório de mensagens padronizadas (TFS) - DEV - requer login TOTVS para acesso

Especificação OpenAPI 3.0

Editor Swagger/OpenAPI - permite a criação de documentos Swagger 2.0 ou OpenAPI 3.0 através da edição de tags YAML.

Conversor Swagger 2.0 para OpenAPI - converte um documento Swagger 2.0 em um documento OpenAPI 3.0.

Modelador de APIs Restlet Studio - permite a modelagem de APIs (endpoints, tipos de dados, etc.) gerando documentação em Swagger 2.0 e RAML 1.0.