blob: 343018893638d4fd0300d152d651d8215f22225b [file] [log] [blame]
#!/usr/bin/env ruby
# encoding: utf-8
require 'wunderbar'
require "date"
require "yaml"
require 'whimsy/asf'
user = ASF::Person.new($USER)
unless user.asf_member? or ASF.pmc_chairs.include? user or $USER=='ea'
print "Status: 401 Unauthorized\r\n"
print "WWW-Authenticate: Basic realm=\"ASF Members and Officers\"\r\n\r\n"
exit
end
HISTORY = '/var/tools/invoice'
if %r{/(?<invoice>\d+)(\.\w+)?$} =~ ENV['PATH_INFO']
if File.exist? "#{HISTORY}/#{invoice.untaint}"
form = YAML.load_file("#{HISTORY}/#{invoice.untaint}")
ENV['QUERY_STRING'] =
form.map {|k,v| "#{k}=#{CGI.escape(v.first)}"}.join("&") if form
end
end
_html do
_head_ do
_title "ASF Invoice #{invoice}"
_style %{
.c1 {padding-top:2pt;margin-right:9pt;text-align:right;padding-bottom:2pt}
.c5 {vertical-align:top;width:51.8pt}
.c7 {vertical-align:top;width:134.1pt;border-style:solid;background-color:#f2f2f2;border-color:#888;border-width:1pt;padding:0pt 3.6pt 0pt 3.6pt}
table {border-collapse:collapse}
tr {height: 15pt}
.c10 {vertical-align:top;width:76.5pt;border-style:solid;border-color:#888;border-width:1pt;padding:0pt 3.6pt 0pt 3.6pt}
.c11 {vertical-align:top;width:72pt;border-style:solid;border-color:#888;border-width:1pt;padding:0pt 3.6pt 0pt 3.6pt}
.c13 {color:#404040}
.c15 {vertical-align:top;width:396pt;border-style:solid;border-color:#888;border-width:1pt;padding:0pt 3.6pt 0pt 3.6pt}
.c16 {vertical-align:top;width:67.5pt;padding:0pt 3.6pt 0pt 3.6pt}
.c17 {padding-top:2pt;padding-bottom:4pt}
.c18 {vertical-align:top;width:267.8pt;padding:0pt 3.6pt 0pt 3.6pt}
.c20 {vertical-align:top;width:267.8pt}
.c21 {text-align:right}
.c23 {width:287.2pt;padding:0pt 3.6pt 0pt 3.6pt}
.c26 {width:220.5pt}
.c29 {padding-top:2pt;text-align:center;padding-bottom:2pt}
.c30 {margin: 1em 5em}
th {font-size: 10pt; font-weight: normal; }
p {min-height:9pt;font-size:9pt;margin:0;font-family:Verdana}
form, img {margin-left: auto; margin-right: auto; display: block; padding-bottom: 10pt}
body {max-width:540pt; padding:0 36pt}
input, textarea {width: 300pt}
input[name=total] {background-color: #8e8; color: #000; border: outset 1px; padding: 2px; border-radius: 0.3em}
input[type=button] {width: 73pt}
.index {border-spacing: 20px 5px; border-collapse: separate}
.index th {border-bottom: solid black}
.index input[type=submit] {width: 10em; display: block; margin-left: auto; margin-right: auto}
.buttons a {text-decoration: none}
.buttons button {width: 100pt}
}
_script src: '/jquery.min.js'
_script src: '/jquery-ui.min.js'
end
_body? do
_img alt: "Logo", src: "https://id.apache.org/img/asf_logo_wide.png"
_svg width: '100%', height: 30 do
_path d: 'M0,0h230v14h-230z', fill: '#636'
_path d: 'M245,0h230v14h-230z', fill: '#996'
_path d: 'M490,0h230v14h-230z', fill: '#669'
end
invoice ||= @invoice_number
invoice ||= Dir.chdir(HISTORY) {Dir['*'].max || 1000}.succ
@invoice_number = nil if ENV['REQUEST_URI'].end_with? '?'
base = ENV['REQUEST_URI'].dup
base.chomp! ENV['QUERY_STRING']
base.chomp! '?'
base.chomp! ENV['PATH_INFO']
if ENV['PATH_INFO'].to_s.end_with? '/'
_table_.index do
_thead_ do
_tr do
_td colspan: 4 do
_form action: "#{base}/#{invoice}" do
_input type: 'submit', value: 'New Invoice'
end
end
end
_tr do
_th 'Invoice'
_th 'Date'
_th 'Customer'
_th 'Amount'
end
end
_tbody do
Dir.chdir(HISTORY) do
Dir['*'].sort.reverse.each do |invoice|
form = YAML.load_file("#{HISTORY}/#{invoice.untaint}")
if form
_tr_ do
_td {_a invoice, href: invoice}
_td Date.parse(form['date'].first)
_td form['customer'].first
_td.c21 form['total'].first.sub('$ ', '')
end
end
end
end
end
end
elsif not @invoice_number and not _.pdf?
start = Date.today
finish = Date.new(start.year+1, start.month, start.day)-1
_form_ method: 'post', action: "#{base}/#{invoice}" do
_table style: 'margin-left: auto; margin-right: auto' do
_tr.presets! style: 'display: none' do
_td 'Presets'
_td do
_input type: 'button', value: 'Bronze', 'data-amount' => 5_000
_input type: 'button', value: 'Silver', 'data-amount' => 20_000
_input type: 'button', value: 'Gold', 'data-amount' => 40_000
_input type: 'button', value: 'Platinum', 'data-amount' => 100_000
end
end
_tr do
_td 'E-Mail'
_td do
_input type: 'email', name: 'email',
value: @email || 'fundraising@apache.org'
end
end
_tr do
_td 'Invoice Number'
_td { _input name: 'invoice_number', value: invoice}
end
_tr do
_td 'Customer Name'
_td do
_input name: 'customer', value: @customer,
required: true, autofocus: true
end
end
_tr do
_td 'Purchase Order #'
_td { _input name: 'po_number', value: @po_number }
end
_tr do
_td 'Bill to'
_td { _textarea @bill_to, name: 'bill_to', rows: 6, required: true }
end
_tr do
_td 'Item Description'
_td do
_textarea @item, name: 'item', rows: 6, required: true,
placeholder: "quantity - description @ $ price"
end
end
_tr do
_td 'Amount'
_td do
_input name: 'total', value: "#{@total or '$ 0'}"
end
end
end
_input name: 'date', type: 'hidden', value: start.strftime("%B %d, %Y")
_input type: 'submit', value: 'Save', style: 'margin-top: 1em;
width: 10em; display: block; margin-left: auto; margin-right: auto'
end
_div.instructions! do
_h4 'Instructions:'
_p.c30 do
_'The'
_em 'Amount'
_ 'field contains the sum of the dollar amounts entered in the'
_em 'Item Description'
_ 'field.'
end
_p.c30 do
_ 'To have the dollar amount placed in the third column of the'
_ 'invoice form, place it at the end of the line preceeded by an'
_em '@'
_ 'sign.'
end
_p.c30 do
_ 'To enter a quantity, start the line with an integer followed by a'
_em '-'
_ '(dash) character.'
end
end
_script %{
$('#presets').show();
$('#presets input[type=button]').click(function() {
var amount = '$ ' + $(this).attr('data-amount');
amount = amount.toString().replace(/(\\d)(?=(\\d\\d\\d)+$)/g, "$1,");
var item = "2013 " + $(this).val() + " Sponsorship @ " +
amount + "\\n\\n";
item += "Start Date: #{start.strftime("%B %d, %Y")}\\n\";
item += "End Date: #{finish.strftime("%B %d, %Y")}\\n";
$('textarea[name=item]').val(item).keyup();
if ($('input[name=customer]').val() == '') {
$('input[name=customer]').focus();
}
});
$('textarea[name=item]').keyup(function() {
var total = 0;
// Process each line in turn
var lines = $(this).val().match(/[^\\r\\n]+/g);
for (var i=0; lines && i<lines.length; i++) {
var line = lines[i];
// Look for a $price at the end
var price = line.match(/\\$\\s?([,\\d]+(\\.\\d\\d)?)$/);
if (price && price.length > 0) {
// Bingo, it's a price one
var amt = parseFloat(price[1].replace(/,/,''));
// Did they give a quantity at the start?
var qty = line.match(/^(\\d+)\\s*[\\@\\-]/);
if (qty && qty.length > 0) {
// This is a "quantity - text @ $price"
var quantity = parseInt( qty[1] );
total += (quantity * amt);
} else {
// This is a "text $price"
total += amt;
}
}
}
// Turn it into a $ figure with commas
// TODO Support other currencies
total = total.toFixed(2);
total = total.replace(/(\\d)(?=(\\d\\d\\d)+[$\\.])/g, "$1,");
if ($('input[name=total]').val() != '$ ' + total) {
$('input[name=total]').stop().css('backgroundColor', '#FF0').
val('$ ' + total).animate({'backgroundColor': '#8e8'}, 1000);
$("input[type=submit]").attr('disabled', (total=='0'));
}
}).keyup();
$("input[name=invoice_number],input[name=total]").
focus(function(){ $(this).blur(); });
}
else
_table_ do
unless _.pdf?
_thead_ do
_tr do
_th.buttons colspan: 2 do
_a href: "#{base}/#{invoice}?" do
_button 'Edit'
end
_a href: "#{base}/#{invoice}.pdf" do
_button 'Generate PDF'
end
end
end
end
end
_tbody do
_tr do
_td style: "width: 270pt; color: #006" do
_p "Dept. 9660"
_p "Los Angeles, CA 90084-9660, USA"
_p "E-mail: #{@email ||'fundraising@apache.org'}"
_p "US IRS Tax/EIN: 47-0825376"
end
_td style: "width: 270pt; text-align: right" do
_p "Invoice", style: "font-size: 28pt; color: #636"
_p @date
end
end
end
end
_p
_table_ do
_tbody do
_tr do
_td.c26 do
_table do
_tbody do
_tr do
_td.c16 do
_p "Invoice No.", class: "c17 c13"
end
_td.c23 do
_p @invoice_number
end
end
_tr do
_td.c16 do
_p "Customer:", class: "c17 c13"
end
_td.c23 do
_p @customer
end
end
if @po_number and not @po_number.empty?
_tr do
_td.c16 do
_p "Reference:", class: "c17 c13"
end
_td.c23 do
_p "PO##{@po_number}"
end
end
end
end
end
end
_td.c5
_td.c20 do
_table do
_tbody do
_tr do
_td.c18 do
_p 'Bill To:', class: "c17 c13"
end
end
_tr do
_td.c18 do
@bill_to.lines.each do |line|
_p line.chomp
end
end
end
end
end
end
end
end
end
_p style: 'height: 30pt'
_table_ do
_thead do
_tr do
_th 'Quantity', class: "c11"
_th 'Item', class: "c15"
_th 'Total', class: "c11"
end
end
_tbody do
@item.lines.each do |line|
line.gsub!(/^(\d+)\s-\s*/,'')
quantity = $1
if line.match(/[-@]?\s?\$\s?([,\d\.]+)$/)
amt = $1.gsub(',', '')
quantity ||= '1'
price = quantity.to_i * amt.to_f
# Format the float as a 2dp number
price = "%0.2f" % price
# Now make it look pretty with commas
price = price.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, '\1,')
else
quantity = price = ''
end
_tr do
_td.c11 do
_p.c29 quantity
end
_td.c15 do
_p.c17 line.chomp
end
_td.c10 do
_p.c1 price
end
end
end
(10-@item.lines.count).times do
_tr do
_td.c11
_td.c15
_td.c10
end
end
end
end
_p
_table_ style: "margin-left: auto" do
_tbody do
_tr do
_td.c7 do
_p "Subtotal:", class: "c17 c21 c13"
end
_td.c10 do
_p.c1 @total
end
end
_tr do
_td.c7 do
_p 'Tax:', class: "c17 c21 c13"
end
_td.c10 do
_p.c1 "-"
end
end
_tr do
_td.c7 do
_p 'Shipping:', class: "c17 c21 c13"
end
_td.c10 do
_p.c1 '-'
end
end
_tr do
_td.c7 do
_p "Miscellaneous:", class: "c17 c21 c13"
end
_td.c10 do
_p.c1 "-"
end
end
_tr do
_td.c7 do
_p "Balance Due:", class: "c17 c21 c13"
end
_td.c10 do
_p.c1 @total
end
end
end
end
_div style: "margin-top: 30pt; color: #006" do
_p "Please make checks payable to “The Apache Software Foundation”."
_p
_p "Wire and ACH payments information:"
_p "Beneficiary: “Apache Software Foundation”"
_p "Routing #: 121 000 248 (for domestic wire or ACH)"
_p "SWIFT: WFBIUS6S (for international wire)"
_p "Account #: 3189163755"
_p "Wells Fargo Bank"
end
@invoice_number.untaint if @invoice_number =~ /^\d+$/
File.open("#{HISTORY}/#{@invoice_number}", 'w') do |file|
file.write params.to_yaml
end
end
end
end