Class: Stock::Holding

Inherits:
MovementTarget show all
Defined in:
lib/porp/stock/holding.rb

Overview

The Holding class represents a collection of holdings of physical stock. A Holding is a queue of HoldingEntries, each of which represents a quantity of Entities. HoldingEntries are created and altered by Movements, and are never destroyed. The lifetime of a HoldingEntry will start with a receipt of stock (e.g. a GRN against a PO), and over time the stock represented by the HoldingEntry will be reduced by issues (e.g. sales). Once a holding reaches zero it will normally be archived as part of the stock movement audit trail.

HoldingEntries can be effectively merged by performing Movements from the original HoldingEntries to a new HoldingEntry, and can likewise be split in similar fashion.

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods inherited from MovementTarget

inherited, #reload_attributes

Constructor Details

- (Holding) initialize(attrs)

A new instance of Holding



33
34
35
36
# File 'lib/porp/stock/holding.rb', line 33

def initialize(attrs)
  super(attrs)
  self.name ||= self.holder.name + "_" + self.status.name
end

Class Method Details

+ (Object) const_missing(name)



129
130
131
# File 'lib/porp/stock/holding.rb', line 129

def self.const_missing(name)
  Stock.const_get(name)
end

Instance Method Details

- (Object) issue(movement)

Issue stock from this holding. The issue proceeds as follows:

1) Issue stock from first entry. If there is sufficient stock, finish. 2) If there is insufficient stock on the first entry, continue issue

from the second entry, and so on until the issue is complete

3) If there is insufficient stock on all entries, create a negative

stock entry for the remainder


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
# File 'lib/porp/stock/holding.rb', line 78

def issue(movement)
  movement_amount_remaining = movement.source_amount
  issued_value = 0
  
  while movement_amount_remaining.int_qty > 0 do
    unless entries.empty?
      current_entry = entries.first
      if current_entry.amount_remaining.int_qty >= movement_amount_remaining.int_qty
        current_entry.amount_out.int_qty += movement_amount_remaining.int_qty
        issued_value += movement_amount_remaining.value
        current_entry.save
        #defunct_entries << entries.shift if current_entry.eol?
        # The below is equivalent to the above but avoids instantiating the
        # intermediate entry
        defunct_entries.key.rpush(entries.key.lpop) if current_entry.eol?
        break
      else
        movement_amount_remaining.int_qty -= current_entry.amount_remaining.int_qty
        issued_value += current_entry.amount_remaining.value
        current_entry.amount_out = current_entry.amount_in            
        current_entry.save
        defunct_entries.key.rpush(entries.key.lpop)
      end
    else
      # Negative stock entry territory
      # We'll have a go at this later
      warn("Can't handle negatives!")
      break
    end
  end
  
  # Recalculate source amount unit cost
  movement.source_amount.ucost = issued_value / movement.source_amount.qty
  
  # Return qty issued
  movement.source_amount.qty
end

- (Integer) qty

Calculates the total quantity of stock held in this holding from the holding entries

Returns:

  • (Integer)

    quantity of stock in the holding



50
51
52
# File 'lib/porp/stock/holding.rb', line 50

def qty
  quantity
end

- (Integer) quantity

Calculates the total quantity of stock held in this holding from the holding entries

Returns:

  • (Integer)

    quantity of stock in the holding



43
44
45
46
47
# File 'lib/porp/stock/holding.rb', line 43

def quantity
  entries.all.inject(0) do |sum, entry|
    sum += entry.amount_remaining.int_qty
  end
end

- (Object) receive(movement)

Receive stock on to this holding. For the moment, we simply create a new entry each time - this may change to allow modifying entries in place



123
124
125
126
127
# File 'lib/porp/stock/holding.rb', line 123

def receive(movement)
  entry = HoldingEntry.create(amount_in: movement.dest_amount, stock_holding_id: id)
  entries << entry
  entry.amount_in.qty
end

- (Object) reverse_issue(movement)

Can't do this yet. Your receipts better not fail :-)



117
118
119
# File 'lib/porp/stock/holding.rb', line 117

def reverse_issue(movement)
  false
end

- (String) to_s

String representation of the holding

Returns:

  • (String)

    string representation of the holding



65
66
67
68
69
# File 'lib/porp/stock/holding.rb', line 65

def to_s
  "Holding: %s; H: %s; S: %s; N: %s; Qty: %s; Value: %s" % [
    id, holder_id, status_id, name, quantity, value
  ]
end

- (Rational) value

Calculates the total value of stock held in this holding from the holding entries

Returns:

  • (Rational)

    value of stock in the holding



58
59
60
61
62
# File 'lib/porp/stock/holding.rb', line 58

def value
  entries.all.inject(Rational(0)) do |sum, entry|
    sum += entry.amount_remaining.value
  end
end