11. Ordenando arquivos

11. Ordenando arquivos

Muitas vezes precisamos que nossos arquivos de entrada sejam lidos numa ordem pré-definida. Esse recurso é necessário, por exemplo, quando precisamos totalizar valores agrupados por determinadas chaves ou quando precisamos comparar dois arquivos. Neste capítulo veremos não só como reordenar arquivos sequenciais usando comandos do COBOL, mas também quatro de suas principais aplicações: quebra, agrupamento, merge e balance-line.

É muito comum, principalmente em mainframes, que se use um utilitário externo para classificar arquivos, entregando seus registros já ordenados para o programa COBOL. Neste caso, o COBOL não faz nenhuma operação especial de classificação. Neste capítulo, porém, veremos uma modalidade conhecida como sort interno, onde os registros são lidos, classificados e processados por um único programa, escrito em COBOL.

Conceitos sobre a classificação de arquivos

O COBOL possui um comando específico para classificação de arquivos sequenciais, chamado SORT. Porém, para que esse comando seja executado são necessárias três etapas:

Cobol: Comando SORT
Figura 36. Processo de classificação de arquivos usando o SORT

Na primeira etapa, chamada fase de release ou fase de input, o programa COBOL lê os registros do arquivo de entrada e entrega esses registros para o processo de classificação. A segunda etapa, chamada simplesmente de sort, é o próprio processo de reordenamento dos registros, orientada por parâmetros fornecidos ao comando SORT. Nesse comando precisamos informar quais são as chaves de classificação e indicar se essas chaves serão classificadas em ordem ascendente ou descendente. A terceira etapa, chamada de fase de return ou fase de output, é quando o programa COBOL solicita ao SORT que devolva os registros reordenados para que ele possa processá-los. Na etapa de release, o programa não precisa enviar todos os registros lidos para a classificação. Na verdade isso depende da finalidade do programa… algumas vezes pode ser necessário enviar para classificação apenas alguns registros do arquivo de entrada, seguindo algum critério de seleção coerente com a funcionalidade do programa. Cobol: Plataformas diferentes podem produzir resultados diferentes

Classificando todos os registros de um arquivo

Para executar o comando SORT precisamos declarar um arquivo temporário onde colocaremos os registros (na fase de release), para que eles sejam classificados (na fase de sort) e posteriormente recuperados (na fase de return). Esse arquivo temporário é declarado na ENVIRONMENT DIVISION como qualquer outro arquivo:

 SELECT nome-do-arquivo ASSIGN TO “nome/externo/do/arquivo”
     ORGANIZATION IS LINE SEQUENTIAL.

Normalmente esse arquivo segue a mesma organização do arquivo de entrada. Logo, se nosso arquivo de entrada for sequencial linear, precisamos deixar isso claro no comando SELECT, como fizemos nesse exemplo acima.

Também como qualquer arquivo, o arquivo temporário precisa ser detalhado na FILE SECTION da DATA DIVISION. Só que nesse caso não usaremos um parágrafo de file description (FD) e sim um parágrafo específico para sort description, ou SD:

      *================================================================*
       DATA DIVISION.
      *----------------------------------------------------------------*
       FILE SECTION.
       SD nome-do-arquivo-temporário.
       01 nome-do-registro-temporário.
          03 nome-do-campo-1 PIC picture-do-campo-1.
          03 nome-do-campo-2 PIC picture-do-campo-2.
          03 nome-do-campo-3 PIC picture-do-campo-3.
          03 nome-do-campo-N PIC picture-do-campo-N.

As regras para definição de nomes de registro, nomes de campo e pictures são as mesmas que usamos até aqui. O arquivo temporário é deletado pelo COBOL assim que o comando SORT terminar a classificação. Esse comando tem a seguinte sintaxe:

SORT nome-do-arquivo-temporário
     ON ASCENDING KEY nome-do-campo-1
     USING  nome-do-arquivo-entrada
     GIVING nome-do-arquivo-saida

Esse é o formato mais simples do comando. Basicamente ele pegará todos os registros do arquivo de entrada, classificará esses registros pelo campo informado e gravará os registros ordenados no arquivo de saída.

Podemos ordenar o arquivo por mais de um campo, incluindo seus nomes na cláusula ON ASCENDING KEY :

SORT nome-do-arquivo-temporário
     ON ASCENDING KEY nome-do-campo-1, nome-do-campo-2
     USING  nome-do-arquivo-entrada
     GIVING nome-do-arquivo-saida

Esse comando classificaria os registros primeiro pelo campo nome-do-campo-1, e nos casos em que os valores desse campo fossem iguais os registros seriam ordenados por nome-do-campo-2.

Podemos ordenar os arquivos em ordem descendente de um ou mais campos, substituindo ON ASCENDING KEY por ON DESCENDING KEY ou mesmo combinando os dois:

SORT nome-do-arquivo-temporário
     ON ASCENDING KEY nome-do-campo-1
     ON DESCEDING KEY nome-do-campo-2
     USING  nome-do-arquivo-entrada
     GIVING nome-do-arquivo-saida

Vamos aplicar esses conceitos num programa real bem simples, cujo único objetivo é classificar nosso velho conhecido CRA0207V gerando um arquivo chamado CRA0209 onde todos os registros estarão ordenados por código do cliente, número da fatura e número da duplicata. Desta vez não usaremos books apenas para ficar evidente que os três arquivos são iguais:

      *================================================================*
       IDENTIFICATION DIVISION.
      *----------------------------------------------------------------*
       PROGRAM-ID.    CRP0209.
       AUTHOR.        PAULO ANDRE DIAS.
       DATE-WRITTEN.  09/01/2017.
       REMARKS.
      *----------------------------------------------------------------*
      * SISTEMA:      CR - CONTAS A RECEBER
      * JOB:          02 - GERACAO DE FLUXO DE CAIXA
      * PROGRAMA:     09 - CLASSIFICA DUPLICATAS
      *
      * OBJETIVO:     ESSE PROGRAMA CLASSIFICA O ARQUIVO DE DUPLICATAS
      *               VALIDAS EM ORDEM DE CLIENTE, FATURA E DUPLICATA
      *                   
      * VERSOES:      DATA    DESCRICAO
      *               ------  ---------------------------------------
      *               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      *
      *----------------------------------------------------------------*

      *================================================================*
       ENVIRONMENT DIVISION.
      *----------------------------------------------------------------*
       CONFIGURATION SECTION.
       SPECIAL-NAMES.
           DECIMAL-POINT IS COMMA.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.

      * ARQUIVO DE ENTRADA
           SELECT CRA0207V ASSIGN TO "$DAT/CRA0207V.DAT"
               ORGANIZATION IS LINE SEQUENTIAL.

      * ARQUIVO TEMPORARIO
           SELECT TMP0207V ASSIGN TO "$DAT/TMP0208.SRT" 
               ORGANIZATION IS LINE SEQUENTIAL.

      * ARQUIVO DE SAIDA
           SELECT CRA0209 ASSIGN TO "$DAT/CRA0209.DAT"
               ORGANIZATION IS LINE SEQUENTIAL. 

      *================================================================*
       DATA DIVISION.
      *----------------------------------------------------------------*
       FILE SECTION.

      * ARQUIVO DE ENTRADA
       FD CRA0207V.
       01 CRA0207V-REGISTRO.
           03 CRA0207V-NR-FATURA        PIC  9(006).
           03 CRA0207V-NR-DUPLICATA     PIC  9(002).
           03 CRA0207V-CD-CLIENTE       PIC  X(006).
           03 CRA0207V-DT-EMISSAO       PIC  9(008).
           03 CRA0207V-DT-VENCIMENTO    PIC  9(008).
           03 CRA0207V-VL-FATURA        PIC S9(013)V9(002).
           03 CRA0207V-CD-CATEGORIA     PIC  X(003).
           03 CRA0207V-ST-DUPLICATA     PIC  X(003).

      * ARQUIVO TEMPORARIO
       SD TMP0207V.
       01 TMP0207V-REGISTRO.
           03 TMP0207V-NR-FATURA        PIC  9(006).
           03 TMP0207V-NR-DUPLICATA     PIC  9(002).
           03 TMP0207V-CD-CLIENTE       PIC  X(006).
           03 TMP0207V-DT-EMISSAO       PIC  9(008).
           03 TMP0207V-DT-VENCIMENTO    PIC  9(008).
           03 TMP0207V-VL-FATURA        PIC S9(013)V9(002).
           03 TMP0207V-CD-CATEGORIA     PIC  X(003).
           03 TMP0207V-ST-DUPLICATA     PIC  X(003).

      * ARQUIVO DE SAIDA
       FD CRA0209.
       01 CRA0209-REGISTRO.
           03 CRA0209-NR-FATURA         PIC  9(006).
           03 CRA0209-NR-DUPLICATA      PIC  9(002).
           03 CRA0209-CD-CLIENTE        PIC  X(006).
           03 CRA0209-DT-EMISSAO        PIC  9(008).
           03 CRA0209-DT-VENCIMENTO     PIC  9(008).
           03 CRA0209-VL-FATURA         PIC S9(013)V9(002).
           03 CRA0209-CD-CATEGORIA      PIC  X(003).
           03 CRA0209-ST-DUPLICATA      PIC  X(003).

      *================================================================*
       PROCEDURE DIVISION.
      *----------------------------------------------------------------*
       0-PRINCIPAL.

           SORT TMP0207V
                ON ASCENDING KEY TMP0207V-CD-CLIENTE
                                 TMP0207V-NR-FATURA
                                 TMP0207V-NR-DUPLICATA
                USING  CRA0207V
                GIVING CRA0209

           STOP RUN.

Repare que os três arquivos são declarados na ENVIRONMENT exatamente da mesma forma. A única diferença está na FILE SECTION, onde o detalhamento do arquivo temporário é feito num parágrafo SD.

Nossa PROCEDURE só tem o comando para classificação. O arquivo temporário não precisa ser aberto nem fechado. Na verdade, como este programa só faz o SORT, nem precisamos abrir ou fechar os arquivos de entrada e saída.

Para mostrar o resultado da classificação, vamos modificar ligeiramente a PROCEDURE DIVISION para exibir na tela os registros do arquivo de entrada e do arquivo de saída, logo após o SORT.

     *================================================================*
       PROCEDURE DIVISION.
      *----------------------------------------------------------------*
       0-PRINCIPAL.

           SORT TMP0207V
                ON ASCENDING KEY TMP0207V-CD-CLIENTE
                                 TMP0207V-NR-FATURA
                                 TMP0207V-NR-DUPLICATA
                USING  CRA0207V
                GIVING CRA0209

           DISPLAY " "
           DISPLAY "*** REGISTROS NO ARQUIVO DE ENTRADA"

           OPEN INPUT CRA0207V

           READ CRA0207V

           PERFORM UNTIL WT-ST-CRA0207V NOT = "00"
               DISPLAY "CLI=" CRA0207V-CD-CLIENTE " "
                       "FAT=" CRA0207V-NR-FATURA " "
                       "DUP=" CRA0207V-NR-DUPLICATA " "
                       "VAL=" CRA0207V-VL-FATURA
               READ CRA0207V
           END-PERFORM

           CLOSE CRA0207V

           DISPLAY " "
           DISPLAY "*** REGISTROS NO ARQUIVO DE SAIDA"

           OPEN INPUT CRA0209

           READ CRA0209

           PERFORM UNTIL WT-ST-CRA0209 NOT = "00"
               DISPLAY "CLI=" CRA0209-CD-CLIENTE " "
                       "FAT=" CRA0209-NR-FATURA " "
                       "DUP=" CRA0209-NR-DUPLICATA " "
                       "VAL=" CRA0209-VL-FATURA
               READ CRA0209 
           END-PERFORM

           CLOSE CRA0209

           STOP RUN.

O trecho em azul abre, lê e fecha o arquivo de entrada, mostrando na tela os campos código do cliente, número da fatura, número da duplicata e valor da duplicata. O trecho em vermelho faz o mesmo com o arquivo de saída, que foi gerado pelo SORT.

O resultado da execução do programa você vê abaixo:

*** REGISTROS NO ARQUIVO DE ENTRADA
CLI=000015 FAT=001324 DUP=01 VAL=+0000000000123,45
CLI=000010 FAT=001325 DUP=01 VAL=+0000000002319,00
CLI=000009 FAT=001326 DUP=01 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=02 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=03 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=04 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=05 VAL=+0000000000590,00
CLI=000010 FAT=001327 DUP=01 VAL=+0000000004850,00
CLI=000011 FAT=001328 DUP=01 VAL=+0000000001810,15
CLI=000008 FAT=001329 DUP=01 VAL=+0000000005650,00
CLI=000005 FAT=001330 DUP=01 VAL=+0000000000045,00
CLI=000004 FAT=001331 DUP=01 VAL=+0000000002100,00
CLI=000003 FAT=001332 DUP=01 VAL=+0000000000510,00
CLI=000003 FAT=001333 DUP=01 VAL=+0000000002740,00
CLI=000003 FAT=001333 DUP=02 VAL=+0000000002740,00

*** REGISTROS NO ARQUIVO DE SAIDA
CLI=000003 FAT=001332 DUP=01 VAL=+0000000000510,00
CLI=000003 FAT=001333 DUP=01 VAL=+0000000002740,00
CLI=000003 FAT=001333 DUP=02 VAL=+0000000002740,00
CLI=000004 FAT=001331 DUP=01 VAL=+0000000002100,00
CLI=000005 FAT=001330 DUP=01 VAL=+0000000000045,00
CLI=000008 FAT=001329 DUP=01 VAL=+0000000005650,00
CLI=000009 FAT=001326 DUP=01 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=02 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=03 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=04 VAL=+0000000000590,00
CLI=000009 FAT=001326 DUP=05 VAL=+0000000000590,00
CLI=000010 FAT=001325 DUP=01 VAL=+0000000002319,00
CLI=000010 FAT=001327 DUP=01 VAL=+0000000004850,00
CLI=000011 FAT=001328 DUP=01 VAL=+0000000001810,15
CLI=000015 FAT=001324 DUP=01 VAL=+0000000000123,45

Repare que o arquivo de saída está classificado primeiro por código do cliente, depois por número da fatura e, por último, pelo número da duplicata.

Classificando apenas alguns registros

O comando SORT que vimos no exemplo anterior fez todo o trabalho de leitura, classificação e geração do arquivo de saída. Mas existem situações em que precisamos processar os registros de entrada antes da classificação (fase de release) e/ou trabalhar os registros classificados para gerar o arquivo de saída (fase de return).

Quando você estiver construindo um programa que precisa classificar milhões de registros (algo muito comum em grandes sistemas) você precisará se preocupar com seu tempo de processamento. E os dois fatores que mais afetam a performance de um processo de classificação são (1) a quantidade de registros classificados e (2) o tamanho de cada registro classificado.

É basicamente para isso que existe outro formato do comando SORT:

SORT arquivo-temporário
     ON [ASCENDING/DESCENDING] KEY campo1 campo2 ... campoN
     INPUT PROCEDURE IS parágrafo1
     OUTPUT PROCEDURE IS parágrafo2

O uso de INPUT e OUTPUT PROCEDURE nos permite construir a lógica que o COBOL seguirá nas fases de release e return, respectivamente. Nesse caso, teremos que gravar os registros no arquivo temporário antes da classificação (na INPUT PROCEDURE) e depois ler os registros classificados do arquivo temporário para gerar o arquivo de saída (na OUTPUT PROCEDURE).

Na INPUT PROCEDURE, para gravar registros no arquivo temporário usamos o comando RELEASE, ao invés do WRITE:

RELEASE registro-do-arquivo-temporário

O comando RELEASE também permite que usemos a cláusula FROM para indicar uma área de trabalho (normalmente criada na WORKING) cujo conteúdo deve ser copiado para o registro antes de gravá-lo:

RELEASE registro-do-arquivo-temporário FROM área-de-trabalho

Na OUTPUT PROCEDURE, para ler os registros classificados, usamos o comando RETURN, ao invés do READ. Como não temos file status para o arquivo temporário precisamos da cláusula AT END para capturar a situação de fim do arquivo.

RETURN arquivo-temporário
       AT END Comando1
              Comando2
              ComandoN
END-RETURN

Vamos ver como isso tudo funciona construindo um novo programa, chamado CRP0210. Esse programa vai ler todos os registros de duplicatas válidas do arquivo CRA0207V e selecionar apenas as que estão vencidas (campo situação igual a “VNC”). Em seguida vai classificar os registros selecionados por código de categoria e gravar um arquivo de saída que contém apenas os campos código de categoria e valor da duplicata.

