Class: Parcel

Inherits:
Ekylibre::Record::Base show all
Includes:
Attachable, Customizable
Defined in:
app/models/parcel.rb

Overview

Informations

License

Ekylibre - Simple agricultural ERP Copyright (C) 2008-2009 Brice Texier, Thibaud Merigon Copyright (C) 2010-2012 Brice Texier Copyright (C) 2012-2016 Brice Texier, David Joulin

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see www.gnu.org/licenses.

Table: parcels

address_id        :integer
created_at        :datetime         not null
creator_id        :integer
custom_fields     :jsonb
delivery_id       :integer
delivery_mode     :string
given_at          :datetime
id                :integer          not null, primary key
in_preparation_at :datetime
lock_version      :integer          default(0), not null
nature            :string           not null
number            :string           not null
ordered_at        :datetime
planned_at        :datetime         not null
position          :integer
prepared_at       :datetime
purchase_id       :integer
recipient_id      :integer
reference_number  :string
remain_owner      :boolean          default(FALSE), not null
sale_id           :integer
sender_id         :integer
separated_stock   :boolean
state             :string           not null
storage_id        :integer
transporter_id    :integer
updated_at        :datetime         not null
updater_id        :integer
with_delivery     :boolean          default(FALSE), not null

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Customizable

#custom_value, #set_custom_value, #validate_custom_fields

Methods inherited from Ekylibre::Record::Base

#already_updated?, attr_readonly_with_conditions, #check_if_destroyable?, #check_if_updateable?, columns_definition, complex_scopes, customizable?, #customizable?, #customized?, #destroyable?, #editable?, has_picture, #human_attribute_name, human_attribute_name_with_id, nomenclature_reflections, #old_record, #others, refers_to, scope_with_registration, simple_scopes, #updateable?

Class Method Details

.convert_to_purchase(parcels) ⇒ Object

Convert parcels to one purchase. Assume that all parcels are checked before. Purchase is written in DB with default values


391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'app/models/parcel.rb', line 391

def convert_to_purchase(parcels)
  purchase = nil
  transaction do
    parcels = parcels.collect do |d|
      (d.is_a?(self) ? d : find(d))
    end.sort { |a, b| a.given_at <=> b.given_at }
    third = detect_third(parcels)
    planned_at = parcels.map(&:given_at).last
    unless nature = PurchaseNature.actives.first
      unless journal = Journal.purchases.opened_at(planned_at).first
        raise 'No purchase journal'
      end
      nature = PurchaseNature.create!(
        active: true,
        currency: Preference[:currency],
        with_accounting: true,
        journal: journal,
        by_default: true,
        name: PurchaseNature.tc('default.name', default: PurchaseNature.model_name.human)
      )
    end
    purchase = Purchase.create!(
      supplier: third,
      nature: nature,
      planned_at: planned_at,
      delivery_address: parcels.last.address
    )

    # Adds items
    parcels.each do |parcel|
      parcel.items.each do |item|
        next unless item.population && item.population > 0
        catalog_item = item.variant.catalog_items.order(id: :desc).first
        item.purchase_item = purchase.items.create!(
          variant: item.variant,
          unit_pretax_amount: (catalog_item ? catalog_item.amount : 0.0),
          tax: item.variant.category.purchase_taxes.first || Tax.first,
          quantity: item.population
        )
        item.save!
      end
      parcel.reload
      parcel.purchase = purchase
      parcel.save!
    end

    # Refreshes affair
    purchase.save!
  end
  purchase
end

.convert_to_sale(parcels) ⇒ Object

Convert parcels to one sale. Assume that all parcels are checked before. Sale is written in DB with default values


324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'app/models/parcel.rb', line 324

