fix some bugs

This commit is contained in:
Gloomy 2025-08-19 00:34:56 +00:00
parent 15d2f40e5d
commit 8d11485530
13 changed files with 733 additions and 284 deletions

View file

@ -134,6 +134,18 @@ class ApprovalController extends AbstractController
$document->setIsApproved(true); $document->setIsApproved(true);
$document->setApprovedBy($user); $document->setApprovedBy($user);
$payments = [];
foreach ($document->getRelatedDocs() as $relatedDoc) {
if ($relatedDoc->getType() === 'sell_receive') {
$payments[] = $relatedDoc;
}
}
foreach ($payments as $payment) {
$payment->setIsPreview(false);
$payment->setIsApproved(true);
$payment->setApprovedBy($user);
}
$entityManager->persist($document); $entityManager->persist($document);
$entityManager->flush(); $entityManager->flush();

View file

@ -263,6 +263,16 @@ class BusinessController extends AbstractController
$business->setFinancialApprover($params['financialApprover']); $business->setFinancialApprover($params['financialApprover']);
} }
if (array_key_exists('requireWarrantyOnDelivery', $params)) {
$business->setRequireWarrantyOnDelivery($params['requireWarrantyOnDelivery']);
}
if (array_key_exists('activationGraceDays', $params)) {
$business->setActivationGraceDays($params['activationGraceDays']);
}
if (array_key_exists('matchWarrantyToSerial', $params)) {
$business->setMatchWarrantyToSerial($params['matchWarrantyToSerial']);
}
//get Money type //get Money type
if (!array_key_exists('arzmain', $params) && $isNew) { if (!array_key_exists('arzmain', $params) && $isNew) {
return $this->json(['result' => 2]); return $this->json(['result' => 2]);

View file

@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Entity\Business;
use App\Service\AccountingPermissionService; use App\Service\AccountingPermissionService;
use App\Service\Jdate; use App\Service\Jdate;
use App\Service\Log; use App\Service\Log;
@ -73,7 +74,7 @@ class SellController extends AbstractController
{ {
$acc = $access->hasRole('sell'); $acc = $access->hasRole('sell');
if (!$acc) throw $this->createAccessDeniedException(); if (!$acc) throw $this->createAccessDeniedException();
$doc = $entityManager->getRepository(\App\Entity\HesabdariDoc::class)->findOneBy([ $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'bid' => $acc['bid'], 'bid' => $acc['bid'],
'code' => $code, 'code' => $code,
'money' => $acc['money'] 'money' => $acc['money']
@ -90,7 +91,7 @@ class SellController extends AbstractController
{ {
$acc = $access->hasRole('sell'); $acc = $access->hasRole('sell');
if (!$acc) throw $this->createAccessDeniedException(); if (!$acc) throw $this->createAccessDeniedException();
$paymentDoc = $entityManager->getRepository(\App\Entity\HesabdariDoc::class)->findOneBy([ $paymentDoc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'bid' => $acc['bid'], 'bid' => $acc['bid'],
'code' => $code, 'code' => $code,
'money' => $acc['money'], 'money' => $acc['money'],
@ -333,7 +334,7 @@ class SellController extends AbstractController
$entityManager->persist($hesabdariRow); $entityManager->persist($hesabdariRow);
// Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد // Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد
$business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); $business = $entityManager->getRepository(Business::class)->find($acc['bid']);
$businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
if ($businessRequire) { if ($businessRequire) {
$doc->setIsPreview(true); $doc->setIsPreview(true);
@ -847,10 +848,13 @@ class SellController extends AbstractController
$accountStatus['label'] = 'بدهکار'; $accountStatus['label'] = 'بدهکار';
$accountStatus['value'] = $bd - $bs; $accountStatus['value'] = $bd - $bs;
} }
// فقط در صورت تایید نهایی مجاز به چاپ هستیم
if ($doc->getStatus() !== 'approved') { $business = $entityManager->getRepository(Business::class)->find($acc['bid']);
$twoApproval = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
if ($twoApproval && $doc->isApproved() !== true && $doc->isPreview() == true) {
return $this->json(['result' => -10, 'message' => 'فاکتور هنوز تایید نشده است'], 403); return $this->json(['result' => -10, 'message' => 'فاکتور هنوز تایید نشده است'], 403);
} }
if ($params['pdf'] == true || $params['printers'] == true) { if ($params['pdf'] == true || $params['printers'] == true) {
$note = ''; $note = '';
if ($printSettings) { if ($printSettings) {
@ -1261,7 +1265,7 @@ class SellController extends AbstractController
$entityManager->persist($hesabdariRow); $entityManager->persist($hesabdariRow);
// Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد // Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد
$business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); $business = $entityManager->getRepository(Business::class)->find($acc['bid']);
$businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
if ($businessRequire) { if ($businessRequire) {
$doc->setIsPreview(true); $doc->setIsPreview(true);
@ -1299,6 +1303,8 @@ class SellController extends AbstractController
$paymentDoc->setDate($params['invoiceDate']); $paymentDoc->setDate($params['invoiceDate']);
$paymentDoc->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode()); $paymentDoc->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode());
$paymentDoc->setAmount($payment['amount']); $paymentDoc->setAmount($payment['amount']);
$paymentDoc->setIsPreview(true);
$paymentDoc->setIsApproved(false);
// ایجاد ارتباط با فاکتور اصلی // ایجاد ارتباط با فاکتور اصلی
$doc->addRelatedDoc($paymentDoc); $doc->addRelatedDoc($paymentDoc);

View file

@ -238,6 +238,9 @@ class StoreroomController extends AbstractController
]); ]);
$sellsForExport = []; $sellsForExport = [];
foreach ($sells as $sell) { foreach ($sells as $sell) {
if ($sell->isPreview()) {
continue;
}
$temp = $provider->Entity2Array($sell, 0); $temp = $provider->Entity2Array($sell, 0);
$person = $this->getPerson($sell); $person = $this->getPerson($sell);
if ($person) { if ($person) {
@ -263,6 +266,9 @@ class StoreroomController extends AbstractController
]); ]);
$rfsellsForExport = []; $rfsellsForExport = [];
foreach ($rfsells as $sell) { foreach ($rfsells as $sell) {
if ($sell->isPreview()) {
continue;
}
$temp = $provider->Entity2Array($sell, 0); $temp = $provider->Entity2Array($sell, 0);
$person = $this->getPerson($sell); $person = $this->getPerson($sell);
if ($person) { if ($person) {
@ -288,6 +294,9 @@ class StoreroomController extends AbstractController
]); ]);
$rfbuysForExport = []; $rfbuysForExport = [];
foreach ($rfbuys as $buy) { foreach ($rfbuys as $buy) {
if ($buy->isPreview()) {
continue;
}
$temp = $provider->Entity2Array($buy, 0); $temp = $provider->Entity2Array($buy, 0);
$person = $this->getPerson($buy); $person = $this->getPerson($buy);
if ($person) { if ($person) {
@ -910,12 +919,12 @@ class StoreroomController extends AbstractController
$business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); $business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']);
$businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
if ($businessRequire) { if ($businessRequire && $doc->isApproved() !== true && $doc->isPreview() == true) {
// بررسی وضعیت تأیید از طریق StoreroomTicket
if ($doc->isPreview()) { if ($doc->isPreview()) {
return $this->json(['result' => -10, 'message' => 'حواله هنوز تایید نشده است'], 403); return $this->json(['result' => -10, 'message' => 'حواله هنوز تایید نشده است'], 403);
} }
} }
$pdfPid = 0; $pdfPid = 0;
$pdfPid = $provider->createPrint( $pdfPid = $provider->createPrint(
$acc['bid'], $acc['bid'],

View file

@ -584,6 +584,9 @@ class Explore
'financialApprover' => $item->getFinancialApprover(), 'financialApprover' => $item->getFinancialApprover(),
'updateSellPrice' => $item->isCommodityUpdateSellPriceAuto(), 'updateSellPrice' => $item->isCommodityUpdateSellPriceAuto(),
'updateBuyPrice' => $item->isCommodityUpdateBuyPriceAuto(), 'updateBuyPrice' => $item->isCommodityUpdateBuyPriceAuto(),
'requireWarrantyOnDelivery' => $item->getRequireWarrantyOnDelivery(),
'activationGraceDays' => $item->getActivationGraceDays(),
'matchWarrantyToSerial' => $item->getMatchWarrantyToSerial(),
]; ];
if (!$item->getProfitCalctype()) { if (!$item->getProfitCalctype()) {
$res['profitCalcType'] = 'lis'; $res['profitCalcType'] = 'lis';

View file

@ -507,6 +507,7 @@ const fa_lang = {
basic_info: "اطلاعات پایه", basic_info: "اطلاعات پایه",
year_label: "سال مالی جاری", year_label: "سال مالی جاری",
global_settings: "تنظیمات سراسری", global_settings: "تنظیمات سراسری",
warranty_settings: "تنظیمات گارانتی",
gate_pay: "درگاه پرداخت", gate_pay: "درگاه پرداخت",
a4l: "کاغذ A4 افقی", a4l: "کاغذ A4 افقی",
a4p: "کاغذ A4 عمودی", a4p: "کاغذ A4 عمودی",

View file

@ -892,6 +892,12 @@ const router = createRouter({
'login': true 'login': true
} }
}, },
{
path: 'plugins/warranty/intro',
name: 'plugin_warranty_intro',
component: () =>
import('../views/acc/plugins/warranty/intro.vue'),
},
{ {
path: 'notifications/list', path: 'notifications/list',
name: 'notification_list', name: 'notification_list',
@ -1080,6 +1086,12 @@ const router = createRouter({
'login': true, 'login': true,
} }
}, },
{
path: 'plugins/import-workflow/intro',
name: 'import_workflow_intro',
component: () =>
import('../views/acc/plugins/import-workflow/intro.vue'),
}
], ],
}, },
{ {

View file

@ -0,0 +1,195 @@
<template>
<div class="plugin-intro">
<div class="intro-header">
<div class="plugin-icon">
<i class="fas fa-ship"></i>
</div>
<div class="plugin-info">
<h1>افزونه مدیریت واردات کالا</h1>
<p class="plugin-description">
مدیریت پروندههای واردات از ثبت تا ترخیص: اسناد، اقلام، پرداختها، حمل و مراحل
</p>
<div class="plugin-version">
<span class="version-badge">نسخه 1.0.0</span>
<span v-if="isPluginActive('importWorkflow')" class="status-badge active">فعال</span>
<RouterLink to="/acc/plugin-center/view-end/import-workflow" v-if="!isPluginActive('importWorkflow')">
<span class="status-badge active text-white d-flex align-items-center">
<i class="fa fa-shopping-cart me-1"></i>
خرید
</span>
</RouterLink>
</div>
</div>
</div>
<div class="intro-content">
<div class="features-section">
<h2>امکانات افزونه</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-folder-plus"></i>
</div>
<h3>پرونده واردات</h3>
<p>ایجاد و مدیریت پروندههای واردات با کد یکتا و وضعیت</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-file-alt"></i>
</div>
<h3>مدیریت اسناد</h3>
<p>بارگذاری و سازماندهی اسناد (پروفرما، پکینگ، حواله، ترخیص و ...)</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-boxes"></i>
</div>
<h3>اقلام و جزئیات</h3>
<p>ثبت اقلام، ارزش، وزن، حجم، برند، مدل و مشخصات کالا</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-money-bill-wave"></i>
</div>
<h3>پرداختها و هزینهها</h3>
<p>مدیریت پرداختها، ارز، نرخ تبدیل و هزینههای مرتبط</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-truck-loading"></i>
</div>
<h3>حمل و لجستیک</h3>
<p>ثبت اطلاعات حمل، کانتینر، مسیر و تاریخها</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-stream"></i>
</div>
<h3>مراحل و پیگیری</h3>
<p>تعریف مراحل، وضعیتها، یادداشتها و پیگیری پیشرفت</p>
</div>
</div>
</div>
<div class="validation-section">
<h2>کنترلها</h2>
<div class="validation-list">
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>کنترل کامل اطلاعات موردنیاز ترخیص</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>همگامسازی اقلام با موجودی و کد کالا</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>گزارشگیری از هزینهها و وضعیت پرونده</span>
</div>
</div>
</div>
<div class="setup-section">
<h2>مراحل راهاندازی</h2>
<div class="setup-steps">
<div class="setup-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>فعالسازی افزونه</h4>
<p>افزونه را از مرکز افزونهها فعال کنید</p>
</div>
</div>
<div class="setup-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>ایجاد پرونده</h4>
<p>پرونده جدید واردات ایجاد و مشخصات پایه را ثبت کنید</p>
</div>
</div>
<div class="setup-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>ثبت اسناد و اقلام</h4>
<p>اسناد، اقلام، پرداختها و حمل را به پرونده اضافه کنید</p>
</div>
</div>
</div>
</div>
</div>
<div class="intro-footer">
<div v-if="isPluginActive('importWorkflow')" class="action-buttons">
<router-link to="/acc/plugins/import-workflow/list" class="btn btn-success">
<i class="fas fa-list"></i>
ورود به مدیریت واردات
</router-link>
</div>
<div style="margin-top: 20px; font-size: 0.75rem; color: #888; text-align: center;">
Developed by <a href="https://pirouz.xyz" target="_blank" style="color: #667eea; text-decoration: none;">Mohammad Rezai</a> 2025
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'ImportWorkflowIntro',
data() {
return {
plugins: {}
}
},
methods: {
isPluginActive(plugName) {
return this.plugins && this.plugins[plugName] !== undefined;
},
},
mounted() {
axios.post('/api/plugin/get/actives').then((response) => {
this.plugins = response.data;
});
if (this.$store) {
this.$store.commit('setPageTitle', 'مدیریت واردات کالا - معرفی افزونه')
}
}
}
</script>
<style scoped>
/* reuse tax intro styles for consistent look */
.plugin-intro { max-width: 1200px; margin: 0 auto; padding: 20px; }
.intro-header { display: flex; align-items: center; gap: 20px; margin-bottom: 40px; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white; }
.plugin-icon { font-size: 3rem; background: rgba(255, 255, 255, 0.2); width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; border-radius: 50%; flex-shrink: 0; }
.plugin-info h1 { margin: 0 0 10px 0; font-size: 2.5rem; font-weight: 700; }
.plugin-description { font-size: 1.1rem; margin: 0 0 15px 0; opacity: 0.9; }
.plugin-version { display: flex; gap: 10px; }
.version-badge, .status-badge { padding: 5px 12px; border-radius: 20px; font-size: 0.9rem; font-weight: 600; }
.version-badge { background: rgba(255, 255, 255, 0.2); }
.status-badge.active { background: #28a745; }
.intro-content { display: flex; flex-direction: column; gap: 40px; }
.features-section, .invoice-types-section, .validation-section, .setup-section, .compliance-section { background: white; border-radius: 15px; padding: 30px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
.features-section h2, .invoice-types-section h2, .validation-section h2, .setup-section h2, .compliance-section h2 { margin: 0 0 25px 0; font-size: 1.8rem; color: #333; border-bottom: 3px solid #667eea; padding-bottom: 10px; }
.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 25px; }
.feature-card { padding: 25px; border-radius: 12px; background: #f8f9fa; border-left: 4px solid #667eea; transition: transform 0.3s ease, box-shadow 0.3s ease; }
.feature-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); }
.feature-icon { font-size: 2rem; color: #667eea; margin-bottom: 15px; width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: rgba(102, 126, 234, 0.1); border-radius: 50%; }
.validation-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px; }
.validation-item { display: flex; align-items: center; gap: 12px; padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #28a745; }
.setup-steps { display: flex; flex-direction: column; gap: 20px; }
.setup-step { display: flex; align-items: flex-start; gap: 20px; padding: 20px; background: #f8f9fa; border-radius: 12px; border-left: 4px solid #667eea; }
.step-number { background: #667eea; color: white; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.2rem; flex-shrink: 0; }
.action-buttons { display: flex; justify-content: center; gap: 20px; flex-wrap: wrap; }
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; transition: all 0.3s ease; border: none; cursor: pointer; }
.btn-success { background: #28a745; color: white; }
.btn-success:hover { background: #218838; transform: translateY(-2px); }
@media (max-width: 768px) { .plugin-intro { padding: 15px; } .intro-header { flex-direction: column; text-align: center; padding: 20px; gap: 15px; } .plugin-icon { width: 60px; height: 60px; font-size: 2rem; } .plugin-info h1 { font-size: 1.8rem; } .features-section, .invoice-types-section, .validation-section, .setup-section, .compliance-section { padding: 20px; } .features-grid { grid-template-columns: 1fr; gap: 20px; } .feature-card { padding: 20px; } .feature-icon { width: 50px; height: 50px; font-size: 1.5rem; } .action-buttons { flex-direction: column; gap: 15px; } .btn { justify-content: center; padding: 15px 20px; font-size: 1rem; } }
</style>

View file

@ -127,141 +127,58 @@
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-card> <v-card>
<v-data-table <v-data-table :headers="headers" :items="serials" :loading="loading" density="comfortable"
:headers="headers" class="elevation-1" :header-props="{ class: 'custom-header' }" hover show-select v-model="selectedItems"
:items="serials" item-key="id" return-object :items-per-page="50">
:loading="loading"
density="comfortable"
class="elevation-1"
:header-props="{ class: 'custom-header' }"
hover
show-select
v-model="selectedItems"
item-key="id"
return-object
:items-per-page="50"
>
<template #top> <template #top>
<div class="d-block d-md-none pa-4"> <div class="d-block d-md-none pa-4">
<div class="d-flex gap-2 flex-column mb-3"> <div class="d-flex gap-2 flex-column mb-3">
<v-btn <v-btn v-if="selectedItems.length > 0" color="error" prepend-icon="mdi-delete-multiple"
v-if="selectedItems.length > 0" @click="showDeleteBulkDialog = true" size="small" block>
color="error" حذف انتخاب شدهها <v-chip size="small" color="error" variant="tonal" class="ms-2"
prepend-icon="mdi-delete-multiple" style="color: white !important;">{{ selectedItems.length }}</v-chip>
@click="showDeleteBulkDialog = true"
size="small"
block
>
حذف انتخاب شدهها <v-chip size="small" color="error" variant="tonal" class="ms-2" style="color: white !important;">{{ selectedItems.length }}</v-chip>
</v-btn> </v-btn>
<v-btn color="primary" prepend-icon="mdi-plus" @click="addSerial" size="small" block> <v-btn color="primary" prepend-icon="mdi-plus" @click="addSerial" size="small" block>
افزودن سریال افزودن سریال
</v-btn> </v-btn>
<v-btn color="secondary" prepend-icon="mdi-upload" @click="showBulkImportDialog = true" size="small" block> <v-btn color="secondary" prepend-icon="mdi-upload" @click="showBulkImportDialog = true" size="small"
block>
وارد کردن انبوه وارد کردن انبوه
</v-btn> </v-btn>
</div> </div>
<v-text-field <v-text-field v-model="filters.search" label="جستجو" prepend-icon="mdi-magnify" clearable
v-model="filters.search" density="compact" variant="outlined" hide-details class="mb-3" @update:model-value="loadSerials" />
label="جستجو"
prepend-icon="mdi-magnify"
clearable
density="compact"
variant="outlined"
hide-details
class="mb-3"
@update:model-value="loadSerials"
/>
<div class="d-flex gap-2 mb-3"> <div class="d-flex gap-2 mb-3">
<v-select <v-select v-model="filters.status" label="وضعیت" :items="statusOptions" clearable density="compact"
v-model="filters.status" variant="outlined" hide-details style="flex: 1;" @update:model-value="loadSerials" />
label="وضعیت" <v-select v-model="filters.commodity_id" label="محصول" :items="commodities" item-title="name"
:items="statusOptions" item-value="id" clearable density="compact" variant="outlined" hide-details style="flex: 1;"
clearable @update:model-value="loadSerials" />
density="compact"
variant="outlined"
hide-details
style="flex: 1;"
@update:model-value="loadSerials"
/>
<v-select
v-model="filters.commodity_id"
label="محصول"
:items="commodities"
item-title="name"
item-value="id"
clearable
density="compact"
variant="outlined"
hide-details
style="flex: 1;"
@update:model-value="loadSerials"
/>
</div> </div>
</div> </div>
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<v-toolbar flat style="height: 70px !important; padding: 10px !important;"> <v-toolbar flat style="height: 70px !important; padding: 10px !important;">
<v-text-field <v-text-field v-model="filters.search" label="جستجو" prepend-icon="mdi-magnify" clearable
v-model="filters.search" density="compact" variant="outlined" hide-details style="max-width: 250px;"
label="جستجو" @update:model-value="loadSerials" class="ml-2" />
prepend-icon="mdi-magnify" <v-select v-model="filters.status" label="وضعیت" :items="statusOptions" clearable density="compact"
clearable variant="outlined" hide-details style="max-width: 220px;" @update:model-value="loadSerials"
density="compact" class="ml-2" />
variant="outlined" <v-select v-model="filters.commodity_id" label="محصول" :items="commodities" item-title="name"
hide-details item-value="id" clearable density="compact" variant="outlined" hide-details
style="max-width: 250px;" style="max-width: 250px;" @update:model-value="loadSerials" class="ml-2" />
@update:model-value="loadSerials"
class="ml-2"
/>
<v-select
v-model="filters.status"
label="وضعیت"
:items="statusOptions"
clearable
density="compact"
variant="outlined"
hide-details
style="max-width: 220px;"
@update:model-value="loadSerials"
class="ml-2"
/>
<v-select
v-model="filters.commodity_id"
label="محصول"
:items="commodities"
item-title="name"
item-value="id"
clearable
density="compact"
variant="outlined"
hide-details
style="max-width: 250px;"
@update:model-value="loadSerials"
class="ml-2"
/>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn v-if="selectedItems.length > 0" color="error" prepend-icon="mdi-delete-multiple"
variant="tonal" @click="showDeleteBulkDialog = true" class="ml-2">
prepend-icon="mdi-cog" حذف انتخاب شدهها <v-chip size="small" color="error" variant="tonal" class="ms-2"
class="ml-2" style="color: red !important;">{{ selectedItems.length }}</v-chip>
@click="openSettings"
>
تنظیمات
</v-btn>
<v-btn
v-if="selectedItems.length > 0"
color="error"
prepend-icon="mdi-delete-multiple"
@click="showDeleteBulkDialog = true"
class="ml-2"
>
حذف انتخاب شدهها <v-chip size="small" color="error" variant="tonal" class="ms-2" style="color: red !important;">{{ selectedItems.length }}</v-chip>
</v-btn> </v-btn>
<v-btn color="primary" prepend-icon="mdi-plus" @click="addSerial"> <v-btn color="primary" prepend-icon="mdi-plus" @click="addSerial">
افزودن سریال افزودن سریال
</v-btn> </v-btn>
<v-btn color="secondary" prepend-icon="mdi-upload" @click="showBulkImportDialog = true" class="ml-2"> <v-btn color="secondary" prepend-icon="mdi-upload" @click="showBulkImportDialog = true"
class="ml-2">
وارد کردن انبوه وارد کردن انبوه
</v-btn> </v-btn>
</v-toolbar> </v-toolbar>
@ -293,20 +210,15 @@
<span> <span>
{{ (item as any).warrantyEndDate ? formatDate((item as any).warrantyEndDate) : '-' }} {{ (item as any).warrantyEndDate ? formatDate((item as any).warrantyEndDate) : '-' }}
</span> </span>
<v-chip <v-chip v-if="(item as any).expired" color="error" size="x-small" variant="flat" class="ms-2">
v-if="(item as any).expired"
color="error"
size="x-small"
variant="flat"
class="ms-2"
>
منقضی منقضی
</v-chip> </v-chip>
</div> </div>
</template> </template>
<template #item.commodity="{ item }"> <template #item.commodity="{ item }">
<v-btn variant="text" color="primary" size="small" @click="viewCommodity((item as any).commodity)" class="text-none"> <v-btn variant="text" color="primary" size="small" @click="viewCommodity((item as any).commodity)"
class="text-none">
{{ (item as any).commodity?.name || 'نامشخص' }} {{ (item as any).commodity?.name || 'نامشخص' }}
</v-btn> </v-btn>
</template> </template>
@ -338,26 +250,13 @@
</v-row> </v-row>
</v-container> </v-container>
<SerialDialog <SerialDialog v-model="showAddDialog" :serial="selectedSerial" :commodities="commodities" @save="saveSerial"
v-model="showAddDialog" @close="closeDialog" />
:serial="selectedSerial"
:commodities="commodities"
@save="saveSerial"
@close="closeDialog"
/>
<SerialViewDialog <SerialViewDialog v-model="showViewDialog" :serial="selectedSerial" @close="closeViewDialog" />
v-model="showViewDialog"
:serial="selectedSerial"
@close="closeViewDialog"
/>
<BulkImportDialog <BulkImportDialog v-model="showBulkImportDialog" :commodities="commodities" @import="bulkImport"
v-model="showBulkImportDialog" @close="closeBulkImportDialog" />
:commodities="commodities"
@import="bulkImport"
@close="closeBulkImportDialog"
/>
<v-dialog v-model="showDeleteDialog" max-width="400"> <v-dialog v-model="showDeleteDialog" max-width="400">
<v-card> <v-card>
@ -387,55 +286,10 @@
</v-card> </v-card>
</v-dialog> </v-dialog>
<v-dialog v-model="showSettingsDialog" max-width="500"> <!-- تنظیمات گارانتی به تب تنظیمات کسبوکار منتقل شد -->
<v-card :loading="settingsLoading || loading">
<v-toolbar color="primary" flat>
<v-icon color="white" class="ms-2">mdi-cog</v-icon>
<v-toolbar-title class="text-white text-subtitle-1">تنظیمات گارانتی</v-toolbar-title>
<v-spacer />
<v-btn icon @click="showSettingsDialog = false">
<v-icon color="white">mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-switch
v-model="settings.requireWarrantyOnDelivery"
label="الزام ثبت گارانتی هنگام صدور حواله"
color="primary"
inset
hide-details
:disabled="settingsLoading || loading"
/>
<v-switch
v-model="settings.matchWarrantyToSerial"
label="تطبیق خودکار گارانتی با سریال کالا (در صورت یکسان بودن کد)"
color="primary"
inset
hide-details
class="mt-2"
:disabled="settingsLoading || loading"
/>
<v-text-field
v-model.number="settings.activationGraceDays"
type="number"
min="0"
label="مهلت فعال‌سازی گارانتی (روز)"
variant="outlined"
density="compact"
hide-details
class="mt-4"
:disabled="settingsLoading || loading"
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="grey" variant="text" @click="showSettingsDialog = false">انصراف</v-btn>
<v-btn color="primary" :loading="loading" :disabled="settingsLoading" @click="saveSettings">ذخیره</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-snackbar v-model="showSnackbar" :color="snackbarColor" :timeout="3000" location="bottom" class="rounded-lg" elevation="2"> <v-snackbar v-model="showSnackbar" :color="snackbarColor" :timeout="3000" location="bottom" class="rounded-lg"
elevation="2">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon :color="snackbarColor" class="me-2"> <v-icon :color="snackbarColor" class="me-2">
{{ snackbarColor === 'success' ? 'mdi-check-circle' : 'mdi-alert-circle' }} {{ snackbarColor === 'success' ? 'mdi-check-circle' : 'mdi-alert-circle' }}
@ -880,27 +734,34 @@ onMounted(async () => {
.warranty-plugin { .warranty-plugin {
padding: 20px; padding: 20px;
} }
.v-data-table { .v-data-table {
direction: rtl; direction: rtl;
} }
:deep(.v-data-table-header th) { :deep(.v-data-table-header th) {
background-color: #f5f5f5 !important; background-color: #f5f5f5 !important;
font-weight: bold !important; font-weight: bold !important;
color: #333 !important; color: #333 !important;
} }
:deep(.v-data-table__wrapper table td) { :deep(.v-data-table__wrapper table td) {
padding: 12px 16px !important; padding: 12px 16px !important;
border-bottom: 1px solid #e0e0e0 !important; border-bottom: 1px solid #e0e0e0 !important;
} }
:deep(.v-data-table__wrapper table tr:hover) { :deep(.v-data-table__wrapper table tr:hover) {
background-color: #f8f9fa !important; background-color: #f8f9fa !important;
} }
:deep(.v-chip) { :deep(.v-chip) {
font-weight: 500; font-weight: 500;
} }
.custom-header { .custom-header {
background-color: #f5f5f5 !important; background-color: #f5f5f5 !important;
} }
.stats-card { .stats-card {
position: relative; position: relative;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@ -916,18 +777,22 @@ onMounted(async () => {
gap: 16px; gap: 16px;
user-select: none; user-select: none;
} }
.stats-card:focus { .stats-card:focus {
outline: 2px solid rgba(255, 255, 255, 0.5); outline: 2px solid rgba(255, 255, 255, 0.5);
outline-offset: 2px; outline-offset: 2px;
} }
.stats-card.active-filter { .stats-card.active-filter {
transform: translateY(-4px) scale(1.02); transform: translateY(-4px) scale(1.02);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
border: 2px solid rgba(255, 255, 255, 0.8); border: 2px solid rgba(255, 255, 255, 0.8);
} }
.stats-card.active-filter::before { .stats-card.active-filter::before {
background: linear-gradient(45deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); background: linear-gradient(45deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%);
} }
.stats-card::before { .stats-card::before {
content: ''; content: '';
position: absolute; position: absolute;
@ -935,14 +800,16 @@ onMounted(async () => {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%); background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
border-radius: 16px; border-radius: 16px;
z-index: 1; z-index: 1;
} }
.stats-card:hover { .stats-card:hover {
transform: translateY(-8px) scale(1.01); transform: translateY(-8px) scale(1.01);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
} }
.stats-icon { .stats-icon {
position: relative; position: relative;
z-index: 2; z-index: 2;
@ -951,11 +818,13 @@ onMounted(async () => {
padding: 12px; padding: 12px;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
.stats-content { .stats-content {
position: relative; position: relative;
z-index: 2; z-index: 2;
flex: 1; flex: 1;
} }
.stats-number { .stats-number {
font-size: 2.5rem; font-size: 2.5rem;
font-weight: 700; font-weight: 700;
@ -963,24 +832,30 @@ onMounted(async () => {
margin-bottom: 4px; margin-bottom: 4px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
} }
.stats-label { .stats-label {
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 500; font-weight: 500;
opacity: 0.9; opacity: 0.9;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
} }
.total-card { .total-card {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
} }
.active-card { .active-card {
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%); background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
} }
.expired-card { .expired-card {
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
} }
.inactive-card { .inactive-card {
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%); background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.stats-card { .stats-card {
flex-direction: column; flex-direction: column;

View file

@ -0,0 +1,224 @@
<template>
<div class="plugin-intro">
<div class="intro-header">
<div class="plugin-icon">
<i class="fas fa-shield-alt"></i>
</div>
<div class="plugin-info">
<h1>افزونه مدیریت گارانتی</h1>
<p class="plugin-description">
مدیریت چرخهعمر گارانتی از ثبت سریال تا تخصیص، اسکن، ارسال برای مشتری و فعالسازی آنلاین
</p>
<div class="plugin-version">
<span class="version-badge">نسخه 1.0.0</span>
<span v-if="isPluginActive('warranty')" class="status-badge active">فعال</span>
<RouterLink to="/acc/plugin-center/view-end/warranty" v-if="!isPluginActive('warranty')">
<span class="status-badge active text-white d-flex align-items-center">
<i class="fa fa-shopping-cart me-1"></i>
خرید
</span>
</RouterLink>
</div>
</div>
</div>
<div class="intro-content">
<div class="features-section">
<h2>امکانات افزونه</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-barcode"></i></div>
<h3>مدیریت سریال گارانتی</h3>
<p>ثبت/ویرایش سریال، جستجو و فیلتر بر اساس وضعیت، تاریخ شروع/پایان و کالا</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-file-import"></i></div>
<h3>واردسازی انبوه با پیشنمایش</h3>
<p>پیشنمایش فایل و واردسازی انبوه سریالها (CSV/Excel) با اعتبارسنجی</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-link"></i></div>
<h3>تخصیص خودکار به فروش</h3>
<p>تخصیص سریالهای «آزاد» به اقلام حواله خروج بر اساس کالا و تعداد موردنیاز</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-qrcode"></i></div>
<h3>اسکن و تأیید انبار</h3>
<p>اسکن سریال در خروج از انبار و تأیید صحت کالا پیش از خروج (Verified)</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-sms"></i></div>
<h3>ارسال سریال برای خریدار</h3>
<p>ارسال سریالهای تخصیصیافته فاکتور به خریدار از طریق پیامک</p>
</div>
<!-- <div class="feature-card">
<div class="feature-icon"><i class="fas fa-chart-pie"></i></div>
<h3>آمار و وضعیتها</h3>
<p>گزارش تعداد سریالها بر اساس وضعیت و شمارش سریالهای منقضیشده</p>
</div> -->
</div>
</div>
<div class="invoice-types-section">
<h2>چرخه وضعیت سریال</h2>
<div class="invoice-types-grid">
<div class="invoice-type-card">
<div class="type-icon info"><i class="fas fa-dot-circle"></i></div>
<h4>آزاد</h4>
<p>سریال ثبت شده و آماده تخصیص به فروش است</p>
</div>
<div class="invoice-type-card">
<div class="type-icon primary"><i class="fas fa-link"></i></div>
<h4>تخصیص یافته</h4>
<p>سریال به فاکتور/سند فروش اختصاص یافته است</p>
</div>
<div class="invoice-type-card">
<div class="type-icon danger"><i class="fas fa-ban"></i></div>
<h4>باطل</h4>
<p>سریال نامعتبر یا ابطال شده است</p>
</div>
<div class="invoice-type-card">
<div class="type-icon info"><i class="fas fa-check-circle"></i></div>
<h4>مصرفشده</h4>
<p>سریال استفاده شده و قابل تخصیص مجدد نیست</p>
</div>
</div>
</div>
<div class="validation-section">
<h2>فعالسازی توسط مشتری</h2>
<div class="validation-list">
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>مسیر عمومی فعالسازی: ارائه کد گارانتی توسط مشتری و اعتبارسنجی</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>مهلت فعالسازی قابل تنظیم</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>پشتیبانی از کد/رمز فعالسازی حواله برای امنیت بیشتر</span>
</div>
</div>
</div>
<div class="validation-section">
<h2>کنترلها</h2>
<div class="validation-list">
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>جلوگیری از تخصیص تکراری سریال</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>همخوانی نوع کالا با سریال</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>مدیریت تاریخ شروع و پایان گارانتی</span>
</div>
</div>
</div>
<div class="setup-section">
<h2>مراحل راهاندازی</h2>
<div class="setup-steps">
<div class="setup-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>فعالسازی افزونه</h4>
<p>افزونه را از مرکز افزونهها فعال کنید</p>
</div>
</div>
<div class="setup-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>واردسازی سریالها</h4>
<p>سریالها را به صورت دستی یا انبوه اضافه کنید</p>
</div>
</div>
<div class="setup-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>تخصیص به حواله خروج</h4>
<p>سریالها را به اقلام حواله خروج تخصیص دهید</p>
</div>
</div>
</div>
</div>
</div>
<div class="intro-footer">
<div style="margin-top: 20px; font-size: 0.75rem; color: #888; text-align: center;">
Developed by <a href="https://pirouz.xyz" target="_blank" style="color: #667eea; text-decoration: none;">Mohammad Rezai</a> 2025
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'WarrantyIntro',
data() {
return {
plugins: {}
}
},
methods: {
isPluginActive(plugName) {
return this.plugins && this.plugins[plugName] !== undefined;
},
},
mounted() {
axios.post('/api/plugin/get/actives').then((response) => {
this.plugins = response.data;
});
if (this.$store) {
this.$store.commit('setPageTitle', 'مدیریت گارانتی - معرفی افزونه')
}
}
}
</script>
<style scoped>
/* same styles as taxsettings intro for consistent look */
.plugin-intro { max-width: 1200px; margin: 0 auto; padding: 20px; }
.intro-header { display: flex; align-items: center; gap: 20px; margin-bottom: 40px; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white; }
.plugin-icon { font-size: 3rem; background: rgba(255, 255, 255, 0.2); width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; border-radius: 50%; flex-shrink: 0; }
.plugin-info h1 { margin: 0 0 10px 0; font-size: 2.5rem; font-weight: 700; }
.plugin-description { font-size: 1.1rem; margin: 0 0 15px 0; opacity: 0.9; }
.plugin-version { display: flex; gap: 10px; }
.version-badge, .status-badge { padding: 5px 12px; border-radius: 20px; font-size: 0.9rem; font-weight: 600; }
.version-badge { background: rgba(255, 255, 255, 0.2); }
.status-badge.active { background: #28a745; }
.intro-content { display: flex; flex-direction: column; gap: 40px; }
.features-section, .invoice-types-section, .validation-section, .setup-section, .compliance-section { background: white; border-radius: 15px; padding: 30px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
.features-section h2, .invoice-types-section h2, .validation-section h2, .setup-section h2, .compliance-section h2 { margin: 0 0 25px 0; font-size: 1.8rem; color: #333; border-bottom: 3px solid #667eea; padding-bottom: 10px; }
.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 25px; }
.feature-card { padding: 25px; border-radius: 12px; background: #f8f9fa; border-left: 4px solid #667eea; transition: transform 0.3s ease, box-shadow 0.3s ease; }
.feature-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); }
.feature-icon { font-size: 2rem; color: #667eea; margin-bottom: 15px; width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: rgba(102, 126, 234, 0.1); border-radius: 50%; }
.invoice-types-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }
.invoice-type-card { text-align: center; padding: 25px; border-radius: 12px; background: #f8f9fa; border: 2px solid transparent; transition: all 0.3s ease; }
.invoice-type-card:hover { border-color: #667eea; transform: translateY(-3px); }
.type-icon { font-size: 2.5rem; margin-bottom: 15px; width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; border-radius: 50%; margin: 0 auto 15px auto; }
.type-icon.primary { background: #e3f2fd; color: #1976d2; }
.type-icon.warning { background: #fff3e0; color: #f57c00; }
.type-icon.info { background: #e8f5e8; color: #388e3c; }
.validation-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px; }
.validation-item { display: flex; align-items: center; gap: 12px; padding: 15px; background: #f8f9fa; border-radius: 8px; border-right: 4px solid #28a745; }
.setup-steps { display: flex; flex-direction: column; gap: 20px; }
.setup-step { display: flex; align-items: flex-start; gap: 20px; padding: 20px; background: #f8f9fa; border-radius: 12px; border-right: 4px solid #667eea; }
.step-number { background: #667eea; color: white; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.2rem; flex-shrink: 0; }
.action-buttons { display: flex; justify-content: center; gap: 20px; flex-wrap: wrap; }
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; transition: all 0.3s ease; border: none; cursor: pointer; }
.btn-success { background: #28a745; color: white; }
.btn-success:hover { background: #218838; transform: translateY(-2px); }
@media (max-width: 768px) { .plugin-intro { padding: 15px; } .intro-header { flex-direction: column; text-align: center; padding: 20px; gap: 15px; } .plugin-icon { width: 60px; height: 60px; font-size: 2rem; } .plugin-info h1 { font-size: 1.8rem; } .features-section, .invoice-types-section, .validation-section, .setup-section, .compliance-section { padding: 20px; } .features-grid { grid-template-columns: 1fr; gap: 20px; } .feature-card { padding: 20px; } .feature-icon { width: 50px; height: 50px; font-size: 1.5rem; } .invoice-types-grid { grid-template-columns: 1fr; gap: 15px; } .invoice-type-card { padding: 20px; } .type-icon { width: 60px; height: 60px; font-size: 2rem; } .action-buttons { flex-direction: column; gap: 15px; } .btn { justify-content: center; padding: 15px 20px; font-size: 1rem; } }
</style>

View file

@ -4,7 +4,8 @@
<template v-slot:prepend> <template v-slot:prepend>
<v-tooltip :text="$t('dialog.back')" location="bottom"> <v-tooltip :text="$t('dialog.back')" location="bottom">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" /> <v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
</template> </template>
</v-tooltip> </v-tooltip>
</template> </template>
@ -21,14 +22,8 @@
</v-tooltip> </v-tooltip>
<v-tooltip v-if="isPluginActive('taxsettings')" text="ارسال گروهی به کارپوشه مودیان" location="bottom"> <v-tooltip v-if="isPluginActive('taxsettings')" text="ارسال گروهی به کارپوشه مودیان" location="bottom">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn <v-btn v-bind="props" icon="mdi-cloud-upload" color="orange" @click="sendBulkToTaxSystem()"
v-bind="props" :disabled="itemsSelected.length === 0" :loading="bulkLoading"></v-btn>
icon="mdi-cloud-upload"
color="orange"
@click="sendBulkToTaxSystem()"
:disabled="itemsSelected.length === 0"
:loading="bulkLoading"
></v-btn>
</template> </template>
</v-tooltip> </v-tooltip>
<v-menu> <v-menu>
@ -46,7 +41,8 @@
</template> </template>
</v-list-item> </v-list-item>
<v-list-subheader color="primary">{{ $t('dialog.change_labels') }}</v-list-subheader> <v-list-subheader color="primary">{{ $t('dialog.change_labels') }}</v-list-subheader>
<v-list-item v-for="item in types" class="text-dark" :title="$t('dialog.change_to') + ' ' + item.label" @click="changeLabel(item)"> <v-list-item v-for="item in types" class="text-dark" :title="$t('dialog.change_to') + ' ' + item.label"
@click="changeLabel(item)">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-label"></v-icon> <v-icon color="green-darken-4" icon="mdi-label"></v-icon>
</template> </template>
@ -72,7 +68,8 @@
<v-tab value="pending">فاکتورهای در انتظار تایید</v-tab> <v-tab value="pending">فاکتورهای در انتظار تایید</v-tab>
</v-tabs> </v-tabs>
</div> </div>
<v-text-field hide-details color="green" class="pt-0 rounded-0 mb-0" density="compact" :placeholder="$t('dialog.search_txt')" v-model="searchValue" type="text" clearable> <v-text-field hide-details color="green" class="pt-0 rounded-0 mb-0" density="compact"
:placeholder="$t('dialog.search_txt')" v-model="searchValue" type="text" clearable>
<template v-slot:prepend-inner> <template v-slot:prepend-inner>
<v-tooltip location="bottom" :text="$t('dialog.search')"> <v-tooltip location="bottom" :text="$t('dialog.search')">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
@ -102,33 +99,23 @@
</template> </template>
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<v-select class="py-2 my-2" v-model="dateFilter" :items="dateFilterOptions" label="فیلتر تاریخ" @update:model-value="filterTable" dense /> <v-select class="py-2 my-2" v-model="dateFilter" :items="dateFilterOptions" label="فیلتر تاریخ"
@update:model-value="filterTable" dense />
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
</template> </template>
</v-text-field> </v-text-field>
<v-data-table-server <v-data-table-server v-model:items-per-page="serverOptions.rowsPerPage" v-model:page="serverOptions.page"
v-model:items-per-page="serverOptions.rowsPerPage" :headers="visibleHeaders" :items="displayItems" :items-length="displayTotal" :loading="loading"
v-model:page="serverOptions.page" :no-data-text="$t('table.no_data')" v-model="itemsSelected" v-model:expanded="expanded"
:headers="visibleHeaders" @update:options="updateServerOptions" show-select class="elevation-1 data-table-wrapper" item-value="code"
:items="displayItems" :max-height="tableHeight" :header-props="{ class: 'custom-header' }" @update:expanded="onExpandedUpdate"
:items-length="displayTotal" multi-sort>
:loading="loading"
:no-data-text="$t('table.no_data')"
v-model="itemsSelected"
v-model:expanded="expanded"
@update:options="updateServerOptions"
show-select
class="elevation-1 data-table-wrapper"
item-value="code"
:max-height="tableHeight"
:header-props="{ class: 'custom-header' }"
@update:expanded="onExpandedUpdate"
multi-sort
>
<template v-slot:item.expand="{ item }"> <template v-slot:item.expand="{ item }">
<v-btn variant="text" size="small" color="primary" :icon="expanded.includes(item.code) ? 'mdi-chevron-up' : 'mdi-chevron-down'" @click.stop="toggleExpand(item.code)" /> <v-btn variant="text" size="small" color="primary"
:icon="expanded.includes(item.code) ? 'mdi-chevron-up' : 'mdi-chevron-down'"
@click.stop="toggleExpand(item.code)" />
</template> </template>
<template v-slot:item.receivedAmount="{ item }"> <template v-slot:item.receivedAmount="{ item }">
<span class="text-dark"> <span class="text-dark">
@ -141,7 +128,8 @@
<v-btn variant="text" size="small" color="error" icon="mdi-menu" v-bind="props" /> <v-btn variant="text" size="small" color="error" icon="mdi-menu" v-bind="props" />
</template> </template>
<v-list> <v-list>
<v-list-item class="text-dark" :title="$t('dialog.accounting_doc')" :to="'/acc/accounting/view/' + item.code"> <v-list-item class="text-dark" :title="$t('dialog.accounting_doc')"
:to="'/acc/accounting/view/' + item.code">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-file"></v-icon> <v-icon color="green-darken-4" icon="mdi-file"></v-icon>
</template> </template>
@ -151,17 +139,20 @@
<v-icon color="green-darken-4" icon="mdi-eye"></v-icon> <v-icon color="green-darken-4" icon="mdi-eye"></v-icon>
</template> </template>
</v-list-item> </v-list-item>
<v-list-item class="text-dark" :title="$t('dialog.export_pdf')" @click="printOptions.selectedPrintCode = item.code; modal = true;"> <v-list-item class="text-dark" :title="$t('dialog.export_pdf')"
@click="printOptions.selectedPrintCode = item.code; modal = true;">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon icon="mdi-file-pdf-box"></v-icon> <v-icon icon="mdi-file-pdf-box"></v-icon>
</template> </template>
</v-list-item> </v-list-item>
<v-list-item v-if="isPluginActive('taxsettings')" class="text-dark" :title="$t('dialog.send_to_tax_system')" @click="sendToTaxSystem(item.code)"> <v-list-item v-if="isPluginActive('taxsettings') && (item.isApproved || !item.isPreview)" class="text-dark"
:title="$t('dialog.send_to_tax_system')" @click="sendToTaxSystem(item.code)">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon color="orange" icon="mdi-cloud-upload"></v-icon> <v-icon color="orange" icon="mdi-cloud-upload"></v-icon>
</template> </template>
</v-list-item> </v-list-item>
<v-list-item v-if="canShowApprovalButton(item)" class="text-dark" title="تایید فاکتور" @click="approveInvoice(item.code)"> <v-list-item v-if="canShowApprovalButton(item)" class="text-dark" title="تایید فاکتور"
@click="approveInvoice(item.code)">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon color="success">mdi-check-decagram</v-icon> <v-icon color="success">mdi-check-decagram</v-icon>
</template> </template>
@ -240,7 +231,8 @@
<template v-slot:expanded-row="{ item }"> <template v-slot:expanded-row="{ item }">
<tr> <tr>
<td :colspan="visibleHeaders.length" class="expanded-row"> <td :colspan="visibleHeaders.length" class="expanded-row">
<v-progress-circular v-if="loading && expanded.includes(item.code)" indeterminate color="primary" class="my-2" /> <v-progress-circular v-if="loading && expanded.includes(item.code)" indeterminate color="primary"
class="my-2" />
<v-list v-else dense class="expanded-list"> <v-list v-else dense class="expanded-list">
<v-list-item v-for="row in item.rows" :key="row.id"> <v-list-item v-for="row in item.rows" :key="row.id">
<v-list-item-subtitle> <v-list-item-subtitle>
@ -270,7 +262,8 @@
</div> </div>
<div class="summary-item"> <div class="summary-item">
<span class="summary-label">جمع سود موارد انتخاب شده:</span> <span class="summary-label">جمع سود موارد انتخاب شده:</span>
<span class="summary-value" :class="{'text-success': sumSelectedProfit >= 0, 'text-danger': sumSelectedProfit < 0}"> <span class="summary-value"
:class="{ 'text-success': sumSelectedProfit >= 0, 'text-danger': sumSelectedProfit < 0 }">
{{ $filters.formatNumber(Math.abs(sumSelectedProfit)) }} {{ $filters.formatNumber(Math.abs(sumSelectedProfit)) }}
<span v-if="sumSelectedProfit < 0">(زیان)</span> <span v-if="sumSelectedProfit < 0">(زیان)</span>
</span> </span>
@ -289,7 +282,8 @@
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col v-for="header in allHeaders" :key="header.value" cols="12" sm="4" class="my-0 py-0"> <v-col v-for="header in allHeaders" :key="header.value" cols="12" sm="4" class="my-0 py-0">
<v-checkbox v-model="header.visible" :label="header.title" @update:model-value="updateColumnVisibility" hide-details="auto" /> <v-checkbox v-model="header.visible" :label="header.title" @update:model-value="updateColumnVisibility"
hide-details="auto" />
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
@ -322,39 +316,46 @@
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-tooltip :text="$t('dialog.bid_info_label')" location="right"> <v-tooltip :text="$t('dialog.bid_info_label')" location="right">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-switch v-bind="props" inset v-model="printOptions.bidInfo" color="primary" :label="$t('dialog.bid_info_label')" hide-details></v-switch> <v-switch v-bind="props" inset v-model="printOptions.bidInfo" color="primary"
:label="$t('dialog.bid_info_label')" hide-details></v-switch>
</template> </template>
</v-tooltip> </v-tooltip>
<v-tooltip :text="$t('dialog.invoice_pays')" location="right"> <v-tooltip :text="$t('dialog.invoice_pays')" location="right">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-switch v-bind="props" inset v-model="printOptions.pays" color="primary" :label="$t('dialog.invoice_pays')" hide-details></v-switch> <v-switch v-bind="props" inset v-model="printOptions.pays" color="primary"
:label="$t('dialog.invoice_pays')" hide-details></v-switch>
</template> </template>
</v-tooltip> </v-tooltip>
<v-tooltip :text="$t('dialog.invoice_footer_note')" location="right"> <v-tooltip :text="$t('dialog.invoice_footer_note')" location="right">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-switch v-bind="props" inset v-model="printOptions.note" color="primary" :label="$t('dialog.invoice_footer_note')" hide-details></v-switch> <v-switch v-bind="props" inset v-model="printOptions.note" color="primary"
:label="$t('dialog.invoice_footer_note')" hide-details></v-switch>
</template> </template>
</v-tooltip> </v-tooltip>
</v-col> </v-col>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-tooltip :text="$t('dialog.tax_dexpo')" location="right"> <v-tooltip :text="$t('dialog.tax_dexpo')" location="right">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-switch v-bind="props" inset v-model="printOptions.taxInfo" color="primary" :label="$t('dialog.tax_dexpo')" hide-details></v-switch> <v-switch v-bind="props" inset v-model="printOptions.taxInfo" color="primary"
:label="$t('dialog.tax_dexpo')" hide-details></v-switch>
</template> </template>
</v-tooltip> </v-tooltip>
<v-tooltip :text="$t('dialog.discount_dexpo')" location="right"> <v-tooltip :text="$t('dialog.discount_dexpo')" location="right">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-switch v-bind="props" inset v-model="printOptions.discountInfo" color="primary" :label="$t('dialog.discount_dexpo')" hide-details></v-switch> <v-switch v-bind="props" inset v-model="printOptions.discountInfo" color="primary"
:label="$t('dialog.discount_dexpo')" hide-details></v-switch>
</template> </template>
</v-tooltip> </v-tooltip>
<v-tooltip :text="$t('dialog.business_stamp')" location="right"> <v-tooltip :text="$t('dialog.business_stamp')" location="right">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.businessStamp" color="primary" :label="$t('dialog.business_stamp')" hide-details></v-switch> <v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.businessStamp"
color="primary" :label="$t('dialog.business_stamp')" hide-details></v-switch>
</template> </template>
</v-tooltip> </v-tooltip>
<v-tooltip :text="$t('dialog.invoice_index')" location="right"> <v-tooltip :text="$t('dialog.invoice_index')" location="right">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.invoiceIndex" color="primary" :label="$t('dialog.invoice_index')" hide-details></v-switch> <v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.invoiceIndex"
color="primary" :label="$t('dialog.invoice_index')" hide-details></v-switch>
</template> </template>
</v-tooltip> </v-tooltip>
</v-col> </v-col>
@ -593,7 +594,7 @@ export default defineComponent({
this.items = all; this.items = all;
this.total = Number(response.data.total) || 0; this.total = Number(response.data.total) || 0;
if (this.business.requireTwoStepApproval) { if (this.business.requireTwoStepApproval) {
this.itemsApproved = all.filter(i => i.isApproved === true); this.itemsApproved = all.filter(i => i.isApproved === true || (!i.isPreview && !i.isApproved));
this.itemsPending = all.filter(i => i.isPreview === true && i.isApproved !== true); this.itemsPending = all.filter(i => i.isPreview === true && i.isApproved !== true);
this.totalApproved = this.itemsApproved.length; this.totalApproved = this.itemsApproved.length;
this.totalPending = this.itemsPending.length; this.totalPending = this.itemsPending.length;
@ -752,21 +753,37 @@ export default defineComponent({
} }
} catch (error) { } catch (error) {
console.error('خطا در دریافت فایل PDF:', error); console.error('خطا در دریافت فایل PDF:', error);
if (error?.response?.data?.message) {
Swal.fire({
text: error?.response?.data?.message,
icon: 'warning',
confirmButtonText: 'قبول'
});
} else {
Swal.fire({ Swal.fire({
text: 'خطا در دریافت فایل PDF', text: 'خطا در دریافت فایل PDF',
icon: 'error', icon: 'error',
confirmButtonText: 'قبول' confirmButtonText: 'قبول'
}); });
}
} finally { } finally {
this.loading = false; this.loading = false;
} }
}).catch((error) => { }).catch((error) => {
this.loading = false; this.loading = false;
if (error?.response?.data?.message) {
Swal.fire({
text: error?.response?.data?.message,
icon: 'warning',
confirmButtonText: 'قبول'
});
} else {
Swal.fire({ Swal.fire({
text: 'خطا در ایجاد نسخه PDF', text: 'خطا در ایجاد نسخه PDF',
icon: 'error', icon: 'error',
confirmButtonText: 'قبول' confirmButtonText: 'قبول'
}); });
}
}); });
}, },
deleteItem(code) { deleteItem(code) {
@ -933,6 +950,17 @@ export default defineComponent({
return; return;
} }
const selectedInvoices = this.items.filter(inv => this.itemsSelected.includes(inv.code));
if (selectedInvoices.some(inv => !inv.isApproved && inv.isPreview)) {
Swal.fire({
text: 'بعضی از فاکتورهای انتخابی هنوز تایید نشده‌اند و قابل ارسال به سامانه مودیان نیستند.',
icon: 'warning',
confirmButtonText: 'قبول'
});
return;
}
const selectedCount = this.itemsSelected.length; const selectedCount = this.itemsSelected.length;
const result = await Swal.fire({ const result = await Swal.fire({

View file

@ -24,7 +24,10 @@
<v-tab value="2"> <v-tab value="2">
{{ $t('dialog.global_settings') }} {{ $t('dialog.global_settings') }}
</v-tab> </v-tab>
<v-tab value="3" v-if="showBackupTab"> <v-tab value="3" v-if="isPluginActive('warranty')">
{{ $t('dialog.warranty_settings') }}
</v-tab>
<v-tab value="4" v-if="showBackupTab">
نسخه پشتیبان نسخه پشتیبان
</v-tab> </v-tab>
</v-tabs> </v-tabs>
@ -195,6 +198,63 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="3" v-if="isPluginActive('warranty')">
<v-card>
<v-card-text>
<h3 class="text-primary mb-6">تنظیمات گارانتی</h3>
<v-row>
<v-col cols="12" md="8">
<v-card variant="outlined" class="mb-6">
<v-card-title class="text-h6 text-primary">
<v-icon icon="mdi-shield-lock-outline" class="mr-2"></v-icon>
رفتار گارانتی در حواله خروج
</v-card-title>
<v-card-text>
<v-switch
v-model="content.requireWarrantyOnDelivery"
label="الزام ثبت گارانتی هنگام صدور حواله خروج"
color="primary"
hide-details
class="mb-2"
/>
<v-switch
v-model="content.matchWarrantyToSerial"
label="تطبیق خودکار گارانتی با سریال کالا (در صورت یکسان بودن کد)"
color="primary"
hide-details
class="mb-2"
/>
<v-text-field
v-model.number="content.activationGraceDays"
type="number"
min="0"
label="مهلت فعال‌سازی گارانتی (روز)"
variant="outlined"
density="compact"
hide-details
/>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="4">
<v-card variant="outlined">
<v-card-title class="text-h6 text-primary">
<v-icon icon="mdi-information-outline" class="mr-2"></v-icon>
نکات
</v-card-title>
<v-card-text>
<v-list density="compact">
<v-list-item title="این تنظیمات برای همین کسب‌وکار ذخیره می‌شود." />
<v-list-item title="در صورت فعال بودن الزام، ثبت حواله بدون سریال مجاز نیست." />
<v-list-item title="مهلت فعال‌سازی فقط برای مسیر فعال‌سازی مشتری استفاده می‌شود." />
</v-list>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-tabs-window-item>
<v-tabs-window-item value="1"> <v-tabs-window-item value="1">
<v-card> <v-card>
<v-card-text> <v-card-text>
@ -534,7 +594,7 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="3" v-if="showBackupTab"> <v-tabs-window-item value="4" v-if="showBackupTab">
<v-card> <v-card>
<v-card-text> <v-card-text>
<h3 class="text-primary mb-4">نسخه پشتیبان از اطلاعات کسب و کار</h3> <h3 class="text-primary mb-4">نسخه پشتیبان از اطلاعات کسب و کار</h3>
@ -693,7 +753,10 @@ export default {
}, },
updateSellPrice: false, updateSellPrice: false,
updateBuyPrice: false, updateBuyPrice: false,
profitCalcType: 'lis' profitCalcType: 'lis',
warrantyRequireOnDelivery: false,
warrantyActivationGraceDays: 7,
warrantyMatchToSerial: false
}, },
users: [], users: [],
listBanks: [], listBanks: [],
@ -814,7 +877,10 @@ export default {
'year': this.content.year, 'year': this.content.year,
'commodityUpdateBuyPriceAuto': this.content.updateBuyPrice, 'commodityUpdateBuyPriceAuto': this.content.updateBuyPrice,
'commodityUpdateSellPriceAuto': this.content.updateSellPrice, 'commodityUpdateSellPriceAuto': this.content.updateSellPrice,
'profitCalcType': this.content.profitCalcType 'profitCalcType': this.content.profitCalcType,
'requireWarrantyOnDelivery': this.content.requireWarrantyOnDelivery,
'activationGraceDays': this.content.activationGraceDays,
'matchWarrantyToSerial': this.content.matchWarrantyToSerial
}; };
axios.post('/api/business/insert', data) axios.post('/api/business/insert', data)

View file

@ -3,6 +3,7 @@ import { ref, onMounted, computed } from 'vue'
import axios from 'axios' import axios from 'axios'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import moment from 'jalali-moment' import moment from 'jalali-moment'
import Swal from 'sweetalert2'
interface Business { interface Business {
legal_name: string legal_name: string
@ -136,6 +137,13 @@ const printInvoice = async () => {
}) })
window.open(`${import.meta.env.VITE_API_URL}/front/print/${response.data.id}`, '_blank', 'noreferrer') window.open(`${import.meta.env.VITE_API_URL}/front/print/${response.data.id}`, '_blank', 'noreferrer')
} catch (error) { } catch (error) {
if (error?.response?.data?.message) {
Swal.fire({
text: error?.response?.data?.message,
icon: 'warning',
confirmButtonText: 'قبول'
});
}
console.error('Error printing invoice:', error) console.error('Error printing invoice:', error)
} }
} }