ref: master
plugins/orders_cycle/models/orders_cycle_plugin/cycle.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 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 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 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 270 271 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 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
class OrdersCyclePlugin::Cycle < ApplicationRecord attr_accessible :profile, :status, :name, :description, :opening_message attr_accessible :start, :finish, :delivery_start, :delivery_finish attr_accessible :start_date, :start_time, :finish_date, :finish_time, :delivery_start_date, :delivery_start_time, :delivery_finish_date, :delivery_finish_time, Statuses = %w[edition orders purchases receipts separation delivery closing] DbStatuses = %w[new] + Statuses UserStatuses = Statuses # which status the sales are on each cycle status SaleStatusMap = { 'edition' => nil, 'orders' => :ordered, 'purchases' => :accepted, 'receipts' => :accepted, 'separation' => :accepted, 'delivery' => :separated, 'closing' => :delivered, } # which status the purchases are on each cycle status PurchaseStatusMap = { 'edition' => nil, 'orders' => nil, 'purchases' => :draft, 'receipts' => :ordered, 'separation' => :received, 'delivery' => :received, 'closing' => :received, } belongs_to :profile has_many :delivery_options, -> { where "delivery_plugin_options.owner_type = 'OrdersCyclePlugin::Cycle'" }, class_name: 'DeliveryPlugin::Option', dependent: :destroy, as: :owner has_many :delivery_methods, through: :delivery_options, source: :delivery_method has_many :cycle_orders, -> { order 'id ASC' }, class_name: 'OrdersCyclePlugin::CycleOrder', foreign_key: :cycle_id, dependent: :destroy # cannot use :order because of months/years scope has_many :sales, through: :cycle_orders, source: :sale has_many :purchases, through: :cycle_orders, source: :purchase has_many :cycle_products, foreign_key: :cycle_id, class_name: 'OrdersCyclePlugin::CycleProduct', dependent: :destroy has_many :products, -> { includes(:from_2x_products, :from_products, {profile: :domains}) }, through: :cycle_products, class_name: 'OrdersCyclePlugin::OfferedProduct', source: :product has_many :consumers, -> { distinct.reorder 'name ASC' }, through: :sales, source: :consumer has_many :suppliers, -> { group 'suppliers_plugin_suppliers.id' }, through: :products has_many :orders_suppliers, -> { reorder 'name ASC' }, through: :sales, source: :profile has_many :from_products, -> { distinct.reorder 'name ASC' }, through: :products has_many :supplier_products, -> { distinct.reorder 'name ASC' }, through: :products has_many :product_categories, -> { distinct.reorder 'name ASC' }, through: :products has_many :orders_confirmed, -> { reorder('id ASC').where 'orders_plugin_orders.ordered_at IS NOT NULL' }, through: :cycle_orders, source: :sale has_many :items_selled, through: :sales, source: :items has_many :items_purchased, through: :purchases, source: :items # DEPRECATED has_many :items, through: :orders_confirmed has_many :ordered_suppliers, through: :orders_confirmed, source: :suppliers has_many :ordered_offered_products, -> { distinct.includes :suppliers }, through: :orders_confirmed, source: :offered_products has_many :ordered_distributed_products, -> { distinct.includes :suppliers }, through: :orders_confirmed, source: :distributed_products has_many :ordered_supplier_products, -> { distinct.includes :suppliers }, through: :orders_confirmed, source: :supplier_products has_many :volunteers_periods, class_name: 'VolunteersPlugin::Period', as: :owner has_many :volunteers, through: :volunteers_periods, source: :profile attr_accessible :volunteers_periods_attributes accepts_nested_attributes_for :volunteers_periods, allow_destroy: true scope :has_volunteers_periods, -> { distinct.joins :volunteers_periods } # status scopes scope :on_edition, -> { where status: 'edition' } scope :on_orders, -> { where status: 'orders' } scope :on_purchases, -> { where status: 'purchases' } scope :on_separation, -> { where status: 'separation' } scope :on_delivery, -> { where status: 'delivery' } scope :on_closing, -> { where status: 'closing' } scope :defuncts, -> { where "status = 'new' AND created_at < ?", 2.days.ago } scope :not_new, -> { where "status <> 'new'" } scope :on_orders, -> { where "status = 'orders' AND ( (start <= :now AND finish IS NULL) OR (start <= :now AND finish >= :now) )", now: DateTime.now } scope :not_on_orders, -> { where "NOT (status = 'orders' AND ( (start <= :now AND finish IS NULL) OR (start <= :now AND finish >= :now) ) )", now: DateTime.now } scope :opened, -> { where "status <> 'new' AND status <> 'closing'" } scope :closing, -> { where "status = 'closing'" } scope :by_status, -> (status) { where status: status } scope :months, -> { order('month DESC').select 'DISTINCT(EXTRACT(months FROM start)) as month' } scope :years, -> { order('year DESC').select 'DISTINCT(EXTRACT(YEAR FROM start)) as year' } scope :by_month, -> (month) { where 'EXTRACT(month FROM start) <= :month AND EXTRACT(month FROM finish) >= :month', month: month } scope :by_year, -> (year) { where 'EXTRACT(year FROM start) <= :year AND EXTRACT(year FROM finish) >= :year', year: year } scope :by_range, -> (range) { where 'start BETWEEN :start AND :finish OR finish BETWEEN :start AND :finish', start: range.first, finish: range.last } # FINANCIAL CALLBACKS if defined? FinancialPlugin has_many :financial_transactions, class_name: "FinancialPlugin::Transaction", as: :target end validates_presence_of :profile validates_presence_of :name, if: :not_new? validates_presence_of :start, if: :not_new? # FIXME: The user frequenqly forget about this, and this will crash the app in some places, so don't enable this #validates_presence_of :delivery_options, unless: :new_or_edition? validates_inclusion_of :status, in: DbStatuses, if: :not_new? validates_numericality_of :margin_percentage, allow_nil: true, if: :not_new? validate :validate_orders_dates, if: :not_new? validate :validate_delivery_dates, if: :not_new? before_save :step_new before_validation :update_orders_status before_save :add_products_on_edition_state after_create :delay_purge_profile_defuncts extend CodeNumbering::ClassMethods code_numbering :code, scope: Proc.new { self.profile.orders_cycles } extend OrdersPlugin::DateRangeAttr::ClassMethods date_range_attr :start, :finish date_range_attr :delivery_start, :delivery_finish extend SplitDatetime::SplitMethods split_datetime :start split_datetime :finish split_datetime :delivery_start split_datetime :delivery_finish serialize :data, Hash def name_with_code I18n.t('orders_cycle_plugin.models.cycle.code_name') % { code: code, name: name } end def total_price_consumer_ordered self.items.sum :price_consumer_ordered end def status self['status'] = 'closing' if self['status'] == 'closed' self['status'] end def step self.status = DbStatuses[DbStatuses.index(self.status)+1] end def step_back self.status = DbStatuses[DbStatuses.index(self.status)-1] end def passed_by? status DbStatuses.index(self.status) > DbStatuses.index(status) rescue false end def new? self.status == 'new' end def not_new? self.status != 'new' end def open? !self.closing? end def closing? self.status == 'closing' end def edition? self.status == 'edition' end def new_or_edition? self.status == 'new' or self.status == 'edition' end def after_orders? now = DateTime.now status == 'orders' && self.finish < now end def before_orders? now = DateTime.now status == 'orders' && self.start >= now end def orders? now = DateTime.now status == 'orders' && ( (self.start <= now && self.finish.nil?) || (self.start <= now && self.finish >= now) ) end def delivery? now = DateTime.now status == 'delivery' && ( (self.delivery_start <= now && self.delivery_finish.nil?) || (self.delivery_start <= now && self.delivery_finish >= now) ) end def may_order? consumer self.orders? and consumer.present? and consumer.in? profile.members end def consumer_previous_orders consumer self.profile.orders_cycles_sales.where(consumer_id: consumer.id). where('orders_cycle_plugin_cycle_orders.cycle_id <> ?', self.id) end def products_for_order self.products.unarchived.alphabetically.with_price end def supplier_products_by_suppliers orders = self.sales.ordered OrdersCyclePlugin::Order.supplier_products_by_suppliers orders end def generate_purchases sales = self.sales.ordered return self.purchases if self.purchases.present? sales.each do |sale| sale.add_purchases_items_without_delay end self.purchases true end def regenerate_purchases sales = self.sales.ordered self.purchases.destroy_all self.generate_purchases sales end def add_products return if self.products.count > 0 scope = self.profile.products.supplied.unarchived.available scope = scope.in_stock if defined? StockPlugin ApplicationRecord.transaction do scope.find_each batch_size: 20 do |product| self.add_product product end end end def add_product product OrdersCyclePlugin::OfferedProduct.create_from product, self end def add_products_job @add_products_job ||= Delayed::Job.find_by id: self.data[:add_products_job_id] end def transactions_for_report { order: financial_transactions.orders, payment: financial_transactions.payments, income: financial_transactions.manual.inputs, expense: financial_transactions.manual.outputs, } end protected def add_products_on_edition_state return unless self.status_was == 'new' job = self.delay.add_products self.data[:add_products_job_id] = job.id end def step_new return if self.new_record? self.step if self.new? end def update_sales_status from, to sales = self.sales.where(status: from.to_s) sales.each do |sale| sale.update status: to.to_s end end def update_purchases_status from, to purchases = self.purchases.where(status: from.to_s) purchases.each do |purchase| purchase.update status: to.to_s end end def update_orders_status return if self.new? or self.status_was == "new" return if self.status_was == self.status # Don't rewind confirmed sales unless self.status_was == 'orders' and self.status == 'edition' sale_status_was = SaleStatusMap[self.status_was] new_sale_status = SaleStatusMap[self.status] self.delay.update_sales_status sale_status_was, new_sale_status unless sale_status_was == new_sale_status end # Don't rewind confirmed purchases unless self.status_was == 'receipts' and self.status == 'purchases' purchase_status_was = PurchaseStatusMap[self.status_was] new_purchase_status = PurchaseStatusMap[self.status] self.delay.update_purchases_status purchase_status_was, new_purchase_status unless purchase_status_was == new_purchase_status end end def validate_orders_dates return if self.new? or self.finish.nil? errors.add :base, (I18n.t('orders_cycle_plugin.models.cycle.invalid_orders_period')) unless self.start < self.finish end def validate_delivery_dates return if self.new? or delivery_start.nil? or delivery_finish.nil? errors.add :base, I18n.t('orders_cycle_plugin.models.cycle.invalid_delivery_peri') unless delivery_start < delivery_finish errors.add :base, I18n.t('orders_cycle_plugin.models.cycle.delivery_period_befor') unless finish <= delivery_start end def purge_profile_defuncts self.class.where(profile_id: self.profile_id).defuncts.destroy_all end def delay_purge_profile_defuncts self.delay.purge_profile_defuncts end end |