# coding: utf-8 require 'yaml' require 'mysql2' require 'optparse' require 'digest' load 'config.rb' Options = {} OptionParser.new do |opts| opts.banner = "Usage: invoice.rb [options]" opts.on("-d", "--draft", "Generate invoices with 'DRAFT' watermark") do |d| Options[:draft] = d end opts.on("-v", "--verbose", "Run verbosely") do |v| Options[:verbose] = v end opts.on("-s", "--silent", "Run silently (overrules -v)") do |s| Options[:silent] = true Options[:verbose] = false end opts.on("-m", "--mobilepay", "Generate QR codes for MobilePay") do |m| require 'rqrcode' Options[:mobilepay] = m end end.parse! if Options[:draft] Options[:mobilepay] = false end Transaction = Struct.new(:time, :person, :product, :amount) Product = Struct.new(:id, :description, :price) do def to_s "#{id} => #{description}, #{price}" end end class Person def initialize(id, name, address) @id = id @name = name @address = address @purchases = {} end def to_s id end attr_reader :id attr_accessor :name, :purchases, :address end def read_file(file) rows = Array.new CSV.foreach(file, col_sep: ';', converters: :float) do |row| rows << Transaction.new(row[0],row[1],row[2],row[3]) end rows end def load_transactions_from_db(db) transactions = Array.new db.query("SELECT * FROM Transactions").each do |row| if Date.today.monday? previous_monday = Date.today.to_time else previous_monday = date_of_prev('monday').to_time end if previous_monday < row["time"] transactions << Transaction.new(row["time"], row["buyer"], row["product"], row["amount"]) end end return transactions end def load_persons_from_db(db) persons = {} db.query("SELECT * FROM Persons").each do |row| address = [] if (row['address']) address << row['address'] end person = Person.new(row['id'],row['name'],address) persons[row['id']] = person if Options[:verbose] puts "Found person: #{person}" end end return persons end def load_products_from_db(db) products = {} db.query("SELECT * FROM Products").each do |p| product = Product.new(p['id'], p['description'], p['price']) products[p['id']] = product if Options[:verbose] puts "Found product: #{product}" end end return products end # Partition transactions into the persons who they belong to def partition_transactions(transactions,persons) transactions.each do |t| # Initialize data structures if not already done if !(persons[t.person].purchases[t.product]) persons[t.person].purchases[t.product] = 0 end persons[t.person].purchases[t.product] += t.amount end return persons end def generate_receipt(persons,products,db,draft) counter = 0 # Iterate through persons who have purchased something in the given timeframe persons.each do |id, person| # Skip persons with no transactions if person.purchases.empty? if Options[:verbose] puts "Skipping #{id} with no transactions" end next end counter += 1 total_payment = 0 yaml = YAML_BASE.clone yaml["to"] = [person.name].concat person.address yaml["invoice-nr"] = Time.now.strftime('%Y%W') + "-" + counter.to_s # Iterate through products purchased and write data to yaml hash person.purchases.each do |id, amount| yaml["service"] = Array.new unless yaml["service"] piece_price = products[id].price.to_i price = piece_price.to_i*amount hash = {description: products[id].description, pieceprice: piece_price, price: price, amount: amount} total_payment += price # Convert Symbols to Strings to ensure compatibility with pandoc yaml["service"] << Hash[hash.map{ |k, v| [k.to_s, v] }] end total_payment = total_payment * 0.90 # Write draft watermark on invoice if marked as draft if draft yaml["draft"] = "true" yaml["drafttext"] = "Udkast" end # Generate QR code for MobilePay if option set if Options[:mobilepay] mobilepay_url = "https://mobilepay.dk/da-dk/pages/betal.aspx?phone=#{MOBILEPAY_PHONE_NUMBER}&amount=#{total_payment.round(2)}&comment=Tilbagebetaling%20-%20Faktura%20nr.%20#{yaml["invoice-nr"]}&lock=1" if Options[:verbose] puts "Generating QR code for #{id} with URL #{mobilepay_url}" end yaml["qrcode"] = true qrcode = RQRCode::QRCode.new(mobilepay_url) image = qrcode.as_png( resize_gte_to: false, resize_exactly_to: false, fill: 'white', color: 'black', size: 480, border_modules: 4, module_px_size: 6, file: nil # path to write ) File.open("./pandoc/qr.png","w+") do |file| file << image end end # Convert Symbols to Strings to ensure compatibility with pandoc output = Hash[yaml.map{ |k, v| [k.to_s, v] }].to_yaml output += "---\n" # Write output to file File.open("./pandoc/details.yml","w+") do |file| file << output end # Generate PDF using pandoc `cd pandoc/; make -B` `cp pandoc/output.pdf #{OUTPUT_PATH}/#{yaml["invoice-nr"]}.pdf` # Upload PDF to MySQL database File.open("pandoc/output.pdf") do |file| data = file.read hash = Digest::MD5.new.hexdigest data statement = db.prepare("INSERT INTO Invoices (id, person, data, hash) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE person = ?, data = ?, hash = ?") result = statement.execute(yaml["invoice-nr"],id,data,hash,id,data,hash) end puts "#{person}: #{yaml["invoice-nr"]}.pdf" unless Options[:silent] end end def date_of_next(day) date = Date.parse(day) delta = date > Date.today ? 0 : 7 date + delta end def date_of_prev(day) date = Date.parse(day) delta = date < Date.today ? 0 : 7 date - delta end ################# db = Mysql2::Client.new(:host => DB_HOST, :username => DB_USER, :database => DB_DB) transactions = load_transactions_from_db(db) persons = load_persons_from_db(db) products = load_products_from_db(db) generate_receipt(partition_transactions(transactions,persons), products, db, Options[:draft])