Até a PROCEDURE DIVISION existem poucas diferenças em relação aos programas anteriores:

      *================================================================*
       IDENTIFICATION DIVISION.
      *----------------------------------------------------------------*
       PROGRAM-ID.    CRP0210.
       AUTHOR.        PAULO ANDRE DIAS.
       DATE-WRITTEN.  10/01/2017.
       REMARKS.
      *----------------------------------------------------------------*
      * SISTEMA:      CR - CONTAS A RECEBER
      * JOB:          02 - GERACAO DE FLUXO DE CAIXA
      * PROGRAMA:     10 - GERA DUPLICATAS A RECEBER POR CATEGORIA
      *
      * OBJETIVO:     ESSE PROGRAMA LE O ARQUIVO DE DUPLICATAS VALIDAS
      *               SELECIONA AS DUPLICATAS VENCIDAS (SITUACAO IGUAL
      *               A "VNC"), CLASSIFICA AS DUPLICATAS SELECIONADAS
      *               POR CODIGO DE CATEGORIA E GERA UM ARQUIVO DE
      *               SAIDA COM CODIGO DE CATEGORIA E VALOR DA DUPLI-
      *               CATA
      *                   
      * VERSOES:      DATA    DESCRICAO
      *               ------  ---------------------------------------
      *               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      *
      *----------------------------------------------------------------*

      *================================================================*
       ENVIRONMENT DIVISION.
      *----------------------------------------------------------------*
       CONFIGURATION SECTION.
       SPECIAL-NAMES.
           DECIMAL-POINT IS COMMA.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.

      * DUPLICATAS VALIDAS
           SELECT CRA0207V ASSIGN TO "$DAT/CRA0207V.DAT"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WT-ST-CRA0207V.

      * ARQUIVO TEMPORARIO PARA SORT         
           SELECT TMP0210 ASSIGN TO "$DAT/TMP0210.SRT"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WT-ST-TMP0210.

      * VALORES DEVIDOS POR CATEGORIA            
           SELECT CRA0210 ASSIGN TO "$DAT/CRA0210.DAT"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WT-ST-CRA0210.

      *================================================================*
       DATA DIVISION.
      *----------------------------------------------------------------*
       FILE SECTION.

      * DUPLICATAS VALIDAS
       FD CRA0207V.
       01 CRA0207V-REGISTRO.
           03 CRA0207V-NR-FATURA        PIC  9(006).
           03 CRA0207V-NR-DUPLICATA     PIC  9(002).
           03 CRA0207V-CD-CLIENTE       PIC  X(006).
           03 CRA0207V-DT-EMISSAO       PIC  9(008).
           03 CRA0207V-DT-VENCIMENTO    PIC  9(008).
           03 CRA0207V-VL-FATURA        PIC S9(013)V9(002).
           03 CRA0207V-CD-CATEGORIA     PIC  X(003).
           03 CRA0207V-ST-DUPLICATA     PIC  X(003).

      * ARQUIVO TEMPORARIO PARA SORT
       SD TMP0210.
       01 TMP0210-REGISTRO.
           03 TMP0210-NR-FATURA         PIC  9(006).
           03 TMP0210-NR-DUPLICATA      PIC  9(002).
           03 TMP0210-CD-CLIENTE        PIC  X(006).
           03 TMP0210-DT-EMISSAO        PIC  9(008).
           03 TMP0210-DT-VENCIMENTO     PIC  9(008).
           03 TMP0210-VL-FATURA         PIC S9(013)V9(002).
           03 TMP0210-CD-CATEGORIA      PIC  X(003).
           03 TMP0210-ST-DUPLICATA      PIC  X(003).

      * VALORES DEVIDOS POR CATEGORIA          
       FD CRA0210.
       01 CRA0210-REGISTRO.
           03 CRA0210-CD-CATEGORIA      PIC  X(003).
           03 CRA0210-VL-FATURA         PIC S9(013)V9(002).

      *----------------------------------------------------------------*
       WORKING-STORAGE SECTION.
      *----------------------------------------------------------------*
       01 WT-FILE-STATUS.
           03 WT-ST-CRA0207V           PIC  X(002) VALUE SPACES.
           03 WT-ST-CRA0210            PIC  X(002) VALUE SPACES.
           03 WT-ST-TMP0210            PIC  X(002) VALUE SPACES.

Repare que, dessa vez, nosso arquivo de saída (CRA0210) tem um layout diferente do arquivo de entrada e do arquivo temporário para classificação. O programa seria mais eficiente se o arquivo temporário tivesse apenas os campos necessários para gerar o arquivo de saída, mas deixamos assim para ter o que mostrar na OUTPUT PROCEDURE, mais adiante.

A PROCEDURE DIVISION começa com o comando SORT, desta vez indicando quais serão os parágrafos para INPUT e OUTPUT PROCEDURE.

      *================================================================*
       PROCEDURE DIVISION.
      *----------------------------------------------------------------*
       0-PRINCIPAL.

           SORT TMP0210
                ON ASCENDING KEY TMP0210-CD-CATEGORIA
                INPUT PROCEDURE IS 1-PROCESSA-ENTRADA
                OUTPUT PROCEDURE IS 2-PROCESSA-SAIDA

           STOP RUN.

Esses parágrafos são iguais a todos os outros que construímos até agora. Vejamos como ficou a INPUT PROCEDURE:

      *----------------------------------------------------------------*
      * LE TODOS OS REGISTROS DO ARQUIVO DE ENTRADA E SELECIONA APENAS
      * AQUELES QUE ESTAO COM CODIGO DE SITUACAO IGUAL A "VNC" (VENCI-
      * DAS). OS REGISTROS SELECIONADOS SERAO GRAVADOS NO ARQUIVO TEM-
      * PORARIO PARA CLASSIFICACAO
      *----------------------------------------------------------------*
       1-PROCESSA-ENTRADA.

           OPEN INPUT CRA0207V

           READ CRA0207V

           PERFORM 11-SELECIONA-REGISTROS
               UNTIL WT-ST-CRA0207V NOT = "00"

           CLOSE CRA0207V.

Ele abre o arquivo de entrada e faz a leitura do primeiro registro. Em seguida, executa um parágrafo chamado 11-SELECIONA-REGISTROS até que não haja mais registros para ler. O último comando fecha o arquivo de entrada pois ele não será mais necessário no programa. O SORT só começa a classificar os registros depois do último comando da INPUT PROCEDURE.

O parágrafo 11-SELECIONA-REGISTROS grava no arquivo temporário apenas as duplicatas cuja situação seja igual a “VNC”, como pede a especificação.

      *----------------------------------------------------------------*
      * SELECIONA REGISTROS PARA CLASSIFICACAO
      *----------------------------------------------------------------*
       11-SELECIONA-REGISTROS.

           IF CRA0207V-ST-DUPLICATA = "VNC"
               RELEASE TMP0210-REGISTRO FROM CRA0207V-REGISTRO
           END-IF

           READ CRA0207V.

Para gravar o registro no arquivo temporário usamos o comando RELEASE com a cláusula FROM, uma vez que o layout do arquivo TMP0210 é igual ao layout do arquivo de entrada, CRA0207V.

Agora vejamos como ficou a OUTPUT PROCEDURE, que é onde vamos gerar nosso arquivo de saída. Esse parágrafo só começa a ser executado depois que o SORT termina de fazer a classificação::

      *----------------------------------------------------------------*
      * LE TODOS OS REGISTROS CLASSIFICADOS E GRAVA ARQUIVO DE SAIDA
      *----------------------------------------------------------------*
       2-PROCESSA-SAIDA.

           OPEN OUTPUT CRA0210

           RETURN TMP0210

           PERFORM 21-GRAVA-ARQUIVO-SAIDA
               UNTIL WT-ST-TMP0210 NOT = “00”

           CLOSE CRA0210.

A leitura dos registros do arquivo temporário é executada com o comando RETURN. O parágrafo 21-GRAVA-ARQUIVO-SAIDA é executado até que não haja mais registros no arquivo temporário de classificação:

      *----------------------------------------------------------------*
      * LE REGISTROS CLASSIFICADOS E GRAVA NO ARQUIVO DE SAIDA
      *----------------------------------------------------------------*
       21-GRAVA-ARQUIVO-SAIDA.

           MOVE TMP0210-CD-CATEGORIA TO CRA0210-CD-CATEGORIA
           MOVE TMP0210-VL-FATURA TO CRA0210-VL-FATURA

           WRITE CRA0210-REGISTRO

           RETURN TMP0210.

Nesse parágrafo movimentamos para o registro de saída apenas os campos que ele possui: código de categoria e valor da fatura.

Você pode combinar as opções do comando SORT de acordo com a necessidade do programa: pode usar USING e GIVING, USING e OUTPUT PROCEDURE, INPUT PROCEDURE e GIVING ou INPUT PROCEDURE e OUTPUT PROCEDURE. Por exemplo:

SORT TMP0210
     ON ASCENDING KEY TMP0210-CD-CATEGORIA
     INPUT PROCEDURE IS 1-SELECIONA-REGISTROS
     GIVING CRA0210

Nesse caso definimos uma INPUT PROCEDURE para selecionar os registros que queremos e gravamos num arquivo temporário que tem o mesmo layout do arquivo de saída. Depois da classificação não precisamos de nenhum tratamento adicional, por isso usamos a opção GIVING para copiar o arquivo temporário para o arquivo de saída definitivo.

Outro exemplo:

SORT TMP0210
     ON ASCENDING KEY TMP0210-CD-CATEGORIA
     USING CRA0207V
     OUTPUT PROCEDURE IS 2-GRAVA-SAIDA

Nesse formato, o arquivo temporário deve ter o mesmo layout do arquivo de entrada e nada será selecionado antes da classificação, por isso a opção USING.

Merge

Merge é o nome que se dá ao processo de juntar dois ou mais arquivos sequenciais que têm o mesmo layout, que foram previamente ordenados pelas mesmas chaves, mas que têm, obviamente, conteúdos diferentes.

É possível usar o comando SORT para juntar arquivos diferentes e produzir um arquivo único, ordenado.

Vejamos um exemplo prático. Vamos imaginar quatro arquivos de duplicatas, um para cada trimestre do ano, que precisamos juntar num único arquivo ordenado por número da fatura e número da duplicata.

      *================================================================*
       DATA DIVISION.
      *----------------------------------------------------------------*
       FILE SECTION.

      * ARQUIVO DO PRIMEIRO TRIMESTRE
       FD ARQUIVO01.
       01 ARQUIVO01-REGISTRO             PIC X(051).

      * ARQUIVO DO SEGUNDO TRIMESTRE
       FD ARQUIVO02.
       01 ARQUIVO02-REGISTRO             PIC X(051).

      * ARQUIVO DO TERCEIRO TRIMESTRE
       FD ARQUIVO03.
       01 ARQUIVO03-REGISTRO             PIC X(051).
     
      * ARQUIVO DO QUARTO TRIMESTRE
       FD ARQUIVO04.
       01 ARQUIVO04-REGISTRO             PIC X(051).

      * ARQUIVO TEMPORARIO PARA MERGE
       SD TMPMERGE.
       01 TMPMERGE-REGISTRO.
           03 TMPMERGE-NR-FATURA         PIC  9(006).
           03 TMPMERGE-NR-DUPLICATA      PIC  9(002).
           03 TMPMERGE-CD-CLIENTE        PIC  X(006).
           03 TMPMERGE-DT-EMISSAO        PIC  9(008).
           03 TMPMERGE-DT-VENCIMENTO     PIC  9(008).
           03 TMPMERGE-VL-FATURA         PIC S9(013)V9(002).
           03 TMPMERGE-CD-CATEGORIA      PIC  X(003).
           03 TMPMERGE-ST-DUPLICATA      PIC  X(003).

      * ARQUIVO UNICO
       FD ARQUIVO99.
       01 ARQUIVO99-REGISTRO             PIC X(051).

O comando abaixo faria todo o trabalho:

SORT TMPMERGE
    ON ASCENDING KEY TMPMERGE-NR-FATURA
                     TMPMERGE-NR-DUPLICATA
    USING ARQUIVO01 ARQUIVO02 ARQUIVO03 ARQUIVO04
    GIVING ARQUIVO99

Se estivéssemos trabalhando com arquivos com layouts diferentes, poderíamos criar uma INPUT PROCEDURE que lesse cada um dos arquivos e gravasse um arquivo temporário com o layout unificado. Em seguida a própria opção GIVING geraria o arquivo definitivo de saída, que estaria unificado e ordenado.

Curiosamente o COBOL possui um comando específico para MERGE que é pouquíssimo utilizado. Sua sintaxe é:

MERGE arquivo-temporário
    ON ASCENDING KEY campo1 campo2 ... campoN
    USING arquivo1 arquivo2 ... arquivo
    GIVING arquivo-saida

Repare que ele é praticamente igual ao comando SORT. No programa anterior, poderíamos ter codificado o merge assim:

MERGE TMPMERGE
    ON ASCENDING KEY TMPMERGE-NR-FATURA
                     TMPMERGE-NR-DUPLICATA
    USING ARQUIVO01 ARQUIVO02 ARQUIVO03 ARQUIVO04
    GIVING ARQUIVO99

Você encontrará muitos programas usando o comando SORT e raramente verá um com o comando MERGE. Existe um motivo para isso: o MERGE espera que todos os arquivos de entrada já estejam ordenados pelas mesmas chaves. Logo, um ou mais SORTs seriam necessários antes do MERGE. E como o próprio SORT faz tanto a classificação quanto a unificação, então não temos necessidade desse comando.

Quebra de um nível

A classificação de arquivos sequenciais é a base de diversas técnicas de programação que usamos frequentemente em ambientes corporativos. Uma dessas técnicas é conhecida pelo nome de “quebra”.

A quebra é utilizada sempre que precisamos separar ou sumarizar um conjunto de informações em função de um ou mais campos de controle.

Imagine um arquivo sequencial chamado FAA0500.DAT que contém a relação de vendas realizadas num período. Em COBOL, seu layout poderia ser assim:

01 FTA0101-REGISTRO.
    03 FTA0101-NR-VENDEDOR   PIC  9(006).
    03 FTA0101-NR-CLIENTE    PIC  9(006).
    03 FTA0101-NR-FATURA     PIC  9(006).
    03 FTA0101-DT-VENDA      PIC  9(008).
    03 FTA0101-VL-VENDA      PIC S9(013)V9(002).

Esse arquivo tem o seguinte conteúdo:

Cobol: Conteúdo hipotético de arquivo
Figura 37. Conteúdo hipotético do arquivo FTA0101

Agora suponha que precisemos imprimir um relatório totalizando as vendas por vendedor, como no modelo abaixo:

EMPRESA XYZ                                                       DATA: 14/01/17
FATURAMENTO                                                       HORA: 09:29:37
FAL0501                       VENDAS POR VENDEDOR                 PAGINA:      1
--------------------------------------------------------------------------------
VENDEDOR       DATA         CLIENTE      FATURA                            VALOR
--------------------------------------------------------------------------------
000001       11/05/2015     001240       015001                        21.250,00
             11/05/2015     002304       015002                        74.750,00
             13/05/2015     000988       015005                        25.790,00
------------
                                                           TOTAL:     121.790,00

000002       11/05/2015     001700       015003                       116.414,00
             12/05/2015     002800       015004                         8.515,00
                                                                   ------------
                                                           TOTAL:     124.929,00

000003       14/05/2015     003501       015006                         1.600,00
                                                                   ------------
                                                           TOTAL:       1.600,00

000004       15/05/2015     000515       015007                        35.300,00
             15/05/2015     000789       015009                         7.280,00
             15/05/2015     000901       015011                        41.200,00
             15/05/2015     001330       015012                         8.920,00
                                                                    ------------
                                                           TOTAL:      92.700,00

000005       15/05/2015     001514       015008                         6.665,00
             15/05/2015     002201       015010                        12.458,00
             16/05/2015     001101       015013                         1.740,00
                                                                    ------------
                                                           TOTAL:      20.863,00

000006       16/05/2015     002005       015014                        64.300,00
                                                                    ------------
                                                           TOTAL:      64.300,00

Observe que precisamos mostrar o total de vendas por vendedor, e que o número do vendedor só deve aparecer na sua primeira venda, não nas demais.

Para fazer isso, teremos que ordenar o arquivo de vendas por número de vendedor e depois “quebrar” o relatório por esse número. O número do vendedor, na prática, é o campo de controle que mencionamos anteriormente. Quando o programa precisa de apenas um campo de controle (que é o caso desse exemplo) dizemos que é uma quebra de um nível.

Existem algoritmos padrões para codificar um programa estruturado com quebra de um nível. Podemos ver o diagrama dele na figura abaixo:

Cobol: Diagrama estruturado de programa
Figura 38. Diagrama estruturado do programa FAP0501

O parágrafo principal executará o parágrafo 1 onde estará o comando para classificação do arquivo de entrada. Nesse exemplo veremos que é possível usar o mesmo arquivo como entrada e saída do SORT.

Também no parágrafo 1 ficarão os comandos para abertura de arquivo e primeira leitura do arquivo de entrada, já ordenado. O parágrafo 3 fecha os arquivos de entrada e saída.

O parágrafo 2 será executado em loop, até o fim do arquivo de entrada. Neste parágrafo vamos salvar o campo de controle do arquivo de entrada (número do vendedor) numa variável de trabalho e zerar o total de vendas por vendedor. Em seguida, executaremos o parágrafo 21 até que o número do vendedor do arquivo de entrada seja diferente da variável de trabalho OU até o fim do arquivo de entrada, o que acontecer primeiro. Por último, chamaremos o parágrafo 22 para imprimir os totais por vendedor.

Sempre que o limite de linhas por página for atingido chamaremos o parágrafo X1 para impressão do cabeçalho.

Nosso programa terá uma IDENTIFICATION DIVISION padrão:

      *================================================================*
       IDENTIFICATION DIVISION.
      *----------------------------------------------------------------*
       PROGRAM-ID.    FAP0501.
       AUTHOR.        PAULO ANDRE DIAS.
       DATE-WRITTEN.  14/01/2017.
       REMARKS.
      *----------------------------------------------------------------*
      * SISTEMA:      FA - FATURAMENTO      
      * JOB:          05 - RELATORIOS              
      * PROGRAMA:     01 - VENDAS POR VENDEDOR     
      *
      * OBJETIVO:     LER O ARQUIVO DE VENDAS NO MES E GERAR RELATORIO
      *               DE VENDAS POR VENDEDOR
      *                   
      * VERSOES:      DATA    DESCRICAO
      *               ------  ---------------------------------------
      *               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      *
      *----------------------------------------------------------------*

Na ENVIRONMENT, além de declarar o arquivo de entrada (FAA0500) e o relatório (FAL0501), temos também o arquivo temporário que usaremos para classificar o arquivo de entrada:

      *================================================================*
       ENVIRONMENT DIVISION.
      *----------------------------------------------------------------*
       CONFIGURATION SECTION.
       SPECIAL-NAMES.
           DECIMAL-POINT IS COMMA.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.

      * VENDAS MENSAIS
           COPY FAS0500.

      * ARQUIVO TEMPORARIO PARA CLASSIFICACAO
          SELECT SRT0500 ASSIGN TO "$DAT/SRT0500.DAT"
              ORGANIZATION IS LINE SEQUENTIAL.
      
      * RELATORIO DE VENDAS POR VENDEDOR      
           SELECT FAL0501 ASSIGN TO "$DAT/FAL0501.TXT"
               ORGANIZATION IS LINE SEQUENTIAL.

