131 lines
3.8 KiB
PHP
131 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace Modules\Maintenance\Services;
|
|
|
|
use App\Product;
|
|
use App\Utils\ProductUtil;
|
|
use App\Variation;
|
|
use App\VariationLocationDetails;
|
|
use Modules\Maintenance\Models\SparePart;
|
|
use Modules\Maintenance\Utils\MaintenanceUtil;
|
|
use InvalidArgumentException;
|
|
|
|
class SparePartInventoryService
|
|
{
|
|
public function __construct(
|
|
protected MaintenanceUtil $maintenanceUtil,
|
|
protected ProductUtil $productUtil
|
|
) {}
|
|
|
|
public function posStockSyncEnabled(int $businessId): bool
|
|
{
|
|
$settings = $this->maintenanceUtil->getMaintenanceSettings($businessId);
|
|
|
|
return ! empty($settings['sync_pos_stock']);
|
|
}
|
|
|
|
public function posStockDeductEnabled(int $businessId): bool
|
|
{
|
|
$settings = $this->maintenanceUtil->getMaintenanceSettings($businessId);
|
|
|
|
return ! array_key_exists('deduct_pos_stock_on_consume', $settings)
|
|
|| ! empty($settings['deduct_pos_stock_on_consume']);
|
|
}
|
|
|
|
public function resolveLocationId(SparePart $part, int $businessId): ?int
|
|
{
|
|
if ($part->location_id) {
|
|
return (int) $part->location_id;
|
|
}
|
|
|
|
$settings = $this->maintenanceUtil->getMaintenanceSettings($businessId);
|
|
|
|
return ! empty($settings['default_stock_location_id'])
|
|
? (int) $settings['default_stock_location_id']
|
|
: null;
|
|
}
|
|
|
|
public function resolveVariationId(SparePart $part): ?int
|
|
{
|
|
if ($part->variation_id) {
|
|
return (int) $part->variation_id;
|
|
}
|
|
if (! $part->product_id) {
|
|
return null;
|
|
}
|
|
|
|
$variation = Variation::where('product_id', $part->product_id)->orderBy('id')->first();
|
|
|
|
return $variation?->id;
|
|
}
|
|
|
|
public function getPosAvailableQty(SparePart $part, int $businessId): ?float
|
|
{
|
|
if (! $part->product_id) {
|
|
return null;
|
|
}
|
|
|
|
$locationId = $this->resolveLocationId($part, $businessId);
|
|
$variationId = $this->resolveVariationId($part);
|
|
if (! $locationId || ! $variationId) {
|
|
return null;
|
|
}
|
|
|
|
$details = VariationLocationDetails::where('product_id', $part->product_id)
|
|
->where('variation_id', $variationId)
|
|
->where('location_id', $locationId)
|
|
->first();
|
|
|
|
return $details ? (float) $details->qty_available : 0.0;
|
|
}
|
|
|
|
public function syncSparePartQuantityFromPos(SparePart $part, int $businessId): void
|
|
{
|
|
if (! $this->posStockSyncEnabled($businessId) || ! $part->product_id) {
|
|
return;
|
|
}
|
|
|
|
$qty = $this->getPosAvailableQty($part, $businessId);
|
|
if ($qty === null) {
|
|
return;
|
|
}
|
|
|
|
$part->update(['quantity' => $qty]);
|
|
}
|
|
|
|
public function deductPosStock(SparePart $part, float $quantity, int $businessId): void
|
|
{
|
|
if (! $this->posStockDeductEnabled($businessId) || ! $part->product_id) {
|
|
return;
|
|
}
|
|
|
|
$locationId = $this->resolveLocationId($part, $businessId);
|
|
$variationId = $this->resolveVariationId($part);
|
|
if (! $locationId || ! $variationId) {
|
|
return;
|
|
}
|
|
|
|
$product = Product::where('business_id', $businessId)->find($part->product_id);
|
|
if (! $product || (int) $product->enable_stock !== 1) {
|
|
return;
|
|
}
|
|
|
|
$available = $this->getPosAvailableQty($part, $businessId) ?? 0;
|
|
if ($available < $quantity) {
|
|
throw new InvalidArgumentException('موجودی انبار POS کافی نیست. موجودی: '.$available);
|
|
}
|
|
|
|
$this->productUtil->decreaseProductQuantity(
|
|
$part->product_id,
|
|
$variationId,
|
|
$locationId,
|
|
$quantity
|
|
);
|
|
|
|
if ($this->posStockSyncEnabled($businessId)) {
|
|
$part->refresh();
|
|
$this->syncSparePartQuantityFromPos($part, $businessId);
|
|
}
|
|
}
|
|
}
|