solidus_invoice 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +250 -0
  5. data/.rvmrc +1 -0
  6. data/.travis.yml +18 -0
  7. data/Gemfile +10 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +38 -0
  10. data/Rakefile +32 -0
  11. data/app/assets/javascripts/spree/backend/solidus_invoice.js +2 -0
  12. data/app/assets/javascripts/spree/frontend/solidus_invoice.js +2 -0
  13. data/app/assets/stylesheets/spree/backend/solidus_invoice.scss +4 -0
  14. data/app/assets/stylesheets/spree/frontend/solidus_invoice.css +4 -0
  15. data/app/jobs/application_job.rb +9 -0
  16. data/app/jobs/solidus_invoice/daily_summary_job.rb +32 -0
  17. data/app/jobs/solidus_invoice/invoice_job.rb +22 -0
  18. data/app/models/concerns/solidus_invoice/line_item_concern.rb +11 -0
  19. data/app/models/concerns/solidus_invoice/order_concern.rb +44 -0
  20. data/app/models/concerns/solidus_invoice/shipment_concern.rb +11 -0
  21. data/app/models/concerns/solidus_invoice/store_concern.rb +11 -0
  22. data/app/models/spree.rb +7 -0
  23. data/app/models/spree/invoice.rb +61 -0
  24. data/app/models/spree/invoice_serial.rb +9 -0
  25. data/app/overrides/spree/line_item_override.rb +5 -0
  26. data/app/overrides/spree/order_override.rb +5 -0
  27. data/app/overrides/spree/shipment_override.rb +5 -0
  28. data/app/overrides/spree/store_override.rb +5 -0
  29. data/bin/rails +9 -0
  30. data/bin/test +7 -0
  31. data/config/locales/en.yml +5 -0
  32. data/config/routes.rb +5 -0
  33. data/db/migrate/20191107042751_create_spree_invoices.rb +14 -0
  34. data/db/migrate/20191115204118_create_spree_invoice_serials.rb +13 -0
  35. data/db/migrate/20191205205004_add_tax_uid_to_spree_addresses.rb +7 -0
  36. data/db/migrate/20191223173229_add_preferences_to_spree_invoices.rb +7 -0
  37. data/lib/generators/solidus_invoice/install/install_generator.rb +22 -0
  38. data/lib/solidus_invoice.rb +4 -0
  39. data/lib/solidus_invoice/engine.rb +22 -0
  40. data/lib/solidus_invoice/errors/invalid_serial_error.rb +6 -0
  41. data/lib/solidus_invoice/factories.rb +34 -0
  42. data/lib/solidus_invoice/railtie.rb +6 -0
  43. data/lib/solidus_invoice/version.rb +5 -0
  44. data/lib/tasks/solidus_invoice_tasks.rake +5 -0
  45. data/solidus_invoice.gemspec +37 -0
  46. data/spec/jobs/solidus_invoice/daily_summary_job_spec.rb +24 -0
  47. data/spec/jobs/solidus_invoice/invoice_job_spec.rb +76 -0
  48. data/spec/models/spree/address_spec.rb +11 -0
  49. data/spec/models/spree/invoice_serial_spec.rb +16 -0
  50. data/spec/models/spree/invoice_spec.rb +55 -0
  51. data/spec/models/spree/line_item_spec.rb +37 -0
  52. data/spec/models/spree/order_spec.rb +31 -0
  53. data/spec/models/spree/shipment_spec.rb +26 -0
  54. data/spec/models/spree/store_spec.rb +9 -0
  55. data/spec/spec_helper.rb +98 -0
  56. metadata +320 -0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sunat_invoice'
