Reputation: 667
I am making an inventory management system. When a product is out of stock, I make an entry in a table and note the "oos_at" field with the date/time.
later, when it is back in stock, i find that entry and update the "restocked_at" timestamp field.
However, when I do the second action, my "oos_at" field is overwritten with the timestamp of the query ('updated_at') field... I had to keep track of this "oos_at" field on another entity so it wouldn't get overwritten and then use that field to update the "oos_at" field a second time when updating that table...
Below is my code....
class Pusher extends Model {
/**
*
*
* @param Carbon $oos_at
* @return bool
*/
public function setAsOutOfStock(Carbon $oos_at)
{
Log::info([
'FILE' => get_class($this),
'method' => 'setAsOutOfStock',
'pusher' => $this->id,
'param:oos_at' => $oos_at->toDateTimeString(),
]);
$this->pusherOutOfStocks()->create([
'location_id' => $this->location_id,
'product_id' => $this->product_id,
'oos_at' => $oos_at->toDateTimeString(),
]);
$this->oos = true;
$this->oos_at = $oos_at;
return $this->save();
}
/**
* Clear the PusherOutOfStocks attached to $this Pusher
* (This Pusher has been restocked!)
*
* @param Carbon $time
* @return bool
*/
public function setAsInStock(Carbon $time)
{
Log::info([
'FILE' => get_class($this),
'method' => 'setAsInStock',
'pusher' => $this->id,
'param:time' => $time->toDateTimeString(),
]);
$this->pusherOutOfStocks()->where('restocked_at', null)
->update(['restocked_at' => $time->toDateTimeString()]);
$this->oos = false;
$this->oos_at = null;
return $this->save();
}
}
When I die and dump the PusherOutOfStocks BEFORE the pusher is restocked, then the "oos_at" is set appropriately.
Illuminate\Database\Eloquent\Collection {#340
#items: array:1 [
0 => Newave\Engineering\PusherOutOfStock {#343
#fillable: array:5 [
0 => "pusher_id"
1 => "location_id"
2 => "product_id"
3 => "oos_at"
4 => "restocked_at"
]
#dates: array:2 [
0 => "oos_at"
1 => "restocked_at"
]
#connection: null
#table: null
#primaryKey: "id"
#perPage: 15
+incrementing: true
+timestamps: true
#attributes: array:9 [
"id" => 246
"pusher_id" => 216
"location_id" => 634
"product_id" => 378
"oos_at" => "2016-03-11 03:00:00"
"restocked_at" => null
"created_at" => "2016-03-11 12:12:01"
"updated_at" => "2016-03-11 12:12:01"
"deleted_at" => null
]
#original: array:9 [
"id" => 246
"pusher_id" => 216
"location_id" => 634
"product_id" => 378
"oos_at" => "2016-03-11 03:00:00"
"restocked_at" => null
"created_at" => "2016-03-11 12:12:01"
"updated_at" => "2016-03-11 12:12:01"
"deleted_at" => null
]
#relations: []
#hidden: []
#visible: []
#appends: []
#guarded: array:1 [
0 => "*"
]
#dateFormat: null
#casts: []
#touches: []
#observables: []
#with: []
#morphClass: null
+exists: true
+wasRecentlyCreated: false
#forceDeleting: false
}
]
}
When I die and dump the PusherOutOfStocks AFTER the pusher is restocked, then the "oos_at" is the same as "updated_at"
Illuminate\Database\Eloquent\Collection {#408
#items: array:1 [
0 => Newave\Engineering\PusherOutOfStock {#775
#fillable: array:5 [
0 => "pusher_id"
1 => "location_id"
2 => "product_id"
3 => "oos_at"
4 => "restocked_at"
]
#dates: array:2 [
0 => "oos_at"
1 => "restocked_at"
]
#connection: null
#table: null
#primaryKey: "id"
#perPage: 15
+incrementing: true
+timestamps: true
#attributes: array:9 [
"id" => 244
"pusher_id" => 214
"location_id" => 626
"product_id" => 374
"oos_at" => "2016-03-11 12:10:23"
"restocked_at" => "2016-03-11 04:00:00"
"created_at" => "2016-03-11 12:10:22"
"updated_at" => "2016-03-11 12:10:23"
"deleted_at" => null
]
#original: array:9 [
"id" => 244
"pusher_id" => 214
"location_id" => 626
"product_id" => 374
"oos_at" => "2016-03-11 12:10:23"
"restocked_at" => "2016-03-11 04:00:00"
"created_at" => "2016-03-11 12:10:22"
"updated_at" => "2016-03-11 12:10:23"
"deleted_at" => null
]
#relations: []
#hidden: []
#visible: []
#appends: []
#guarded: array:1 [
0 => "*"
]
#dateFormat: null
#casts: []
#touches: []
#observables: []
#with: []
#morphClass: null
+exists: true
+wasRecentlyCreated: false
#forceDeleting: false
}
]
}
I have even used DB::getQueryLog() and saw NOWHERE that 'oos_at' was being explicitly set/updated....
Nowhere else in my code is this table modified...
Does anyone understand what is happening here???? Thank you!!
=============
Table Migration::
Schema::create('pusher_out_of_stocks', function (Blueprint $table) {
$table->increments('id');
$table->integer('pusher_id');
$table->integer('location_id');
$table->integer('product_id');
$table->timestamp('oos_at');
$table->timestamp('restocked_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
PusherOutOfStock Class
class PusherOutOfStock extends Model
{
use SoftDeletes;
protected $fillable = [
'pusher_id',
'location_id',
'product_id',
'oos_at',
'restocked_at',
];
protected $dates = [
'oos_at',
'restocked_at'
];
/**
* A PusherOutOfStock belongsTo a Product
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function product()
{
return $this->belongsTo(Product::class);
}
/**
* A PusherOutOfStock belongsTo a Pusher
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function pusher()
{
return $this->belongsTo(Pusher::class);
}
/**
* A PusherOutOfStock belongsTo a Location
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function location()
{
return $this->belongsTo(Location::class);
}
}
First, it hits my InventoryController in my API namespace
public function upload(Request $request)
{
$data = $request->all();
$header = $this->getHeader($data);
$body = $this->getBody($data);
$reader = $this->getReaderFromHeader($header);
if ( ! $this->guardAgainstNoReader($reader))
return (new Response("Reader with mac-address: ". $header['reader_mac'] . " does not exist.", 409));
$result = $this->inventoryRepository->uploadPusherData($reader, $header, $body);
return $result;
}
Then it sends the request to the InventoryRepository
public function uploadPusherData(Reader $reader, $header, $body)
{
foreach ($body as $pusherData)
{
$result[] = $this->processPusherData($pusher, $header['timestamp'], $pusherData);
}
return $result;
}
Then, inside the repository, it processes one line at a time (in my test, there is only one line)
private function processPusherData(Pusher $pusher, $timestamp, $pusherData)
{
$latestInventory = $pusher->latestInventory;
if (! $latestInventory)
return $this->updatePusher($pusher, $timestamp, $pusherData);
if ($latestInventory->tags_blocked == $pusherData['data_TAGSBLKED'])
return $this->noChangeRecorded($pusher, $latestInventory, $timestamp);
return $this->updatePusher($pusher, $timestamp, $pusherData);
}
Then the pusher is updated...
public function updatePusher($pusher, $timestamp, $pusherData)
{
// See if there are any existing already
$prevInv = $pusher->latestInventory;
// Create the new data
$inventory = Inventory::create([
'pusher_id' => $pusher->id,
'product_id' => $pusher->product_id,
'reader_id' => $pusher->reader->id,
'tags_blocked' => $pusherData['data_TAGSBLKED'],
'paddle_exposed'=> $pusherData['paddle_exposed'],
'created_at' => Carbon::createFromTimestamp($timestamp)
->toDateTimeString(),
]);
if ( !$prevInv || $prevInv->id == $inventory->id )
{
return "first-data" . $timestamp;
}
return $this->checkForEvents($inventory, $prevInv);
}
We check if any events should be triggered... In this case, previous inventory had 9 items in stock... now there are 0.
private function checkForEvents(Inventory $currentInventory, Inventory $previousInventory)
{
if ( ! $previousInventory->oos && $currentInventory->oos && $previousInventory->pusher->oos_notified == 0)
{
$currentInventory->pusher->oos_notified = true;
$currentInventory->pusher->save();
return Event::fire(new InventoryOutOfStock($currentInventory));
}
if ( ( $previousInventory->oos || $previousInventory->status == "RESTOCK" )
&& $currentInventory->tags_blocked > 2 )
{
return Event::fire(new PusherWasRestocked($currentInventory));
}
if ( $currentInventory->status == "RESTOCK" && $previousInventory->pusher->low_stock_notified == 0)
{
$currentInventory->pusher->low_stock_notified = true;
$currentInventory->pusher->save();
return Event::fire(new LowStockAlert($currentInventory));
}
return "no-events";
}
This then fires the event InventoryOutOfStock
That triggers 3 events... 2 are related to notifications being sent etc..
'App\Events\InventoryOutOfStock' => [
'App\Listeners\InventoryOutOfStockUpdater',
'App\Listeners\EmailInventoryOutOfStockNotification',
'App\Listeners\SMSInventoryOutOfStockNotification',
// 'App\Listeners\OutOfStocksUpdater', ],
Which leads us to ...
public function handle(InventoryOutOfStock $event)
{
$pusher = $event->pusher;
$inventory = $event->inventory;
$product = $pusher->product;
$oos = $pusher->setAsOutOfStock($inventory->created_at);
$locationPushers = $product->getPushersByLocation($pusher->location);
$isInStock = false;
foreach ($locationPushers as $pusher)
if ($pusher->oos == 0)
$isInStock = true;
if (! $isInStock)
$product->productOutOfStocks()->create([
'location_id' => $pusher->location_id,
'oos_at' => $event->inventory->created_at,
]);
}
Upvotes: 3
Views: 2265
Reputation: 16364
I think that you do not have to use the timestamp
method to create your fields, but you should use the dateTime
method:
Schema::create('pusher_out_of_stocks', function (Blueprint $table) {
$table->increments('id');
$table->integer('pusher_id');
$table->integer('location_id');
$table->integer('product_id');
$table->dateTime('oos_at');
$table->dateTime('restocked_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
This should work :)
Hope it helped!
Upvotes: 5