solidus_shipstation 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.circleci/config.yml +41 -0
- data/.gem_release.yml +5 -0
- data/.github/stale.yml +17 -0
- data/.github_changelog_generator +2 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +40 -0
- data/CHANGELOG.md +36 -0
- data/Gemfile +33 -0
- data/LICENSE +26 -0
- data/README.md +208 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/spree/backend/solidus_shipstation.js +2 -0
- data/app/assets/javascripts/spree/frontend/solidus_shipstation.js +2 -0
- data/app/assets/stylesheets/spree/backend/solidus_shipstation.css +4 -0
- data/app/assets/stylesheets/spree/frontend/solidus_shipstation.css +4 -0
- data/app/controllers/spree/shipstation_controller.rb +45 -0
- data/app/decorators/models/solidus_shipstation/spree/shipment_decorator.rb +33 -0
- data/app/helpers/solidus_shipstation/export_helper.rb +32 -0
- data/app/jobs/solidus_shipstation/api/schedule_shipment_syncs_job.rb +19 -0
- data/app/jobs/solidus_shipstation/api/sync_shipments_job.rb +41 -0
- data/app/queries/solidus_shipstation/shipment/between_query.rb +14 -0
- data/app/queries/solidus_shipstation/shipment/exportable_query.rb +24 -0
- data/app/queries/solidus_shipstation/shipment/pending_api_sync_query.rb +63 -0
- data/app/views/spree/shipstation/export.xml.builder +58 -0
- data/bin/console +17 -0
- data/bin/rails +7 -0
- data/bin/rails-engine +13 -0
- data/bin/rails-sandbox +16 -0
- data/bin/rake +7 -0
- data/bin/sandbox +86 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20210220093010_add_shipstation_api_sync_fields.rb +9 -0
- data/lib/generators/solidus_shipstation/install/install_generator.rb +27 -0
- data/lib/generators/solidus_shipstation/install/templates/initializer.rb +62 -0
- data/lib/solidus_shipstation.rb +16 -0
- data/lib/solidus_shipstation/api/batch_syncer.rb +70 -0
- data/lib/solidus_shipstation/api/client.rb +38 -0
- data/lib/solidus_shipstation/api/rate_limited_error.rb +23 -0
- data/lib/solidus_shipstation/api/request_error.rb +33 -0
- data/lib/solidus_shipstation/api/request_runner.rb +50 -0
- data/lib/solidus_shipstation/api/shipment_serializer.rb +84 -0
- data/lib/solidus_shipstation/api/threshold_verifier.rb +28 -0
- data/lib/solidus_shipstation/configuration.rb +44 -0
- data/lib/solidus_shipstation/engine.rb +19 -0
- data/lib/solidus_shipstation/errors.rb +23 -0
- data/lib/solidus_shipstation/shipment_notice.rb +58 -0
- data/lib/solidus_shipstation/testing_support/factories.rb +4 -0
- data/lib/solidus_shipstation/version.rb +5 -0
- data/solidus_shipstation.gemspec +40 -0
- data/spec/controllers/spree/shipstation_controller_spec.rb +103 -0
- data/spec/fixtures/shipstation_xml_schema.xsd +171 -0
- data/spec/jobs/solidus_shipstation/api/schedule_shipment_syncs_job_spec.rb +32 -0
- data/spec/jobs/solidus_shipstation/api/sync_shipments_job_spec.rb +102 -0
- data/spec/lib/solidus_shipstation/api/batch_syncer_spec.rb +229 -0
- data/spec/lib/solidus_shipstation/api/client_spec.rb +120 -0
- data/spec/lib/solidus_shipstation/api/rate_limited_error_spec.rb +21 -0
- data/spec/lib/solidus_shipstation/api/request_error_spec.rb +20 -0
- data/spec/lib/solidus_shipstation/api/request_runner_spec.rb +64 -0
- data/spec/lib/solidus_shipstation/api/shipment_serializer_spec.rb +12 -0
- data/spec/lib/solidus_shipstation/api/threshold_verifier_spec.rb +61 -0
- data/spec/lib/solidus_shipstation/shipment_notice_spec.rb +111 -0
- data/spec/lib/solidus_shipstation_spec.rb +9 -0
- data/spec/models/spree/shipment_spec.rb +49 -0
- data/spec/queries/solidus_shipstation/shipment/between_query_spec.rb +53 -0
- data/spec/queries/solidus_shipstation/shipment/exportable_query_spec.rb +53 -0
- data/spec/queries/solidus_shipstation/shipment/pending_api_sync_query_spec.rb +37 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/configuration_helper.rb +13 -0
- data/spec/support/controllers.rb +1 -0
- data/spec/support/webmock.rb +3 -0
- data/spec/support/xsd.rb +5 -0
- metadata +248 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusShipstation
|
4
|
+
module Api
|
5
|
+
class ThresholdVerifier
|
6
|
+
class << self
|
7
|
+
def call(shipment)
|
8
|
+
return false unless shipment.order.completed?
|
9
|
+
|
10
|
+
!!(shipment_requires_creation?(shipment) || shipment_requires_update?(shipment))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def shipment_requires_creation?(shipment)
|
16
|
+
shipment.shipstation_synced_at.nil? &&
|
17
|
+
Time.zone.now - shipment.order.updated_at < SolidusShipstation.config.api_sync_threshold
|
18
|
+
end
|
19
|
+
|
20
|
+
def shipment_requires_update?(shipment)
|
21
|
+
shipment.shipstation_synced_at &&
|
22
|
+
shipment.shipstation_synced_at < shipment.order.updated_at &&
|
23
|
+
Time.zone.now - shipment.order.updated_at < SolidusShipstation.config.api_sync_threshold
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusShipstation
|
4
|
+
class Configuration
|
5
|
+
attr_accessor(
|
6
|
+
:username,
|
7
|
+
:password,
|
8
|
+
:weight_units,
|
9
|
+
:ssl_encrypted,
|
10
|
+
:capture_at_notification,
|
11
|
+
:export_canceled_shipments,
|
12
|
+
:api_batch_size,
|
13
|
+
:api_sync_threshold,
|
14
|
+
:api_shipment_serializer,
|
15
|
+
:api_key,
|
16
|
+
:api_secret,
|
17
|
+
:api_shipment_matcher,
|
18
|
+
:error_handler,
|
19
|
+
)
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@api_batch_size = 100
|
23
|
+
@api_sync_threshold = 7.days
|
24
|
+
@error_handler = ->(_error, _extra = {}) {
|
25
|
+
Rails.logger.error "#{error.inspect} (#{extra.inspect})"
|
26
|
+
}
|
27
|
+
@api_shipment_matcher = proc do |shipstation_order, shipments|
|
28
|
+
shipments.find { |shipment| shipment.number == shipstation_order['orderNumber'] }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def configuration
|
35
|
+
@configuration ||= Configuration.new
|
36
|
+
end
|
37
|
+
|
38
|
+
alias config configuration
|
39
|
+
|
40
|
+
def configure
|
41
|
+
yield configuration
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'solidus_core'
|
4
|
+
require 'solidus_support'
|
5
|
+
|
6
|
+
module SolidusShipstation
|
7
|
+
class Engine < Rails::Engine
|
8
|
+
include SolidusSupport::EngineExtensions
|
9
|
+
|
10
|
+
isolate_namespace ::Spree
|
11
|
+
|
12
|
+
engine_name 'solidus_shipstation'
|
13
|
+
|
14
|
+
# use rspec for tests
|
15
|
+
config.generators do |g|
|
16
|
+
g.test_framework :rspec
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusShipstation
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class OrderNotPaidError < Error
|
7
|
+
def initialize(order, *args)
|
8
|
+
super("Order #{order.number} is not paid and capture_at_notification is false", *args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ShipmentNotFoundError < Error
|
13
|
+
def initialize(shipment_number, *args)
|
14
|
+
super("Could not find shipment with number #{shipment_number}", *args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class PaymentError < Error
|
19
|
+
def initialize(payment, *args)
|
20
|
+
super("Could not process payment #{payment.id}", *args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusShipstation
|
4
|
+
class ShipmentNotice
|
5
|
+
attr_reader :shipment_number, :shipment_tracking
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def from_payload(params)
|
9
|
+
new(
|
10
|
+
shipment_number: params[:order_number],
|
11
|
+
shipment_tracking: params[:tracking_number],
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(shipment_number:, shipment_tracking:)
|
17
|
+
@shipment_number = shipment_number
|
18
|
+
@shipment_tracking = shipment_tracking
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply
|
22
|
+
unless shipment
|
23
|
+
raise ShipmentNotFoundError, shipment
|
24
|
+
end
|
25
|
+
|
26
|
+
process_payment
|
27
|
+
ship_shipment
|
28
|
+
|
29
|
+
shipment
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def shipment
|
35
|
+
@shipment ||= ::Spree::Shipment.find_by(number: shipment_number)
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_payment
|
39
|
+
return if shipment.order.paid?
|
40
|
+
|
41
|
+
unless SolidusShipstation.configuration.capture_at_notification
|
42
|
+
raise OrderNotPaidError, shipment.order
|
43
|
+
end
|
44
|
+
|
45
|
+
shipment.order.payments.pending.each do |payment|
|
46
|
+
payment.capture!
|
47
|
+
rescue ::Spree::Core::GatewayError
|
48
|
+
raise PaymentError, payment
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ship_shipment
|
53
|
+
shipment.update!(tracking: shipment_tracking)
|
54
|
+
shipment.ship! if shipment.can_ship?
|
55
|
+
shipment.order.recalculate
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/solidus_shipstation/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'solidus_shipstation'
|
7
|
+
spec.version = SolidusShipstation::VERSION
|
8
|
+
spec.authors = ['Stephen Puiszis']
|
9
|
+
spec.email = 'steve@tablexi.com'
|
10
|
+
|
11
|
+
spec.summary = 'Solidus/ShipStation Integration'
|
12
|
+
spec.description = 'Integrates ShipStation API with Solidus. Supports exporting shipments and importing tracking numbers'
|
13
|
+
spec.homepage = 'https://github.com/solidusio-contrib/solidus_shipstation'
|
14
|
+
spec.license = 'BSD-3-Clause'
|
15
|
+
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/solidusio-contrib/solidus_shipstation'
|
18
|
+
spec.metadata['changelog_uri'] = 'https://github.com/solidusio-contrib/solidus_shipstation/blob/master/CHANGELOG.md'
|
19
|
+
|
20
|
+
spec.required_ruby_version = Gem::Requirement.new('~> 2.5')
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
|
25
|
+
|
26
|
+
spec.files = files.grep_v(%r{^(test|spec|features)/})
|
27
|
+
spec.test_files = files.grep(%r{^(test|spec|features)/})
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_dependency 'httparty', '~> 0.18'
|
33
|
+
spec.add_dependency 'solidus_core', ['>= 2.0.0', '< 4']
|
34
|
+
spec.add_dependency 'solidus_support', '~> 0.5'
|
35
|
+
|
36
|
+
spec.add_development_dependency 'rails-controller-testing'
|
37
|
+
spec.add_development_dependency 'rspec-xsd'
|
38
|
+
spec.add_development_dependency 'solidus_dev_support', '~> 2.5'
|
39
|
+
spec.add_development_dependency 'webmock'
|
40
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Spree::ShipstationController do
|
4
|
+
render_views
|
5
|
+
|
6
|
+
describe '#export' do
|
7
|
+
context 'when the authentication is invalid' do
|
8
|
+
it 'returns an error error' do
|
9
|
+
get :export, params: { format: 'xml' }
|
10
|
+
|
11
|
+
expect(response.status).to eq(401)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when the authentication is valid' do
|
16
|
+
it 'responds with 200 OK' do
|
17
|
+
stub_shipstation_auth
|
18
|
+
create(:order_ready_to_ship)
|
19
|
+
|
20
|
+
get :export,
|
21
|
+
params: {
|
22
|
+
start_date: 1.day.ago.strftime('%m/%d/%Y %H:%M'),
|
23
|
+
end_date: 1.day.from_now.strftime('%m/%d/%Y %H:%M'),
|
24
|
+
format: 'xml'
|
25
|
+
}
|
26
|
+
|
27
|
+
expect(response.status).to eq(200)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'generates ShipStation-compliant XML' do
|
31
|
+
stub_shipstation_auth
|
32
|
+
create(:order_ready_to_ship)
|
33
|
+
|
34
|
+
get :export, params: {
|
35
|
+
start_date: 1.day.ago.strftime('%m/%d/%Y %H:%M'),
|
36
|
+
end_date: 1.day.from_now.strftime('%m/%d/%Y %H:%M'),
|
37
|
+
format: 'xml'
|
38
|
+
}
|
39
|
+
|
40
|
+
expect(response.body).to pass_validation('spec/fixtures/shipstation_xml_schema.xsd')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#shipnotify' do
|
46
|
+
context 'when the authentication is valid' do
|
47
|
+
context 'when the shipment can be found' do
|
48
|
+
it 'responds with 200 OK' do
|
49
|
+
stub_shipstation_auth
|
50
|
+
shipment = create(:order_ready_to_ship).shipments.first
|
51
|
+
|
52
|
+
post :shipnotify, params: {
|
53
|
+
order_number: shipment.number,
|
54
|
+
tracking_number: '123456',
|
55
|
+
format: 'xml',
|
56
|
+
}
|
57
|
+
shipment.reload
|
58
|
+
|
59
|
+
expect(response.status).to eq(200)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'updates the shipment' do
|
63
|
+
stub_shipstation_auth
|
64
|
+
shipment = create(:order_ready_to_ship).shipments.first
|
65
|
+
|
66
|
+
post :shipnotify, params: {
|
67
|
+
order_number: shipment.number,
|
68
|
+
tracking_number: '123456',
|
69
|
+
format: 'xml',
|
70
|
+
}
|
71
|
+
shipment.reload
|
72
|
+
|
73
|
+
expect(shipment).to have_attributes(
|
74
|
+
tracking: '123456',
|
75
|
+
state: 'shipped',
|
76
|
+
shipped_at: an_instance_of(ActiveSupport::TimeWithZone),
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when the shipment cannot be found' do
|
82
|
+
it 'responds with 400 Bad Request' do
|
83
|
+
stub_shipstation_auth
|
84
|
+
shipment = create(:order_ready_to_ship).shipments.first
|
85
|
+
|
86
|
+
post :shipnotify, params: {
|
87
|
+
order_number: 'ABC123',
|
88
|
+
tracking_number: '123456',
|
89
|
+
format: 'xml',
|
90
|
+
}
|
91
|
+
shipment.reload
|
92
|
+
|
93
|
+
expect(response.status).to eq(400)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def stub_shipstation_auth(username = 'mario', password = 'lemieux')
|
100
|
+
stub_configuration(username: username, password: password)
|
101
|
+
request.headers['Authorization'] = ActionController::HttpAuthentication::Basic.encode_credentials(username, password)
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
2
|
+
<xs:element name="Orders">
|
3
|
+
<xs:complexType>
|
4
|
+
<xs:sequence>
|
5
|
+
<xs:element name="Order" maxOccurs="unbounded" minOccurs="0">
|
6
|
+
<xs:complexType>
|
7
|
+
<xs:all>
|
8
|
+
<xs:element type="String50" name="OrderID" minOccurs="0"/>
|
9
|
+
<xs:element type="String50" name="OrderNumber"/>
|
10
|
+
<xs:element type="DateTime" name="OrderDate"/>
|
11
|
+
<xs:element type="String50" name="OrderStatus"/>
|
12
|
+
<xs:element type="DateTime" name="LastModified"/>
|
13
|
+
<xs:element type="String100" name="ShippingMethod" minOccurs="0"/>
|
14
|
+
<xs:element type="String50" name="PaymentMethod" minOccurs="0"/>
|
15
|
+
<xs:element type="xs:float" name="OrderTotal"/>
|
16
|
+
<xs:element type="xs:float" name="TaxAmount" minOccurs="0"/>
|
17
|
+
<xs:element type="xs:float" name="ShippingAmount" minOccurs="0"/>
|
18
|
+
<xs:element type="String1000" name="CustomerNotes" minOccurs="0"/>
|
19
|
+
<xs:element type="String1000" name="InternalNotes" minOccurs="0"/>
|
20
|
+
<xs:element type="xs:boolean" name="Gift" minOccurs="0"/>
|
21
|
+
<xs:element type="String1000" name="GiftMessage" minOccurs="0"/>
|
22
|
+
<xs:element type="String100" name="CustomField1" minOccurs="0"/>
|
23
|
+
<xs:element type="String100" name="CustomField2" minOccurs="0"/>
|
24
|
+
<xs:element type="String100" name="CustomField3" minOccurs="0"/>
|
25
|
+
<xs:element type="String100" name="RequestedWarehouse" minOccurs="0"/>
|
26
|
+
<xs:element type="String50" name="Source" minOccurs="0" />
|
27
|
+
<xs:element name="Customer">
|
28
|
+
<xs:complexType>
|
29
|
+
<xs:all>
|
30
|
+
<xs:element type="String100" name="CustomerCode"/>
|
31
|
+
<xs:element name="BillTo">
|
32
|
+
<xs:complexType>
|
33
|
+
<xs:all>
|
34
|
+
<xs:element type="String100" name="Name"/>
|
35
|
+
<xs:element type="String100" name="Company" minOccurs="0"/>
|
36
|
+
<xs:element type="String50" name="Phone" minOccurs="0"/>
|
37
|
+
<xs:element type="Email" name="Email" minOccurs="0"/>
|
38
|
+
<xs:element type="String200" name="Address1" minOccurs="0"/>
|
39
|
+
<xs:element type="String200" name="Address2" minOccurs="0"/>
|
40
|
+
<xs:element type="String100" name="City" minOccurs="0"/>
|
41
|
+
<xs:element type="String100" name="State" minOccurs="0"/>
|
42
|
+
<xs:element type="String50" name="PostalCode" minOccurs="0"/>
|
43
|
+
<xs:element type="StringExactly2" name="Country" minOccurs="0"/>
|
44
|
+
</xs:all>
|
45
|
+
</xs:complexType>
|
46
|
+
</xs:element>
|
47
|
+
<xs:element name="ShipTo">
|
48
|
+
<xs:complexType>
|
49
|
+
<xs:all>
|
50
|
+
<xs:element type="String100" name="Name"/>
|
51
|
+
<xs:element type="String100" name="Company" minOccurs="0"/>
|
52
|
+
<xs:element type="String200" name="Address1"/>
|
53
|
+
<xs:element type="String200" name="Address2" minOccurs="0"/>
|
54
|
+
<xs:element type="String100" name="City"/>
|
55
|
+
<xs:element type="String100" name="State" minOccurs="0"/>
|
56
|
+
<xs:element type="String50" name="PostalCode" minOccurs="1"/>
|
57
|
+
<xs:element type="StringExactly2" name="Country"/>
|
58
|
+
<xs:element type="String50" name="Phone" minOccurs="0"/>
|
59
|
+
</xs:all>
|
60
|
+
</xs:complexType>
|
61
|
+
</xs:element>
|
62
|
+
</xs:all>
|
63
|
+
</xs:complexType>
|
64
|
+
</xs:element>
|
65
|
+
<xs:element name="Items">
|
66
|
+
<xs:complexType>
|
67
|
+
<xs:sequence>
|
68
|
+
<xs:element name="Item" maxOccurs="unbounded" minOccurs="0">
|
69
|
+
<xs:complexType>
|
70
|
+
<xs:all>
|
71
|
+
<xs:element type="String50" name="LineItemID" minOccurs="0"/>
|
72
|
+
<xs:element type="String100" name="SKU"/>
|
73
|
+
<xs:element type="String200" name="Name"/>
|
74
|
+
<xs:element type="xs:boolean" name="Adjustment" minOccurs="0"/>
|
75
|
+
<xs:element type="xs:anyURI" name="ImageUrl" minOccurs="0"/>
|
76
|
+
<xs:element type="xs:float" name="Weight" minOccurs="0"/>
|
77
|
+
<xs:element name="WeightUnits" minOccurs="0">
|
78
|
+
<xs:simpleType>
|
79
|
+
<xs:restriction base="xs:string">
|
80
|
+
<xs:pattern value="|pound|pounds|lb|lbs|gram|grams|gm|oz|ounces|Pound|Pounds|Lb|Lbs|Gram|Grams|Gm|Oz|Ounces|POUND|POUNDS|LB|LBS|GRAM|GRAMS|GM|OZ|OUNCES"/>
|
81
|
+
</xs:restriction>
|
82
|
+
</xs:simpleType>
|
83
|
+
</xs:element>
|
84
|
+
<xs:element name="Dimensions" minOccurs="0">
|
85
|
+
<xs:complexType>
|
86
|
+
<xs:all>
|
87
|
+
<xs:element name="DimensionUnits" minOccurs="0">
|
88
|
+
<xs:simpleType>
|
89
|
+
<xs:restriction base="xs:string">
|
90
|
+
<xs:pattern value="|inch|inches|in|centimeter|centimeters|cm|INCH|INCHES|IN|CENTIMETER|CENTIMETERS|CM"/>
|
91
|
+
</xs:restriction>
|
92
|
+
</xs:simpleType>
|
93
|
+
</xs:element>
|
94
|
+
<xs:element type="xs:float" name="Length"/>
|
95
|
+
<xs:element type="xs:float" name="Width"/>
|
96
|
+
<xs:element type="xs:float" name="Height"/>
|
97
|
+
</xs:all>
|
98
|
+
</xs:complexType>
|
99
|
+
</xs:element>
|
100
|
+
<xs:element type="xs:int" name="Quantity"/>
|
101
|
+
<xs:element type="xs:float" name="UnitPrice"/>
|
102
|
+
<xs:element type="String100" name="Location" minOccurs="0"/>
|
103
|
+
<xs:element name="Options" minOccurs="0">
|
104
|
+
<xs:complexType>
|
105
|
+
<xs:sequence>
|
106
|
+
<xs:element name="Option" maxOccurs="100" minOccurs="0">
|
107
|
+
<xs:complexType>
|
108
|
+
<xs:all>
|
109
|
+
<xs:element type="String100" name="Name"/>
|
110
|
+
<xs:element type="String100" name="Value"/>
|
111
|
+
<xs:element type="xs:float" name="Weight" minOccurs="0"/>
|
112
|
+
</xs:all>
|
113
|
+
</xs:complexType>
|
114
|
+
</xs:element>
|
115
|
+
</xs:sequence>
|
116
|
+
</xs:complexType>
|
117
|
+
</xs:element>
|
118
|
+
</xs:all>
|
119
|
+
</xs:complexType>
|
120
|
+
</xs:element>
|
121
|
+
</xs:sequence>
|
122
|
+
</xs:complexType>
|
123
|
+
</xs:element>
|
124
|
+
</xs:all>
|
125
|
+
</xs:complexType>
|
126
|
+
</xs:element>
|
127
|
+
</xs:sequence>
|
128
|
+
<xs:attribute type="xs:short" name="pages"/>
|
129
|
+
</xs:complexType>
|
130
|
+
</xs:element>
|
131
|
+
<xs:simpleType name="DateTime">
|
132
|
+
<xs:restriction base="xs:string">
|
133
|
+
<xs:pattern value="[0-9][0-9]?/[0-9][0-9]?/[0-9][0-9][0-9]?[0-9]? [0-9][0-9]?:[0-9][0-9]?:?[0-9]?[0-9]?. ?[aApP]?[mM]?"/>
|
134
|
+
</xs:restriction>
|
135
|
+
</xs:simpleType>
|
136
|
+
<xs:simpleType name="Email">
|
137
|
+
<xs:restriction base="xs:string">
|
138
|
+
</xs:restriction>
|
139
|
+
</xs:simpleType>
|
140
|
+
<xs:simpleType name="StringExactly2">
|
141
|
+
<xs:restriction base="xs:string">
|
142
|
+
<xs:minLength value="2"/>
|
143
|
+
<xs:maxLength value="2"/>
|
144
|
+
</xs:restriction>
|
145
|
+
</xs:simpleType>
|
146
|
+
<xs:simpleType name="String30">
|
147
|
+
<xs:restriction base="xs:string">
|
148
|
+
<xs:maxLength value="30"/>
|
149
|
+
</xs:restriction>
|
150
|
+
</xs:simpleType>
|
151
|
+
<xs:simpleType name="String50">
|
152
|
+
<xs:restriction base="xs:string">
|
153
|
+
<xs:maxLength value="50"/>
|
154
|
+
</xs:restriction>
|
155
|
+
</xs:simpleType>
|
156
|
+
<xs:simpleType name="String100">
|
157
|
+
<xs:restriction base="xs:string">
|
158
|
+
<xs:maxLength value="100"/>
|
159
|
+
</xs:restriction>
|
160
|
+
</xs:simpleType>
|
161
|
+
<xs:simpleType name="String200">
|
162
|
+
<xs:restriction base="xs:string">
|
163
|
+
<xs:maxLength value="200"/>
|
164
|
+
</xs:restriction>
|
165
|
+
</xs:simpleType>
|
166
|
+
<xs:simpleType name="String1000">
|
167
|
+
<xs:restriction base="xs:string">
|
168
|
+
<xs:maxLength value="1000"/>
|
169
|
+
</xs:restriction>
|
170
|
+
</xs:simpleType>
|
171
|
+
</xs:schema>
|