4
+
5
+ class SolidusInvoice::InvoiceJob < ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(*args)
9
+ opts = args[0]
10
+ invoice = Spree::Invoice.find(opts[:invoice_id])
11
+ env = opts[:env]
12
+
13
+ sunat_invoice = invoice.build_sunat_invoice
14
+
15
+ if invoice.doc_type == '01'
16
+ # send to SUNAT
17
+ client = SunatInvoice::InvoiceClient.new(env)
18
+ response = client.dispatch(sunat_invoice)
19
+ invoice.update(communicated: true) if response.success?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ module LineItemConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ def price_excluding_vat
8
+ (price - (included_tax_total / quantity)).round(2)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_invoice/errors/invalid_serial_error'
4
+
5
+ module SolidusInvoice
6
+ module OrderConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ has_many :invoices
11
+ after_save :generate_invoice
12
+
13
+ def generate_invoice
14
+ return unless can_generate_invoice?
15
+
16
+ doc_type = bill_address.tax_uid? ? '01' : '03'
17
+ doc_number = next_correlative(doc_type, store.id)
18
+
19
+ # generate Spree::Invoice
20
+ invoices.create(invoice_serial: get_serial(doc_type, store),
21
+ doc_number: doc_number,
22
+ order: self)
23
+ end
24
+
25
+ def can_generate_invoice?
26
+ completed? && paid?
27
+ end
28
+
29
+ def get_serial(doc_type, store = Spree::Store.default)
30
+ store.invoice_serials.find_by(doc_type: doc_type)
31
+ end
32
+
33
+ # given a store and document type get next posible correlative
34
+ def next_correlative(doc_type, store_id = Spree::Store.default.id)
35
+ store = Spree::Store.includes(invoice_serials: :invoices).find(store_id)
36
+ serial = get_serial(doc_type, store)
37
+ raise SolidusInvoice::InvalidSerialError unless serial
38
+
39
+ current = serial&.invoices&.order(doc_number: :desc)&.first&.doc_number || 0
40
+ current + 1
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ module ShipmentConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ def price_excluding_vat
8
+ (amount - included_tax_total).round(2)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ module StoreConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :invoice_serials
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ def self.table_name_prefix
5
+ 'spree_'
6
+ end
7
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sunat_invoice'
4
+
5
+ class Spree::Invoice < Spree::Base
6
+ validates :doc_number, presence: true
7
+
8
+ belongs_to :order
9
+ belongs_to :invoice_serial
10
+
11
+ delegate :doc_type, :serial, to: :invoice_serial
12
+
13
+ preference :sunat_attributes, :hash
14
+
15
+ def sunat_attributes
16
+ attrs = (preferred_sunat_attributes || {}).dup
17
+ provider = SunatInvoice::Provider.new(attrs[:provider] || {})
18
+ attrs[:provider] = provider
19
+ attrs
20
+ end
21
+
22
+ # return a `SunatInvoice::Invoice`
23
+ def build_sunat_invoice
24
+ # generate SunatInvoice::Invoice
25
+ sunat_invoice = SunatInvoice::Invoice.new(sunat_attributes)
26
+
27
+ # add invoice lines from line_items
28
+ order.line_items.map do |line_item|
29
+ add_sunat_line(sunat_invoice, line_item)
30
+ end
31
+
32
+ # add invoice lines from shipment
33
+ order.shipments.map do |shipment|
34
+ next unless shipment.amount.positive?
35
+
36
+ add_sunat_line(sunat_invoice, shipment)
37
+ end
38
+
39
+ # store xml
40
+ store_xml(sunat_invoice.xml) if respond_to?(:store_xml)
41
+
42
+ sunat_invoice
43
+ end
44
+
45
+ # add a new `SunatInvoice::Item` to the given sunat invoice
46
+ # sunat_invoice => `SunatInvoice::Invoice` instance to add lines
47
+ # resource => an order's line_item or shipment
48
+ def add_sunat_line(sunat_invoice, resource)
49
+ # TODO: handle items with taxes different than vat
50
+ tax_amount = resource.adjustments.tax.sum(:amount)
51
+ tax = SunatInvoice::Tax.new(amount: tax_amount, tax_type: :igv)
52
+ quantity = resource.respond_to?(:quantity) ? resource.quantity : 1
53
+ item_attrs = {
54
+ quantity: quantity,
55
+ price: resource.price_excluding_vat,
56
+ price_code: '01',
57
+ taxes: [tax]
58
+ }
59
+ sunat_invoice.lines << SunatInvoice::Item.new(item_attrs)
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Spree::InvoiceSerial < Spree::Base
4
+ validates :serial, presence: true, uniqueness: { scope: :store_id }
5
+ validates :store_id, presence: true
6
+
7
+ belongs_to :store
8
+ has_many :invoices
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree::LineItemOverride
4
+ Spree::LineItem.include SolidusInvoice::LineItemConcern
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree::OrderOverride
4
+ Spree::Order.include SolidusInvoice::OrderConcern
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree::ShipmentOverride
4
+ Spree::Shipment.include SolidusInvoice::ShipmentConcern
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree::StoreOverride
4
+ Spree::Store.include SolidusInvoice::StoreConcern
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
6
+ ENGINE_PATH = File.expand_path('../../lib/solidus_invoice/engine', __FILE__)
7
+
8
+ require 'rails/all'
9
+ require 'rails/engine/commands'
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $: << File.expand_path("../test", __dir__)
5
+
6
+ require "bundler/setup"
7
+ require "rails/plugin/test"
@@ -0,0 +1,5 @@
1
+ # Sample localization file for English. Add more files in this directory for other locales.
2
+ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
+
4
+ en:
5
+ hello: Hello world
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Spree::Core::Engine.routes.draw do
4
+ # Add your extension routes here
5
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSpreeInvoices < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :spree_invoices do |t|
6
+ t.integer :doc_number
7
+ t.integer :order_id
8
+ t.integer :invoice_serial_id
9
+ t.boolean :communicated, default: false
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSpreeInvoiceSerials < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :spree_invoice_serials do |t|
6
+ t.string :serial
7
+ t.integer :store_id
8
+ t.string :doc_type
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddTaxUidToSpreeAddresses < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column :spree_addresses, :tax_uid, :string
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddPreferencesToSpreeInvoices < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column :spree_invoices, :preferences, :text
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ class_option :auto_run_migrations, type: :boolean, default: false
7
+
8
+ def add_migrations
9
+ run 'bundle exec rake railties:install:migrations FROM=solidus_invoice'
10
+ end
11
+
12
+ def run_migrations
13
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
14
+ if run_migrations
15
+ run 'bundle exec rake db:migrate'
16
+ else
17
+ puts 'Skipping rake db:migrate, don\'t forget to run it!'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_core'
4
+ require 'solidus_invoice/engine'
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ class Engine < Rails::Engine
5
+ require 'spree/core'
6
+ isolate_namespace Spree
7
+ engine_name 'solidus_invoice'
8
+
9
+ # use rspec for tests
10
+ config.generators do |g|
11
+ g.test_framework :rspec
12
+ end
13
+
14
+ def self.activate
15
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/overrides/**/*_override*.rb')) do |c|
16
+ require_dependency(c)
17
+ end
18
+ end
19
+
20
+ config.to_prepare(&method(:activate).to_proc)
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ class InvalidSerialError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :invoice, class: 'Spree::Invoice' do
5
+ doc_number { rand(450) }
6
+ order
7
+ invoice_serial
8
+
9
+ factory :invoice_with_serial do
10
+ before(:create) do |invoice|
11
+ store = create(:store)
12
+ invoice.invoice_serial { create(:invoice_serial, store: store) }
13
+ end
14
+ end
15
+ end
16
+
17
+ factory :invoice_serial, class: 'Spree::InvoiceSerial' do
18
+ store
19
+ doc_type { '03' }
20
+ serial { 'B023' }
21
+ end
22
+
23
+ factory :provider, class: 'SunatInvoice::Provider' do
24
+ signature_id { 'signatureST' }
25
+ signature_location_id { 'signQWI3' }
26
+ ruc { '20100454523' }
27
+ name { 'MY BUSINESS' }
28
+ end
29
+
30
+ factory :customer, class: 'SunatInvoice::Customer' do
31
+ ruc { '20293028401' }
32
+ name { 'SOME BUSINESS' }
33
+ end
34
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ class Railtie < ::Rails::Railtie
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusInvoice
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :solidus_invoice do
4
+ # # Task goes here
5
+ # end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ $:.push File.expand_path('lib', __dir__)
4
+ require 'solidus_invoice/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'solidus_invoice'
8
+ s.version = SolidusInvoice::VERSION
9
+ s.summary = 'generate invoice documents for solidus orders'
10
+ s.description = s.summary
11
+ s.license = 'MIT'
12
+
13
+ s.author = 'César Carruitero'
14
+ s.email = 'ccarruitero@protonmail.com'
15
+ s.homepage = 'https://github.com/ccarruitero/solidus_invoice'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+
20
+ s.add_dependency 'solidus_core'
21
+ s.add_dependency 'solidus_support'
22
+ s.add_dependency 'sunat_invoice'
23
+
24
+ s.add_development_dependency 'capybara'
25
+ s.add_development_dependency 'database_cleaner'
26
+ s.add_development_dependency 'factory_bot'
27
+ s.add_development_dependency 'ffaker'
28
+ s.add_development_dependency 'mysql2'
29
+ s.add_development_dependency 'pg'
30
+ s.add_development_dependency 'pry'
31
+ s.add_development_dependency 'rspec-rails'
32
+ s.add_development_dependency 'rubocop'
33
+ s.add_development_dependency 'sass-rails'
34
+ s.add_development_dependency 'shoulda-matchers'
35
+ s.add_development_dependency 'simplecov'
36
+ s.add_development_dependency 'sqlite3'
37
+ end