Ruby on Rails app with billing and fiscalization (Part1: Billing model)


 

Example of using  billing and extface modules.
Create a new app:

$ rails new shop
$ cd shop

Add to your ‘Gemfile’:

gem "billing"
gem "extface"

$ bundle install

To use latest gems, make sure that your Rails version is minimum 4.0.5.
Let’s create our point of sale model and controller .
Note: Our model will be called Po. If you want to use singular “pos”, and plural “poses” you have to edit ‘config/initializers/inflections.rb ‘ (Rails Inflections)

$ bundle exec bin/rails g scaffold pos name:string

Edit config/routes.rb and set:

root 'pos#index' 

$ bundle exec bin/rake db:migrate $ bundle exec bin/rails server

Check that your shop application is up and running at http://localhost:3000/
You can create first pos object at http://localhost:3000/pos/new, choose name and save .

That was standard. Now add the billing module :

$ bundle exec bin/rake billing:install
$ bundle exec bin/rake db:migrate

To integrate billing to some model just add has_billing to it

app/models/po.rb

class Po < ActiveRecord::Base
  has_billing
end

According to the rules of programming it’s time to write some model tests.

test/model/po_test.rb

require 'test_helper'

class PoTest < ActiveSupport::TestCase
  setup do
    @pos = pos(:one)
  end
  
  test 'check billing' do
    bill = @pos.billing_accounts.new do |b|
      b.charge 2
      b.modify(-1) #discount
    end
    assert bill.save
    assert_equal '1 USD'.to_money, bill.total
    assert_equal '-1 USD'.to_money, bill.balance
  end
end

$ bundle exec bin/rake test
Result: 8 runs, 16 assertions, 0 failures, 0 errors, 0 skips

OK, we have a working billing account!
An error like “undefined method `billing_accounts'” means that “has_billing” was not correctrly added to your pos model.  

Lets try payment by writing another test:

test 'check billing account payment' do
  bill = @pos.billing_accounts.new do |b|
    b.charge 2
    b.pay
  end
  assert bill.save
  assert bill.balance.zero?
end

Test will fail with message “Payments payment type can’t be blank” because we don’t have default payment type. If payment type is not specified, billing account is looking for #default_payment_type in it’s parent model. This method may be implemented in our pos model or delegated to some settings object. Actually, we don’t have any payment types yet.

Billing module comes with STI model Billing::PaymentType. The lazy way is to create one Billing::PaymentType.create!(name: ‘Cash’, cash: true) and point default_payment_type to #first, but it’s not cool.
We can create global payment types for all pos objects, or separate types for each of them.

$ bundle exec bin/rails g scaffold payment_type name:string cash:boolean fiscal:boolean –skip-migration

We don’t need migration (–skip-migration), cause we use Single Table Inheritance.
Now change the class declaration in app/models/payment_type.rb

class PaymentType < Billing::PaymentType
...

Create a payment type by the user interface at http://localhost:3000/payment_types/new
And now implement default_payment_type for our pos model (migration, model, view, controller, test).

$ bundle exec bin/rails g migration add_default_payment_type_to_pos default_payment_type_id:integer
$ bundle exec bin/rake db:migrate

Add to app/models/po.rb:

belongs_to :default_payment_type, class_name: 'PaymentType'

Modify view app/views/pos/_form.html.erb

<%= f.label :default_payment_type %>
<%= f.select :default_payment_type, PaymentType.all.collect{ |pt| [pt.name, pt.id] } %>

Modify app/controllers/pos_controller.rb to allow new param (Strong Parameters)

params.require(:po).permit(:name, :default_payment_type_id)

Get back to the tests…
Add the new attributes to pos and payment_types fixtures:

app/tests/fixtures/pos.yml

one:
  name: Pos1
  default_payment_type: one

two:
  name: Pos2
  default_payment_type: two

app/tests/fixtures/payment_types.yml

one:
  name: Cash
  cash: true
  fiscal: false
  type: PaymentType

Tests should pass.
Now add another test with partial payment by type:

test "check billing parital payment by type" do
  bill = @pos.billing_accounts.new do |b|
    b.charge 2
    b.pay payment_types(:one), 1
  end
  assert bill.save, "Save with payment failed, message: #{bill.errors.full_messages.join(', ')}"
  assert_equal '-1 USD'.to_money, bill.balance
end

Test should pass. We have billing accounts for pos model with charges, discounts, surcharges and payments by type.

Next part will deal with the visualization. 10x for reading!