spree_tradebyte 0.1.12.alpha

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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.byebug_history +39 -0
  3. data/.gitignore +22 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +24 -0
  6. data/.travis.yml +31 -0
  7. data/Appraisals +29 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE +26 -0
  10. data/README.md +59 -0
  11. data/Rakefile +21 -0
  12. data/app/assets/javascripts/spree/backend/spree_tradebyte.js +2 -0
  13. data/app/assets/javascripts/spree/frontend/spree_tradebyte.js +2 -0
  14. data/app/assets/stylesheets/spree/backend/spree_tradebyte.css +4 -0
  15. data/app/assets/stylesheets/spree/frontend/spree_tradebyte.css +4 -0
  16. data/app/jobs/spree_tradebyte/application_job.rb +7 -0
  17. data/app/jobs/spree_tradebyte/export_products_job.rb +229 -0
  18. data/app/jobs/spree_tradebyte/export_stock_job.rb +39 -0
  19. data/app/jobs/spree_tradebyte/import_orders_job.rb +70 -0
  20. data/app/jobs/spree_tradebyte/order/processor.rb +158 -0
  21. data/app/mailers/application_mailer.rb +5 -0
  22. data/app/mailers/spree_tradebyte/error_notification_mailer.rb +9 -0
  23. data/app/models/spree/order/mail_defuser.rb +15 -0
  24. data/app/models/spree/order/tradebyte_message_builder.rb +133 -0
  25. data/app/models/spree/order_decorator.rb +14 -0
  26. data/app/models/spree/order_updater_decorator.rb +18 -0
  27. data/app/models/spree/payment_method/tradebyte.rb +7 -0
  28. data/app/models/spree/product_decorator.rb +15 -0
  29. data/app/models/spree/reimbursement/mail_defuser.rb +9 -0
  30. data/app/models/spree/reimbursement_decorator.rb +11 -0
  31. data/app/models/spree/shipment_decorator.rb +9 -0
  32. data/app/models/spree/shipment_handler/mail_defuser.rb +7 -0
  33. data/app/models/spree/shipment_handler_decorator.rb +5 -0
  34. data/app/models/spree/variant_decorator.rb +47 -0
  35. data/app/overrides/order_index.rb +13 -0
  36. data/app/overrides/tradebyte_settings.rb +13 -0
  37. data/app/views/layouts/spree_tradebyte/mailer.html.erb +13 -0
  38. data/app/views/layouts/spree_tradebyte/mailer.text.erb +1 -0
  39. data/app/views/spree/admin/orders/_index_tradebyte_state_override.html.erb +7 -0
  40. data/app/views/spree/admin/orders/_index_tradebyte_state_override_head.html.erb +1 -0
  41. data/app/views/spree/admin/products/_tradebyte_fields.html.erb +15 -0
  42. data/app/views/spree/admin/variants/_tradebyte_fields.html.erb +16 -0
  43. data/app/views/spree_tradebyte/error_notification_mailer/exception_mail.html.erb +12 -0
  44. data/bin/rails +8 -0
  45. data/config/locales/en.yml +5 -0
  46. data/config/routes.rb +3 -0
  47. data/db/migrate/20190301122602_add_tradebyte_exported_to_spree_orders.rb +5 -0
  48. data/db/migrate/20190305123644_add_columns_to_spree_orders.rb +8 -0
  49. data/db/migrate/20190307133555_add_tradebyte_id_to_spree_line_items.rb +5 -0
  50. data/db/migrate/20190315162630_add_tradebyte_sku_to_line_items.rb +5 -0
  51. data/db/migrate/20190529120919_add_tradebyte_fields_to_products.rb +7 -0
  52. data/db/migrate/20190529145337_add_tradebyte_settings_to_spree_variants.rb +5 -0
  53. data/db/migrate/20190611114706_create_shipping_methods_for_tradebyte.rb +17 -0
  54. data/db/migrate/20190611114720_create_payment_method_for_tradebyte.rb +5 -0
  55. data/lib/generators/spree_tradebyte/install/install_generator.rb +28 -0
  56. data/lib/generators/spree_tradebyte/templates/initializer.rb +42 -0
  57. data/lib/helpers/setup_configuration.rb +27 -0
  58. data/lib/spree_tradebyte.rb +26 -0
  59. data/lib/spree_tradebyte/engine.rb +25 -0
  60. data/lib/spree_tradebyte/factories.rb +6 -0
  61. data/lib/spree_tradebyte/tb_logger.rb +14 -0
  62. data/lib/spree_tradebyte/tradebyte_ftp.rb +98 -0
  63. data/lib/spree_tradebyte/version.rb +18 -0
  64. data/lib/tasks/variants.rake +13 -0
  65. data/spree_tradebyte.gemspec +48 -0
  66. metadata +427 -0
