added rahimi
This commit is contained in:
@@ -746,4 +746,37 @@ class DashboardController extends Controller
|
|||||||
|
|
||||||
return response()->json(['deleted' => true]);
|
return response()->json(['deleted' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تطبیق دفتر اکانت روی نود همکار (Montzar) با سفارشهای مچشده — نه تهحساب.
|
||||||
|
*/
|
||||||
|
public function montzarReconcile(Request $request)
|
||||||
|
{
|
||||||
|
$config = app(ConfigService::class);
|
||||||
|
if (!$config->usesMontzarPricing()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'این گزارش فقط وقتی منبع قیمت montzar است در دسترس است.',
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$startDate = $request->input('startDate')
|
||||||
|
?: now()->subDays(30)->toDateString();
|
||||||
|
$endDate = $request->input('endDate')
|
||||||
|
?: now()->toDateString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$payload = app(\App\Services\MontzarReconcileService::class)
|
||||||
|
->reconcile((string) $startDate, (string) $endDate);
|
||||||
|
} catch (\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e) {
|
||||||
|
throw $e;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'خواندن دفتر همکار ناموفق بود: ' . $e->getMessage(),
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($payload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,560 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Order;
|
||||||
|
use App\Models\TransactionsAdmin;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Hekmatinasser\Verta\Verta;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تطبیق دفتر اکانت روی نود همکار (Montzar) با سفارشهای مچشدهٔ اپ.
|
||||||
|
* ربطی به تهحساب / زرین / پرگار ندارد.
|
||||||
|
*/
|
||||||
|
class MontzarReconcileService
|
||||||
|
{
|
||||||
|
private const GOLD_EPS = 0.002;
|
||||||
|
private const RIAL_EPS = 5000;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private MontzarService $montzar,
|
||||||
|
private ConfigService $config
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function reconcile(string $startDate, string $endDate): array
|
||||||
|
{
|
||||||
|
$start = Carbon::parse($startDate)->startOfDay();
|
||||||
|
$end = Carbon::parse($endDate)->endOfDay();
|
||||||
|
|
||||||
|
$mandehRaw = $this->montzar->fetchColleagueLedgerMandeh();
|
||||||
|
$mandeh = $this->normalizeMandeh($mandehRaw);
|
||||||
|
|
||||||
|
$colleagueRows = $this->montzar->fetchColleagueLedgerTable(
|
||||||
|
$start->toDateString(),
|
||||||
|
$end->toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
$classified = [];
|
||||||
|
foreach ($colleagueRows as $index => $row) {
|
||||||
|
if (!is_array($row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$classified[] = $this->classifyColleagueRow($row, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
$localOrders = $this->loadMatchedLocalOrders($start, $end);
|
||||||
|
$localGifts = $this->loadLocalGifts($start, $end);
|
||||||
|
|
||||||
|
[$matchedPairs, $diffs, $usedOrderIds, $usedGiftIds] = $this->matchSiteRows(
|
||||||
|
$classified,
|
||||||
|
$localOrders,
|
||||||
|
$localGifts
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($classified as $row) {
|
||||||
|
if (($row['kind'] ?? '') === 'site_buy' || ($row['kind'] ?? '') === 'site_sell') {
|
||||||
|
if (!empty($row['_matched'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$diffs[] = [
|
||||||
|
'code' => 'colleague_site_only',
|
||||||
|
'reason' => 'سند «از سایت» روی دفتر همکار هست ولی سفارش مچشدهٔ متناظر در اپ پیدا نشد.',
|
||||||
|
'colleague' => $this->publicColleagueRow($row),
|
||||||
|
'local' => null,
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($row['_matched'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($row['kind'], ['settlement', 'physical', 'gift', 'other'], true)) {
|
||||||
|
$diffs[] = [
|
||||||
|
'code' => $this->diffCodeForKind($row['kind']),
|
||||||
|
'reason' => $this->reasonForKind($row['kind'], $row['desc'] ?? ''),
|
||||||
|
'colleague' => $this->publicColleagueRow($row),
|
||||||
|
'local' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($localOrders as $order) {
|
||||||
|
if (isset($usedOrderIds[(int) $order['id']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$diffs[] = [
|
||||||
|
'code' => 'local_order_only',
|
||||||
|
'reason' => 'سفارش مچشده در اپ هست ولی در ریز دفتر همکار (بازه) پیدا نشد.',
|
||||||
|
'colleague' => null,
|
||||||
|
'local' => $order,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($localGifts as $gift) {
|
||||||
|
if (isset($usedGiftIds[(int) $gift['id']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$diffs[] = [
|
||||||
|
'code' => 'gift_cover',
|
||||||
|
'reason' => 'پاداش در اپ ثبت شده؛ سند متناظر پوشش روی دفتر همکار در بازه جور نشد (یا خرید پوشش خاموش بوده).',
|
||||||
|
'colleague' => null,
|
||||||
|
'local' => $gift,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$colleagueSite = $this->sumColleagueSite($classified);
|
||||||
|
$localSite = $this->sumLocalOrders($localOrders);
|
||||||
|
|
||||||
|
$lastRemain = null;
|
||||||
|
if ($classified !== []) {
|
||||||
|
$last = $classified[array_key_last($classified)];
|
||||||
|
$lastRemain = [
|
||||||
|
'gold' => (float) ($last['remain_gold'] ?? 0),
|
||||||
|
'rial' => (float) ($last['remain_rial'] ?? 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'mandeh' => $mandeh,
|
||||||
|
'last_table_remain' => $lastRemain,
|
||||||
|
'summary' => [
|
||||||
|
'colleague_site' => $colleagueSite,
|
||||||
|
'local_matched' => $localSite,
|
||||||
|
'diff_site_gold' => round(
|
||||||
|
($colleagueSite['net_gold'] ?? 0) - ($localSite['net_gold'] ?? 0),
|
||||||
|
3
|
||||||
|
),
|
||||||
|
'diff_site_rial' => (int) round(
|
||||||
|
($colleagueSite['net_rial'] ?? 0) - ($localSite['net_rial'] ?? 0)
|
||||||
|
),
|
||||||
|
'matched_count' => count($matchedPairs),
|
||||||
|
'diff_count' => count($diffs),
|
||||||
|
'colleague_row_count' => count($classified),
|
||||||
|
'local_order_count' => count($localOrders),
|
||||||
|
],
|
||||||
|
'colleague_rows' => array_map(fn ($r) => $this->publicColleagueRow($r), $classified),
|
||||||
|
'local_orders' => array_values($localOrders),
|
||||||
|
'local_gifts' => array_values($localGifts),
|
||||||
|
'matched' => $matchedPairs,
|
||||||
|
'diffs' => $diffs,
|
||||||
|
'range' => [
|
||||||
|
'start' => $start->toDateTimeString(),
|
||||||
|
'end' => $end->toDateTimeString(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<array<string, mixed>> $raw
|
||||||
|
* @return array{gold: float, cash: float, gold_unit: string, cash_unit: string, items: list<array<string, mixed>>}
|
||||||
|
*/
|
||||||
|
private function normalizeMandeh(array $raw): array
|
||||||
|
{
|
||||||
|
$gold = 0.0;
|
||||||
|
$cash = 0.0;
|
||||||
|
$goldUnit = 'گرم';
|
||||||
|
$cashUnit = 'ریال';
|
||||||
|
|
||||||
|
foreach ($raw as $item) {
|
||||||
|
if (!is_array($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$eng = strtolower((string) ($item['name_eng'] ?? ''));
|
||||||
|
$value = (float) ($item['value'] ?? 0);
|
||||||
|
if ($eng === 'gold' || ($item['name_fa'] ?? '') === 'طلا') {
|
||||||
|
$gold = $value;
|
||||||
|
$goldUnit = (string) ($item['unit'] ?? 'گرم');
|
||||||
|
}
|
||||||
|
if ($eng === 'cash' || ($item['name_fa'] ?? '') === 'ریال') {
|
||||||
|
$cash = $value;
|
||||||
|
$cashUnit = (string) ($item['unit'] ?? 'ریال');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'gold' => $gold,
|
||||||
|
'cash' => $cash,
|
||||||
|
'gold_abs' => abs($gold),
|
||||||
|
'cash_abs' => abs($cash),
|
||||||
|
'gold_state' => $gold < 0 ? 'بد' : ($gold > 0 ? 'بس' : 'صفر'),
|
||||||
|
'rial_state' => $cash < 0 ? 'بد' : ($cash > 0 ? 'بس' : 'صفر'),
|
||||||
|
'gold_unit' => $goldUnit,
|
||||||
|
'cash_unit' => $cashUnit,
|
||||||
|
'items' => $raw,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $row
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function classifyColleagueRow(array $row, int $index): array
|
||||||
|
{
|
||||||
|
$desc = trim((string) ($row['desc'] ?? ''));
|
||||||
|
$bedGold = $this->num($row['bedGold'] ?? 0);
|
||||||
|
$besGold = $this->num($row['besGold'] ?? 0);
|
||||||
|
$bedRial = $this->num($row['bedRial'] ?? 0);
|
||||||
|
$besRial = $this->num($row['besRial'] ?? 0);
|
||||||
|
|
||||||
|
$kind = 'other';
|
||||||
|
$siteDirection = null; // 1=فروش همکار به اپ (=خرید مشتری)، 2=خرید همکار از اپ (=فروش مشتری)
|
||||||
|
|
||||||
|
$fromSite = str_contains($desc, 'سايت') || str_contains($desc, 'سایت');
|
||||||
|
if ($fromSite && (str_contains($desc, 'فروش') || str_starts_with($desc, 'فروش'))) {
|
||||||
|
$kind = 'site_sell';
|
||||||
|
$siteDirection = 1;
|
||||||
|
} elseif ($fromSite && (str_contains($desc, 'خريد') || str_contains($desc, 'خرید'))) {
|
||||||
|
$kind = 'site_buy';
|
||||||
|
$siteDirection = 2;
|
||||||
|
} elseif ($desc === '') {
|
||||||
|
$kind = 'settlement';
|
||||||
|
} elseif (str_contains($desc, 'پرداخت')) {
|
||||||
|
$kind = 'physical';
|
||||||
|
} elseif (str_contains($desc, 'پاداش') || str_contains($desc, 'ثبت نام') || str_contains($desc, 'معرفی')) {
|
||||||
|
$kind = 'gift';
|
||||||
|
}
|
||||||
|
|
||||||
|
$gregorianDate = $this->colleagueDateToYmd((string) ($row['date'] ?? ''));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'index' => $index,
|
||||||
|
'id' => $row['id'] ?? null,
|
||||||
|
'doc_id' => $row['docId'] ?? null,
|
||||||
|
'date' => $row['date'] ?? null,
|
||||||
|
'date_ymd' => $gregorianDate,
|
||||||
|
'desc' => $desc,
|
||||||
|
'kind' => $kind,
|
||||||
|
'site_direction' => $siteDirection,
|
||||||
|
'bed_gold' => $bedGold,
|
||||||
|
'bes_gold' => $besGold,
|
||||||
|
'bed_rial' => $bedRial,
|
||||||
|
'bes_rial' => $besRial,
|
||||||
|
'net_gold' => round($besGold - $bedGold, 3),
|
||||||
|
'net_rial' => round($besRial - $bedRial, 0),
|
||||||
|
'remain_gold' => $this->num($row['remainGold'] ?? 0),
|
||||||
|
'remain_rial' => $this->num($row['remainRial'] ?? 0),
|
||||||
|
'gold_state' => $row['goldState'] ?? null,
|
||||||
|
'rial_state' => $row['rialState'] ?? null,
|
||||||
|
'_matched' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function loadMatchedLocalOrders(Carbon $start, Carbon $end): array
|
||||||
|
{
|
||||||
|
$query = Order::query()
|
||||||
|
->with(['customer:id,name,mobile'])
|
||||||
|
->where('sts', 2)
|
||||||
|
->whereBetween('created_at', [$start, $end])
|
||||||
|
->orderBy('id');
|
||||||
|
|
||||||
|
if (Schema::hasColumn('orders', 'provider_id')) {
|
||||||
|
$query->where(function ($q) {
|
||||||
|
$q->whereNotNull('provider_id');
|
||||||
|
if (Schema::hasColumn('orders', 'total_price_montzar')) {
|
||||||
|
$q->orWhereNotNull('total_price_montzar');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} elseif (Schema::hasColumn('orders', 'total_price_montzar')) {
|
||||||
|
$query->whereNotNull('total_price_montzar');
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->get()->map(function (Order $order) {
|
||||||
|
$direction = (int) $order->direction;
|
||||||
|
$amount = (float) $order->amount;
|
||||||
|
$cash = (float) ($order->total_price ?? $order->payment_amount ?? 0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => (int) $order->id,
|
||||||
|
'type' => 'order',
|
||||||
|
'direction' => $direction,
|
||||||
|
'direction_label' => Order::$DIRECTION[$direction] ?? (string) $direction,
|
||||||
|
'amount' => round($amount, 3),
|
||||||
|
'cash' => (int) round($cash),
|
||||||
|
'provider_id' => $order->provider_id ?? null,
|
||||||
|
'customer_id' => (int) $order->customer_id,
|
||||||
|
'customer_name' => $order->customer->name ?? null,
|
||||||
|
'created_at' => optional($order->created_at)?->toDateTimeString(),
|
||||||
|
'date_ymd' => optional($order->created_at)?->toDateString(),
|
||||||
|
];
|
||||||
|
})->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function loadLocalGifts(Carbon $start, Carbon $end): array
|
||||||
|
{
|
||||||
|
return TransactionsAdmin::query()
|
||||||
|
->whereBetween('created_at', [$start, $end])
|
||||||
|
->whereIn('note', ['پاداش بابت معرفی', 'پاداش بابت ثبت نام'])
|
||||||
|
->where('gold', '>', 0)
|
||||||
|
->orderBy('id')
|
||||||
|
->get()
|
||||||
|
->map(function (TransactionsAdmin $row) {
|
||||||
|
return [
|
||||||
|
'id' => (int) $row->id,
|
||||||
|
'type' => 'gift',
|
||||||
|
'direction' => 1,
|
||||||
|
'direction_label' => 'خرید پوشش پاداش',
|
||||||
|
'amount' => round((float) $row->gold, 3),
|
||||||
|
'cash' => 0,
|
||||||
|
'note' => $row->note,
|
||||||
|
'customer_id' => (int) ($row->customer_to_id ?: $row->customer_id),
|
||||||
|
'created_at' => optional($row->created_at)?->toDateTimeString(),
|
||||||
|
'date_ymd' => optional($row->created_at)?->toDateString(),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<array<string, mixed>> $classified
|
||||||
|
* @param list<array<string, mixed>> $localOrders
|
||||||
|
* @param list<array<string, mixed>> $localGifts
|
||||||
|
* @return array{0: list<array<string, mixed>>, 1: list<array<string, mixed>>, 2: array<int, true>, 3: array<int, true>}
|
||||||
|
*/
|
||||||
|
private function matchSiteRows(array &$classified, array $localOrders, array $localGifts): array
|
||||||
|
{
|
||||||
|
$matchedPairs = [];
|
||||||
|
$diffs = [];
|
||||||
|
$usedOrders = [];
|
||||||
|
$usedGifts = [];
|
||||||
|
|
||||||
|
$ordersByDirDate = [];
|
||||||
|
foreach ($localOrders as $order) {
|
||||||
|
$key = ((int) $order['direction']) . '|' . ($order['date_ymd'] ?? '');
|
||||||
|
$ordersByDirDate[$key][] = $order;
|
||||||
|
}
|
||||||
|
|
||||||
|
$giftsByDate = [];
|
||||||
|
foreach ($localGifts as $gift) {
|
||||||
|
$giftsByDate[$gift['date_ymd'] ?? ''][] = $gift;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($classified as &$row) {
|
||||||
|
if (($row['kind'] ?? '') === 'gift') {
|
||||||
|
$day = $row['date_ymd'] ?? '';
|
||||||
|
$candidates = $giftsByDate[$day] ?? [];
|
||||||
|
$found = null;
|
||||||
|
foreach ($candidates as $gift) {
|
||||||
|
if (isset($usedGifts[(int) $gift['id']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($this->goldClose((float) $gift['amount'], abs((float) $row['net_gold']))) {
|
||||||
|
$found = $gift;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($found) {
|
||||||
|
$usedGifts[(int) $found['id']] = true;
|
||||||
|
$row['_matched'] = true;
|
||||||
|
$matchedPairs[] = [
|
||||||
|
'code' => 'matched',
|
||||||
|
'colleague' => $this->publicColleagueRow($row),
|
||||||
|
'local' => $found,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($row['kind'] ?? '') !== 'site_buy' && ($row['kind'] ?? '') !== 'site_sell') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dir = (int) ($row['site_direction'] ?? 0);
|
||||||
|
$day = $row['date_ymd'] ?? '';
|
||||||
|
$key = $dir . '|' . $day;
|
||||||
|
$candidates = $ordersByDirDate[$key] ?? [];
|
||||||
|
|
||||||
|
$targetGold = abs((float) $row['net_gold']);
|
||||||
|
$targetRial = abs((float) $row['net_rial']);
|
||||||
|
|
||||||
|
$found = null;
|
||||||
|
foreach ($candidates as $order) {
|
||||||
|
if (isset($usedOrders[(int) $order['id']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
$this->goldClose((float) $order['amount'], $targetGold)
|
||||||
|
&& $this->rialClose((float) $order['cash'], $targetRial)
|
||||||
|
) {
|
||||||
|
$found = $order;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback: فقط گرم در همان روز
|
||||||
|
if (!$found) {
|
||||||
|
foreach ($candidates as $order) {
|
||||||
|
if (isset($usedOrders[(int) $order['id']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($this->goldClose((float) $order['amount'], $targetGold)) {
|
||||||
|
$found = $order;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($found) {
|
||||||
|
$usedOrders[(int) $found['id']] = true;
|
||||||
|
$row['_matched'] = true;
|
||||||
|
$matchedPairs[] = [
|
||||||
|
'code' => 'matched',
|
||||||
|
'colleague' => $this->publicColleagueRow($row),
|
||||||
|
'local' => $found,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
|
||||||
|
return [$matchedPairs, $diffs, $usedOrders, $usedGifts];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<array<string, mixed>> $classified
|
||||||
|
* @return array{buy_gold: float, sell_gold: float, buy_rial: float, sell_rial: float, net_gold: float, net_rial: float}
|
||||||
|
*/
|
||||||
|
private function sumColleagueSite(array $classified): array
|
||||||
|
{
|
||||||
|
$buyGold = 0.0; // فروش همکار به اپ
|
||||||
|
$sellGold = 0.0;
|
||||||
|
$buyRial = 0.0;
|
||||||
|
$sellRial = 0.0;
|
||||||
|
|
||||||
|
foreach ($classified as $row) {
|
||||||
|
if (($row['kind'] ?? '') === 'site_sell') {
|
||||||
|
$buyGold += abs((float) $row['net_gold']);
|
||||||
|
$buyRial += abs((float) $row['net_rial']);
|
||||||
|
}
|
||||||
|
if (($row['kind'] ?? '') === 'site_buy') {
|
||||||
|
$sellGold += abs((float) $row['net_gold']);
|
||||||
|
$sellRial += abs((float) $row['net_rial']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'buy_gold' => round($buyGold, 3),
|
||||||
|
'sell_gold' => round($sellGold, 3),
|
||||||
|
'buy_rial' => (int) round($buyRial),
|
||||||
|
'sell_rial' => (int) round($sellRial),
|
||||||
|
'net_gold' => round($buyGold - $sellGold, 3),
|
||||||
|
'net_rial' => (int) round($sellRial - $buyRial),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<array<string, mixed>> $orders
|
||||||
|
* @return array{buy_gold: float, sell_gold: float, buy_rial: float, sell_rial: float, net_gold: float, net_rial: float}
|
||||||
|
*/
|
||||||
|
private function sumLocalOrders(array $orders): array
|
||||||
|
{
|
||||||
|
$buyGold = 0.0;
|
||||||
|
$sellGold = 0.0;
|
||||||
|
$buyRial = 0.0;
|
||||||
|
$sellRial = 0.0;
|
||||||
|
|
||||||
|
foreach ($orders as $order) {
|
||||||
|
if ((int) $order['direction'] === 1) {
|
||||||
|
$buyGold += (float) $order['amount'];
|
||||||
|
$buyRial += (float) $order['cash'];
|
||||||
|
} else {
|
||||||
|
$sellGold += (float) $order['amount'];
|
||||||
|
$sellRial += (float) $order['cash'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'buy_gold' => round($buyGold, 3),
|
||||||
|
'sell_gold' => round($sellGold, 3),
|
||||||
|
'buy_rial' => (int) round($buyRial),
|
||||||
|
'sell_rial' => (int) round($sellRial),
|
||||||
|
'net_gold' => round($buyGold - $sellGold, 3),
|
||||||
|
'net_rial' => (int) round($sellRial - $buyRial),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $row
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function publicColleagueRow(array $row): array
|
||||||
|
{
|
||||||
|
unset($row['_matched']);
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function diffCodeForKind(string $kind): string
|
||||||
|
{
|
||||||
|
return match ($kind) {
|
||||||
|
'settlement' => 'colleague_settlement',
|
||||||
|
'physical' => 'colleague_physical',
|
||||||
|
'gift' => 'gift_cover',
|
||||||
|
default => 'other',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function reasonForKind(string $kind, string $desc): string
|
||||||
|
{
|
||||||
|
return match ($kind) {
|
||||||
|
'settlement' => 'واریز/برداشت یا تعدیل روی دفتر همکار (بدون توضیح سایت) — معمولاً تسویه بانکی است و در اپ سفارش نیست.',
|
||||||
|
'physical' => 'پرداخت فیزیکی روی دفتر همکار — در کیف مشتری لزوماً همان لحظه کم نمیشود.',
|
||||||
|
'gift' => 'سند مرتبط با پاداش روی دفتر همکار.',
|
||||||
|
default => $desc !== ''
|
||||||
|
? 'سند سایر روی دفتر همکار: ' . $desc
|
||||||
|
: 'سند سایر روی دفتر همکار.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function colleagueDateToYmd(string $date): ?string
|
||||||
|
{
|
||||||
|
$date = trim($date);
|
||||||
|
if ($date === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// جلالی شبیه 1405-05-11 (سال جلالی معمولاً ۱۳xx/۱۴xx)
|
||||||
|
if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $date, $m) && (int) $m[1] >= 1300 && (int) $m[1] < 1600) {
|
||||||
|
try {
|
||||||
|
return Verta::createJalaliDate((int) $m[1], (int) $m[2], (int) $m[3])
|
||||||
|
->toCarbon()
|
||||||
|
->toDateString();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Carbon::parse($date)->toDateString();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function num(mixed $v): float
|
||||||
|
{
|
||||||
|
return (float) str_replace(',', '', (string) ($v ?? 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function goldClose(float $a, float $b): bool
|
||||||
|
{
|
||||||
|
return abs($a - $b) <= self::GOLD_EPS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rialClose(float $a, float $b): bool
|
||||||
|
{
|
||||||
|
return abs($a - $b) <= self::RIAL_EPS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,6 +118,112 @@ class MontzarService
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* مانده طلا/ریال اکانت اپ روی نود همکار (دفتر پنل همکار — نه تهحساب اپ).
|
||||||
|
*
|
||||||
|
* @return list<array{name_eng?: string, name_fa?: string, value?: float|int, unit?: string, isKol?: bool}>
|
||||||
|
*/
|
||||||
|
public function fetchColleagueLedgerMandeh(): array
|
||||||
|
{
|
||||||
|
$body = $this->requestColleagueLedger('GET', '/server/customer/fin_tahesab/mandeh');
|
||||||
|
|
||||||
|
if (!is_array($body)) {
|
||||||
|
throw new UnprocessableEntityHttpException('پاسخ نامعتبر مانده دفتر همکار');
|
||||||
|
}
|
||||||
|
|
||||||
|
// بعضی نودها آرایهٔ مستقیم برمیگردانند؛ بعضی داخل data
|
||||||
|
if (isset($body[0]) || $body === []) {
|
||||||
|
return array_values($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($body['data']) && is_array($body['data'])) {
|
||||||
|
return array_values($body['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$body];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ریز اسناد دفتر اکانت روی نود همکار در بازهٔ تاریخ میلادی Y-m-d.
|
||||||
|
*
|
||||||
|
* @return list<array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public function fetchColleagueLedgerTable(string $startDate, string $endDate): array
|
||||||
|
{
|
||||||
|
$body = $this->requestColleagueLedger('POST', '/server/customer/fin_tahesab/table', [
|
||||||
|
'startDate' => $startDate,
|
||||||
|
'endDate' => $endDate,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!is_array($body)) {
|
||||||
|
throw new UnprocessableEntityHttpException('پاسخ نامعتبر ریز دفتر همکار');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($body[0]) || $body === []) {
|
||||||
|
return array_values($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($body['data']) && is_array($body['data'])) {
|
||||||
|
return array_values($body['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($body['rows']) && is_array($body['rows'])) {
|
||||||
|
return array_values($body['rows']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed>|null $payload
|
||||||
|
* @return array<mixed>|null
|
||||||
|
*/
|
||||||
|
private function requestColleagueLedger(string $method, string $path, ?array $payload = null): ?array
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$url = $this->endpoint($path);
|
||||||
|
|
||||||
|
$httpResponse = $this->sendColleagueLedgerHttp($method, $url, $token, $payload);
|
||||||
|
$body = $this->decodeMontzarHttpBody($httpResponse);
|
||||||
|
|
||||||
|
if (is_array($body) && ($body['message'] ?? null) === 'no authorised') {
|
||||||
|
$token = $this->montzarLogin();
|
||||||
|
$httpResponse = $this->sendColleagueLedgerHttp($method, $url, $token, $payload);
|
||||||
|
$body = $this->decodeMontzarHttpBody($httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpResponse->failed() && !is_array($body)) {
|
||||||
|
Log::warning('Colleague ledger request failed', [
|
||||||
|
'method' => $method,
|
||||||
|
'url' => $url,
|
||||||
|
'status' => $httpResponse->status(),
|
||||||
|
'raw' => $httpResponse->body(),
|
||||||
|
]);
|
||||||
|
throw new UnprocessableEntityHttpException(
|
||||||
|
$this->formatMontzarErrorMessage(null, $httpResponse)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($body) && !empty($body['message']) && is_string($body['message']) && !isset($body[0])) {
|
||||||
|
throw new UnprocessableEntityHttpException($body['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendColleagueLedgerHttp(string $method, string $url, string $token, ?array $payload)
|
||||||
|
{
|
||||||
|
$pending = Http::timeout(30)
|
||||||
|
->acceptJson()
|
||||||
|
->withToken($token);
|
||||||
|
|
||||||
|
if (strtoupper($method) === 'GET') {
|
||||||
|
return $pending->get($url, $payload ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pending->post($url, $payload ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ثبت سفارش در منتظر — قبل از ایجاد سفارش/تراکنش محلی صدا زده میشود.
|
* ثبت سفارش در منتظر — قبل از ایجاد سفارش/تراکنش محلی صدا زده میشود.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ Route::middleware('auth:user')->group(function (){
|
|||||||
Route::post('dashboard/profit-conversions', [DashboardController::class, 'profitConversions']);
|
Route::post('dashboard/profit-conversions', [DashboardController::class, 'profitConversions']);
|
||||||
Route::post('dashboard/profit-conversions/store', [DashboardController::class, 'storeProfitConversion']);
|
Route::post('dashboard/profit-conversions/store', [DashboardController::class, 'storeProfitConversion']);
|
||||||
Route::delete('dashboard/profit-conversions/{id}', [DashboardController::class, 'deleteProfitConversion']);
|
Route::delete('dashboard/profit-conversions/{id}', [DashboardController::class, 'deleteProfitConversion']);
|
||||||
|
Route::post('dashboard/montzar-reconcile', [DashboardController::class, 'montzarReconcile']);
|
||||||
|
|
||||||
//bank account
|
//bank account
|
||||||
Route::post('account/table', [BankAccountController::class, 'table']);
|
Route::post('account/table', [BankAccountController::class, 'table']);
|
||||||
|
|||||||
Reference in New Issue
Block a user