spree_fosdick_integration 0.0.3

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +28 -0
  3. data/Gemfile +21 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +76 -0
  6. data/Rakefile +15 -0
  7. data/app/controllers/spree/admin/fosdick_shipments_controller.rb +18 -0
  8. data/app/mailers/spree/fosdick_shipment_mailer.rb +13 -0
  9. data/app/models/spree/fosdick_exception.rb +15 -0
  10. data/app/models/spree/fosdick_shipment.rb +8 -0
  11. data/app/models/spree/shipment_decorator.rb +17 -0
  12. data/app/overrides/add_fosdick_panel.rb +8 -0
  13. data/app/serializers/address_serializer.rb +18 -0
  14. data/app/serializers/inventory_unit_serializer.rb +21 -0
  15. data/app/serializers/shipment_serializer.rb +104 -0
  16. data/app/services/exception_logger.rb +21 -0
  17. data/app/views/spree/admin/fosdick_shipments/_filters_bar.html.haml +36 -0
  18. data/app/views/spree/admin/fosdick_shipments/_fosdick_shipments_list.html.haml +28 -0
  19. data/app/views/spree/admin/fosdick_shipments/_fosdick_shipments_list_respond.js.erb +1 -0
  20. data/app/views/spree/admin/fosdick_shipments/_titles.html.haml +5 -0
  21. data/app/views/spree/admin/fosdick_shipments/index.html.haml +3 -0
  22. data/app/views/spree/admin/fosdick_shipments/index.js.haml +1 -0
  23. data/app/views/spree/fosdick_shipment_mailer/order_shipped.html.erb +135 -0
  24. data/config/locales/en.yml +14 -0
  25. data/config/locales/ru.yml +14 -0
  26. data/config/routes.rb +10 -0
  27. data/db/migrate/20151013153126_create_spree_fosdick_shipment.rb +17 -0
  28. data/db/migrate/20151015122038_create_fosdick_exceptions.rb +15 -0
  29. data/db/migrate/20151028105216_add_fosdick_atempt_and_state_spree_shipments.rb +6 -0
  30. data/lib/fosdick/documents/shipment.rb +89 -0
  31. data/lib/fosdick/processor.rb +64 -0
  32. data/lib/fosdick/receiver.rb +49 -0
  33. data/lib/fosdick/sender.rb +39 -0
  34. data/lib/generators/spree_fosdick_integration/install/install_generator.rb +32 -0
  35. data/lib/generators/spree_fosdick_integration/install/templates/fosdick.rb +6 -0
  36. data/lib/generators/spree_fosdick_integration/install/templates/fosdick.yml.template +18 -0
  37. data/lib/spree_fosdick_integration.rb +8 -0
  38. data/lib/spree_fosdick_integration/engine.rb +22 -0
  39. data/lib/spree_fosdick_integration/factories.rb +31 -0
  40. data/lib/spree_fosdick_integration/version.rb +3 -0
  41. data/script/rails +7 -0
  42. data/spec/controllers/admin/fosdick_shipments_controller_spec.rb +16 -0
  43. data/spec/models/spree/fosdick_exception_spec.rb +21 -0
  44. data/spec/models/spree/fosdick_shipment_spec.rb +9 -0
  45. data/spec/models/spree/shipment_spec.rb +50 -0
  46. data/spec/services/exception_logger_spec.rb +11 -0
  47. data/spec/services/fosdick/processor_spec.rb +94 -0
  48. data/spec/services/fosdick/receiver_spec.rb +43 -0
  49. data/spec/services/fosdick/sender_spec.rb +34 -0
  50. data/spec/spec_helper.rb +99 -0
  51. data/spree_fosdick_integration.gemspec +37 -0
  52. metadata +286 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b13986672e0deb67ca5905d5bb4ddd945baf4535
