ydim 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/LICENSE.txt +339 -0
- data/Manifest.txt +43 -0
- data/README.txt +21 -0
- data/Rakefile +28 -0
- data/bin/ydim-edit +55 -0
- data/bin/ydim-inject +33 -0
- data/bin/ydimd +55 -0
- data/install.rb +1098 -0
- data/lib/ydim/autoinvoicer.rb +41 -0
- data/lib/ydim/client.rb +29 -0
- data/lib/ydim/config.rb +32 -0
- data/lib/ydim/currency_converter.rb +28 -0
- data/lib/ydim/currency_updater.rb +39 -0
- data/lib/ydim/debitor.rb +86 -0
- data/lib/ydim/factory.rb +58 -0
- data/lib/ydim/invoice.rb +146 -0
- data/lib/ydim/item.rb +36 -0
- data/lib/ydim/mail.rb +95 -0
- data/lib/ydim/odba.rb +39 -0
- data/lib/ydim/root_session.rb +143 -0
- data/lib/ydim/root_user.rb +14 -0
- data/lib/ydim/server.rb +165 -0
- data/lib/ydim/smtp_tls.rb +58 -0
- data/lib/ydim/util.rb +16 -0
- data/test/data/search.html +58 -0
- data/test/stub/odba.rb +21 -0
- data/test/suite.rb +10 -0
- data/test/test_autoinvoicer.rb +51 -0
- data/test/test_currency_converter.rb +37 -0
- data/test/test_currency_updater.rb +45 -0
- data/test/test_debitor.rb +96 -0
- data/test/test_factory.rb +99 -0
- data/test/test_invoice.rb +131 -0
- data/test/test_item.rb +41 -0
- data/test/test_mail.rb +94 -0
- data/test/test_root_session.rb +347 -0
- data/test/test_root_user.rb +20 -0
- data/test/test_server.rb +142 -0
- metadata +137 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# RootSession -- ydim -- 10.01.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'drb'
|
5
|
+
require 'encoding/character/utf-8'
|
6
|
+
require 'ydim/autoinvoicer'
|
7
|
+
require 'ydim/debitor'
|
8
|
+
require 'ydim/invoice'
|
9
|
+
require 'ydim/item'
|
10
|
+
require 'ydim/mail'
|
11
|
+
require 'odba'
|
12
|
+
|
13
|
+
module YDIM
|
14
|
+
class RootSession
|
15
|
+
attr_accessor :serv, :client
|
16
|
+
def initialize(user)
|
17
|
+
@user = user
|
18
|
+
end
|
19
|
+
def add_items(invoice_id, items, invoice_key=:invoice)
|
20
|
+
@serv.logger.debug(whoami) {
|
21
|
+
size = (items.respond_to?(:size)) ? items.size : nil
|
22
|
+
"add_items(#{invoice_id}, #{items.class}[#{size}], #{invoice_key})" }
|
23
|
+
invoice = self.send(invoice_key, invoice_id)
|
24
|
+
rate = invoice.suppress_vat ? 0 : @serv.config.vat_rate
|
25
|
+
items.each { |data|
|
26
|
+
item = Item.new({:vat_rate => rate}.update(data))
|
27
|
+
invoice.add_item(item)
|
28
|
+
}
|
29
|
+
invoice.odba_store
|
30
|
+
invoice.items
|
31
|
+
end
|
32
|
+
def autoinvoice(invoice_id)
|
33
|
+
@serv.logger.debug(whoami) { "autoinvoice #{invoice_id}" }
|
34
|
+
AutoInvoice.find_by_unique_id(invoice_id.to_s) \
|
35
|
+
or begin
|
36
|
+
msg = "invalid invoice_id: #{invoice_id}"
|
37
|
+
@serv.logger.error(whoami) { msg }
|
38
|
+
raise IndexError, msg
|
39
|
+
end
|
40
|
+
end
|
41
|
+
def collect_garbage(debitor_id=nil)
|
42
|
+
@serv.logger.info(whoami) { "collect_garbage" }
|
43
|
+
deleted = []
|
44
|
+
Invoice.odba_extent { |inv|
|
45
|
+
if([nil, inv.debitor_id].include?(debitor_id) && inv.deleted)
|
46
|
+
deleted.push(inv.info)
|
47
|
+
inv.odba_delete
|
48
|
+
end
|
49
|
+
}
|
50
|
+
deleted unless(deleted.empty?)
|
51
|
+
end
|
52
|
+
def create_autoinvoice(debitor_id)
|
53
|
+
@serv.logger.debug(whoami) { "create_autoinvoice(#{debitor_id})" }
|
54
|
+
ODBA.transaction {
|
55
|
+
@serv.factory.create_autoinvoice(debitor(debitor_id))
|
56
|
+
}
|
57
|
+
end
|
58
|
+
def create_debitor
|
59
|
+
@serv.logger.info(whoami) { "create_debitor" }
|
60
|
+
ODBA.transaction {
|
61
|
+
id = @serv.id_server.next_id(:debitor)
|
62
|
+
Debitor.new(id).odba_store
|
63
|
+
}
|
64
|
+
end
|
65
|
+
def create_invoice(debitor_id)
|
66
|
+
@serv.logger.debug(whoami) { "create_invoice(#{debitor_id})" }
|
67
|
+
ODBA.transaction {
|
68
|
+
@serv.factory.create_invoice(debitor(debitor_id))
|
69
|
+
}
|
70
|
+
end
|
71
|
+
def currency_converter
|
72
|
+
@serv.logger.debug(whoami) { "currency_converter" }
|
73
|
+
@serv.currency_converter.drb_dup
|
74
|
+
end
|
75
|
+
def debitor(debitor_id)
|
76
|
+
@serv.logger.debug(whoami) { "debitor #{debitor_id}" }
|
77
|
+
Debitor.find_by_unique_id(debitor_id.to_s)\
|
78
|
+
or begin
|
79
|
+
msg = "invalid debitor_id: #{debitor_id}"
|
80
|
+
@serv.logger.error(whoami) { msg }
|
81
|
+
raise IndexError, msg
|
82
|
+
end
|
83
|
+
end
|
84
|
+
def debitors
|
85
|
+
@serv.logger.debug(whoami) { "debitors" }
|
86
|
+
Debitor.odba_extent
|
87
|
+
end
|
88
|
+
def delete_autoinvoice(invoice_id)
|
89
|
+
@serv.logger.debug(whoami) {
|
90
|
+
"delete_autoinvoice(#{invoice_id})" }
|
91
|
+
if(invoice = autoinvoice(invoice_id))
|
92
|
+
invoice.odba_delete
|
93
|
+
end
|
94
|
+
end
|
95
|
+
def delete_item(invoice_id, index, invoice_key=:invoice)
|
96
|
+
@serv.logger.debug(whoami) {
|
97
|
+
"delete_item(#{invoice_id}, #{index}, #{invoice_key})" }
|
98
|
+
invoice = self.send(invoice_key, invoice_id)
|
99
|
+
invoice.items.delete_if { |item| item.index == index }
|
100
|
+
invoice.odba_store
|
101
|
+
invoice.items
|
102
|
+
end
|
103
|
+
def generate_invoice(invoice_id)
|
104
|
+
@serv.logger.info(whoami) { "generate_invoice(#{invoice_id})" }
|
105
|
+
invoice = autoinvoice(invoice_id)
|
106
|
+
AutoInvoicer.new(@serv).generate(invoice)
|
107
|
+
end
|
108
|
+
def invoice(invoice_id)
|
109
|
+
@serv.logger.debug(whoami) { "invoice #{invoice_id}" }
|
110
|
+
Invoice.find_by_unique_id(invoice_id.to_s) \
|
111
|
+
or begin
|
112
|
+
msg = "invalid invoice_id: #{invoice_id}"
|
113
|
+
@serv.logger.error(whoami) { msg }
|
114
|
+
raise IndexError, msg
|
115
|
+
end
|
116
|
+
end
|
117
|
+
def invoice_infos(status=nil)
|
118
|
+
@serv.logger.debug(whoami) { "invoice_infos(#{status})" }
|
119
|
+
Invoice.search_by_status(status).collect { |inv| inv.info }
|
120
|
+
end
|
121
|
+
def search_debitors(email_or_name)
|
122
|
+
@serv.logger.debug(whoami) { "search_debitors(#{email_or_name})" }
|
123
|
+
Debitor.search_by_exact_email(email_or_name) |
|
124
|
+
Debitor.search_by_exact_name(email_or_name)
|
125
|
+
end
|
126
|
+
def send_invoice(invoice_id, sort_args={})
|
127
|
+
@serv.logger.info(whoami) { "send_invoice(#{invoice_id})" }
|
128
|
+
Mail.send_invoice(@serv.config, invoice(invoice_id), sort_args)
|
129
|
+
end
|
130
|
+
def update_item(invoice_id, index, data, invoice_key=:invoice)
|
131
|
+
@serv.logger.debug(whoami) {
|
132
|
+
"update_item(#{invoice_id}, #{index}, #{data.inspect})" }
|
133
|
+
invoice = self.send(invoice_key, invoice_id)
|
134
|
+
item = invoice.item(index)
|
135
|
+
item.update(data)
|
136
|
+
invoice.odba_store
|
137
|
+
item
|
138
|
+
end
|
139
|
+
def whoami
|
140
|
+
@user.unique_id.to_s
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# User -- ydim -- 10.01.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'rrba/user'
|
5
|
+
require 'odba/drbwrapper'
|
6
|
+
require 'ydim/root_session'
|
7
|
+
|
8
|
+
module YDIM
|
9
|
+
class RootUser < RRBA::User
|
10
|
+
def new_session
|
11
|
+
ODBA::DRbWrapper.new(RootSession.new(self))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/ydim/server.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Server -- ydim -- 10.01.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require 'needle'
|
6
|
+
require 'odba/id_server'
|
7
|
+
require 'rrba/server'
|
8
|
+
require 'ydim/autoinvoicer'
|
9
|
+
require 'ydim/client'
|
10
|
+
require 'ydim/currency_converter'
|
11
|
+
require 'ydim/currency_updater'
|
12
|
+
require 'ydim/factory'
|
13
|
+
require 'ydim/root_user'
|
14
|
+
require 'ydim/util'
|
15
|
+
|
16
|
+
module YDIM
|
17
|
+
class Server
|
18
|
+
ydim_default_dir = File.join(ENV['HOME'], '.ydim')
|
19
|
+
default_config_files = [
|
20
|
+
File.join(ydim_default_dir, 'ydimd.yml'),
|
21
|
+
'/etc/ydim/ydimd.yml',
|
22
|
+
]
|
23
|
+
defaults = {
|
24
|
+
'autoinvoice_hour' => 1,
|
25
|
+
'config' => default_config_files,
|
26
|
+
'conf_dir' => File.join(ydim_default_dir, 'conf'),
|
27
|
+
'currencies' => ['CHF', 'EUR', 'USD'],
|
28
|
+
'currency_update_hour' => 2,
|
29
|
+
'data_dir' => File.join(ydim_default_dir, 'data'),
|
30
|
+
'server_url' => 'druby://localhost:12375',
|
31
|
+
'db_driver_url' => 'DBI:Pg:ydim',
|
32
|
+
'db_user' => 'ydim',
|
33
|
+
'db_auth' => '',
|
34
|
+
'detach' => false,
|
35
|
+
'home_country' => 'Schweiz',
|
36
|
+
'invoice_number_start' => 10000,
|
37
|
+
'log_level' => 'INFO',
|
38
|
+
'log_file' => STDOUT,
|
39
|
+
'mail_body' => "%s %s\n%s",
|
40
|
+
'mail_charset' => 'iso-8859-1',
|
41
|
+
'mail_from' => '',
|
42
|
+
'mail_recipients' => [],
|
43
|
+
'root_name' => 'Root',
|
44
|
+
'root_email' => '',
|
45
|
+
'root_key' => 'root_dsa',
|
46
|
+
'salutation' => {
|
47
|
+
'' => 'Sehr geehrter Herr',
|
48
|
+
'Herr' => 'Sehr geehrter Herr',
|
49
|
+
'Frau' => 'Sehr geehrte Frau',
|
50
|
+
},
|
51
|
+
'smtp_from' => '',
|
52
|
+
'smtp_authtype' => :plain,
|
53
|
+
'smtp_domain' => 'ywesee.com',
|
54
|
+
'smtp_pass' => nil,
|
55
|
+
'smtp_port' => 587,
|
56
|
+
'smtp_server' => 'localhost',
|
57
|
+
'smtp_user' => 'ydim@ywesee.com',
|
58
|
+
'vat_rate' => 7.6,
|
59
|
+
}
|
60
|
+
config = RCLConf::RCLConf.new(ARGV, defaults)
|
61
|
+
config.load(config.config)
|
62
|
+
CONFIG = config
|
63
|
+
SECONDS_IN_DAY = 24*60*60
|
64
|
+
def Server.config
|
65
|
+
CONFIG
|
66
|
+
end
|
67
|
+
def initialize(config, logger)
|
68
|
+
@serv = Needle::Registry.new
|
69
|
+
@serv.register(:auth_server) {
|
70
|
+
auth = RRBA::Server.new
|
71
|
+
root = RootUser.new(:root)
|
72
|
+
root.name = config.root_name
|
73
|
+
root.email = config.root_email
|
74
|
+
root_key = config.root_key
|
75
|
+
path = File.expand_path(root_key, config.conf_dir)
|
76
|
+
path_or_key = File.exist?(path) ? path : root_key
|
77
|
+
root.public_key = Util.load_key(path_or_key)
|
78
|
+
auth.root = root
|
79
|
+
auth
|
80
|
+
}
|
81
|
+
@serv.register(:clients) {
|
82
|
+
ClientHandler.new(@serv)
|
83
|
+
}
|
84
|
+
@serv.register(:config) {
|
85
|
+
config
|
86
|
+
}
|
87
|
+
@serv.register(:currency_converter) {
|
88
|
+
ODBA.cache.fetch_named('currency_converter', self) {
|
89
|
+
CurrencyConverter.new
|
90
|
+
}
|
91
|
+
}
|
92
|
+
@serv.register(:factory) {
|
93
|
+
Factory.new(@serv)
|
94
|
+
}
|
95
|
+
@serv.register(:id_server) {
|
96
|
+
ODBA.cache.fetch_named('id_server', self) {
|
97
|
+
ODBA::IdServer.new
|
98
|
+
}
|
99
|
+
}
|
100
|
+
@serv.register(:logger) {
|
101
|
+
logger
|
102
|
+
}
|
103
|
+
if(hour = config.autoinvoice_hour)
|
104
|
+
@autoinvoicer = repeat_at(hour, 'AutoInvoicer') {
|
105
|
+
AutoInvoicer.new(@serv).run
|
106
|
+
}
|
107
|
+
end
|
108
|
+
if(hour = config.currency_update_hour)
|
109
|
+
if(@serv.currency_converter.known_currencies \
|
110
|
+
< @serv.config.currencies.size)
|
111
|
+
CurrencyUpdater.new(@serv).run
|
112
|
+
end
|
113
|
+
@currency_updater = repeat_at(hour, 'CurrencyUpdater') {
|
114
|
+
CurrencyUpdater.new(@serv).run
|
115
|
+
}
|
116
|
+
end
|
117
|
+
@status_updater = repeat_at(1, 'StatusUpdater') {
|
118
|
+
Invoice.all { |inv| inv.save }
|
119
|
+
}
|
120
|
+
@sessions = []
|
121
|
+
end
|
122
|
+
def login(client, name=nil, &block)
|
123
|
+
@serv.logger.debug(client.__drburi) { 'attempting login' }
|
124
|
+
session = @serv.auth_server.authenticate(name, &block)
|
125
|
+
session.serv = @serv
|
126
|
+
session.client = client
|
127
|
+
@serv.logger.info(session.whoami) { 'login' }
|
128
|
+
@sessions.push(session)
|
129
|
+
session
|
130
|
+
rescue Exception => error
|
131
|
+
@serv.logger.error('unknown user') {
|
132
|
+
[error.class, error.message].join(' - ') }
|
133
|
+
raise
|
134
|
+
end
|
135
|
+
def logout(session)
|
136
|
+
@serv.logger.info(session.whoami) { 'logout' }
|
137
|
+
@sessions.delete(session)
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
def ping
|
141
|
+
true
|
142
|
+
end
|
143
|
+
private
|
144
|
+
def repeat_at(hour, thread_name)
|
145
|
+
Thread.new {
|
146
|
+
Thread.current.abort_on_exception = true
|
147
|
+
loop {
|
148
|
+
now = Time.now
|
149
|
+
next_run = Time.local(now.year, now.month, now.day, hour)
|
150
|
+
sleepy_time = next_run - now
|
151
|
+
if(sleepy_time < 0)
|
152
|
+
sleepy_time += SECONDS_IN_DAY
|
153
|
+
next_run += SECONDS_IN_DAY
|
154
|
+
end
|
155
|
+
@serv.logger.info(thread_name) {
|
156
|
+
sprintf("next run %s, sleeping %i seconds",
|
157
|
+
next_run.strftime("%c"), sleepy_time)
|
158
|
+
}
|
159
|
+
sleep(sleepy_time)
|
160
|
+
yield
|
161
|
+
}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "net/smtp"
|
3
|
+
|
4
|
+
Net::SMTP.class_eval do
|
5
|
+
private
|
6
|
+
def do_start(helodomain, user, secret, authtype)
|
7
|
+
raise IOError, 'SMTP session already started' if @started
|
8
|
+
check_auth_args user, secret, authtype if user or secret
|
9
|
+
|
10
|
+
sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
11
|
+
@socket = Net::InternetMessageIO.new(sock)
|
12
|
+
@socket.read_timeout = 60 #@read_timeout
|
13
|
+
@socket.debug_output = STDERR #@debug_output
|
14
|
+
|
15
|
+
check_response(critical { recv_response() })
|
16
|
+
do_helo(helodomain)
|
17
|
+
|
18
|
+
raise 'openssl library not installed' unless defined?(OpenSSL)
|
19
|
+
starttls
|
20
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock)
|
21
|
+
ssl.sync_close = true
|
22
|
+
ssl.connect
|
23
|
+
@socket = Net::InternetMessageIO.new(ssl)
|
24
|
+
@socket.read_timeout = 60 #@read_timeout
|
25
|
+
@socket.debug_output = STDERR #@debug_output
|
26
|
+
do_helo(helodomain)
|
27
|
+
|
28
|
+
authenticate user, secret, authtype if user
|
29
|
+
@started = true
|
30
|
+
ensure
|
31
|
+
unless @started
|
32
|
+
# authentication failed, cancel connection.
|
33
|
+
@socket.close if not @started and @socket and not @socket.closed?
|
34
|
+
@socket = nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def do_helo(helodomain)
|
39
|
+
begin
|
40
|
+
if @esmtp
|
41
|
+
ehlo helodomain
|
42
|
+
else
|
43
|
+
helo helodomain
|
44
|
+
end
|
45
|
+
rescue Net::ProtocolError
|
46
|
+
if @esmtp
|
47
|
+
@esmtp = false
|
48
|
+
@error_occured = false
|
49
|
+
retry
|
50
|
+
end
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def starttls
|
56
|
+
getok('STARTTLS')
|
57
|
+
end
|
58
|
+
end
|
data/lib/ydim/util.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Util -- ydim -- 10.01.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'openssl'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module YDIM
|
8
|
+
module Util
|
9
|
+
def Util.load_key(key)
|
10
|
+
if(File.exist?(key))
|
11
|
+
key = File.read(key)
|
12
|
+
end
|
13
|
+
key = OpenSSL::PKey::DSA.new(key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<html><head><meta HTTP-EQUIV="content-type" CONTENT="text/html; charset=UTF-8"><title>1 USD in CHF - Google Search</title><style><!--
|
2
|
+
body,td,div,.p,a{font-family:arial,sans-serif }
|
3
|
+
div,td{color:#000}
|
4
|
+
.f{color:#6f6f6f}
|
5
|
+
.fl:link{color:#77c}
|
6
|
+
a:link,.w,a.w:link,.w a:link{color:#00c}
|
7
|
+
a:visited,.fl:visited{color:#551a8b}
|
8
|
+
a:active,.fl:active{color:#f00}
|
9
|
+
.t a:link,.t a:active,.t a:visited,.t{color:#000}
|
10
|
+
.t{background-color:#e5ecf9}
|
11
|
+
.k{background-color:#36c}
|
12
|
+
.j{width:34em}
|
13
|
+
.h{color:#36c;font-size:14px}
|
14
|
+
.i,.i:link{color:#a90a08}
|
15
|
+
.a,.a:link{color:#008000}
|
16
|
+
.z{display:none}
|
17
|
+
div.n{margin-top:1ex}
|
18
|
+
.n a{font-size:10pt;color:#000}
|
19
|
+
.n .i{font-size:10pt;font-weight:bold}
|
20
|
+
.q:visited,.q:link,.q:active,.q{color:#00c;}
|
21
|
+
.b{font-size:12pt;color:#00c;font-weight:bold}
|
22
|
+
.ch{cursor:pointer;cursor:hand}
|
23
|
+
.sem{display:inline;margin:0;font-size:100%;font-weight:inherit}
|
24
|
+
.e{margin-top:.75em;margin-bottom:.75em}
|
25
|
+
.g{margin-top:1em;margin-bottom:1em}
|
26
|
+
.m{color:#666666;font-size:83%}
|
27
|
+
.ml:link{color:#551a8b;}
|
28
|
+
.sm{display:block;margin-top:0px;margin-bottom:0px;margin-left:40px}
|
29
|
+
.bl{display:none}
|
30
|
+
.fl2,.fl2:link,.fl2:visited{color:#7777CC}
|
31
|
+
.fl2:active{color:#f00}
|
32
|
+
.gh{background-color:#FFFF88;}
|
33
|
+
-->
|
34
|
+
</style>
|
35
|
+
<script>
|
36
|
+
<!--
|
37
|
+
if(document.images){new Image().src="/url?sa=Q&q=1+USD+in+CHF&ct=q&ei=I8_gQ4bMF9DqiwHQ9JHlCQ&sig2=UD3Tzz5it4Ox-8P_bqGNQQ";}
|
38
|
+
function ss(w,id){window.status=w;return true;}
|
39
|
+
function cs(){window.status='';}
|
40
|
+
function rwt(el,ct,cd,sg){el.href="/url?sa=t&ct="+escape(ct)+"&cd="+escape(cd)+"&url="+escape(el.href).replace(/\+/g,"%2B")+"&ei=I8_gQ4bMF9DqiwHQ9JHlCQ"+sg;el.onmousedown="";return true;}
|
41
|
+
var q="1+USD+in+CHF";var eid="I8_gQ4bMF9DqiwHQ9JHlCQ";document.write("<style type=text/css>.bl{ display:inline !important;}</style>");function _inv(){if(window.location.hash=="#invalid"){var a=window.location.href;window.location.replace(a.substring(0,a.indexOf("#")));if(window.location.hash=="#invalid"){window.location.hash=""}if(window.location.hash==""||window.location.hash=="#"){window.location.reload(true)}}}_inv();function ga(o,e) {if (document.getElementById) {var a = o.id.substring(1); var p = "", r = "", t, f, h;var g = e.target;if (g) { t = g.id;f = g.parentNode;if (f) {p = f.id;h = f.parentNode;if (h)r = h.id;}} else {h = e.srcElement;f = h.parentNode;if (f)p = f.id;t = h.id;}if (t==a || p==a || r==a)return true;document.getElementById(a).href += "&ct=bg";top.location.href=document.getElementById(a).href}}
|
42
|
+
//-->
|
43
|
+
</script>
|
44
|
+
</head><body bgcolor=#ffffff onload="document.gs.reset();" topmargin=3 marginheight=3><table border=0 cellspacing=0 cellpadding=0 width=100%><tr><td align=right nowrap><font size=-1><b>hannes.wyss@gmail.com</b> | <a href="/searchhistory/?hl=en">Search History</a> | <a href="https://www.google.com/accounts/ManageAccount">My Account</a> | <a href="http://www.google.com/accounts/Logout?continue=http://www.google.com/search%3Fq%3D1%2BUSD%2Bin%2BCHF">Sign out</a></font></td></tr><tr height=4><td><img alt="" width=1 height=1></td></tr></table><table border=0 cellpadding=0 cellspacing=0 width=100%><tr><form name=gs method=GET action=/search><td valign=top><a href="http://www.google.com/webhp?hl=en"><img src="/images/logo_sm.gif" width=150 height=55 alt="Go to Google Home" border=0 vspace=12></a></td><td> </td><td valign=top width=100%><table cellpadding=0 cellspacing=0 border=0><tr><td height=14 valign=bottom><script><!--
|
45
|
+
function qs(el) {if (window.RegExp && window.encodeURIComponent) {var ue=el.href;var qe=encodeURIComponent(document.gs.q.value);if(ue.indexOf("q=")!=-1){el.href=ue.replace(new RegExp("q=[^&$]*"),"q="+qe);}else{el.href=ue+"&q="+qe;}}return 1;}
|
46
|
+
// -->
|
47
|
+
</script><table border=0 cellpadding=4 cellspacing=0><tr><td><font size=-1><b>Web</b> <a id=t1a class=q href="http://images.google.com/images?q=1+USD+in+CHF&sa=N&tab=wi" onClick="return qs(this);">Images</a> <a id=t2a class=q href="http://groups.google.com/groups?q=1+USD+in+CHF&sa=N&tab=wg" onClick="return qs(this);">Groups</a> <a id=t4a class=q href="http://news.google.com/news?q=1+USD+in+CHF&sa=N&tab=wn" onClick="return qs(this);">News</a> <a id=t5a class=q href="http://froogle.google.com/froogle?q=1+USD+in+CHF&sa=N&tab=wf" onClick="return qs(this);">Froogle</a> <a id=t7a class=q href="http://local.google.com/local?q=1+USD+in+CHF&sa=N&tab=wl" onClick="return qs(this);">Local</a> <b><a href="/intl/en/options/" class=q>more »</a></b></font></td></tr></table></td></tr><tr><td><table border=0 cellpadding=0 cellspacing=0><tr><td nowrap><input type=hidden name=hl value="en"><input type=hidden name=lr value=""><input type=hidden name=c2coff value=1><input type=text name=q size=41 maxlength=2048 value="1 USD in CHF" title="Search"><font size=-1> <input type=submit name="btnG" value="Search"><span id=hf></span></font></td><td nowrap><font size=-2> <a href=/advanced_search?q=1+USD+in+CHF&hl=en&lr=&c2coff=1>Advanced Search</a><br> <a href=/preferences?q=1+USD+in+CHF&hl=en&lr=&c2coff=1>Preferences</a> </font></td></tr></table></td></tr></table><table cellpadding=0 cellspacing=0 border=0><tr><td><font size=-1></font></td></tr><tr><td height=7><img width=1 height=1 alt=""></td></tr></table></td></form></tr></table><table width=100% border=0 cellpadding=0 cellspacing=0><tr><td bgcolor=#3366cc><img width=1 height=1 alt=""></td></tr></table><table width=100% border=0 cellpadding=0 cellspacing=0 bgcolor=#e5ecf9><tr><td bgcolor=#e5ecf9 width=1% nowrap><font size=+1> <b>Web</b></font> </td></tr></table>
|
48
|
+
<script><!--
|
49
|
+
var tt = document.gs.q;
|
50
|
+
tt.focus();
|
51
|
+
var r = tt.createTextRange;
|
52
|
+
if (r) {
|
53
|
+
var s = r();
|
54
|
+
s.collapse(false);
|
55
|
+
s.select(); }
|
56
|
+
// -->
|
57
|
+
</script>
|
58
|
+
<p><table><tr><td><img src=/images/calc_img.gif></td><td> </td><td nowrap><font size=+1><b>1 U.S. dollar = 1.2864003 Swiss francs</b></td></tr><tr><td> </td><td> </td><td><font size=-1><nobr>Rates provided for information only - <a href="/help/currency_disclaimer.html">see disclaimer</a>.</nobr> <a href=/help/features.html#currency>More about currency conversion.</a></td></tr></table><br><table><tr><td><font size=+0>Search for documents containing the terms <a href=/search?hl=en&lr=&c2coff=1&q=%2B1+USD+in+CHF&nocalc=1><b><i>1 USD in CHF</i></b></a>.</td></tr></table><br><br><br clear=all><center><p><hr class=z><table width=100% border=0 cellpadding=0 cellspacing=0><tr><td bgcolor=#3366cc colspan=2><img width=1 height=1 alt=""></td></tr></table><table width=100% cellpadding=2 cellspacing=0 border=0><tr><td align=center class=t><font size=-1><a href="http://www.google.com/">Google Home</a> - <a href="/ads/">Advertising Programs</a> - <a href="/services/">Business Solutions</a> - <a href=/about.html>About Google</a></font></table><br><font size=-1 class=p>©2006 Google</font></center></body></html>
|
data/test/stub/odba.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ODBA Stub -- ydim -- 24.01.2005 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'odba/odba'
|
5
|
+
|
6
|
+
module ODBA
|
7
|
+
def ODBA.transaction(&block)
|
8
|
+
block.call
|
9
|
+
end
|
10
|
+
module Persistable
|
11
|
+
attr_reader :odba_stored
|
12
|
+
def odba_store
|
13
|
+
@odba_stored = @odba_stored.to_i.next
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class Cache
|
17
|
+
def fetch_named(*args, &block)
|
18
|
+
block.call
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/test/suite.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# TestAutoInvoicer -- ydim -- 01.02.2007 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'flexmock'
|
8
|
+
require 'ydim/autoinvoicer'
|
9
|
+
require 'ydim/invoice'
|
10
|
+
|
11
|
+
module YDIM
|
12
|
+
class TestAutoInvoicer < Test::Unit::TestCase
|
13
|
+
include FlexMock::TestCase
|
14
|
+
def setup
|
15
|
+
@serv = flexmock('Registry')
|
16
|
+
@autoinvoicer = AutoInvoicer.new(@serv)
|
17
|
+
end
|
18
|
+
def test_run
|
19
|
+
deb1 = flexmock('Debitor1')
|
20
|
+
deb1.should_receive(:autoinvoices).and_return([])
|
21
|
+
deb2 = flexmock('Debitor2')
|
22
|
+
inv1 = flexmock('AutoInvoice1')
|
23
|
+
inv1.should_receive(:total_netto).and_return(1)
|
24
|
+
inv1.should_receive(:date).and_return(Date.today)
|
25
|
+
inv2 = flexmock('AutoInvoice2')
|
26
|
+
inv2.should_receive(:total_netto).and_return(1)
|
27
|
+
inv2.should_receive(:date).and_return(Date.today >> 1)
|
28
|
+
inv3 = flexmock('AutoInvoice3')
|
29
|
+
inv3.should_receive(:total_netto).and_return(0)
|
30
|
+
deb2.should_receive(:autoinvoices).and_return([inv1, inv2, inv3])
|
31
|
+
debitors = { 0 => deb1, 1 => deb2 }
|
32
|
+
flexstub(Debitor).should_receive(:odba_extent).and_return { |blk|
|
33
|
+
debitors.each_value(&blk)
|
34
|
+
}
|
35
|
+
@serv.should_receive(:config).and_return('configuration')
|
36
|
+
factory = flexmock('Factory')
|
37
|
+
factory.should_receive(:generate_invoice).with(inv1)\
|
38
|
+
.times(1).and_return(:generated_invoice)
|
39
|
+
@serv.should_receive(:factory).and_return(factory)
|
40
|
+
ODBA.cache = cache = flexmock('ODBA')
|
41
|
+
cache.should_receive(:transaction).and_return { |bl| bl.call }
|
42
|
+
mail = flexstub(Mail)
|
43
|
+
mail.should_receive(:send_invoice)\
|
44
|
+
.with('configuration', :generated_invoice)\
|
45
|
+
.and_return { assert(true) }
|
46
|
+
mail.should_receive(:send_reminder).with('configuration', inv2)\
|
47
|
+
.and_return { assert(true) }
|
48
|
+
@autoinvoicer.run
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# TestCurrencyConverter -- ydim -- 01.02.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'ydim/currency_converter'
|
8
|
+
|
9
|
+
module YDIM
|
10
|
+
class TestCurrencyConverter < Test::Unit::TestCase
|
11
|
+
def setup
|
12
|
+
@converter = CurrencyConverter.new
|
13
|
+
end
|
14
|
+
def test_known_currencies
|
15
|
+
assert_equal(0, @converter.known_currencies)
|
16
|
+
@converter.store('EUR', 'CHF', 1.55474)
|
17
|
+
assert_equal(2, @converter.known_currencies)
|
18
|
+
@converter.store('USD', 'CHF', 1.28640)
|
19
|
+
assert_equal(3, @converter.known_currencies)
|
20
|
+
end
|
21
|
+
def test_convert
|
22
|
+
@converter.store('EUR', 'CHF', 1.55474)
|
23
|
+
assert_equal(1.55474, @converter.convert(1, 'EUR', 'CHF'))
|
24
|
+
assert_equal(1, @converter.convert(1, 'CHF', 'CHF'))
|
25
|
+
assert_equal(1/1.55474, @converter.convert(1, 'CHF', 'EUR'))
|
26
|
+
assert_raises(RuntimeError) {
|
27
|
+
@converter.convert(1, 'RND', 'CHF')
|
28
|
+
}
|
29
|
+
end
|
30
|
+
def test_drb_dup
|
31
|
+
@converter.store('EUR', 'CHF', 1.55474)
|
32
|
+
dup = @converter.drb_dup
|
33
|
+
assert_instance_of(MobileCurrencyConverter, dup)
|
34
|
+
assert_equal(1.55474, dup.convert(1, 'EUR', 'CHF'))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# TestCurrencyUpdater -- ydim -- 01.02.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
|
5
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
require 'ydim/currency_updater'
|
9
|
+
require 'flexmock'
|
10
|
+
|
11
|
+
module YDIM
|
12
|
+
class TestCurrencyUpdater < Test::Unit::TestCase
|
13
|
+
include FlexMock::TestCase
|
14
|
+
def setup
|
15
|
+
@serv = flexmock('Config')
|
16
|
+
@updater = CurrencyUpdater.new(@serv)
|
17
|
+
end
|
18
|
+
def test_extract_conversion
|
19
|
+
html = File.read(File.expand_path('data/search.html',
|
20
|
+
File.dirname(__FILE__)))
|
21
|
+
assert_equal('1.2864003', @updater.extract_conversion(html))
|
22
|
+
end
|
23
|
+
def test_run
|
24
|
+
resp = flexmock('HttpResponse')
|
25
|
+
resp.should_receive(:body)\
|
26
|
+
.and_return(File.read(File.expand_path('data/search.html',
|
27
|
+
File.dirname(__FILE__))))
|
28
|
+
session = flexmock('HttpSession')
|
29
|
+
session.should_receive(:get).with('/search?q=1+CHF+in+EUR')\
|
30
|
+
.and_return(resp)
|
31
|
+
flexstub(Net::HTTP).should_receive(:start)\
|
32
|
+
.times(1).and_return { |host, block|
|
33
|
+
block.call(session)
|
34
|
+
}
|
35
|
+
conv = flexmock('Converter')
|
36
|
+
conv.should_receive(:odba_store).times(1)
|
37
|
+
conv.should_receive(:store).times(1).times(1)
|
38
|
+
config = flexmock('Config')
|
39
|
+
config.should_receive(:currencies).and_return(['CHF', 'EUR'])
|
40
|
+
@serv.should_receive(:currency_converter).and_return(conv)
|
41
|
+
@serv.should_receive(:config).and_return(config)
|
42
|
+
@updater.run
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|