feat(shayegan): per-product item code and auto customer Account/Put
This commit is contained in:
@@ -979,6 +979,13 @@ class ProductController extends SuperController
|
||||
unset($data['cash_fee_buy_is_percent'], $data['cash_fee_sell_is_percent']);
|
||||
}
|
||||
|
||||
if (! Product::hasShayeganItemColumn()) {
|
||||
unset($data['shayegan_item_number']);
|
||||
} elseif (array_key_exists('shayegan_item_number', $data)) {
|
||||
$item = trim((string) $data['shayegan_item_number']);
|
||||
$data['shayegan_item_number'] = $item === '' ? null : $item;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,18 @@ class ShayeganController extends Controller
|
||||
return $this->sync->recordWalletGold($data);
|
||||
}
|
||||
|
||||
public function registerCustomer($customerId)
|
||||
{
|
||||
if (! app(\App\Services\ConfigService::class)->isShayeganAccounting()) {
|
||||
return response()->json(['ok' => false, 'message' => 'حسابداری شایگان فعال نیست'], 422);
|
||||
}
|
||||
|
||||
$customer = \App\Models\Customer::findOrFail($customerId);
|
||||
$result = $this->sync->registerCustomer($customer);
|
||||
|
||||
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
|
||||
}
|
||||
|
||||
public function table(Request $request)
|
||||
{
|
||||
if (! Schema::hasTable('shayegan')) {
|
||||
|
||||
@@ -421,6 +421,9 @@ class CartController extends Controller
|
||||
'amount' => $product->amount,
|
||||
'unit' => $product->unit,
|
||||
'trade_by_quantity' => $product->trade_by_quantity,
|
||||
'shayegan_item_number' => Product::hasShayeganItemColumn()
|
||||
? ($product->shayegan_item_number ?? null)
|
||||
: null,
|
||||
],
|
||||
'line' => $lineIndex + 1,
|
||||
'order' => [
|
||||
|
||||
@@ -33,6 +33,7 @@ class Product extends Model
|
||||
private static ?bool $karatColumnExists = null;
|
||||
private static ?bool $tvPriceFormulaColumnsExist = null;
|
||||
private static ?bool $priceFormulaColumnsExist = null;
|
||||
private static ?bool $shayeganItemColumnExists = null;
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
@@ -104,9 +105,27 @@ class Product extends Model
|
||||
$product->attributes['tv_price_formula']
|
||||
);
|
||||
}
|
||||
if (!static::hasShayeganItemColumn()) {
|
||||
unset($product->attributes['shayegan_item_number']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function hasShayeganItemColumn(): bool
|
||||
{
|
||||
if (static::$shayeganItemColumnExists !== null) {
|
||||
return static::$shayeganItemColumnExists;
|
||||
}
|
||||
|
||||
try {
|
||||
static::$shayeganItemColumnExists = Schema::hasColumn((new static)->getTable(), 'shayegan_item_number');
|
||||
} catch (\Throwable) {
|
||||
static::$shayeganItemColumnExists = false;
|
||||
}
|
||||
|
||||
return static::$shayeganItemColumnExists;
|
||||
}
|
||||
|
||||
public static function hasTvPriceFormulaColumns(): bool
|
||||
{
|
||||
if (static::$tvPriceFormulaColumnsExist !== null) {
|
||||
@@ -497,6 +516,7 @@ class Product extends Model
|
||||
'unit', // The unit of the item.
|
||||
'group_type', // The unit of the item.
|
||||
'finance_p_name', // کد کار زرین (idKar)
|
||||
'shayegan_item_number', // ItemNumber در فاکتور شایگان
|
||||
'file_id', // The ID of the associated file.
|
||||
'can_canceled', // Whether the record can be canceled (boolean).
|
||||
'active', // The active status of the record (boolean).
|
||||
|
||||
@@ -14,5 +14,6 @@ class CustomerObserver
|
||||
app(TahesabSyncService::class)->registerOnCreate($customer);
|
||||
app(ZarinSyncService::class)->registerOnCreate($customer);
|
||||
app(PargarSyncService::class)->registerOnCreate($customer);
|
||||
app(\App\Services\ShayeganSyncService::class)->registerOnCreate($customer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Customer;
|
||||
use App\Models\Product;
|
||||
use App\Models\Shayegan;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -50,8 +51,155 @@ class ShayeganSyncService
|
||||
|
||||
public function tradeMappingReady(): bool
|
||||
{
|
||||
return $this->config->getString('shayegan_item_copper', '') !== ''
|
||||
&& $this->config->getString('shayegan_stock_number', '') !== '';
|
||||
return $this->config->getString('shayegan_stock_number', '') !== '';
|
||||
}
|
||||
|
||||
public function customerMappingReady(): bool
|
||||
{
|
||||
return trim($this->config->getString('shayegan_moin_customer', '')) !== '';
|
||||
}
|
||||
|
||||
public function registerOnCreate(Customer $customer): void
|
||||
{
|
||||
if (! empty($customer->moshtari_code) || ! $this->isSyncEnabled() || ! $this->customerMappingReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$customerId = (int) $customer->id;
|
||||
|
||||
dispatch(function () use ($customerId) {
|
||||
$service = app(self::class);
|
||||
$customer = Customer::find($customerId);
|
||||
if ($customer === null || ! empty($customer->moshtari_code) || ! $service->isSyncEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $service->registerCustomer($customer);
|
||||
if (! ($result['ok'] ?? false)) {
|
||||
Log::warning('shayegan.auto_register_on_create_failed', [
|
||||
'customer_id' => $customerId,
|
||||
'message' => $result['message'] ?? null,
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('shayegan.auto_register_on_create_threw', [
|
||||
'customer_id' => $customerId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
})->afterResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{ok:bool,message:string,moshtari_code?:string}
|
||||
*/
|
||||
public function registerCustomer(Customer $customer): array
|
||||
{
|
||||
$customer->refresh();
|
||||
|
||||
if (! empty($customer->moshtari_code)) {
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'مشتری قبلاً در شایگان ثبت شده است',
|
||||
'moshtari_code' => (string) $customer->moshtari_code,
|
||||
];
|
||||
}
|
||||
|
||||
if (! $this->isSyncEnabled()) {
|
||||
return ['ok' => false, 'message' => 'حسابداری شایگان فعال نیست یا تنظیمات اتصال ناقص است'];
|
||||
}
|
||||
|
||||
if (! $this->customerMappingReady()) {
|
||||
return ['ok' => false, 'message' => 'شماره معین مشتریان (shayegan_moin_customer) در تنظیمات خالی است'];
|
||||
}
|
||||
|
||||
$moinRaw = trim($this->config->getString('shayegan_moin_customer', ''));
|
||||
$moinNumber = is_numeric($moinRaw) ? (int) $moinRaw : $moinRaw;
|
||||
$name = trim((string) ($customer->name ?? ''));
|
||||
if ($name === '') {
|
||||
$name = 'مشتری ' . $customer->id;
|
||||
}
|
||||
|
||||
$guid = (string) Str::uuid();
|
||||
$put = [[
|
||||
'MoinNumber' => $moinNumber,
|
||||
'AccountTypeId' => null,
|
||||
'AccountName' => mb_substr($name, 0, 200),
|
||||
'Mobile' => trim((string) ($customer->mobile ?? '')) ?: null,
|
||||
'mellicode' => trim((string) ($customer->n_code ?? '')) ?: null,
|
||||
'GuId' => $guid,
|
||||
'UpdateKind' => 3,
|
||||
]];
|
||||
|
||||
$result = $this->client->put('Account/Put', $put);
|
||||
$accountNumber = trim((string) ($result['number'] ?? ''));
|
||||
|
||||
if (! empty($result['timeout'])) {
|
||||
return ['ok' => false, 'message' => (string) ($result['message'] ?? 'timeout')];
|
||||
}
|
||||
|
||||
if (empty($result['ok']) || $accountNumber === '') {
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => (string) ($result['message'] ?? 'ثبت حساب مشتری در شایگان ناموفق بود'),
|
||||
];
|
||||
}
|
||||
|
||||
$customer->moshtari_code = $accountNumber;
|
||||
$customer->save();
|
||||
|
||||
$row = new Shayegan();
|
||||
$row->customer_id = (int) $customer->id;
|
||||
$row->order_id = null;
|
||||
$row->withdraw_id = null;
|
||||
$row->product_id = null;
|
||||
$row->amount = null;
|
||||
$row->desc = 'ثبت حساب مشتری';
|
||||
$row->type = self::TYPE_CUSTOMER;
|
||||
$row->date = now();
|
||||
$row->endpoint = 'Account/Put';
|
||||
$row->request_guid = $guid;
|
||||
$row->shayegan_number = $accountNumber;
|
||||
$row->shayegan_guid = $result['guid'] ?? null;
|
||||
$row->sync_status = self::STATUS_SUCCESS;
|
||||
$row->data = json_encode($this->client->redactPayload([
|
||||
'Config' => $this->client->buildConfig(),
|
||||
'PutObject' => $put,
|
||||
]), JSON_UNESCAPED_UNICODE);
|
||||
$row->res = json_encode($result['body'] ?? [], JSON_UNESCAPED_UNICODE);
|
||||
$row->save();
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'حساب تفصیلی مشتری در شایگان ایجاد شد',
|
||||
'moshtari_code' => $accountNumber,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function resolveItemNumber(array $data): string
|
||||
{
|
||||
$productPayload = is_array($data['product'] ?? null) ? $data['product'] : [];
|
||||
$fromPayload = trim((string) ($productPayload['shayegan_item_number'] ?? ''));
|
||||
if ($fromPayload !== '') {
|
||||
return $fromPayload;
|
||||
}
|
||||
|
||||
$productId = (int) ($data['product_id'] ?? ($productPayload['id'] ?? 0));
|
||||
if ($productId > 0 && Product::hasShayeganItemColumn()) {
|
||||
$product = Product::query()->select('id', 'shayegan_item_number')->find($productId);
|
||||
if ($product) {
|
||||
$item = trim((string) ($product->shayegan_item_number ?? ''));
|
||||
if ($item !== '') {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trim($this->config->getString('shayegan_item_copper', ''));
|
||||
}
|
||||
|
||||
public function cashMappingReady(): bool
|
||||
@@ -92,15 +240,40 @@ class ShayeganSyncService
|
||||
return null;
|
||||
}
|
||||
|
||||
$existing = $this->findExisting(self::TYPE_TRADE, $orderId > 0 ? $orderId : null, null, $customerId);
|
||||
$productId = (int) ($data['product_id'] ?? ($data['order']['product_id'] ?? 0));
|
||||
|
||||
$existing = $this->findExisting(
|
||||
self::TYPE_TRADE,
|
||||
$orderId > 0 ? $orderId : null,
|
||||
null,
|
||||
$customerId,
|
||||
$productId > 0 ? $productId : null
|
||||
);
|
||||
if ($existing && $this->shouldSkipSend($existing)) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$customer = Customer::find($customerId);
|
||||
$accountNumber = $this->customerAccountNumber($customer);
|
||||
$accountNumber = $this->ensureCustomerAccountNumber($customer);
|
||||
if ($accountNumber === '') {
|
||||
return $this->markLocalFailed($existing, $customerId, $orderId, null, self::TYPE_TRADE, 'کد حساب مشتری در شایگان خالی است (moshtari_code).', null);
|
||||
$msg = $this->customerMappingReady()
|
||||
? 'ثبت حساب تفصیلی مشتری در شایگان ناموفق بود.'
|
||||
: 'کد حساب مشتری خالی است؛ shayegan_moin_customer را در تنظیمات پر کنید.';
|
||||
|
||||
return $this->markLocalFailed($existing, $customerId, $orderId, null, self::TYPE_TRADE, $msg, null);
|
||||
}
|
||||
|
||||
$itemNumber = $this->resolveItemNumber($data);
|
||||
if ($itemNumber === '') {
|
||||
return $this->markLocalFailed(
|
||||
$existing,
|
||||
$customerId,
|
||||
$orderId,
|
||||
null,
|
||||
self::TYPE_TRADE,
|
||||
'کد کالای شایگان برای این محصول تنظیم نشده (محصول یا shayegan_item_copper).',
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
$isBuy = (int) ($data['direction'] ?? 0) === 1;
|
||||
@@ -120,7 +293,7 @@ class ShayeganSyncService
|
||||
'Body' => [[
|
||||
'STNumber' => $this->config->getString('shayegan_stock_number', ''),
|
||||
'STGuId' => null,
|
||||
'ItemNumber' => $this->config->getString('shayegan_item_copper', ''),
|
||||
'ItemNumber' => $itemNumber,
|
||||
'ItemGuId' => null,
|
||||
'Quan' => $quan,
|
||||
'Quan2' => null,
|
||||
@@ -146,6 +319,9 @@ class ShayeganSyncService
|
||||
$row = $existing ?: new Shayegan();
|
||||
$row->customer_id = $customerId;
|
||||
$row->order_id = $orderId > 0 ? $orderId : null;
|
||||
if (Schema::hasColumn('shayegan', 'product_id')) {
|
||||
$row->product_id = $productId > 0 ? $productId : null;
|
||||
}
|
||||
$row->withdraw_id = null;
|
||||
$row->amount = $this->scaleAmount((float) ($data['total'] ?? $data['total_price'] ?? ($quan * $price)));
|
||||
$row->desc = $put[0]['InvDescription'];
|
||||
@@ -183,7 +359,7 @@ class ShayeganSyncService
|
||||
$customerId = (int) ($data['customer_id'] ?? 0);
|
||||
$withdrawId = isset($data['withdraw_id']) ? (int) $data['withdraw_id'] : null;
|
||||
$customer = Customer::find($customerId);
|
||||
$accountNumber = $this->customerAccountNumber($customer);
|
||||
$accountNumber = $this->ensureCustomerAccountNumber($customer);
|
||||
if ($accountNumber === '') {
|
||||
return null;
|
||||
}
|
||||
@@ -269,7 +445,7 @@ class ShayeganSyncService
|
||||
$customerId = (int) ($data['customer_id'] ?? 0);
|
||||
$withdrawId = isset($data['withdraw_id']) ? (int) $data['withdraw_id'] : null;
|
||||
$customer = Customer::find($customerId);
|
||||
$accountNumber = $this->customerAccountNumber($customer);
|
||||
$accountNumber = $this->ensureCustomerAccountNumber($customer);
|
||||
if ($accountNumber === '') {
|
||||
return null;
|
||||
}
|
||||
@@ -294,10 +470,14 @@ class ShayeganSyncService
|
||||
return null;
|
||||
}
|
||||
$invTyp = $isDeposit ? self::INV_RECEIPT : self::INV_HAVALE;
|
||||
$itemNumber = $this->resolveItemNumber($data);
|
||||
if ($itemNumber === '') {
|
||||
return null;
|
||||
}
|
||||
$put = [[
|
||||
'Body' => [[
|
||||
'STNumber' => $this->config->getString('shayegan_stock_number', ''),
|
||||
'ItemNumber' => $this->config->getString('shayegan_item_copper', ''),
|
||||
'ItemNumber' => $itemNumber,
|
||||
'Quan' => $quan,
|
||||
'Price' => null,
|
||||
'LineItemDesc' => $isDeposit ? 'واریز محصول' : 'برداشت محصول',
|
||||
@@ -401,8 +581,13 @@ class ShayeganSyncService
|
||||
return false;
|
||||
}
|
||||
|
||||
private function findExisting(string $type, ?int $orderId, ?int $withdrawId, int $customerId): ?Shayegan
|
||||
{
|
||||
private function findExisting(
|
||||
string $type,
|
||||
?int $orderId,
|
||||
?int $withdrawId,
|
||||
int $customerId,
|
||||
?int $productId = null
|
||||
): ?Shayegan {
|
||||
if (! Schema::hasTable('shayegan')) {
|
||||
return null;
|
||||
}
|
||||
@@ -414,6 +599,9 @@ class ShayeganSyncService
|
||||
if ($withdrawId) {
|
||||
$q->where('withdraw_id', $withdrawId);
|
||||
}
|
||||
if ($productId && Schema::hasColumn('shayegan', 'product_id')) {
|
||||
$q->where('product_id', $productId);
|
||||
}
|
||||
if (! $orderId && ! $withdrawId) {
|
||||
return null;
|
||||
}
|
||||
@@ -421,6 +609,32 @@ class ShayeganSyncService
|
||||
return $q->orderByDesc('id')->first();
|
||||
}
|
||||
|
||||
private function ensureCustomerAccountNumber(?Customer $customer): string
|
||||
{
|
||||
$code = $this->customerAccountNumber($customer);
|
||||
if ($code !== '' || ! $customer) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
if (! $this->customerMappingReady()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = $this->registerCustomer($customer);
|
||||
if (! ($result['ok'] ?? false)) {
|
||||
Log::info('shayegan.ensure_customer_failed', [
|
||||
'customer_id' => $customer->id,
|
||||
'message' => $result['message'] ?? null,
|
||||
]);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$customer->refresh();
|
||||
|
||||
return $this->customerAccountNumber($customer);
|
||||
}
|
||||
|
||||
private function customerAccountNumber(?Customer $customer): string
|
||||
{
|
||||
if (! $customer) {
|
||||
|
||||
Reference in New Issue
Block a user