Gerar parcelas dias da semana
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?
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
Curtidas 0
Melhor post
Arthur Heinrich
27/10/2024
Exemplo:
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); 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
24/10/2024
Obrigado Arthur.
GOSTEI 0
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
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.
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
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
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
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
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
Aqui na função, ela não reconhece Feriados e a Qry(devo declarar essas variaveis como globais?)
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
24/10/2024
Eu coloquei o parêntesis no lugar errado:
Deveria ser:
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.
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
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
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
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
24/10/2024
Olá, Emerson.
Vou alterar para dias uteis, para fins de relatórios e fluxos de caixa.
Vou alterar para dias uteis, para fins de relatórios e fluxos de caixa.
GOSTEI 0
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
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
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.
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
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).
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
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).
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:
na função/método DiaUtil() você verifica se é fim de semana ou feriado:
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:
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
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.
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
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
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
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
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
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
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 ?
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
24/10/2024
Olá, Arthur.
A modificação sugerida funcionou bem.
Agradeço a todos que contribuíram
A modificação sugerida funcionou bem.
Agradeço a todos que contribuíram
GOSTEI 0