4
+ data.tar.gz: c8f580eb733f0bf062a9ed39154c7879eee2d110
5
+ SHA512:
6
+ metadata.gz: 97ee5e4468d6637dca27732d38eec742864ae783e7acb5872745fa8bdef388337a6bac43dbccd32fcc751cfc0f171cc9e715cef8135695f5f8cd9b970dd54f19
7
+ data.tar.gz: e0370185fa1dd7097b24be8b92705ebe2b3acca64c132462ef31c5235f742cdf773e70c7e36bc2c060e2de59dd46d32b3dbf1a75863b3927c520db428d2da58b
@@ -0,0 +1,28 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+
16
+ \#*
17
+ *~
18
+ .#*
19
+ .DS_Store
20
+ .idea
21
+ .project
22
+ .sass-cache
23
+ .ruby-version
24
+ .ruby-gemset
25
+ nbproject
26
+ *.swp
27
+ spec/dummy
28
+ config/fosdick.yml
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spree_fosdick_integration.gemspec
4
+ gem 'httparty'
5
+ gem 'nokogiri'
6
+ gem 'model_un'
7
+
8
+ spree_version = '3-1-stable'
9
+
10
+ gem 'spree', github: 'spree/spree', branch: spree_version
11
+ gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: spree_version
12
+
13
+ gem 'haml'
14
+ gem 'haml-rails'
15
+ gem 'active_model_serializers', '~> 0.9.3'
16
+
17
+ group :test do
18
+ gem 'shoulda-matchers', require: false
19
+ gem 'webmock'
20
+ end
21
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 JetRuby Agency
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,76 @@
1
+ # SpreeFosdickIntegration
2
+
3
+ Current gem provides easy integration for you Spree Commerce based apps with full service fulfillment services of Fosdick Fulfillment.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'spree_fosdick_integration'
11
+ ```
12
+
13
+ Bundle your dependencies and run the installation generator:
14
+
15
+ ```shell
16
+ bundle
17
+ bundle exec rails g spree_fosdick_integration:install
18
+ ```
19
+ Update ``` config/fosdick.yml ``` with your credentials received from Fosdick
20
+
21
+ ## Usage
22
+ Create a rake task to push shipments to Fosdick iPost interface:
23
+
24
+ ```
25
+ desc "Push shipments to Fosdick iPost interface"
26
+ task push_shipments_fosdick: :environment do
27
+ eligible_shipments = Spree::Shipment.perform_fosdick_shipments
28
+
29
+ if eligible_shipments.present?
30
+ eligible_shipments.each do |shipment|
31
+ Fosdick::Processor.send_shipment(shipment, FOSDICK_CONFIG)
32
+ end
33
+ end
34
+ end
35
+ ```
36
+
37
+ Create a rake task to receive shipment information from Fosdick API:
38
+
39
+ ```
40
+ desc "Receive shipment information from Fosdick API"
41
+ task receive_shipments_fosdick: :environment do
42
+ eligible_shipments = Spree::FosdickShipment.eligible_fosdick_shipments
43
+
44
+ if eligible_shipments.present?
45
+ eligible_shipments.each do |fosdick_shipment|
46
+ # get tracking details
47
+ Fosdick::Processor.receive_shipment({external_order_num: fosdick_shipment.external_order_num }) if fosdick_shipment.tracking_number.nil?
48
+ # get ship_date
49
+ Fosdick::Processor.receive_shipment({external_order_num: fosdick_shipment.external_order_num }, 'shipments.json') if fosdick_shipment.ship_date.nil?
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ Override the email view to customise:
56
+
57
+ ```
58
+ app/views/spree/fosdick_shipment_mailer/order_shipped.html.erb
59
+ ```
60
+
61
+ ## Testing
62
+
63
+ Be sure to bundle your dependencies and then create a dummy test app for the specs to run against.
64
+
65
+ ```shell
66
+ bundle
67
+ bundle exec rake test_app
68
+ bundle exec rspec spec
69
+ ```
70
+
71
+ When testing your applications integration with this extension you may use it's factories.
72
+ Simply add this require statement to your spec_helper:
73
+
74
+ ```ruby
75
+ require 'spree_fosdick_integration/factories'
76
+ ```
@@ -0,0 +1,15 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/extension_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task default: [:spec]
10
+
11
+ desc 'Generates a dummy app for testing'
12
+ task :test_app do
13
+ ENV['LIB_NAME'] = 'spree_fosdick_integration'
14
+ Rake::Task['extension:test_app'].invoke
15
+ end
@@ -0,0 +1,18 @@
1
+ class Spree::Admin::FosdickShipmentsController < Spree::Admin::BaseController
2
+ respond_to :js, only: [:index]
3
+
4
+ def index
5
+ params[:q] ||= {}
6
+
7
+ params_clone = params[:q].deep_dup
8
+
9
+ [:created_at_gt, :created_at_lt, :ship_date_gt, :ship_date_lt].each do |date_param|
10
+ if params_clone[date_param].present?
11
+ params_clone[date_param] = Time.zone.parse(params_clone[date_param]).beginning_of_day rescue ''
12
+ end
13
+ end
14
+
15
+ @search = Spree::FosdickShipment.includes(:fosdick_exceptions).ransack(params_clone)
16
+ @fosdick_shipments = @search.result(distinct: true).page(params[:page]).per(Spree::Config[:admin_products_per_page]).order(created_at: :desc)
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ class FosdickShipmentMailer < ActionMailer::Base
3
+ default from: 'spree@example.com'
4
+
5
+ def order_shipped(shipment)
6
+ @shipment = shipment
7
+ @tracking = shipment.tracking,
8
+ @order_number = shipment.order.number
9
+
10
+ mail to: @shipment.order.email, subject: Spree.t('fosdick.order_shipped.subject')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ class Spree::FosdickException < ActiveRecord::Base
2
+ belongs_to :fosdick_shipment, class_name: 'Spree::FosdickShipment', foreign_key: :spree_fosdick_shipment_id
3
+
4
+ after_create :update_shipment_fosdick_state
5
+
6
+ def shipment
7
+ self.fosdick_shipment.shipment
8
+ end
9
+
10
+ private
11
+
12
+ def update_shipment_fosdick_state
13
+ self.shipment.update_column(:fosdick_state, "#{message.include?('Duplicate') ? 'duplicate' : 'exception'}")
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ class Spree::FosdickShipment < ActiveRecord::Base
2
+ belongs_to :shipment, class_name: 'Spree::Shipment', foreign_key: :spree_shipment_id
3
+ has_many :fosdick_exceptions, class_name: 'Spree::FosdickException', foreign_key: :spree_fosdick_shipment_id
4
+
5
+ scope :eligible_fosdick_shipments, -> { where.not(fosdick_order_num: nil).where(state: 'sent', ship_date: nil)}
6
+
7
+ serialize :tracking_number
8
+ end
@@ -0,0 +1,17 @@
1
+ Spree::Shipment.class_eval do
2
+ has_one :fosdick_shipment, class_name: 'Spree::FosdickShipment', foreign_key: :spree_shipment_id, dependent: :delete
3
+
4
+ def self.perform_fosdick_shipments
5
+ shipments = where(shipped_at: nil, state: 'ready', send_atempt: 0..2).where.not(fosdick_state: ['duplicate', 'success'])
6
+
7
+ JSON.parse(ActiveModel::ArraySerializer.new(shipments, each_serializer: ShipmentSerializer, root: false).to_json) if shipments.present?
8
+ end
9
+
10
+ def bill_to
11
+ order.bill_address
12
+ end
13
+
14
+ def ship_to
15
+ order.ship_address
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/admin/orders/index',
3
+ name: 'admin_fosdick_index_button',
4
+ insert_before: "erb[silent]:contains('if can? :edit, Spree::Order.new ')",
5
+ text: "<li style='padding-right: 5px;'>
6
+ <%= button_link_to 'Fosdick panel', admin_fosdick_shipments_path, icon: '', id: 'admin_fosdick_index' %>
7
+ </li>"
8
+ )
@@ -0,0 +1,18 @@
1
+ require 'active_model/serializer'
2
+
3
+ class AddressSerializer < ActiveModel::Serializer
4
+ attributes :firstname, :lastname, :address1, :address2, :zipcode, :city,
5
+ :state, :country, :phone
6
+
7
+ def country
8
+ object.country.try(:iso)
9
+ end
10
+
11
+ def state
12
+ if object.state
13
+ object.state.abbr
14
+ else
15
+ object.state_name
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_model/serializer'
2
+
3
+ class InventoryUnitSerializer < ActiveModel::Serializer
4
+ attributes :product_id, :name, :quantity, :price
5
+
6
+ def quantity
7
+ object.respond_to?(:quantity) ? object.quantity : 1
8
+ end
9
+
10
+ def price
11
+ object.line_item.price.round(2).to_f
12
+ end
13
+
14
+ def product_id
15
+ object.variant.sku
16
+ end
17
+
18
+ def name
19
+ object.variant.name
20
+ end
21
+ end
@@ -0,0 +1,104 @@
1
+ require 'active_model/serializer'
2
+
3
+ class ShipmentSerializer < ActiveModel::Serializer
4
+ attributes :id, :order_id, :email, :cost, :status, :stock_location,
5
+ :shipping_method, :tracking, :placed_on, :shipped_at, :totals,
6
+ :updated_at, :channel, :items, :shipping_method_code
7
+
8
+ has_one :bill_to, serializer: AddressSerializer, root: 'billing_address'
9
+ has_one :ship_to, serializer: AddressSerializer, root: 'shipping_address'
10
+
11
+ def id
12
+ object.number
13
+ end
14
+
15
+ def order_id
16
+ object.order.number
17
+ end
18
+
19
+ def email
20
+ object.order.email
21
+ end
22
+
23
+ def channel
24
+ object.order.channel || 'spree'
25
+ end
26
+
27
+ def cost
28
+ object.cost.to_f
29
+ end
30
+
31
+ def status
32
+ object.state
33
+ end
34
+
35
+ def stock_location
36
+ object.stock_location.name
37
+ end
38
+
39
+ def shipping_method
40
+ if object.shipping_method.try(:name)
41
+ if object.shipping_method.try(:name).include?('Standard') || object.shipping_method.try(:name).include?('Free')
42
+ ''
43
+ elsif object.shipping_method.try(:name).include?('Expedited')
44
+ object.shipping_method.try(:code)
45
+ end
46
+ else
47
+ ''
48
+ end
49
+ end
50
+
51
+ def shipping_method_code
52
+ object.shipping_method.try(:code)
53
+ end
54
+
55
+ def placed_on
56
+ if object.order.completed_at?
57
+ object.order.completed_at.getutc.try(:iso8601)
58
+ else
59
+ ''
60
+ end
61
+ end
62
+
63
+ def shipped_at
64
+ object.shipped_at.try(:iso8601)
65
+ end
66
+
67
+ def totals
68
+ {
69
+ item: object.order.display_item_total.to_s,
70
+ adjustment: object.order.display_adjustment_total.to_s, #adjustment_total.to_s,
71
+ tax: object.order.display_tax_total.to_s, #tax_total.to_s,
72
+ shipping: object.order.display_ship_total.to_s, #shipping_total.to_s
73
+ payment: object.order.payments.completed.sum(:amount).to_s,
74
+ order: object.order.display_total.to_s
75
+ }
76
+ end
77
+
78
+ def updated_at
79
+ object.updated_at.iso8601
80
+ end
81
+
82
+ def items
83
+ i = []
84
+ object.inventory_units.each do |inventory_unit|
85
+ i << InventoryUnitSerializer.new(inventory_unit, root: false)
86
+ end
87
+ i
88
+ end
89
+
90
+ private
91
+
92
+ def adjustment_total
93
+ object.order.adjustment_total.to_f
94
+ end
95
+
96
+ def shipping_total
97
+ object.order.shipment_total.to_f
98
+ end
99
+
100
+ def tax_total
101
+ object.order.tax_total.to_f
102
+ end
103
+
104
+ end
@@ -0,0 +1,21 @@
1
+ class ExceptionLogger
2
+
3
+ def log(code, message, instance_id)
4
+ # Include notification logic if needed
5
+ unless Spree::FosdickException.find_by(spree_fosdick_shipment_id: instance_id, error_code: code).present?
6
+ Spree::FosdickException.create! build_fosdick_exception_attributes(code, message, instance_id)
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def build_fosdick_exception_attributes(code, message, instance_id)
13
+ {
14
+ spree_fosdick_shipment_id: instance_id,
15
+ error_code: code,
16
+ message: message,
17
+ state: 'error',
18
+ happened_at: Time.now.in_time_zone
19
+ }
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ .row
2
+ - content_for :table_filter do
3
+ %div{'data-hook' => 'admin_fosdick_shipments_index_search'}
4
+ = search_form_for [:admin, @search] do |f|
5
+ .field-block.alpha.four.columns
6
+ .date-range-filter.field
7
+ = label_tag :q_created_at, Spree.t('fosdick.date_range_created')
8
+ .date-range-fields
9
+ = f.text_field :created_at_gt, class: 'datepicker datepicker-from', value: params[:q][:created_at_gt], placeholder: Spree.t(:start)
10
+ %span.range-divider{style: 'padding-top: 3px;'}
11
+ = f.text_field :created_at_lt, class: 'datepicker datepicker-to', value: params[:q][:created_at_lt], placeholder: Spree.t(:stop)
12
+ .field-block.alpha.four.columns
13
+ .date-range-filter.field
14
+ = label_tag :q_ship_date, Spree.t('fosdick.date_range_shipped')
15
+ .date-range-fields
16
+ = f.text_field :ship_date_gt, class: 'datepicker datepicker-from', value: params[:q][:ship_date_gt], placeholder: Spree.t(:start)
17
+ %span.range-divider{style: 'padding-top: 3px;'}
18
+ = f.text_field :ship_date_lt, class: 'datepicker datepicker-to', value: params[:q][:ship_date_lt], placeholder: Spree.t(:stop)
19
+
20
+ .field-block.alpha.four.columns
21
+ .date-range-filter.field
22
+ = label_tag :q_external_order_num_cont, Spree.t('fosdick.shipment_num')
23
+ = f.text_field :external_order_num_cont
24
+
25
+ .four.columns
26
+ .field
27
+ = label_tag :q_state_count, Spree.t('fosdick.integration_state')
28
+ = f.select :state_eq, [[Spree.t('fosdick.successfully_sent'),'sent'],
29
+ [Spree.t('fosdick.successfully_shipped'),'shipped'],
30
+ [Spree.t('fosdick.state_exception'),'exception']],
31
+ {include_blank: Spree.t('fosdick.select_integration_state')}, class: 'select2'
32
+
33
+ .clearfix
34
+ .actions.filter-actions
35
+ %div{'data-hook' => 'admin_abandoned_orders_index_search_buttons'}
36
+ = button Spree.t(:filter_results), 'search'