Neste programa, o comando SORT usará o próprio arquivo FAA0500 como entrada e saída, ou seja, o SORT vai ler todos os registros na fase de release, fará a classificação por número do vendedor, e gravará os registros ordenados no mesmo arquivo que usou como entrada. Por isso, ao detalhar o arquivo temporário na FILE SECTION só precisamos mencionar a chave de classificação (NR-VENDEDOR). Também precisamos completar seu registro com um FILLER X(035) para que ele fique com o mesmo tamanho registro de entrada.

      *================================================================*
       DATA DIVISION.
      *----------------------------------------------------------------*
       FILE SECTION.

      * VENDAS MENSAIS
           COPY FAF0500.

      * ARQUIVO TEMPORARIO PARA CLASSIFICACAO
       SD SRT0500.
       01 SRT0500-REGISTRO.
           03 SRT0500-NR-VENDEDOR      PIC 9(006).
           03 FILLER                   PIC X(035).
    
      * RELATORIO DE VENDAS POR VENDEDOR
       FD FAL0501.
       01 FAL0501-REGISTRO             PIC  X(080).

A WORKING começa com uma constante para estabelecer a quantidade de linhas por página, o file status do arquivo de entrada, os contadores de páginas e linhas impressas, um acumulador para o total de vendas por vendedor, variáveis para receber data e hora do sistema e algumas variáveis auxiliares para chamada da subrotina de formatação de datas.

      *================================================================*
       WORKING-STORAGE SECTION.
      *----------------------------------------------------------------*
       01 WC-CONSTANTES.
           03 WC-LINHAS-POR-PAGINA     PIC  9(002) VALUE 60.

       01 WT-FILE-STATUS.
           03 WT-ST-FAA0500            PIC  X(002) VALUE SPACES.

       01 WT-CONTADORES.
           03 WT-CT-LIDOS              PIC  9(006) VALUE ZEROS.
           03 WT-CT-PAGINA             PIC  9(003) VALUE ZEROS.
           03 WT-CT-LINHAS             PIC  9(002) VALUE 99.

       01 WT-ACUMULADORES.
           03 WT-AC-VENDAS             PIC S9(013)V9(002) VALUE ZEROS.

       01 WT-AUXILIARES.
           03 WT-NM-EDT4               PIC X(008) VALUE "XXREDT4".
           03 WT-RC-EDT4               PIC 9(002) VALUE ZEROS.
           03 WT-DT-SISTEMA.
              05 ANO                   PIC 9(002) VALUE ZEROS.
              05 MES                   PIC 9(002) VALUE ZEROS.
              05 DIA                   PIC 9(002) VALUE ZEROS.

           03 WT-HR-SISTEMA.
              05 HORA                  PIC 9(002) VALUE ZEROS.
              05 MINUTO                PIC 9(002) VALUE ZEROS.
              05 SEGUNDO               PIC 9(002) VALUE ZEROS.

Toda vez que o programa ler um registro de entrada que tenha um número de vendedor diferente do número de vendedor do registro anterior, teremos que imprimir o total de vendas do vendedor anterior antes de continuar a impressão do relatório. Precisamos, portanto, guardar o número do vendedor anterior para fazer essa comparação. Para isso, criamos uma variável de trabalho adicional na WORKING:

       01 WT-CAMPOS-CONTROLE.
           03 WT-CTL-NR-VENDEDOR       PIC 9(006) VALUE ZEROS.

Em seguida detalhamos o relatório. Sua estrutura é praticamente igual ao relatório que vimos anteriormente, com uma única diferença: repare que o campo WR-DET-NR-VENDEDOR tem uma cláusula nova, chamada BLANK WHEN ZEROS. Essa opção faz com que uma variável numérica não seja exibida quando estiver zerada. Usaremos esse recurso para fazer com que o número do vendedor só apareça na primeira linha de suas vendas.

      01 WR-CAB1.
           03 FILLER                   PIC X(066) VALUE
              "EMPRESA XYZ".
           03 FILLER                   PIC X(006) VALUE
              "DATA: ".
           03 WR-CAB-DATA.
              05 DIA                   PIC 9(002) VALUE ZEROS.
              05 FILLER                PIC X(001) VALUE "/".
              05 MES                   PIC 9(002) VALUE ZEROS.
              05 FILLER                PIC X(001) VALUE "/".
              05 ANO                   PIC 9(002) VALUE ZEROS.

       01 WR-CAB2.
           03 FILLER                   PIC X(066) VALUE
              "FATURAMENTO".
           03 FILLER                   PIC X(006) VALUE
              "HORA: ".
           03 WR-CAB-HORA.
              05 HORA                  PIC 9(002) VALUE ZEROS.
              05 FILLER                PIC X(001) VALUE ":".
              05 MINUTO                PIC 9(002) VALUE ZEROS.
              05 FILLER                PIC X(001) VALUE ":".
              05 SEGUNDO               PIC 9(002) VALUE ZEROS.

       01 WR-CAB3.
           03 FILLER                   PIC X(030) VALUE
              "FAL0501".
           03 FILLER                   PIC X(036) VALUE
              "VENDAS POR VENDEDOR".
           03 FILLER                   PIC X(011) VALUE
              "PAGINA: ".
           03 WR-CAB-PAGINA            PIC ZZ9 VALUE
              ZEROS.

       01 WR-SEP1.
           03  FILLER                  PIC X(080) VALUE ALL "-".

       01 WR-CAB4.
           03  FILLER                  PIC X(015) VALUE "VENDEDOR".
           03  FILLER                  PIC X(013) VALUE "DATA".
           03  FILLER                  PIC X(013) VALUE "CLIENTE".
           03  FILLER                  PIC X(034) VALUE "FATURA".
           03  FILLER                  PIC X(005) VALUE "VALOR".

       01 WR-DET1.
           03  WR-DET-NR-VENDEDOR      PIC 9(006) VALUE ZEROS
               BLANK WHEN ZEROS.
           03  FILLER                  PIC X(007) VALUE SPACES.
           03  WR-DET-DT-VENDA         PIC X(010) VALUE SPACES.
           03  FILLER                  PIC X(005) VALUE SPACES.
           03  WR-DET-CD-CLIENTE       PIC X(006) VALUE SPACES.
           03  FILLER                  PIC X(007) VALUE SPACES.
           03  WR-DET-NR-FATURA        PIC 9(006) VALUE ZEROS.
           03  FILLER                  PIC X(023) VALUE SPACES.
           03  WR-DET-VL-VENDA         PIC ZZZ.ZZ9,99 VALUE ZEROS.

       01 WR-ROD1.
           03  FILLER                  PIC X(068) VALUE SPACES.
           03  FILLER                  PIC X(012) VALUE ALL "-".

       01 WR-ROD2.
           03  FILLER                  PIC X(059) VALUE SPACES.
           03  FILLER                  PIC X(009) VALUE
               "TOTAL: ".
           03  WR-ROD-AC-VENDAS        PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

O diagrama estruturado mostra que esse programa deve ser organizado em três parágrafos principais. O parágrafo 2-PROCESSA será executado até que não haja mais registros no arquivo de entrada:

      *================================================================*
       PROCEDURE DIVISION.
      *----------------------------------------------------------------*
       0-PRINCIPAL.

           PERFORM 1-INICIA
           PERFORM 2-PROCESSA UNTIL WT-ST-FAA0500 NOT = "00"
           PERFORM 3-TERMINA




           STOP RUN.

No parágrafo 1-INICIA ficará a classificação do arquivo de entrada. Nenhum arquivo pode estar aberto no momento da classificação, por isso colocamos o comando SORT antes da abertura dos arquivos. Repare que ele usa o arquivo FAA0500 tanto como entrada (USING) quanto como saída (GIVING):

      *----------------------------------------------------------------*
      * CLASSIFICA O ARQUIVO DE ENTRADA POR NUMERO DO VENDEDOR. EM SE-
      * GUIDA, ABRE OS ARQUIVOS E LE O PRIMEIRO REGISTRO DO ARQUIVO DE
      * ENTRADA
      *----------------------------------------------------------------*
       1-INICIA.

           SORT SRT0500
               ON ASCENDING KEY SRT0500-NR-VENDEDOR
               USING FAA0500
               GIVING FAA0500

           OPEN INPUT FAA0500

           OPEN OUTPUT FAL0501

           READ FAA0500.

É no parágrafo 2-PROCESSA que acontecerá o controle da quebra de vendedor. Observe que a primeira coisa que ele faz é salvar o número do vendedor que acabou de ser lido naquela variável de trabalho que criamos na WORKING.

Logo em seguida ele move esse mesmo número de vendedor para o campo correspondente no relatório. Essa movimentação só acontecerá nesse ponto do programa, pois queremos que o número do vendedor só apareça na primeira linha de suas vendas. Além disso, como esse parágrafo só é executado quando o programa encontra um novo vendedor, aqui também é o lugar perfeito para zerar o total de vendas por vendedor.

      *----------------------------------------------------------------*
      * SALVA O CAMPO DE CONTROLE (NUMERO DO VENDEDOR) IMPRIME TODAS
      * AS SUAS VENDAS E IMPRIME SEU TOTAL DE VENDAS NO PERIODO
      *----------------------------------------------------------------*

       2-PROCESSA.

           MOVE FAA0500-NR-VENDEDOR TO WT-CTL-NR-VENDEDOR
           MOVE FAA0500-NR-VENDEDOR TO WR-DET-NR-VENDEDOR
           MOVE ZEROS TO WT-AC-VENDAS

Agora vamos chamar o parágrafo que será responsável por imprimir as vendas. Esse parágrafo será executado até que não existam mais registros no arquivo de entrada ou que ele leia um registro do arquivo de entrada onde o número do vendedor seja diferente do que salvamos na variável de controle.

      *----------------------------------------------------------------*
      * SALVA O CAMPO DE CONTROLE (NUMERO DO VENDEDOR) IMPRIME TODAS
      * AS SUAS VENDAS E IMPRIME SEU TOTAL DE VENDAS NO PERIODO
      *----------------------------------------------------------------*
       2-PROCESSA.

           MOVE FAA0500-NR-VENDEDOR TO WT-CTL-NR-VENDEDOR
           MOVE FAA0500-NR-VENDEDOR TO WR-DET-NR-VENDEDOR
           MOVE ZEROS TO WT-AC-VENDAS

           PERFORM 21-IMPRIME-VENDAS
               UNTIL WT-ST-FAA0500 NOT = "00" OR
                     FAA0500-NR-VENDEDOR NOT = WT-CTL-NR-VENDEDOR

Quando o fluxo de execução voltar para o parágrafo 2-PROCESSA (ou porque acabaram os registros ou porque detectou-se a quebra de número de vendedor), precisaremos imprimir o total de vendas do último vendedor processado:

      *----------------------------------------------------------------*
      * SALVA O CAMPO DE CONTROLE (NUMERO DO VENDEDOR) IMPRIME TODAS
      * AS SUAS VENDAS E IMPRIME SEU TOTAL DE VENDAS NO PERIODO
      *----------------------------------------------------------------*
       2-PROCESSA.

           MOVE FAA0500-NR-VENDEDOR TO WT-CTL-NR-VENDEDOR
           MOVE FAA0500-NR-VENDEDOR TO WR-DET-NR-VENDEDOR
           MOVE ZEROS TO WT-AC-VENDAS

           PERFORM 21-IMPRIME-VENDAS
               UNTIL WT-ST-FAA0500 NOT = "00" OR
                     FAA0500-NR-VENDEDOR NOT = WT-CTL-NR-VENDEDOR

           PERFORM 22-IMPRIME-TOTAL.

Vamos codificar o parágrafo final para que os parágrafos apareçam no programa na mesma ordem em que são mencionados pelos PERFORMs. O comando CANCEL está aqui porque teremos que chamar o subprograma XXREDT4 para formatar as datas da linha de detalhe:

      *----------------------------------------------------------------*
      * FECHA ARQUIVOS E EXIBE OS CONTADORES DE REGISTROS LIDOS E IM-
      * PRESSOS        
      *----------------------------------------------------------------*
       3-TERMINA.

           CANCEL WT-NM-EDT4

           CLOSE FAA0500 FAL0501.

O parágrafo 21-IMPRIME-VENDAS começa verificando se a quantidade de linhas impressas está maior que o limite de linhas por página. No início da execução a quantidade de linhas impressas está com um valor maior que esse limite para que o cabeçalho seja impresso na primeira página:

      *----------------------------------------------------------------*
      * IMPRIME TODAS AS VENDAS DO VENDEDOR
      *----------------------------------------------------------------*
       21-IMPRIME-VENDAS.

           IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
               PERFORM X1-IMPRIME-CABECALHO
           END-IF

Todos os campos do arquivo de entrada precisam ser movidos para os campos correspondentes na linha detalhe. Observe que o número do vendedor não é movimentado, porque já fizemos isso no parágrafo 2-PROCESSA. Aqui também chamamos a subrotina XXREDT4 para transformar uma data numérica AAAAMMDD em alfanumérica DD/MM/AAAA:

      *----------------------------------------------------------------*
      * IMPRIME TODAS AS VENDAS DO VENDEDOR
      *----------------------------------------------------------------*
       21-IMPRIME-VENDAS.

           IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
               PERFORM X1-IMPRIME-CABECALHO
           END-IF
   
           CALL WT-NM-EDT4 USING FAA0500-DT-VENDA   
                                 WR-DET-DT-VENDA   
                                 WT-RC-EDT4

           MOVE FAA0500-CD-CLIENTE TO WR-DET-CD-CLIENTE
           MOVE FAA0500-NR-FATURA TO WR-DET-NR-FATURA
           MOVE FAA0500-VL-VENDA TO WR-DET-VL-VENDA

Para encerrar esse parágrafo, imprimimos a linha detalhe, incrementamos o contador de linhas impressas, acumulamos o total de vendas por vendedor e lemos o próximo registro do arquivo de entrada.

Mas além disso, precisamos também zerar o número do vendedor na linha detalhe. É isso que vai garantir que a próxima linha de vendas desse vendedor não mostrará seu número. Como a variável WR-DET-NR-VENDEDOR foi declarada na WORKING com BLANK WHEN ZEROS, se ela estiver zerada esse número será exibido como brancos:

      *----------------------------------------------------------------*
      * IMPRIME TODAS AS VENDAS DO VENDEDOR
      *----------------------------------------------------------------*
       21-IMPRIME-VENDAS.

           IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
               PERFORM X1-IMPRIME-CABECALHO
           END-IF
         
           CALL WT-NM-EDT4 USING FAA0500-DT-VENDA  
                                 WR-DET-DT-VENDA  
                                 WT-RC-EDT4

           MOVE FAA0500-CD-CLIENTE TO WR-DET-CD-CLIENTE
           MOVE FAA0500-NR-FATURA TO WR-DET-NR-FATURA
           MOVE FAA0500-VL-VENDA TO WR-DET-VL-VENDA 
        
           WRITE FAL0501-REGISTRO FROM WR-DET1
           ADD 1 TO WT-CT-LINHAS
           ADD FAA0500-VL-VENDA TO WT-AC-VENDAS    

           MOVE ZEROS TO WR-DET-NR-VENDEDOR

           READ FAA0500.

O próximo parágrafo é aquele que imprime o total de vendas por vendedor. Ele também verifica a quantidade de linhas impressas para saber se precisa saltar uma página e imprimir novo cabeçalho. Em seguida, simplesmente movimenta o acumulador de vendas por vendedor para o rodapé e imprime as linhas correspondentes.

Repare que ao imprimir a última linha do rodapé estamos usando uma opção nova, BEFORE 2. Ela faz com que a linha WR-ROD2 seja impressa antes do programa saltar duas linhas. Na prática, ela coloca uma linha em branco entre WR-ROD2 e a próxima linha de vendas, como estava previsto no layout do relatório. Por causa dessa linha em branco, precisamos somar 3 ao contador de linhas impressas.

      *----------------------------------------------------------------*
      * IMPRIME TOTAL POR VENDEDOR
      *----------------------------------------------------------------*
       22-IMPRIME-TOTAL.   

           IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
               PERFORM X1-IMPRIME-CABECALHO
           END-IF

           MOVE WT-AC-VENDAS TO WR-ROD-AC-VENDAS

           WRITE FAL0501-REGISTRO FROM WR-ROD1
           WRITE FAL0501-REGISTRO FROM WR-ROD2 BEFORE 2

           ADD 3 TO WT-CT-LINHAS.

O último parágrafo do programa é aquele que vai imprimir o cabeçalho no topo de cada página. Seu nome tem o prefixo X1- porque ele é chamado tanto por 21-IMPRIME-VENDAS quanto por 22-IMPRIME-TOTAL.

      *----------------------------------------------------------------*
      * IMPRIME CABECALHO
      *----------------------------------------------------------------*
       X1-IMPRIME-CABECALHO.

           ACCEPT WT-DT-SISTEMA FROM DATE
           ACCEPT WT-HR-SISTEMA FROM TIME
           ADD 1 TO WT-CT-PAGINA

           MOVE CORR WT-DT-SISTEMA TO WR-CAB-DATA
           MOVE CORR WT-HR-SISTEMA TO WR-CAB-HORA
           MOVE WT-CT-PAGINA TO WR-CAB-PAGINA

           WRITE FAL0501-REGISTRO FROM WR-CAB1 AFTER PAGE
           WRITE FAL0501-REGISTRO FROM WR-CAB2
           WRITE FAL0501-REGISTRO FROM WR-CAB3
           WRITE FAL0501-REGISTRO FROM WR-SEP1
           WRITE FAL0501-REGISTRO FROM WR-CAB4
           WRITE FAL0501-REGISTRO FROM WR-SEP1
        
           MOVE 6 TO WT-CT-LINHAS.

