change copperConvertPage
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
فرمت هر خط:
|
فرمت هر خط:
|
||||||
- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی
|
- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی
|
||||||
|
|
||||||
|
- 2026-11-01 | درخواست تبدیل مس | `CopperConvertRequestService` + `copper_convert_requests` + `POST customer/copper-convert-request` + `admin/copper-convert-request/{id}/approve` | تأیید = فروش دارایی و خرید حلقه با quote_unit_price؛ مهاجرت `2026_11_01_000001`
|
||||||
- 2026-10-28 | شایگان کالا/مشتری | `products.shayegan_item_number` + `ShayeganSyncService::registerCustomer` Account/Put + `ensureCustomerAccountNumber` | بانک 10001 و معین 12101 در settings؛ ItemNumber per-product
|
- 2026-10-28 | شایگان کالا/مشتری | `products.shayegan_item_number` + `ShayeganSyncService::registerCustomer` Account/Put + `ensureCustomerAccountNumber` | بانک 10001 و معین 12101 در settings؛ ItemNumber per-product
|
||||||
- 2026-10-17 | لاگ ترافیک شایگان | `ShayeganClient::request` | `Log::info` با کلید `shayegan.http_request` / `shayegan.http_response`; رمز `***`; خاموش: setting `shayegan_log_traffic=0`
|
- 2026-10-17 | لاگ ترافیک شایگان | `ShayeganClient::request` | `Log::info` با کلید `shayegan.http_request` / `shayegan.http_response`; رمز `***`; خاموش: setting `shayegan_log_traffic=0`
|
||||||
- 2026-10-17 | گروه شایگان در تنظیمات پیشرفته | `settingGroups.resolveSettingMenu` + migration `2026_10_17_000001` | اگر `menu=misc` ولی کلید `shayegan_*` → گروه accounting_shayegan؛ نه «سایر»
|
- 2026-10-17 | گروه شایگان در تنظیمات پیشرفته | `settingGroups.resolveSettingMenu` + migration `2026_10_17_000001` | اگر `menu=misc` ولی کلید `shayegan_*` → گروه accounting_shayegan؛ نه «سایر»
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\CopperConvertRequestService;
|
||||||
|
|
||||||
|
class CopperConvertRequestController extends Controller
|
||||||
|
{
|
||||||
|
public function index(CopperConvertRequestService $service)
|
||||||
|
{
|
||||||
|
return response()->json($service->listForAdmin());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function approve(int $id, CopperConvertRequestService $service)
|
||||||
|
{
|
||||||
|
$row = $service->approve($id, (int) auth('user')->id());
|
||||||
|
|
||||||
|
return response()->json($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject(int $id, CopperConvertRequestService $service)
|
||||||
|
{
|
||||||
|
$row = $service->reject($id, (int) auth('user')->id());
|
||||||
|
|
||||||
|
return response()->json($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Customer;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Customer;
|
||||||
|
use App\Services\CopperConvertRequestService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||||
|
|
||||||
|
class CopperConvertRequestController extends Controller
|
||||||
|
{
|
||||||
|
public function store(Request $request, CopperConvertRequestService $service)
|
||||||
|
{
|
||||||
|
$customer = auth('customer')->user();
|
||||||
|
if (!$customer instanceof Customer) {
|
||||||
|
throw new UnprocessableEntityHttpException('مشتری یافت نشد.');
|
||||||
|
}
|
||||||
|
self::assertCustomerIdentityVerified($customer);
|
||||||
|
if (!$customer->trade_active) {
|
||||||
|
throw new UnprocessableEntityHttpException('امکان سفارش برای شما غیرفعال است.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = (string) $request->input('source');
|
||||||
|
if (!in_array($source, ['katod', 'maftoul', 'shamsh'], true)) {
|
||||||
|
throw new UnprocessableEntityHttpException('نوع دارایی نامعتبر است.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $service->submit(
|
||||||
|
$customer,
|
||||||
|
$source,
|
||||||
|
max(0, (int) $request->input('rings15')),
|
||||||
|
max(0, (int) $request->input('rings25'))
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,7 +122,9 @@ class SingleOrderController extends Controller
|
|||||||
|
|
||||||
// Check if the user wants to transact with a product or non-product (gold)
|
// Check if the user wants to transact with a product or non-product (gold)
|
||||||
if (isset($data['product_id']) and $data['product'] = Product::find($data['product_id'])) {
|
if (isset($data['product_id']) and $data['product'] = Product::find($data['product_id'])) {
|
||||||
|
if (empty($data['quote_unit_price'])) {
|
||||||
$config->assertProductTradingOpen($data['product']);
|
$config->assertProductTradingOpen($data['product']);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the user wants to buy
|
// Check if the user wants to buy
|
||||||
if ($data['direction'] == 1) {
|
if ($data['direction'] == 1) {
|
||||||
@@ -353,6 +355,7 @@ class SingleOrderController extends Controller
|
|||||||
$data['total_price'] = $data['amount'] * $data['price'];
|
$data['total_price'] = $data['amount'] * $data['price'];
|
||||||
$data['payment_amount'] = $data['total_price'] + $data['service_charge_price'];
|
$data['payment_amount'] = $data['total_price'] + $data['service_charge_price'];
|
||||||
$data['fee_asset'] = 'rial';
|
$data['fee_asset'] = 'rial';
|
||||||
|
$this->applyConvertQuote($data);
|
||||||
|
|
||||||
$this->enforceProductOrderLimits($data, (float) $data['total_price']);
|
$this->enforceProductOrderLimits($data, (float) $data['total_price']);
|
||||||
|
|
||||||
@@ -366,7 +369,7 @@ class SingleOrderController extends Controller
|
|||||||
if ($wallet->cash < $data['payment_amount']) {
|
if ($wallet->cash < $data['payment_amount']) {
|
||||||
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
|
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
|
||||||
}
|
}
|
||||||
if (!$data['product']['open_buy']) {
|
if (empty($data['quote_unit_price']) && !$data['product']['open_buy']) {
|
||||||
throw new UnprocessableEntityHttpException('خرید این محصول در این لحظه مقدور نمیباشد');
|
throw new UnprocessableEntityHttpException('خرید این محصول در این لحظه مقدور نمیباشد');
|
||||||
}
|
}
|
||||||
$this->syncOrderProfitFields($data);
|
$this->syncOrderProfitFields($data);
|
||||||
@@ -647,6 +650,7 @@ class SingleOrderController extends Controller
|
|||||||
$data['total_price'] = $data['amount'] * $data['price'] - $data['service_charge_price'];
|
$data['total_price'] = $data['amount'] * $data['price'] - $data['service_charge_price'];
|
||||||
$data['payment_amount'] = $data['total_price'];
|
$data['payment_amount'] = $data['total_price'];
|
||||||
$data['fee_asset'] = 'rial';
|
$data['fee_asset'] = 'rial';
|
||||||
|
$this->applyConvertQuote($data);
|
||||||
if ($data['total_price'] < 0) {
|
if ($data['total_price'] < 0) {
|
||||||
throw new UnprocessableEntityHttpException(
|
throw new UnprocessableEntityHttpException(
|
||||||
'مبلغ دریافتی فروش پس از کارمزد منفی شد. درصد کارمزد سفارش را بررسی کنید (باید اعشاری باشد؛ مثلاً ۰٫۰۱ برای ۱٪).'
|
'مبلغ دریافتی فروش پس از کارمزد منفی شد. درصد کارمزد سفارش را بررسی کنید (باید اعشاری باشد؛ مثلاً ۰٫۰۱ برای ۱٪).'
|
||||||
@@ -659,7 +663,7 @@ class SingleOrderController extends Controller
|
|||||||
if ($wallet_interface_product->wallet->gold < $data['number']) {
|
if ($wallet_interface_product->wallet->gold < $data['number']) {
|
||||||
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
|
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
|
||||||
}
|
}
|
||||||
if (!$data['product']['open_sell']) {
|
if (empty($data['quote_unit_price']) && !$data['product']['open_sell']) {
|
||||||
throw new UnprocessableEntityHttpException('فروش این محصول در این لحظه مقدور نمیباشد');
|
throw new UnprocessableEntityHttpException('فروش این محصول در این لحظه مقدور نمیباشد');
|
||||||
}
|
}
|
||||||
$this->syncOrderProfitFields($data);
|
$this->syncOrderProfitFields($data);
|
||||||
@@ -1403,6 +1407,32 @@ class SingleOrderController extends Controller
|
|||||||
* اگر قیمت واحد سفارش حدود قیمت مثقال باشد، به قیمت هر گرم برگردان.
|
* اگر قیمت واحد سفارش حدود قیمت مثقال باشد، به قیمت هر گرم برگردان.
|
||||||
* جلوگیری از ۱۰ میلیون تومان → ۱۰۵ سوت وقتی مثقال در فیلد «هر گرم» نشسته.
|
* جلوگیری از ۱۰ میلیون تومان → ۱۰۵ سوت وقتی مثقال در فیلد «هر گرم» نشسته.
|
||||||
*/
|
*/
|
||||||
|
/** قیمت قفلشدهٔ تبدیل: تعداد × قیمت واحد، بدون کارمزد و بدون ضرب در وزن واحد. */
|
||||||
|
private function applyConvertQuote(array &$data): void
|
||||||
|
{
|
||||||
|
$quote = (float) ($data['quote_unit_price'] ?? 0);
|
||||||
|
if ($quote <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$number = (float) ($data['number'] ?? 0);
|
||||||
|
$data['amount'] = $number;
|
||||||
|
$data['price'] = $quote;
|
||||||
|
$data['price_original'] = $quote;
|
||||||
|
$data['difference'] = 0;
|
||||||
|
$data['fee'] = 0;
|
||||||
|
$data['total_fee'] = 0;
|
||||||
|
$data['service_charge'] = 0;
|
||||||
|
$data['service_charge_price'] = 0;
|
||||||
|
$data['service_charge_percentage'] = 0;
|
||||||
|
$data['total_amount'] = $number;
|
||||||
|
$data['total_price'] = $number * $quote;
|
||||||
|
$data['payment_amount'] = $data['total_price'];
|
||||||
|
$data['fee_asset'] = 'rial';
|
||||||
|
$data['fee_free'] = true;
|
||||||
|
$data['first_buy'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
private function ensureOrderUnitPriceIsPerGram(array &$data): void
|
private function ensureOrderUnitPriceIsPerGram(array &$data): void
|
||||||
{
|
{
|
||||||
$live = $this->liveGramQuoteRial();
|
$live = $this->liveGramQuoteRial();
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CopperConvertRequest extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'customer_id',
|
||||||
|
'status',
|
||||||
|
'source',
|
||||||
|
'source_product_id',
|
||||||
|
'asset_unit',
|
||||||
|
'consumed_amount',
|
||||||
|
'leftover_amount',
|
||||||
|
'source_unit_price',
|
||||||
|
'wire15_product_id',
|
||||||
|
'wire15_rings',
|
||||||
|
'wire15_unit_price',
|
||||||
|
'wire25_product_id',
|
||||||
|
'wire25_rings',
|
||||||
|
'wire25_unit_price',
|
||||||
|
'sell_order_id',
|
||||||
|
'buy15_order_id',
|
||||||
|
'buy25_order_id',
|
||||||
|
'admin_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'consumed_amount' => 'float',
|
||||||
|
'leftover_amount' => 'float',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function customer()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Customer::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ class AdminPageAccess
|
|||||||
'productsMain_archive',
|
'productsMain_archive',
|
||||||
'productsMain_categories',
|
'productsMain_categories',
|
||||||
'ordersMain',
|
'ordersMain',
|
||||||
|
'copperConvertMain',
|
||||||
'transactionsMain',
|
'transactionsMain',
|
||||||
'accountsMain',
|
'accountsMain',
|
||||||
'walletMain',
|
'walletMain',
|
||||||
@@ -87,6 +88,7 @@ class AdminPageAccess
|
|||||||
'dashboard' => 'dashboardMain',
|
'dashboard' => 'dashboardMain',
|
||||||
'main' => 'dashboardMain',
|
'main' => 'dashboardMain',
|
||||||
'orders' => 'ordersMain',
|
'orders' => 'ordersMain',
|
||||||
|
'copperConvert' => 'copperConvertMain',
|
||||||
'transactions' => 'transactionsMain',
|
'transactions' => 'transactionsMain',
|
||||||
'support' => 'supportMain',
|
'support' => 'supportMain',
|
||||||
'factor' => 'withdrawMain',
|
'factor' => 'withdrawMain',
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (Schema::hasTable('copper_convert_requests')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::create('copper_convert_requests', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('customer_id')->index();
|
||||||
|
$table->string('status', 20)->default('pending')->index();
|
||||||
|
$table->string('source', 20);
|
||||||
|
$table->unsignedBigInteger('source_product_id');
|
||||||
|
$table->string('asset_unit', 32);
|
||||||
|
$table->decimal('consumed_amount', 16, 4);
|
||||||
|
$table->decimal('leftover_amount', 16, 4)->default(0);
|
||||||
|
$table->unsignedBigInteger('source_unit_price');
|
||||||
|
$table->unsignedBigInteger('wire15_product_id')->nullable();
|
||||||
|
$table->unsignedInteger('wire15_rings')->default(0);
|
||||||
|
$table->unsignedBigInteger('wire15_unit_price')->default(0);
|
||||||
|
$table->unsignedBigInteger('wire25_product_id')->nullable();
|
||||||
|
$table->unsignedInteger('wire25_rings')->default(0);
|
||||||
|
$table->unsignedBigInteger('wire25_unit_price')->default(0);
|
||||||
|
$table->unsignedBigInteger('sell_order_id')->nullable();
|
||||||
|
$table->unsignedBigInteger('buy15_order_id')->nullable();
|
||||||
|
$table->unsignedBigInteger('buy25_order_id')->nullable();
|
||||||
|
$table->unsignedBigInteger('admin_id')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('copper_convert_requests');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Admin\ConstController;
|
use App\Http\Controllers\Admin\ConstController;
|
||||||
use App\Http\Controllers\Admin\OrderController;
|
use App\Http\Controllers\Admin\OrderController;
|
||||||
|
use App\Http\Controllers\Admin\CopperConvertRequestController;
|
||||||
use App\Http\Controllers\Admin\PriceController;
|
use App\Http\Controllers\Admin\PriceController;
|
||||||
use App\Http\Controllers\Admin\UserController;
|
use App\Http\Controllers\Admin\UserController;
|
||||||
use App\Http\Controllers\Admin\CustomerController;
|
use App\Http\Controllers\Admin\CustomerController;
|
||||||
@@ -209,6 +210,9 @@ Route::middleware(['auth:user', 'admin.page'])->group(function (){
|
|||||||
Route::post('order/single/table', [OrderController::class, 'singleOrderTable']);
|
Route::post('order/single/table', [OrderController::class, 'singleOrderTable']);
|
||||||
Route::post('order/colleague/table', [OrderController::class, 'colleagueTable']);
|
Route::post('order/colleague/table', [OrderController::class, 'colleagueTable']);
|
||||||
Route::post('single/order', [SingleOrderController::class, 'store']);
|
Route::post('single/order', [SingleOrderController::class, 'store']);
|
||||||
|
Route::get('copper-convert-request', [CopperConvertRequestController::class, 'index']);
|
||||||
|
Route::post('copper-convert-request/{id}/approve', [CopperConvertRequestController::class, 'approve']);
|
||||||
|
Route::post('copper-convert-request/{id}/reject', [CopperConvertRequestController::class, 'reject']);
|
||||||
|
|
||||||
//-------------------price
|
//-------------------price
|
||||||
Route::post('price', [PriceController::class, 'store']);
|
Route::post('price', [PriceController::class, 'store']);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use App\Http\Controllers\Customer\CustomerController;
|
|||||||
use App\Http\Controllers\Customer\DigipayInstallmentController;
|
use App\Http\Controllers\Customer\DigipayInstallmentController;
|
||||||
use App\Http\Controllers\Customer\GoldCreditController;
|
use App\Http\Controllers\Customer\GoldCreditController;
|
||||||
use App\Http\Controllers\Customer\OrderController;
|
use App\Http\Controllers\Customer\OrderController;
|
||||||
|
use App\Http\Controllers\Customer\CopperConvertRequestController;
|
||||||
use App\Http\Controllers\Customer\AddressController;
|
use App\Http\Controllers\Customer\AddressController;
|
||||||
use App\Http\Controllers\Customer\WalletController;
|
use App\Http\Controllers\Customer\WalletController;
|
||||||
use App\Http\Controllers\Customer\PriceTriggerController;
|
use App\Http\Controllers\Customer\PriceTriggerController;
|
||||||
@@ -131,6 +132,7 @@ Route::middleware(['auth:customer', 'customer.active'])->group(function () use (
|
|||||||
Route::get('order/pending', [SingleOrderController::class, 'pendingColleague']);
|
Route::get('order/pending', [SingleOrderController::class, 'pendingColleague']);
|
||||||
Route::get('order/{id}/status', [SingleOrderController::class, 'colleagueStatus']);
|
Route::get('order/{id}/status', [SingleOrderController::class, 'colleagueStatus']);
|
||||||
Route::post('order', [OrderController::class, 'store']);
|
Route::post('order', [OrderController::class, 'store']);
|
||||||
|
Route::post('copper-convert-request', [CopperConvertRequestController::class, 'store']);
|
||||||
|
|
||||||
//-----------------single order
|
//-----------------single order
|
||||||
Route::post('single/order/table', [SingleOrderController::class, 'table']);
|
Route::post('single/order/table', [SingleOrderController::class, 'table']);
|
||||||
|
|||||||
Reference in New Issue
Block a user