Gerar parcelas dias da semana

Delphi

24/10/2024

Pessoal, fiz algumas buscas, mas não consegui achar o que preciso.

Minha rotina de gerar parcelas tem 2 opções:

por período(usuário informa o intervalo de dias)
por dia fixo(sempre no dia 15, por exemplo, IncMonth)

Pego o valor total, divido pelo numero de parcelas e uso o intervalo selecionado para gerar as parcelas.

Quando cai no sábado ou domingo, jogo a primeira parcela para a segunda-feira(IncDay), o problema é que a segunda parcela pega a data da primeira como base para calcular o próximo vencimento, mas deveria pegar a data original + prazo

Alguém tem uma rotina que faça isso?
Renan

Renan

Curtidas 0

Melhor post

Arthur Heinrich

Arthur Heinrich

27/10/2024

Exemplo:

DataOrigPrimParcela = 10/11/2024
NumParcelas = 10

for i:=0 to Pred(NumParcelas) do
  begin
    DataParcela:=AddMonths(DataOrigPrimParcela,i);
    case DayOfWeek(DataParcela) of
      1: DataParcela:=DataParcela+1;
      7: DataParcela:=DataParcela+2;
      end;
  end;


Obviamente, a rotina acima não checa se é feriado, que é muito mais complexa, pois depende de um cadastro de feriados.

Se houver uma função Feriado(Data) que retorna True se for feriado, você pode utilizar uma lógica simplificada, assim:

DataOrigPrimParcela = 10/11/2024
NumParcelas = 10

for i:=0 to Pred(NumParcelas) do
  begin
    DataParcela:=AddMonths(DataOrigPrimParcela,i);
    if ( (DayOfWeek(DataParcela) in [1,7]) or Feriado(DataParcela) ) then DataParcela:=DataParcela+1;
  end;

GOSTEI 1

Mais Respostas

Renan

Renan

24/10/2024

Obrigado Arthur.
GOSTEI 0
Arthur Heinrich

Arthur Heinrich

24/10/2024

Onde eu havia sugerido um "if", na realidade seria um "while"

DataOrigPrimParcela = 10/11/2024
NumParcelas = 10
 
for i:=0 to Pred(NumParcelas) do
  begin
    DataParcela:=AddMonths(DataOrigPrimParcela,i);
    while ( (DayOfWeek(DataParcela) in [1,7]) or Feriado(DataParcela) ) do DataParcela:=DataParcela+1;
  end;
GOSTEI 0
Renan

Renan

24/10/2024

Olá, Arthur

Seria mesmo interessante ter ao menos os feriados nacionais.
Vou criar uma tabela e cadastrar.

Nesse meio tempo vou pesquisar uma função para adaptar ao código que você postou.
GOSTEI 0
Arthur Heinrich

Arthur Heinrich

24/10/2024

{ Carregar Feriados }
Q:=TQuery.Create;
Q.Lines.Add('select data from feriados order by 1');
Q.Open;
Feriados:=TStringList.Create;
while (not Q.EOF) do
  begin
    Feriados.Add( FormatDateTime( Q.FieldByName('data').AsDateTime, 'yyyy-mm-dd') );
    Q.Next;
  end;
Feriados.Sorted:=true;
Q.Close;
Q.Free;

{ Verificar se uma data é feriado }
if ( Feriados.IndexOf( FormatDateTime( DATA, 'yyyy-mm-dd') ) >=0 ) then  else {não feriado};

function Feriado( Data : TDateTime ):Boolean;
begin
  Result:=( Feriados.IndexOf( FormatDateTime( Data, 'yyyy-mm-dd') ) >=0 );
end;

GOSTEI 1
Renan

Renan

24/10/2024

Legal, obrigado pela dica.

Logo que eu sobrar um tempo vou adaptar para usar na mesma instrução, a verificação de feriado e sabado e domingo.

abraço
GOSTEI 0
Renan

Renan

24/10/2024

Arthur, estou tentando implementar teu exemplo, mas barrei em uns erros.

aqui recebo um erro de tipos incompativeis String e Integer, na ultima linha, na verificação do feriado

procedure TForm1.Button1Click(Sender: TObject);
var
 Feriados : TStringList;
 Qry : TIBQuery;
begin
  { Carregar Feriados }
  Qry := TIBQuery.Create(Self);
  Qry.SQL.Add('SELECT DATA FROM FERIADOS ORDER BY 1');
  Qry.Open;

  Feriados := TStringList.Create;

  while not Qry.EOF do
  begin
    Feriados.Add(FormatDateTime('dd/mm/yyyy', Qry.FieldByName('DATA').AsDateTime));
    Qry.Next;
  end;

  Feriados.Sorted:=true;
  FreeAndNil(Qry);

  { Verificar se uma data é feriado }
  if Feriados.IndexOf(FormatDateTime('dd/mm/yyyy', Qry.FieldByName('DATA').AsDateTime) >= 0)  then

  else {não feriado};