A quebra pressupõe que o arquivo de entrada esteja ordenado pelo(s) campo(s) de controle e por isso usamos o comando SORT no início do programa. No dia a dia, porém, é mais comum que essa classificação seja feita por um utilitário externo que é codificado um step antes da execução do programa, no mesmo job ou script.

Quebra de múltiplos níveis

O algoritmo para quebra de um nível que vimos no exemplo anterior pode ser facilmente expandido para contemplar quebras de múltiplos níveis, em processos que precisam de mais de um campo de controle.

Vamos acompanhar outro exemplo, desta vez usando dois campos de controle para gerar um relatório com quebra de dois níveis.

Considere o arquivo abaixo, que se chama FPA0513.DAT:

Cobol: Conteúdo de arquivo
Figura 39. Conteúdo do arquivo de salários por diretoria e departamento

Seu layout já está detalhado no book FPF0513.CPY:

      *----------------------------------------------------------------*
      * SALARIOS                           
      *----------------------------------------------------------------*
       FD FPA0513.
       01 FPA0513-REGISTRO.
           03 FPA0513-CD-DIRETORIA     PIC  X(003).
           03 FPA0513-CD-DEPARTAMENTO  PIC  X(003).
           03 FPA0513-MT-FUNCIONARIO   PIC  9(006).
           03 FPA0513-VL-SALARIO       PIC S9(013)V9(002).

E suponha que pretendemos gerar o seguinte relatório…

EMPRESA XYZ                                                       DATA: 16/01/17
FOLHA PAGTO                                                       HORA: 09:59:56
FPL0514              SALARIOS POR DIRETORIA E DEPARTAMENTO        PAGINA:      1
--------------------------------------------------------------------------------
DIRETORIA      DEPARTAMENTO      FUNCIONARIO                             SALARIO
--------------------------------------------------------------------------------
ENS            ACD               055314                                17.910,00
                                 073415                                 8.230,00
                                 081873                                 8.244,00
                                                                   ------------
                                             TOTAL DO DEPARTAMENTO:    34.384,00

               SEC               097341                                 2.560,00
                                                                    ------------
                                             TOTAL DO DEPARTAMENTO:     2.560,00

--------------------------------------------------------------------------------
                                                TOTAL DA DIRETORIA:    36.944,00
--------------------------------------------------------------------------------

NEG            MKT               056321                                17.734,00
                                 109314                                 3.645,00
                                                                    ------------
                                             TOTAL DO DEPARTAMENTO:    21.379,00

--------------------------------------------------------------------------------
                                                TOTAL DA DIRETORIA:    21.379,00
--------------------------------------------------------------------------------

OPE            ADM               064321                                17.408,00
                                 103445                                 3.200,00
                                 109346                                 1.005,00
                                                                    ------------
                                             TOTAL DO DEPARTAMENTO:    21.613,00

               FIN               065663                                17.410,00
                                 101230                                 5.600,00
                                                                    ------------
                                             TOTAL DO DEPARTAMENTO:    23.010,00


               INF               090816                                11.250,00
                                 081715                                12.750,00
                                 099920                                 7.300,00
                                                                   ------------
                                             TOTAL DO DEPARTAMENTO:    31.300,00

--------------------------------------------------------------------------------
                                                TOTAL DA DIRETORIA:    75.923,00
--------------------------------------------------------------------------------

Observe que, desta vez, queremos totalizar os valores por dois campos de controle: código da diretoria e código do departamento, e que esses códigos são mostrados apenas nas primeiras linhas em que aparecem.

O algoritmo estruturado que vamos usar é muito semelhante ao que usamos no exemplo anterior. Ele tem um loop a mais de controle justamente para fazer o segundo nível de quebra:

Cobol: Diagrama estruturado do programa
Figura 40. Diagrama estruturado do programa FPP0514

O parágrafo 2-PROCESSA será executado até o final do arquivo de entrada. Ele salvará o código da diretoria, processará todos os salários dessa diretoria e imprimirá o total.

O Parágrafo 21-PROCESSA-DIRETORIA será executado até o fim do arquivo de entrada OU até que seja lido um registro com código de diretoria diferente do anterior. Ele salvará o código do departamento, processará todos os salários desse departamento e imprimirá o total.

O parágrafo 211-PROCESSA-DEPARTAMENTO vai imprimir e acumular os salários em duas variáveis: uma será usada para totalizar os salários por departamento, a outra para totalizar os salários por diretoria.

Para quebrar o relatório por código de diretoria e código de departamento precisamos classificar o arquivo de entrada por esses dois campos. O detalhamento do arquivo temporário na FILE SECTION só precisa desses dois campos. O FILLER PIC X(021) garante que os registros do arquivo temporário terão os mesmos tamanhos dos registros de entrada:

*================================================================*
DATA DIVISION.
*----------------------------------------------------------------*
FILE SECTION.

* SALARIOS
COPY FPF0513.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO
SD SRT0514.
01 SRT0514-REGISTRO.
   03 SRT0514-CD-DIRETORIA     PIC X(003).
   03 SRT0514-CD-DEPARTAMENTO  PIC X(003).
   03 FILLER                   PIC X(021).

* RELATORIO DE SALARIOS POR DIRETORIA E DEPARTAMENTO
FD FPL0514.
01 FPL0514-REGISTRO             PIC  X(080).

Ao declarar as variáveis de trabalho, precisaremos de duas variáveis de controle e dois acumuladores:

*================================================================*
 WORKING-STORAGE SECTION.
*----------------------------------------------------------------*
 01 WC-CONSTANTES.
    03 WC-LINHAS-POR-PAGINA     PIC  9(002) VALUE 60.

 01 WT-FILE-STATUS.
    03 WT-ST-FPA0513            PIC  X(002) VALUE SPACES.

 01 WT-CONTADORES.
    03 WT-CT-PAGINA             PIC  9(003) VALUE ZEROS.
    03 WT-CT-LINHAS             PIC  9(002) VALUE 99.

 01 WT-ACUMULADORES.
    03 WT-AC-DIRETORIA          PIC S9(013)V9(002) VALUE ZEROS.
    03 WT-AC-DEPARTAMENTO       PIC S9(013)V9(002) VALUE ZEROS.

 01 WT-AUXILIARES.
    03 WT-DT-SISTEMA.
       05 ANO                   PIC 9(002) VALUE ZEROS.
       05 MES                   PIC 9(002) VALUE ZEROS.
       05 DIA                   PIC 9(002) VALUE ZEROS.

    03 WT-HR-SISTEMA.
       05 HORA                  PIC 9(002) VALUE ZEROS.
       05 MINUTO                PIC 9(002) VALUE ZEROS.
       05 SEGUNDO               PIC 9(002) VALUE ZEROS.

 01 WT-CAMPOS-CONTROLE.
    03 WT-CTL-CD-DIRETORIA      PIC X(003) VALUE SPACES.
    03 WT-CTL-CD-DEPARTAMENTO   PIC X(003) VALUE SPACES.

Na definição do relatório precisamos definir as duas linhas de total que aparecem no layout:

       01 WR-ROD2.
           03  FILLER                  PIC X(045) VALUE SPACES.
           03  FILLER                  PIC X(023) VALUE
               "TOTAL DO DEPARTAMENTO: ".
           03  WR-ROD-AC-DEPARTAMENTO  PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

       01 WR-ROD3.
           03  FILLER                  PIC X(048) VALUE SPACES.
           03  FILLER                  PIC X(020) VALUE
               "TOTAL DA DIRETORIA: ".
           03  WR-ROD-AC-DIRETORIA     PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

A PROCEDURE começa seguindo o modelo padrão. Como no exemplo anterior, a classificação do arquivo de entrada acontece no parágrafo 1-INICIA, antes da abertura dos arquivos. Mas desta vez o comando SORT ordena o arquivo pelos dois campos de controle:

*================================================================*
 PROCEDURE DIVISION.
*----------------------------------------------------------------*
 0-PRINCIPAL.

     PERFORM 1-INICIA
     PERFORM 2-PROCESSA UNTIL WT-ST-FPA0513 NOT = "00"
     PERFORM 3-TERMINA

     STOP RUN.

*----------------------------------------------------------------*
* CLASSIFICA O ARQUIVO DE ENTRADA POR CODIGO DA DIRETORIA E CODI-
* GO DO DEPARTAMENTO. EM SEGUIDA, ABRE OS ARQUIVOS E FAZ A PRI-
* MEIRA LEITURA DO ARQUIVO DE ENTRADA
*----------------------------------------------------------------*
 1-INICIA.

     SORT SRT0514
           ON ASCENDING KEY SRT0514-CD-DIRETORIA
                            SRT0514-CD-DEPARTAMENTO
           USING FPA0513
           GIVING FPA0513

     OPEN INPUT FPA0513
     OPEN OUTPUT FPL0514
     READ FPA0513.

O parágrafo 2-PROCESSA salva o código da diretoria e processa todos os salários dessa diretoria até o fim do arquivo ou a leitura de um registro cujo código da diretoria seja diferente do código anterior. Esse código é movido nesse momento para a linha de detalhe pois só queremos que ele apareça uma vez. Depois que todos os salários de uma diretoria são impressos, o programa chama o parágrafo 22-IMPRIME-TOTAL-DIRETORIA.

*----------------------------------------------------------------*
* SALVA O CODIGO DA DIRETORIA E IMPRIME TODOS OS DEPARTAMENTOS
* DESSA DIRETORIA. EM SEGUIDA IMPRIME O TOTAL DE SALARIOS DA
* DIRETORIA
*----------------------------------------------------------------*
 2-PROCESSA.

     MOVE FPA0513-CD-DIRETORIA TO WT-CTL-CD-DIRETORIA 
     MOVE FPA0513-CD-DIRETORIA TO WR-DET-CD-DIRETORIA
     MOVE ZEROS TO WT-AC-DIRETORIA

     PERFORM 21-PROCESSA-DIRETORIA
               UNTIL WT-ST-FPA0513 NOT = "00" OR
                     FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA

     PERFORM 22-IMPRIME-TOTAL-DIRETORIA.

O parágrafo 21-PROCESSA-DIRETORIA será responsável por controlar o segundo nível de quebra. Ele salvará o código do departamento e moverá esse código para a linha de detalhe nesse momento pois também só queremos que ele apareça uma vez. Logo em seguida executará o parágrafo que lista os salários do departamento até que: (1) não haja mais registros no arquivo de entrada, ou (2) leia um código de diretoria diferente do código anterior, ou (3) leia um código de departamento diferente do código anterior. Se uma dessas condições acontecer o programa imprimirá o total de salários por departamento:

*----------------------------------------------------------------*
* SALVA O CODIGO DO DEPARTAMENTO E IMPRIME TODOS OS SALARIOS DES-
* SE DEPARTAMENTO. EM SEGUIDA, IMPRIME O TOTAL DE SALARIOS DO
* DEPARTAMENTO
*----------------------------------------------------------------*
 21-PROCESSA-DIRETORIA.

     MOVE FPA0513-CD-DEPARTAMENTO TO WT-CTL-CD-DEPARTAMENTO
     MOVE FPA0513-CD-DEPARTAMENTO TO WR-DET-CD-DEPARTAMENTO
     MOVE ZEROS TO WT-AC-DEPARTAMENTO

     PERFORM 211-PROCESSA-DEPARTAMENTO
             UNTIL WT-ST-FPA0513 NOT = "00" OR
                   FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA OR
                   FPA0513-CD-DEPARTAMENTO NOT = WT-CTL-CD-DEPARTAMENTO

     PERFORM 212-IMPRIME-TOTAL-DEPARTAMENTO.

Em seguida precisamos codificar o parágrafo que imprime o total de salários por diretoria…

*----------------------------------------------------------------*
* IMPRIME TOTAL DE SALARIOS DA DIRETORIA
*----------------------------------------------------------------*
 22-IMPRIME-TOTAL-DIRETORIA.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     MOVE WT-AC-DIRETORIA TO WR-ROD-AC-DIRETORIA

     WRITE FPL0514-REGISTRO FROM WR-SEP1
     WRITE FPL0514-REGISTRO FROM WR-ROD3
     WRITE FPL0514-REGISTRO FROM WR-SEP1 BEFORE 2

     ADD 4 TO WT-CT-LINHAS.

A impressão das linhas de detalhe acontece no parágrafo 211-PROCESSA-DEPARTAMENTO.  O salário que foi impresso é somado a dois acumuladores porque teremos duas linhas de totalização no relatório. Depois que uma linha é impressa, movemos espaços para o código da diretoria e para o código de departamento porque só queremos que eles apareçam uma vez.

*----------------------------------------------------------------*
* IMPRIME SALARIOS DOS FUNCIONARIOS DE DETERMINADO DEPARTAMENTO
*----------------------------------------------------------------*
 211-PROCESSA-DEPARTAMENTO.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     MOVE FPA0513-MT-FUNCIONARIO TO WR-DET-MT-FUNCIONARIO
     MOVE FPA0513-VL-SALARIO TO WR-DET-VL-SALARIO

     WRITE FPL0514-REGISTRO FROM WR-DET1
     ADD 1 TO WT-CT-LINHAS
     ADD FPA0513-VL-SALARIO TO WT-AC-DIRETORIA
     ADD FPA0513-VL-SALARIO TO WT-AC-DEPARTAMENTO

     MOVE SPACES TO WR-DET-CD-DIRETORIA
     MOVE SPACES TO WR-DET-CD-DEPARTAMENTO

     READ FPA0513.

Os últimos parágrafos completam o programa e serão usados para imprimir o total de salários por departamento e o cabeçalho no topo de cada página, sem diferenças em relação ao programa do exemplo anterior:

*----------------------------------------------------------------*
* IMPRIME TOTAL DE SALARIOS DO DEPARTAMENTO
*----------------------------------------------------------------*
 212-IMPRIME-TOTAL-DEPARTAMENTO.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     MOVE WT-AC-DEPARTAMENTO TO WR-ROD-AC-DEPARTAMENTO

     WRITE FPL0514-REGISTRO FROM WR-ROD1
     WRITE FPL0514-REGISTRO FROM WR-ROD2 BEFORE 2
     ADD 3 TO WT-CT-LINHAS.

Resumindo, cada nível de quebra precisa de um parágrafo que:

  • Salve o campo de controle numa variável de trabalho
  • Zere o contador ou acumulador desse campo de controle, se houver
  • Execute um comando PERFORM até o fim do arquivo de entrada ou até que ocorra uma quebra do campo de controle atual ou dos campos de controle anteriores
  • Imprima, se houver, contadores ou acumuladores do campo de controle que acabou de quebrar

Seguindo essa estrutura, podemos construir programas que façam quebras em três, quatro, cinco… ou quantos níveis forem necessários.  O programa FPP0514 completo é mostrado abaixo:

*================================================================*
 IDENTIFICATION DIVISION.
*----------------------------------------------------------------*
PROGRAM-ID.    FPP0514.
AUTHOR.        PAULO ANDRE DIAS.
DATE-WRITTEN.  16/01/2017.
REMARKS.
*----------------------------------------------------------------*
* SISTEMA:      FP - FOLHA DE PAGAMENTO
* JOB:          05 - RELATORIOS
* PROGRAMA:     14 - SALARIOS POR DEPARTAMENTO
*
* OBJETIVO:     LER O ARQUIVO DE SALARIOS E GERAR RELATORIO TO-
*               TALIZANDO POR DIRETORIA E DEPARTAMENTO
*
* VERSOES:      DATA    DESCRICAO
*               ------  ---------------------------------------
*               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*
*----------------------------------------------------------------*

*================================================================*
 ENVIRONMENT DIVISION.
*----------------------------------------------------------------*
 CONFIGURATION SECTION.
 SPECIAL-NAMES.
 DECIMAL-POINT IS COMMA.

 INPUT-OUTPUT SECTION.
 FILE-CONTROL.

* SALARIOS
 COPY FPS0513.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO
 SELECT SRT0514 ASSIGN TO "$DAT/SRT0514.DAT"
 ORGANIZATION IS LINE SEQUENTIAL.

* RELATORIO DE SALARIOS POR DIRETORIA E DEPARTAMENTO
 SELECT FPL0514 ASSIGN TO "$DAT/FPL0514.TXT"
 ORGANIZATION IS LINE SEQUENTIAL.

*================================================================*
 DATA DIVISION.
*----------------------------------------------------------------*
 FILE SECTION.

* SALARIOS
 COPY FPF0513.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO
 SD SRT0514.
 01 SRT0514-REGISTRO.
    03 SRT0514-CD-DIRETORIA     PIC X(003).
    03 SRT0514-CD-DEPARTAMENTO  PIC X(003).
    03 FILLER                   PIC X(021).

* RELATORIO DE SALARIOS POR DIRETORIA E DEPARTAMENTO
 FD FPL0514.
 01 FPL0514-REGISTRO             PIC  X(080).

*================================================================*
 WORKING-STORAGE SECTION.
*----------------------------------------------------------------*
 01 WC-CONSTANTES.
    03 WC-LINHAS-POR-PAGINA     PIC  9(002) VALUE 60.

 01 WT-FILE-STATUS.
    03 WT-ST-FPA0513            PIC  X(002) VALUE SPACES.

 01 WT-CONTADORES.
    03 WT-CT-PAGINA             PIC  9(003) VALUE ZEROS.
    03 WT-CT-LINHAS             PIC  9(002) VALUE 99.

01 WT-ACUMULADORES.
   03 WT-AC-DIRETORIA          PIC S9(013)V9(002) VALUE ZEROS.
   03 WT-AC-DEPARTAMENTO       PIC S9(013)V9(002) VALUE ZEROS.

