cornerstore 0.6.2

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.
@@ -0,0 +1,94 @@
1
+ class Cornerstore::Order < Cornerstore::Model::Base
2
+ attr_accessor :number,
3
+ :reference,
4
+ :checkout_id,
5
+ :placed_at,
6
+ :invoice_number,
7
+ :invoiced_at,
8
+ :checkout_started_at,
9
+ :requested_carrier,
10
+ :weight,
11
+ :subtotal,
12
+ :shipping_costs,
13
+ :payment_costs,
14
+ :sales_tax,
15
+ :total,
16
+ :customer_comment,
17
+
18
+ :customer,
19
+ :shipping_address,
20
+ :billing_address,
21
+ :payment_means,
22
+ :requested_carrier,
23
+ :line_items,
24
+ :shipments,
25
+ :cancellations
26
+
27
+ def initialize(attributes, parent=nil)
28
+ self.payment_costs = Cornerstore::Price.new(attributes.delete('payment_costs')) if attributes['payment_costs']
29
+ self.shipping_costs = Cornerstore::Price.new(attributes.delete('shipping_costs')) if attributes['shipping_costs']
30
+ self.subtotal = Cornerstore::Price.new(attributes.delete('subtotal'))
31
+ self.sales_tax = Cornerstore::Price.new(attributes.delete('sales_tax')) if attributes['sales_tax']
32
+ self.total = Cornerstore::Price.new(attributes.delete('total'))
33
+
34
+ self.customer = Cornerstore::Customer.new(attributes.delete('customer'))
35
+ self.shipping_address = Cornerstore::Address.new(attributes.delete('shipping_address'))
36
+ self.billing_address = Cornerstore::Address.new(attributes.delete('billing_address'))
37
+ self.payment_means = Cornerstore::PaymentMeans.new(attributes.delete('payment_means'))
38
+ self.requested_carrier = Cornerstore::Carrier.new(attributes.delete('requested_carrier'))
39
+
40
+ self.line_items = Cornerstore::LineItem::Resource.new(self, attributes.delete('line_items') || [], 'line_items')
41
+ self.shipments = Cornerstore::Shipment::Resource.new(self, attributes.delete('shipments') || [], 'shipments')
42
+ self.cancellations = Cornerstore::Cancellation::Resource.new(self, attributes.delete('cancellations') || [], 'cancellations')
43
+
44
+ self.placed_at = DateTime.parse(attributes.delete('placed_at')) unless attributes['placed_at'].blank?
45
+ self.invoiced_at = DateTime.parse(attributes.delete('invoiced_at')) unless attributes['invoiced_at'].blank?
46
+
47
+ super
48
+ end
49
+
50
+ def requires_shipment?
51
+ self.line_items.any? { |li| li.requires_shipment? }
52
+ end
53
+
54
+ # Returns true if at least one line item of this order is shipped
55
+ def shipped?
56
+ !self.shipments.empty?
57
+ end
58
+
59
+ # Returns true if at least one line item of this order is canceled
60
+ def canceled?
61
+ !self.cancellations.empty?
62
+ end
63
+
64
+ def invoiced?
65
+ !self.invoice_number.blank?
66
+ end
67
+
68
+ # Returns true if all line items are shipped
69
+ def completely_shipped?
70
+ self.shipments.collect(&:shipped_items).flatten.size == self.line_items.size
71
+ end
72
+
73
+ # Returns true if all line items are canceled
74
+ def completely_canceled?
75
+ self.cancellations.collect(&:canceled_items).flatten.size == self.line_items.size
76
+ end
77
+
78
+ def handling_costs
79
+ if self.shipping_costs and self.payment_costs
80
+ self.shipping_costs + self.payment_costs
81
+ elsif self.shipping_costs
82
+ self.shipping_costs
83
+ elsif self.payment_costs
84
+ self.payment_costs
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ def check_signature
91
+ # TODO: Implement signature check
92
+ return true
93
+ end
94
+ end
@@ -0,0 +1,32 @@
1
+ class Cornerstore::PaymentMeans < Cornerstore::Model::Base
2
+ attr_accessor :kind,
3
+ :paid_at,
4
+ :refunded_at,
5
+ :card_token,
6
+ :issuer,
7
+ :last_digits,
8
+ :owner,
9
+ :bank,
10
+ :iban,
11
+ :swift_bic,
12
+ :payment_id
13
+
14
+ alias_method :type, :kind
15
+
16
+ def initialize(attributes = {}, parent = nil)
17
+ if attributes
18
+ self.paid_at = DateTime.parse(attributes.delete('paid_at')) unless attributes['paid_at'].blank?
19
+ self.refunded_at = DateTime.parse(attributes.delete('refunded_at')) unless attributes['refunded_at'].blank?
20
+ end
21
+
22
+ super
23
+ end
24
+
25
+ def paid?
26
+ self.paid_at.is_a?(DateTime)
27
+ end
28
+
29
+ def refunded?
30
+ self.refunded_at.is_a?(DateTime)
31
+ end
32
+ end
@@ -0,0 +1,77 @@
1
+ class Cornerstore::Price < Cornerstore::Model::Base
2
+ include Comparable
3
+
4
+ attr_accessor :gross,
5
+ :net,
6
+ :tax,
7
+ :tax_rate,
8
+ :currency,
9
+ :amount
10
+
11
+ validates :gross, numericality: { greater_than_or_equal_to: 0 }, if: Proc.new { |p| not p.amount }
12
+ validates :amount, numericality: { greater_than_or_equal_to: 0 }, if: Proc.new { |p| not p.gross }
13
+ validates :net, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
14
+ validates :tax_rate, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
15
+ validates :currency, presence: true, inclusion: { in: %w( EUR USD ) }
16
+
17
+ def attributes
18
+ if gross or tax_rate
19
+ {
20
+ gross: gross,
21
+ net: net,
22
+ tax_rate: tax_rate,
23
+ currency: currency
24
+ }
25
+ else
26
+ {
27
+ amount: amount,
28
+ currency: currency
29
+ }
30
+ end
31
+ end
32
+
33
+ def <=> (other_object)
34
+ case other_object
35
+ when Integer, Float, Fixnum
36
+ self.amount <=> other_object
37
+ when Cornerstore::Price
38
+ self.amount <=> other_object.amount
39
+ else
40
+ raise ArgumentError, 'can only compare Integer, Float, Fixnum or Price objects with Price'
41
+ end
42
+ end
43
+
44
+ def +(other_object)
45
+ return self if not other_object or other_object == 0
46
+
47
+ if gross
48
+ Cornerstore::Price.new({
49
+ gross: gross + other_object.gross,
50
+ net: net + other_object.net,
51
+ currency: currency,
52
+ tax_rate: other_object.tax_rate == tax_rate ? tax_rate : nil
53
+ })
54
+ else
55
+ Cornerstore::Price.new({
56
+ amount: amount + other_object.amount,
57
+ currency: currency
58
+ })
59
+
60
+ end
61
+ end
62
+
63
+ def to_f
64
+ @amount || @gross
65
+ end
66
+
67
+ def amount
68
+ @amount || @gross
69
+ end
70
+
71
+ def currency_symbol
72
+ {
73
+ 'USD' => '$',
74
+ 'EUR' => '€',
75
+ }[currency]
76
+ end
77
+ end
@@ -0,0 +1,72 @@
1
+ class Cornerstore::Product < Cornerstore::Model::Base
2
+ attr_accessor :name,
3
+ :description,
4
+ :manufacturer,
5
+ :enabled,
6
+ :variants,
7
+ :images,
8
+ :properties
9
+
10
+ def initialize(attributes = {}, parent = nil)
11
+ self.images = Cornerstore::Image::Resource.new(self, attributes.delete('images') || [], 'images')
12
+ self.variants = Cornerstore::Variant::Resource.new(self, attributes.delete('variants') || [], 'variants')
13
+ self.properties = Cornerstore::Property::Resource.new(self, attributes.delete('properties') || [], 'properties')
14
+ super
15
+ end
16
+
17
+ def attributes
18
+ {
19
+ name: name,
20
+ description: description,
21
+ manufacturer: manufacturer,
22
+ enabled: enabled
23
+ }
24
+ end
25
+
26
+ def price
27
+ variants.collect { |v| v.price }.sort.first
28
+ end
29
+
30
+ def order_number
31
+ variants.many? ? :many : variants.first.order_number
32
+ end
33
+
34
+ def sold_out?
35
+ variants.all? { |v| v.sold_out? }
36
+ end
37
+
38
+ def offer?
39
+ variants.any? { |v| v.offer? }
40
+ end
41
+
42
+ def new?
43
+ self.created_at >= 2.weeks.ago
44
+ end
45
+
46
+ def cover_image
47
+ @cover_image ||= self.images.to_a.find {|i| i.cover == true }
48
+ end
49
+
50
+ class Resource < Cornerstore::Resource::Base
51
+ include Cornerstore::Resource::Remote
52
+ include Cornerstore::Resource::Filter
53
+
54
+ def enabled
55
+ self.clone.set_filter(:enabled, true)
56
+ end
57
+
58
+ def by_collection(collection_id)
59
+ self.clone.tap{|r| r.parent = Cornerstore::Collection.find(collection_id)}
60
+ end
61
+ alias find_by_collection by_collection
62
+
63
+ def order(key)
64
+ first = %w(name popularity created_at price)
65
+ second = %w(desc asc)
66
+ pattern = /\A(#{first.join('|')})( (#{second.join('|')}))?\z/i
67
+ raise "order key must be one of: #{first.join(', ')} [#{second.join(', ')}]" unless key.match pattern
68
+ super
69
+ end
70
+ alias order_by order
71
+ end
72
+ end
@@ -0,0 +1,30 @@
1
+ class Cornerstore::Property < Cornerstore::Model::Base
2
+ include Cornerstore::Model::Writable
3
+
4
+ attr_accessor :key,
5
+ :value
6
+
7
+ def attributes
8
+ {
9
+ key: key,
10
+ value: value
11
+ }
12
+ end
13
+
14
+ # TODO: Rewrite #url on base model to automatically detect parent/url depth.
15
+ def url
16
+ super(2)
17
+ end
18
+
19
+ def initialize(attributes = {}, parent = nil)
20
+ @key = attributes.delete(:key)
21
+ @value = attributes.delete(:value)
22
+
23
+ super
24
+ end
25
+
26
+ class Resource < Cornerstore::Resource::Base
27
+ include Cornerstore::Resource::Remote
28
+ include Cornerstore::Resource::Writable
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ class Cornerstore::Search
2
+ extend ActiveModel::Naming
3
+
4
+ attr_accessor :keywords, :products, :scores
5
+ alias :results :products
6
+
7
+ def initialize(keywords = nil)
8
+ @keywords, @scores, @products = keywords, Array.new, Cornerstore::Product::Resource.new
9
+ end
10
+
11
+ def results?
12
+ !@products.empty?
13
+ end
14
+
15
+ def to_key
16
+ ['']
17
+ end
18
+
19
+ def persisted?
20
+ false
21
+ end
22
+
23
+ def run
24
+ return false unless @keywords
25
+
26
+ RestClient.get("#{Cornerstore.root_url}/products/search?keywords=#{URI::encode(@keywords)}", Cornerstore.headers) do |response, request, result, &block|
27
+ if response.code == 200
28
+ data = ActiveSupport::JSON.decode(response)
29
+ @scores = data['scores']
30
+ @products = Cornerstore::Product::Resource.new(nil, data['products'])
31
+ return true
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ class Cornerstore::Shipment < Cornerstore::Model::Base
2
+ attr_accessor :carrier,
3
+ :tracking_number,
4
+ :created_at,
5
+ :line_item_ids
6
+
7
+ alias_method :shipped_at, :created_at
8
+
9
+ def initialize(attributes = {}, parent=nil)
10
+ self.carrier = Cornerstore::Carrier.new(attributes.delete('carrier'))
11
+ self.line_item_ids = attributes.delete('shipped_items')
12
+ super
13
+ end
14
+
15
+ def line_items
16
+ return [] unless self.parent
17
+ self.parent.line_items.select { |li| self.line_item_ids.include?(li.id) }
18
+ end
19
+ alias_method :shipped_items, :line_items
20
+
21
+
22
+ class Resource < Cornerstore::Resource::Base
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ class Cornerstore::Variant < Cornerstore::Model::Base
2
+ attr_accessor :order_number,
3
+ :price,
4
+ :qty_in_stock,
5
+ :qty_reserved,
6
+ :offer,
7
+ :oversell,
8
+ :weight,
9
+ :unit,
10
+ :properties,
11
+ :differentiating_properties
12
+
13
+ alias product parent
14
+ alias offer? offer
15
+
16
+ def attributes
17
+ {
18
+ order_number: order_number,
19
+ price: price,
20
+ qty_in_stock: qty_in_stock,
21
+ qty_reserved: qty_reserved,
22
+ offer: offer,
23
+ oversell: oversell,
24
+ weight: weight,
25
+ unit: unit
26
+ }
27
+ end
28
+
29
+ def initialize(attributes = {}, parent = nil)
30
+ self.price = Cornerstore::Price.new(attributes.delete('price'))
31
+ self.properties = Cornerstore::Property::Resource.new(self, attributes.delete('properties') || [])
32
+ super
33
+ end
34
+
35
+ def qty_available
36
+ if self.qty_in_stock and not self.oversell
37
+ self.qty_in_stock - self.qty_reserved
38
+ else
39
+ Float::INFINITY
40
+ end
41
+ end
42
+
43
+ def sold_out?
44
+ self.qty_available == 0
45
+ end
46
+
47
+ class Resource < Cornerstore::Resource::Base
48
+ end
49
+ end
@@ -0,0 +1,117 @@
1
+ module Cornerstore
2
+ module Model
3
+ class Base
4
+ include ActiveModel::Validations
5
+ include ActiveModel::Serializers::JSON
6
+
7
+ attr_accessor :_id
8
+ attr_accessor :_slugs
9
+ attr_accessor :parent
10
+ attr_accessor :created_at
11
+ attr_accessor :updated_at
12
+
13
+ alias id _id
14
+
15
+ def initialize(attributes = {}, parent = nil)
16
+ self.attributes = attributes
17
+ self.parent = parent
18
+ yield self if block_given?
19
+ end
20
+
21
+ def to_param
22
+ if _slugs and !_slugs.empty?
23
+ _slugs.first
24
+ else
25
+ _id
26
+ end
27
+ end
28
+
29
+ def ==(other)
30
+ other.id == self.id
31
+ end
32
+
33
+ alias eql? ==
34
+
35
+ def inspect
36
+ {class: self.class.name, id: id}.merge!(attributes).to_s
37
+ end
38
+
39
+ def attributes
40
+ {}
41
+ end
42
+
43
+ def attributes=(attributes)
44
+ attributes ||= {}
45
+ attributes.each_pair do |name, value|
46
+ send("#{name}=", value) if respond_to?("#{name}=")
47
+ end
48
+ end
49
+
50
+ def url(depth = 1)
51
+ root = (@parent && depth > 0) ? @parent.url(depth-1) : Cornerstore.root_url
52
+ "#{root}/#{self.class.name.split('::').last.underscore.pluralize}/#{id}"
53
+ end
54
+
55
+ def self.method_missing(method, *args, &block)
56
+ if (self.const_defined?("Resource") and self.const_get("Resource").method_defined?(method))
57
+ self.const_get("Resource").new.send(method, *args, &block)
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ def method_missing(method, *args, &block)
64
+ if Writable.method_defined?(method)
65
+ raise "Sorry, this part of the Cornerstore-API is currently read-only"
66
+ else
67
+ super
68
+ end
69
+ end
70
+ end
71
+
72
+ module Writable
73
+ def new?
74
+ id.nil?
75
+ end
76
+
77
+ alias new_record? new?
78
+
79
+ def to_key
80
+ new? ? [id] : nil
81
+ end
82
+
83
+ def save
84
+ return false unless valid?
85
+ wrapped_attributes = {self.class.name.split('::').last.underscore => self.attributes}
86
+ if new?
87
+ response = RestClient.post(url, wrapped_attributes, Cornerstore.headers){|response| response}
88
+ self.attributes = ActiveSupport::JSON.decode(response)
89
+ else
90
+ response = RestClient.patch(url, wrapped_attributes, Cornerstore.headers){|response| response}
91
+ end
92
+
93
+ if response.success?
94
+ return true
95
+ else
96
+ if data = ActiveSupport::JSON.decode(response) and data.has_key?('errors')
97
+ data['errors'].each_pair do |key, messages|
98
+ messages.map { |message| self.errors.add(key, message) }
99
+ end
100
+
101
+ return false
102
+ else
103
+ return false
104
+ end
105
+ end
106
+ end
107
+
108
+ def destroy
109
+ RestClient.delete(url, Cornerstore.headers).success?
110
+ end
111
+
112
+ def self.create(attributes = {}, &block)
113
+ self.new(attributes, &block).tap{|obj| obj.save}
114
+ end
115
+ end
116
+ end
117
+ end