def convert_to_sale(parcels)
  sale = nil
  transaction do
    parcels = parcels.collect do |d|
      (d.is_a?(self) ? d : find(d))
    end.sort { |a, b| a.given_at <=> b.given_at }
    third = detect_third(parcels)
    planned_at = parcels.map(&:given_at).last || Time.zone.now
    unless nature = SaleNature.actives.first
      unless journal = Journal.sales.opened_at(planned_at).first
        raise 'No sale journal'
      end
      nature = SaleNature.create!(
        active: true,
        currency: Preference[:currency],
        with_accounting: true,
        journal: journal,
        by_default: true,
        name: SaleNature.tc('default.name', default: SaleNature.model_name.human)
      )
    end
    sale = Sale.create!(
      client: third,
      nature: nature,
      # created_at: planned_at,
      delivery_address: parcels.last.address
    )

    # Adds items
    parcels.each do |parcel|
      parcel.items.each do |item|
        # raise "#{item.variant.name} cannot be sold" unless item.variant.saleable?
        unless item.variant.saleable?
          item.category. = Account.find_or_import_from_nomenclature(:revenues)
          item.category.saleable = true
        end
        next unless item.population && item.population > 0
        unless catalog_item = item.variant.catalog_items.first
          unless catalog = Catalog.of_usage(:sale).first
            catalog = Catalog.create!(
              name: Catalog.enumerized_attributes[:usage].human_value_name(:sales),
              usage: :sales
            )
          end
          catalog_item = catalog.items.create!(amount: 0, variant: item.variant)
        end
        item.sale_item = sale.items.create!(
          variant: item.variant,
          unit_pretax_amount: catalog_item.amount,
          tax: item.variant.category.sale_taxes.first || Tax.first,
          quantity: item.population
        )
        item.save!
      end
      parcel.reload
      parcel.sale_id = sale.id
      parcel.save!
    end

    # Refreshes affair
    sale.save!
  end
  sale
end

.detect_third(parcels) ⇒ Object


443
444
445
446
447
# File 'app/models/parcel.rb', line 443

def detect_third(parcels)
  thirds = parcels.map(&:third_id).uniq
  raise "Need unique third (#{thirds.inspect})" if thirds.count != 1
  Entity.find(thirds.first)
end

.ship(parcels, options = {}) ⇒ Object

Ships parcels. Returns a delivery options:

- delivery_mode: delivery mode
- transporter_id: the transporter ID if delivery mode is :transporter
- responsible_id: the responsible (Entity) ID for the delivery

raises:

- "Need an obvious transporter to ship parcels" if there is no unique transporter for the parcels

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
# File 'app/models/parcel.rb', line 284

def ship(parcels, options = {})
  delivery = nil
  transaction do
    if options[:transporter_id]
      options[:delivery_mode] ||= :transporter
    elsif !delivery_mode.values.include? options[:delivery_mode].to_s
      raise "Need a valid delivery mode at least if no transporter given. Got: #{options[:delivery_mode].inspect}. Expecting one of: #{delivery_mode.values.map(&:inspect).to_sentence}"
    end
    delivery_mode = options[:delivery_mode].to_sym
    if delivery_mode == :transporter
      unless options[:transporter_id] && Entity.find_by(id: options[:transporter_id])
        transporter_ids = transporters_of(parcels).uniq
        if transporter_ids.size == 1
          options[:transporter_id] = transporter_ids.first
        else
          raise StandardError, 'Need an obvious transporter to ship parcels'
        end
      end
    end
    options[:started_at] ||= Time.zone.now
    options[:mode] = options.delete(:delivery_mode)
    delivery = Delivery.create!(options.slice!(:started_at, :transporter_id, :mode, :responsible_id, :driver_id))
    parcels.each do |parcel|
      parcel.delivery_mode = delivery_mode
      parcel.transporter_id = options[:transporter_id]
      parcel.delivery = delivery
      parcel.save!
    end
    delivery.save!
  end
  delivery
end

.transporters_of(parcels) ⇒ Object

Returns an array of all the transporter ids for the given parcels


318
319
320
# File 'app/models/parcel.rb', line 318

def transporters_of(parcels)
  parcels.map(&:transporter_id).compact
end

Instance Method Details

#address_coordinateObject


188
189
190
# File 'app/models/parcel.rb', line 188

def address_coordinate
  address.coordinate if address
end

#address_mail_coordinateObject


