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