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,28 @@
1
+ = paginate fosdick_shipments
2
+ %table#listing_fosdick_shipments.index.sortable{'data-hook' => '', 'data-sortable-link' => update_positions_admin_fosdick_shipments_path}
3
+ %thead
4
+ %tr{'data-hook' => 'fosdick_shipments_header'}
5
+ %th.no-border
6
+ %th Fosdick order
7
+ %th External order
8
+ %th Tracking
9
+ %th Ship date
10
+ %th Created at
11
+ %th State
12
+ %th Errors
13
+ %tbody
14
+ - if fosdick_shipments.present?
15
+ - fosdick_shipments.each do |ship|
16
+ %tr{class: cycle('odd', 'even'), 'data-hook' => 'fosdick_shipments_row', id: ship.id}
17
+ %td.no-border
18
+ %span.handle
19
+ %td.align-center= ship.fosdick_order_num
20
+ %td.align-center= link_to ship.external_order_num, edit_admin_order_path(ship.shipment.order)
21
+ %td.align-center= ship.tracking_number
22
+ %td.align-center= ship.ship_date
23
+ %td.align-center= ship.created_at
24
+ %td.align-center= ship.state
25
+ - if ship.fosdick_exceptions.present? && ship.state != 'shipped'
26
+ %td{style: 'color: red;'}= ship.fosdick_exceptions.pluck(:message).uniq.join('. ')
27
+ - else
28
+ %td
@@ -0,0 +1 @@
1
+ $('table#listing_fosdick_shipments').html('<%= j(render('fosdick_shipments_list', fosdick_shipments: @fosdick_shipments))%>');
@@ -0,0 +1,5 @@
1
+ - content_for :page_title do
2
+ Fosdick integration panel
3
+ - content_for :page_actions do
4
+ %li
5
+ = button_link_to Spree.t(:back_to_orders_list), admin_orders_path, icon: 'arrow-left'
@@ -0,0 +1,3 @@
1
+ = render 'titles'
2
+ = render 'filters_bar'
3
+ = render 'fosdick_shipments_list', fosdick_shipments: @fosdick_shipments
@@ -0,0 +1 @@
1
+ = render 'fosdick_shipments_list_respond'
@@ -0,0 +1,135 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5
+ <title><%= Spree.t('fosdick.order_shipped.subject') %></title>
6
+ <style type="text/css">
7
+ #outlook a{padding:0;} /* Force Outlook to provide a "view in browser" button. */
8
+ body{width:100% !important;} .ReadMsgBody{width:100%;} .ExternalClass{width:100%;} /* Force Hotmail to display emails at full width */
9
+ body{-webkit-text-size-adjust:none;} /* Prevent Webkit platforms from changing default text sizes. */
10
+
11
+ body{font-family:Arial;margin:0; padding:0;}
12
+ img{border:0; height:auto; line-height:100%; outline:none; text-decoration:none;}
13
+ table td{border-collapse:collapse;}
14
+ #backgroundTable{height:100% !important; margin:0; padding:0; width:100% !important;}
15
+
16
+ body, #backgroundTable{
17
+ background-color:#FAFAFA;
18
+ }
19
+
20
+ #templateContainer{
21
+ border: 1px solid #DDDDDD;
22
+ margin-top: 10px;
23
+ padding: 20px;
24
+ }
25
+
26
+ h1, .h1{
27
+ color:#202020;
28
+ display:block;
29
+ font-family:Arial;
30
+ font-size:34px;
31
+ font-weight:bold;
32
+ line-height:100%;
33
+ margin-top:0;
34
+ margin-right:0;
35
+ margin-bottom:10px;
36
+ margin-left:0;
37
+ text-align:left;
38
+ }
39
+
40
+ h2, .h2{
41
+ color:#202020;
42
+ display:block;
43
+ font-family:Arial;
44
+ font-size:30px;
45
+ font-weight:bold;
46
+ line-height:100%;
47
+ margin-top:0;
48
+ margin-right:0;
49
+ margin-bottom:10px;
50
+ margin-left:0;
51
+ text-align:left;
52
+ }
53
+
54
+ h3, .h3{
55
+ color:#202020;
56
+ display:block;
57
+ font-family:Arial;
58
+ font-size:26px;
59
+ font-weight:bold;
60
+ line-height:100%;
61
+ margin-top:0;
62
+ margin-right:0;
63
+ margin-bottom:10px;
64
+ margin-left:0;
65
+ text-align:left;
66
+ }
67
+
68
+ h4, .h4{
69
+ color:#202020;
70
+ display:block;
71
+ font-family:Arial;
72
+ font-size:22px;
73
+ font-weight:bold;
74
+ line-height:100%;
75
+ margin-top:0;
76
+ margin-right:0;
77
+ margin-bottom:10px;
78
+ margin-left:0;
79
+ text-align:left;
80
+ }
81
+
82
+ #templateContainer, .bodyContent{
83
+ background-color:#FFFFFF;
84
+ }
85
+
86
+ .bodyContent div{
87
+ color:#505050;
88
+ font-family:Arial;
89
+ font-size:14px;
90
+ line-height:150%;
91
+ text-align:left;
92
+ }
93
+
94
+ .bodyContent div a:link, .bodyContent div a:visited, /* Yahoo! Mail Override */ .bodyContent div a .yshortcuts /* Yahoo! Mail Override */{
95
+ color:#336699;
96
+ font-weight:normal;
97
+ text-decoration:underline;
98
+ }
99
+
100
+ .bodyContent img{
101
+ display:inline;
102
+ height:auto;
103
+ }
104
+
105
+ #items {
106
+ margin: 10px 0;
107
+ }
108
+ </style>
109
+ </head>
110
+ <body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">
111
+ <center>
112
+ <table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="backgroundTable">
113
+ <tr>
114
+ <td align="center" valign="top">
115
+ <table border="0" cellpadding="0" cellspacing="0" width="600" id="templateContainer">
116
+ <tr>
117
+ <td align="center" valign="top">
118
+ <table border="0" cellpadding="0" cellspacing="0" width="600" id="templateBody">
119
+ <tr>
120
+ <td valign="top" class="bodyContent">
121
+ <%= "Your order #{@order_number} has shipped!" %><br>
122
+ <%= "Your tracking number - #{@tracking}" %>
123
+ </td>
124
+ </tr>
125
+ </table>
126
+ </td>
127
+ </tr>
128
+ </table>
129
+ <br />
130
+ </td>
131
+ </tr>
132
+ </table>
133
+ </center>
134
+ </body>
135
+ </html>
@@ -0,0 +1,14 @@
1
+ en:
2
+ spree:
3
+ fosdick:
4
+ order_shipped:
5
+ subject: Oreder shipped
6
+ fosdick_num: Fosdick Number
7
+ shipment_num: External order
8
+ state_exception: Exception
9
+ state_sent: Successfully sent
10
+ state_shipped: Successfully shipped
11
+ integration_state: Integration state
12
+ date_range_created: Date range - Created
13
+ date_range_shipped: Date range - Shipped
14
+ select_integration_state: Select integration state
@@ -0,0 +1,14 @@
1
+ ru:
2
+ spree:
3
+ fosdick:
4
+ order_shipped:
5
+ subject: Заказ отправлен
6
+ fosdick_num: Fosdick номер
7
+ shipment_num: Номер доставки
8
+ state_exception: Исключение
9
+ state_sent: Успешно отправлено
10
+ state_shipped: Успешно поставляется
11
+ integration_state: Статус интеграции
12
+ date_range_created: Диапазон дат - Дата создания
13
+ date_range_shipped: Диапазон дат - Поставляется
14
+ select_integration_state: Выберите статус интеграции
@@ -0,0 +1,10 @@
1
+ Spree::Core::Engine.routes.draw do
2
+ namespace :admin do
3
+ resources :fosdick_shipments, only: [:index] do
4
+ collection do
5
+ post :update_positions
6
+ end
7
+ end
8
+ end
9
+ get '123' => 'admin/fosdick_shipments#index'
10
+ end
@@ -0,0 +1,17 @@
1
+ class CreateSpreeFosdickShipment < ActiveRecord::Migration
2
+ def change
3
+ create_table :spree_fosdick_shipments do |t|
4
+ t.belongs_to :spree_shipment
5
+ t.string :fosdick_order_num
6
+ t.string :external_order_num
7
+ t.text :tracking_number
8
+ t.string :state
9
+ t.boolean :confirmation_sent, default: false, null: false
10
+
11
+ t.datetime :ship_date
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :spree_fosdick_shipments, :spree_shipment_id
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ class CreateFosdickExceptions < ActiveRecord::Migration
2
+ def change
3
+ create_table :spree_fosdick_exceptions do |t|
4
+ t.belongs_to :spree_fosdick_shipment
5
+ t.string :error_code
6
+ t.string :message
7
+ t.string :state
8
+
9
+ t.datetime :happened_at
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :spree_fosdick_exceptions, :spree_fosdick_shipment_id
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ class AddFosdickAtemptAndStateSpreeShipments < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_shipments, :send_atempt, :integer, default: 0, null: false
4
+ add_column :spree_shipments, :fosdick_state, :string, default: 'ready', null: false
5
+ end
6
+ end
@@ -0,0 +1,89 @@
1
+ module Fosdick
2
+ module Documents
3
+ class Shipment
4
+ def initialize(shipment, config)
5
+ @shipment = shipment
6
+ @config = config
7
+ end
8
+
9
+ def to_xml
10
+ builder = Nokogiri::XML::Builder.new do |xml|
11
+ xml.UnitycartOrderPost('xml:lang' => 'en-US') {
12
+ xml.ClientCode(@config['client_code'])
13
+ xml.Test('Y') if test?
14
+ xml.TransactionID(SecureRandom.hex(15))
15
+ xml.Order {
16
+ xml.ShippingMethod(@shipment['shipping_method'])
17
+ xml.Subtotal(0)
18
+ xml.Total(0)
19
+ xml.ExternalID("#{@shipment['id']}")
20
+ xml.AdCode(@shipment['adcode'] || @config['adcode'])
21
+ xml.Prepaid('Y')
22
+ xml.PaymentType(5)
23
+ xml.ShipFirstname truncate_name
24
+ xml.ShipLastname(@shipment['shipping_address']['lastname'])
25
+ xml.ShipAddress1(@shipment['shipping_address']['address1'])
26
+ xml.ShipAddress2(@shipment['shipping_address']['address2'])
27
+ xml.ShipCity(truncate_city)
28
+
29
+ if (@shipment['shipping_address']['country'] != 'US')
30
+ xml.ShipStateOther(ship_state)
31
+ else
32
+ xml.ShipState(ship_state)
33
+ end
34
+
35
+ xml.ShipZip(@shipment['shipping_address']['zipcode'])
36
+ xml.ShipCountry(@shipment['shipping_address']['country'])
37
+ xml.ShipPhone(@shipment['shipping_address']['phone'])
38
+ xml.Email(@shipment['email'])
39
+ xml.Code(@shipment['shipping_method_code'])
40
+
41
+ (1..5).each do |i|
42
+ next unless @shipment.key? "custom#{i}"
43
+ xml.send("Custom#{i}", @shipment["custom#{i}"])
44
+ end
45
+
46
+ xml.Items {
47
+ @shipment['items'].each_with_index do |item, index|
48
+ xml.Item {
49
+ xml.Inv item['product_id']
50
+ xml.Qty item['quantity']
51
+ xml.PricePer 0
52
+ }
53
+ end
54
+ }
55
+ }
56
+ }
57
+ end
58
+
59
+ builder.to_xml
60
+ end
61
+
62
+ private
63
+
64
+ def truncate_name
65
+ if @shipment['shipping_address']['firstname'].present?
66
+ name = @shipment['shipping_address']['firstname']
67
+
68
+ (name.length > 15) ? name.slice(0..15) : name
69
+ end
70
+ end
71
+
72
+ def test?
73
+ @config['test']
74
+ end
75
+
76
+ def truncate_city
77
+ city = @shipment['shipping_address']['city']
78
+
79
+ (city.length > 12) ? city.slice(0..12) : city
80
+ end
81
+
82
+ def ship_state
83
+ state = @shipment['shipping_address']['state']
84
+
85
+ FOSDICK_STATES_EXCEPTIONS.has_key?(state) ? FOSDICK_STATES_EXCEPTIONS[state] : ModelUN.convert_state_name(state)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,64 @@
1
+ module Fosdick
2
+ class Processor
3
+ def self.send_shipment(shipment, config)
4
+ doc = Fosdick::Documents::Shipment.new(shipment, config).to_xml
5
+ res = Fosdick::Sender.send_doc(doc, config)
6
+
7
+ log_fosdick_shipment(shipment, res)
8
+ end
9
+
10
+ def self.receive_shipment(options = {}, end_point = 'shipmentdetail.json' )
11
+ res = Fosdick::Receiver.new(end_point, options).call_api(FOSDICK_CONFIG)
12
+
13
+ update_shipment_info(res, options)
14
+ end
15
+
16
+ private
17
+
18
+ def self.log_fosdick_shipment(shipment, fosdick_response)
19
+ spree_shipment = Spree::Shipment.find_by_number(shipment['id'])
20
+
21
+ if fosdick_response.is_a? String
22
+ Spree::FosdickShipment.where(fosdick_order_num: fosdick_response).first_or_create(
23
+ spree_shipment_id: spree_shipment.id,
24
+ external_order_num: shipment['id'],
25
+ state: 'sent')
26
+ spree_shipment.update(state: 'shipped', fosdick_state: 'success')
27
+
28
+ else
29
+ fosdick_shipment = Spree::FosdickShipment.create(
30
+ spree_shipment_id: spree_shipment.id,
31
+ external_order_num: shipment['id'],
32
+ state: 'exception')
33
+ Array(fosdick_response[:errors]).map {|exception| ExceptionLogger.new.log(fosdick_response[:code], exception.join(' - '), fosdick_shipment.id)}
34
+ end
35
+
36
+ spree_shipment.increment!(:send_atempt)
37
+ end
38
+
39
+ def self.update_shipment_info(fosdick_response, options)
40
+ shipment = Spree::Shipment.find_by_number(options[:external_order_num])
41
+ fosdick_shipment = Spree::FosdickShipment.where(external_order_num: options[:external_order_num], spree_shipment_id: shipment.id).first_or_create
42
+
43
+ if fosdick_response.is_a? Array
44
+ fosdick_response.each do |fos_shipment|
45
+ trackings = []
46
+ ship_date = fos_shipment.has_key?('ship_date') ? fos_shipment['ship_date'].to_date : nil
47
+
48
+ fos_shipment['trackings'].each {|tracking| trackings << tracking['tracking_num'] unless trackings.include?(tracking['tracking_num']) }
49
+ fosdick_shipment.update(tracking_number: trackings, ship_date: ship_date, fosdick_order_num: fos_shipment['fosdick_order_num'])
50
+ fosdick_shipment.update(state: 'shipped') unless ship_date.nil?
51
+ shipment.update(shipped_at: ship_date, state: 'shipped', tracking: trackings.join(', ')) if shipment.present?
52
+ shipment.order.update(shipment_state: 'shipped') if ship_date.present?
53
+
54
+ if fosdick_shipment.confirmation_sent == false && trackings.present?
55
+ Spree::FosdickShipmentMailer.order_shipped(shipment).deliver
56
+ fosdick_shipment.update(confirmation_sent: true)
57
+ end
58
+ end
59
+ elsif fosdick_response.is_a? Hash
60
+ ExceptionLogger.new.log('Error', fosdick_response['error'], fosdick_shipment.id)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,49 @@
1
+ require 'open-uri'
2
+ require 'net/http'
3
+
4
+ module Fosdick
5
+ class Receiver
6
+
7
+ def initialize(end_point, request_options={})
8
+ @end_point = end_point
9
+ @request_options = request_options
10
+ end
11
+
12
+ def call_api(fosdick_options)
13
+ fetch_data(@end_point, fosdick_options)
14
+ end
15
+
16
+ private
17
+
18
+ def fetch_data(end_point, options={})
19
+ # required sleep 2 sec for Fosdick API
20
+ sleep 2
21
+ response = request(end_point, options)
22
+ # process only valid to JSON.parse data
23
+ begin
24
+ response.nil? ? nil : JSON.parse(response.body)
25
+ rescue JSON::ParserError
26
+ nil
27
+ end
28
+ end
29
+
30
+ def api_url(end_point)
31
+ "https://www.customerstatus.com/fosdickapi/#{end_point}?#{ { }.merge(@request_options).to_query }"
32
+ end
33
+
34
+ def request(end_point, options={})
35
+ uri = URI(api_url(end_point))
36
+
37
+ Net::HTTP.start(uri.host, uri.port,
38
+ use_ssl: uri.scheme == 'https',
39
+ verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
40
+
41
+ request = Net::HTTP::Get.new uri.request_uri
42
+ request['Accept'] = 'application/json'
43
+ request.basic_auth(options['basic_auth']['login'], options['basic_auth']['password']) if options['basic_auth'].present?
44
+
45
+ http.request request
46
+ end
47
+ end
48
+ end
49
+ end