01 WT-AUXILIARES.
   03 WT-DT-SISTEMA.
      05 ANO                   PIC 9(002) VALUE ZEROS.
      05 MES                   PIC 9(002) VALUE ZEROS.
      05 DIA                   PIC 9(002) VALUE ZEROS.
   03 WT-HR-SISTEMA.
      05 HORA                  PIC 9(002) VALUE ZEROS.
      05 MINUTO                PIC 9(002) VALUE ZEROS.
      05 SEGUNDO               PIC 9(002) VALUE ZEROS.

01 WT-CAMPOS-CONTROLE.
   03 WT-CTL-CD-DIRETORIA      PIC X(003) VALUE SPACES.
   03 WT-CTL-CD-DEPARTAMENTO   PIC X(003) VALUE SPACES.

01 WR-CAB1.
   03 FILLER                   PIC X(066) VALUE
      "EMPRESA XYZ".
   03 FILLER                   PIC X(006) VALUE
      "DATA: ".
   03 WR-CAB-DATA.
      05 DIA                   PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE "/".
      05 MES                   PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE "/".
      05 ANO                   PIC 9(002) VALUE ZEROS.

01 WR-CAB2.
   03 FILLER                   PIC X(066) VALUE
      "FOLHA PAGTO".
   03 FILLER                   PIC X(006) VALUE
      "HORA: ".
   03 WR-CAB-HORA.
      05 HORA                  PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE ":".
      05 MINUTO                PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE ":".
      05 SEGUNDO               PIC 9(002) VALUE ZEROS.

01 WR-CAB3.
   03 FILLER                   PIC X(021) VALUE
      "FPL0514".
   03 FILLER                   PIC X(045) VALUE
      "SALARIOS POR DIRETORIA E DEPARTAMENTO".
   03 FILLER                   PIC X(011) VALUE
      "PAGINA: ".
   03 WR-CAB-PAGINA            PIC ZZ9 VALUE
      ZEROS.

 01 WR-SEP1.
    03  FILLER                  PIC X(080) VALUE ALL "-".

 01 WR-CAB4.
    03  FILLER                  PIC X(015) VALUE "DIRETORIA".
    03  FILLER                  PIC X(018) VALUE "DEPARTAMENTO".
    03  FILLER                  PIC X(040) VALUE "FUNCIONARIO".
    03  FILLER                  PIC X(007) VALUE "SALARIO".

 01 WR-DET1.
    03  WR-DET-CD-DIRETORIA     PIC X(003) VALUE SPACES.
    03  FILLER                  PIC X(012) VALUE SPACES.
    03  WR-DET-CD-DEPARTAMENTO  PIC X(003) VALUE SPACES.
    03  FILLER                  PIC X(015) VALUE SPACES.
    03  WR-DET-MT-FUNCIONARIO   PIC 9(006) VALUE ZEROS.
    03  FILLER                  PIC X(031) VALUE SPACES.
    03  WR-DET-VL-SALARIO       PIC ZZZ.ZZ9,99 VALUE ZEROS.

 01 WR-ROD1.
    03  FILLER                  PIC X(068) VALUE SPACES.
    03  FILLER                  PIC X(012) VALUE ALL "-".

 01 WR-ROD2.
    03  FILLER                  PIC X(045) VALUE SPACES.
    03  FILLER                  PIC X(023) VALUE
        "TOTAL DO DEPARTAMENTO: ".
    03  WR-ROD-AC-DEPARTAMENTO  PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

 01 WR-ROD3.
    03  FILLER                  PIC X(048) VALUE SPACES.
    03  FILLER                  PIC X(020) VALUE
        "TOTAL DA DIRETORIA: ".
    03  WR-ROD-AC-DIRETORIA     PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

*================================================================*
 PROCEDURE DIVISION.
*----------------------------------------------------------------*
 0-PRINCIPAL.

     PERFORM 1-INICIA
     PERFORM 2-PROCESSA UNTIL WT-ST-FPA0513 NOT = "00"
     PERFORM 3-TERMINA

     STOP RUN.

*----------------------------------------------------------------*
* CLASSIFICA O ARQUIVO DE ENTRADA POR CODIGO DA DIRETORIA E CODI-
* GO DO DEPARTAMENTO. EM SEGUIDA, ABRE OS ARQUIVOS E FAZ A PRI-
* MEIRA LEITURA DO ARQUIVO DE ENTRADA
*----------------------------------------------------------------*
 1-INICIA.

     SORT SRT0514
          ON ASCENDING KEY SRT0514-CD-DIRETORIA
                           SRT0514-CD-DEPARTAMENTO
          USING FPA0513
          GIVING FPA0513

     OPEN INPUT FPA0513
     OPEN OUTPUT FPL0514

     READ FPA0513.

*----------------------------------------------------------------*
* SALVA O CODIGO DA DIRETORIA E IMPRIME TODOS OS DEPARTAMENTOS
* DESSA DIRETORIA. EM SEGUIDA IMPRIME O TOTAL DE SALARIOS DA
* DIRETORIA
*----------------------------------------------------------------*
 2-PROCESSA.

     MOVE FPA0513-CD-DIRETORIA TO WT-CTL-CD-DIRETORIA
     MOVE FPA0513-CD-DIRETORIA TO WR-DET-CD-DIRETORIA
     MOVE ZEROS TO WT-AC-DIRETORIA

     PERFORM 21-PROCESSA-DIRETORIA
       UNTIL WT-ST-FPA0513 NOT = "00" OR
             FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA

     PERFORM 22-IMPRIME-TOTAL-DIRETORIA.

*----------------------------------------------------------------*
* FECHA ARQUIVOS E ZERA O RETURN-CODE
*----------------------------------------------------------------*
 3-TERMINA.

     CLOSE FPA0513 FPL0514

     MOVE ZEROS TO RETURN-CODE.

*----------------------------------------------------------------*
* SALVA O CODIGO DO DEPARTAMENTO E IMPRIME TODOS OS SALARIOS DES-
* SE DEPARTAMENTO. EM SEGUIDA, IMPRIME O TOTAL DE SALARIOS DO
* DEPARTAMENTO
*----------------------------------------------------------------*
 21-PROCESSA-DIRETORIA.

     MOVE FPA0513-CD-DEPARTAMENTO TO WT-CTL-CD-DEPARTAMENTO
     MOVE FPA0513-CD-DEPARTAMENTO TO WR-DET-CD-DEPARTAMENTO
     MOVE ZEROS TO WT-AC-DEPARTAMENTO

     PERFORM 211-PROCESSA-DEPARTAMENTO
       UNTIL WT-ST-FPA0513 NOT = "00" OR
             FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA OR
             FPA0513-CD-DEPARTAMENTO NOT = WT-CTL-CD-DEPARTAMENTO

     PERFORM 212-IMPRIME-TOTAL-DEPARTAMENTO.

*----------------------------------------------------------------*
* IMPRIME TOTAL DE SALARIOS DA DIRETORIA
*----------------------------------------------------------------*
 22-IMPRIME-TOTAL-DIRETORIA.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     MOVE WT-AC-DIRETORIA TO WR-ROD-AC-DIRETORIA

     WRITE FPL0514-REGISTRO FROM WR-SEP1
     WRITE FPL0514-REGISTRO FROM WR-ROD3
     WRITE FPL0514-REGISTRO FROM WR-SEP1 BEFORE 2

     ADD 4 TO WT-CT-LINHAS.

*----------------------------------------------------------------*
* IMPRIME SALARIOS DOS FUNCIONARIOS DE DETERMINADO DEPARTAMENTO
*----------------------------------------------------------------*
 211-PROCESSA-DEPARTAMENTO.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     MOVE FPA0513-MT-FUNCIONARIO TO WR-DET-MT-FUNCIONARIO
     MOVE FPA0513-VL-SALARIO TO WR-DET-VL-SALARIO

     WRITE FPL0514-REGISTRO FROM WR-DET1
     ADD 1 TO WT-CT-LINHAS
     ADD FPA0513-VL-SALARIO TO WT-AC-DIRETORIA
     ADD FPA0513-VL-SALARIO TO WT-AC-DEPARTAMENTO

     MOVE SPACES TO WR-DET-CD-DIRETORIA
     MOVE SPACES TO WR-DET-CD-DEPARTAMENTO

     READ FPA0513.

*----------------------------------------------------------------*
* IMPRIME TOTAL DE SALARIOS DO DEPARTAMENTO
*----------------------------------------------------------------*
 212-IMPRIME-TOTAL-DEPARTAMENTO.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     MOVE WT-AC-DEPARTAMENTO TO WR-ROD-AC-DEPARTAMENTO

     WRITE FPL0514-REGISTRO FROM WR-ROD1
     WRITE FPL0514-REGISTRO FROM WR-ROD2 BEFORE 2
     ADD 3 TO WT-CT-LINHAS.

*----------------------------------------------------------------*
* IMPRIME CABECALHO
*----------------------------------------------------------------*
X1-IMPRIME-CABECALHO.

     ACCEPT WT-DT-SISTEMA FROM DATE
     ACCEPT WT-HR-SISTEMA FROM TIME
     ADD 1 TO WT-CT-PAGINA

     MOVE CORR WT-DT-SISTEMA TO WR-CAB-DATA
     MOVE CORR WT-HR-SISTEMA TO WR-CAB-HORA
     MOVE WT-CT-PAGINA TO WR-CAB-PAGINA

     WRITE FPL0514-REGISTRO FROM WR-CAB1 AFTER PAGE
     WRITE FPL0514-REGISTRO FROM WR-CAB2
     WRITE FPL0514-REGISTRO FROM WR-CAB3
     WRITE FPL0514-REGISTRO FROM WR-SEP1
     WRITE FPL0514-REGISTRO FROM WR-CAB4
     WRITE FPL0514-REGISTRO FROM WR-SEP1

     MOVE 6 TO WT-CT-LINHAS.

Agrupamento

Os algoritmos de quebra que vimos até aqui nos permitiram exibir informações ordenadas por um ou mais campos de controle. Em muitas situações, porém, não queremos (ou não precisamos) de informações detalhadas, e sim de um sumário dessas informações.

A estrutura de um programa que sumariza ou acumula informações por um ou mais campos de controle é praticamente a mesma que usamos nos programas de quebra.

Para mostrar como o agrupamento funciona, criaremos um programa chamado FPP0515 que inicialmente será uma cópia do nosso último exemplo, FPP0514. Só que dessa vez, queremos um relatório que mostre apenas a média salarial de cada diretoria/departamento:

EMPRESA XYZ                                                       DATA: 99/99/99
FOLHA PAGTO                                                       HORA: 99:99:99
FPL0514         MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO       PAGINA:    ZZ9
--------------------------------------------------------------------------------
DIRETORIA      DEPARTAMENTO                                        SALARIO MEDIO
--------------------------------------------------------------------------------
XXX            XXX                                                    ZZZ.ZZ9,99
XXX                                                    ZZZ.ZZ9,99
XXX                                                    ZZZ.ZZ9,99
------------
MEDIA SALARIAL DA DIRETORIA:  ZZZ.ZZ9,99

XXX            XXX                                                    ZZZ.ZZ9,99
XXX                                                    ZZZ.ZZ9,99
------------
MEDIA SALARIAL DA DIRETORIA:  ZZZ.ZZ9,99

XXX            XXX                                                    ZZZ.ZZ9,99
XXX                                                    ZZZ.ZZ9,99
XXX                                                    ZZZ.ZZ9,99
XXX                                                    ZZZ.ZZ9,99
XXX                                                    ZZZ.ZZ9,99
------------
MEDIA SALARIAL DA DIRETORIA:  ZZZ.ZZ9,99

Depois de copiar do programa, vamos destacar apenas as alterações necessárias para transformá-lo num programa de agrupamento. A primeira coisa que precisamos fazer, naturalmente, é alterar a IDENTIFICATION DIVISION:

*================================================================*
 IDENTIFICATION DIVISION.
*----------------------------------------------------------------*
 PROGRAM-ID.    FPP0515.
 AUTHOR.        PAULO ANDRE DIAS.
 DATE-WRITTEN.  17/01/2017.
 REMARKS.
*----------------------------------------------------------------*
* SISTEMA:      FP - FOLHA DE PAGAMENTO
* JOB:          05 - RELATORIOS
* PROGRAMA:     15 - MEDIA SALARIAL POR DEPARTAMENTO
*
* OBJETIVO:     LER O ARQUIVO DE SALARIOS E GERAR RELATORIO MOS-
*               TRANDO A MEDIA SALARIAL DE CADA DEPARTAMENTO E
*               DIRETORIA
*
* VERSOES:      DATA    DESCRICAO
*               ------  ---------------------------------------
*               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*
*----------------------------------------------------------------*

O arquivo de entrada será o mesmo, FPA0513, e sua classificação também será igual, pois os salários serão sumarizados por diretoria e departamento, como no programa anterior. Na ENVIRONMENT e na DATA DIVISION alteraremos apenas o nome do arquivo temporário e o nome do relatório. Faremos isso com um único find & replace, disponível em qualquer editor de programas, substituindo “0514” para “0515”. Assim atualizamos de uma só vez tanto a ENVIRONMENT e a DATA quanto a PROCEDURE DIVISION:

*================================================================*
 ENVIRONMENT DIVISION.
*----------------------------------------------------------------*
 CONFIGURATION SECTION.
 SPECIAL-NAMES.
 DECIMAL-POINT IS COMMA.

 INPUT-OUTPUT SECTION.
 FILE-CONTROL.

* SALARIOS
 COPY FPS0513.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO
 SELECT SRT0515 ASSIGN TO "$DAT/SRT0515.DAT"
 ORGANIZATION IS LINE SEQUENTIAL.

* MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO        
 SELECT FPL0515 ASSIGN TO "$DAT/FPL0515.TXT"
 ORGANIZATION IS LINE SEQUENTIAL.

*================================================================*
 DATA DIVISION.
*----------------------------------------------------------------*
 FILE SECTION.

* SALARIOS
 COPY FPF0513.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO
 SD SRT0515.
 01 SRT0515-REGISTRO.
    03 SRT0515-CD-DIRETORIA     PIC X(003).
    03 SRT0515-CD-DEPARTAMENTO  PIC X(003).
    03 FILLER                   PIC X(021).

* MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO
 FD FPL0515.
 01 FPL0515-REGISTRO             PIC  X(080).

O novo programa usará os mesmos acumuladores, só que agora para calcular a média (e não o somatório) dos salários de cada departamento. Nós vamos fazer isso somando os salários de cada registro lido e depois dividindo essa soma pela quantidade de salários lidos até a quebra. Precisamos criar dois novos contadores na WORKING: um para a quantidade de salários de cada diretoria e outro para a quantidade de salários de cada departamento:

01 WT-CONTADORES.
   03 WT-CT-PAGINA             PIC  9(003) VALUE ZEROS.
   03 WT-CT-LINHAS             PIC  9(002) VALUE 99.
   03 WT-CT-DIRETORIA          PIC  9(006) VALUE ZEROS.
   03 WT-CT-DEPARTAMENTO       PIC  9(006) VALUE ZEROS.

O relatório não precisa mais mostrar o salário de cada funcionário. A linha de detalhe mostrará a média de salários ao lado do código de cada departamento. O código da diretoria só será exibido na primeira linha em que aparecer, e o único rodapé que teremos mostrará agora a média salarial de a cada quebra de diretoria.

Precisamos alterar o título do relatório, obviamente atualizando o tamanho do FILLER anterior para que esse título continue centralizado no cabeçalho:

01 WR-CAB3.
   03 FILLER                   PIC X(018) VALUE 
      "FPL0515".
   03 FILLER                   PIC X(048) VALUE
      "MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO".
   03 FILLER                   PIC X(011) VALUE
     "PAGINA: ".
   03 WR-CAB-PAGINA            PIC ZZ9 VALUE
      ZEROS.

Podemos remover o título da coluna que mostrava a matrícula dos funcionários. Também alteraremos a picture do FILLER anterior para que a média salarial de cada departamento continue na margem direita da página

01 WR-CAB4.
   03  FILLER                  PIC X(015) VALUE "DIRETORIA".
   03  FILLER                  PIC X(052) VALUE "DEPARTAMENTO".
   03  FILLER                  PIC X(040) VALUE "FUNCIONARIO".
   03  FILLER                  PIC X(013) VALUE "SALARIO MEDIO".

Faremos o mesmo na linha de detalhe…

01 WR-DET1.
   03  WR-DET-CD-DIRETORIA     PIC X(003) VALUE SPACES.
   03  FILLER                  PIC X(012) VALUE SPACES.
   03  WR-DET-CD-DEPARTAMENTO  PIC X(003) VALUE SPACES.
   03  FILLER                  PIC X(052) VALUE SPACES.
   03  WR-DET-MT-FUNCIONARIO   PIC 9(006) VALUE ZEROS.  
   03  FILLER                  PIC X(031) VALUE SPACES.
   03  WR-DET-VL-SALARIO       PIC ZZZ.ZZ9,99 VALUE ZEROS.

E atualizamos o rodapé para refletir o novo layout:

01 WR-ROD1.
   03  FILLER                  PIC X(068) VALUE SPACES.
   03  FILLER                  PIC X(012) VALUE ALL "-".

01 WR-ROD2.
   03  FILLER                  PIC X(039) VALUE SPACES.
   03  FILLER                  PIC X(029) VALUE
       "MEDIA SALARIAL DA DIRETORIA: ".
   03  WR-ROD-AC-DIRETORIA     PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

01 WR-ROD3.
   03  FILLER                  PIC X(048) VALUE SPACES.
   03  FILLER                  PIC X(020) VALUE
       "TOTAL DA DIRETORIA: ".
   03  WR-ROD-AC-DIRETORIA     PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

Em 2-PROCESSA vamos apenas alterar a linha de comentários e o nome do parágrafo que antes imprimia o total de salários. Além de zerar o acumulador de salários da diretoria, agora precisamos zerar também o contador de salários:

