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.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +76 -0
- data/Rakefile +15 -0
- data/app/controllers/spree/admin/fosdick_shipments_controller.rb +18 -0
- data/app/mailers/spree/fosdick_shipment_mailer.rb +13 -0
- data/app/models/spree/fosdick_exception.rb +15 -0
- data/app/models/spree/fosdick_shipment.rb +8 -0
- data/app/models/spree/shipment_decorator.rb +17 -0
- data/app/overrides/add_fosdick_panel.rb +8 -0
- data/app/serializers/address_serializer.rb +18 -0
- data/app/serializers/inventory_unit_serializer.rb +21 -0
- data/app/serializers/shipment_serializer.rb +104 -0
- data/app/services/exception_logger.rb +21 -0
- data/app/views/spree/admin/fosdick_shipments/_filters_bar.html.haml +36 -0
- data/app/views/spree/admin/fosdick_shipments/_fosdick_shipments_list.html.haml +28 -0
- data/app/views/spree/admin/fosdick_shipments/_fosdick_shipments_list_respond.js.erb +1 -0
- data/app/views/spree/admin/fosdick_shipments/_titles.html.haml +5 -0
- data/app/views/spree/admin/fosdick_shipments/index.html.haml +3 -0
- data/app/views/spree/admin/fosdick_shipments/index.js.haml +1 -0
- data/app/views/spree/fosdick_shipment_mailer/order_shipped.html.erb +135 -0
- data/config/locales/en.yml +14 -0
- data/config/locales/ru.yml +14 -0
- data/config/routes.rb +10 -0
- data/db/migrate/20151013153126_create_spree_fosdick_shipment.rb +17 -0
- data/db/migrate/20151015122038_create_fosdick_exceptions.rb +15 -0
- data/db/migrate/20151028105216_add_fosdick_atempt_and_state_spree_shipments.rb +6 -0
- data/lib/fosdick/documents/shipment.rb +89 -0
- data/lib/fosdick/processor.rb +64 -0
- data/lib/fosdick/receiver.rb +49 -0
- data/lib/fosdick/sender.rb +39 -0
- data/lib/generators/spree_fosdick_integration/install/install_generator.rb +32 -0
- data/lib/generators/spree_fosdick_integration/install/templates/fosdick.rb +6 -0
- data/lib/generators/spree_fosdick_integration/install/templates/fosdick.yml.template +18 -0
- data/lib/spree_fosdick_integration.rb +8 -0
- data/lib/spree_fosdick_integration/engine.rb +22 -0
- data/lib/spree_fosdick_integration/factories.rb +31 -0
- data/lib/spree_fosdick_integration/version.rb +3 -0
- data/script/rails +7 -0
- data/spec/controllers/admin/fosdick_shipments_controller_spec.rb +16 -0
- data/spec/models/spree/fosdick_exception_spec.rb +21 -0
- data/spec/models/spree/fosdick_shipment_spec.rb +9 -0
- data/spec/models/spree/shipment_spec.rb +50 -0
- data/spec/services/exception_logger_spec.rb +11 -0
- data/spec/services/fosdick/processor_spec.rb +94 -0
- data/spec/services/fosdick/receiver_spec.rb +43 -0
- data/spec/services/fosdick/sender_spec.rb +34 -0
- data/spec/spec_helper.rb +99 -0
- data/spree_fosdick_integration.gemspec +37 -0
- 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 @@
|
|
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: Выберите статус интеграции
|
data/config/routes.rb
ADDED
@@ -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,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
|