Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env ruby
- # invoice_optimizer.rb
- #
- # Ez az alkalmazás beolvassa a CSV fájlból az számlaütemtervet, majd
- # optimalizálja a fizetési dátumokat azzal a céllal, hogy a végső banki egyenleg a lehető legnagyobb legyen.
- #
- # FONTOS: A kódnál figyeltünk a bemeneti adatok ellenőrzésére és szűrésére.
- # Bár ez nem webalkalmazás, a kód "biztonsági" ellenőrzéseket tartalmaz (pl. fájl ellenőrzés).
- #
- require 'csv'
- require 'date'
- require 'optparse'
- require 'securerandom'
- # Bank osztály: tartalmazza a bank paramétereit (kamat, tranzakciós díj stb.)
- class Bank
- attr_reader :name, :interest_rate_30_days, :transaction_fee_percentage, :transaction_fee_min, :transaction_fee_max
- def initialize(name, interest_rate_30_days, transaction_fee_percentage, transaction_fee_min, transaction_fee_max)
- @name = name
- @interest_rate_30_days = interest_rate_30_days.to_f
- @transaction_fee_percentage = transaction_fee_percentage.to_f
- @transaction_fee_min = transaction_fee_min.to_f
- @transaction_fee_max = transaction_fee_max.to_f
- end
- # Számolja ki a tranzakciós díjat adott összegre
- def calculate_transaction_fee(amount)
- fee = amount * @transaction_fee_percentage
- fee = @transaction_fee_min if fee < @transaction_fee_min
- fee = @transaction_fee_max if fee > @transaction_fee_max
- fee
- end
- # Napi kamatláb kiszámítása (egyszerű kamatszámítás a 30 napos kamatláb alapján)
- def daily_interest_rate
- @interest_rate_30_days / 30.0
- end
- end
- # Számla (invoice) osztály: tartalmazza a számla adatait
- class Invoice
- attr_reader :type, :partner, :bank_name, :gross_amount, :net_amount, :due_date
- attr_accessor :payment_date
- def initialize(type, partner, bank_name, gross_amount, net_amount, due_date)
- @type = type.strip
- @partner = partner.strip
- @bank_name = bank_name.strip
- @gross_amount = gross_amount.to_f
- @net_amount = net_amount.to_f
- @due_date = Date.strptime(due_date.strip, '%m/%d/%Y')
- # Alapesetben a fizetés a lejárat napján történik
- @payment_date = @due_date
- end
- end
- # KONFIGURÁCIÓS KONSTANSOK
- MAX_EARLY_PAYMENT_DAYS = 10 # Legkorábbi nap, ameddig fizethetünk (inbound számlák esetében)
- EARLY_PAYMENT_DISCOUNT_RATE = 0.001 # Napi kedvezmény (pl. 0,1% naponta)
- # Globális tranzakciós adó paraméterek
- TRANSACTION_TAX_PERCENTAGE = 0.02
- TRANSACTION_TAX_MIN = 5.0
- TRANSACTION_TAX_MAX = 50.0
- # Globális bank konfigurációk (példa: Bank1 és Bank2)
- BANKS = {
- 'Bank1' => Bank.new('Bank1', 0.05, 0.01, 10.0, 100.0),
- 'Bank2' => Bank.new('Bank2', 0.04, 0.015, 8.0, 90.0)
- }
- # Számolja ki a globális tranzakciós adót adott összegre
- def calculate_transaction_tax(amount)
- tax = amount * TRANSACTION_TAX_PERCENTAGE
- tax = TRANSACTION_TAX_MIN if tax < TRANSACTION_TAX_MIN
- tax = TRANSACTION_TAX_MAX if tax > TRANSACTION_TAX_MAX
- tax
- end
- # Biztonságos fájl beolvasás: ellenőrizzük, hogy a fájl létezik, és a kiterjesztés CSV
- def safe_csv_read(file_path)
- unless File.exist?(file_path)
- abort("Hiba: A megadott fájl nem létezik: #{file_path}")
- end
- unless File.extname(file_path).downcase == '.csv'
- abort("Hiba: Csak CSV fájlokat lehet feldolgozni.")
- end
- CSV.read(file_path, headers: true, encoding: 'utf-8')
- end
- # A CSV fájlból beolvassa a számlákat, és létrehoz egy Invoice objektumot minden sorból
- def read_invoices(file_path)
- csv_data = safe_csv_read(file_path)
- invoices = []
- csv_data.each do |row|
- # Bemeneti adatok szűrése (trim, ellenőrzés)
- type = row['Típus'] || row['Tipus']
- partner = row['Partner']
- bank_name = row['Számla'] || row['Szamla']
- gross_amount = row['Bruttó összeg'] || row['Brutto osszeg']
- net_amount = row['Nettó összeg'] || row['Netto osszeg']
- due_date = row['Lejárat'] || row['Lejarat']
- if type.nil? or partner.nil? or bank_name.nil? or gross_amount.nil? or net_amount.nil? or due_date.nil?
- next # Ha hiányos az adat, kihagyjuk a sort
- end
- invoices << Invoice.new(type, partner, bank_name, gross_amount, net_amount, due_date)
- end
- invoices
- end
- # A szimuláció kiszámolja a végső banki egyenleget a megadott számlaütemterv alapján.
- # A pénzmozgás naponta történik, és minden bank esetében:
- # - Az outbound számlák (kimenő) bevételt jelentenek, melyek a due_date-n kerülnek letétbe.
- # - Az inbound számlák (bejövő) kifizetésként szerepelnek a fizetési dátumukon,
- # csoportosítva partner szerint, így az egy napra eső fizetésekre a banki tranzakciós díj
- # a teljes összeg alapján kerül kiszámolásra.
- # - A fizetett számlák esetében a korai fizetés kedvezménnyel jár (napi szinten számolva).
- def simulate_cash_flow(invoices)
- # Szétválasztjuk az inbound és outbound számlákat
- inbound_invoices = invoices.select { |inv| inv.type.downcase.include?('bejövő') }
- outbound_invoices = invoices.select { |inv| inv.type.downcase.include?('kimenő') }
- # Időtartam meghatározása: a legkorábbi esemény napjától a legkésőbb esemény napjáig
- all_dates = []
- inbound_invoices.each { |inv| all_dates << inv.payment_date; all_dates << inv.due_date }
- outbound_invoices.each { |inv| all_dates << inv.due_date }
- start_date = all_dates.min
- end_date = all_dates.max
- # Bank egyenlegek inicializálása (minden banknál 0-ról indulunk)
- bank_balances = {}
- BANKS.each_key do |bank_name|
- bank_balances[bank_name] = 0.0
- end
- # Események csoportosítása: minden napra minden banknál
- # events[nap][bank] = { deposits: [kimenő számlák], payments: { partner => [bejövő számlák] } }
- events = {}
- (start_date..end_date).each do |date|
- events[date] = {}
- BANKS.each_key do |bank_name|
- events[date][bank_name] = { deposits: [], payments: {} }
- end
- end
- # Outbound számlák: a due_date-n letétbe kerülnek (bevétel)
- outbound_invoices.each do |inv|
- bank = inv.bank_name
- date = inv.due_date
- if events.key?(date) and events[date].key?(bank)
- events[date][bank][:deposits] << inv
- end
- end
- # Inbound számlák: a fizetés a payment_date-n történik
- inbound_invoices.each do |inv|
- bank = inv.bank_name
- date = inv.payment_date
- if events.key?(date) and events[date].key?(bank)
- events[date][bank][:payments][inv.partner] ||= []
- events[date][bank][:payments][inv.partner] << inv
- end
- end
- # Napi szimuláció: minden nap végigmegyünk a pénzmozgáson
- current_date = start_date
- while current_date <= end_date
- BANKS.each do |bank_name, bank_obj|
- # 1. Letétek: hozzáadjuk a bank egyenlegéhez a kimenő számlák nettó összegét
- if events[current_date][bank_name][:deposits].any?
- events[current_date][bank_name][:deposits].each do |inv|
- bank_balances[bank_name] += inv.net_amount # Feltételezzük, hogy ezek pozitív értékűek
- end
- end
- # 2. Kifizetések: inbound számlák fizetése, csoportosítva partner szerint
- if events[current_date][bank_name][:payments].any?
- events[current_date][bank_name][:payments].each do |partner, inv_list|
- total_payment = 0.0
- inv_list.each do |inv|
- # Az alap kifizetendő összeg: a számla nettó összegének abszolút értéke
- base_amount = inv.net_amount.abs
- # Számoljuk ki, hány nappal fizetünk korábban a lejárathoz képest
- days_early = (inv.due_date - inv.payment_date).to_i
- discount = days_early * EARLY_PAYMENT_DISCOUNT_RATE * base_amount
- effective_payment = base_amount - discount
- total_payment += effective_payment
- end
- # Banki tranzakciós díj számítása
- fee = bank_obj.calculate_transaction_fee(total_payment)
- # Globális tranzakciós adó
- tax = calculate_transaction_tax(total_payment)
- total_outflow = total_payment + fee + tax
- bank_balances[bank_name] -= total_outflow
- end
- end
- # 3. Napi kamat, ha pozitív a bank egyenleg
- if bank_balances[bank_name] > 0
- bank_balances[bank_name] += bank_balances[bank_name] * bank_obj.daily_interest_rate
- end
- end
- current_date += 1
- end
- # Összegezzük az összes bank egyenlegét
- total_balance = bank_balances.values.sum
- total_balance
- end
- # Véletlenszerűen módosítja egy inbound számla fizetési dátumát az engedélyezett intervallumon belül
- def random_modify_schedule(invoices)
- # Csak a bejövő számlákat vesszük figyelembe
- inbound_invoices = invoices.select { |inv| inv.type.downcase.include?('bejövő') }
- return if inbound_invoices.empty?
- inv = inbound_invoices.sample
- # Engedélyezett intervallum: [lejárat - MAX_EARLY_PAYMENT_DAYS, lejárat]
- earliest = inv.due_date - MAX_EARLY_PAYMENT_DAYS
- latest = inv.due_date
- range = (earliest..latest).to_a
- inv.payment_date = range.sample
- end
- # Mélyklónozza a számlák aktuális ütemtervét (így módosítások esetén nem módosítjuk az eredetit)
- def clone_schedule(invoices)
- invoices.map do |inv|
- cloned = Invoice.new(inv.type, inv.partner, inv.bank_name, inv.gross_amount, inv.net_amount, inv.due_date.strftime('%m/%d/%Y'))
- cloned.payment_date = inv.payment_date
- cloned
- end
- end
- # Optimalizálja a számlaütemtervet iteratív (véletlenszerű keresés alapú) módon,
- # hogy a végső banki egyenleg a lehető legnagyobb legyen.
- def optimize_schedule(invoices, iterations)
- best_invoices = clone_schedule(invoices)
- best_score = simulate_cash_flow(best_invoices)
- current_invoices = clone_schedule(invoices)
- current_score = best_score
- iterations.times do |i|
- # Véletlenszerű módosítás egy számla fizetési dátumán
- new_invoices = clone_schedule(current_invoices)
- random_modify_schedule(new_invoices)
- new_score = simulate_cash_flow(new_invoices)
- # Ha javulást látunk, elfogadjuk a módosítást
- if new_score > current_score
- current_invoices = new_invoices
- current_score = new_score
- # Ha ez a legjobb eddigi eredmény, frissítjük a best_invoices változót
- if current_score > best_score
- best_invoices = clone_schedule(current_invoices)
- best_score = current_score
- end
- end
- # Időközben jelenítsük meg a futás előrehaladását (ha lehetséges)
- if (i+1) % (iterations/100) == 0
- progress = ((i+1).to_f / iterations.to_f * 100).round(2)
- puts "Haladás: #{progress} %"
- end
- end
- return best_invoices, best_score
- end
- # FŐPROGRAM
- if __FILE__ == $0
- # Parancssori argumentumok feldolgozása
- options = {}
- OptionParser.new do |opts|
- opts.banner = "Használat: ruby invoice_optimizer.rb [opciók] CSV_FÁJL"
- opts.on("-i", "--iterations ITERATIONS", Integer, "Iterációk száma (alapértelmezett: 10000)") do |it|
- options[:iterations] = it
- end
- end.parse!
- if ARGV.empty?
- abort("Hiba: Kérlek add meg a CSV fájl elérési útját!")
- end
- csv_file = ARGV[0]
- iterations = options[:iterations] || 10000
- # Számlák beolvasása
- invoices = read_invoices(csv_file)
- if invoices.empty?
- abort("Hiba: Nem sikerült számlákat beolvasni a fájlból!")
- end
- puts "Számlák beolvasva: #{invoices.size} számla."
- # Ütemterv optimalizálása
- best_schedule, best_score = optimize_schedule(invoices, iterations)
- puts "\nOptimális végső bank egyenleg: #{best_score.round(2)}"
- # Az optimalizált ütemterv megjelenítése: inbound számlák fizetési dátumai
- puts "\nOptimalizált fizetési dátumok (bejövő számlák):"
- best_schedule.each do |inv|
- if inv.type.downcase.include?('bejövő')
- puts "Partner: #{inv.partner}, Eredeti lejárat: #{inv.due_date}, Fizetési dátum: #{inv.payment_date}"
- end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement