fix profit value after each order 2

This commit is contained in:
2026-07-18 17:37:41 +03:30
parent b66cd5e8f9
commit 285a159762
8 changed files with 512 additions and 25 deletions
@@ -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())
@@ -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<array<string, mixed>>
* }
*/
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)
+16
View File
@@ -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);
}
+89
View File
@@ -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();
@@ -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('orders')) {
return;
}
Schema::table('orders', function (Blueprint $table) {
if (! Schema::hasColumn('orders', 'product_id')) {
if (Schema::hasColumn('orders', 'customer_id')) {
$table->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');
}
});
}
};
@@ -0,0 +1,42 @@
<?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('orders')) {
return;
}
Schema::table('orders', function (Blueprint $table) {
if (! Schema::hasColumn('orders', 'fee_source')) {
$table->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);
}
}
});
}
};
@@ -0,0 +1,34 @@
<?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('orders')) {
return;
}
Schema::table('orders', function (Blueprint $table) {
if (! Schema::hasColumn('orders', 'fee_breakdown')) {
$table->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');
}
});
}
};
@@ -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;