bookkeeper 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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