spree_fosdick_integration 0.0.3

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