*----------------------------------------------------------------*
* SALVA O CODIGO DA DIRETORIA E IMPRIME TODOS OS DEPARTAMENTOS
* DESSA DIRETORIA. EM SEGUIDA IMPRIME A MEDIA SALARIAL DA DI-
* RETORIA
*----------------------------------------------------------------*
 2-PROCESSA.

     MOVE FPA0513-CD-DIRETORIA TO WT-CTL-CD-DIRETORIA
     MOVE FPA0513-CD-DIRETORIA TO WR-DET-CD-DIRETORIA
     MOVE ZEROS TO WT-CT-DIRETORIA
     MOVE ZEROS TO WT-AC-DIRETORIA

     PERFORM 21-PROCESSA-DIRETORIA
       UNTIL WT-ST-FPA0513 NOT = "00" OR
             FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA

PERFORM 22-IMPRIME-MEDIA-DIRETORIA.

E faremos praticamente a mesma coisa em 21-PROCESSA-DIRETORIA:

*----------------------------------------------------------------*
* SALVA O CODIGO DO DEPARTAMENTO, PROCESSA TODOS OS SALARIOS DES-
* SE DEPARTAMENTO. EM SEGUIDA, IMPRIME A MEDIA DE SALARIOS DO
* DEPARTAMENTO
*----------------------------------------------------------------*
 21-PROCESSA-DIRETORIA.

     MOVE FPA0513-CD-DEPARTAMENTO TO WT-CTL-CD-DEPARTAMENTO
     MOVE FPA0513-CD-DEPARTAMENTO TO WR-DET-CD-DEPARTAMENTO
     MOVE ZEROS TO WT-CT-DEPARTAMENTO
     MOVE ZEROS TO WT-AC-DEPARTAMENTO

     PERFORM 211-PROCESSA-DEPARTAMENTO
       UNTIL WT-ST-FPA0513 NOT = "00" OR
             FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA OR
             FPA0513-CD-DEPARTAMENTO NOT = WT-CTL-CD-DEPARTAMENTO

     PERFORM 212-IMPRIME-MEDIA-DEPARTAMENTO.

Como veremos um pouco mais adiante, o parágrafo 211-PROCESSA-DEPARTAMENTO não imprimirá mais nada no relatório. Ele vai apenas acumular os salários lidos nas variáveis WT-AC-DIRETORIA e WT-AC-DEPARTAMENTO e contar os salários lidos nas variáveis WT-CT-DIRETORIA e WT-CT-DEPARTAMENTO. A média salarial, tanto da diretoria quanto do departamento, será calculada no momento em que for impressa. E é isso que faremos agora no parágrafo 22-IMPRIME-MEDIA-DIRETORIA usando o comando DIVIDE. Também precisamos alterar as linhas que serão exibidas no rodapé para refletir o novo layout do relatório:

*----------------------------------------------------------------*
* IMPRIME MEDIA DE SALARIOS DA DIRETORIA
*----------------------------------------------------------------*
 22-IMPRIME-MEDIA-DIRETORIA.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     DIVIDE WT-CT-DIRETORIA INTO WT-AC-DIRETORIA
     MOVE WT-AC-DIRETORIA TO WR-ROD-AC-DIRETORIA

     WRITE FPL0514-REGISTRO FROM WR-SEP1
     WRITE FPL0514-REGISTRO FROM WR-ROD3
     WRITE FPL0514-REGISTRO FROM WR-SEP1 BEFORE 2
     ADD 4 TO WT-CT-LINHAS.

Aqui vale relembrar alguns detalhes sobre operações aritméticas em COBOL. Existem vários formatos possíveis para o comando DIVIDE. Esse que usamos…

DIVIDE variavel1 INTO variavel2

Faz a mesma coisa que…

DIVIDE variavel2 BY variavel1 GIVING variavel2

Ou…

COMPUTE variavel2 = variavel2 / variavel1

Uma dúvida que pode ter surgido nesse momento é: por que simplesmente não usar a variável WR-ROD-AC-DIRETORIA como destino? Algo parecido com…

DIVIDE WT-AC-DIRETORIA BY WT-CT-DIRETORIA GIVING WR-ROD-AC-DIRETORIA

O COBOL não permitiria essa operação porque, como já mencionamos, a variável WR-ROD-AC-DIRETORIA tem picture editada (ZZZ.ZZ9,99), e variáveis com picture editada só podem ser usadas para exibição; não podem ser usadas em operações aritméticas.

Voltando ao programa…

O parágrafo que antes imprimia a linha detalhe agora só vai contar e acumular os salários do departamento. Por causa disso, o parágrafo 211-PROCESSA-DEPARTAMENTO talvez seja o mais modificado do novo programa: eliminamos os comandos de impressão e incluímos os comandos para contar a quantidade de salários:

*----------------------------------------------------------------*
* CONTA E ACUMULA SALARIOS DE DETERMINADO DEPARTAMENTO
*----------------------------------------------------------------*
 211-PROCESSA-DEPARTAMENTO.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     MOVE FPA0513-MT-FUNCIONARIO TO WR-DET-MT-FUNCIONARIO 
     MOVE FPA0513-VL-SALARIO TO WR-DET-VL-SALARIO
     WRITE FPL0515-REGISTRO FROM WR-DET1
     ADD 1 TO WT-CT-LINHAS

     ADD FPA0513-VL-SALARIO TO WT-AC-DIRETORIA
     ADD FPA0513-VL-SALARIO TO WT-AC-DEPARTAMENTO

     ADD 1 TO WT-CT-DIRETORIA
     ADD 1 TO WT-CT-DEPARTAMENTO

     MOVE SPACES TO WR-DET-CD-DIRETORIA
     MOVE SPACES TO WR-DET-CD-DEPARTAMENTO

     READ FPA0513.

A impressão da linha detalhe agora ficará no parágrafo 22-IMPRIME-MEDIA-DEPARTAMENTO. Calcularemos a média salarial do departamento antes de movê-la para a linha detalhe. Também precisaremos apagar o código da diretoria, porque só queremos que ele apareça na primeira linha em que ele aparecer.

*----------------------------------------------------------------*
* IMPRIME MEDIA DE SALARIOS DO DEPARTAMENTO
*----------------------------------------------------------------*
 212-IMPRIME-MEDIA-DEPARTAMENTO.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     DIVIDE WT-CT-DEPARTAMENTO INTO WT-AC-DEPARTAMENTO
     MOVE WT-AC-DEPARTAMENTO TO WR-DET-VL-SALARIO

    WRITE FPL0515-REGISTRO FROM WR-DET1
    WRITE FPL0515-REGISTRO FROM WR-ROD2 BEFORE 2
    ADD 1 TO WT-CT-LINHAS

    MOVE SPACES TO WR-DET-CD-DIRETORIA.

E com isso transformamos um programa que imprimia salários em um programa que calcula a média de salários, usando as mesmas variáveis de controle, diretoria e departamento:

EMPRESA XYZ                                                       DATA: 17/01/17
FOLHA PAGTO                                                       HORA: 08:44:56
FPL0515           MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO     PAGINA:      1
--------------------------------------------------------------------------------
DIRETORIA      DEPARTAMENTO                                        SALARIO MEDIO
--------------------------------------------------------------------------------
ENS            ACD                                                     11.461,33
SEC                                                      2.560,00
------------
MEDIA SALARIAL DA DIRETORIA:     9.236,00

NEG            MKT                                                     10.689,50
------------
MEDIA SALARIAL DA DIRETORIA:    10.689,50

OPE            ADM                                                      7.204,33
FIN                                                     11.505,00
INF                                                     10.433,33
------------
MEDIA SALARIAL DA DIRETORIA:     9.490,37

Nosso novo programa ficou assim:

*================================================================*
 IDENTIFICATION DIVISION.
*----------------------------------------------------------------*
 PROGRAM-ID.    FPP0515.
 AUTHOR.        PAULO ANDRE DIAS.
 DATE-WRITTEN.  17/01/2017.
 REMARKS.
*----------------------------------------------------------------*
* SISTEMA:      FP - FOLHA DE PAGAMENTO
* JOB:          05 - RELATORIOS
* PROGRAMA:     15 - MEDIA SALARIAL POR DEPARTAMENTO
*
* OBJETIVO:     LER O ARQUIVO DE SALARIOS E GERAR RELATORIO MOS-
*               TRANDO A MEDIA SALARIAL DE CADA DEPARTAMENTO E
*               DIRETORIA
*
* VERSOES:      DATA    DESCRICAO
*               ------  ---------------------------------------
*               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*
*----------------------------------------------------------------*

*================================================================*
 ENVIRONMENT DIVISION.
*----------------------------------------------------------------*
 CONFIGURATION SECTION.
 SPECIAL-NAMES.
 DECIMAL-POINT IS COMMA.

 INPUT-OUTPUT SECTION.
 FILE-CONTROL.

* SALARIOS
 COPY FPS0513.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO
 SELECT SRT0515 ASSIGN TO "$DAT/SRT0515.DAT"
 ORGANIZATION IS LINE SEQUENTIAL.

* MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO
 SELECT FPL0515 ASSIGN TO "$DAT/FPL0515.TXT"
 ORGANIZATION IS LINE SEQUENTIAL.

*================================================================*
 DATA DIVISION.
*----------------------------------------------------------------*
 FILE SECTION.

* SALARIOS
 COPY FPF0513.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO
 SD SRT0515.
 01 SRT0515-REGISTRO.
    03 SRT0515-CD-DIRETORIA     PIC X(003).
    03 SRT0515-CD-DEPARTAMENTO  PIC X(003).
    03 FILLER                   PIC X(021).

* MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO
 FD FPL0515.
 01 FPL0515-REGISTRO             PIC  X(080).

*================================================================*
 WORKING-STORAGE SECTION.
*----------------------------------------------------------------*
 01 WC-CONSTANTES.
    03 WC-LINHAS-POR-PAGINA     PIC  9(002) VALUE 60.

 01 WT-FILE-STATUS.
    03 WT-ST-FPA0513            PIC  X(002) VALUE SPACES.

 01 WT-CONTADORES.
    03 WT-CT-PAGINA             PIC  9(003) VALUE ZEROS.
    03 WT-CT-LINHAS             PIC  9(002) VALUE 99.
    03 WT-CT-DIRETORIA          PIC  9(006) VALUE ZEROS.
    03 WT-CT-DEPARTAMENTO       PIC  9(006) VALUE ZEROS.

 01 WT-ACUMULADORES.
    03 WT-AC-DIRETORIA          PIC S9(013)V9(002) VALUE ZEROS.
    03 WT-AC-DEPARTAMENTO       PIC S9(013)V9(002) VALUE ZEROS.

 01 WT-AUXILIARES.
    03 WT-DT-SISTEMA.
       05 ANO                   PIC 9(002) VALUE ZEROS.
       05 MES                   PIC 9(002) VALUE ZEROS.
       05 DIA                   PIC 9(002) VALUE ZEROS.
    03 WT-HR-SISTEMA.
       05 HORA                  PIC 9(002) VALUE ZEROS.
       05 MINUTO                PIC 9(002) VALUE ZEROS.
       05 SEGUNDO               PIC 9(002) VALUE ZEROS.

 01 WT-CAMPOS-CONTROLE.
    03 WT-CTL-CD-DIRETORIA      PIC X(003) VALUE SPACES.
    03 WT-CTL-CD-DEPARTAMENTO   PIC X(003) VALUE SPACES.

01 WR-CAB1.
   03 FILLER                   PIC X(066) VALUE
      "EMPRESA XYZ".
   03 FILLER                   PIC X(006) VALUE
      "DATA: ".
   03 WR-CAB-DATA.
      05 DIA                   PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE "/".
      05 MES                   PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE "/".
      05 ANO                   PIC 9(002) VALUE ZEROS.

01 WR-CAB2.
   03 FILLER                   PIC X(066) VALUE
      "FOLHA PAGTO".
   03 FILLER                   PIC X(006) VALUE
      "HORA: ".
   03 WR-CAB-HORA.
      05 HORA                  PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE ":".
      05 MINUTO                PIC 9(002) VALUE ZEROS.
      05 FILLER                PIC X(001) VALUE ":".
      05 SEGUNDO               PIC 9(002) VALUE ZEROS.

 01 WR-CAB3.
    03 FILLER                   PIC X(018) VALUE
       "FPL0515".
    03 FILLER                   PIC X(048) VALUE
       "MEDIA SALARIAL POR DIRETORIA E DEPARTAMENTO".
    03 FILLER                   PIC X(011) VALUE
       "PAGINA: ".
    03 WR-CAB-PAGINA            PIC ZZ9 VALUE
       ZEROS.

01 WR-SEP1.
   03  FILLER                  PIC X(080) VALUE ALL "-".

01 WR-CAB4.
   03  FILLER                  PIC X(015) VALUE "DIRETORIA".
   03  FILLER                  PIC X(052) VALUE "DEPARTAMENTO".
   03  FILLER                  PIC X(013) VALUE "SALARIO MEDIO".

01 WR-DET1.
   03  WR-DET-CD-DIRETORIA     PIC X(003) VALUE SPACES.
   03  FILLER                  PIC X(012) VALUE SPACES.
   03  WR-DET-CD-DEPARTAMENTO  PIC X(003) VALUE SPACES.
   03  FILLER                  PIC X(052) VALUE SPACES.
   03  WR-DET-VL-SALARIO       PIC ZZZ.ZZ9,99 VALUE ZEROS.

01 WR-ROD1.
   03  FILLER                  PIC X(068) VALUE SPACES.
   03  FILLER                  PIC X(012) VALUE ALL "-".

01 WR-ROD2.
   03  FILLER                  PIC X(039) VALUE SPACES.
   03  FILLER                  PIC X(029) VALUE
       "MEDIA SALARIAL DA DIRETORIA: ".
   03  WR-ROD-AC-DIRETORIA     PIC Z.ZZZ.ZZ9,99 VALUE ZEROS.

*================================================================*
 PROCEDURE DIVISION.
*----------------------------------------------------------------*
 0-PRINCIPAL.

     PERFORM 1-INICIA
     PERFORM 2-PROCESSA UNTIL WT-ST-FPA0513 NOT = "00"
     PERFORM 3-TERMINA

     STOP RUN.

*----------------------------------------------------------------*
* CLASSIFICA O ARQUIVO DE ENTRADA POR CODIGO DA DIRETORIA E CODI-
* GO DO DEPARTAMENTO. EM SEGUIDA, ABRE OS ARQUIVOS E FAZ A PRI-
* MEIRA LEITURA DO ARQUIVO DE ENTRADA
*----------------------------------------------------------------*
 1-INICIA.

     SORT SRT0515
          ON ASCENDING KEY SRT0515-CD-DIRETORIA
                           SRT0515-CD-DEPARTAMENTO
          USING FPA0513
          GIVING FPA0513

     OPEN INPUT FPA0513
     OPEN OUTPUT FPL0515
   
     READ FPA0513.

*----------------------------------------------------------------*
* SALVA O CODIGO DA DIRETORIA E IMPRIME TODOS OS DEPARTAMENTOS
* DESSA DIRETORIA. EM SEGUIDA IMPRIME A MEDIA SALARIAL DA DI-
* RETORIA
*----------------------------------------------------------------*
 2-PROCESSA.

     MOVE FPA0513-CD-DIRETORIA TO WT-CTL-CD-DIRETORIA
     MOVE FPA0513-CD-DIRETORIA TO WR-DET-CD-DIRETORIA
     MOVE ZEROS TO WT-CT-DIRETORIA
     MOVE ZEROS TO WT-AC-DIRETORIA

     PERFORM 21-PROCESSA-DIRETORIA
       UNTIL WT-ST-FPA0513 NOT = "00" OR
             FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA

     PERFORM 22-IMPRIME-MEDIA-DIRETORIA.

*----------------------------------------------------------------*
* FECHA ARQUIVOS E ZERA O RETURN-CODE
*----------------------------------------------------------------*
 3-TERMINA.

     CLOSE FPA0513 FPL0515

     MOVE ZEROS TO RETURN-CODE.

*----------------------------------------------------------------*
* SALVA O CODIGO DO DEPARTAMENTO, PROCESSA TODOS OS SALARIOS DES-
* SE DEPARTAMENTO. EM SEGUIDA, IMPRIME A MEDIA DE SALARIOS DO
* DEPARTAMENTO
*----------------------------------------------------------------*
 21-PROCESSA-DIRETORIA.

     MOVE FPA0513-CD-DEPARTAMENTO TO WT-CTL-CD-DEPARTAMENTO
     MOVE FPA0513-CD-DEPARTAMENTO TO WR-DET-CD-DEPARTAMENTO
     MOVE ZEROS TO WT-CT-DEPARTAMENTO
     MOVE ZEROS TO WT-AC-DEPARTAMENTO

     PERFORM 211-PROCESSA-DEPARTAMENTO
       UNTIL WT-ST-FPA0513 NOT = "00" OR
             FPA0513-CD-DIRETORIA NOT = WT-CTL-CD-DIRETORIA OR
             FPA0513-CD-DEPARTAMENTO NOT = WT-CTL-CD-DEPARTAMENTO

     PERFORM 212-IMPRIME-MEDIA-DEPARTAMENTO.

*----------------------------------------------------------------*
* IMPRIME MEDIA DE SALARIOS DA DIRETORIA
*----------------------------------------------------------------*
 22-IMPRIME-MEDIA-DIRETORIA.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     DIVIDE WT-CT-DIRETORIA INTO WT-AC-DIRETORIA
     MOVE WT-AC-DIRETORIA TO WR-ROD-AC-DIRETORIA

     WRITE FPL0515-REGISTRO FROM WR-ROD1
     WRITE FPL0515-REGISTRO FROM WR-ROD2 BEFORE 2
     ADD 3 TO WT-CT-LINHAS.

*----------------------------------------------------------------*
* CONTA E ACUMULA SALARIOS DE DETERMINADO DEPARTAMENTO
*----------------------------------------------------------------*
 211-PROCESSA-DEPARTAMENTO.
 
     ADD FPA0513-VL-SALARIO TO WT-AC-DIRETORIA
     ADD FPA0513-VL-SALARIO TO WT-AC-DEPARTAMENTO

     ADD 1 TO WT-CT-DIRETORIA
     ADD 1 TO WT-CT-DEPARTAMENTO

     READ FPA0513.

