Class: Stock::Movement

Inherits:
Ohm::Model
  • Object
show all
Includes:
Ohm::Locking, Ohm::Looseref, Ohm::Struct
Defined in:
lib/porp/stock/movement.rb

Overview

The StockMovement class represents movements of physical stock by moving quantities and cost value from one MovementTarget to another.

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Movement) initialize(attrs = {})

Set defaults for attributes



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/porp/stock/movement.rb', line 52

def initialize(attrs = {})
  super(attrs)
  self.creation_time ||= Time.now.to_f

  # Source quantity and unit cost default to one and zero if not supplied. It is
  # expected that unit cost will be determined by the source
  # (may want to raise here in future if zero qty passed in)
  self.source_amount ||= Amount.new(1, 0)
  
  # Destination stock entity is assumed to be the same as the source
  # stock entity if not supplied
  self.dest_entity_id ||= self.source_entity_id
  
  # Destination quantity is assumed to be the same as the source
  # quantity if not supplied.
  # Destination unit cost is always calculated such that
  # source_amount.value == dest_amount.value
  self.dest_amount ||= Amount.new(self.source_amount.qty, 0)      
  self.dest_amount.ucost = Rational(source_amount.value) / Integer(dest_amount.qty)
end

Class Method Details

+ (Object) move(*args)

Create a new movement and commit it



37
38
39
40
41
# File 'lib/porp/stock/movement.rb', line 37

def self.move(*args)
  movement = create(*args)
  movement.move()
  movement
end

+ (Object) move_no_cleanup(*args)

Create a new movement and commit it. If the commit fails, the uncommitted movement remains in the database.



45
46
47
48
49
# File 'lib/porp/stock/movement.rb', line 45

def self.move_no_cleanup(*args)
#      movement = create(*args)
  movement = new(*args)
  movement.move_no_cleanup()
end

Instance Method Details

- (Object) move(*args)

Commit the movement. If the movement fails, destroy the inconsistent movement and reraise. Wraps move_no_cleanup



87
88
89
90
91
92
93
94
95
96
97
# File 'lib/porp/stock/movement.rb', line 87

def move(*args)
  begin
    move_no_cleanup(*args)
  rescue Orp::MovementAlreadyComplete
    warn "Attempted to complete already completed movement"
    nil
  rescue
    delete
    raise
  end
end

- (Object) move_no_cleanup(*args)

Commit the movement. If the movement fails, the uncommitted movement will be left in the database for inspection

Raises:

  • (Orp::MovementAlreadyComplete)


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

def move_no_cleanup(*args)
  # Abort if the movement is already complete
  raise Orp::MovementAlreadyComplete if completed

  raise Orp::MovementValidationError if !self.valid?
  self.update(*args) if args.length > 0

  # This mutex prevents against two attempts to complete the same move
  # simultaneously. The movement remains editable throughout.
  #
  # BUT: Do we even need this? If the movement is only created just in time
  # then nothing else is going to have that instance 
#      mutex(0.01) do
	# Commit the issue
	self.issue_committed = self.source_target.issue(self) ? true : false
	
	# Commit the receipt if the issue succeeded
	if self.issue_committed
	  self.issue_time = Time.now.to_f
	  # Recalculate the destination unit cost in case the source
	  # overrided the source unit cost
	  self.dest_amount.ucost = Rational(self.source_amount.value / self.dest_amount.qty)
	  self.rcpt_committed = self.dest_target.receive(self) ? true : false
	else
	  raise Orp::MovementIssueError
	end
	
	# Reverse the issue if the receipt failed
	if self.rcpt_committed
	  self.rcpt_time = Time.now.to_f
	else
	  self.issue_committed = self.source_target.reverse_issue(self) ? false : true
	  if !self.issue_committed
 # If issue_committed is still true, then the issue reversal also
 # failed!
 raise Orp::MovementReverseIssueError
	  else
 self.issue_time = nil
 raise Orp::MovementRcptError
	  end
	end
	
    # Movement is now done
    self.completed = true
    self.completion_time = Time.now.to_f
#      end
  self.save
end

- (Object) validate

Validation. A movement must specify the following:

* Source target
* Destination target
* Source stock entity


77
78
79
80
81
82
83
# File 'lib/porp/stock/movement.rb', line 77

def validate
  assert_present :source_target_id
  assert_present :source_target_class
  assert_present :dest_target_id
  assert_present :dest_target_class
  assert_present :source_entity_id
end