From 285a159762a33e33ab213d9b21dd5e24c62f4b03 Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 18 Jul 2026 17:37:41 +0330 Subject: [PATCH] fix profit value after each order 2 --- .../Controllers/Admin/OrderController.php | 2 + .../Single/SingleOrderController.php | 288 ++++++++++++++++-- app/Models/Order.php | 16 + app/Services/BalanceReportService.php | 89 ++++++ ..._000001_add_product_id_to_orders_table.php | 38 +++ ..._000002_add_fee_source_to_orders_table.php | 42 +++ ...0003_add_fee_breakdown_to_orders_table.php | 34 +++ .../sql/backfill_order_product_and_fees.sql | 28 ++ 8 files changed, 512 insertions(+), 25 deletions(-) create mode 100644 database/migrations/2026_07_18_000001_add_product_id_to_orders_table.php create mode 100644 database/migrations/2026_07_18_000002_add_fee_source_to_orders_table.php create mode 100644 database/migrations/2026_07_18_000003_add_fee_breakdown_to_orders_table.php create mode 100644 database/sql/backfill_order_product_and_fees.sql diff --git a/app/Http/Controllers/Admin/OrderController.php b/app/Http/Controllers/Admin/OrderController.php index 280d70c..dbaa456 100644 --- a/app/Http/Controllers/Admin/OrderController.php +++ b/app/Http/Controllers/Admin/OrderController.php @@ -17,6 +17,7 @@ class OrderController extends Controller $records = Order::with([ 'customer', + 'product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', 'transactions.product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', ]); TableEngine::create($records, $request->all()) @@ -43,6 +44,7 @@ class OrderController extends Controller $records = Order::with([ 'customer', + 'product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', 'transactions.product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', ])->where('type',2); TableEngine::create($records, $request->all()) diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index 0f5cdbc..427262b 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -118,9 +118,9 @@ class SingleOrderController extends Controller if ($data['direction'] == 1) { $data['service_charge_percentage'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_buy']) * $data['number']; - $data['difference'] = $data['fee'] = $this->resolveCashPriceDiff($config, $data['product'], 1); - $data['total_fee'] = $data['number'] * $data['fee']; $data['price_original'] = $this->resolveProductUnitBasePrice($data['product']); + $this->applyCashPriceDiff($config, $data['product'], 1, $data, (float) $data['price_original']); + $data['total_fee'] = $data['number'] * $data['fee']; $data['total_price_original'] = $data['product']['amount'] * ($data['price_original'] ?? 0); // Check if the method is cash @@ -136,9 +136,9 @@ class SingleOrderController extends Controller } // Check if the user wants to sell else if ($data['direction'] == 2) { $data['service_charge_percentage'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_sell']) * $data['number']; - $data['difference'] = $data['fee'] = $this->resolveCashPriceDiff($config, $data['product'], 2); - $data['total_fee'] = $data['number'] * $data['fee']; $data['price_original'] = $this->resolveProductUnitBasePrice($data['product']); + $this->applyCashPriceDiff($config, $data['product'], 2, $data, (float) $data['price_original']); + $data['total_fee'] = $data['number'] * $data['fee']; $data['total_price_original'] = $data['product']['amount'] * ($data['price_original'] ?? 0); $this->assertMinimumSellCash($data); @@ -164,14 +164,13 @@ class SingleOrderController extends Controller } $config->assertProductTradingOpen($data['product']); if ($data['direction'] == 1) { - $data['difference'] = $this->resolveCashPriceDiff($config, $data['product'], 1); + $this->applyCashPriceDiff($config, $data['product'], 1, $data, (float) ($data['price_original'] ?? 0)); $data['service_charge_percentage'] = $data['product']['percent_fee_buy']; } elseif ($data['direction'] == 2) { - $data['difference'] = $this->resolveCashPriceDiff($config, $data['product'], 2); + $this->applyCashPriceDiff($config, $data['product'], 2, $data, (float) ($data['price_original'] ?? 0)); $data['service_charge_percentage'] = $data['product']['percent_fee_sell']; } // fee اولیه از orderPriceDiffRial ممکن است با اختلاف واقعی محصول فرق کند - $data['fee'] = $data['difference']; $data['total_fee'] = $data['amount'] * $data['difference']; $data['product_id'] = $data['product']->id; // Check if the user wants to buy @@ -1160,29 +1159,160 @@ class SingleOrderController extends Controller } /** - * گلد علی / zaarbox / main_product: اختلاف سفارش = buy_fee/sell_fee نمایش (مثل /api/price/last). - * montzar و بقیه: همان cash_fee_buy/sell محصول (رفتار قبلی ابشده‌خانگی). + * اختلاف قیمت سفارش: + * - محصول با «قیمت اپ»: اختلاف پایه تنظیمات (sell_fee/buy_fee/fee_single) + تب اختلاف محصول + * - بدون قیمت اپ: فقط تب اختلاف محصول (cash_fee) — بدون ۱M تنظیمات اپ + * + * @return array{ + * amount: float, + * source: string, + * key: string, + * label: string, + * uses_app_price: bool, + * base_price: float, + * lines: list> + * } */ - private function resolveCashPriceDiff(ConfigService $config, Product $product, int $direction): float - { - $provider = $config->priceProvider(); - $alignWithDisplay = in_array($provider, ['zaarbox', 'main_product'], true); + private function resolveCashPriceDiffMeta( + ConfigService $config, + Product $product, + int $direction, + ?float $basePriceRial = null + ): array { + $display = app(ProductDisplayPriceService::class); + $usesAppPrice = $display->usesAppPrice($product); + $isBuy = $direction === 1; + $side = $isBuy ? 'خرید' : 'فروش'; - if ($alignWithDisplay) { - $display = $direction === 1 - ? $config->displayBuyPriceDiffRial() - : $config->displaySellPriceDiffRial(); + $base = (float) ($basePriceRial ?? 0); + if ($base <= 0) { + $base = (float) $this->resolveProductUnitBasePrice($product); + } + if ($base <= 0) { + $base = ProductPriceService::productBasePriceRial($product); + } - if ($display > 0) { - return $display; + $lines = []; + $amount = 0.0; + $keys = []; + + if ($usesAppPrice) { + $feeSingle = (float) $config->get('fee_single', 0); + if ($isBuy) { + $buyFee = (float) $config->get('buy_fee', $feeSingle); + $settingsAmt = $buyFee > 0 ? $buyFee : $feeSingle; + $settingsKey = $buyFee > 0 ? 'buy_fee' : 'fee_single'; + $settingsTitle = $buyFee > 0 + ? 'اختلاف پایه اپ · اضافه قیمت خرید (buy_fee)' + : 'اختلاف پایه اپ · اختلاف پایه (fee_single)'; + } else { + $sellFee = (float) $config->get('sell_fee', $feeSingle); + $settingsAmt = $sellFee > 0 ? $sellFee : $feeSingle; + $settingsKey = $sellFee > 0 ? 'sell_fee' : 'fee_single'; + $settingsTitle = $sellFee > 0 + ? 'اختلاف پایه اپ · کسر قیمت فروش (sell_fee)' + : 'اختلاف پایه اپ · اختلاف پایه (fee_single)'; + } + + if ($settingsAmt > 0) { + $amount += $settingsAmt; + $keys[] = $settingsKey; + $lines[] = [ + 'kind' => 'app_spread', + 'key' => $settingsKey, + 'label' => $settingsTitle, + 'is_percent' => false, + 'raw' => $settingsAmt, + 'per_unit' => $settingsAmt, + 'detail' => number_format($settingsAmt) . ' ریال / گرم', + ]; } } - return ProductPriceService::resolveCashFeeRial( - $product, - $direction, - ProductPriceService::productBasePriceRial($product) - ); + $productAmt = ProductPriceService::resolveCashFeeRial($product, $direction, $base); + $productKey = $isBuy ? 'cash_fee_buy' : 'cash_fee_sell'; + $isPercent = $isBuy + ? (bool) ($product->cash_fee_buy_is_percent ?? false) + : (bool) ($product->cash_fee_sell_is_percent ?? false); + $raw = (float) ($isBuy ? ($product->cash_fee_buy ?? 0) : ($product->cash_fee_sell ?? 0)); + + if ($raw != 0.0 || $productAmt > 0) { + $amount += $productAmt; + $keys[] = $productKey; + $rawLabel = $isPercent + ? (rtrim(rtrim(number_format($raw, 4, '.', ''), '0'), '.') . '%') + : (number_format($raw) . ' ریال'); + $detail = $isPercent + ? ($rawLabel . ' از ' . number_format($base) . ' ≈ ' . number_format($productAmt) . ' ریال / گرم') + : (number_format($productAmt) . ' ریال / گرم'); + + $lines[] = [ + 'kind' => 'product_spread', + 'key' => $productKey, + 'label' => 'تب اختلاف محصول · هزینه ' . $side, + 'is_percent' => $isPercent, + 'raw' => $raw, + 'percent_of' => $isPercent ? $base : null, + 'per_unit' => $productAmt, + 'detail' => $detail, + ]; + } + + $hasApp = collect($lines)->contains(fn ($l) => ($l['kind'] ?? '') === 'app_spread'); + $hasProduct = collect($lines)->contains(fn ($l) => ($l['kind'] ?? '') === 'product_spread'); + + if ($hasApp && $hasProduct) { + $source = 'app+product'; + $label = 'قیمت اپ + تب اختلاف محصول = ' . number_format($amount) . ' ریال / گرم'; + } elseif ($hasApp) { + $source = 'settings'; + $label = ($lines[0]['label'] ?? 'اختلاف پایه اپ') . ' = ' . number_format($amount) . ' ریال'; + } elseif ($hasProduct) { + $source = 'product'; + $label = ($lines[0]['label'] ?? 'تب اختلاف محصول') + . (isset($lines[0]['detail']) ? ' (' . $lines[0]['detail'] . ')' : ''); + } else { + $source = 'none'; + $label = 'بدون اختلاف قیمت'; + } + + return [ + 'amount' => $amount, + 'source' => $source, + 'key' => implode('+', $keys) ?: 'none', + 'label' => $label, + 'uses_app_price' => $usesAppPrice, + 'base_price' => $base, + 'lines' => $lines, + ]; + } + + private function resolveCashPriceDiff( + ConfigService $config, + Product $product, + int $direction, + ?float $basePriceRial = null + ): float { + return $this->resolveCashPriceDiffMeta($config, $product, $direction, $basePriceRial)['amount']; + } + + /** اختلاف + منبع + اقلام فاکتور را روی $data می‌نشاند. */ + private function applyCashPriceDiff( + ConfigService $config, + Product $product, + int $direction, + array &$data, + ?float $basePriceRial = null + ): void { + $meta = $this->resolveCashPriceDiffMeta($config, $product, $direction, $basePriceRial); + $data['difference'] = $meta['amount']; + $data['fee'] = $meta['amount']; + $data['fee_source'] = $meta['source']; + $data['fee_source_key'] = $meta['key']; + $data['fee_source_label'] = $meta['label']; + $data['_fee_spread_lines'] = $meta['lines']; + $data['_fee_uses_app_price'] = $meta['uses_app_price']; + $data['_fee_base_price'] = $meta['base_price']; } /** @@ -1201,12 +1331,28 @@ class SingleOrderController extends Controller return (int) (($product->price ?? 0) + ($product->price_profit ?? 0)); } + /** + * product_id سفارش را برای ذخیره روی جدول orders تثبیت کن. + */ + private function ensureOrderProductId(array &$data): void + { + $productId = (int) ($data['product_id'] ?? $data['abshode_id'] ?? 0); + if ($productId <= 0) { + $productId = (int) (app(ConfigService::class)->mainProductId() ?? 0); + } + if ($productId > 0) { + $data['product_id'] = $productId; + } + } + /** * سود هر گرم / سود کل را از قیمت نهایی و قیمت بدون سود همگام کن - * (بعد از نهایی شدن amount و اعمال difference روی price). + * و فاکتور اقلام (مثل فاکتور رستوران) را کامل کن. */ private function syncOrderProfitFields(array &$data): void { + $this->ensureOrderProductId($data); + $price = (float) ($data['price'] ?? 0); $priceOriginal = (float) ($data['price_original'] ?? 0); $amount = (float) ($data['amount'] ?? 0); @@ -1214,6 +1360,98 @@ class SingleOrderController extends Controller $data['fee'] = abs($price - $priceOriginal); $data['total_price_original'] = $amount * $priceOriginal; $data['total_fee'] = $amount * $data['fee']; + + $spreadLines = is_array($data['_fee_spread_lines'] ?? null) ? $data['_fee_spread_lines'] : []; + $receiptLines = []; + $spreadTotal = 0.0; + + foreach ($spreadLines as $line) { + $perUnit = (float) ($line['per_unit'] ?? 0); + $total = $perUnit * $amount; + $spreadTotal += $total; + $receiptLines[] = array_merge($line, [ + 'amount_units' => $amount, + 'total' => $total, + 'line_text' => ($line['detail'] ?? number_format($perUnit) . ' ریال / گرم') + . ($amount > 0 + ? (' × ' . rtrim(rtrim(number_format($amount, 4, '.', ''), '0'), '.') . ' گرم = ' . number_format($total) . ' ریال') + : ''), + ]); + } + + // اگر اقلام ذخیره نشده بود، حداقل یک خط از اختلاف واقعی بساز + if ($receiptLines === [] && $data['fee'] > 0) { + $receiptLines[] = [ + 'kind' => 'spread', + 'key' => $data['fee_source_key'] ?? 'fee', + 'label' => $data['fee_source_label'] ?? 'اختلاف قیمت', + 'is_percent' => false, + 'per_unit' => $data['fee'], + 'amount_units' => $amount, + 'total' => $data['total_fee'], + 'detail' => number_format($data['fee']) . ' ریال / گرم', + 'line_text' => number_format($data['fee']) . ' ریال / گرم × ' + . rtrim(rtrim(number_format($amount, 4, '.', ''), '0'), '.') + . ' گرم = ' . number_format($data['total_fee']) . ' ریال', + ]; + $spreadTotal = (float) $data['total_fee']; + } + + $scGold = (float) ($data['service_charge'] ?? 0); + $scRial = (float) ($data['service_charge_price'] ?? 0); + $scPct = (float) ($data['service_charge_percentage'] ?? 0); + + if ($scGold > 0 || $scRial > 0) { + $goldLabel = rtrim(rtrim(number_format($scGold, 4, '.', ''), '0'), '.') . ' گرم'; + $detailParts = []; + if ($scPct > 0) { + $detailParts[] = rtrim(rtrim(number_format($scPct, 4, '.', ''), '0'), '.') . '%'; + } + if ($scGold > 0 && $priceOriginal > 0) { + $detailParts[] = $goldLabel . ' × ' . number_format($priceOriginal) . ' = ' . number_format($scRial) . ' ریال'; + } elseif ($scRial > 0 && $priceOriginal > 0) { + $goldEquiv = $scRial / $priceOriginal; + $detailParts[] = number_format($scRial) . ' ریال ≈ ' + . rtrim(rtrim(number_format($goldEquiv, 4, '.', ''), '0'), '.') . ' گرم'; + } else { + $detailParts[] = number_format($scRial) . ' ریال'; + } + + $receiptLines[] = [ + 'kind' => 'service_charge', + 'key' => 'service_charge', + 'label' => 'کارمزد معامله', + 'is_percent' => $scPct > 0, + 'raw' => $scPct, + 'gold' => $scGold, + 'rial' => $scRial, + 'per_unit' => $amount > 0 ? ($scRial / $amount) : 0, + 'amount_units' => $amount, + 'total' => $scRial, + 'detail' => implode(' · ', $detailParts), + 'line_text' => implode(' · ', $detailParts), + ]; + } + + $grandTotal = $spreadTotal + $scRial; + $data['fee_breakdown'] = [ + 'uses_app_price' => (bool) ($data['_fee_uses_app_price'] ?? false), + 'amount_units' => $amount, + 'price_original' => $priceOriginal, + 'price_customer' => $price, + 'spread_per_unit' => (float) $data['fee'], + 'spread_total' => $spreadTotal, + 'service_charge_gold' => $scGold, + 'service_charge_rial' => $scRial, + 'grand_total' => $grandTotal, + 'lines' => $receiptLines, + ]; + + unset( + $data['_fee_spread_lines'], + $data['_fee_uses_app_price'], + $data['_fee_base_price'] + ); } private function bonusGift($customer, $referrer) diff --git a/app/Models/Order.php b/app/Models/Order.php index dc02351..b6e6f99 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -12,6 +12,7 @@ class Order extends Model use HasFactory,Selector; protected $fillable = [ 'customer_id', + 'product_id', 'sts', 'direction', 'amount',//میزان طلا @@ -22,6 +23,10 @@ class Order extends Model 'total_price_original',//قیمت کل بدون سود 'fee',//سود هر گرم 'total_fee',//سود کل + 'fee_source',//settings|product|app+product|none + 'fee_source_key',//sell_fee|buy_fee|fee_single|cash_fee_sell|... + 'fee_source_label', + 'fee_breakdown',//فاکتور اقلام سود (JSON) 'service_charge_percentage',//درصد کارمزد 'service_charge',//میزان کارمزد 'service_charge_price',//مبلغ کارمزد به ریال @@ -33,6 +38,11 @@ class Order extends Model 'pay_at',//تاریخ انجام معامله در پنل ادمین 'price_id',// ]; + + protected $casts = [ + 'fee_breakdown' => 'array', + ]; + static $STS = [ 1 => 'باز', 2 => 'موفق', @@ -58,6 +68,12 @@ class Order extends Model public function customer(){ return $this->belongsTo(Customer::class); } + + public function product() + { + return $this->belongsTo(Product::class); + } + public function transactions(){ return $this->hasMany(Transaction::class); } diff --git a/app/Services/BalanceReportService.php b/app/Services/BalanceReportService.php index 116acb5..3bf04c0 100644 --- a/app/Services/BalanceReportService.php +++ b/app/Services/BalanceReportService.php @@ -605,6 +605,31 @@ class BalanceReportService $metalId = $this->resolveOrderMetalId($row); $metalLabel = $this->metalLabel($metalId); $price = (float) ($this->lineValue($row, 'price') ?? 0); + $fee = (float) ($this->lineValue($row, 'fee') ?? 0); + $totalFee = (float) ($this->lineValue($row, 'total_fee') ?? 0); + $direction = (int) ($this->lineValue($row, 'direction') ?? 0); + + $productId = (int) ($this->lineValue($row, 'product_id') ?? 0); + if ($productId <= 0 && $product !== null) { + $productId = (int) ($product->id ?? 0); + } + $productTitle = $product !== null ? trim((string) ($product->title ?? '')) : ''; + + $storedSource = trim((string) ($this->lineValue($row, 'fee_source') ?? '')); + $storedLabel = trim((string) ($this->lineValue($row, 'fee_source_label') ?? '')); + $storedKey = trim((string) ($this->lineValue($row, 'fee_source_key') ?? '')); + $storedBreakdown = $this->lineValue($row, 'fee_breakdown'); + if (is_string($storedBreakdown) && $storedBreakdown !== '') { + $decoded = json_decode($storedBreakdown, true); + $storedBreakdown = is_array($decoded) ? $decoded : null; + } elseif (! is_array($storedBreakdown)) { + $storedBreakdown = null; + } + $feeSource = $storedSource !== '' ? $storedSource : $this->resolveOrderFeeSourceKey($product); + $feeSourceLabel = $storedLabel !== '' + ? $storedLabel + : $this->resolveOrderFeeSourceLabel($direction, $feeSource); + $feeSourceKey = $storedKey !== '' ? $storedKey : null; $metalPrices = []; foreach ($catalogMetalIds as $mid) { @@ -618,6 +643,14 @@ class BalanceReportService 'order_metal_id' => $metalId, 'order_metal_label' => $metalLabel, 'metal_prices' => $metalPrices, + 'product_id' => $productId > 0 ? $productId : null, + 'product_title' => $productTitle !== '' ? $productTitle : null, + 'fee_source' => $feeSource, + 'fee_source_key' => $feeSourceKey, + 'fee_source_label' => $feeSourceLabel, + 'fee_breakdown' => $storedBreakdown, + 'fee_display' => abs($fee), + 'total_fee_display' => abs($totalFee), ]; foreach ($catalogMetalIds as $mid) { @@ -639,6 +672,44 @@ class BalanceReportService } } + /** settings | product | app+product — هم‌تراز resolveCashPriceDiffMeta در SingleOrderController */ + public function resolveOrderFeeSourceKey(?object $product = null): string + { + if ($product instanceof Product) { + if (app(ProductDisplayPriceService::class)->usesAppPrice($product)) { + return 'app+product'; + } + + return 'product'; + } + + $provider = $this->configService->priceProvider(); + if (in_array($provider, ['zaarbox', 'main_product'], true)) { + return 'settings'; + } + + return 'product'; + } + + public function resolveOrderFeeSourceLabel(int $direction, ?string $feeSource = null): string + { + $source = $feeSource ?? $this->resolveOrderFeeSourceKey(); + if ($source === 'app+product') { + return $direction === 2 + ? 'قیمت اپ (sell_fee) + تب اختلاف محصول' + : 'قیمت اپ (buy_fee) + تب اختلاف محصول'; + } + if ($source === 'settings') { + return $direction === 2 + ? 'تنظیمات سراسری (sell_fee / fee_single)' + : 'تنظیمات سراسری (buy_fee / fee_single)'; + } + + return $direction === 2 + ? 'هزینه فروش محصول (cash_fee_sell)' + : 'هزینه خرید محصول (cash_fee_buy)'; + } + public function resolveOrderAmountUnit(?object $product): string { if ($product === null) { @@ -670,6 +741,24 @@ class BalanceReportService private function resolveOrderProduct(mixed $order): ?object { + $embedded = $this->lineValue($order, 'product'); + if (is_object($embedded) && ! empty($embedded->title ?? null)) { + return $embedded; + } + if (is_array($embedded) && ! empty($embedded['title'])) { + return (object) $embedded; + } + + $productId = (int) ($this->lineValue($order, 'product_id') ?? 0); + if ($productId > 0) { + $byId = Product::query() + ->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price']) + ->find($productId); + if ($byId) { + return $byId; + } + } + $transactions = $this->lineValue($order, 'transactions'); if ($transactions instanceof \Illuminate\Support\Collection) { $transactions = $transactions->all(); diff --git a/database/migrations/2026_07_18_000001_add_product_id_to_orders_table.php b/database/migrations/2026_07_18_000001_add_product_id_to_orders_table.php new file mode 100644 index 0000000..d55de9b --- /dev/null +++ b/database/migrations/2026_07_18_000001_add_product_id_to_orders_table.php @@ -0,0 +1,38 @@ +unsignedBigInteger('product_id')->nullable()->after('customer_id'); + } else { + $table->unsignedBigInteger('product_id')->nullable(); + } + } + }); + } + + public function down(): void + { + if (! Schema::hasTable('orders')) { + return; + } + + Schema::table('orders', function (Blueprint $table) { + if (Schema::hasColumn('orders', 'product_id')) { + $table->dropColumn('product_id'); + } + }); + } +}; diff --git a/database/migrations/2026_07_18_000002_add_fee_source_to_orders_table.php b/database/migrations/2026_07_18_000002_add_fee_source_to_orders_table.php new file mode 100644 index 0000000..15cb9b5 --- /dev/null +++ b/database/migrations/2026_07_18_000002_add_fee_source_to_orders_table.php @@ -0,0 +1,42 @@ +string('fee_source', 32)->nullable()->after('total_fee'); + } + if (! Schema::hasColumn('orders', 'fee_source_key')) { + $table->string('fee_source_key', 64)->nullable()->after('fee_source'); + } + if (! Schema::hasColumn('orders', 'fee_source_label')) { + $table->string('fee_source_label', 255)->nullable()->after('fee_source_key'); + } + }); + } + + public function down(): void + { + if (! Schema::hasTable('orders')) { + return; + } + + Schema::table('orders', function (Blueprint $table) { + foreach (['fee_source_label', 'fee_source_key', 'fee_source'] as $col) { + if (Schema::hasColumn('orders', $col)) { + $table->dropColumn($col); + } + } + }); + } +}; diff --git a/database/migrations/2026_07_18_000003_add_fee_breakdown_to_orders_table.php b/database/migrations/2026_07_18_000003_add_fee_breakdown_to_orders_table.php new file mode 100644 index 0000000..9e2a7e5 --- /dev/null +++ b/database/migrations/2026_07_18_000003_add_fee_breakdown_to_orders_table.php @@ -0,0 +1,34 @@ +json('fee_breakdown')->nullable()->after('fee_source_label'); + } + }); + } + + public function down(): void + { + if (! Schema::hasTable('orders')) { + return; + } + + Schema::table('orders', function (Blueprint $table) { + if (Schema::hasColumn('orders', 'fee_breakdown')) { + $table->dropColumn('fee_breakdown'); + } + }); + } +}; diff --git a/database/sql/backfill_order_product_and_fees.sql b/database/sql/backfill_order_product_and_fees.sql new file mode 100644 index 0000000..33a9709 --- /dev/null +++ b/database/sql/backfill_order_product_and_fees.sql @@ -0,0 +1,28 @@ +-- Backfill helpers for orders (run manually after migrate; test on one row first). +-- 1) product_id from first related transaction that has product_id +UPDATE orders o +INNER JOIN ( + SELECT order_id, MIN(product_id) AS product_id + FROM transactions + WHERE order_id IS NOT NULL + AND product_id IS NOT NULL + AND product_id > 0 + GROUP BY order_id +) t ON t.order_id = o.id +SET o.product_id = t.product_id +WHERE o.product_id IS NULL; + +-- 2) Recalc fee / total_fee / total_price_original from stored prices +-- Example dry-run for one order: +-- SELECT id, fee, total_fee, +-- ABS(price - price_original) AS fee_new, +-- ABS(amount * (price - price_original)) AS total_fee_new +-- FROM orders WHERE id = ?; + +UPDATE orders +SET + fee = ABS(price - price_original), + total_fee = ABS(amount * (price - price_original)), + total_price_original = amount * price_original +WHERE price_original IS NOT NULL + AND amount IS NOT NULL;