213 lines
6.5 KiB
PHP
213 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\SIMBHPJenis;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class SimbhpStockService
|
|
{
|
|
/**
|
|
* Barcode format:
|
|
* - MAIN (satuan besar): 91{ID(8-digit)}1 (example: 91000001231)
|
|
* - BREAKDOWN (satuan kecil): 91{ID(8-digit)}2 (example: 91000001232)
|
|
*
|
|
* Backward compatibility:
|
|
* - {KODE}-B / {KODE}-K
|
|
* - plain {KODE} treated as "besar"
|
|
*
|
|
* @return array{kode:string, jenis_id:int|null, satuan_transaksi:string, barcode_value:string}
|
|
*/
|
|
public function parseBarcode(string $raw): array
|
|
{
|
|
$raw = $this->sanitizeKode($raw);
|
|
if ($raw === '') {
|
|
return [
|
|
'kode' => '',
|
|
'jenis_id' => null,
|
|
'satuan_transaksi' => 'besar',
|
|
'barcode_value' => '',
|
|
];
|
|
}
|
|
|
|
if (preg_match('/^91(\d{8})([12])$/', $raw, $m)) {
|
|
$id = (int) ltrim((string) $m[1], '0');
|
|
$unitDigit = (string) $m[2];
|
|
|
|
return [
|
|
'kode' => '',
|
|
'jenis_id' => $id > 0 ? $id : null,
|
|
'satuan_transaksi' => ($unitDigit === '2') ? 'kecil' : 'besar',
|
|
'barcode_value' => $raw,
|
|
];
|
|
}
|
|
|
|
if (preg_match('/^(.*)-(B|K)$/', $raw, $m)) {
|
|
$base = $this->sanitizeKode((string) $m[1]);
|
|
$suffix = (string) $m[2];
|
|
return [
|
|
'kode' => $base,
|
|
'jenis_id' => null,
|
|
'satuan_transaksi' => ($suffix === 'K') ? 'kecil' : 'besar',
|
|
'barcode_value' => $base . '-' . $suffix,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'kode' => $raw,
|
|
'jenis_id' => null,
|
|
'satuan_transaksi' => 'besar',
|
|
'barcode_value' => $raw,
|
|
];
|
|
}
|
|
|
|
public function sanitizeKode(string $kode): string
|
|
{
|
|
$kode = strtoupper(trim($kode));
|
|
$kode = preg_replace('/\s+/', '', $kode);
|
|
$kode = preg_replace('/[^A-Z0-9._-]/', '', $kode);
|
|
|
|
return substr($kode, 0, 40);
|
|
}
|
|
|
|
public function getJenisByKode(string $kode): ?SIMBHPJenis
|
|
{
|
|
$parsed = $this->parseBarcode($kode);
|
|
$jenisId = $parsed['jenis_id'] ?? null;
|
|
if (!is_null($jenisId) && (int) $jenisId > 0) {
|
|
return SIMBHPJenis::where('id', (int) $jenisId)->first();
|
|
}
|
|
|
|
$kode = $parsed['kode'] ?? '';
|
|
if ($kode === '') {
|
|
return null;
|
|
}
|
|
|
|
return SIMBHPJenis::where('kodejenis', $kode)->first();
|
|
}
|
|
|
|
public function makeBarcodeValue(int $jenisId, string $satuanTransaksi = 'besar'): string
|
|
{
|
|
if ($jenisId <= 0) {
|
|
return '';
|
|
}
|
|
|
|
$idPart = str_pad((string) $jenisId, 8, '0', STR_PAD_LEFT);
|
|
$unitDigit = ($satuanTransaksi === 'kecil') ? '2' : '1';
|
|
|
|
return '91' . $idPart . $unitDigit;
|
|
}
|
|
|
|
/**
|
|
* @return array{has_breakdown: bool, konversi: int, satuan_kecil: string}
|
|
*/
|
|
public function getUnitSetting(SIMBHPJenis $jenis): array
|
|
{
|
|
$satuanKecil = trim((string) ($jenis->satuan_kecil ?? ''));
|
|
$konversi = (int) ($jenis->konversi_kecil ?? 1);
|
|
if ($konversi <= 0) {
|
|
$konversi = 1;
|
|
}
|
|
$hasBreakdown = ($satuanKecil !== '' && $konversi > 1);
|
|
|
|
return [
|
|
'has_breakdown' => $hasBreakdown,
|
|
'konversi' => $konversi,
|
|
'satuan_kecil' => $satuanKecil,
|
|
];
|
|
}
|
|
|
|
public function calculateBaseQty(SIMBHPJenis $jenis, int $qtyInput, string $satuanTransaksi = 'besar'): int
|
|
{
|
|
$qtyInput = (int) $qtyInput;
|
|
if ($qtyInput < 0) {
|
|
$qtyInput = 0;
|
|
}
|
|
|
|
$setting = $this->getUnitSetting($jenis);
|
|
$konversi = (int) ($setting['konversi'] ?? 1);
|
|
if ($konversi <= 0) {
|
|
$konversi = 1;
|
|
}
|
|
|
|
if ($satuanTransaksi === 'kecil') {
|
|
return $qtyInput;
|
|
}
|
|
|
|
return $qtyInput * $konversi;
|
|
}
|
|
|
|
public function getStockBaseByJenis(string $jenisNama): int
|
|
{
|
|
$jenisNama = trim((string) $jenisNama);
|
|
if ($jenisNama === '') {
|
|
return 0;
|
|
}
|
|
|
|
$row = DB::table('simbhpreport')
|
|
->where('jenis', $jenisNama)
|
|
->selectRaw("SUM(CASE WHEN pemasukan IS NOT NULL AND pemasukan > 0 THEN COALESCE(qty_base, pemasukan) ELSE 0 END) AS masuk_base")
|
|
->selectRaw("SUM(CASE WHEN pengeluaran IS NOT NULL AND pengeluaran > 0 THEN COALESCE(qty_base, pengeluaran) ELSE 0 END) AS keluar_base")
|
|
->first();
|
|
|
|
$masuk = (int) ($row->masuk_base ?? 0);
|
|
$keluar = (int) ($row->keluar_base ?? 0);
|
|
|
|
return $masuk - $keluar;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $jenisNames
|
|
* @return array<string, int> map jenis => saldo_base
|
|
*/
|
|
public function getStockBaseByJenisMany(array $jenisNames): array
|
|
{
|
|
$jenisNames = array_values(array_filter(array_map(function ($v) {
|
|
return trim((string) $v);
|
|
}, $jenisNames)));
|
|
|
|
if (count($jenisNames) === 0) {
|
|
return [];
|
|
}
|
|
|
|
$rows = DB::table('simbhpreport')
|
|
->whereIn('jenis', $jenisNames)
|
|
->select('jenis')
|
|
->selectRaw("SUM(CASE WHEN pemasukan IS NOT NULL AND pemasukan > 0 THEN COALESCE(qty_base, pemasukan) ELSE 0 END) AS masuk_base")
|
|
->selectRaw("SUM(CASE WHEN pengeluaran IS NOT NULL AND pengeluaran > 0 THEN COALESCE(qty_base, pengeluaran) ELSE 0 END) AS keluar_base")
|
|
->groupBy('jenis')
|
|
->get();
|
|
|
|
$map = [];
|
|
foreach ($rows as $row) {
|
|
$jenis = (string) ($row->jenis ?? '');
|
|
if ($jenis === '') {
|
|
continue;
|
|
}
|
|
$map[$jenis] = ((int) ($row->masuk_base ?? 0)) - ((int) ($row->keluar_base ?? 0));
|
|
}
|
|
|
|
foreach ($jenisNames as $jenis) {
|
|
if (!array_key_exists($jenis, $map)) {
|
|
$map[$jenis] = 0;
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
public function formatStockDisplay(int $saldoBase, string $satuanBesar, string $satuanKecil, int $konversi): string
|
|
{
|
|
$saldoBase = (int) $saldoBase;
|
|
$konversi = (int) $konversi;
|
|
if ($konversi <= 1 || trim($satuanKecil) === '') {
|
|
return number_format($saldoBase, 0, '.', ',') . ' ' . $satuanBesar;
|
|
}
|
|
|
|
$besar = intdiv($saldoBase, $konversi);
|
|
$kecil = $saldoBase % $konversi;
|
|
|
|
return number_format($besar, 0, '.', ',') . ' ' . $satuanBesar . ' + ' . number_format($kecil, 0, '.', ',') . ' ' . $satuanKecil;
|
|
}
|
|
}
|