end;


Aqui na função, ela não reconhece Feriados e a Qry(devo declarar essas variaveis como globais?)

function TForm1.Feriado(Data: TDateTime): Boolean;
begin
  Result := (Feriados.IndexOf(FormatDateTime('dd/mm/yyyy', Qry.FieldByName('DATA').AsDateTime)) >= 0);
end;
GOSTEI 0
Arthur Heinrich

Arthur Heinrich

24/10/2024

Eu coloquei o parêntesis no lugar errado:

if Feriados.IndexOf(FormatDateTime('dd/mm/yyyy', Qry.FieldByName('DATA').AsDateTime) >= 0)  then


Deveria ser:

if Feriados.IndexOf(FormatDateTime('dd/mm/yyyy', Qry.FieldByName('DATA').AsDateTime)) >= 0 then


Eu declarei a variável Feriados, no exemplo, como uma variável local.

Mas você pode declarar como uma propriedade da classe que contém o método/função Feriados(), para que a função tenha acesso à lista carregada no início.

Ou, pode utilizar uma variável global, em uma unit utilizada para gerenciar os feriados, onde seja colocada a função Feriados().

A escolha tem mais a ver com o seu estilo de organização e o escopo em que a função feriados irá funcionar.
GOSTEI 1
Renan

Renan

24/10/2024

Entendi, mas fiquei na dúvida de como verificar se é feriado ou final de semana na mesma rotina, conforme o codigo a seguir.

if ( (DayOfWeek(DataParcela) in [1,7]) or Feriado(DataParcela) ) then 
  DataParcela:=DataParcela+1;
GOSTEI 0
Emerson Nascimento

Emerson Nascimento

24/10/2024

Porque você quer trocar a data? É uma política da empresa?
Os boletos que eu recebo (são muitos, hahahaha) têm vencimentos em qualquer dia, seja final de semana ou feriado.

Porque?
Se o vencimento do boleto for no sábado, eu pago na segunda (dia útil) e não há juros nem mora.
Mas se eu pagar na terça-feira serão cobrados juros e 3 dias de mora, porque o vencimento era sábado (cliente - eu - no "prejuízo").

Se esse boleto tivesse a data de vencimento alterada para segunda, ao pagar na terça eu pagaria menos mora (credor recebe menos pelo meu atrazo).

Boletos com vencimento em finais de semana ou feriados são aceitos no primeiro dia útil seguinte sem cobrança de juros ou mora (artigo 1º da Lei 7089/83).

Impostos e taxas são diferentes: caso o vencimento seja no final de semana ou feriado, o pagamento deve ser antecipado.

https://www.bcb.gov.br/meubc/faqs/p/boleto-com-vencimento-em-dia-nao-util-e-boleto-vencido







GOSTEI 0
Renan

Renan

24/10/2024

Olá, Emerson.

Vou alterar para dias uteis, para fins de relatórios e fluxos de caixa.
GOSTEI 0
Emerson Nascimento

Emerson Nascimento

24/10/2024


eu trabalho com 2 campos de vencimento: 1 para o vencimento real (aquele obtido a partir condição de pagamento) e outro para gravar a data útil, aquela que corresponde ao primeiro dia útil seguinte. caso o vencimento real já seja um dia útil, o conteúdo dos campos é o mesmo.

desta forma posso trabalhar com uma data para envio ao banco e outra data para relatórios. inclusive os relatórios são parametrizáveis quanto ao vencimento utilizado no filtro e o vencimento apresentado.


GOSTEI 0
Emerson Nascimento

Emerson Nascimento

24/10/2024


eu trabalho com 2 campos de vencimento: 1 para o vencimento real (aquele obtido a partir condição de pagamento) e outro para gravar a data útil, aquela que corresponde ao primeiro dia útil seguinte. caso o vencimento real já seja um dia útil, o conteúdo dos campos é o mesmo.

desta forma posso trabalhar com uma data para envio ao banco e outra data para relatórios. inclusive os relatórios são parametrizáveis quanto ao vencimento utilizado no filtro e o vencimento apresentado.


GOSTEI 1
Renan

Renan

24/10/2024

É uma opção.
No meu caso não vou gerar remessa, apenas controlar contas a pagar e a receber.

Posso controlar o vencimento ao gerar o relatório, mas ainda acho que vou jogar o registo para o próximo dia útil.

Tenho parte do código pronto, usando dia fixo. Ainda preciso adaptar o intervalo de dias que o usuário pode informar, como por exemplo, parcelas a cada 15 dias.

A questão do feriado, ainda não consegui adaptar a função do Arthur.
GOSTEI 0
Emerson Nascimento

