feat(restock): fitur restock toko

This commit is contained in:
Yoga Pangestu 2026-01-02 13:13:23 +07:00
parent 65b1b4d6cf
commit 77d3d25cd9
16 changed files with 1021 additions and 1 deletions

View File

@ -0,0 +1,77 @@
<?php
namespace App\Livewire\Datatable\Manage;
use App\Models\Restock;
use App\Traits\Datatable\WithAppendColumn;
use App\Traits\Datatable\WithConfiguration;
use App\Traits\Datatable\WithPrependColumn;
use Illuminate\Database\Eloquent\Builder;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn;
class RestocksTable extends DataTableComponent
{
use WithAppendColumn, WithConfiguration, WithPrependColumn;
protected $model = Restock::class;
protected string $sortColumn = 'restock_date';
protected string $sortDirection = 'desc';
public function columns(): array
{
return [
Column::make('Gudang', 'warehouse.name')->searchable(),
Column::make('Outlet', 'outlet.name')->searchable(),
Column::make('Tanggal Restock', 'restock_date')
->format(fn ($value) => formatDateLocalized($value))
->searchable()
->sortable(),
Column::make('Catatan', 'note')->searchable(),
ArrayColumn::make('Item')
->data(
fn ($value, $row) => $row->items->map(fn ($item) => [
'name' => $item->restockable?->name,
'quantity' => formatCurrencyNumber($item->quantity, ''),
])->toArray()
)
->outputFormat(
fn ($index, $value) => "
<div class='text-[13px] leading-tight mb-1'>
<div class='font-bold text-gray-800 dark:text-white'>{$value['name']}</div>
<div class='text-gray-400 text-[12px]'>Qty: {$value['quantity']}</div>
</div>"
)
->flexCol(['class' => 'flex-col gap-3']),
Column::make('Aksi')
->label(function ($row) {
$actions = '';
if (auth()->user()->can('delete restock')) {
$actions .= view('components.actions.table.delete', [
'id' => $row->hash,
])->render();
}
return $actions;
})
->html()
->hideIf(auth()->user()->cannot('update restock')),
];
}
public function builder(): Builder
{
return Restock::select('restocks.id', 'warehouse_id', 'outlet_id', 'restock_date', 'note')
->with(['warehouse', 'outlet', 'items', 'items.restockable'])
->whereIn('outlet_id', auth()->user()->outlets->pluck('id')->toArray());
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace App\Livewire\Forms\Studio\Manage;
use App\Models\Restock;
use App\Traits\Media\WithMediaHandler;
use App\Traits\Restock\WithRestockItems;
use App\Traits\Restock\WithRestockStock;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Form;
class RestockForm extends Form
{
use WithMediaHandler, WithRestockItems, WithRestockStock;
public ?Restock $restock = null;
public ?string $note = null;
public string $restock_date = '';
public string $warehouse_id = '';
public string $outlet_id = '';
public array $image = [];
public string $perfume_id = '';
public string $quantity_perfume = '';
public string $product_id = '';
public string $quantity_product = '';
public string $bottle_id = '';
public string $quantity_bottle = '';
public string $item_id = '';
public string $quantity_edit = '';
public function rules(): array
{
return [
'note' => ['nullable', 'string', 'max:100'],
'restock_date' => ['required', 'date', 'before_or_equal:today'],
'warehouse_id' => ['required', Rule::exists('warehouses', 'id')],
'outlet_id' => ['required', Rule::exists('outlets', 'id')],
'image' => ['nullable', 'array', 'max:1'],
];
}
public function validationAttributes(): array
{
return [
'note' => 'catatan',
'restock_date' => 'tanggal restock',
'warehouse_id' => 'gudang',
'outlet_id' => 'outlet',
'image' => 'gambar',
];
}
public function setRestock(Restock $restock): void
{
$this->restock = $restock;
$this->note = $restock->note;
$this->restock_date = $restock->restock_date;
$this->warehouse_id = $restock->warehouse_id;
$this->outlet_id = $restock->outlet_id;
$this->image = $this->mapMediaCollection($restock->getMedia('image'));
}
public function store(): void
{
$this->validate();
$restockItems = $this->loadRestockItems();
DB::transaction(function () use ($restockItems) {
$data = $this->prepareSavedData();
$restock = Restock::create($data);
$restock->load(['warehouse', 'outlet']);
$this->uploadMedia($this->image, $restock, 'image');
foreach ($restockItems as $item) {
if ($item->quantity <= 0) {
continue;
}
$item->update(['restock_id' => $restock->id]);
$this->transferStockFromWarehouseToOutlet($restock->warehouse, $restock->outlet, $item);
}
});
}
public function update(): void
{
$this->validate();
DB::transaction(function () {
$data = $this->prepareSavedData();
$this->restock->update($data);
$this->syncMedia($this->image, $this->restock, 'image');
$this->uploadMedia($this->image, $this->restock, 'image');
});
}
private function prepareSavedData(): array
{
return [
'note' => $this->note,
'warehouse_id' => $this->warehouse_id,
'outlet_id' => $this->outlet_id,
'restock_date' => $this->restock_date,
];
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace App\Livewire\Studio\Manage\Restock;
use App\Livewire\Forms\Studio\Manage\RestockForm;
use App\Models\Warehouse;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
use App\Traits\Restock\WithRestockAddItem;
use App\Traits\Restock\WithRestockDeleteItem;
use App\Traits\Restock\WithRestockItems;
use App\Traits\Restock\WithRestockUpdateItem;
use App\Traits\Utilities\WithUpdatedData;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Tambah Restock Toko')]
class Create extends Component
{
use WithAuthorization, WithConfirmation, WithRestockAddItem, WithRestockDeleteItem, WithRestockItems, WithRestockUpdateItem, WithToast, WithUpdatedData;
public RestockForm $form;
public array $warehouses = [];
public array $outlets = [];
public array $perfumes = [];
public array $products = [];
public array $bottles = [];
public $restockItems;
public function mount(): void
{
$this->warehouses = Warehouse::orderBy('name')->pluck('name', 'id')->toArray();
$this->outlets = auth()->user()->outlets->pluck('name', 'id')->toArray();
$this->restockItems = $this->loadRestockItems();
}
public function updated($property, $value): void
{
if ($property === 'form.warehouse_id' || $property === 'form.outlet_id') {
$this->loadCreateItems();
}
}
private function loadCreateItems(): void
{
$this->reset(['perfumes', 'products', 'bottles']);
$this->form->reset(
'perfume_id',
'quantity_perfume',
'product_id',
'quantity_product',
'bottle_id',
'quantity_bottle'
);
if (! $this->form->warehouse_id || ! $this->form->outlet_id) {
return;
}
$warehouse = Warehouse::find($this->form->warehouse_id);
if (! $warehouse) {
return;
}
$this->perfumes = $warehouse->perfumes()
->wherePivot('stock', '>', 0)
->orderBy('name')
->get()
->pluck('name', 'id')
->toArray();
$this->products = $warehouse->products()
->wherePivot('stock', '>', 0)
->orderBy('name')
->get()
->pluck('name', 'id')
->toArray();
$this->bottles = $warehouse->bottles()
->wherePivot('stock', '>', 0)
->orderBy('name')
->get()
->pluck('name', 'id')
->toArray();
}
public function save(): void
{
$this->canOrAbort('create restock');
if ($this->restockItems->isEmpty()) {
$this->toast('Keranjang restock tidak boleh kosong.', 'Gagal', 'danger');
return;
}
try {
$this->form->store();
} catch (\Exception $e) {
$this->toast($e->getMessage(), 'Gagal', 'danger');
return;
}
$this->redirectRoute('studio.manage.restock.index', [
'notification' => 'Restock berhasil diproses.',
], navigate: true);
}
public function render(): View
{
return view('livewire.studio.manage.restock.form', [
'pageTitle' => 'Tambah Restock Toko',
]);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Livewire\Studio\Manage\Restock;
use App\Models\Restock;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithCloseModal;
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
use App\Traits\Notification\WithSubscribeNotification;
use App\Traits\Restock\WithRestockStock;
use App\Traits\Utilities\WithRequestNotification;
use Flux\Flux;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Restock Toko')]
class Index extends Component
{
use WithAuthorization, WithCloseModal, WithConfirmation, WithRequestNotification, WithRestockStock, WithSubscribeNotification, WithToast;
public function delete(Restock $restock): void
{
$this->canOrAbort('delete restock');
DB::transaction(function () use ($restock) {
foreach ($restock->items as $item) {
$this->reverseStockTransfer($restock->warehouse, $restock->outlet, $item);
}
$restock->delete();
});
$this->dispatch('refreshDatatable');
$this->toast('Restock berhasil dihapus.');
Flux::modals()->close();
}
public function render(): View
{
return view('livewire.studio.manage.restock.index', [
'pageTitle' => 'Restock Toko',
]);
}
}

55
app/Models/Restock.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Veelasky\LaravelHashId\Eloquent\HashableId;
class Restock extends Model implements HasMedia
{
use HasFactory, HashableId, InteractsWithMedia, SoftDeletes;
protected $guarded = ['id'];
protected function casts(): array
{
return [
'restock_date' => 'date',
];
}
#[Scope]
public function today(Builder $query): void
{
$query->whereDate('created_at', today());
}
#[Scope]
public function yesterday(Builder $query): void
{
$query->whereDate('created_at', today()->subDay());
}
public function warehouse(): BelongsTo
{
return $this->belongsTo(Warehouse::class);
}
public function outlet(): BelongsTo
{
return $this->belongsTo(Outlet::class);
}
public function items(): HasMany
{
return $this->hasMany(RestockItem::class);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Veelasky\LaravelHashId\Eloquent\HashableId;
class RestockItem extends Model
{
use HashableId, SoftDeletes;
protected $guarded = ['id'];
protected function casts(): array
{
return [
'quantity' => 'int',
];
}
#[Scope]
public function currentUser(Builder $query): void
{
$query->where('user_id', auth()->id());
}
public function restock(): BelongsTo
{
return $this->belongsTo(Restock::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function restockable(): MorphTo
{
return $this->morphTo();
}
}

View File

@ -182,7 +182,7 @@ public function boot(): void
'can' => 'view purchase',
],
[
'label' => 'Restock Toko',
'label' => 'Restock Outlet',
'icon' => 'arrows-right-left',
'route' => 'studio.manage.restock.index',
'match' => 'studio.manage.restock.*',

View File

@ -0,0 +1,112 @@
<?php
namespace App\Traits\Restock;
use App\Models\Bottle;
use App\Models\Perfume;
use App\Models\Product;
use App\Models\RestockItem;
use App\Models\Warehouse;
trait WithRestockAddItem
{
public function addItem(string $type): void
{
$this->canOrAbort('create restock');
if (! $this->form->warehouse_id) {
$this->toast('Silakan pilih gudang terlebih dahulu.', 'Gagal', 'warning');
return;
}
$warehouse = Warehouse::find($this->form->warehouse_id);
if (! $warehouse) {
$this->toast('Gudang tidak ditemukan.', 'Gagal', 'danger');
return;
}
$data = match ($type) {
'parfum' => [
'restockable_type' => Perfume::class,
'restockable_id' => $this->form->perfume_id,
'quantity' => parseRupiahToInt($this->form->quantity_perfume),
],
'produk' => [
'restockable_type' => Product::class,
'restockable_id' => $this->form->product_id,
'quantity' => parseRupiahToInt($this->form->quantity_product),
],
'botol' => [
'restockable_type' => Bottle::class,
'restockable_id' => $this->form->bottle_id,
'quantity' => parseRupiahToInt($this->form->quantity_bottle),
],
default => null,
};
if (! $data || ! $data['restockable_id'] || $data['quantity'] <= 0) {
$this->toast('Data item tidak valid.', 'Gagal', 'danger');
return;
}
// Validate stock availability
$relationName = match ($type) {
'parfum' => 'perfumes',
'produk' => 'products',
'botol' => 'bottles',
default => null,
};
if ($relationName) {
$warehouseStock = $warehouse->{$relationName}()
->where($data['restockable_type']::make()->getTable().'.id', $data['restockable_id'])
->first()
?->pivot
?->stock ?? 0;
// Is item already in cart?
$existing = RestockItem::where([
'user_id' => auth()->id(),
'restockable_id' => $data['restockable_id'],
'restockable_type' => $data['restockable_type'],
])->whereNull('restock_id')->first();
$currentCartQty = $existing ? $existing->quantity : 0;
$requestedQty = $data['quantity'];
if (($currentCartQty + $requestedQty) > $warehouseStock) {
$this->toast("Stok tidak mencukupi. Stok di gudang: {$warehouseStock}. Sudah di keranjang: {$currentCartQty}.", 'Gagal', 'danger');
return;
}
if ($existing) {
$existing->increment('quantity', $requestedQty);
} else {
RestockItem::create(array_merge($data, ['user_id' => auth()->id()]));
}
}
$this->resetFormItem($type);
$this->restockItems = $this->loadRestockItems();
$this->toast('Item berhasil ditambahkan ke keranjang.');
}
private function resetFormItem(string $type): void
{
if ($type === 'parfum') {
$this->form->perfume_id = '';
$this->form->quantity_perfume = '';
} elseif ($type === 'produk') {
$this->form->product_id = '';
$this->form->quantity_product = '';
} elseif ($type === 'botol') {
$this->form->bottle_id = '';
$this->form->quantity_bottle = '';
}
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Traits\Restock;
use App\Models\RestockItem;
trait WithRestockDeleteItem
{
public function deleteItem(RestockItem $item): void
{
$this->canOrAbort('create restock');
$item->delete();
$this->restockItems = $this->loadRestockItems();
$this->toast('Item berhasil dihapus dari keranjang.');
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Traits\Restock;
use App\Models\RestockItem;
use Illuminate\Database\Eloquent\Collection;
trait WithRestockItems
{
public function loadRestockItems(): Collection
{
return $this->restockItems = RestockItem::query()
->currentUser()
->where('user_id', auth()->id())
->whereNull('restock_id')
->latest()
->get();
}
}

View File

@ -0,0 +1,169 @@
<?php
namespace App\Traits\Restock;
use App\Models\Bottle;
use App\Models\Perfume;
use App\Models\Product;
use App\Models\Restock;
use App\Services\StockActivityLogService;
trait WithRestockStock
{
public function transferStockFromWarehouseToOutlet($warehouse, $outlet, $item): void
{
$quantity = $item->quantity;
switch ($item->restockable_type) {
case Perfume::class:
$relation = 'perfumes';
break;
case Product::class:
$relation = 'products';
break;
case Bottle::class:
$relation = 'bottles';
break;
default:
return;
}
// Decrease warehouse stock
$warehouseItem = $warehouse->{$relation}()
->where("{$relation}.id", $item->restockable_id)
->lockForUpdate()
->withPivot('stock')
->first();
$warehousePreviousStock = 0;
if ($warehouseItem) {
$warehousePreviousStock = $warehouseItem->pivot->stock ?? 0;
}
if ($warehousePreviousStock < $quantity) {
throw new \Exception("Stok {$item->restockable->name} di gudang tidak mencukupi. Tersisa {$warehousePreviousStock}, diminta {$quantity}.");
}
$warehouseNewStock = $warehousePreviousStock - $quantity;
$warehouse->{$relation}()->updateExistingPivot($item->restockable_id, [
'stock' => $warehouseNewStock,
]);
// Increase outlet stock
$outletItem = $outlet->{$relation}()
->where("{$relation}.id", $item->restockable_id)
->lockForUpdate()
->withPivot('stock')
->first();
if ($outletItem) {
$outletPreviousStock = $outletItem->pivot->stock ?? 0;
$outlet->{$relation}()->updateExistingPivot($item->restockable_id, [
'stock' => $outletPreviousStock + $quantity,
]);
} else {
$outletPreviousStock = 0;
$outlet->{$relation}()->attach($item->restockable_id, [
'stock' => $quantity,
]);
}
$outletNewStock = $outletPreviousStock + $quantity;
$restock = $item->restock ?? Restock::find($item->restock_id);
if ($restock) {
StockActivityLogService::distribution(
restock: $restock,
product: $item->restockable,
quantity: $quantity,
warehousePreviousStock: $warehousePreviousStock,
warehouseNewStock: $warehouseNewStock,
outletPreviousStock: $outletPreviousStock,
outletNewStock: $outletNewStock
);
}
}
public function reverseStockTransfer($warehouse, $outlet, $item): void
{
$quantity = $item->quantity;
switch ($item->restockable_type) {
case Perfume::class:
$relation = 'perfumes';
break;
case Product::class:
$relation = 'products';
break;
case Bottle::class:
$relation = 'bottles';
break;
default:
return;
}
// Return items to Warehouse
$warehousePivot = $warehouse->{$relation}()
->where("{$relation}.id", $item->restockable_id)
->first();
$currentWarehouseStock = $warehousePivot?->pivot?->stock ?? 0;
$newWarehouseStock = $currentWarehouseStock + $quantity;
if ($warehousePivot) {
$warehouse->{$relation}()->updateExistingPivot($item->restockable_id, [
'stock' => $newWarehouseStock,
]);
} else {
$warehouse->{$relation}()->attach($item->restockable_id, [
'stock' => $quantity,
]);
}
$restock = $item->restock ?? Restock::find($item->restock_id);
if ($restock) {
StockActivityLogService::logWarehouse(
logName: StockActivityLogService::LOG_DISTRIBUTION,
event: 'increase',
description: "Restock #{$restock->hash_id} Deleted: Returned {$quantity} stock to Warehouse.",
performedOn: $item->restockable,
warehouse: $warehouse,
quantityChange: $quantity,
previousStock: $currentWarehouseStock,
newStock: $newWarehouseStock,
extraProperties: ['restock_id' => $restock->id]
);
}
// Remove items from Outlet
$outletPivot = $outlet->{$relation}()
->where("{$relation}.id", $item->restockable_id)
->first();
$currentOutletStock = $outletPivot?->pivot?->stock ?? 0;
$newOutletStock = max(0, $currentOutletStock - $quantity);
if ($outletPivot) {
$outlet->{$relation}()->updateExistingPivot($item->restockable_id, [
'stock' => $newOutletStock,
]);
}
if ($restock) {
StockActivityLogService::logOutlet(
logName: StockActivityLogService::LOG_DISTRIBUTION,
event: 'decrease',
description: "Restock #{$restock->hash_id} Deleted: Removed {$quantity} stock from Outlet.",
performedOn: $item->restockable,
outlet: $outlet,
quantityChange: -1 * abs($quantity),
previousStock: $currentOutletStock,
newStock: $newOutletStock,
extraProperties: ['restock_id' => $restock->id]
);
}
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\Traits\Restock;
use App\Models\Bottle;
use App\Models\Perfume;
use App\Models\Product;
use App\Models\RestockItem;
use App\Models\Warehouse;
use Livewire\Attributes\On;
trait WithRestockUpdateItem
{
public string $modalTitle = '';
public function updateItem(): void
{
$this->canOrAbort('create restock');
if (! $this->form->warehouse_id) {
$this->toast('Silakan pilih gudang terlebih dahulu.', 'Gagal', 'warning');
return;
}
$warehouse = Warehouse::find($this->form->warehouse_id);
if (! $warehouse) {
$this->toast('Gudang tidak ditemukan.', 'Gagal', 'danger');
return;
}
$item = RestockItem::find($this->form->item_id);
if ($item) {
$newQuantity = parseRupiahToInt($this->form->quantity_edit);
if ($newQuantity <= 0) {
$this->toast('Jumlah harus lebih dari 0.', 'Gagal', 'danger');
return;
}
$relationName = match ($item->restockable_type) {
Perfume::class => 'perfumes',
Product::class => 'products',
Bottle::class => 'bottles',
default => null,
};
if ($relationName) {
$warehouseStock = $warehouse->{$relationName}()
->where($item->restockable_type::make()->getTable().'.id', $item->restockable_id)
->first()
?->pivot
?->stock ?? 0;
if ($newQuantity > $warehouseStock) {
$this->toast("Stok tidak mencukupi (Update). Stok di gudang: {$warehouseStock}.", 'Gagal', 'danger');
return;
}
}
$item->update([
'quantity' => $newQuantity,
]);
}
$this->restockItems = $this->loadRestockItems();
$this->toast('Item berhasil diperbarui.');
$this->dispatch('modal:close', 'form-modal');
}
#[On('modal:open')]
public function openModal($item): void
{
$item = RestockItem::find($item['item']);
$this->form->item_id = $item->id;
$this->form->quantity_edit = formatCurrencyNumber($item->quantity, '');
$this->modalTitle = $item->restockable->name;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Database\Factories;
use App\Models\Outlet;
use App\Models\Restock;
use App\Models\Warehouse;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Restock>
*/
class RestockFactory extends Factory
{
protected $model = Restock::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'warehouse_id' => Warehouse::factory(),
'outlet_id' => Outlet::factory(),
'restock_date' => fake()->dateTimeBetween('-1 month', 'now'),
'note' => fake()->optional()->sentence(),
];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Database\Factories;
use App\Models\Bottle;
use App\Models\Perfume;
use App\Models\Product;
use App\Models\Restock;
use App\Models\RestockItem;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\RestockItem>
*/
class RestockItemFactory extends Factory
{
protected $model = RestockItem::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$restockableType = fake()->randomElement([Perfume::class, Product::class, Bottle::class]);
$restockable = $restockableType::factory()->create();
return [
'restock_id' => Restock::factory(),
'user_id' => User::factory(),
'restockable_type' => $restockableType,
'restockable_id' => $restockable->id,
'quantity' => fake()->numberBetween(1, 100),
];
}
}

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('restocks', function (Blueprint $table) {
$table->id();
$table->foreignId('warehouse_id')->constrained()->cascadeOnDelete();
$table->foreignId('outlet_id')->constrained()->cascadeOnDelete();
$table->date('restock_date');
$table->string('note', 100)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('restocks');
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('restock_items', function (Blueprint $table) {
$table->id();
$table->foreignId('restock_id')->nullable()->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->morphs('restockable');
$table->unsignedInteger('quantity');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('restock_items');
}
};