feat(accounting): document colleague trades on configured accounts

Post Montzar-covered buy/sell to Zarin/Tahesab/Pargar using montzar_account_* settings, and harden public storage URL/MIME handling for uploads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 16:59:27 +03:30
co-authored by Cursor
parent ad78545ebd
commit 885ed33e6e
10 changed files with 457 additions and 34 deletions
+32 -3
View File
@@ -40,9 +40,7 @@ class FileController extends Controller
$path = $this->createDir();
$originalName = $uploaded->getClientOriginalName();
$ext = $uploaded->getClientOriginalExtension()
?: $uploaded->extension()
?: 'jpg';
$ext = $this->resolveUploadExtension($uploaded);
$title = $request->input('title') ?: $originalName;
$file_name_src = md5(time().$originalName);
$file_name = $file_name_src.'.'.$ext;
@@ -70,4 +68,35 @@ class FileController extends Controller
return response()->json($payload);
}
/**
* پسوند واقعی فایل (MIME)، نه اسم اصلی مرورگر — آپلود بنر WebP است.
*/
private function resolveUploadExtension(\Illuminate\Http\UploadedFile $uploaded): string
{
$mime = strtolower((string) ($uploaded->getMimeType() ?: $uploaded->getClientMimeType()));
$fromMime = match (true) {
str_contains($mime, 'webp') => 'webp',
str_contains($mime, 'png') => 'png',
str_contains($mime, 'jpeg'), str_contains($mime, 'jpg') => 'jpg',
str_contains($mime, 'gif') => 'gif',
str_contains($mime, 'svg') => 'svg',
default => null,
};
if ($fromMime) {
return $fromMime;
}
$client = strtolower((string) $uploaded->getClientOriginalExtension());
if (in_array($client, ['webp', 'png', 'jpg', 'jpeg', 'gif', 'svg'], true)) {
return $client === 'jpeg' ? 'jpg' : $client;
}
$guessed = strtolower((string) $uploaded->extension());
if (in_array($guessed, ['webp', 'png', 'jpg', 'jpeg', 'gif', 'svg'], true)) {
return $guessed === 'jpeg' ? 'jpg' : $guessed;
}
return 'jpg';
}
}
@@ -453,10 +453,25 @@ class TahesabController extends Controller
}
$customer = Customer::where('id', $data['customer_id'])->first();
$accountOverride = trim((string) ($data['accounting_account_code'] ?? ''));
$isColleagueLeg = ! empty($data['colleague_leg']) && $accountOverride !== '';
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInTahesab($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
if (! $isColleagueLeg) {
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInTahesab($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
}
}
if (! $customer) {
return false;
}
$hesabCode = $isColleagueLeg
? (int) $accountOverride
: (int) ($customer->moshtari_code ?? 0);
if ($hesabCode <= 0) {
return false;
}
$kind = $mapper->resolveKind($product);
@@ -490,7 +505,14 @@ class TahesabController extends Controller
$action = $isBuy ? 'خرید' : 'فروش';
$kindFa = $mapper->kindLabel($kind);
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}");
$sharh = trim((string) ($data['sharh_override'] ?? ''));
if ($sharh === '') {
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}");
}
$orderId = is_object($data['order'] ?? null)
? ($data['order']->id ?? null)
: ($data['order']['id'] ?? null);
$data['handler'] = 'storeTrade';
if ($existing && empty($existing->data)) {
@@ -500,21 +522,21 @@ class TahesabController extends Controller
'customer_id' => $customer->id,
'desc' => $sharh,
'amount' => $mapper->isCountDoc($product) ? $count : $vazn,
'order_id' => $data['order']['id'] ?? null,
'order_id' => $orderId,
'date' => $data['pay_at'] ?? Carbon::now(),
'type' => $mapper->syncTypeLabel($product),
'created_at' => Carbon::now(),
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
]);
if ($mapper->isCountDoc($product) && $title === '') {
if ($mapper->isCountDoc($product) && $title === '' && ! $isColleagueLeg) {
$this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی ته‌حساب خالی است');
return false;
}
$body = $this->buildBuySaleBody(
$kind,
(int) $customer->moshtari_code,
$hesabCode,
(int) $year,
(int) $month,
(int) $day,
+33 -11
View File
@@ -1394,29 +1394,49 @@ class ZarinController extends Controller
}
$customer = Customer::where('id', $data['customer_id'])->first();
$accountOverride = trim((string) ($data['accounting_account_code'] ?? ''));
$isColleagueLeg = ! empty($data['colleague_leg']) && $accountOverride !== '';
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInZarin($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
if (! $isColleagueLeg) {
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInZarin($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
}
if (!$customer || empty($customer->moshtari_code)) {
return false;
}
} elseif (! $customer) {
return false;
}
if (!$customer || empty($customer->moshtari_code)) {
return false;
$accountCustomer = $customer;
if ($isColleagueLeg) {
$accountCustomer = clone $customer;
$accountCustomer->moshtari_code = $accountOverride;
}
$rawAmount = (float) $data['amount'];
$count = $mapper->accountingQty($product, $rawAmount, $data);
$vazn = $mapper->accountingWeight($product, $rawAmount, $data);
$isBuy = (int) ($data['direction'] ?? 0) === 1;
$orderId = $data['order']['id'] ?? ($existing->order_id ?? null);
$orderId = is_object($data['order'] ?? null)
? ($data['order']->id ?? null)
: ($data['order']['id'] ?? ($existing->order_id ?? null));
$title = $mapper->productTitle($product);
$action = $isBuy ? 'خرید' : 'فروش';
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$mapper->kindLabel($mapper->resolveKind($product))}");
$sharh = trim((string) ($data['sharh_override'] ?? ''));
if ($sharh === '') {
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$mapper->kindLabel($mapper->resolveKind($product))}");
}
$syncAmount = $mapper->isCountDoc($product) ? $count : $vazn;
$uidSource = $orderId ?? null;
if (!empty($data['line'])) {
$uidSource = ($orderId ?? '0') . 'L' . (int) $data['line'];
}
if ($isColleagueLeg) {
$uidSource = ($uidSource ?? '0') . 'C';
}
$data['handler'] = 'storeTrade';
if ($existing && empty($existing->data)) {
@@ -1440,6 +1460,8 @@ class ZarinController extends Controller
]);
}
$uidKind = $isColleagueLeg ? '11' : '10';
try {
if ($mapper->isCountDoc($product)) {
$fi = (float) ($data['price'] ?? 0);
@@ -1453,25 +1475,25 @@ class ZarinController extends Controller
$result = $this->postZarinCountDoc(
$zarin,
$data,
$customer,
$accountCustomer,
$product,
$count,
$isBuy ? -1 : 1,
$fi,
$sharh,
$this->zarinUid('10', $uidSource ?? $zarin->id)
$this->zarinUid($uidKind, $uidSource ?? $zarin->id)
);
} else {
$result = $this->postZarinWeightDoc(
$zarin,
$data,
$customer,
$accountCustomer,
$vazn,
$isBuy ? 'f' : 'k',
(float) ($data['price'] ?? 0),
$sharh,
$mapper->jensFelez($product) ?? 0,
$this->zarinUid('10', $uidSource ?? $zarin->id)
$this->zarinUid($uidKind, $uidSource ?? $zarin->id)
);
}
} catch (\Exception $e) {