192
193
194
# File 'app/models/parcel.rb', line 192

def address_mail_coordinate
  (address || sale.client.default_mail_address).mail_coordinate
end

#all_items_prepared?Boolean

Returns:

  • (Boolean)

209
210
211
# File 'app/models/parcel.rb', line 209

def all_items_prepared?
  items.all?(&:prepared?)
end

#allow_items_update?Boolean

Returns:

  • (Boolean)

184
185
186
# File 'app/models/parcel.rb', line 184

def allow_items_update?
  !prepared? && !given?
end

#checkObject


253
254
255
256
257
258
259
260
261
262
263
264
# File 'app/models/parcel.rb', line 253

def check
  order if can_order?
  prepare if can_prepare?
  return false unless can_check?
  now = Time.zone.now
  values = { prepared_at: now }
  # values[:ordered_at] = now unless ordered_at
  # values[:in_preparation_at] = now unless in_preparation_at
  update_columns(values)
  items.each(&:check)
  super
end

#delivery?Boolean

Returns:

  • (Boolean)

172
173
174
# File 'app/models/parcel.rb', line 172

def delivery?
  delivery.present?
end

#delivery_started?Boolean

Returns:

  • (Boolean)

176
177
178
# File 'app/models/parcel.rb', line 176

def delivery_started?
  delivery?
end

#giveObject


266
267
268
269
270
271
272
273
274
# File 'app/models/parcel.rb', line 266

def give
  order if can_order?
  prepare if can_prepare?
  check if can_check?
  return false unless can_give?
  update_column(:given_at, Time.zone.now)
  items.each(&:give)
  super
end

#human_delivery_modeObject


196
197
198
# File 'app/models/parcel.rb', line 196

def human_delivery_mode
  delivery_mode.text
end

#human_delivery_natureObject


200
201
202
# File 'app/models/parcel.rb', line 200

def human_delivery_nature
  nature.text
end

#invoiceable?Boolean

Returns:

  • (Boolean)

168
169
170
# File 'app/models/parcel.rb', line 168

def invoiceable?
  !invoiced?
end

#invoiced?Boolean

Returns:

  • (Boolean)

164
165
166
# File 'app/models/parcel.rb', line 164

def invoiced?
  purchase.present? || sale.present?
end

#issues?Boolean

Returns:

  • (Boolean)

217
218
219
# File 'app/models/parcel.rb', line 217

def issues?
  issues.any?
end

#items?Boolean

Returns:

  • (Boolean)

213
214
215
# File 'app/models/parcel.rb', line 213

def items?
  items.any?
end

#items_quantityObject

Number of products delivered


205
206
207
# File 'app/models/parcel.rb', line 205

def items_quantity
  items.sum(:population)
end

#orderObject


237
238
239
240
241
# File 'app/models/parcel.rb', line 237

def order
  return false unless can_order?
  update_column(:ordered_at, Time.zone.now)
  super
end

#prepareObject


243
244
245
246
247
248
249
250
251
# File 'app/models/parcel.rb', line 243

def prepare
  order if can_order?
  return false unless can_prepare?
  now = Time.zone.now
  values = { in_preparation_at: now }
  # values[:ordered_at] = now unless ordered_at
  update_columns(values)
  super
end

#separated_stock?Boolean

Returns:

  • (Boolean)

160
161
162
# File 'app/models/parcel.rb', line 160

def separated_stock?
  separated_stock
end

#shippable?Boolean

Returns:

  • (Boolean)

180
181
182
# File 'app/models/parcel.rb', line 180

def shippable?
  with_delivery && !delivery.present?
end

#statusObject


221
222
223
224
225
226
227
# File 'app/models/parcel.rb', line 221

def status
  if given?
    return (issues? ? :caution : :go)
  else
    return (issues? ? :stop : :caution)
  end
end

#thirdObject


233
234
235
# File 'app/models/parcel.rb', line 233

def third
  (incoming? ? sender : recipient)
end

#third_idObject


229
230
231
# File 'app/models/parcel.rb', line 229

def third_id
  (incoming? ? sender_id : recipient_id)
end