added some feature for accounting and price
This commit is contained in:
@@ -52,6 +52,8 @@ class ProductController extends SuperController
|
||||
->select($this->select)
|
||||
->export($query_table);
|
||||
|
||||
$this->enrichAdminTablePrices($query_table['rows'] ?? null);
|
||||
|
||||
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'
|
||||
@@ -83,6 +85,8 @@ class ProductController extends SuperController
|
||||
->select($this->select)
|
||||
->export($query_table);
|
||||
|
||||
$this->enrichAdminTablePrices($query_table['rows'] ?? null);
|
||||
|
||||
return $query_table;
|
||||
}
|
||||
|
||||
@@ -557,6 +561,27 @@ class ProductController extends SuperController
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* جدول ادمین — قیمت لحظهای روی مدل (بدون 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);
|
||||
}
|
||||
|
||||
private function filterProductData(array $data): array
|
||||
{
|
||||
if (!Product::hasPriceOffsetColumn()) {
|
||||
@@ -616,6 +641,12 @@ class ProductController extends SuperController
|
||||
unset($data['inventory']);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,18 @@
|
||||
namespace App\Http\Controllers\Finance;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Admin\TahesabController;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Order;
|
||||
use App\Models\Price;
|
||||
use App\Models\Product;
|
||||
use App\Models\TransactionsAdmin;
|
||||
use App\Models\User;
|
||||
use App\Models\Withdraw;
|
||||
use App\Models\Tahesab;
|
||||
use App\Services\TahesabSyncService;
|
||||
use App\DB\Te\TableEngine;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -31,41 +34,142 @@ class FinanceController extends Controller
|
||||
->export($transactions_admins);
|
||||
|
||||
if (isset($transactions_admins['rows']) && is_array($transactions_admins['rows']) && $this->Config('send_to_tahesab') === 'true') {
|
||||
$withdrawIds = collect($transactions_admins['rows'])
|
||||
->pluck('withdraw_id')
|
||||
->filter()
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if ($withdrawIds !== []) {
|
||||
$tahesabByWithdraw = Tahesab::query()
|
||||
->whereIn('withdraw_id', $withdrawIds)
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->unique('withdraw_id')
|
||||
->keyBy('withdraw_id');
|
||||
|
||||
foreach ($transactions_admins['rows'] as &$row) {
|
||||
$tahesab = $tahesabByWithdraw->get($row['withdraw_id'] ?? null);
|
||||
if (! $tahesab) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$row['tahesab_record_id'] = $tahesab->id;
|
||||
$row['tahesab_sync_status'] = $tahesab->sync_status
|
||||
?? (empty($tahesab->tahesab_id) ? TahesabSyncService::STATUS_PENDING : TahesabSyncService::STATUS_SUCCESS);
|
||||
|
||||
if (empty($tahesab->tahesab_id) || $row['tahesab_sync_status'] === TahesabSyncService::STATUS_FAILED) {
|
||||
$row['tahesab_failed_id'] = $tahesab->id;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
$this->enrichRowsWithTahesab($transactions_admins['rows']);
|
||||
}
|
||||
|
||||
return response()->json($transactions_admins);
|
||||
}
|
||||
|
||||
/**
|
||||
* هر ردیف دفترکل را با وضعیت سند تهحساب مرتبط غنی میکند.
|
||||
* لینک: ابتدا از طریق withdraw_id (کیفپول/پاداش) و سپس order_id (معامله).
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $rows با ارجاع تغییر میکند
|
||||
*/
|
||||
private function enrichRowsWithTahesab(array &$rows): void
|
||||
{
|
||||
$withdrawIds = collect($rows)->pluck('withdraw_id')->filter()->unique()->values()->all();
|
||||
$orderIds = collect($rows)->pluck('order_id')->filter()->unique()->values()->all();
|
||||
|
||||
if ($withdrawIds === [] && $orderIds === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tahesabByWithdraw = $withdrawIds === []
|
||||
? collect()
|
||||
: Tahesab::query()->whereIn('withdraw_id', $withdrawIds)
|
||||
->orderByDesc('id')->get()->unique('withdraw_id')->keyBy('withdraw_id');
|
||||
|
||||
$tahesabByOrder = $orderIds === []
|
||||
? collect()
|
||||
: Tahesab::query()->whereIn('order_id', $orderIds)
|
||||
->orderByDesc('id')->get()->unique('order_id')->keyBy('order_id');
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$tahesab = $tahesabByWithdraw->get($row['withdraw_id'] ?? null)
|
||||
?? $tahesabByOrder->get($row['order_id'] ?? null);
|
||||
|
||||
if (! $tahesab) {
|
||||
// ردیف دفترکل بدون سند تهحساب — یعنی هنوز به حسابداری ارسال نشده
|
||||
$row['tahesab_record_id'] = null;
|
||||
$row['tahesab_sync_status'] = TahesabSyncService::STATUS_PENDING;
|
||||
$row['tahesab_failed_id'] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$row['tahesab_record_id'] = $tahesab->id;
|
||||
$row['tahesab_sync_status'] = $tahesab->sync_status
|
||||
?? (empty($tahesab->tahesab_id) ? TahesabSyncService::STATUS_PENDING : TahesabSyncService::STATUS_SUCCESS);
|
||||
|
||||
if (empty($tahesab->tahesab_id) || $row['tahesab_sync_status'] === TahesabSyncService::STATUS_FAILED) {
|
||||
$row['tahesab_failed_id'] = $tahesab->id;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* سینک یک ردیف دفترکل با حسابداری (فعلاً تهحساب).
|
||||
* اگر سند تهحساب مرتبط موجود باشد → retry؛ وگرنه از روی منبع (معامله) ساخته و ارسال میشود.
|
||||
*/
|
||||
public function sync(int $id)
|
||||
{
|
||||
if ($this->Config('send_to_tahesab') !== 'true') {
|
||||
throw new UnprocessableEntityHttpException('حسابداری تهحساب فعال نیست');
|
||||
}
|
||||
|
||||
$row = TransactionsAdmin::find($id);
|
||||
if (! $row) {
|
||||
throw new UnprocessableEntityHttpException('سند دفترکل یافت نشد');
|
||||
}
|
||||
|
||||
$tahesab = $this->findLinkedTahesab($row);
|
||||
|
||||
if ($tahesab) {
|
||||
$result = app(TahesabSyncService::class)->retry($tahesab);
|
||||
$status = ($result['ok'] ?? false) ? 200 : 422;
|
||||
|
||||
return response()->json($result, $status);
|
||||
}
|
||||
|
||||
// سند تهحساب وجود ندارد — اگر معامله است، از روی سفارش بساز و ارسال کن
|
||||
if ($row->order_id) {
|
||||
return $this->syncTradeFromOrder((int) $row->order_id);
|
||||
}
|
||||
|
||||
throw new UnprocessableEntityHttpException(
|
||||
'برای این ردیف سند تهحسابی یافت نشد و امکان ساخت خودکار آن وجود ندارد'
|
||||
);
|
||||
}
|
||||
|
||||
private function findLinkedTahesab(TransactionsAdmin $row): ?Tahesab
|
||||
{
|
||||
if ($row->withdraw_id) {
|
||||
$byWithdraw = Tahesab::where('withdraw_id', $row->withdraw_id)->orderByDesc('id')->first();
|
||||
if ($byWithdraw) {
|
||||
return $byWithdraw;
|
||||
}
|
||||
}
|
||||
|
||||
if ($row->order_id) {
|
||||
return Tahesab::where('order_id', $row->order_id)->orderByDesc('id')->first();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function syncTradeFromOrder(int $orderId)
|
||||
{
|
||||
$order = Order::find($orderId);
|
||||
if (! $order) {
|
||||
throw new UnprocessableEntityHttpException('سفارش مرتبط یافت نشد');
|
||||
}
|
||||
|
||||
$product = Product::find($order->product_id);
|
||||
if (! $product) {
|
||||
throw new UnprocessableEntityHttpException('محصول سفارش یافت نشد — امکان ساخت سند تهحساب نیست');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'customer_id' => $order->customer_id,
|
||||
'direction' => (int) $order->direction,
|
||||
'price' => $order->price,
|
||||
'total_price' => $order->total_price,
|
||||
'amount' => $order->amount,
|
||||
'pay_at' => $order->pay_at ?? $order->created_at,
|
||||
'product' => $product,
|
||||
'order' => ['id' => $order->id],
|
||||
];
|
||||
|
||||
$result = app(TahesabController::class)->storeTrade($data);
|
||||
$ok = $result !== false && ! is_string($result);
|
||||
|
||||
return response()->json([
|
||||
'ok' => $ok,
|
||||
'message' => $ok ? 'سند معامله در تهحساب ثبت شد' : (is_string($result) ? $result : 'ارسال به تهحساب ناموفق بود'),
|
||||
], $ok ? 200 : 422);
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$tallaPrice = Price::latest()->first();
|
||||
|
||||
@@ -44,7 +44,7 @@ class SingleOrderController extends Controller
|
||||
}
|
||||
$config = app(ConfigService::class);
|
||||
|
||||
$config->assertTradingOpen();
|
||||
// فیلتر ساعت معامله per-product (سراسری یا override محصول) پایینتر در assertProductTradingOpen اعمال میشود.
|
||||
|
||||
// throw new UnprocessableEntityHttpException('به دلیل نوسانات سفارشات بسته میباشد.');
|
||||
$transactionType = $data['direction'] == 1 ? "خرید" : "فروش";
|
||||
@@ -192,6 +192,15 @@ class SingleOrderController extends Controller
|
||||
$response['sell'] = $this->processSellTransaction($data, $wallet, $wallet_interface);
|
||||
}
|
||||
}
|
||||
// دفترکل داخلی همیشه ثبت شود — مستقل از اتصال به حسابداری بیرونی
|
||||
if (isset($data['order'])) {
|
||||
try {
|
||||
app(\App\Services\AccountingLedgerService::class)->recordTrade($data['order']);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->getString('send_to_tahesab', 'false') === 'true') {
|
||||
try {
|
||||
$data['tahesab'] = app(TahesabController::class)->storeTrade($data);
|
||||
|
||||
@@ -126,6 +126,17 @@ class ReportController extends Controller
|
||||
|
||||
protected function appendUpPrices(array $payload, bool $saveFullCache = false): array
|
||||
{
|
||||
// status: open | closed (خارج از ساعت کاری) | no_quote (مظنه در دسترس نیست)
|
||||
if (!isset($payload['status'])) {
|
||||
if (!empty($payload['closed'])) {
|
||||
$payload['status'] = 'closed';
|
||||
} elseif ((int) ($payload['price'] ?? 0) <= 0 && (int) ($payload['buy'] ?? 0) <= 0) {
|
||||
$payload['status'] = 'no_quote';
|
||||
} else {
|
||||
$payload['status'] = 'open';
|
||||
}
|
||||
}
|
||||
|
||||
$payload['up'] = $this->buildUpDownPrices();
|
||||
$payload['last_historical'] = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
|
||||
app(ProductDisplayPriceService::class)->cacheLivePricePayload($payload);
|
||||
@@ -387,6 +398,7 @@ class ReportController extends Controller
|
||||
$priceValue = $storedPrice;
|
||||
$priceSource = 'database_last';
|
||||
} else {
|
||||
// مظنه زنده نداریم و show_last_price خاموش است → نه «بسته»، بلکه «مظنه در دسترس نیست»
|
||||
return $this->appendUpPrices([
|
||||
'sell' => 0,
|
||||
'buy' => 0,
|
||||
@@ -395,7 +407,8 @@ class ReportController extends Controller
|
||||
'main_product' => $pr_price ?? 0,
|
||||
'montzar' => $montzar_price ?? 0,
|
||||
'zaarbox' => $zaarbox_price ?? 0,
|
||||
'closed' => true,
|
||||
'closed' => false,
|
||||
'status' => 'no_quote',
|
||||
'time' => now(),
|
||||
], $forceRefresh);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ class Product extends Model
|
||||
private static ?bool $directPriceColumnExists = null;
|
||||
private static ?bool $tradeByQuantityColumnExists = null;
|
||||
private static ?bool $inventoryColumnExists = null;
|
||||
private static ?bool $overrideTradingHoursColumnExists = null;
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
@@ -52,9 +53,30 @@ class Product extends Model
|
||||
if (!static::hasInventoryColumn()) {
|
||||
unset($product->attributes['inventory']);
|
||||
}
|
||||
if (!static::hasOverrideTradingHoursColumn()) {
|
||||
unset($product->attributes['override_trading_hours']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function hasOverrideTradingHoursColumn(): bool
|
||||
{
|
||||
if (static::$overrideTradingHoursColumnExists !== null) {
|
||||
return static::$overrideTradingHoursColumnExists;
|
||||
}
|
||||
|
||||
try {
|
||||
static::$overrideTradingHoursColumnExists = Schema::hasColumn(
|
||||
(new static)->getTable(),
|
||||
'override_trading_hours'
|
||||
);
|
||||
} catch (\Throwable) {
|
||||
static::$overrideTradingHoursColumnExists = false;
|
||||
}
|
||||
|
||||
return static::$overrideTradingHoursColumnExists;
|
||||
}
|
||||
|
||||
public static function hasPriceOffsetColumn(): bool
|
||||
{
|
||||
if (static::$priceOffsetColumnExists !== null) {
|
||||
@@ -253,6 +275,7 @@ class Product extends Model
|
||||
'open_sell', // Whether selling is open (boolean).
|
||||
'auto_open_at', // Whether selling is open (boolean).
|
||||
'auto_close_at', // Whether selling is open (boolean).
|
||||
'override_trading_hours', // روشن: بازه ساعت خود محصول جایگزین بازه سراسری میشود
|
||||
'kalhor_name', // The name of the Kalhor entity.
|
||||
'default_credit', // Default credit amount.
|
||||
'reading_method', // The method of reading.
|
||||
@@ -286,6 +309,7 @@ class Product extends Model
|
||||
'show_in_assets' => 'boolean',
|
||||
'trade_by_quantity' => 'boolean',
|
||||
'use_last_price_if_zero' => 'boolean',
|
||||
'override_trading_hours' => 'boolean',
|
||||
'for_barayand' => 'boolean', // Cast to boolean.
|
||||
'open_sell' => 'boolean', // Cast to boolean.
|
||||
'auto_sell' => 'boolean', // Cast to boolean (you mentioned this in your casts but it's missing in `$fillable`).
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Order;
|
||||
use App\Models\TransactionsAdmin;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* دفترکل حسابداری داخلی (transactions_admins).
|
||||
*
|
||||
* اصل: گزارشات باید همیشه کامل باشد — مستقل از اینکه اپ به حسابداری بیرونی (تهحساب و …) وصل است یا نه.
|
||||
* این سرویس تنها نقطهٔ متمرکز ثبت سند دفترکل برای معامله/کیفپول/پاداش است و idempotent عمل میکند.
|
||||
*/
|
||||
class AccountingLedgerService
|
||||
{
|
||||
/**
|
||||
* ثبت سند دفترکل برای معاملهٔ خرید/فروش مشتری.
|
||||
* idempotent بر اساس order_id — اگر قبلاً ثبت شده باشد همان را برمیگرداند.
|
||||
*/
|
||||
public function recordTrade(?Order $order): ?TransactionsAdmin
|
||||
{
|
||||
if (! $order || ! $order->id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$existing = TransactionsAdmin::where('order_id', $order->id)->first();
|
||||
if ($existing) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
try {
|
||||
return TransactionsAdmin::create([
|
||||
'customer_id' => $order->customer_id,
|
||||
'customer_to_id' => $order->customer_id,
|
||||
'order_id' => $order->id,
|
||||
'user_id' => $order->customer_id,
|
||||
'wallet' => null,
|
||||
'transaction_id' => null,
|
||||
'cash' => (int) round((float) ($order->total_price ?? 0)),
|
||||
'note' => $this->tradeNote((int) ($order->direction ?? 0)),
|
||||
'type' => 1,
|
||||
'gold' => (float) ($order->amount ?? 0),
|
||||
'balance_cash' => 0,
|
||||
'balance_gold' => 0,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
report($e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** توضیح سند معامله — همسان با desc تهحساب تا گزارشات و حسابداری یکی دیده شوند. */
|
||||
public function tradeNote(int $direction): string
|
||||
{
|
||||
return $direction === 1 ? 'خرید آبشده' : ($direction === 2 ? 'فروش آبشده' : 'معامله');
|
||||
}
|
||||
}
|
||||
@@ -580,21 +580,46 @@ class ConfigService
|
||||
return $current >= $openStr && $current <= $closeStr;
|
||||
}
|
||||
|
||||
/** ساعت کاری سراسری (close_price) + بازه auto_open_at/auto_close_at هر محصول */
|
||||
/**
|
||||
* آیا محصول بازه زمانی اختصاصی خودش را override کرده؟
|
||||
* فقط وقتی سوییچ override_trading_hours روشن باشد و بازه معتبری (شروع != پایان) داشته باشد.
|
||||
*/
|
||||
public function productOverridesTradingHours(Product $product): bool
|
||||
{
|
||||
if (!Product::hasOverrideTradingHoursColumn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!($product->override_trading_hours ?? false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasProductTradingWindow($product->auto_open_at, $product->auto_close_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* ساعت معامله یک محصول:
|
||||
* - اگر سوییچ override محصول روشن باشد → فقط بازه خود محصول حاکم است (سراسری نادیده گرفته میشود).
|
||||
* - در غیر این صورت → فیلتر سراسری close_price + startDay/endDay اعمال میشود.
|
||||
*/
|
||||
public function isProductWithinTradingHours(Product $product, ?Carbon $now = null): bool
|
||||
{
|
||||
if ($this->productOverridesTradingHours($product)) {
|
||||
return $this->isWithinTimeWindow($product->auto_open_at, $product->auto_close_at, $now);
|
||||
}
|
||||
|
||||
if ($this->isClosePriceEnabled() && $this->isOutsideTradingHours($now)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->isWithinTimeWindow($product->auto_open_at, $product->auto_close_at, $now);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function productTradingClosedMessage(Product $product): string
|
||||
{
|
||||
$title = trim((string) ($product->title ?? 'محصول'));
|
||||
|
||||
if ($this->hasProductTradingWindow($product->auto_open_at, $product->auto_close_at)) {
|
||||
if ($this->productOverridesTradingHours($product)) {
|
||||
$open = $this->formatProductTime($product->auto_open_at);
|
||||
$close = $this->formatProductTime($product->auto_close_at);
|
||||
|
||||
@@ -606,8 +631,6 @@ class ConfigService
|
||||
|
||||
public function assertProductTradingOpen(Product $product, ?Carbon $now = null): void
|
||||
{
|
||||
$this->assertTradingOpen($now);
|
||||
|
||||
if (!$this->isProductWithinTradingHours($product, $now)) {
|
||||
throw new UnprocessableEntityHttpException($this->productTradingClosedMessage($product));
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ class LivePriceCacheService
|
||||
'main_product' => 0,
|
||||
'montzar' => 0,
|
||||
'zaarbox' => 0,
|
||||
'closed' => true,
|
||||
'closed' => false,
|
||||
'status' => 'no_quote',
|
||||
'time' => now(),
|
||||
];
|
||||
} else {
|
||||
@@ -123,6 +124,7 @@ class LivePriceCacheService
|
||||
'montzar' => $montzarSource,
|
||||
'zaarbox' => $zaarboxSource,
|
||||
'closed' => false,
|
||||
'status' => 'open',
|
||||
'time' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?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('products')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('products', 'override_trading_hours')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('products', 'auto_close_at')) {
|
||||
$table->boolean('override_trading_hours')->default(false)->after('auto_close_at');
|
||||
} else {
|
||||
$table->boolean('override_trading_hours')->default(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('products') || ! Schema::hasColumn('products', 'override_trading_hours')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->dropColumn('override_trading_hours');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Backfill: هر معاملهٔ گذشته (orders) که هنوز سند دفترکل ندارد را به transactions_admins منتقل میکند.
|
||||
* تا گزارشات به دفترکل کامل تبدیل شود — مستقل از اتصال به حسابداری بیرونی.
|
||||
*
|
||||
* idempotent: order_idهایی که از قبل در transactions_admins هستند رد میشوند.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('orders') || ! Schema::hasTable('transactions_admins')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hasOrderId = Schema::hasColumn('transactions_admins', 'order_id');
|
||||
if (! $hasOrderId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = array_flip(
|
||||
DB::table('transactions_admins')->whereNotNull('order_id')->pluck('order_id')->all()
|
||||
);
|
||||
|
||||
$now = now();
|
||||
|
||||
DB::table('orders')
|
||||
->when(Schema::hasColumn('orders', 'sts'), function ($q) {
|
||||
// معاملات ناموفق (sts=3) را وارد دفترکل نکن
|
||||
$q->where(function ($inner) {
|
||||
$inner->whereNull('sts')->orWhere('sts', '!=', 3);
|
||||
});
|
||||
})
|
||||
->orderBy('id')
|
||||
->chunk(500, function ($orders) use (&$existing, $now) {
|
||||
$rows = [];
|
||||
foreach ($orders as $order) {
|
||||
if (isset($existing[$order->id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$direction = (int) ($order->direction ?? 0);
|
||||
$note = $direction === 1 ? 'خرید آبشده' : ($direction === 2 ? 'فروش آبشده' : 'معامله');
|
||||
|
||||
$rows[] = [
|
||||
'customer_id' => $order->customer_id,
|
||||
'customer_to_id' => $order->customer_id,
|
||||
'order_id' => $order->id,
|
||||
'user_id' => $order->customer_id,
|
||||
'wallet' => null,
|
||||
'transaction_id' => null,
|
||||
'cash' => (int) round((float) ($order->total_price ?? 0)),
|
||||
'note' => $note,
|
||||
'type' => 1,
|
||||
'gold' => (float) ($order->amount ?? 0),
|
||||
'balance_cash' => 0,
|
||||
'balance_gold' => 0,
|
||||
'created_at' => $order->created_at ?? $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
$existing[$order->id] = true;
|
||||
}
|
||||
|
||||
if ($rows !== []) {
|
||||
DB::table('transactions_admins')->insert($rows);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// برگشتپذیر نیست: تفکیک ردیفهای backfill از ردیفهای واقعی forward مطمئن نیست،
|
||||
// پس برای جلوگیری از حذف دادهٔ درست، down هیچ کاری نمیکند.
|
||||
}
|
||||
};
|
||||
@@ -134,6 +134,7 @@ Route::middleware('auth:user')->group(function (){
|
||||
|
||||
|
||||
Route::post('finance', [FinanceController::class, 'fall']);
|
||||
Route::post('finance/{id}/sync', [FinanceController::class, 'sync']);
|
||||
|
||||
|
||||
//dashboard
|
||||
|
||||
Reference in New Issue
Block a user