Files
boursecu_server/app/Services/CopperConvertRequestService.php
T

306 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use App\Http\Controllers\Single\SingleOrderController;
use App\Models\CopperConvertRequest;
use App\Models\Customer;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
class CopperConvertRequestService
{
public function submit(Customer $customer, string $source, int $rings15, int $rings25): CopperConvertRequest
{
if ($rings15 < 1 && $rings25 < 1) {
throw new UnprocessableEntityHttpException('حداقل یک حلقه انتخاب کنید.');
}
$pending = CopperConvertRequest::query()
->where('customer_id', $customer->id)
->where('status', 'pending')
->exists();
if ($pending) {
throw new UnprocessableEntityHttpException('یک درخواست تبدیل در انتظار تأیید دارید.');
}
$quote = $this->quote($customer, $source, $rings15, $rings25);
return CopperConvertRequest::query()->create([
'customer_id' => $customer->id,
'status' => 'pending',
'source' => $source,
'source_product_id' => $quote['source']->id,
'asset_unit' => $quote['unit'],
'consumed_amount' => $quote['consumed'],
'leftover_amount' => $quote['leftover'],
'source_unit_price' => $quote['source_unit_price'],
'wire15_product_id' => $quote['wire15']?->id,
'wire15_rings' => $rings15,
'wire15_unit_price' => $quote['price15'],
'wire25_product_id' => $quote['wire25']?->id,
'wire25_rings' => $rings25,
'wire25_unit_price' => $quote['price25'],
]);
}
public function listForAdmin(): array
{
return CopperConvertRequest::query()
->with('customer:id,fname,lname,name,mobile')
->orderByDesc('id')
->limit(200)
->get()
->map(function (CopperConvertRequest $row) {
$customer = $row->customer;
$name = trim((string) ($customer->name ?? ''));
if ($name === '') {
$name = trim(((string) ($customer->fname ?? '')).' '.((string) ($customer->lname ?? '')));
}
return [
'id' => $row->id,
'status' => $row->status,
'source' => $row->source,
'customer_name' => $name !== '' ? $name : 'مشتری',
'customer_mobile' => $customer->mobile ?? '',
'asset_unit' => $row->asset_unit,
'consumed_amount' => $row->consumed_amount,
'leftover_amount' => $row->leftover_amount,
'source_unit_price' => $row->source_unit_price,
'wire15_rings' => $row->wire15_rings,
'wire15_unit_price' => $row->wire15_unit_price,
'wire25_rings' => $row->wire25_rings,
'wire25_unit_price' => $row->wire25_unit_price,
'created_at' => optional($row->created_at)->toDateTimeString(),
];
})
->all();
}
public function approve(int $id, int $adminId): CopperConvertRequest
{
return DB::transaction(function () use ($id, $adminId) {
$row = CopperConvertRequest::query()->lockForUpdate()->findOrFail($id);
if ($row->status !== 'pending') {
throw new UnprocessableEntityHttpException('این درخواست قبلاً تعیین‌تکلیف شده است.');
}
$customer = Customer::query()->findOrFail($row->customer_id);
$available = $this->availableQuantity($customer, (int) $row->source_product_id);
if ($available + 0.0001 < (float) $row->consumed_amount) {
throw new UnprocessableEntityHttpException('موجودی مشتری برای این تبدیل کافی نیست.');
}
$note = 'درخواست تبدیل #'.$row->id;
$sellId = $this->placeLeg($customer, (int) $row->source_product_id, 2, (float) $row->consumed_amount, (float) $row->source_unit_price, $note);
$buy15 = (int) $row->wire15_rings > 0
? $this->placeLeg($customer, (int) $row->wire15_product_id, 1, (float) $row->wire15_rings, (float) $row->wire15_unit_price, $note)
: null;
$buy25 = (int) $row->wire25_rings > 0
? $this->placeLeg($customer, (int) $row->wire25_product_id, 1, (float) $row->wire25_rings, (float) $row->wire25_unit_price, $note)
: null;
$row->status = 'approved';
$row->admin_id = $adminId;
$row->sell_order_id = $sellId;
$row->buy15_order_id = $buy15;
$row->buy25_order_id = $buy25;
$row->save();
return $row;
});
}
public function reject(int $id, int $adminId): CopperConvertRequest
{
$row = CopperConvertRequest::query()->findOrFail($id);
if ($row->status !== 'pending') {
throw new UnprocessableEntityHttpException('این درخواست قبلاً تعیین‌تکلیف شده است.');
}
$row->status = 'rejected';
$row->admin_id = $adminId;
$row->save();
return $row;
}
private function placeLeg(Customer $customer, int $productId, int $direction, float $number, float $unitPrice, string $note): int
{
if ($productId < 1 || $number <= 0 || $unitPrice <= 0) {
throw new UnprocessableEntityHttpException('اطلاعات سفارش تبدیل ناقص است.');
}
auth('customer')->setUser($customer);
try {
$response = app(SingleOrderController::class)->store(Request::create('/admin/single/order', 'POST', [
'customer_id' => $customer->id,
'product_id' => $productId,
'direction' => $direction,
'number' => $number,
'method' => 'cash',
'price' => $unitPrice,
'difference' => 0,
'quote_unit_price' => $unitPrice,
'description' => $note,
]));
} finally {
auth('customer')->forgetUser();
}
$body = $response->getData(true);
$orderId = (int) ($body[1]['order']['id'] ?? 0);
if ($orderId < 1) {
throw new UnprocessableEntityHttpException('ثبت سفارش تبدیل انجام نشد.');
}
return $orderId;
}
private function quote(Customer $customer, string $source, int $rings15, int $rings25): array
{
$products = Product::query()->where('active', 1)->get();
$sourceProduct = $this->findSource($products, $source);
if (!$sourceProduct) {
throw new UnprocessableEntityHttpException('محصول این دارایی پیدا نشد.');
}
$wire15 = $rings15 > 0 ? $this->findWire($products, '1.5') : null;
$wire25 = $rings25 > 0 ? $this->findWire($products, '2.5') : null;
if ($rings15 > 0 && !$wire15) {
throw new UnprocessableEntityHttpException('محصول سیم ۱.۵ پیدا نشد.');
}
if ($rings25 > 0 && !$wire25) {
throw new UnprocessableEntityHttpException('محصول سیم ۲.۵ پیدا نشد.');
}
$prices = app(ProductDisplayPriceService::class);
$sourcePerKg = (float) $prices->resolveOrderTradePrice($sourceProduct, 1);
$price15 = $wire15 ? (int) round((float) $prices->resolveOrderTradePrice($wire15, 1)) : 0;
$price25 = $wire25 ? (int) round((float) $prices->resolveOrderTradePrice($wire25, 1)) : 0;
if ($sourcePerKg <= 0 || ($rings15 > 0 && $price15 <= 0) || ($rings25 > 0 && $price25 <= 0)) {
throw new UnprocessableEntityHttpException('قیمت تبدیل در دسترس نیست.');
}
$unit = trim((string) ($sourceProduct->unit ?: 'کیلوگرم'));
if ($unit === '') {
$unit = 'کیلوگرم';
}
$available = $this->availableQuantity($customer, (int) $sourceProduct->id);
$availableValue = $this->toKg($available, $unit, $sourceProduct) * $sourcePerKg;
$spent = ($rings15 * $price15) + ($rings25 * $price25);
if ($spent > $availableValue + 1) {
throw new UnprocessableEntityHttpException('تعداد حلقه از ارزش دارایی بیشتر است.');
}
$consumedKg = $spent / $sourcePerKg;
$consumed = round($this->fromKg($consumedKg, $unit, $sourceProduct), 4);
if ($consumed <= 0) {
throw new UnprocessableEntityHttpException('مقدار قابل تبدیل صفر است.');
}
if ($consumed > $available + 0.0001) {
throw new UnprocessableEntityHttpException('موجودی این دارایی کافی نیست.');
}
$leftover = round(max(0, $available - $consumed), 4);
$sourceUnitPrice = (int) ceil($spent / $consumed);
return [
'source' => $sourceProduct,
'wire15' => $wire15,
'wire25' => $wire25,
'unit' => $unit,
'consumed' => $consumed,
'leftover' => $leftover,
'source_unit_price' => max(1, $sourceUnitPrice),
'price15' => (int) round($price15),
'price25' => (int) round($price25),
];
}
private function availableQuantity(Customer $customer, int $productId): float
{
return (float) app(AssetSummaryService::class)->productDisplayQuantity(
(int) $customer->id,
(int) ($customer->type ?? 2),
$productId
);
}
private function findSource(Collection $products, string $source): ?Product
{
return $products->first(function (Product $product) use ($source) {
$group = trim((string) $product->group_type);
$title = (string) $product->title;
if ($source === 'katod') {
return $group === 'کاتد مس' || str_contains($title, 'کاتد');
}
if ($source === 'maftoul') {
return $group === 'مفتول مس' || str_contains($title, 'مفتول');
}
if ($source === 'shamsh') {
return str_contains($group, 'شمش') || str_contains($title, 'شمش');
}
return false;
});
}
private function findWire(Collection $products, string $section): ?Product
{
return $products->first(function (Product $product) use ($section) {
$title = (string) $product->title;
$pattern = $section === '1.5' ? '/1\.5|۱\.۵/u' : '/2\.5|۲\.۵/u';
return preg_match($pattern, $title) === 1;
});
}
private function toKg(float $quantity, string $unit, Product $product): float
{
if ($quantity <= 0) {
return 0;
}
if (str_contains($unit, 'کیلو')) {
return $quantity;
}
if (str_contains($unit, 'گرم')) {
return $quantity / 1000;
}
$perPiece = (float) ($product->amount ?? 0);
if ($perPiece <= 0) {
return $quantity;
}
$pieceUnit = trim((string) $product->unit);
$grams = str_contains($pieceUnit, 'کیلو') ? $perPiece * 1000 : $perPiece;
return ($quantity * $grams) / 1000;
}
private function fromKg(float $kg, string $unit, Product $product): float
{
if ($kg <= 0) {
return 0;
}
if (str_contains($unit, 'کیلو')) {
return $kg;
}
if (str_contains($unit, 'گرم')) {
return $kg * 1000;
}
$perPiece = (float) ($product->amount ?? 0);
if ($perPiece <= 0) {
return $kg;
}
$pieceUnit = trim((string) $product->unit);
$grams = str_contains($pieceUnit, 'کیلو') ? $perPiece * 1000 : $perPiece;
if ($grams <= 0) {
return $kg;
}
return ($kg * 1000) / $grams;
}
}