*----------------------------------------------------------------*
* IMPRIME MEDIA DE SALARIOS DO DEPARTAMENTO
*----------------------------------------------------------------*
 212-IMPRIME-MEDIA-DEPARTAMENTO.

     IF WT-CT-LINHAS > WC-LINHAS-POR-PAGINA
        PERFORM X1-IMPRIME-CABECALHO
     END-IF

     DIVIDE WT-CT-DEPARTAMENTO INTO WT-AC-DEPARTAMENTO
     MOVE WT-AC-DEPARTAMENTO TO WR-DET-VL-SALARIO

     WRITE FPL0515-REGISTRO FROM WR-DET1
     ADD 1 TO WT-CT-LINHAS

     MOVE SPACES TO WR-DET-CD-DIRETORIA.

*----------------------------------------------------------------*
* IMPRIME CABECALHO
*----------------------------------------------------------------*
 X1-IMPRIME-CABECALHO.

     ACCEPT WT-DT-SISTEMA FROM DATE
     ACCEPT WT-HR-SISTEMA FROM TIME
     ADD 1 TO WT-CT-PAGINA

     MOVE CORR WT-DT-SISTEMA TO WR-CAB-DATA
     MOVE CORR WT-HR-SISTEMA TO WR-CAB-HORA
     MOVE WT-CT-PAGINA TO WR-CAB-PAGINA

     WRITE FPL0515-REGISTRO FROM WR-CAB1 AFTER PAGE
     WRITE FPL0515-REGISTRO FROM WR-CAB2
     WRITE FPL0515-REGISTRO FROM WR-CAB3
     WRITE FPL0515-REGISTRO FROM WR-SEP1
     WRITE FPL0515-REGISTRO FROM WR-CAB4
     WRITE FPL0515-REGISTRO FROM WR-SEP1

     MOVE 6 TO WT-CT-LINHAS.

Balance-Line

A técnica de balance-line é usada quando precisamos processar dois ou mais arquivos sequenciais  interdependentes. Ela é muito comum em grandes sistemas e pode ser empregada em diversas situações, como por exemplo:

  • Quando precisamos atualizar um arquivo sequencial com informações existentes em outro arquivo sequencial;
  • Quando precisamos comparar os arquivos e tomar decisões sobre registros que existem em um mas não existem em outros;
  • Ou ao contrário, quando precisamos comparar os arquivos e tomar decisões sobre registros que existem em todos

A complexidade do algoritmo, naturalmente, aumenta em função do número de arquivos envolvidos. Por esse motivo, é mais comum encontrarmos programas que façam balance-line somente entre dois arquivos.

Quando a quantidade de registros é grande nos dois arquivos, o desempenho de um programa batch fazendo balance-line é significativamente superior a um programa que tente fazer o mesmo processamento usando arquivos de acesso direto, como tabelas em banco de dados ou arquivos indexados.

O algoritmo usado na construção de um balance-line pressupõe que todos os arquivos envolvidos estejam classificados pelas mesmas chaves, formadas por um ou mais campos. Os arquivos são lidos simultaneamente e suas chaves de classificação são comparadas para que se possa tomar alguma decisão quando…

  • A chave no primeiro arquivo é menor do que a chave no segundo arquivo, o que significa que a chave existe no primeiro mas não existe no segundo;
  • A chave no primeiro arquivo é igual à chave no segundo arquivo, o que significa que a chave existe nos dois arquivos
  • A chave no primeiro arquivo é maior do que a chave no segundo arquivo, que significa que a chave existe no segundo mas não existe no primeiro

A melhor forma de entender como isso tudo funciona (e para que serve) é analisando um exemplo prático.

Vamos supor que em nosso sistema exista um arquivo chamado CRA0207V que contém duplicatas que ainda não foram pagas. A chave desse arquivo é composta pelos campos NR-FATURA e NR-DUPLICATA, e seu conteúdo é mostrado na figura abaixo:

Cobol: Conteúdo de arquivo
Figura 41. Conteúdo do arquivo. CRA0207V

O layout desse arquivo está detalhado no book CRF0207V:

FD CRA0207V.
01 CRA0207V-REGISTRO.
   03 CRA0207V-CHAVE.
      05 CRA0207V-NR-FATURA      PIC  9(006).
      05 CRA0207V-NR-DUPLICATA    PIC  9(002).
   03 CRA0207V-CD-CLIENTE         PIC  X(006).
   03 CRA0207V-DT-EMISSAO         PIC  9(008).
   03 CRA0207V-DT-VENCTO          PIC  9(008).
   03 CRA0207V-VL-DUPLICATA       PIC S9(013)V9(002).
   03 CRA0207V-CD-CATEGORIA       PIC  X(003).
   03 CRA0207V-ST-DUPLICATA       PIC  X(003).

Vamos supor, também, que os bancos nos enviem periodicamente um segundo arquivo, chamado CRA0212, com as duplicatas pagas num determinado período. A chave desse arquivo também é formada pelos campos NR-FATURA e NR-DUPLICATA:

Cobol: Conteúdo de arquivo
Figura 42. Conteúdo do arquivo CRA0212

Seu layout está definido no book CRF0212:

FD CRA0212.
01 CRA0212-REGISTRO.
   03 CRA0212-CHAVE.
      05 CRA0212-NR-FATURA       PIC  9(006).
      05 CRA0212-NR-DUPLICATA     PIC  9(002).
   03 CRA0212-CD-BANCO            PIC  X(003).
   03 CRA0212-DT-PAGAMENTO        PIC  9(008).
   03 CRA0212-VL-PAGO             PIC S9(013)V9(002).

Precisamos construir um programa chamado CRP0213 que vai gerar um terceiro arquivo, chamado CRA0213. Esse arquivo precisa receber todos os registros do arquivo CRA0207V, atualizados com as informações provenientes do arquivo CRA0212:

Cobol: Conteúdo de arquivo
Figura 43. Conteúdo atualizado do arquivo CRA0213

Repare que apenas os registros cuja chave existe nos dois arquivos é que serão atualizados. Os demais serão gravados no arquivo de saída com os campos CD-BANCO, DT-PAGAMENTO e VL-PAGO vazios (em branco ou zerados, dependendo da picture).

O layout do arquivo de saída está definido no book CRF0213, e contém os campos dos dois arquivos de entrada:

FD CRA0213.
01 CRA0213-REGISTRO.
   03 CRA0213-CHAVE.
      05 CRA0213-NR-FATURA        PIC  9(006).
      05 CRA0213-NR-DUPLICATA     PIC  9(002).
   03 CRA0213-CD-CLIENTE          PIC  X(006).
   03 CRA0213-DT-EMISSAO          PIC  9(008).
   03 CRA0213-DT-VENCTO           PIC  9(008).
   03 CRA0213-VL-DUPLICATA        PIC S9(013)V9(002).
   03 CRA0213-CD-BANCO            PIC  X(003).
   03 CRA0213-DT-PAGAMENTO        PIC  9(008).
   03 CRA0213-VL-PAGO             PIC S9(013)V9(002).

Quando compararmos as chaves dos dois arquivos de entrada existirão três situações possíveis:

  • CRA0207V-CHAVE < CRA0212-CHAVE: A duplicata não existe no arquivo de pagamentos que foi enviado pelo banco
  • CRA0207V-CHAVE = CRA0212-CHAVE: A duplicata existe no arquivo de pagamentos que foi enviado pelo banco
  • CRA0207V-CHAVE > CRA0212-CHAVE: O arquivo enviado pelo banco contém uma duplicata que não existe no nosso arquivo

O segredo do algoritmo para balance-line é definir o que fazer nas três situações possíveis. A figura abaixo mostra, lado a lado, as chaves dos arquivos CRA0207V e CRA0212 à medida em que o programa for lendo os registros desses arquivos.

Repare que, quando as chaves forem iguais, o programa gravará o registro completo no arquivo de saída e lerá o próximo registro tanto do arquivo CRA0207V quanto do arquivo CRA0212.

Quando a chave do arquivo CRA0207V for menor que a chave do arquivo CRA0212 concluiremos que nossa duplicata não existe no arquivo enviado pelo banco. O programa vai gravar o registro incompleto no arquivo de saída e ler o próximo registro do arquivo CRA0207V. O arquivo CRA0212 fica parado.

Quando a chave do arquivo CRA0207V for maior que a chave do arquivo CRA0212 concluiremos que o banco informou o pagamento de uma duplicata que não existe no nosso arquivo. O programa vai ignorar e ler o próximo registro do arquivo CRA0212. O arquivo CRA0207V fica parado.

Cobol: Balance-Line
Figura 44. Comparação das chaves e a ação correspondente no programa

O programa termina quando não houver mais registros nem no arquivo CRA0207V nem no arquivo CRA0212. Mas é possível que um arquivo termine antes do outro. Para garantir que todos os registros restantes serão processados pelo programa, normalmente movemos o maior valor possível para a chave do arquivo que terminou primeiro. Normalmente, esse maior valor possível é obtido através da constante figurativa HIGH-VALUES, como no exemplo abaixo:

MOVE HIGH-VALUES TO variavel

Mover HIGH-VALUES para uma variável significa preencher todos os seus bytes com o maior hexadecimal possível (x’FF’).

Para entender melhor esse uso do HiGH-VALUES, imagine que no exemplo anterior o arquivo CRA0207V tivesse três registros a mais, e que depois do último registro do arquivo CRA0212 movêssemos HIGH-VALUES para a sua chave. A figura abaixo mostra as decisões que o programa tomaria em cada caso:

Cobol: Balance-Line
Figura 45. Arquivo CRA0212 terminando antes do arquivo CRA0207V

Agora imagine o contrário: o arquivo CRA0212 tem três registros a mais e quando o CRA0207V terminar moveremos HIGH-VALUES para a sua chave:

Cobol: Balance-Line
Figura 46. Arquivo CRA0207V terminando antes do arquivo CRA0212

Repare que, tanto no primeiro caso quanto no segundo, o uso do HIGH-VALUES permitiu que todos os registros restantes fossem tratados corretamente pelo programa. Os registros a mais no arquivo CRA0207V seriam gravados no arquivo de saída, como previsto. Os registros a mais no arquivo CRA0212 seriam ignorados, também como havia sido solicitado na especificação. O programa só termina quando os dois arquivos terminam, e as duas chaves estiverem preenchidas com HIGH-VALUES.

Agora que já entendemos o que o programa fará, podemos acompanhar a construção desse balance-line. O diagrama estruturado do programa é o seguinte:

Cobol: Diagrama estruturado de programa
Figura 47. Diagrama estruturado do programa CRP0213

O que faremos em cada parágrafo será o seguinte:

  • 1-INICIA: Classificar os dois arquivos de entrada pelos campos NR-FATURA e NR-DUPLICATA. Abrir os dois arquivos de entrada e o arquivo de saída. Ler os primeiros registros dos arquivos de entrada.
  • 2-PROCESSA: Se CRA0207V-CHAVE for igual a CRA0212-CHAVE, executar o parágrafo 21-DUPLICATA-PAGA; se for menor, executar o parágrafo 22-DUPLICATA-NAO-PAGA; se for maior executar 23-PAGAMENTO-NAO-ENCONTRADO.
  • 21-DUPLICATA-PAGA: Mover os campos NR-FATURA, NR-DUPLICATA, NR-CLIENTE, DT-EMISSAO, DT-VENCIMENTO, VL-DUPLICATA, CD-CATEGORIA e ST-DUPLICATA do arquivo CRA0207V para o arquivo de saída CRA0213. Mover os campos CD-BANCO, DT-PAGAMENTO e VL-PAGO do arquivo CRA0212 para o arquivo CRA0213. Gravar o registro no arquivo CRA0213. Executar o parágrafo X1-LE-CRA0207V. Executar o parágrafo X2-LE-CRA0212.
  • 22-DUPLICATA-NAO-PAGA: Mover os campos NR-FATURA, NR-DUPLICATA, NR-CLIENTE, DT-EMISSAO, DT-VENCIMENTO, VL-DUPLICATA, CD-CATEGORIA e ST-DUPLICATA do arquivo CRA0206 para o arquivo de saída CRA0213. Mover espaços para o campo CD-BANCO e zeros para os campos DT-PAGAMENTO e VL-PAGO do arquivo CRA0213. Gravar o registro no arquivo CRA0213. Executar o parágrafo X1-LE-CRA0207V.
  • 22-PAGAMENTO-NAO-ENCONTRADO: Executar o parágrafo X2-LE-CRA0212.
  • X1-LE-CRA0207V: Lê o próximo registro do arquivo CRA0207V. Se for fim de arquivo (file status diferente de “00”), mover HIGH-VALUES para o campo CRA0207V-CHAVE.
  • X1-LE-CRA0212: Lê o próximo registro do arquivo CRA0212. Se for fim de arquivo (file status diferente de “00”), mover HIGH-VALUES para o campo CRA0212-CHAVE.

Agora podemos construir o programa:

*================================================================*
 IDENTIFICATION DIVISION.
*----------------------------------------------------------------*
 PROGRAM-ID.    CRP0213.
 AUTHOR.        PAULO ANDRE DIAS.
 DATE-WRITTEN.  18/01/2017.
 REMARKS.
*----------------------------------------------------------------*
* SISTEMA:      CR - CONTAS A RECEBER
* JOB:          02 - GERACAO DE FLUXO DE CAIXA
* PROGRAMA:     13 - REGISTRA PAGAMENTO DE DUPLICATAS
*
* OBJETIVO:     BALANCE-LINE ENTRE O ARQUIVO DE DUPLICATAS ATI-
*               VAS E O ARQUIVO DE PAGAMENTOS BANCARIOS PARA
*               GERACAO DO ARQUIVO DE DUPLICATAS ATUALIZADAS
*               COM REGISTRO DE PAGAMENTOS
*
* VERSOES:      DATA    DESCRICAO
*               ------  ---------------------------------------
*               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*
*----------------------------------------------------------------*

Na ENVIRONMENT vamos usar os books de declaração para os arquivos de entrada (CRA0207V e CRA0212) e saída (CRA0213). Também declaramos dois arquivos temporários para classificação dos arquivos de entrada:

*================================================================*
 ENVIRONMENT DIVISION.
*----------------------------------------------------------------*
 CONFIGURATION SECTION.
 SPECIAL-NAMES.
 DECIMAL-POINT IS COMMA.

 INPUT-OUTPUT SECTION.
 FILE-CONTROL.

* DUPLICATAS VALIDAS
 COPY CRS0207V.

* PAGAMENTOS BANCARIOS
 COPY CRS0212.

* DUPLICATAS ATUALIZADAS COM REGISTRO DE PAGAMENTO
 COPY CRS0213.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0207V
 SELECT TMP0207V ASSIGN TO "$DAT/TMP0207V.DAT"
 ORGANIZATION IS LINE SEQUENTIAL.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0212
 SELECT TMP0212 ASSIGN TO "$DAT/TMP0212.DAT"
 ORGANIZATION IS LINE SEQUENTIAL.

E faremos a mesma coisa no detalhamento dos arquivos, na DATA DIVISION. Mais uma vez, na descrição dos arquivos temporários, mencionaremos apenas as chaves e completaremos os registros com um FILLER garantindo que eles ficarão com o mesmo tamanho dos registros dos arquivos originais.

*================================================================*
 DATA DIVISION.
*----------------------------------------------------------------*
 FILE SECTION.

* DUPLICATAS VALIDAS
 COPY CRF0207V.

* PAGAMENTOS BANCARIOS
 COPY CRF0212.

* DUPLICATAS ATUALIZADAS COM REGISTRO DE PAGAMENTO
 COPY CRF0213.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0207V
SD TMP0207V.
01 TMP0207V-REGISTRO.
   03 TMP0207V-CHAVE.
      05 TMP0207V-NR-FATURA       PIC  9(006).
      05 TMP0207V-NR-DUPLICATA    PIC  9(002).
   03 FILLER                      PIC  X(043).

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0212
 SD TMP0212.
 01 TMP0212-REGISTRO.
    03 TMP0212-CHAVE.
       05 TMP0212-NR-FATURA        PIC  9(006).
       05 TMP0212-NR-DUPLICATA     PIC  9(002).
    03 FILLER                      PIC  X(026).

A WORKING terá apenas as variáveis para file status. Elas não serão usadas nesse programa, mas precisam ser declaradas porque foram mencionadas nos books de declaração da ENVIRONMENT:

*================================================================*
 WORKING-STORAGE SECTION.
*----------------------------------------------------------------*
01 WT-FILE-STATUS.
   03 WT-ST-CRA0207V           PIC  X(002) VALUE SPACES.
   03 WT-ST-CRA0212            PIC  X(002) VALUE SPACES.
   03 WT-ST-CRA0213            PIC  X(002) VALUE SPACES.

O parágrafo principal executará o parágrafo 22-PROCESSA até que as chaves dos dois arquivos sejam iguais a HIGH-VALUES:

*================================================================*
 PROCEDURE DIVISION.
*----------------------------------------------------------------*
 0-PRINCIPAL.

     PERFORM 1-INICIA

     PERFORM 2-PROCESSA
             UNTIL CRA0207V-CHAVE = HIGH-VALUES AND
                   CRA0212-CHAVE  = HIGH-VALUES

     PERFORM 3-TERMINA

     STOP RUN.

No parágrafo dos procedimentos iniciais, classificamos os dois arquivos de entrada pelas mesmas chaves. Em seguida, abrimos os arquivos de entrada e saída. Desta vez usamos um formato do comando OPEN que permite abrir todos os arquivos com um comando só. Por último, chamamos os parágrafos que farão as leituras dos primeiros registros:

