361 lines
12 KiB
PHP
361 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use App\Services\SimbhpStockService;
|
|
use App\SIMBHPJenis;
|
|
use App\SIMBHPReport;
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Livewire\Component;
|
|
use Livewire\Attributes\On;
|
|
use Livewire\WithPagination;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class GudangPos extends Component
|
|
{
|
|
use WithPagination;
|
|
|
|
public string $tanggal = '';
|
|
public ?int $penerimaId = null;
|
|
|
|
public string $scan = '';
|
|
public string $selectedKode = '';
|
|
public string $satuanTransaksi = 'besar';
|
|
public int $qty = 1;
|
|
|
|
public string $search = '';
|
|
|
|
/**
|
|
* @var array<int, array{kode:string, jenis:string, qty:int, satuan_transaksi:string}>
|
|
*/
|
|
public array $cart = [];
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->tanggal = date('Y-m-d');
|
|
$this->penerimaId = User::orderBy('nama', 'ASC')->value('id');
|
|
}
|
|
|
|
public function updatingSearch(): void
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function scanLookup(): void
|
|
{
|
|
$service = app(SimbhpStockService::class);
|
|
$raw = (string) $this->scan;
|
|
$parsed = $service->parseBarcode($raw);
|
|
$kode = (string) ($parsed['kode'] ?? '');
|
|
$jenisId = $parsed['jenis_id'] ?? null;
|
|
$this->scan = '';
|
|
|
|
if ($kode === '' && (is_null($jenisId) || (int) $jenisId <= 0)) {
|
|
$this->toast('error', 'Kode barcode kosong.');
|
|
return;
|
|
}
|
|
|
|
$jenis = $service->getJenisByKode($raw);
|
|
if (!$jenis) {
|
|
$this->toast('error', 'Kode barang tidak ditemukan.');
|
|
return;
|
|
}
|
|
|
|
$saldoBase = $service->getStockBaseByJenis((string) ($jenis->jenis ?? ''));
|
|
if ($saldoBase <= 0) {
|
|
$this->toast('error', 'Stok kosong untuk ' . ((string) ($jenis->jenis ?? 'barang')) . '.');
|
|
return;
|
|
}
|
|
|
|
$this->selectedKode = (string) ($jenis->kodejenis ?? '');
|
|
$this->qty = 1;
|
|
$this->satuanTransaksi = (string) ($parsed['satuan_transaksi'] ?? 'besar');
|
|
$this->syncSatuanAvailability();
|
|
$this->dispatch('gudangpos-open-modal');
|
|
}
|
|
|
|
#[On('gudangpos-select')]
|
|
public function selectProduct(string $kode): void
|
|
{
|
|
$service = app(SimbhpStockService::class);
|
|
$kode = $service->sanitizeKode($kode);
|
|
$jenis = $service->getJenisByKode($kode);
|
|
|
|
if (!$jenis) {
|
|
$this->toast('error', 'Kode barang tidak ditemukan: ' . $kode);
|
|
return;
|
|
}
|
|
|
|
$saldoBase = $service->getStockBaseByJenis((string) ($jenis->jenis ?? ''));
|
|
if ($saldoBase <= 0) {
|
|
$this->toast('error', 'Stok kosong untuk ' . ((string) ($jenis->jenis ?? 'barang')) . '.');
|
|
return;
|
|
}
|
|
|
|
$this->selectedKode = (string) ($jenis->kodejenis ?? '');
|
|
$this->qty = 1;
|
|
$this->syncSatuanAvailability();
|
|
$this->dispatch('gudangpos-open-modal');
|
|
}
|
|
|
|
#[On('gudangpos-refresh')]
|
|
public function refreshComponent(): void
|
|
{
|
|
// Force rerender + keep UI responsive when stok berubah dari tab lain.
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function addSelected(): void
|
|
{
|
|
$service = app(SimbhpStockService::class);
|
|
$kode = $service->sanitizeKode($this->selectedKode);
|
|
if ($kode === '') {
|
|
$this->toast('error', 'Pilih barang dulu (scan barcode / klik daftar produk).');
|
|
return;
|
|
}
|
|
|
|
$jenis = $service->getJenisByKode($kode);
|
|
if (!$jenis) {
|
|
$this->toast('error', 'Kode barang tidak ditemukan: ' . $kode);
|
|
return;
|
|
}
|
|
|
|
$qty = (int) $this->qty;
|
|
if ($qty <= 0) {
|
|
$this->toast('error', 'Jumlah harus lebih dari 0.');
|
|
return;
|
|
}
|
|
|
|
$setting = $service->getUnitSetting($jenis);
|
|
if ($this->satuanTransaksi === 'kecil' && !$setting['has_breakdown']) {
|
|
$this->satuanTransaksi = 'besar';
|
|
$this->toast('info', 'Barang ini tidak memiliki satuan kecil, otomatis pakai satuan besar.');
|
|
}
|
|
|
|
$found = false;
|
|
foreach ($this->cart as $i => $line) {
|
|
if (($line['kode'] ?? '') === $kode && ($line['satuan_transaksi'] ?? '') === $this->satuanTransaksi) {
|
|
$this->cart[$i]['qty'] = ((int) ($this->cart[$i]['qty'] ?? 0)) + $qty;
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$found) {
|
|
$this->cart[] = [
|
|
'kode' => $kode,
|
|
'jenis' => (string) ($jenis->jenis ?? ''),
|
|
'qty' => $qty,
|
|
'satuan_transaksi' => $this->satuanTransaksi,
|
|
];
|
|
}
|
|
|
|
$this->qty = 1;
|
|
$this->dispatch('gudangpos-close-modal');
|
|
$this->dispatch('gudangpos-focus', field: 'scan');
|
|
}
|
|
|
|
public function removeLine(int $index): void
|
|
{
|
|
if (!isset($this->cart[$index])) {
|
|
return;
|
|
}
|
|
|
|
array_splice($this->cart, $index, 1);
|
|
}
|
|
|
|
public function processCart(): void
|
|
{
|
|
if (count($this->cart) === 0) {
|
|
$this->toast('info', 'Keranjang masih kosong.');
|
|
return;
|
|
}
|
|
|
|
if (!$this->penerimaId) {
|
|
$this->toast('error', 'Penerima wajib dipilih.');
|
|
return;
|
|
}
|
|
|
|
$tanggal = trim((string) $this->tanggal);
|
|
if ($tanggal === '') {
|
|
$this->toast('error', 'Tanggal wajib diisi.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$date = Carbon::parse($tanggal);
|
|
} catch (\Throwable $e) {
|
|
$this->toast('error', 'Format tanggal tidak valid.');
|
|
return;
|
|
}
|
|
|
|
$penerima = User::select('id', 'nama')->find($this->penerimaId);
|
|
if (!$penerima) {
|
|
$this->toast('error', 'Penerima tidak ditemukan.');
|
|
return;
|
|
}
|
|
|
|
$service = app(SimbhpStockService::class);
|
|
|
|
$prepared = [];
|
|
$stockCache = [];
|
|
foreach ($this->cart as $line) {
|
|
$kode = $service->sanitizeKode((string) ($line['kode'] ?? ''));
|
|
$qty = (int) ($line['qty'] ?? 0);
|
|
$satuan = (string) ($line['satuan_transaksi'] ?? 'besar');
|
|
if ($kode === '' || $qty <= 0) {
|
|
$this->toast('error', 'Ada item keranjang yang tidak valid.');
|
|
return;
|
|
}
|
|
|
|
$jenis = $service->getJenisByKode($kode);
|
|
if (!$jenis) {
|
|
$this->toast('error', 'Kode barang tidak ditemukan: ' . $kode);
|
|
return;
|
|
}
|
|
|
|
$setting = $service->getUnitSetting($jenis);
|
|
if ($satuan === 'kecil' && !$setting['has_breakdown']) {
|
|
$satuan = 'besar';
|
|
}
|
|
|
|
$qtyBase = $service->calculateBaseQty($jenis, $qty, $satuan);
|
|
$jenisNama = (string) ($jenis->jenis ?? '');
|
|
if (!array_key_exists($jenisNama, $stockCache)) {
|
|
$stockCache[$jenisNama] = $service->getStockBaseByJenis($jenisNama);
|
|
}
|
|
|
|
if ($qtyBase > $stockCache[$jenisNama]) {
|
|
if ((int) $stockCache[$jenisNama] <= 0) {
|
|
$this->toast('error', 'Stok kosong untuk ' . $jenisNama . '.');
|
|
} else {
|
|
$this->toast('error', 'Stok tidak cukup untuk ' . $jenisNama . '.');
|
|
}
|
|
return;
|
|
}
|
|
$stockCache[$jenisNama] -= $qtyBase;
|
|
|
|
$prepared[] = [
|
|
'jenis' => $jenis,
|
|
'qty' => $qty,
|
|
'qty_base' => $qtyBase,
|
|
'satuan_transaksi' => $satuan,
|
|
];
|
|
}
|
|
|
|
DB::transaction(function () use ($prepared, $date, $penerima) {
|
|
foreach ($prepared as $row) {
|
|
/** @var SIMBHPJenis $jenis */
|
|
$jenis = $row['jenis'];
|
|
SIMBHPReport::create([
|
|
'tanggal' => (int) $date->format('d'),
|
|
'bulan' => (int) $date->format('m'),
|
|
'tahun' => (int) $date->format('Y'),
|
|
'deskripsi' => 'Diterima oleh ' . ($penerima->nama ?? 'Unkown'),
|
|
'pemasukan' => null,
|
|
'pengeluaran' => (int) $row['qty'],
|
|
'qty_base' => (int) $row['qty_base'],
|
|
'satuan_transaksi' => (string) $row['satuan_transaksi'],
|
|
'masa_expired' => null,
|
|
'jenis' => (string) ($jenis->jenis ?? ''),
|
|
'keterangan' => '',
|
|
'marking' => '',
|
|
]);
|
|
}
|
|
});
|
|
|
|
$this->cart = [];
|
|
$this->qty = 1;
|
|
$this->toast('success', 'Barang keluar berhasil diproses.');
|
|
$this->dispatch('gudang-refresh');
|
|
$this->dispatch('gudangpos-close-modal');
|
|
$this->dispatch('gudangpos-focus', field: 'scan');
|
|
}
|
|
|
|
public function updatedSelectedKode(): void
|
|
{
|
|
$this->syncSatuanAvailability();
|
|
}
|
|
|
|
public function updatedSatuanTransaksi(): void
|
|
{
|
|
$this->syncSatuanAvailability();
|
|
}
|
|
|
|
private function syncSatuanAvailability(): void
|
|
{
|
|
$service = app(SimbhpStockService::class);
|
|
$kode = $service->sanitizeKode($this->selectedKode);
|
|
if ($kode === '') {
|
|
return;
|
|
}
|
|
|
|
$jenis = $service->getJenisByKode($kode);
|
|
if (!$jenis) {
|
|
return;
|
|
}
|
|
|
|
$setting = $service->getUnitSetting($jenis);
|
|
if (!$setting['has_breakdown'] && $this->satuanTransaksi === 'kecil') {
|
|
$this->satuanTransaksi = 'besar';
|
|
}
|
|
}
|
|
|
|
private function toast(string $type, string $message): void
|
|
{
|
|
$this->dispatch('gudangpos-toast', type: $type, message: $message);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$service = app(SimbhpStockService::class);
|
|
|
|
$users = User::select('id', 'nama', 'previlage')->orderBy('nama', 'ASC')->get();
|
|
|
|
$query = SIMBHPJenis::query()->orderBy('jenis', 'ASC');
|
|
$search = trim((string) $this->search);
|
|
if ($search !== '') {
|
|
$searchLike = '%' . $search . '%';
|
|
$driver = DB::connection()->getDriverName();
|
|
$operator = ($driver === 'pgsql') ? 'ILIKE' : 'LIKE';
|
|
$query->where(function ($q) use ($searchLike, $operator) {
|
|
$q->where('jenis', $operator, $searchLike)
|
|
->orWhere('kodejenis', $operator, $searchLike);
|
|
});
|
|
}
|
|
|
|
$products = $query->paginate(12);
|
|
|
|
$jenisNames = $products->getCollection()->pluck('jenis')->filter()->values()->all();
|
|
$stockMap = $service->getStockBaseByJenisMany($jenisNames);
|
|
|
|
$selectedJenis = null;
|
|
$selectedStockDisplay = null;
|
|
$selectedSetting = null;
|
|
$kode = $service->sanitizeKode($this->selectedKode);
|
|
if ($kode !== '') {
|
|
$selectedJenis = $service->getJenisByKode($kode);
|
|
if ($selectedJenis) {
|
|
$saldoBase = $service->getStockBaseByJenis((string) $selectedJenis->jenis);
|
|
$selectedSetting = $service->getUnitSetting($selectedJenis);
|
|
$selectedStockDisplay = $service->formatStockDisplay(
|
|
$saldoBase,
|
|
(string) ($selectedJenis->satuan ?? ''),
|
|
(string) ($selectedJenis->satuan_kecil ?? ''),
|
|
(int) ($selectedJenis->konversi_kecil ?? 1),
|
|
);
|
|
}
|
|
}
|
|
|
|
return view('livewire.gudang-pos', [
|
|
'users' => $users,
|
|
'products' => $products,
|
|
'stockMap' => $stockMap,
|
|
'selectedJenis' => $selectedJenis,
|
|
'selectedStockDisplay' => $selectedStockDisplay,
|
|
'selectedSetting' => $selectedSetting,
|
|
]);
|
|
}
|
|
}
|