solidus_invoice 0.1.0

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 (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