2026-01-24 14:03:11 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Http\Controllers\SuperController;
|
2026-05-25 20:55:05 +03:30
|
|
|
use App\Models\Cnt;
|
2026-01-24 14:03:11 +00:00
|
|
|
use App\Models\Product;
|
2026-06-02 11:18:05 +03:30
|
|
|
use App\Models\Setting;
|
2026-01-24 14:03:11 +00:00
|
|
|
use App\DB\Te\TableEngine;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
|
use \App\Http\Controllers\Customer\FundController;
|
2026-06-01 18:46:57 +03:30
|
|
|
use App\Http\Controllers\Site\ReportController;
|
2026-06-12 19:49:53 +03:30
|
|
|
use App\Services\ConfigService;
|
2026-06-11 16:41:28 +03:30
|
|
|
use App\Services\ProductDisplayPriceService;
|
2026-06-02 16:00:33 +03:30
|
|
|
use App\Services\ProductPriceService;
|
2026-06-12 19:49:53 +03:30
|
|
|
use App\Services\ProductPriceSyncService;
|
2026-06-17 12:12:35 +03:30
|
|
|
use App\Services\PriceBroadcastService;
|
2026-06-13 11:17:32 +03:30
|
|
|
use App\Services\CategoryService;
|
2026-06-04 13:48:37 +03:30
|
|
|
use Illuminate\Support\Facades\Schema;
|
2026-05-25 20:55:05 +03:30
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
class ProductController extends SuperController
|
|
|
|
|
{
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
2026-05-17 16:39:53 +00:00
|
|
|
$this->with = ['file'/*, 'price', 'default', 'sub'*/];
|
2026-05-25 20:55:05 +03:30
|
|
|
// $this->with = [];
|
2026-01-24 14:03:11 +00:00
|
|
|
parent::__construct(Product::class, $this->with, ['*'], null, null, ['title']);
|
|
|
|
|
}
|
|
|
|
|
public function tableProduct(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$data = $request->all();
|
2026-05-25 20:55:05 +03:30
|
|
|
if (isset($data["select"]) && is_null($data["select"])){
|
2026-01-24 14:03:11 +00:00
|
|
|
$data["select"] = [];
|
|
|
|
|
}
|
2026-05-25 20:55:05 +03:30
|
|
|
if (is_null($this->query)){
|
2026-01-24 14:03:11 +00:00
|
|
|
$query_table = $this->model::with($this->with);
|
|
|
|
|
//*******کداضافه شده
|
|
|
|
|
// ->where('active', '<=', now()) // فیلتر محصولات فعال
|
|
|
|
|
// ->get();
|
|
|
|
|
//*********
|
2026-05-25 20:55:05 +03:30
|
|
|
}else{
|
2026-01-24 14:03:11 +00:00
|
|
|
$query_table = $this->query;
|
|
|
|
|
// $query_table = $query_table->where('active', '<=', now());
|
|
|
|
|
}
|
|
|
|
|
TableEngine::create($query_table, $data)
|
|
|
|
|
->initSearch($this->search)
|
|
|
|
|
->select($this->select)
|
|
|
|
|
->export($query_table);
|
2026-06-04 13:48:37 +03:30
|
|
|
|
2026-06-28 15:16:39 +03:30
|
|
|
$this->enrichAdminTablePrices($query_table['rows'] ?? null);
|
|
|
|
|
|
2026-06-04 13:48:37 +03:30
|
|
|
if (Schema::hasColumn((new Product)->getTable(), 'auto_open_at')) {
|
|
|
|
|
$query_table['auto_open_at'] = Product::selectRaw(
|
|
|
|
|
'SEC_TO_TIME(AVG(TIME_TO_SEC(TIME(auto_open_at)))) as avg_time'
|
|
|
|
|
)->value('avg_time');
|
|
|
|
|
}
|
|
|
|
|
if (Schema::hasColumn((new Product)->getTable(), 'auto_close_at')) {
|
|
|
|
|
$query_table['auto_close_at'] = Product::selectRaw(
|
|
|
|
|
'SEC_TO_TIME(AVG(TIME_TO_SEC(TIME(auto_close_at)))) as avg_time'
|
|
|
|
|
)->value('avg_time');
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
// $query_table['11'] = $avgDuration;
|
|
|
|
|
// $query_table['11'] = Product::avg('auto_open_at');
|
|
|
|
|
// $query_table['112'] = Product::avg('auto_close_at');
|
|
|
|
|
return $query_table;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 21:09:43 +03:30
|
|
|
public function archiveTable(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$data = $request->all();
|
|
|
|
|
if (isset($data['select']) && is_null($data['select'])) {
|
|
|
|
|
$data['select'] = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$query_table = Product::onlyTrashed()->with($this->with);
|
|
|
|
|
TableEngine::create($query_table, $data)
|
|
|
|
|
->initSearch($this->search)
|
|
|
|
|
->select($this->select)
|
|
|
|
|
->export($query_table);
|
|
|
|
|
|
2026-06-28 15:16:39 +03:30
|
|
|
$this->enrichAdminTablePrices($query_table['rows'] ?? null);
|
|
|
|
|
|
2026-06-14 21:09:43 +03:30
|
|
|
return $query_table;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function archive($id)
|
|
|
|
|
{
|
|
|
|
|
$product = Product::onlyTrashed()->findOrFail($id);
|
|
|
|
|
$product->restore();
|
|
|
|
|
|
|
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
public function editProducts(Request $request)
|
|
|
|
|
{
|
|
|
|
|
if (isset($request->auto_open_at, $request->auto_close_at)) {
|
|
|
|
|
Product::query()->update([
|
|
|
|
|
'auto_open_at' => $request->auto_open_at,
|
|
|
|
|
'auto_close_at' => $request->auto_close_at,
|
|
|
|
|
]);
|
|
|
|
|
return response()->json(['message' => 'Products updated successfully.']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json(['error' => 'auto_open_at and auto_close_at must be set'], 400);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function store(Request $request)
|
|
|
|
|
{
|
2026-06-02 16:00:33 +03:30
|
|
|
$data = $this->filterProductData($request->all());
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
$product = Product::create($data);
|
|
|
|
|
return $this->resultCreate($product->id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function update(Request $request, $id)
|
|
|
|
|
{
|
|
|
|
|
// return 1;
|
|
|
|
|
|
2026-06-02 16:00:33 +03:30
|
|
|
$data = $this->filterProductData($request->all());
|
2026-06-09 12:43:41 +03:30
|
|
|
$clearingReadingMethod = array_key_exists('reading_method', $data)
|
|
|
|
|
&& ($data['reading_method'] === null || $data['reading_method'] === '');
|
|
|
|
|
if ($clearingReadingMethod) {
|
|
|
|
|
$data['reading_method'] = null;
|
|
|
|
|
}
|
|
|
|
|
if (isset($data["price"])) {
|
2026-05-25 19:42:48 +03:30
|
|
|
$data["reading_method"] = null;
|
2026-06-02 16:00:33 +03:30
|
|
|
if (Product::hasPriceOffsetColumn()) {
|
|
|
|
|
$data['price_offset_percent'] = 0;
|
|
|
|
|
}
|
2026-05-25 19:42:48 +03:30
|
|
|
}
|
2026-05-25 20:55:05 +03:30
|
|
|
if (isset($data["auto_close_at"]) and isset($data["auto_close_at"])){
|
|
|
|
|
if ($data["auto_close_at"] == $data["auto_open_at"]){
|
|
|
|
|
unset($data["auto_open_at"]);
|
|
|
|
|
unset($data["auto_close_at"]);
|
2026-05-25 19:42:48 +03:30
|
|
|
}
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
// return $data['price'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if(!isset($data['default'])){
|
|
|
|
|
// throw new UnprocessableEntityHttpException('مقادیر اعتبارات را وارد کنید');
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// $data['auto_open_at'] = Carbon::parse($data['auto_open_at'])->format('Y-m-d H:i:s');
|
|
|
|
|
// $data['auto_close_at'] = Carbon::parse($data['auto_close_at'])->format('Y-m-d H:i:s');
|
2026-06-02 16:00:33 +03:30
|
|
|
$product = Product::findOrFail($id);
|
|
|
|
|
$oldOffsetPercent = (float) ($product->price_offset_percent ?? 0);
|
|
|
|
|
|
2026-06-17 21:26:08 +03:30
|
|
|
unset($data['trade_by_quantity']);
|
|
|
|
|
|
2026-06-02 16:00:33 +03:30
|
|
|
if (
|
|
|
|
|
Product::hasPriceOffsetColumn()
|
|
|
|
|
&& $product->reading_method
|
|
|
|
|
&& array_key_exists('price_offset_percent', $data)
|
2026-06-09 12:43:41 +03:30
|
|
|
&& !$clearingReadingMethod
|
2026-06-02 16:00:33 +03:30
|
|
|
) {
|
|
|
|
|
ProductPriceService::reapplyOffsetPercent(
|
|
|
|
|
$product,
|
|
|
|
|
(float) $data['price_offset_percent'],
|
|
|
|
|
$oldOffsetPercent
|
|
|
|
|
);
|
|
|
|
|
unset($data['price'], $data['price_profit']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product->update($data);
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function destroy($id)
|
|
|
|
|
{
|
|
|
|
|
Product::findOrFail($id)->delete();
|
|
|
|
|
return response()->json(true);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function listProduct()
|
|
|
|
|
{
|
2026-06-12 19:49:53 +03:30
|
|
|
if (app(ConfigService::class)->isStore()) {
|
|
|
|
|
app(ProductPriceSyncService::class)->syncStorePrices(true);
|
|
|
|
|
|
2026-06-15 00:58:15 +03:30
|
|
|
if (! $this->isCustomerApiRequest()) {
|
2026-06-12 20:10:14 +03:30
|
|
|
$this->auto_close_open();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$products = Product::with(['file'])->where('active', 1)->get();
|
2026-06-13 11:17:32 +03:30
|
|
|
$categoryService = app(CategoryService::class);
|
|
|
|
|
$products = $categoryService->filterProductsByVisibleCategories($products);
|
|
|
|
|
$products = $categoryService->sortProductsForStore($products);
|
2026-06-12 20:10:14 +03:30
|
|
|
|
2026-06-15 00:58:15 +03:30
|
|
|
if ($this->isCustomerApiRequest()) {
|
2026-06-15 00:52:13 +03:30
|
|
|
$this->applyCustomerShow($products);
|
2026-06-12 20:10:14 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json($products);
|
2026-06-12 19:49:53 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-13 17:35:40 +03:30
|
|
|
// آبشده خانگی — کران قیمت را آپدیت میکند؛ list فقط از DB میخواند
|
2026-06-02 16:00:33 +03:30
|
|
|
$products = Product::with(['file'])->where('active', 1)->get();
|
|
|
|
|
|
2026-06-15 00:58:15 +03:30
|
|
|
if ($this->isCustomerApiRequest()) {
|
2026-06-15 00:52:13 +03:30
|
|
|
$this->applyCustomerShow($products);
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-15 00:58:15 +03:30
|
|
|
if (Product::hasPriceOffsetColumn() && $this->isCustomerApiRequest()) {
|
2026-06-09 12:43:41 +03:30
|
|
|
$products->each(function (Product $product) {
|
|
|
|
|
if ($product->reading_method) {
|
|
|
|
|
$product->makeHidden(['price_offset_percent']);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-06-02 16:00:33 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json($products);
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-11 22:08:47 +03:30
|
|
|
public function ProductById($id)
|
|
|
|
|
{
|
2026-06-12 19:49:53 +03:30
|
|
|
app(ProductPriceSyncService::class)->syncStorePrices(true);
|
2026-06-11 22:08:47 +03:30
|
|
|
$this->auto_close_open();
|
|
|
|
|
$product = Product::with(['file'])
|
|
|
|
|
->where('id', $id)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if ($product) {
|
2026-06-15 00:58:15 +03:30
|
|
|
if ($this->isCustomerApiRequest()) {
|
2026-06-15 00:52:13 +03:30
|
|
|
$this->applyCustomerShow(collect([$product]));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 22:08:47 +03:30
|
|
|
$product->similar = Product::with(['file'])
|
|
|
|
|
->where('group_type', $product->group_type)
|
|
|
|
|
->where('id', '!=', $id)
|
|
|
|
|
->get();
|
2026-06-15 00:52:13 +03:30
|
|
|
|
2026-06-15 00:58:15 +03:30
|
|
|
if ($this->isCustomerApiRequest()) {
|
2026-06-15 00:52:13 +03:30
|
|
|
$this->applyCustomerShow($product->similar);
|
|
|
|
|
}
|
2026-06-11 22:08:47 +03:30
|
|
|
} else {
|
|
|
|
|
return response()->json(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json($product);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
public function getPrice()
|
|
|
|
|
{
|
2026-06-12 19:49:53 +03:30
|
|
|
if (app(ConfigService::class)->isStore()) {
|
|
|
|
|
app(ProductPriceSyncService::class)->syncStorePrices(true);
|
|
|
|
|
} else {
|
|
|
|
|
app(ProductPriceSyncService::class)->syncAll(true);
|
|
|
|
|
}
|
2026-05-25 19:42:48 +03:30
|
|
|
|
2026-06-17 12:12:35 +03:30
|
|
|
dispatch(function () {
|
|
|
|
|
app(PriceBroadcastService::class)->broadcastAfterPriceUpdate();
|
|
|
|
|
})->afterResponse();
|
|
|
|
|
|
2026-05-25 20:55:05 +03:30
|
|
|
return response()->json(true);
|
2026-05-25 19:42:48 +03:30
|
|
|
}
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
public function auto_close_open()
|
|
|
|
|
{
|
2026-06-14 22:00:21 +03:30
|
|
|
$config = app(ConfigService::class);
|
2026-01-24 14:03:11 +00:00
|
|
|
$products = Product::with([])->get();
|
|
|
|
|
foreach ($products as $product) {
|
|
|
|
|
Product::where('id', $product->id)->update([
|
2026-06-14 22:00:21 +03:30
|
|
|
'show' => $config->isProductWithinTradingHours($product),
|
2026-01-24 14:03:11 +00:00
|
|
|
]);
|
|
|
|
|
}
|
2026-06-12 17:02:12 +03:30
|
|
|
|
2026-05-25 20:55:05 +03:30
|
|
|
return response()->json(true);
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function getProduct($id)
|
|
|
|
|
{
|
|
|
|
|
$product = Product::with($this->with)->where('id', $id)->first();
|
|
|
|
|
return response()->json($product);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-06 13:47:26 +03:30
|
|
|
* فعال/غیرفعال محصول — show باید با active همگام باشد (اپ کاربر از show استفاده میکند).
|
2026-01-24 14:03:11 +00:00
|
|
|
*/
|
2026-06-06 13:47:26 +03:30
|
|
|
public function active($id)
|
|
|
|
|
{
|
|
|
|
|
$product = Product::findOrFail($id);
|
|
|
|
|
$active = !$product->active;
|
|
|
|
|
$product->update([
|
|
|
|
|
'active' => $active,
|
|
|
|
|
'show' => $active,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
public function hide($id)
|
|
|
|
|
{
|
|
|
|
|
$query = Product::findOrFail($id);
|
|
|
|
|
$query->update([
|
|
|
|
|
'show' => !$query->show
|
|
|
|
|
]);
|
|
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function openSell($id)
|
|
|
|
|
{
|
|
|
|
|
return $this->toggleBooleanStatus($id, 'open_sell');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function openBuy($id)
|
|
|
|
|
{
|
|
|
|
|
return $this->toggleBooleanStatus($id, 'open_buy');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function auto_buy($id)
|
|
|
|
|
{
|
|
|
|
|
return $this->toggleBooleanStatus($id, 'auto_buy');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function auto_sell($id)
|
|
|
|
|
{
|
|
|
|
|
return $this->toggleBooleanStatus($id, 'auto_sell');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function toggleBooleanStatus($id, $field)
|
|
|
|
|
{
|
|
|
|
|
$product = Product::findOrFail($id);
|
|
|
|
|
$product->$field = !$product->$field;
|
|
|
|
|
$product->save();
|
|
|
|
|
if (!$product->open_buy and !$product->open_sell) {
|
|
|
|
|
$product->show = false;
|
|
|
|
|
$product->active = false;
|
|
|
|
|
} else {
|
|
|
|
|
$product->show = true;
|
|
|
|
|
$product->active = true;
|
|
|
|
|
}
|
|
|
|
|
$product->save();
|
|
|
|
|
return response()->json($product->$field);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function auto_group($action, $method)
|
|
|
|
|
{
|
|
|
|
|
// Define valid actions and methods
|
|
|
|
|
$validActions = ['sell' => 'auto_sell', 'buy' => 'auto_buy', 'all' => ['auto_buy', 'auto_sell']];
|
|
|
|
|
$validMethods = ['true' => 1, 'false' => 0];
|
|
|
|
|
// Validate action and method
|
|
|
|
|
if (!isset($validActions[$action]) || !array_key_exists($method, $validMethods)) {
|
|
|
|
|
return response()->json(['error' => 'Invalid action or method'], 400);
|
|
|
|
|
}
|
|
|
|
|
// Get fields to update and value to set
|
|
|
|
|
$fields = $validActions[$action];
|
|
|
|
|
$value = $validMethods[$method];
|
|
|
|
|
// Update products
|
|
|
|
|
Product::query()->update(array_fill_keys((array)$fields, $value));
|
|
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 21:44:44 +03:30
|
|
|
private const ACTIVATION_SNAPSHOT_KEY = 'products_activation_snapshot';
|
|
|
|
|
|
|
|
|
|
public function activationSnapshotStatus()
|
|
|
|
|
{
|
|
|
|
|
$row = Cnt::where('key', self::ACTIVATION_SNAPSHOT_KEY)->first();
|
|
|
|
|
$items = $row?->value_json['items'] ?? [];
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'has_snapshot' => ! empty($items),
|
|
|
|
|
'saved_at' => $row?->value_json['saved_at'] ?? null,
|
|
|
|
|
'count' => count($items),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function deactivateAllProducts()
|
|
|
|
|
{
|
|
|
|
|
$products = Product::query()
|
|
|
|
|
->where(function ($query) {
|
|
|
|
|
$query->where('active', 1)
|
|
|
|
|
->orWhere('open_buy', 1)
|
|
|
|
|
->orWhere('open_sell', 1);
|
|
|
|
|
})
|
|
|
|
|
->get(['id', 'active', 'show', 'open_buy', 'open_sell']);
|
|
|
|
|
|
|
|
|
|
$items = $products->map(static fn (Product $product) => [
|
|
|
|
|
'id' => $product->id,
|
|
|
|
|
'active' => (bool) $product->active,
|
|
|
|
|
'show' => (bool) $product->show,
|
|
|
|
|
'open_buy' => (bool) $product->open_buy,
|
|
|
|
|
'open_sell' => (bool) $product->open_sell,
|
|
|
|
|
])->values()->all();
|
|
|
|
|
|
|
|
|
|
Cnt::updateOrCreate(
|
|
|
|
|
['key' => self::ACTIVATION_SNAPSHOT_KEY],
|
|
|
|
|
[
|
|
|
|
|
'title_fa' => 'ذخیره وضعیت محصولات',
|
|
|
|
|
'value_json' => [
|
|
|
|
|
'saved_at' => now()->timezone('Asia/Tehran')->format('Y-m-d H:i:s'),
|
|
|
|
|
'items' => $items,
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$affected = Product::query()->update([
|
|
|
|
|
'active' => 0,
|
|
|
|
|
'show' => 0,
|
|
|
|
|
'open_buy' => 0,
|
|
|
|
|
'open_sell' => 0,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->broadcastPricesAfterResponse();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'message' => 'همه محصولات بسته شد.',
|
|
|
|
|
'snapshot_count' => count($items),
|
|
|
|
|
'affected' => $affected,
|
|
|
|
|
'has_snapshot' => ! empty($items),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function restoreActivationSnapshot()
|
|
|
|
|
{
|
|
|
|
|
$row = Cnt::where('key', self::ACTIVATION_SNAPSHOT_KEY)->first();
|
|
|
|
|
$items = $row?->value_json['items'] ?? [];
|
|
|
|
|
|
|
|
|
|
if (empty($items)) {
|
|
|
|
|
return response()->json(['error' => 'اسنAPSHOTی برای بازگردانی وجود ندارد.'], 422);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$restored = 0;
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
$updated = Product::where('id', $item['id'] ?? 0)->update([
|
|
|
|
|
'active' => ! empty($item['active']) ? 1 : 0,
|
|
|
|
|
'show' => ! empty($item['show']) ? 1 : 0,
|
|
|
|
|
'open_buy' => ! empty($item['open_buy']) ? 1 : 0,
|
|
|
|
|
'open_sell' => ! empty($item['open_sell']) ? 1 : 0,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($updated) {
|
|
|
|
|
$restored++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->auto_close_open();
|
|
|
|
|
$this->broadcastPricesAfterResponse();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'message' => 'وضعیت قبلی محصولات بازگردانی شد.',
|
|
|
|
|
'restored' => $restored,
|
|
|
|
|
'has_snapshot' => true,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function closeAllBuy()
|
|
|
|
|
{
|
|
|
|
|
Product::query()->update(['open_buy' => 0]);
|
|
|
|
|
$inactive = Product::where('open_buy', 0)->where('open_sell', 0)->update([
|
|
|
|
|
'active' => 0,
|
|
|
|
|
'show' => 0,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->broadcastPricesAfterResponse();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'message' => 'خرید همه محصولات بسته شد.',
|
|
|
|
|
'inactive' => $inactive,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function closeAllSell()
|
|
|
|
|
{
|
|
|
|
|
Product::query()->update(['open_sell' => 0]);
|
|
|
|
|
$inactive = Product::where('open_buy', 0)->where('open_sell', 0)->update([
|
|
|
|
|
'active' => 0,
|
|
|
|
|
'show' => 0,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->broadcastPricesAfterResponse();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'message' => 'فروش همه محصولات بسته شد.',
|
|
|
|
|
'inactive' => $inactive,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function syncProductShow()
|
|
|
|
|
{
|
|
|
|
|
$this->auto_close_open();
|
|
|
|
|
$this->broadcastPricesAfterResponse();
|
|
|
|
|
|
|
|
|
|
return response()->json(['message' => 'نمایش محصولات طبق ساعات بهروز شد.']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function broadcastPricesAfterResponse(): void
|
|
|
|
|
{
|
|
|
|
|
dispatch(function () {
|
|
|
|
|
app(PriceBroadcastService::class)->broadcastAfterPriceUpdate();
|
|
|
|
|
})->afterResponse();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 20:55:05 +03:30
|
|
|
public function updatePrice(Request $request){
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
}
|
2026-06-02 16:00:33 +03:30
|
|
|
|
2026-06-14 22:00:21 +03:30
|
|
|
/** فروشگاه — نمایش در اپ کاربر: فعال + ساعات + قیمت ذخیرهشده یا محصول وزنی */
|
2026-06-12 20:10:14 +03:30
|
|
|
private function storeProductShowForCustomer(Product $product): bool
|
|
|
|
|
{
|
|
|
|
|
if (! $product->active) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 22:00:21 +03:30
|
|
|
if (! app(ConfigService::class)->isProductWithinTradingHours($product)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 20:10:14 +03:30
|
|
|
$storedPrice = (int) ($product->price ?? 0) + (int) ($product->price_profit ?? 0);
|
|
|
|
|
if ($storedPrice > 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((int) ($product->direct_price ?? 0) === 0 && (float) ($product->amount ?? 0) > 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 00:58:15 +03:30
|
|
|
private function isCustomerApiRequest(): bool
|
|
|
|
|
{
|
|
|
|
|
return str_starts_with(request()->path(), 'customer/');
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 00:52:13 +03:30
|
|
|
private function applyCustomerShow($products): void
|
|
|
|
|
{
|
|
|
|
|
$products = $products instanceof \Illuminate\Support\Collection
|
|
|
|
|
? $products
|
|
|
|
|
: collect($products);
|
|
|
|
|
|
|
|
|
|
if ($products->isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (app(ConfigService::class)->isStore()) {
|
|
|
|
|
$products->each(function (Product $product) {
|
|
|
|
|
$product->setAttribute('show', $this->storeProductShowForCustomer($product));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$displayPriceService = app(ProductDisplayPriceService::class);
|
|
|
|
|
$displayPriceService->warmLivePriceCache();
|
|
|
|
|
$displayPriceService->enrichForApi($products);
|
|
|
|
|
$products->each(function (Product $product) use ($displayPriceService) {
|
|
|
|
|
$product->setAttribute('show', $displayPriceService->resolveCustomerShow($product));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 15:16:39 +03:30
|
|
|
/**
|
|
|
|
|
* جدول ادمین — قیمت لحظهای روی مدل (بدون UPDATE دیتابیس)، مثل فرم ویرایش و اپ مشتری.
|
|
|
|
|
*
|
|
|
|
|
* @param iterable<Product>|null $rows
|
|
|
|
|
*/
|
|
|
|
|
private function enrichAdminTablePrices($rows): void
|
|
|
|
|
{
|
|
|
|
|
if ($rows === null || app(ConfigService::class)->isStore()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$products = collect($rows)->filter(fn ($row) => $row instanceof Product);
|
|
|
|
|
if ($products->isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$displayPriceService = app(ProductDisplayPriceService::class);
|
|
|
|
|
$displayPriceService->warmLivePriceCache();
|
|
|
|
|
$displayPriceService->enrichForApi($products);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 16:00:33 +03:30
|
|
|
private function filterProductData(array $data): array
|
|
|
|
|
{
|
|
|
|
|
if (!Product::hasPriceOffsetColumn()) {
|
|
|
|
|
unset($data['price_offset_percent']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 22:06:53 +03:30
|
|
|
if (!Product::hasOrderLimitColumns()) {
|
|
|
|
|
unset($data['order_limit_enabled'], $data['min_order_cash'], $data['max_order_cash']);
|
|
|
|
|
} elseif (empty($data['order_limit_enabled'])) {
|
|
|
|
|
$data['order_limit_enabled'] = false;
|
|
|
|
|
$data['min_order_cash'] = null;
|
|
|
|
|
$data['max_order_cash'] = null;
|
|
|
|
|
} else {
|
|
|
|
|
$min = $data['min_order_cash'] ?? null;
|
|
|
|
|
$max = $data['max_order_cash'] ?? null;
|
|
|
|
|
$hasMin = $min !== null && $min !== '';
|
|
|
|
|
$hasMax = $max !== null && $max !== '';
|
|
|
|
|
|
|
|
|
|
if (!$hasMin && !$hasMax) {
|
|
|
|
|
throw new UnprocessableEntityHttpException(
|
|
|
|
|
'با فعال بودن محدودیت مبلغ سفارش، حداقل یکی از حداقل یا حداکثر مبلغ باید وارد شود'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($hasMin && $hasMax && (float) $min > (float) $max) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('حداقل مبلغ نمیتواند بیشتر از حداکثر باشد');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data['order_limit_enabled'] = true;
|
|
|
|
|
$data['min_order_cash'] = $hasMin ? (int) $min : null;
|
|
|
|
|
$data['max_order_cash'] = $hasMax ? (int) $max : null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 16:44:02 +03:30
|
|
|
if (!Product::hasShowInAssetsColumn()) {
|
|
|
|
|
unset($data['show_in_assets']);
|
|
|
|
|
} elseif (array_key_exists('show_in_assets', $data)) {
|
|
|
|
|
$data['show_in_assets'] = !empty($data['show_in_assets']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 13:11:51 +03:30
|
|
|
if (!Product::hasUseLastPriceIfZeroColumn()) {
|
|
|
|
|
unset($data['use_last_price_if_zero']);
|
|
|
|
|
} elseif (array_key_exists('use_last_price_if_zero', $data)) {
|
|
|
|
|
$data['use_last_price_if_zero'] = !empty($data['use_last_price_if_zero']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 16:13:18 +03:30
|
|
|
if (!Product::hasTradeByQuantityColumn()) {
|
|
|
|
|
unset($data['trade_by_quantity']);
|
|
|
|
|
} elseif (array_key_exists('trade_by_quantity', $data)) {
|
|
|
|
|
$data['trade_by_quantity'] = !empty($data['trade_by_quantity']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 13:31:32 +03:30
|
|
|
if (!Product::hasDirectPriceColumn()) {
|
|
|
|
|
unset($data['direct_price']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 20:09:56 +03:30
|
|
|
if (!Product::hasInventoryColumn()) {
|
|
|
|
|
unset($data['inventory']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 15:16:39 +03:30
|
|
|
if (!Product::hasOverrideTradingHoursColumn()) {
|
|
|
|
|
unset($data['override_trading_hours']);
|
|
|
|
|
} elseif (array_key_exists('override_trading_hours', $data)) {
|
|
|
|
|
$data['override_trading_hours'] = !empty($data['override_trading_hours']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 16:00:33 +03:30
|
|
|
return $data;
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|