Class: Article
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Article
- Includes:
- LocalizeInput, PriceCalculation
- Defined in:
- app/models/article.rb
Direct Known Subclasses
Instance Attribute Summary collapse
-
#article_category ⇒ ArticleCategory
Category this article is in.
-
#article_prices ⇒ Array<ArticlePrice>
Price history (current price first).
-
#availability ⇒ Boolean
Whether this article is available within the Foodcoop.
-
#deposit ⇒ Number
Deposit.
-
#manufacturer ⇒ String
Original manufacturer.
-
#name ⇒ String
Article name.
-
#note ⇒ String
Short line with optional extra article information.
-
#order ⇒ Array<Order>
Orders this article appears in.
-
#order_articles ⇒ Array<OrderArticle>
Order articles for this article.
-
#order_number ⇒ Object
Order number, this can be used by the supplier to identify articles.
-
#origin ⇒ String
Where the article was produced.
-
#price ⇒ Number
Net price.
-
#supplier ⇒ Supplier
Supplier this article belongs to.
-
#tax ⇒ Number
VAT percentage (10 is 10%).
-
#unit ⇒ String
Unit, e.g.
-
#unit_quantity ⇒ Number
Number of units in wholesale package (box).
Class Method Summary collapse
-
.compare_attributes(attributes) ⇒ Hash<Symbol, Object>
Compare attributes from two different articles.
- .ransackable_associations(_auth_object = nil) ⇒ Object
- .ransackable_attributes(_auth_object = nil) ⇒ Object
Instance Method Summary collapse
-
#check_article_in_use ⇒ Object
protected
Checks if the article is in use before it will deleted.
-
#convert_units(new_article = shared_article) ⇒ Object
convert units in foodcoop-size uses unit factors in app_config.yml to calc the price/unit_quantity returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity] returns false if units aren’t foodsoft-compatible returns nil if units are eqal.
- #deleted? ⇒ Boolean
-
#in_open_order ⇒ Object
If the article is used in an open Order, the Order will be returned.
- #mark_as_deleted ⇒ Object
-
#ordered_in_order?(order) ⇒ Boolean
Returns true if the article has been ordered in the given order at least once.
- #price_changed? ⇒ Boolean protected
-
#recently_updated ⇒ Object
Returns true if article has been updated at least 2 days ago.
-
#shared_article(supplier = self.supplier) ⇒ Object
to get the correspondent shared article.
-
#shared_article_changed?(supplier = self.supplier) ⇒ Boolean
this method checks, if the shared_article has been changed unequal attributes will returned in array if only the timestamps differ and the attributes are equal, false will returned and self.shared_updated_on will be updated.
-
#unequal_attributes(new_article, options = {}) ⇒ Hash<Symbol, Object>
Return article attributes that were changed (incl. unit conversion).
-
#uniqueness_of_name ⇒ Object
protected
We used have the name unique per supplier+deleted_at+type.
-
#update_price_history ⇒ Object
protected
Create an ArticlePrice, when the price-attr are changed.
Methods included from PriceCalculation
Methods included from LocalizeInput
Instance Attribute Details
#article_category ⇒ ArticleCategory
Returns Category this article is in.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#article_prices ⇒ Array<ArticlePrice>
Returns Price history (current price first).
45 |
# File 'app/models/article.rb', line 45 has_many :article_prices, -> { order('created_at DESC') } |
#availability ⇒ Boolean
Returns Whether this article is available within the Foodcoop.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#deposit ⇒ Number
Returns Deposit.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#manufacturer ⇒ String
Returns Original manufacturer.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#name ⇒ String
Returns Article name.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#note ⇒ String
Returns Short line with optional extra article information.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#order ⇒ Array<Order>
Returns Orders this article appears in.
51 |
# File 'app/models/article.rb', line 51 has_many :orders, through: :order_articles |
#order_articles ⇒ Array<OrderArticle>
Returns Order articles for this article.
48 |
# File 'app/models/article.rb', line 48 has_many :order_articles |
#order_number ⇒ Object
Order number, this can be used by the supplier to identify articles. This is required when using the shared database functionality.
@return [String] Order number.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#origin ⇒ String
Where the article was produced. ISO 3166-1 2-letter country code, optionally prefixed with region. E.g. NL
or Sicily, IT or Berlin, DE.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#price ⇒ Number
Returns Net price.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#supplier ⇒ Supplier
Returns Supplier this article belongs to.
42 |
# File 'app/models/article.rb', line 42 belongs_to :supplier |
#tax ⇒ Number
Returns VAT percentage (10 is 10%).
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#unit ⇒ String
Returns Unit, e.g. kg
, 2 L or 5 pieces.
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
#unit_quantity ⇒ Number
Returns Number of units in wholesale package (box).
39 |
# File 'app/models/article.rb', line 39 belongs_to :article_category |
Class Method Details
.compare_attributes(attributes) ⇒ Hash<Symbol, Object>
Compare attributes from two different articles.
This is used for auto-synchronization
168 169 170 171 172 173 |
# File 'app/models/article.rb', line 168 def self.compare_attributes(attributes) unequal_attributes = attributes.select do |_name, values| values[0] != values[1] && !(values[0].blank? && values[1].blank?) end unequal_attributes.to_a.map { |a| [a[0], a[1].last] }.to_h end |
.ransackable_associations(_auth_object = nil) ⇒ Object
85 86 87 |
# File 'app/models/article.rb', line 85 def self.ransackable_associations(_auth_object = nil) %w[article_category supplier order_articles orders] end |
.ransackable_attributes(_auth_object = nil) ⇒ Object
81 82 83 |
# File 'app/models/article.rb', line 81 def self.ransackable_attributes(_auth_object = nil) %w[id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number] end |
Instance Method Details
#check_article_in_use ⇒ Object (protected)
Checks if the article is in use before it will deleted
238 239 240 |
# File 'app/models/article.rb', line 238 def check_article_in_use raise I18n.t('articles.model.error_in_use', article: name.to_s) if in_open_order end |
#convert_units(new_article = shared_article) ⇒ Object
convert units in foodcoop-size uses unit factors in app_config.yml to calc the price/unit_quantity returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity] returns false if units aren’t foodsoft-compatible returns nil if units are eqal
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'app/models/article.rb', line 190 def convert_units(new_article = shared_article) return unless unit != new_article.unit return false if new_article.unit.include?(',') # legacy, used by foodcoops in Germany if new_article.unit == 'KI' && unit == 'ST' # 'KI' means a box, with a different amount of items in it # try to match the size out of its name, e.g. "banana 10-12 St" => 10 new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i if new_unit_quantity && new_unit_quantity > 0 new_price = (new_article.price / new_unit_quantity.to_f).round(2) [new_price, new_unit_quantity] else false end else # use ruby-units to convert fc_unit = begin ::Unit.new(unit) rescue StandardError nil end supplier_unit = begin ::Unit.new(new_article.unit) rescue StandardError nil end if fc_unit != 0 && supplier_unit != 0 && fc_unit && supplier_unit && fc_unit =~ supplier_unit conversion_factor = (supplier_unit / fc_unit).to_base.to_r new_price = new_article.price / conversion_factor new_unit_quantity = new_article.unit_quantity * conversion_factor [new_price, new_unit_quantity] else false end end end |
#deleted? ⇒ Boolean
226 227 228 |
# File 'app/models/article.rb', line 226 def deleted? deleted_at.present? end |
#in_open_order ⇒ Object
If the article is used in an open Order, the Order will be returned.
95 96 97 98 99 100 101 |
# File 'app/models/article.rb', line 95 def in_open_order @in_open_order ||= begin order_articles = OrderArticle.where(order_id: Order.open.collect(&:id)) order_article = order_articles.detect { |oa| oa.article_id == id } order_article&.order end end |
#mark_as_deleted ⇒ Object
230 231 232 233 |
# File 'app/models/article.rb', line 230 def mark_as_deleted check_article_in_use update_column :deleted_at, Time.now end |
#ordered_in_order?(order) ⇒ Boolean
Returns true if the article has been ordered in the given order at least once
104 105 106 |
# File 'app/models/article.rb', line 104 def ordered_in_order?(order) order.order_articles.where(article_id: id).where('quantity > 0').one? end |
#price_changed? ⇒ Boolean (protected)
254 255 256 |
# File 'app/models/article.rb', line 254 def price_changed? changed.detect { |attr| attr == 'price' || 'tax' || 'deposit' || 'unit_quantity' } ? true : false end |
#recently_updated ⇒ Object
Returns true if article has been updated at least 2 days ago
90 91 92 |
# File 'app/models/article.rb', line 90 def recently_updated updated_at > 2.days.ago end |
#shared_article(supplier = self.supplier) ⇒ Object
to get the correspondent shared article
176 177 178 179 180 181 182 183 |
# File 'app/models/article.rb', line 176 def shared_article(supplier = self.supplier) order_number.blank? and return nil @shared_article ||= begin supplier.shared_supplier.find_article_by_number(order_number) rescue StandardError nil end end |
#shared_article_changed?(supplier = self.supplier) ⇒ Boolean
this method checks, if the shared_article has been changed unequal attributes will returned in array if only the timestamps differ and the attributes are equal, false will returned and self.shared_updated_on will be updated
112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'app/models/article.rb', line 112 def shared_article_changed?(supplier = self.supplier) # skip early if the timestamp hasn't changed shared_article = self.shared_article(supplier) return if shared_article.nil? || shared_updated_on == shared_article.updated_on attrs = unequal_attributes(shared_article) if attrs.empty? # when attributes not changed, update timestamp of article update_attribute(:shared_updated_on, shared_article.updated_on) false else attrs end end |
#unequal_attributes(new_article, options = {}) ⇒ Hash<Symbol, Object>
Return article attributes that were changed (incl. unit conversion)
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'app/models/article.rb', line 131 def unequal_attributes(new_article, = {}) # try to convert different units when desired if [:convert_units] == false new_price = nil new_unit_quantity = nil else new_price, new_unit_quantity = convert_units(new_article) end if new_price && new_unit_quantity new_unit = unit else new_price = new_article.price new_unit_quantity = new_article.unit_quantity new_unit = new_article.unit end Article.compare_attributes( { name: [name, new_article.name], manufacturer: [manufacturer, new_article.manufacturer.to_s], origin: [origin, new_article.origin], unit: [unit, new_unit], price: [price.to_f.round(2), new_price.to_f.round(2)], tax: [tax, new_article.tax], deposit: [deposit.to_f.round(2), new_article.deposit.to_f.round(2)], # take care of different num-objects. unit_quantity: [unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f], note: [note.to_s, new_article.note.to_s] } ) end |
#uniqueness_of_name ⇒ Object (protected)
We used have the name unique per supplier+deleted_at+type. With the addition of shared_sync_method all, this came in the way, and we now allow duplicate names for the ‘all’ methods - expecting foodcoops to make their own choice among products with different units by making articles available/unavailable.
261 262 263 264 265 266 267 268 269 270 |
# File 'app/models/article.rb', line 261 def uniqueness_of_name matches = Article.where(name: name, supplier_id: supplier_id, deleted_at: deleted_at, type: type) matches = matches.where.not(id: id) unless new_record? # supplier should always be there - except, perhaps, on initialization (on seeding) if supplier && (supplier.shared_sync_method.blank? || supplier.shared_sync_method == 'import') errors.add :name, :taken if matches.any? elsif matches.where(unit: unit, unit_quantity: unit_quantity).any? errors.add :name, :taken_with_unit end end |
#update_price_history ⇒ Object (protected)
Create an ArticlePrice, when the price-attr are changed.
243 244 245 246 247 248 249 250 251 252 |
# File 'app/models/article.rb', line 243 def update_price_history return unless price_changed? article_prices.build( price: price, tax: tax, deposit: deposit, unit_quantity: unit_quantity ) end |