KiaiFighter
KiaiFighter

Reputation: 667

Laravel - Eloquent overwriting a custom timestamp... WHY?

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!!

=============

Extra Code Snippets

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);
    }
}

Below is the sequence the code goes through to hit that update method

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

Answers (1)

Hammerbot
Hammerbot

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

Related Questions