Class: Order
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Order
- Includes:
- DateTimeAttributeValidate
- Defined in:
- app/models/order.rb
Instance Attribute Summary collapse
- #article_ids ⇒ Object
-
#ignore_warnings ⇒ Object
Returns the value of attribute ignore_warnings.
-
#transport_distribution ⇒ Object
Returns the value of attribute transport_distribution.
Class Method Summary collapse
- .finish_ended! ⇒ Object
-
.ordergroup_group_orders_map(ordergroup) ⇒ Object
fetch current Order scope’s records and map the current user’s GroupOrders in (if any) (performance enhancement as opposed to fetching each GroupOrder separately from the view).
- .ransackable_associations(_auth_object = nil) ⇒ Object
- .ransackable_attributes(_auth_object = nil) ⇒ Object
Instance Method Summary collapse
- #articles_for_ordering ⇒ Object
-
#articles_grouped_by_category ⇒ Object
Returns OrderArticles in a nested Array, grouped by category and ordered by article name.
- #articles_sort_by_category ⇒ Object
- #boxfill? ⇒ Boolean
-
#close!(user, transaction_type = nil, financial_link = nil, create_foodcoop_transaction: false) ⇒ Object
Sets order.status to ‘close’ and updates all Ordergroup.account_balances.
-
#close_direct!(user) ⇒ Object
Close the order directly, without automaticly updating ordergroups account balances.
- #closed? ⇒ Boolean
- #do_end_action! ⇒ Object
-
#erroneous_article_ids ⇒ Object
Returns an array of article ids that lead to a validation error.
- #expired? ⇒ Boolean
-
#finish!(user) ⇒ Object
Finishes this order.
- #finished? ⇒ Boolean
-
#group_order(ordergroup) ⇒ Object
search GroupOrder of given Ordergroup.
- #include_articles ⇒ Object protected
-
#init_dates ⇒ Object
sets up first guess of dates when initializing a new object I guess ‘def initialize` would work, but it’s tricky stackoverflow.com/questions/1186400.
- #is_boxfill_useful? ⇒ Boolean
- #keep_ordered_articles ⇒ Object protected
- #name ⇒ Object
- #open? ⇒ Boolean
-
#profit(options = {}) ⇒ Object
Returns the defecit/benefit for the foodcoop Requires a valid invoice, belonging to this order FIXME: Consider order.foodcoop_result.
- #received? ⇒ Boolean
- #save_order_articles ⇒ Object protected
- #send_to_supplier!(user) ⇒ Object
- #starts_before_ends ⇒ Object protected
- #stock_group_order ⇒ Object
- #stockit? ⇒ Boolean
-
#sum(type = :gross) ⇒ Object
Returns the all round price of a finished order :groups returns the sum of all GroupOrders :clear returns the price without tax, deposit and markup :gross includes tax and deposit.
- #supplier_articles ⇒ Object
- #upload_via_ftp ⇒ Object
Instance Attribute Details
#article_ids ⇒ Object
95 96 97 |
# File 'app/models/order.rb', line 95 def article_ids @article_ids ||= order_articles.map { |oa| oa.article_version.article_id.to_s } end |
#ignore_warnings ⇒ Object
Returns the value of attribute ignore_warnings.
2 3 4 |
# File 'app/models/order.rb', line 2 def ignore_warnings @ignore_warnings end |
#transport_distribution ⇒ Object
Returns the value of attribute transport_distribution.
2 3 4 |
# File 'app/models/order.rb', line 2 def transport_distribution @transport_distribution end |
Class Method Details
.finish_ended! ⇒ Object
357 358 359 360 361 362 363 364 |
# File 'app/models/order.rb', line 357 def self.finish_ended! orders = Order.where.not(end_action: Order.end_actions[:no_end_action]).where(state: 'open').where(ends: ..DateTime.now) orders.each do |order| order.do_end_action! rescue StandardError => e ExceptionNotifier.notify_exception(e, data: { foodcoop: FoodsoftConfig.scope, order_id: order.id }) end end |
.ordergroup_group_orders_map(ordergroup) ⇒ Object
fetch current Order scope’s records and map the current user’s GroupOrders in (if any) (performance enhancement as opposed to fetching each GroupOrder separately from the view)
157 158 159 160 161 162 163 164 165 166 167 |
# File 'app/models/order.rb', line 157 def self.ordergroup_group_orders_map(ordergroup) orders = includes(:supplier) group_orders = GroupOrder.where(ordergroup_id: ordergroup.id, order_id: orders.map(&:id)) group_orders_hash = group_orders.index_by { |go| go.order_id } orders.map do |order| { order: order, group_order: group_orders_hash[order.id] } end end |
.ransackable_associations(_auth_object = nil) ⇒ Object
59 60 61 |
# File 'app/models/order.rb', line 59 def self.ransackable_associations(_auth_object = nil) %w[supplier articles order_articles] end |
.ransackable_attributes(_auth_object = nil) ⇒ Object
55 56 57 |
# File 'app/models/order.rb', line 55 def self.ransackable_attributes(_auth_object = nil) %w[id state supplier_id starts boxfill ends pickup] end |
Instance Method Details
#articles_for_ordering ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'app/models/order.rb', line 71 def articles_for_ordering if stockit? # make sure to include those articles which are no longer available # but which have already been ordered in this stock order StockArticle.available.includes(latest_article_version: :article_category) .order('article_categories.name', 'article_versions.name').reject do |a| a.quantity_available <= 0 && !a.ordered_in_order?(self) end.group_by { |a| a.article_category.name } else supplier.articles.available.group_by { |a| a.article_category.name } end end |
#articles_grouped_by_category ⇒ Object
Returns OrderArticles in a nested Array, grouped by category and ordered by article name. The array has the following form: e.g: [[“drugs”,[teethpaste, toiletpaper]], [“fruits” => [apple, banana, lemon]]]
181 182 183 184 185 186 187 188 |
# File 'app/models/order.rb', line 181 def articles_grouped_by_category @articles_grouped_by_category ||= order_articles .includes([:group_order_articles, { article_version: %i[article_category article_unit_ratios] }]) .order('article_versions.name', 'article_unit_ratios.sort') .group_by { |oa| oa.article_version.article_category.name } .sort { |a, b| a[0] <=> b[0] } end |
#articles_sort_by_category ⇒ Object
190 191 192 193 194 |
# File 'app/models/order.rb', line 190 def articles_sort_by_category order_articles.includes(:article).order('articles.name').sort do |a, b| a.article.article_category.name <=> b.article.article_category.name end end |
#boxfill? ⇒ Boolean
120 121 122 |
# File 'app/models/order.rb', line 120 def boxfill? !!FoodsoftConfig[:use_boxfill] && open? && boxfill.present? && boxfill < Time.now end |
#close!(user, transaction_type = nil, financial_link = nil, create_foodcoop_transaction: false) ⇒ Object
Sets order.status to ‘close’ and updates all Ordergroup.account_balances
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'app/models/order.rb', line 272 def close!(user, transaction_type = nil, financial_link = nil, create_foodcoop_transaction: false) raise I18n.t('orders.model.error_closed') if closed? update_price_of_group_orders! transaction do # Start updating account balances charge_group_orders!(user, transaction_type, financial_link) if stockit? # Decreases the quantity of stock_articles for oa in order_articles.includes(article_version: :article) oa.update_results! # Update units_to_order of order_article stock_changes.create! stock_article: oa.article_version.article, quantity: oa.units_to_order * -1 end end if create_foodcoop_transaction ft = FinancialTransaction.new({ financial_transaction_type: transaction_type, user: user, amount: sum(:groups), note: transaction_note, financial_link: financial_link }) ft.save! end update! state: 'closed', updated_by: user, foodcoop_result: profit end end |
#close_direct!(user) ⇒ Object
Close the order directly, without automaticly updating ordergroups account balances
301 302 303 304 305 306 307 308 309 |
# File 'app/models/order.rb', line 301 def close_direct!(user) raise I18n.t('orders.model.error_closed') if closed? unless FoodsoftConfig[:charge_members_manually] comments.create(user: user, text: I18n.t('orders.model.close_direct_message')) end update! state: 'closed', updated_by: user end |
#closed? ⇒ Boolean
116 117 118 |
# File 'app/models/order.rb', line 116 def closed? state == 'closed' end |
#do_end_action! ⇒ Object
345 346 347 348 349 350 351 352 353 354 355 |
# File 'app/models/order.rb', line 345 def do_end_action! if auto_close? finish!(created_by) elsif auto_close_and_send? finish!(created_by) send_to_supplier!(created_by) elsif auto_close_and_send_min_quantity? finish!(created_by) send_to_supplier!(created_by) if sum >= supplier.min_order_quantity.to_r end end |
#erroneous_article_ids ⇒ Object
Returns an array of article ids that lead to a validation error.
100 101 102 |
# File 'app/models/order.rb', line 100 def erroneous_article_ids @erroneous_article_ids ||= [] end |
#expired? ⇒ Boolean
128 129 130 |
# File 'app/models/order.rb', line 128 def expired? ends.present? && ends < Time.now end |
#finish!(user) ⇒ Object
Finishes this order. This will set the order state to “finish” and the end property to the current time. Ignored if the order is already finished.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'app/models/order.rb', line 244 def finish!(user) return if finished? Order.transaction do # set new order state (needed by notify_order_finished) update!(state: 'finished', ends: Time.now, updated_by: user) # Update order_articles. Save the current article_version to keep price consistency # Also save results for each group_order_result # Clean up order_articles.each do |oa| oa.group_order_articles.each do |goa| goa.save_results! end end # Update GroupOrder prices group_orders.each(&:update_price!) # Stats ordergroups.each(&:update_stats!) # Notifications NotifyFinishedOrderJob.perform_later(self) end end |
#finished? ⇒ Boolean
108 109 110 |
# File 'app/models/order.rb', line 108 def finished? %w[finished received].include?(state) end |
#group_order(ordergroup) ⇒ Object
search GroupOrder of given Ordergroup
170 171 172 |
# File 'app/models/order.rb', line 170 def group_order(ordergroup) group_orders.where(ordergroup_id: ordergroup.id).first end |
#include_articles ⇒ Object (protected)
378 379 380 |
# File 'app/models/order.rb', line 378 def include_articles errors.add(:articles, I18n.t('orders.model.error_nosel')) if article_ids.empty? end |
#init_dates ⇒ Object
sets up first guess of dates when initializing a new object I guess ‘def initialize` would work, but it’s tricky stackoverflow.com/questions/1186400
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'app/models/order.rb', line 134 def init_dates self.starts ||= Time.now if FoodsoftConfig[:order_schedule] # try to be smart when picking a reference day last = begin DateTime.parse(FoodsoftConfig[:order_schedule][:initial]) rescue StandardError nil end last ||= Order.finished.reorder(:starts).first.try(:starts) last ||= self.starts # adjust boxfill and end date if is_boxfill_useful? self.boxfill ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:boxfill] end self.ends ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:ends] end self end |
#is_boxfill_useful? ⇒ Boolean
124 125 126 |
# File 'app/models/order.rb', line 124 def is_boxfill_useful? !!FoodsoftConfig[:use_boxfill] && !!supplier.try(:has_tolerance?) end |
#keep_ordered_articles ⇒ Object (protected)
382 383 384 385 386 387 388 389 390 |
# File 'app/models/order.rb', line 382 def keep_ordered_articles chosen_order_articles = order_articles.joins(:article_version).where(article_versions: { article_id: article_ids }) to_be_removed = order_articles - chosen_order_articles to_be_removed_but_ordered = to_be_removed.select { |a| a.quantity > 0 || a.tolerance > 0 } return if to_be_removed_but_ordered.empty? || ignore_warnings errors.add(:articles, I18n.t(stockit? ? 'orders.model.warning_ordered_stock' : 'orders.model.warning_ordered')) @erroneous_article_ids = to_be_removed_but_ordered.map { |oa| oa.article_version.article_id } end |
#name ⇒ Object
67 68 69 |
# File 'app/models/order.rb', line 67 def name stockit? ? I18n.t('orders.model.stock') : supplier.name end |
#open? ⇒ Boolean
104 105 106 |
# File 'app/models/order.rb', line 104 def open? state == 'open' end |
#profit(options = {}) ⇒ Object
Returns the defecit/benefit for the foodcoop Requires a valid invoice, belonging to this order FIXME: Consider order.foodcoop_result
199 200 201 202 203 204 205 |
# File 'app/models/order.rb', line 199 def profit( = {}) markup = [:without_markup] || false return unless invoice groups_sum = markup ? sum(:groups_without_markup) : sum(:groups) groups_sum - invoice.net_amount end |
#received? ⇒ Boolean
112 113 114 |
# File 'app/models/order.rb', line 112 def received? state == 'received' end |
#save_order_articles ⇒ Object (protected)
392 393 394 395 396 397 398 399 400 401 402 |
# File 'app/models/order.rb', line 392 def save_order_articles # fetch selected articles articles_list = Article.find(article_ids) # create new order_articles articles = article_versions.map(&:article) (articles_list - articles).each { |article| order_articles.create(article_version: article.latest_article_version) } # delete old order_articles articles.reject { |article| articles_list.include?(article) }.each do |article| order_articles.detect { |order_article| order_article.article_version.article_id == article.id }.destroy end end |
#send_to_supplier!(user) ⇒ Object
311 312 313 314 315 316 317 318 319 320 |
# File 'app/models/order.rb', line 311 def send_to_supplier!(user) if supplier.remote_order_method == :email Mailer.deliver_now_with_default_locale do Mailer.order_result_supplier(user, self) end else upload_via_ftp end update_attribute(:remote_ordered_at, Time.now) end |
#starts_before_ends ⇒ Object (protected)
368 369 370 371 372 373 374 375 376 |
# File 'app/models/order.rb', line 368 def starts_before_ends delta = Rails.env.test? ? 1 : 0 # since Rails 4.2 tests appear to have time differences, with this validation failing errors.add(:ends, I18n.t('orders.model.error_starts_before_ends')) if ends && starts && ends <= (starts - delta) errors.add(:ends, I18n.t('orders.model.error_boxfill_before_ends')) if ends && boxfill && ends <= (boxfill - delta) return unless boxfill && starts && boxfill <= (starts - delta) errors.add(:boxfill, I18n.t('orders.model.error_starts_before_boxfill')) end |
#stock_group_order ⇒ Object
174 175 176 |
# File 'app/models/order.rb', line 174 def stock_group_order group_orders.where(ordergroup_id: nil).first end |
#stockit? ⇒ Boolean
63 64 65 |
# File 'app/models/order.rb', line 63 def stockit? supplier_id.nil? end |
#sum(type = :gross) ⇒ Object
Returns the all round price of a finished order :groups returns the sum of all GroupOrders :clear returns the price without tax, deposit and markup :gross includes tax and deposit. this amount should be equal to suppliers bill :fc, guess what…
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'app/models/order.rb', line 212 def sum(type = :gross) total = 0 if %i[net gross fc].include?(type) for oa in order_articles.ordered.includes(:article_version) quantity = oa.units * oa.article_version.convert_quantity(1, oa.article_version.supplier_order_unit, oa.article_version.group_order_unit) case type when :net total += quantity * oa.article_version.group_order_price when :gross total += quantity * oa.article_version.gross_group_order_price when :fc total += quantity * oa.article_version.fc_group_order_price end end elsif %i[groups groups_without_markup].include?(type) for go in group_orders.includes(group_order_articles: { order_article: :article_version }) for goa in go.group_order_articles case type when :groups total += goa.result * goa.order_article.article_version.fc_group_order_price when :groups_without_markup total += goa.result * goa.order_article.article_version.gross_group_order_price end end end end total end |
#supplier_articles ⇒ Object
84 85 86 87 88 89 90 |
# File 'app/models/order.rb', line 84 def supplier_articles if stockit? StockArticle.undeleted.with_latest_versions_and_categories.reorder('article_versions.name') else supplier.articles.undeleted.with_latest_versions_and_categories.reorder('article_versions.name') end end |
#upload_via_ftp ⇒ Object
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'app/models/order.rb', line 322 def upload_via_ftp raise I18.t('orders.model.error_invalid') unless valid? formatter_class = supplier.remote_order_formatter raise "No formatter registered for remote order method: #{supplier.read_attribute_before_type_cast(:remote_order_method)}" unless formatter_class local_temp_file = Tempfile.new begin formatter = formatter_class.new(self) local_temp_file.write(formatter.to_remote_format) local_temp_file.rewind remote_filename = formatter.remote_file_name uri = URI(supplier.remote_order_url) Net::FTP.open(uri.host) do |ftp| ftp.login(uri.user, uri.password) ftp.putbinaryfile(local_temp_file.path, remote_filename) end ensure local_temp_file.close local_temp_file.unlink end end |