cornerstore 0.6.2

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