Emerson Nascimento

24/10/2024

a questão do feriado é simples: você cria uma tabela de feriados com a data, descrição e tipo de feriado (nacional/estadual/municipal).
CREATE TABLE [dbo].[Feriado](
[Data] [date] NOT NULL,
[Descricao] [varchar](50) NOT NULL,
[Tipo] [char](1) NULL,
) ON [PRIMARY]
GO

após obter a data a partir da condição de pagamento você terá dois procedimentos obrigatórios:
- verificar se é fim de semana e
- verificar se é feriado.
deverá ser um loop até encontrar um dia útil.

imagine que pela condição de pagamento, a data de vencimento caiu no dia 30/12/2023. é um sábado. dia 01/01/2024 é feriado, e esta data deve ser incluída na tabela Feriado.
deixe a query que acessa a tabela Feriado sempre aberta - num DM, se possível.

então você precisa ter um loop que seja executado enquanto NÃO for um dia útil:

while not DiaUtil(vencimento) do
vencimento := vencimento + 1;

então, na função/método DiaUtil() você verifica se é fim de semana ou feriado:

function DiaUtil(dia: date): boolean;
begin
result := not( (DayOfWeek(dia) in [1,7]) or qryFeriado.Locate('Data', dia) );
end;

se você usar um DM, coloque a função DiaUtil nele ou em uma unit de funções (lib).
GOSTEI 0
Emerson Nascimento

Emerson Nascimento

24/10/2024

a questão do feriado é simples: você cria uma tabela de feriados com a data, descrição e tipo de feriado (nacional/estadual/municipal).
CREATE TABLE [dbo].[Feriado](
      [Data] [date] NOT NULL,
      [Descricao] [varchar](50) NOT NULL,
      [Tipo] [char](1) NULL
) ON [PRIMARY]
GO

após obter a data a partir da condição de pagamento você terá dois procedimentos obrigatórios:
- verificar se é fim de semana e
- verificar se é feriado.
deverá ser um loop até encontrar um dia útil.

imagine que pela condição de pagamento, a data de vencimento caiu no dia 30/12/2023. é um sábado. dia 01/01/2024 é feriado, e esta data deve ser incluída na tabela Feriado.
deixe a query que acessa a tabela Feriado sempre aberta - num DM, se possível.

você precisa ter um loop que seja executado enquanto NÃO for um dia útil:
while not DiaUtil(vencimento) do
   vencimento := vencimento + 1;

na função/método DiaUtil() você verifica se é fim de semana ou feriado:
function DiaUtil(dia: date): boolean;
begin
   result := not( (DayOfWeek(dia) in [1,7]) or qryFeriado.Locate('Data', dia) );
end;

se você usar um DM, coloque a função DiaUtil nele ou em uma unit de funções (lib).

utilizando no código que eu vi um pouco acima:
for i := 0 to Pred(NumParcelas) do
begin
    DataParcela := AddMonths(DataOrigPrimParcela,i);

    while not DiaUtil(DataParcela) do
        DataParcela := DataParcela + 1;

    // Saiu do loop, então faça o que for necessário com a DataParcela
    // to-do
end;




GOSTEI 1
Renan

Renan

24/10/2024

Olá, Emerson.

Obrigado pelo retorno, na semana que vem vou testar e retorno aqui.

Barrei numa situação… além de vencimento fixo(incmonth), tenho a opção de vencimento por período , onde o usuário define o intervalo de dias entre as parcelas. Tentei adaptar ao código acima, mas não consegui fazer o vencimento pegar sempre como base a data original. Então se for informado 15 dias por exemplo, onde uma parcela cai no sábado, na próxima parcela o código está gerando 17 dias, pois pega a data anterior e não a original.
GOSTEI 0
Emerson Nascimento

Emerson Nascimento

24/10/2024

Então se for informado 15 dias por exemplo, onde uma parcela cai no sábado, na próxima parcela o código está gerando 17 dias, pois pega a data anterior e não a original.

Foi por isso que eu disse que não se troca a data. Se você mantivesse o vencimento no dia 15, o próximo cairia no dia correto. E, se o vencimento cair num sábado/feriado, o banco já aceita a pagamento no próximo dia útil sem acréscimo do valor.

de qualquer modo, se você quer fazer assim, basta ter uma variável para a data calculada e outra para o dia útil. algo assim:
Intervalo := 15; // 15 dias entre as parcelas
DataParcela := 15/11/2024; // ou uma função que retorne o dia atual

for i := 0 to Pred(NumParcelas) do
begin
    DataParcela := DataParcela + Intervalo; // soma os dias de intervalo.
    DataGravar := DataParcela; // esta data será manipulada até encontrar o dia útil
 
    while not DiaUtil(DataGravar) do
        DataGravar := DataGravar + 1;
 
    // Saiu do loop, então faça o que for necessário com a DataGravar
    // to-do

