Commit f9922035 authored by o@immerda.ch's avatar o@immerda.ch
Browse files

support for bill management

* import, parse and process transactions xml from postfinance
* create bills for any user
parent 398d6874
class BillsAdminController < ApplicationController
include ApiBackend
include BillUtils
def show
end
def import_statement
transactions, @other_transactions = process_xml(params[:statements].read)
@transactions = transactions.map do |tx|
tx.merge(status: update_transaction_status(tx))
end
end
def create
amount = params[:amount].to_f
subject = params[:subject]
accounts = params[:accounts].split(",")
@res = []
accounts.each do |a|
a = a.gsub(" ","")
begin
res = create_bill(api, amount, subject, a)
@res << [a, res]
rescue ApiError => e
@res << [a, e.api_msg || e.message]
rescue Exception => e
@res << [a, e.message]
end
end
end
private
def process_xml(file)
# Our IBAN
iban='CH7809000000602170959'
doc = Nokogiri::XML(file)
doc.remove_namespaces!
txs = []
div = []
doc.xpath('//Ntry').each do |e|
if e = e.at('NtryDtls')
to = e.xpath('TxDtls/RltdPties/CdtrAcct').first
if to.to_s =~ /#{iban}/
from = e.xpath('TxDtls/RltdPties/Dbtr/Nm').first.content
ref = e.xpath('TxDtls/RmtInf/Strd/CdtrRefInf/Ref').map{|r| r.content.gsub(" ","") if r.content =~ /^RF/}.compact.first
id = e.xpath('TxDtls/Refs/AcctSvcrRef').first.content
msg = e.xpath('TxDtls/RmtInf/Ustrd').map{|m| m.content}.join(" ")
amt = e.xpath('TxDtls/Amt').first
ccy = amt['Ccy']
amt = amt.content.to_f
info = {id: id, from: from, ref: ref, currency: ccy, amount: amt, msg: msg}
if ref
txs << info
else
div << info
end
end
end
end
[txs, div]
end
def update_transaction_status(tx)
ref = tx_from_creditor_reference(tx[:ref])
stat = api.get_global_transaction('bill', ref)
up = {
currency: tx[:currency],
amount: tx[:amount],
id: tx[:id],
}
if stat.nil?
api.add_global_transaction('bill', ref, [up], 6*365)
"paid"
else
if stat.any?{|t| t[:id] == up[:id]}
"already imported"
else
api.add_global_transaction('bill', ref, stat + [up], 6*365, true)
"paid to old reference"
end
end
rescue RuntimeError => e
e.message
end
end
class BillsController < ApplicationController
include ApiBackend
def to_creditor_reference(key)
str = ""
key.split("").each do |d|
d = d.downcase.chr
if d >= '0' && d <= '9'
str << d
elsif d >= 'a' && d <= 'z'
str << (d.bytes.first-'a'.bytes.first+10).to_s
else
throw d
throw :invalid_char
end
end
str2 = "#{str}271500"
rem = str2.to_i % 97
diff = 98 - rem
"RF#{'%02d' % diff}#{key}"
end
include BillUtils
def show
begin
......@@ -41,18 +23,7 @@ class BillsController < ApplicationController
end
def create
ttl = 6*365
amount = params[:amount].to_f
subject = params[:subject]
reference = api.get_fresh_global_transaction_key('bill', 20, ttl)
reference = reference['key']
if reference.length == 20
api.add_user_transaction('bill',
{reference: reference,
amount: amount,
currency: 'CHF',
subject: subject}, ttl)
end
create_bill(api, params[:amount].to_f, params[:subject])
redirect_to action: :show, pane: 'show_bills'
rescue ApiError => e
flash[:danger] = e.api_msg
......
......@@ -413,13 +413,16 @@ module ApiBackend
res['values'].map{|e| [YAML.load(e.first), DateTime.parse(e.second)] }
end
def add_user_transaction(scope, value, ttl)
post(['user_transaction', 'add'], {scope: scope, value: value.to_yaml, ttl: ttl})
def add_user_transaction(scope, value, ttl, target_user=nil)
post(['user_transaction', 'add'], {scope: scope, value: value.to_yaml, ttl: ttl, target_user: target_user})
end
def get_global_transaction(scope, key)
res = get(['global_transaction', 'get'], {scope: scope, key: key})
if res['notfound']
if res['status'] == "invalid"
raise "transaction not found"
end
if res['status'] == "empty"
return nil
end
YAML.load(res['value'])
......@@ -429,8 +432,8 @@ module ApiBackend
post(['global_transaction', 'get_fresh_key'], {scope: scope, lenght: length, ttl: ttl})
end
def add_global_transaction(scope, key, value, ttl)
post(['global_transaction', 'add'], {scope: scope, key: key, value: value.to_yaml, ttl: ttl})
def add_global_transaction(scope, key, value, ttl, allow_update=false)
post(['global_transaction', 'add'], {scope: scope, key: key, value: value.to_yaml, ttl: ttl, allow_update: allow_update})
end
end
......
module BillUtils
def tx_from_creditor_reference(ref)
ref[4..-1].upcase
end
def to_creditor_reference(key)
str = ""
key.split("").each do |d|
d = d.downcase.chr
if d >= '0' && d <= '9'
str << d
elsif d >= 'a' && d <= 'z'
str << (d.bytes.first-'a'.bytes.first+10).to_s
else
throw d
throw :invalid_char
end
end
str2 = "#{str}271500"
rem = str2.to_i % 97
diff = 98 - rem
"RF#{'%02d' % diff}#{key}"
end
def create_bill(api, amount, subject, user=nil)
ttl = 6*365
amount = amount.to_f
reference = api.get_fresh_global_transaction_key('bill', 20, ttl)
reference = reference['key']
raise "invalid reference" unless reference.length == 20
api.add_user_transaction('bill',
{reference: reference,
amount: amount,
currency: 'CHF',
subject: subject}, ttl, user)
to_creditor_reference(reference)
end
end
......@@ -8,6 +8,7 @@
reserved_identifiers: {},
jabber_ids: {label: :manage_jabber_ids},
jabber_domains: {label: :manage_jabber_domains},
bills_admin: {},
} %>
<% else %>
<% menu = {
......@@ -18,7 +19,7 @@
mail_crypt: {nudge: !mail_crypt_enabled?},
app_passwords: {},
pgpkeys: {},
bills: ({} if feature_toggle?('bills')),
bills: {},
invites: ({} if allow_invites?),
delete_account: {},
services: ({divider: true} if (any_resource_enabled? || any_resource_exists?)),
......
......@@ -55,9 +55,10 @@
<% bill, date = e
id = id+1
if bill[:paid]
p = bill[:paid]
sum[p[:currency]] ||= 0.0
sum[p[:currency]] += (p[:amount] || 0)
bill[:paid].each do |p|
sum[p[:currency]] ||= 0.0
sum[p[:currency]] += (p[:amount] || 0)
end
end %>
<%= "#{date.day}.#{date.month}.#{date.year}" %>:
<%= bill[:subject] %>
......@@ -67,10 +68,11 @@
<%= bill[:amount] %>
<% end %>
<% if bill[:paid] %>
<b><%= t(bill[:paid][:status]) %></b>
<% if bill[:paid][:amount] != bill[:amount] ||
bill[:paid][:currency] != bill[:currency] %>
(<%= bill[:paid][:currency] %> <%= bill[:paid][:amount] %>)
<b><%= t(:paid) %></b>
<% if bill[:paid].all?{|t| t[:currency] == bill[:currency]} &&
bill[:paid].map{|t| t[:amount]}.sum == bill[:amount] %>
<% else %>
<%= bill[:paid].map{|t| "#{t[:currency]} #{t[:amount]}"}.join(" + ") %>
<% end %>
<% end %>
<a href="#"
......
<h2>Issued Bills</h2>
<textarea cols=120 rows=<%=@res.size + 5%>>
<% @res.each do |r| %>
<%= r[0] %>, <%= r[1] %>
<% end %>
</textarea>
<h2><%= t("Transactions with references") %></h2>
<% @transactions.each do |t| %>
<%= t.values.join(" | ") %> </br>
<% end %>
<h2><%= t("Other Incoming Transactions") %></h2>
<% @other_transactions.each do |t| %>
<%= t.values.join(" | ") %> </br>
<% end %>
<h2><%= t("Import Statements") %></h2>
<%= form_tag 'bills_admin_import_statement', multipart: true do %>
<div class="form-group">
<%= label_tag "xml" %>
<%= file_field_tag('statements', class: 'form-control') %>
</div>
<%= submit_tag t(:submit), class: "btn btn-primary" %>
<% end %>
<hr />
<h2><%= t("Create Bills") %></h2>
<%= form_tag 'bills_admin_create' do %>
<div class="form-group">
<%= label_tag "accounts" %>
<%= text_field_tag('accounts', "", class: 'form-control') %>
</div>
<div class="form-group">
<%= label_tag "CHF" %>
<%= text_field_tag('amount', "", class: 'form-control') %>
</div>
<div class="form-group">
<%= label_tag t("subject") %>
<%= text_field_tag('subject', "", class: 'form-control') %>
</div>
<%= submit_tag t(:create), name: 'create', class: "btn btn-primary" %>
<% end %>
......@@ -119,11 +119,16 @@ Rails.application.routes.draw do
get '/resources_admin', to: 'resources_admin#show'
post '/resources_admin', to: 'resources_admin#update'
end
get '/reserved_identifiers', to: 'reserved_identifiers#show'
post '/reserved_identifiers', to: 'reserved_identifiers#release'
get '/reserved_identifiers', to: 'reserved_identifiers#show'
post '/reserved_identifiers', to: 'reserved_identifiers#release'
get '/bills_admin', to: 'bills_admin#show'
post '/bills_admin_import_statement', to: 'bills_admin#import_statement'
post '/bills_admin_create', to: 'bills_admin#create'
end
# Both, user and admin:
get '/invites', to: 'invites#generate'
post '/invites', to: 'invites#show'
end
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment