bookkeeper 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/app/assets/javascripts/bookkeeper.js +2 -0
  2. data/app/controllers/bookkeeper/balance_controller.rb +15 -0
  3. data/app/controllers/bookkeeper/movements_controller.rb +67 -0
  4. data/app/models/bookkeeper/account.rb +25 -0
  5. data/app/models/bookkeeper/categories.rb +9 -0
  6. data/app/models/bookkeeper/movement.rb +27 -0
  7. data/app/models/bookkeeper/purchase.rb +4 -1
  8. data/app/uploaders/receipt_uploader.rb +8 -8
  9. data/app/views/bookkeeper/balance/index.html.erb +31 -0
  10. data/app/views/bookkeeper/movements/_form.html.erb +15 -0
  11. data/app/views/bookkeeper/movements/_search.html.erb +6 -0
  12. data/app/views/bookkeeper/movements/edit.html.erb +5 -0
  13. data/app/views/bookkeeper/movements/index.html.erb +36 -0
  14. data/app/views/bookkeeper/movements/new.html.erb +5 -0
  15. data/app/views/bookkeeper/purchases/_form.html.erb +7 -1
  16. data/config/locales/en.yml +23 -0
  17. data/config/locales/rails-i18n.en.yml +206 -0
  18. data/config/locales/rails-i18n.it.yml +206 -0
  19. data/config/locales/simple_form.en.yml +3 -2
  20. data/config/routes.rb +2 -0
  21. data/db/migrate/20130228234536_create_bookkeeper_accounts.rb +14 -0
  22. data/db/migrate/20130228234703_create_bookkeeper_movements.rb +14 -0
  23. data/db/migrate/20130308231159_add_details_to_purchases.rb +6 -0
  24. data/db/migrate/20130308231918_create_bookkeeper_categories.rb +9 -0
  25. data/lib/bookkeeper/engine.rb +0 -1
  26. data/lib/bookkeeper/version.rb +1 -1
  27. data/spec/controllers/bookkeeper/balance_controller_spec.rb +26 -0
  28. data/spec/controllers/bookkeeper/movements_controller_spec.rb +147 -0
  29. data/spec/controllers/bookkeeper/purchases_controller_spec.rb +144 -158
  30. data/spec/dummy/db/development.sqlite3 +0 -0
  31. data/spec/dummy/db/schema.rb +37 -3
  32. data/spec/dummy/db/test.sqlite3 +0 -0
  33. data/spec/dummy/log/development.log +69 -0
  34. data/spec/dummy/log/test.log +8485 -0
  35. data/spec/factories/bookkeeper_accounts.rb +10 -0
  36. data/spec/factories/bookkeeper_categories.rb +6 -0
  37. data/spec/factories/bookkeeper_movements.rb +22 -0
  38. data/spec/factories/bookkeeper_purchases.rb +1 -0
  39. data/spec/models/bookkeeper/account_spec.rb +21 -0
  40. data/spec/models/bookkeeper/categories_spec.rb +7 -0
  41. data/spec/models/bookkeeper/movement_spec.rb +28 -0
  42. data/spec/uploaders/receipt_uploader_spec.rb +5 -5
  43. metadata +53 -18
@@ -1,6 +1,8 @@
1
1
  //= require_tree './bookkeeper'
2
2
 
3
3
  $(document).ready(function(){
4
+ $('a[data-toggle=tooltip]').tooltip();
5
+
4
6
  $('input.datepicker').datepicker({ yearRange: "1920:2020", changeMonth: true, changeYear: true });
5
7
 
6
8
  $('form .add-on.calendar').click(function(){
@@ -0,0 +1,15 @@
1
+ require_dependency "bookkeeper/application_controller"
2
+
3
+ module Bookkeeper
4
+ class BalanceController < ApplicationController
5
+ def index
6
+ @movements = Movement.all
7
+
8
+ respond_to do |format|
9
+ format.html
10
+ format.json { render json: @movements }
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,67 @@
1
+ require_dependency "bookkeeper/application_controller"
2
+
3
+ module Bookkeeper
4
+ class MovementsController < ApplicationController
5
+ def index
6
+ @movements = Movement.all
7
+
8
+ respond_to do |format|
9
+ format.html
10
+ format.json { render json: @movements }
11
+ end
12
+ end
13
+
14
+ def new
15
+ @movement = Movement.new
16
+
17
+ respond_to do |format|
18
+ format.html
19
+ format.json { render json: @movement }
20
+ end
21
+ end
22
+
23
+ def edit
24
+ @movement = Movement.find(params[:id])
25
+ end
26
+
27
+ def create
28
+ @movement = Movement.new(params[:movement])
29
+
30
+ respond_to do |format|
31
+ if @movement.save
32
+ format.html { redirect_to balance_index_path, notice: I18n.t('.bookkeeper.controllers.movements.created') }
33
+ format.json { render json: @movement, status: :created, location: @movement }
34
+ else
35
+ format.html { render action: "new" }
36
+ format.json { render json: @movement.errors, status: :unprocessable_entity }
37
+ end
38
+ end
39
+ end
40
+
41
+ def update
42
+ @movement = Movement.find(params[:id])
43
+
44
+ respond_to do |format|
45
+ if @movement.update_attributes(params[:movement])
46
+ format.html { redirect_to balance_index_path, notice: I18n.t('.bookkeeper.controllers.movements.updated') }
47
+ format.json { head :no_content }
48
+ else
49
+ format.html { render action: "edit" }
50
+ format.json { render json: @movement.errors, status: :unprocessable_entity }
51
+ end
52
+ end
53
+ end
54
+
55
+ def destroy
56
+ @movement = Movement.find(params[:id])
57
+ @movement.destroy
58
+ flash[:notice] = I18n.t('.bookkeeper.controllers.movements.destroyed')
59
+
60
+ respond_to do |format|
61
+ format.html { redirect_to balance_index_path }
62
+ format.json { head :no_content }
63
+ end
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,25 @@
1
+ module Bookkeeper
2
+ class Account < ActiveRecord::Base
3
+ attr_accessible :start_date, :initial_balance
4
+
5
+ has_many :movements, class_name: "Bookkeeper::Movement"
6
+
7
+ validates :balance, numericality: { allow_nil: true }
8
+ validates :title, presence: true, uniqueness: true
9
+
10
+ before_save do
11
+ self.update_start_date if self.start_date.blank? and !self.movements.empty?
12
+ self.rebuild_balance
13
+ end
14
+
15
+ def rebuild_balance
16
+ self.balance = self.initial_balance + Movement.where('bookkeeper_movements.account_id = ?', self.id).sum(:amount)
17
+ end
18
+
19
+ def update_start_date
20
+ self.update_attribute(:start_date, self.movements.first.created_at)
21
+ end
22
+ end
23
+ end
24
+
25
+
@@ -0,0 +1,9 @@
1
+ module Bookkeeper
2
+ class Categories < ActiveRecord::Base
3
+ attr_accessible :name
4
+
5
+ belongs_to :categorizable, polymorphic: true
6
+
7
+ validates :name, presence: true, uniqueness: true
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ module Bookkeeper
2
+ class Movement < ActiveRecord::Base
3
+ attr_accessible :amount, :description
4
+
5
+ belongs_to :account, class_name: "Bookkeeper::Account"
6
+ has_many :categories, as: :categorizable
7
+
8
+ validates :amount, presence: true, numericality: true
9
+ validates :description, presence: true
10
+ validate :amount_cannot_be_zero
11
+ validates :account, presence: true
12
+
13
+ default_scope order('created_at DESC')
14
+
15
+
16
+ after_save do
17
+ self.account.rebuild_balance
18
+ end
19
+
20
+ def amount_cannot_be_zero
21
+ if !amount.blank? && amount == 0
22
+ errors.add(:amount, I18n.t(".cannot_be_zero"))
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -2,10 +2,13 @@ module Bookkeeper
2
2
  class Purchase < ActiveRecord::Base
3
3
  mount_uploader :receipt, ReceiptUploader
4
4
 
5
- attr_accessible :description, :purchase_date, :title, :warranty_duration, :receipt
5
+ attr_accessible :description, :purchase_date, :title, :warranty_duration, :receipt, :invoice, :amount
6
+
7
+ has_many :categories, as: :categorizable
6
8
 
7
9
  validates_presence_of :title
8
10
  validates_numericality_of :warranty_duration, allow_nil: true
11
+ validates_numericality_of :amount, allow_nil: true
9
12
  validates_date :purchase_date, allow_nil: true, allow_blank: true
10
13
 
11
14
  default_scope order('created_at DESC')
@@ -3,7 +3,7 @@
3
3
  class ReceiptUploader < CarrierWave::Uploader::Base
4
4
 
5
5
  # Include RMagick or MiniMagick support:
6
- include CarrierWave::RMagick
6
+ # include CarrierWave::RMagick
7
7
  # include CarrierWave::MiniMagick
8
8
 
9
9
  # Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
@@ -31,22 +31,22 @@ class ReceiptUploader < CarrierWave::Uploader::Base
31
31
  end
32
32
 
33
33
  # Process files as they are uploaded:
34
- process :resize_to_fit => [1000, 1000]
34
+ # process :resize_to_fit => [1000, 1000]
35
35
  #
36
36
  # def scale(width, height)
37
37
  # # do something
38
38
  # end
39
39
 
40
40
  # Create different versions of your uploaded files:
41
- version :thumb do
42
- process :resize_to_fill => [64, 64]
43
- end
41
+ # version :thumb do
42
+ # process :resize_to_fill => [64, 64]
43
+ # end
44
44
 
45
45
  # Add a white list of extensions which are allowed to be uploaded.
46
46
  # For images you might use something like this:
47
- def extension_white_list
48
- %w(jpg jpeg gif png pdf)
49
- end
47
+ # def extension_white_list
48
+ # %w(jpg jpeg gif png pdf)
49
+ # end
50
50
 
51
51
  # Override the filename of the uploaded files:
52
52
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
@@ -0,0 +1,31 @@
1
+ <h1><%= t '.title' %></h1>
2
+
3
+ <table class='table table-striped table-condensed table-bordered'>
4
+ <thead>
5
+ <tr>
6
+ <th><%=t 'bookkeeper.models.movement.attributes.amount' %></th>
7
+ <th><%=t 'bookkeeper.models.movement.attributes.description' %></th>
8
+ <th><%=t 'bookkeeper.models.movement.attributes.created_at' %></th>
9
+ <th><%=t 'bookkeeper.models.movement.attributes.updated_at' %></th>
10
+ <th></th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% @movements.each do |movement| %>
15
+ <tr>
16
+ <td><%= number_to_currency movement.amount %></td>
17
+ <td><%= movement.description %></td>
18
+ <td><%=l movement.created_at, format: :short rescue '' %></td>
19
+ <td><%=l movement.updated_at, format: :short rescue '' %></td>
20
+ <td>
21
+ <%= link_to ('<i class="icon-pencil"></i> ' + t('bookkeeper.actions.edit')).html_safe, edit_movement_path(movement), class: 'btn btn-mini' %>
22
+ <%= link_to ('<i class="icon-remove"></i> ' + t('bookkeeper.actions.destroy')).html_safe, movement, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-mini btn-danger' %>
23
+ </td>
24
+ </tr>
25
+ <% end %>
26
+ </tbody>
27
+ </table>
28
+
29
+ <br />
30
+
31
+ <%= link_to ('<i class="icon-plus"></i> ' + t('.new')).html_safe, new_movement_path, class: 'btn' %>
@@ -0,0 +1,15 @@
1
+ <%= simple_form_for @movement, html: { class: 'form-horizontal' } do |f| %>
2
+
3
+ <%= f.error_notification %>
4
+
5
+ <div class="form-inputs">
6
+ <%= f.input :amount, :wrapper => :append do %>
7
+ <%= f.input_field :amount, class: "input-mini" %>
8
+ <%= content_tag :span, '&euro;'.html_safe, class: "add-on" %>
9
+ <% end %>
10
+ <%= f.input :description, input_html: { class: 'input-xxlarge' } %>
11
+ </div>
12
+ <div class="form-actions">
13
+ <%= button_tag ('<i class="icon-ok"></i> ' + t('bookkeeper.actions.save')).html_safe, class: 'btn' %>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <div class="pull-right">
2
+ <%= form_tag search_index_path, class: "navbar-search pull-left" do %>
3
+ <%= text_field_tag 'search[q]', '', class: 'search-query', placeholder: t('.search') %>
4
+ <% end %>
5
+ </div>
6
+
@@ -0,0 +1,5 @@
1
+ <h1><%= t '.title' %></h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to ('<i class="icon-arrow-left"></i> ' + t('bookkeeper.actions.back')).html_safe, balance_index_path, class: 'btn' %>
@@ -0,0 +1,36 @@
1
+ <%= render partial: 'search' %>
2
+ <h1><%= t '.title' %></h1>
3
+
4
+ <table class='table table-striped table-condensed table-bordered'>
5
+ <thead>
6
+ <tr>
7
+ <th><%=t 'bookkeeper.models.purchase.attributes.title' %></th>
8
+ <th><%=t 'bookkeeper.models.purchase.attributes.description' %></th>
9
+ <th><%=t 'bookkeeper.models.purchase.attributes.purchase_date' %></th>
10
+ <th><%=t 'bookkeeper.models.purchase.attributes.warranty_duration' %></th>
11
+ <th><%=t 'bookkeeper.models.purchase.attributes.receipt' %></th>
12
+ <th><%=t 'bookkeeper.models.purchase.attributes.updated_at' %></th>
13
+ <th></th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <% @purchases.each do |purchase| %>
18
+ <tr>
19
+ <td><%= purchase.title %></td>
20
+ <td><%= purchase.description %></td>
21
+ <td><%=l purchase.purchase_date rescue '' %></td>
22
+ <td><%= purchase.warranty_duration %> <%=t '.months' %></td>
23
+ <td><%= link_to "<i class='icon-download-alt'></i> #{purchase.receipt.file.filename}".html_safe, purchase.receipt.url, class: 'btn btn-mini' if purchase.receipt.file %></td>
24
+ <td><%=l purchase.updated_at, format: :short rescue '' %></td>
25
+ <td>
26
+ <%= link_to ('<i class="icon-pencil"></i> ' + t('bookkeeper.actions.edit')).html_safe, edit_purchase_path(purchase), class: 'btn btn-mini' %>
27
+ <%= link_to ('<i class="icon-remove"></i> ' + t('bookkeeper.actions.destroy')).html_safe, purchase, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-mini btn-danger' %>
28
+ </td>
29
+ </tr>
30
+ <% end %>
31
+ </tbody>
32
+ </table>
33
+
34
+ <br />
35
+
36
+ <%= link_to ('<i class="icon-plus"></i> ' + t('bookkeeper.actions.new')).html_safe, new_purchase_path, class: 'btn' %>
@@ -0,0 +1,5 @@
1
+ <h1><%= t '.title' %></h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to ('<i class="icon-arrow-left"></i> ' + t('bookkeeper.actions.back')).html_safe, balance_index_path, class: 'btn' %>
@@ -6,7 +6,13 @@
6
6
  <%= f.input :title %>
7
7
  <%= f.input :description, input_html: { class: 'input-xxlarge', rows: 4 } %>
8
8
  <%= f.input :purchase_date, :wrapper => :prepend do %>
9
- <%= content_tag :span, '<i class="icon-th"></i>'.html_safe, class: "add-on calendar" %><%= f.input_field :purchase_date, as: :datepicker, data: { 'date-format' => 'dd/mm/yyyy' } %>
9
+ <%= content_tag :span, '<i class="icon-calendar"></i>'.html_safe, class: "add-on calendar" %>
10
+ <%= f.input_field :purchase_date, as: :datepicker, data: { 'date-format' => 'dd/mm/yyyy' } %>
11
+ <% end %>
12
+ <%= f.input :invoice %>
13
+ <%= f.input :amount, :wrapper => :prepend do %>
14
+ <%= content_tag :span, '<i class="icon-money"></i>'.html_safe, class: "add-on calendar" %>
15
+ <%= f.input_field :amount, class: "input-mini" %>
10
16
  <% end %>
11
17
  <%= f.input :warranty_duration, collection: [["6 months", 6], ["1 year", 12], ["2 years", 24], ["3 years", 36]], input_html: { class: 'input-small' } %>
12
18
  <%= f.input :receipt %>
@@ -18,12 +18,22 @@ en:
18
18
  warranty_duration: 'Warranty duration'
19
19
  receipt: 'Receipt'
20
20
  updated_at: 'Last update'
21
+ movement:
22
+ attributes:
23
+ amount: 'Amount'
24
+ description: 'Description'
25
+ created_at: 'Creation date'
26
+ updated_at: 'Last update'
21
27
  # controllers
22
28
  controllers:
23
29
  purchases:
24
30
  created: 'Purchase was successfully created.'
25
31
  updated: 'Purchase was successfully updated.'
26
32
  destroyed: 'Purchase was successfully deleted.'
33
+ movementss:
34
+ created: 'Movement was successfully created.'
35
+ updated: 'Movement was successfully updated.'
36
+ destroyed: 'Movement was successfully deleted.'
27
37
  # views
28
38
  search:
29
39
  create:
@@ -38,3 +48,16 @@ en:
38
48
  title: 'Edit purchase'
39
49
  search:
40
50
  search: 'Search'
51
+ balance:
52
+ index:
53
+ title: 'Last movements'
54
+ new: 'Insert new movement'
55
+ movements:
56
+ index:
57
+ title: 'Listing movements'
58
+ new:
59
+ title: 'New movement'
60
+ edit:
61
+ title: 'Edit movement'
62
+ search:
63
+ search: 'Search'
@@ -0,0 +1,206 @@
1
+ #encoding: utf-8
2
+ en:
3
+ date:
4
+ abbr_day_names:
5
+ - Sun
6
+ - Mon
7
+ - Tue
8
+ - Wed
9
+ - Thu
10
+ - Fri
11
+ - Sat
12
+ abbr_month_names:
13
+ -
14
+ - Jan
15
+ - Feb
16
+ - Mar
17
+ - Apr
18
+ - May
19
+ - Jun
20
+ - Jul
21
+ - Aug
22
+ - Sep
23
+ - Oct
24
+ - Nov
25
+ - Dec
26
+ day_names:
27
+ - Sunday
28
+ - Monday
29
+ - Tuesday
30
+ - Wednesday
31
+ - Thursday
32
+ - Friday
33
+ - Saturday
34
+ formats:
35
+ default: ! '%Y-%m-%d'
36
+ long: ! '%B %d, %Y'
37
+ short: ! '%b %d'
38
+ month_names:
39
+ -
40
+ - January
41
+ - February
42
+ - March
43
+ - April
44
+ - May
45
+ - June
46
+ - July
47
+ - August
48
+ - September
49
+ - October
50
+ - November
51
+ - December
52
+ order:
53
+ - :year
54
+ - :month
55
+ - :day
56
+ datetime:
57
+ distance_in_words:
58
+ about_x_hours:
59
+ one: about 1 hour
60
+ other: about %{count} hours
61
+ about_x_months:
62
+ one: about 1 month
63
+ other: about %{count} months
64
+ about_x_years:
65
+ one: about 1 year
66
+ other: about %{count} years
67
+ almost_x_years:
68
+ one: almost 1 year
69
+ other: almost %{count} years
70
+ half_a_minute: half a minute
71
+ less_than_x_minutes:
72
+ one: less than a minute
73
+ other: less than %{count} minutes
74
+ less_than_x_seconds:
75
+ one: less than 1 second
76
+ other: less than %{count} seconds
77
+ over_x_years:
78
+ one: over 1 year
79
+ other: over %{count} years
80
+ x_days:
81
+ one: 1 day
82
+ other: ! '%{count} days'
83
+ x_minutes:
84
+ one: 1 minute
85
+ other: ! '%{count} minutes'
86
+ x_months:
87
+ one: 1 month
88
+ other: ! '%{count} months'
89
+ x_seconds:
90
+ one: 1 second
91
+ other: ! '%{count} seconds'
92
+ prompts:
93
+ day: Day
94
+ hour: Hour
95
+ minute: Minute
96
+ month: Month
97
+ second: Seconds
98
+ year: Year
99
+ errors: &errors
100
+ format: ! '%{attribute} %{message}'
101
+ messages:
102
+ accepted: must be accepted
103
+ blank: can't be blank
104
+ confirmation: doesn't match confirmation
105
+ empty: can't be empty
106
+ equal_to: must be equal to %{count}
107
+ even: must be even
108
+ exclusion: is reserved
109
+ greater_than: must be greater than %{count}
110
+ greater_than_or_equal_to: must be greater than or equal to %{count}
111
+ inclusion: is not included in the list
112
+ invalid: is invalid
113
+ less_than: must be less than %{count}
114
+ less_than_or_equal_to: must be less than or equal to %{count}
115
+ not_a_number: is not a number
116
+ not_an_integer: must be an integer
117
+ odd: must be odd
118
+ record_invalid: ! 'Validation failed: %{errors}'
119
+ taken: has already been taken
120
+ too_long:
121
+ one: is too long (maximum is 1 character)
122
+ other: is too long (maximum is %{count} characters)
123
+ too_short:
124
+ one: is too short (minimum is 1 character)
125
+ other: is too short (minimum is %{count} characters)
126
+ wrong_length:
127
+ one: is the wrong length (should be 1 character)
128
+ other: is the wrong length (should be %{count} characters)
129
+ template:
130
+ body: ! 'There were problems with the following fields:'
131
+ header:
132
+ one: 1 error prohibited this %{model} from being saved
133
+ other: ! '%{count} errors prohibited this %{model} from being saved'
134
+ helpers:
135
+ select:
136
+ prompt: Please select
137
+ submit:
138
+ create: Create %{model}
139
+ submit: Save %{model}
140
+ update: Update %{model}
141
+ number:
142
+ currency:
143
+ format:
144
+ delimiter: .
145
+ format: ! '%n %u'
146
+ precision: 2
147
+ separator: ! '.'
148
+ significant: false
149
+ strip_insignificant_zeros: false
150
+ unit: €
151
+ format:
152
+ delimiter: ! ','
153
+ precision: 3
154
+ separator: .
155
+ significant: false
156
+ strip_insignificant_zeros: false
157
+ human:
158
+ decimal_units:
159
+ format: ! '%n %u'
160
+ units:
161
+ billion: Billion
162
+ million: Million
163
+ quadrillion: Quadrillion
164
+ thousand: Thousand
165
+ trillion: Trillion
166
+ unit: ''
167
+ format:
168
+ delimiter: ''
169
+ precision: 3
170
+ significant: true
171
+ strip_insignificant_zeros: true
172
+ storage_units:
173
+ format: ! '%n %u'
174
+ units:
175
+ byte:
176
+ one: Byte
177
+ other: Bytes
178
+ gb: GB
179
+ kb: KB
180
+ mb: MB
181
+ tb: TB
182
+ percentage:
183
+ format:
184
+ delimiter: ''
185
+ precision:
186
+ format:
187
+ delimiter: ''
188
+ support:
189
+ array:
190
+ last_word_connector: ! ', and '
191
+ two_words_connector: ! ' and '
192
+ words_connector: ! ', '
193
+ time:
194
+ am: am
195
+ formats:
196
+ default: ! '%a, %d %b %Y %H:%M:%S %z'
197
+ long: ! '%B %d, %Y %H:%M'
198
+ short: ! '%d %b %H:%M'
199
+ pm: pm
200
+ # remove these aliases after 'activemodel' and 'activerecord' namespaces are removed from Rails repository
201
+ activemodel:
202
+ errors:
203
+ <<: *errors
204
+ activerecord:
205
+ errors:
206
+ <<: *errors