end;


GOSTEI 0
Renan

Renan

24/10/2024

Emerson, acho que tem algum problema no código, mas não consegui identificar.

Adaptei o código pra gerar com o IncMonth também, mas o problema está no intervalo de dias
EX: se eu setar 10 parcelas com intervalo de 1 dia com data inicial para 18/11/2024, veja o resultado que obtenho

19/11/2024-100
20/11/2024-100
21/11/2024-100
22/11/2024-100
25/11/2024-100
25/11/2024-100
25/11/2024-100
26/11/2024-100
27/11/2024-100
28/11/2024-100


procedure TForm1.Button1Click(Sender: TObject);
var
  Intervalo, i, NumParcelas, SubParc : Integer;
  DataParcela, DataGravar, DataPrimParcela : TDate;
  ValorParcela, Sobra, Total : Double;
begin
  Intervalo    := StrToInt(edintervalo.Text);; // 15 dias entre as parcelas
  DataParcela  := DateTimePicker1.Date; // ou uma função que retorne o dia atual
  DataPrimParcela := DateTimePicker1.Date;
  NumParcelas  := StrToInt(edNumParcelas.Text);
  ValorParcela := 0;
  Total        := StrToFloat(StringReplace(Trim(EdTotal.Text), '.', '', [rfReplaceAll]));

  for i := 0 to Pred(NumParcelas) do
  begin
    if RBIntervalo.Checked = True then
    begin
      DataParcela := DataParcela + Intervalo; // soma os dias de intervalo.
      DataGravar := DataParcela; // esta data será manipulada até encontrar o dia útil
    end
    else
    DataGravar := IncMonth(DataPrimParcela,i);

    while not DiaUtil(DataGravar) do
      DataGravar := DataGravar + 1;

    if i = Pred(NumParcelas) then
    begin
      SubParc      := StrToInt(edNumParcelas.Text) - 1;
      Sobra        := ValorParcela * SubParc;
      ValorParcela := Total - Sobra;
    end
    else
      ValorParcela := StrToFloat(FormatFloat('0.00',Total / NumParcelas));

    Memo1.Lines.Add(DateToStr(DataGravar) + '-' + FloatToStr(ValorParcela));
  end;
end;
GOSTEI 0
Emerson Nascimento

Emerson Nascimento

24/10/2024

Adaptei o código pra gerar com o IncMonth também, mas o problema está no intervalo de dias
EX: se eu setar 10 parcelas com intervalo de 1 dia com data inicial para 18/11/2024, veja o resultado que obtenho

19/11/2024-100
20/11/2024-100
21/11/2024-100
22/11/2024-100
25/11/2024-100
25/11/2024-100
25/11/2024-100
26/11/2024-100
27/11/2024-100
28/11/2024-100


E onde está o erro ?
GOSTEI 0
Arthur Heinrich

Arthur Heinrich

24/10/2024

Adaptei o código pra gerar com o IncMonth também, mas o problema está no intervalo de dias
EX: se eu setar 10 parcelas com intervalo de 1 dia com data inicial para 18/11/2024, veja o resultado que obtenho

19/11/2024-100
20/11/2024-100
21/11/2024-100
22/11/2024-100
25/11/2024-100
25/11/2024-100
25/11/2024-100
26/11/2024-100
27/11/2024-100
28/11/2024-100

E onde está o erro ?


Quando você optou por um intervalo, ao entrar no "if", a primeira coisa que a rotina faz é somar o intervalo à data inicial, transformando o dia 18 (início do intervalo) em 19. Por isso a primeira data aparece incorreta.

  for i := 0 to Pred(NumParcelas) do
  begin
    if RBIntervalo.Checked = True then
    begin
      DataParcela := DataParcela + Intervalo; // soma os dias de intervalo.
      DataGravar := DataParcela; // esta data será manipulada até encontrar o dia útil
    end
    else
    DataGravar := IncMonth(DataPrimParcela,i);


Quando não utiliza intervalo, a rotina soma meses, partindo do zero.

Dá para corrigir o problema, utilizando a variável "i", da parcela, para multiplicar o intervalo:

  for i := 0 to Pred(NumParcelas) do
  begin
    if RBIntervalo.Checked = True then
    begin
      DataParcela := DataPrimParcela + i * Intervalo; // soma os dias de intervalo da i-ésima parcela.
      DataGravar := DataParcela; // esta data será manipulada até encontrar o dia útil
    end
    else
    DataGravar := IncMonth(DataPrimParcela,i);

GOSTEI 1
Renan

Renan

24/10/2024

Olá, Arthur.

A modificação sugerida funcionou bem.

Agradeço a todos que contribuíram
GOSTEI 0
POSTAR