*----------------------------------------------------------------*
* CLASSIFICA OS DOIS ARQUIVOS DE ENTRADA POR NR-FATURA E
* NR-DUPLICATA. ABRE OS ARQUIVOS DE ENTRADA E SAIDA. LE O PRI-
* MEIRO REGISTRO DOS DOIS ARQUIVOS.
*----------------------------------------------------------------*
 1-INICIA.

     SORT TMP0207V
          ON ASCENDING KEY TMP0207V-NR-FATURA
                           TMP0207V-NR-DUPLICATA
          USING CRA0207V
          GIVING CRA0207V

     SORT TMP0212
          ON ASCENDING KEY TMP0212-NR-FATURA
                           TMP0212-NR-DUPLICATA
          USING CRA0212
          GIVING CRA0212

     OPEN INPUT CRA0207V
                CRA0212
          OUTPUT CRA0213

     PERFORM X1-LE-CRA0207V
     PERFORM X2-LE-CRA0212.

O balance-line acontece no parágrafo 2-PROCESSA. Observe que primeiro verificamos se as chaves dos dois arquivos são iguais. Sabemos que essas chaves são itens de grupo compostos por NR-FATURA e NR-DUPLICATA, e por isso classificamos os arquivos por esse campos.

Chaves iguais significam que a duplicata existe tanto no arquivo de duplicatas quanto no arquivo de pagamentos e por isso ela será tratada como “duplicata paga”. Se a chave do arquivo de duplicatas (CRA0207V) for menor do que a chave do arquivo de pagamentos (CRA0212) então a duplicata do arquivo CRA0207V não existe no arquivo CRA0212, e por isso ela será tratada como “não paga”. Se a chave do arquivo de duplicatas for maior do que a chave do arquivo de pagamentos então a duplicata do arquivo CRA0212 não existe no arquivo CRA0207V, e por isso será ignorada.

*----------------------------------------------------------------*
* SE A DUPLICATA EXISTIR NOS DOIS ARQUIVOS ELA SERA' ATUALIZADA
* COM OS DADOS DO PAGAMENTO.
*----------------------------------------------------------------*
 2-PROCESSA.

     IF CRA0207V-CHAVE = CRA0212-CHAVE
        PERFORM 21-DUPLICATA-PAGA
     ELSE
        IF CRA0207V-CHAVE < CRA0212-CHAVE
           PERFORM 22-DUPLICATA-NAO-PAGA
        ELSE
           PERFORM 23-PAGAMENTO-IGNORADO
        END-IF
     END-IF.

O tratamento de duplicatas pagas implica em gravar no arquivo de saída um registro com todos os campos do arquivo CRA0207V mais as informações sobre o pagamento que vieram no arquivo CRA0212. Em seguida o programa lê o próximo registro dos dois arquivos:

*----------------------------------------------------------------*
* GRAVA REGISTRO COMPLETO NO ARQUIVO DE SAIDA, COM DADOS DA DU-
* PLICATA E DADOS DO PAGAMENTO
*----------------------------------------------------------------*
 21-DUPLICATA-PAGA.

     MOVE CRA0207V-NR-FATURA TO CRA0213-NR-FATURA
     MOVE CRA0207V-NR-DUPLICATA TO CRA0213-NR-DUPLICATA
     MOVE CRA0207V-CD-CLIENTE TO CRA0213-CD-CLIENTE
     MOVE CRA0207V-DT-EMISSAO TO CRA0213-DT-EMISSAO
     MOVE CRA0207V-DT-VENCIMENTO TO CRA0213-DT-VENCIMENTO
     MOVE CRA0207V-VL-DUPLICATA TO CRA0213-VL-DUPLICATA
     MOVE CRA0207V-CD-CATEGORIA TO CRA0213-CD-CATEGORIA
     MOVE CRA0207V-ST-DUPLICATA TO CRA0213-ST-DUPLICATA

     MOVE CRA0212-CD-BANCO TO CRA0213-CD-BANCO
     MOVE CRA0212-DT-PAGAMENTO TO CRA0213-DT-PAGAMENTO
     MOVE CRA0212-VL-PAGO TO CRA0213-VL-PAGO

     WRITE CRA0213-REGISTRO

     PERFORM X1-LE-CRA0207V
     PERFORM X2-LE-CRA0212.

O tratamento de duplicata não paga implica em gravar no arquivo de saída todos os campos do arquivo CRA0207V, sem informações sobre pagamento. Movemos espaços para o campo CD-BANCO porque ele é alfanumérico; movemos zeros para os dois outros campos porque eles são numéricos.

Dessa vez, lemos o próximo registro apenas do arquivo CRA0207V. Fazemos isso porque, nesse momento, a chave de CRA0207V é menor do que a chave de CRA0212. E o arquivo CRA0212 ficará parado até que as chaves sejam iguais novamente.

*----------------------------------------------------------------*
* GRAVA REGISTRO INCOMPLETO NO ARQUIVO DE SAIDA PORQUE A DUPLI-
* CATA NAO FOI ENCONTRADA NO ARQUIVO DE PAGAMENTOS BANCARIOS
*----------------------------------------------------------------*
 22-DUPLICATA-NAO-PAGA.

     MOVE CRA0207V-NR-FATURA TO CRA0213-NR-FATURA
     MOVE CRA0207V-NR-DUPLICATA TO CRA0213-NR-DUPLICATA
     MOVE CRA0207V-CD-CLIENTE TO CRA0213-CD-CLIENTE
     MOVE CRA0207V-DT-EMISSAO TO CRA0213-DT-EMISSAO
     MOVE CRA0207V-DT-VENCIMENTO TO CRA0213-DT-VENCIMENTO
     MOVE CRA0207V-VL-DUPLICATA TO CRA0213-VL-DUPLICATA
     MOVE CRA0207V-CD-CATEGORIA TO CRA0213-CD-CATEGORIA
     MOVE CRA0207V-ST-DUPLICATA TO CRA0213-ST-DUPLICATA

     MOVE SPACES TO CRA0213-CD-BANCO
     MOVE ZEROS TO CRA0213-DT-PAGAMENTO
     MOVE ZEROS TO CRA0213-VL-PAGO

     WRITE CRA0213-REGISTRO

     PERFORM X1-LE-CRA0207V.

Para ignorar o pagamento que não encontramos no arquivo de duplicatas, simplesmente lemos o próximo registro do arquivo de pagamentos. Lembre-se que, nesse momento, a chave do arquivo CRA0212 está menor do que a chave do arquivo CRA0207V, e, desta vez, é o arquivo CRA0207V que vai ficar parado esperando que as chaves fiquem iguais novamente.

*----------------------------------------------------------------*
* EXISTE UMA DUPLICATA NO ARQUIVO GERADO PELO BANCO QUE NAO FOI
* ENCONTRADA NO ARQUIVO DE DUPLICATAS ATIVAS. O REGISTRO SERA'
* IGNORADO.
*----------------------------------------------------------------*
 23-PAGAMENTO-IGNORADO.

     PERFORM X2-LE-CRA0212.

Os parágrafos que fazem a leitura de registros nos arquivos de entrada usam o comando READ com a opção AT END para mover HIGH-VALUES para as chaves. Essa movimentação acontecerá apenas quando não existirem mais registros a serem lidos.

*----------------------------------------------------------------*
* LE PROXIMO REGISTRO DO ARQUIVO DE DUPLICATAS VALIDAS. SE FOR
* FIM DE ARQUIVO MOVE HIGH-VALUES PARA A CHAVE
*----------------------------------------------------------------*
 X1-LE-CRA0207V.

     READ CRA0207V AT END MOVE HIGH-VALUES TO CRA0207V-CHAVE.

*----------------------------------------------------------------*
* LE PROXIMO REGISTRO DO ARQUIVO DE PAGAMENTOS. SE FOR
* FIM DE ARQUIVO MOVE HIGH-VALUES PARA A CHAVE
*----------------------------------------------------------------*
 X2-LE-CRA0212.

     READ CRA0212 AT END MOVE HIGH-VALUES TO CRA0212-CHAVE.

Também poderíamos testar o file status de cada arquivo, ao invés de usar a cláusula AT END, como no exemplo abaixo.

READ CRA0207V
IF WT-ST-CRA0207V NOT = “00”
   MOVE HIGH-VALUES TO CRA0207V-CHAVE
END-IF.

O efeito seria o mesmo e talvez fosse a melhor opção se houvesse mais comandos a executar na situação de fim de arquivo, o que não foi o caso dessa vez.

Esse é o programa completo:

*================================================================*
 IDENTIFICATION DIVISION.
*----------------------------------------------------------------*
 PROGRAM-ID.    CRP0213.
 AUTHOR.        PAULO ANDRE DIAS.
 DATE-WRITTEN.  18/01/2017.
 REMARKS.
*----------------------------------------------------------------*
* SISTEMA:      CR - CONTAS A RECEBER
* JOB:          02 - GERACAO DE FLUXO DE CAIXA
* PROGRAMA:     13 - REGISTRA PAGAMENTO DE DUPLICATAS
*
* OBJETIVO:     BALANCE-LINE ENTRE O ARQUIVO DE DUPLICATAS ATI-
*               VAS E O ARQUIVO DE PAGAMENTOS BANCARIOS PARA
*               GERACAO DO ARQUIVO DE DUPLICATAS ATUALIZADAS
*               COM REGISTRO DE PAGAMENTOS
*
* VERSOES:      DATA    DESCRICAO
*               ------  ---------------------------------------
*               XXXXXX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*
*----------------------------------------------------------------*

*================================================================*
 ENVIRONMENT DIVISION.
*----------------------------------------------------------------*
 CONFIGURATION SECTION.
 SPECIAL-NAMES.
 DECIMAL-POINT IS COMMA.

 INPUT-OUTPUT SECTION.
 FILE-CONTROL.

* DUPLICATAS VALIDAS
 COPY CRS0207V.

* PAGAMENTOS BANCARIOS
 COPY CRS0212.

* DUPLICATAS ATUALIZADAS COM REGISTRO DE PAGAMENTO
 COPY CRS0213.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0207V
 SELECT TMP0207V ASSIGN TO "$DAT/TMP0207V.DAT"
 ORGANIZATION IS LINE SEQUENTIAL.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0212
 SELECT TMP0212 ASSIGN TO "$DAT/TMP0212.DAT"
 ORGANIZATION IS LINE SEQUENTIAL.

*================================================================*
 DATA DIVISION.
*----------------------------------------------------------------*
 FILE SECTION.

* DUPLICATAS VALIDAS
 COPY CRF0207V.

* PAGAMENTOS BANCARIOS
 COPY CRF0212.

* DUPLICATAS ATUALIZADAS COM REGISTRO DE PAGAMENTO
 COPY CRF0213.

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0207V
 SD TMP0207V.
 01 TMP0207V-REGISTRO.
    03 TMP0207V-CHAVE.
       05 TMP0207V-NR-FATURA       PIC  9(006).
       05 TMP0207V-NR-DUPLICATA    PIC  9(002).
    03 FILLER                       PIC  X(043).

* ARQUIVO TEMPORARIO PARA CLASSIFICACAO DO ARQUIVO CRA0212
 SD TMP0212.
 01 TMP0212-REGISTRO.
    03 TMP0212-CHAVE.
       05 TMP0212-NR-FATURA        PIC  9(006).
       05 TMP0212-NR-DUPLICATA     PIC  9(002).
    03 FILLER                       PIC  X(026).

*================================================================*
 WORKING-STORAGE SECTION.
*----------------------------------------------------------------*
01 WT-FILE-STATUS.
   03 WT-ST-CRA0207V           PIC  X(002) VALUE SPACES.
   03 WT-ST-CRA0212            PIC  X(002) VALUE SPACES.
   03 WT-ST-CRA0213            PIC  X(002) VALUE SPACES.

*================================================================*
 PROCEDURE DIVISION.
*----------------------------------------------------------------*
 0-PRINCIPAL.

     PERFORM 1-INICIA
     PERFORM 2-PROCESSA
       UNTIL CRA0207V-CHAVE = HIGH-VALUES AND
             CRA0212-CHAVE  = HIGH-VALUES
     PERFORM 3-TERMINA

     STOP RUN.

*----------------------------------------------------------------*
* CLASSIFICA OS DOIS ARQUIVOS DE ENTRADA POR NR-FATURA E
* NR-DUPLICATA. ABRE OS ARQUIVOS DE ENTRADA E SAIDA. LE O PRI-
* MEIRO REGISTRO DOS DOIS ARQUIVOS.
*----------------------------------------------------------------*
 1-INICIA.

     SORT TMP0207V
          ON ASCENDING KEY TMP0207V-NR-FATURA
                           TMP0207V-NR-DUPLICATA
          USING CRA0207V
          GIVING CRA0207V

     SORT TMP0212
          ON ASCENDING KEY TMP0212-NR-FATURA
                           TMP0212-NR-DUPLICATA
          USING CRA0212
          GIVING CRA0212

     OPEN INPUT  CRA0207V
     OPEN INPUT  CRA0212
     OPEN OUTPUT CRA0213

     PERFORM X1-LE-CRA0207V
     PERFORM X2-LE-CRA0212.

*----------------------------------------------------------------*
* SE A DUPLICATA EXISTIR NOS DOIS ARQUIVOS ELA SERA' ATUALIZADA
* COM OS DADOS DO PAGAMENTO.
*----------------------------------------------------------------*
 2-PROCESSA.

     IF CRA0207V-CHAVE = CRA0212-CHAVE
        PERFORM 21-DUPLICATA-PAGA
     ELSE
        IF CRA0207V-CHAVE < CRA0212-CHAVE
           PERFORM 22-DUPLICATA-NAO-PAGA
        ELSE
           PERFORM 23-PAGAMENTO-IGNORADO
        END-IF
     END-IF.

*----------------------------------------------------------------*
* FECHA ARQUIVOS
*----------------------------------------------------------------*
 3-TERMINA.

     CLOSE CRA0207V CRA0212 CRA0213.

*----------------------------------------------------------------*
* GRAVA REGISTRO COMPLETO NO ARQUIVO DE SAIDA, COM DADOS DA DU-
* PLICATA E DADOS DO PAGAMENTO
*----------------------------------------------------------------*
 21-DUPLICATA-PAGA.

     MOVE CRA0207V-NR-FATURA TO CRA0213-NR-FATURA
     MOVE CRA0207V-NR-DUPLICATA TO CRA0213-NR-DUPLICATA
     MOVE CRA0207V-CD-CLIENTE TO CRA0213-CD-CLIENTE
     MOVE CRA0207V-DT-EMISSAO TO CRA0213-DT-EMISSAO
     MOVE CRA0207V-DT-VENCIMENTO TO CRA0213-DT-VENCIMENTO
     MOVE CRA0207V-VL-DUPLICATA TO CRA0213-VL-DUPLICATA
     MOVE CRA0207V-CD-CATEGORIA TO CRA0213-CD-CATEGORIA
     MOVE CRA0207V-ST-DUPLICATA TO CRA0213-ST-DUPLICATA

     MOVE CRA0212-CD-BANCO TO CRA0213-CD-BANCO
     MOVE CRA0212-DT-PAGAMENTO TO CRA0213-DT-PAGAMENTO
     MOVE CRA0212-VL-PAGO TO CRA0213-VL-PAGO

     WRITE CRA0213-REGISTRO

     PERFORM X1-LE-CRA0207V
     PERFORM X2-LE-CRA0212.

*----------------------------------------------------------------*
* GRAVA REGISTRO INCOMPLETO NO ARQUIVO DE SAIDA PORQUE A DUPLI-
* CATA NAO FOI ENCONTRADA NO ARQUIVO DE PAGAMENTOS BANCARIOS
*----------------------------------------------------------------*
 22-DUPLICATA-NAO-PAGA.

     MOVE CRA0207V-NR-FATURA TO CRA0213-NR-FATURA
     MOVE CRA0207V-NR-DUPLICATA TO CRA0213-NR-DUPLICATA
     MOVE CRA0207V-CD-CLIENTE TO CRA0213-CD-CLIENTE
     MOVE CRA0207V-DT-EMISSAO TO CRA0213-DT-EMISSAO
     MOVE CRA0207V-DT-VENCIMENTO TO CRA0213-DT-VENCIMENTO
     MOVE CRA0207V-VL-DUPLICATA TO CRA0213-VL-DUPLICATA
     MOVE CRA0207V-CD-CATEGORIA TO CRA0213-CD-CATEGORIA
     MOVE CRA0207V-ST-DUPLICATA TO CRA0213-ST-DUPLICATA

     MOVE SPACES TO CRA0213-CD-BANCO
     MOVE ZEROS TO CRA0213-DT-PAGAMENTO
     MOVE ZEROS TO CRA0213-VL-PAGO

     WRITE CRA0213-REGISTRO

     PERFORM X1-LE-CRA0207V.

*----------------------------------------------------------------*
* EXISTE UMA DUPLICATA NO ARQUIVO GERADO PELO BANCO QUE NAO FOI
* ENCONTRADA NO ARQUIVO DE DUPLICATAS ATIVAS. O REGISTRO SERA'
* IGNORADO.
*----------------------------------------------------------------*
 23-PAGAMENTO-IGNORADO.

     PERFORM X2-LE-CRA0212.

*----------------------------------------------------------------*
* LE PROXIMO REGISTRO DO ARQUIVO DE DUPLICATAS VALIDAS. SE FOR
* FIM DE ARQUIVO MOVE HIGH-VALUES PARA A CHAVE
*----------------------------------------------------------------*
 X1-LE-CRA0207V.

    READ CRA0207V AT END MOVE HIGH-VALUES TO CRA0207V-CHAVE.

*----------------------------------------------------------------*
* LE PROXIMO REGISTRO DO ARQUIVO DE PAGAMENTOS. SE FOR
* FIM DE ARQUIVO MOVE HIGH-VALUES PARA A CHAVE
*----------------------------------------------------------------*
 X2-LE-CRA0212.

     READ CRA0212 AT END MOVE HIGH-VALUES TO CRA0212-CHAVE.

Anterior Conteúdo Próxima