@@ -0,0 +1,39 @@
1
+ require 'nokogiri'
2
+
3
+ class SpreeTradebyte::ExportStockJob < ApplicationJob
4
+ queue_as :tradebyte
5
+
6
+ def perform(*args)
7
+ stock_items = get_stock_items
8
+ stock_xml = parse_stock_items(stock_items)
9
+ transport_stock(stock_xml)
10
+ end
11
+
12
+ def get_stock_items
13
+ products = Spree::Product.active
14
+ Spree::Variant.includes(:stock_items).where(product: products).collect do |v|
15
+ {
16
+ sku: v.sku,
17
+ stock_item: v.stock_items.first
18
+ }
19
+ end
20
+ end
21
+
22
+ def parse_stock_items(stock_items)
23
+ Nokogiri::XML::Builder.new do |xml|
24
+ xml.TBSTOCK do
25
+ stock_items.each do |stock_item|
26
+ xml.ARTICLE do
27
+ xml.A_EAN stock_item[:sku]
28
+ xml.A_STOCK stock_item[:stock_item].count_on_hand
29
+ end
30
+ end
31
+ end
32
+ end.to_xml
33
+ end
34
+
35
+ def transport_stock(stock_xml)
36
+ ftp = SpreeTradebyte::TradebyteFTP.new(SpreeTradebyte.ftp_user, SpreeTradebyte.ftp_password, SpreeTradebyte.ftp_server, SpreeTradebyte.ftp_port)
37
+ ftp.put_stock_file(stock_xml)
38
+ end
39
+ end
@@ -0,0 +1,70 @@
1
+ require 'nokogiri'
2
+ require 'spree_tradebyte/order/processor'
3
+ require 'spree_tradebyte/tb_logger'
4
+
5
+ class SpreeTradebyte::ImportOrdersJob < ApplicationJob
6
+ include SpreeTradebyte::Order::Processor
7
+
8
+ queue_as :tradebyte
9
+
10
+ def perform(*args)
11
+ ftp = SpreeTradebyte::TradebyteFTP.new(SpreeTradebyte.ftp_user, SpreeTradebyte.ftp_password, SpreeTradebyte.ftp_server, SpreeTradebyte.ftp_port)
12
+ files = ftp.get_order_files
13
+
14
+ orders_hash = parse_order_files(files)
15
+ orders_hash.map do |order_hash|
16
+ process_order(order_hash)
17
+ end
18
+
19
+ ftp.archive_files(files.map {|f| f[:file_name]})
20
+ rescue StandardError => e
21
+ SpreeTradebyte::TBLogger.debug("Something went wrong while processing ALL TB ORDERS")
22
+ SpreeTradebyte::TBLogger.debug(e)
23
+ SpreeTradebyte::TBLogger.debug(e.backtrace.join("\n"))
24
+ ErrorNotificationMailer.exception_mail(e).deliver_now
25
+ end
26
+
27
+ def parse_order_files(files)
28
+ files.each_with_object([]) do |file, list|
29
+ xml = Nokogiri::XML(file[:file_contents])
30
+ xml.search('//ORDER_LIST/ORDER').each do |order_xml|
31
+ list << {
32
+ tb_id: order_xml.search('./ORDER_DATA/TB_ID').text,
33
+ channel_id: order_xml.search('./ORDER_DATA/CHANNEL_ID').text,
34
+ channel_sign: order_xml.search('./ORDER_DATA/CHANNEL_SIGN').text,
35
+ paid: order_xml.search('./ORDER_DATA/PAID').text.to_i == 1,
36
+ approved: order_xml.search('./ORDER_DATA/APPROVED').text.to_i == 1,
37
+ email: order_xml.search('./SELL_TO/EMAIL').text,
38
+ bill_address: parse_address(order_xml.search('./SELL_TO')),
39
+ ship_address: parse_address(order_xml.search('./SHIP_TO')),
40
+ line_items: parse_line_items(order_xml.search('./ITEMS/ITEM')),
41
+ completed_at: DateTime.parse(order_xml.search('./ORDER_DATA/DATE_CREATED').text)
42
+ }
43
+ end
44
+ end
45
+ end
46
+
47
+ def parse_address(address_xml)
48
+ {
49
+ firstname: address_xml.search('./FIRSTNAME').text,
50
+ lastname: address_xml.search('./LASTNAME').text,
51
+ address1: address_xml.search('./STREET_NO').text,
52
+ zipcode: address_xml.search('./ZIP').text,
53
+ city: address_xml.search('./CITY').text,
54
+ country: Spree::Country.find_by(iso: address_xml.search('./COUNTRY').text),
55
+ }
56
+ end
57
+
58
+ def parse_line_items(items_xml)
59
+ items_xml.map do |item_xml|
60
+ {
61
+ tb_id: item_xml.search('./TB_ID').text,
62
+ sku: item_xml.search('./EAN').text,
63
+ tradebyte_sku: item_xml.search('./SKU').text,
64
+ quantity: item_xml.search('./QUANTITY').text,
65
+ price: item_xml.search('./ITEM_PRICE').text,
66
+ }
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,158 @@
1
+ require 'spree_tradebyte/tb_logger'
2
+
3
+ class TradebyteOrderInvalid < StandardError;
4
+ end
5
+
6
+ module SpreeTradebyte
7
+
8
+ module Order
9
+ module Processor
10
+
11
+ def process_order(order_hash)
12
+ order = Spree::Order.find_by_tradebyte_id(order_hash[:tb_id])
13
+ return order unless order_hash[:approved]
14
+ begin
15
+ if order.present?
16
+ update_order(order, order_hash)
17
+ else
18
+ order = create_order(order_hash)
19
+ end
20
+
21
+ if order.payments.none? && order.persisted?
22
+ order.update_with_updater!
23
+ payment_method = Spree::PaymentMethod.find_by_type!('Spree::PaymentMethod::Tradebyte')
24
+ payment = order.payments.create!(payment_method: payment_method, amount: order.total)
25
+ payment.capture!
26
+ order.state = 'complete'
27
+ order.completed_at = order_hash[:completed_at]
28
+ order.save!
29
+ order.reload
30
+ order.update_with_updater!
31
+ raise TradebyteOrderInvalid("Tradebyte order #{order.number} not completed. Tradebyte ID #{order.tradebyte_id}") unless order.complete?
32
+ order.tb_send_confirm_messages
33
+ end
34
+ rescue StandardError => e
35
+ SpreeTradebyte::TBLogger.debug("Something went wrong while processing tb order #{order_hash[:tb_id]}")
36
+ SpreeTradebyte::TBLogger.debug(e.message)
37
+ SpreeTradebyte::TBLogger.debug(e.backtrace.join("\n"))
38
+ ErrorNotificationMailer.exception_mail(e, order).deliver_now
39
+ end
40
+ order
41
+ end
42
+
43
+ def update_order(order, order_hash)
44
+ #Not implemented
45
+ end
46
+
47
+ def create_order(order_hash)
48
+ order_attibutes = {
49
+ tradebyte_id: order_hash[:tb_id],
50
+ tradebyte_channel_id: order_hash[:channel_id],
51
+ tradebyte_channel_sign: order_hash[:channel_sign],
52
+ is_tradebyte_order: true,
53
+ }.merge(build_order_attributes(order_hash))
54
+
55
+ order = Spree::Order.new(order_attibutes)
56
+ unavailable_line_items, order.line_items = order.line_items.partition do |line_item|
57
+ !line_item.variant.present? || !line_item.variant.available? || !line_item.variant.can_supply?(line_item.quantity)
58
+ end # split variants in array of unavailable and available line items
59
+
60
+ if unavailable_line_items.any?
61
+ order.tb_send_cancel_messages("Items are out of stock", unavailable_line_items) rescue nil
62
+ end
63
+
64
+ if order.line_items.any?
65
+ unless order.save
66
+ order.tb_send_cancel_messages(order.errors.full_messages) if order
67
+ SpreeTradebyte::TBLogger.debug("Cancelled order #{order.try(:tradebyte_id)} due to #{order.errors.full_messages}")
68
+ return order
69
+ end
70
+ elsif order.line_items.none?
71
+ return order
72
+ end
73
+
74
+ create_shipments(order_hash, order)
75
+
76
+ order.save!
77
+ order
78
+
79
+ rescue StandardError => e
80
+ SpreeTradebyte::TBLogger.debug("Something went wrong while creating tb order #{order_hash[:tb_id]}")
81
+ SpreeTradebyte::TBLogger.debug(e.message)
82
+ SpreeTradebyte::TBLogger.debug(e.backtrace.join("\n"))
83
+ ErrorNotificationMailer.exception_mail(e, order).deliver_now
84
+ end
85
+
86
+ def create_shipments(order_hash, order)
87
+ channel_shipping_method = Spree::ShippingMethod.where('code LIKE CONCAT(\'%\',?,\'%\')', order_hash[:channel_sign]).first
88
+
89
+ raise TradebyteOrderInvalid.new('Could not find a matching shipping method') unless channel_shipping_method.present?
90
+
91
+ inventory_units = Spree::Stock::InventoryUnitBuilder.new(order).units
92
+
93
+ shipment = order.shipments.build
94
+ shipment.stock_location = Spree::StockLocation.find_by(default: true)
95
+
96
+ order.line_items.each do |line_item|
97
+
98
+ inventory_unit = inventory_units.detect { |iu| iu.variant_id.to_i == line_item.variant_id }
99
+
100
+ if inventory_unit.present?
101
+ inventory_unit.shipment = shipment
102
+ inventory_unit.state = 'on_hand'
103
+ inventory_unit.save!
104
+
105
+ # Don't assign shipments to this inventory unit more than once
106
+ inventory_units.delete(inventory_unit)
107
+ end
108
+ end
109
+
110
+ shipment.state = 'ready'
111
+ shipment.save!
112
+
113
+ rate = shipment.shipping_rates.create!(shipping_method: channel_shipping_method, cost: 0.0)
114
+ shipment.selected_shipping_rate_id = rate.id
115
+
116
+ shipment.save!
117
+ shipment.update_amounts
118
+
119
+ rescue StandardError => e
120
+ SpreeTradebyte::TBLogger.debug("Something went wrong while creating tb shipment for order #{order_hash[:tb_id]}")
121
+ SpreeTradebyte::TBLogger.debug(e.message)
122
+ SpreeTradebyte::TBLogger.debug(e.backtrace.join("\n"))
123
+ ErrorNotificationMailer.exception_mail(e, order).deliver_now
124
+ end
125
+
126
+ def build_order_attributes(order_hash)
127
+ {
128
+ email: order_hash[:email],
129
+ line_items_attributes: order_hash[:line_items].map(&method(:build_line_item_attributes)).compact,
130
+ bill_address_attributes: build_address_attributes(order_hash[:bill_address]),
131
+ ship_address_attributes: build_address_attributes(order_hash[:ship_address])
132
+ }
133
+ end
134
+
135
+ def build_line_item_attributes(line_item_hash)
136
+ {
137
+ variant: Spree::Variant.find_by_sku(line_item_hash[:sku]),
138
+ quantity: line_item_hash[:quantity],
139
+ price: line_item_hash[:price],
140
+ tradebyte_item_id: line_item_hash[:tb_id],
141
+ tradebyte_sku: line_item_hash[:tradebyte_sku]
142
+ }
143
+ end
144
+
145
+ def build_address_attributes(address_hash)
146
+ {
147
+ firstname: address_hash[:firstname],
148
+ lastname: address_hash[:lastname],
149
+ address1: address_hash[:address1],
150
+ zipcode: address_hash[:zipcode],
151
+ city: address_hash[:city],
152
+ country: address_hash[:country],
153
+ }
154
+ end
155
+
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,5 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: 'from@example.com'
3
+ layout 'mailer'
4
+ end
5
+
@@ -0,0 +1,9 @@
1
+ class SpreeTradebyte::ErrorNotificationMailer < ApplicationMailer
2
+
3
+ def exception_mail(exception, order = nil)
4
+ @exception = exception
5
+ @order = order
6
+ mail(to: SpreeTradebyte.admin_email, subject: 'SpreeTradebyte: Something went wrong')
7
+ end
8
+
9
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Order::MailDefuser
3
+ def deliver_order_confirmation_email
4
+ unless is_tradebyte_order?
5
+ super
6
+ end
7
+ end
8
+
9
+ def send_cancel_email
10
+ unless is_tradebyte_order?
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,133 @@
1
+ module Spree
2
+ class Order < Spree::Base
3
+ module TradebyteMessageBuilder
4
+ extend ActiveSupport::Concern
5
+
6
+ def tb_send_confirm_messages
7
+ return unless is_tradebyte_order?
8
+ line_item_message = Nokogiri::XML::Builder.new do |xml|
9
+ xml.MESSAGES_LIST do
10
+ line_items.map {|li| tb_confirm_message(xml, li)}
11
+ end
12
+ end
13
+
14
+ ftp = tb_ftp
15
+ ftp.put_message_file(line_item_message.to_xml)
16
+ ftp.close
17
+ end
18
+
19
+ def tb_send_cancel_messages(comment = nil, line_items = self.line_items)
20
+ return unless is_tradebyte_order?
21
+ line_item_message = Nokogiri::XML::Builder.new do |xml|
22
+ xml.MESSAGES_LIST do
23
+ line_items.map {|li| tb_cancel_message(xml, li, comment)}
24
+ end
25
+ end
26
+
27
+ ftp = tb_ftp
28
+ ftp.put_message_file(line_item_message.to_xml)
29
+ ftp.close
30
+ end
31
+
32
+ def tb_send_no_stock_messages(line_items)
33
+ return unless is_tradebyte_order?
34
+ line_item_message = Nokogiri::XML::Builder.new do |xml|
35
+ xml.MESSAGES_LIST do
36
+ line_items.map {|li| tb_no_stock_message(xml, li)}
37
+ end
38
+ end
39
+
40
+ ftp = tb_ftp
41
+ ftp.put_message_file(line_item_message.to_xml)
42
+ ftp.close
43
+ end
44
+
45
+ def tb_send_shipped_messages(inventory_units)
46
+ return unless is_tradebyte_order?
47
+ inventory_unit_message = Nokogiri::XML::Builder.new do |xml|
48
+ xml.MESSAGES_LIST do
49
+ inventory_units.map {|iu| tb_shipped_message(xml, iu)}
50
+ end
51
+ end
52
+
53
+ ftp = tb_ftp
54
+ ftp.put_message_file(inventory_unit_message.to_xml)
55
+ ftp.close
56
+ end
57
+
58
+ def tb_send_return_messages(return_items)
59
+ return unless is_tradebyte_order?
60
+ return_item_message = Nokogiri::XML::Builder.new do |xml|
61
+ xml.MESSAGES_LIST do
62
+ return_items.map {|ri| tb_return_message(xml, ri)}
63
+ end
64
+ end
65
+
66
+ ftp = tb_ftp
67
+ ftp.put_message_file(return_item_message.to_xml)
68
+ ftp.close
69
+ end
70
+
71
+ private
72
+
73
+ def tb_ftp
74
+ SpreeTradebyte::TradebyteFTP.new(SpreeTradebyte.ftp_user, SpreeTradebyte.ftp_password, SpreeTradebyte.ftp_server, SpreeTradebyte.ftp_port)
75
+ end
76
+
77
+ def tb_confirm_message(xml, line_item)
78
+ xml.MESSAGE do
79
+ xml.MESSAGE_TYPE 'ORDER_ACKNOWLEDGE'
80
+ xml.TB_ORDER_ID line_item.order.tradebyte_id
81
+ xml.TB_ORDER_ITEM_ID line_item.tradebyte_item_id
82
+ xml.SKU line_item.tradebyte_sku
83
+ xml.QUANTITY line_item.quantity
84
+ end
85
+ end
86
+
87
+ def tb_cancel_message(xml, line_item, comment = 'Cancelled')
88
+ xml.MESSAGE do
89
+ xml.MESSAGE_TYPE 'CUST_CANCEL'
90
+ xml.TB_ORDER_ID line_item.order.tradebyte_id
91
+ xml.TB_ORDER_ITEM_ID line_item.tradebyte_item_id
92
+ xml.SKU line_item.tradebyte_sku
93
+ xml.QUANTITY line_item.quantity
94
+ xml.COMMENT comment
95
+ end
96
+ end
97
+
98
+ def tb_no_stock_message(xml, line_item)
99
+ xml.MESSAGE do
100
+ xml.MESSAGE_TYPE 'NO_INVENTORY'
101
+ xml.TB_ORDER_ID self.tradebyte_id
102
+ xml.TB_ORDER_ITEM_ID line_item.tradebyte_item_id
103
+ xml.SKU line_item.tradebyte_sku
104
+ xml.QUANTITY line_item.quantity
105
+ end
106
+ end
107
+
108
+ def tb_shipped_message(xml, inventory_unit)
109
+ xml.MESSAGE do
110
+ xml.MESSAGE_TYPE 'SHIP'
111
+ xml.TB_ORDER_ID self.tradebyte_id
112
+ xml.TB_ORDER_ITEM_ID inventory_unit.line_item.tradebyte_item_id
113
+ xml.SKU inventory_unit.line_item.tradebyte_sku
114
+ xml.QUANTITY inventory_unit.quantity
115
+ xml.CARRIER_PARCEL_TYPE inventory_unit.shipment.shipping_method.send(SpreeTradebyte.parcel_type_method)
116
+ xml.IDCODE inventory_unit.shipment.tracking
117
+ end
118
+ end
119
+
120
+ def tb_return_message(xml, return_item)
121
+ xml.MESSAGE do
122
+ xml.MESSAGE_TYPE 'RETURN'
123
+ xml.TB_ORDER_ID self.tradebyte_id
124
+ xml.TB_ORDER_ITEM_ID return_item.inventory_unit.line_item.tradebyte_item_id
125
+ xml.SKU return_item.inventory_unit.line_item.tradebyte_sku
126
+ xml.QUANTITY return_item.inventory_unit.quantity
127
+ xml.RETURN_CAUSE return_item.return_authorization.try(:return_authorization_reason).try(:name)
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,14 @@
1
+ Spree::Order.class_eval do
2
+ include Spree::Order::TradebyteMessageBuilder
3
+
4
+ Spree::Order.state_machine.after_transition :to => :canceled, do: :cancel_tb_order
5
+
6
+ self.prepend(Spree::Order::MailDefuser)
7
+
8
+ private
9
+
10
+ def cancel_tb_order
11
+ tb_send_cancel_messages('The merchant cancelled the order')
12
+ end
13
+
14
+ end