bug fix in modules and start working on hrm plugin
This commit is contained in:
parent
78f296cdcf
commit
cdbe6e3ae9
|
@ -542,6 +542,7 @@ class BusinessController extends AbstractController
|
|||
'plugAccproCloseYear' => true,
|
||||
'plugAccproPresell' => true,
|
||||
'plugRepservice' => true,
|
||||
'plugHrmDocs' => true,
|
||||
];
|
||||
} elseif ($perm) {
|
||||
$result = [
|
||||
|
@ -583,6 +584,7 @@ class BusinessController extends AbstractController
|
|||
'plugAccproCloseYear' => $perm->isPlugAccproCloseYear(),
|
||||
'plugRepservice' => $perm->isPlugRepservice(),
|
||||
'plugAccproPresell' => $perm->isPlugAccproPresell(),
|
||||
'plugHrmDocs' => $perm->isPlugHrmDocs(),
|
||||
];
|
||||
}
|
||||
return $this->json($result);
|
||||
|
@ -650,6 +652,7 @@ class BusinessController extends AbstractController
|
|||
$perm->setPlugAccproPresell($params['plugAccproPresell']);
|
||||
$perm->setPlugAccproAccounting($params['plugAccproAccounting']);
|
||||
$perm->setPlugRepservice($params['plugRepservice']);
|
||||
$perm->setPlugHrmDocs($params['plugHrmDocs']);
|
||||
$entityManager->persist($perm);
|
||||
$entityManager->flush();
|
||||
$log->insert('تنظیمات پایه', 'ویرایش دسترسیهای کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business);
|
||||
|
|
|
@ -269,10 +269,10 @@ class CostController extends AbstractController
|
|||
->setParameter('today', $today);
|
||||
break;
|
||||
case 'custom':
|
||||
if (isset($filters['dateFrom']) && isset($filters['dateTo'])) {
|
||||
if (isset($filters['date']) && isset($filters['date']['from']) && isset($filters['date']['to'])) {
|
||||
$queryBuilder->andWhere('d.date BETWEEN :dateFrom AND :dateTo')
|
||||
->setParameter('dateFrom', $filters['dateFrom'])
|
||||
->setParameter('dateTo', $filters['dateTo']);
|
||||
->setParameter('dateFrom', $filters['date']['from'])
|
||||
->setParameter('dateTo', $filters['date']['to']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -123,6 +123,9 @@ class Permission
|
|||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $plugAccproPresell = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $plugHrmDocs = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -560,4 +563,16 @@ class Permission
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function isPlugHrmDocs(): ?bool
|
||||
{
|
||||
return $this->plugHrmDocs;
|
||||
}
|
||||
|
||||
public function setPlugHrmDocs(?bool $plugHrmDocs): static
|
||||
{
|
||||
$this->plugHrmDocs = $plugHrmDocs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"@zxing/browser": "^0.1.5",
|
||||
"@zxing/library": "^0.21.3",
|
||||
"animate.css": "^4.1.1",
|
||||
"apexcharts": "^4.6.0",
|
||||
"axios": "^1.8.4",
|
||||
|
@ -27,6 +29,7 @@
|
|||
"date-fns-jalali": "^3.2.0-0",
|
||||
"downloadjs": "^1.4.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"jalali-moment": "^3.3.11",
|
||||
"libphonenumber-js": "^1.12.7",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -42,6 +45,7 @@
|
|||
"vue-loading-overlay": "^6.0.6",
|
||||
"vue-media-upload": "^2.2.4",
|
||||
"vue-persian-datetime-picker": "^2.10.4",
|
||||
"vue-qrcode-reader": "^5.7.2",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-select": "^4.0.0-beta.6",
|
||||
"vue-spinner": "^1.0.4",
|
||||
|
@ -49,6 +53,7 @@
|
|||
"vue3-easy-data-table": "^1.5.47",
|
||||
"vue3-perfect-scrollbar": "^2.0.0",
|
||||
"vue3-persian-datetime-picker": "^1.2.2",
|
||||
"vue3-qrcode-reader": "^0.0.1",
|
||||
"vue3-tel-input": "^1.0.4",
|
||||
"vue3-treeselect": "^0.1.10",
|
||||
"vue3-treeview": "^0.4.2",
|
||||
|
|
BIN
webUI/public/img/plugins/hmr.jpg
Normal file
BIN
webUI/public/img/plugins/hmr.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
webUI/public/sounds/beep.mp3
Normal file
BIN
webUI/public/sounds/beep.mp3
Normal file
Binary file not shown.
471
webUI/src/components/application/buttons/ShortcutsButton.vue
Normal file
471
webUI/src/components/application/buttons/ShortcutsButton.vue
Normal file
|
@ -0,0 +1,471 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-menu location="bottom end" :close-on-content-click="false">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="" stacked v-bind="props">
|
||||
<v-icon>mdi-apps</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-card min-width="300" class="shortcuts-menu">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="primary">mdi-apps</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-h6">دسترسیهای سریع</v-list-item-title>
|
||||
<template v-slot:append>
|
||||
<v-tooltip
|
||||
location="top"
|
||||
text="افزودن دسترسی سریع جدید"
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
color="primary"
|
||||
size="small"
|
||||
v-bind="props"
|
||||
@click="showAddDialog = true"
|
||||
>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-0"></v-divider>
|
||||
|
||||
<template v-if="!customShortcuts || customShortcuts.length === 0">
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-center py-4">
|
||||
<v-icon
|
||||
size="32"
|
||||
color="grey-lighten-1"
|
||||
class="mb-2 d-block mx-auto"
|
||||
>
|
||||
mdi-star-outline
|
||||
</v-icon>
|
||||
<span class="text-body-2 text-grey">
|
||||
هیچ دسترسی سریعی تعریف نشده است
|
||||
</span>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<v-list-item
|
||||
v-for="(shortcut, index) in customShortcuts"
|
||||
:key="index"
|
||||
class="shortcut-item"
|
||||
@click="navigateTo(shortcut.path)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar
|
||||
:color="getRandomColor(index)"
|
||||
size="32"
|
||||
class="shortcut-icon"
|
||||
>
|
||||
<v-icon :icon="shortcut.icon" color="white" size="18"></v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
|
||||
<v-list-item-title class="text-body-2">
|
||||
{{ shortcut.name }}
|
||||
</v-list-item-title>
|
||||
|
||||
<template v-slot:append>
|
||||
<div class="shortcut-actions" :class="{ 'always-show': showActions }">
|
||||
<v-btn
|
||||
v-show="showActions"
|
||||
icon
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="primary"
|
||||
@click.stop="editShortcut(index)"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-show="showActions"
|
||||
icon
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="error"
|
||||
@click.stop="deleteShortcut(index)"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<v-divider class="my-0"></v-divider>
|
||||
|
||||
<v-list-item class="edit-mode-item">
|
||||
<template v-slot:prepend>
|
||||
<v-icon size="small" color="primary">mdi-pencil</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-caption">حالت ویرایش</v-list-item-title>
|
||||
<template v-slot:append>
|
||||
<v-tooltip
|
||||
location="bottom"
|
||||
text="نمایش دکمههای ویرایش و حذف"
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-switch
|
||||
v-model="showActions"
|
||||
color="primary"
|
||||
hide-details
|
||||
density="compact"
|
||||
class="edit-mode-switch"
|
||||
v-bind="props"
|
||||
></v-switch>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
|
||||
<!-- دیالوگ افزودن/ویرایش دسترسی سریع -->
|
||||
<v-dialog v-model="showAddDialog" max-width="500">
|
||||
<v-card class="shortcut-dialog">
|
||||
<v-card-title class="text-h6 pa-4 d-flex align-center">
|
||||
<v-icon color="primary" class="me-2">{{ editingIndex === null ? 'mdi-plus' : 'mdi-pencil' }}</v-icon>
|
||||
{{ editingIndex === null ? 'افزودن دسترسی سریع' : 'ویرایش دسترسی سریع' }}
|
||||
</v-card-title>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text class="pa-6">
|
||||
<v-form @submit.prevent="saveShortcut">
|
||||
<v-text-field
|
||||
v-model="newShortcut.name"
|
||||
label="نام"
|
||||
required
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-4"
|
||||
:rules="[v => !!v || 'نام الزامی است']"
|
||||
autofocus
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="newShortcut.path"
|
||||
label="مسیر"
|
||||
required
|
||||
hint="برای آدرس داخلی: /acc/dashboard - برای آدرس خارجی: https://example.com"
|
||||
persistent-hint
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-4"
|
||||
:rules="[
|
||||
v => !!v || 'مسیر الزامی است',
|
||||
v => isInternalPath(v) || v.startsWith('http') || 'مسیر باید با / یا http شروع شود'
|
||||
]"
|
||||
></v-text-field>
|
||||
|
||||
<v-select
|
||||
v-model="newShortcut.icon"
|
||||
:items="availableIcons"
|
||||
label="آیکون"
|
||||
required
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
return-object
|
||||
:rules="[v => !!v || 'آیکون الزامی است']"
|
||||
>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props">
|
||||
<template v-slot:prepend>
|
||||
<v-icon :icon="item.raw.value" color="primary"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-slot:selection="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon :icon="item.raw.value" color="primary" class="me-2"></v-icon>
|
||||
<span>{{ item.raw.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="text"
|
||||
@click="showAddDialog = false"
|
||||
class="px-4"
|
||||
>
|
||||
انصراف
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="saveShortcut"
|
||||
:loading="saving"
|
||||
class="px-4"
|
||||
>
|
||||
{{ editingIndex === null ? 'افزودن' : 'ویرایش' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ShortcutsButton',
|
||||
data() {
|
||||
return {
|
||||
showAddDialog: false,
|
||||
editingIndex: null,
|
||||
customShortcuts: [],
|
||||
saving: false,
|
||||
showActions: false,
|
||||
newShortcut: {
|
||||
name: '',
|
||||
path: '',
|
||||
icon: 'mdi-link'
|
||||
},
|
||||
availableIcons: [
|
||||
{ title: 'لینک', value: 'mdi-link' },
|
||||
{ title: 'داشبورد', value: 'mdi-view-dashboard' },
|
||||
{ title: 'کاربران', value: 'mdi-account-group' },
|
||||
{ title: 'گزارشها', value: 'mdi-chart-bar' },
|
||||
{ title: 'تنظیمات', value: 'mdi-cog' },
|
||||
{ title: 'فایل', value: 'mdi-file' },
|
||||
{ title: 'پیام', value: 'mdi-message' },
|
||||
{ title: 'نوتیفیکیشن', value: 'mdi-bell' },
|
||||
{ title: 'فروش', value: 'mdi-cart' },
|
||||
{ title: 'خرید', value: 'mdi-cart-arrow-down' },
|
||||
{ title: 'انبار', value: 'mdi-warehouse' },
|
||||
{ title: 'بانک', value: 'mdi-bank' },
|
||||
{ title: 'صندوق', value: 'mdi-cash-register' },
|
||||
{ title: 'چک', value: 'mdi-checkbook' },
|
||||
{ title: 'حسابداری', value: 'mdi-calculator' },
|
||||
{ title: 'گزارش', value: 'mdi-file-document' },
|
||||
{ title: 'فاکتور جدید', value: 'mdi-file-plus' },
|
||||
{ title: 'فاکتور فروش', value: 'mdi-file-document-edit' },
|
||||
{ title: 'فاکتور خرید', value: 'mdi-file-document-edit-outline' },
|
||||
{ title: 'شخص جدید', value: 'mdi-account-plus' },
|
||||
{ title: 'مشتری', value: 'mdi-account' },
|
||||
{ title: 'تامین کننده', value: 'mdi-account-tie' },
|
||||
{ title: 'کالا جدید', value: 'mdi-package-variant-plus' },
|
||||
{ title: 'کالا', value: 'mdi-package-variant' },
|
||||
{ title: 'دستهبندی', value: 'mdi-shape' },
|
||||
{ title: 'قیمت', value: 'mdi-currency-usd' },
|
||||
{ title: 'تخفیف', value: 'mdi-tag-multiple' },
|
||||
{ title: 'مالیات', value: 'mdi-percent' },
|
||||
{ title: 'پرداخت', value: 'mdi-cash' },
|
||||
{ title: 'دریافت', value: 'mdi-cash-plus' },
|
||||
{ title: 'انتقال وجه', value: 'mdi-bank-transfer' },
|
||||
{ title: 'صورتحساب', value: 'mdi-receipt' },
|
||||
{ title: 'سند حسابداری', value: 'mdi-book-open-variant' },
|
||||
{ title: 'دفتر کل', value: 'mdi-book' },
|
||||
{ title: 'دفتر روزنامه', value: 'mdi-book-open' },
|
||||
{ title: 'ترازنامه', value: 'mdi-scale-balance' },
|
||||
{ title: 'سود و زیان', value: 'mdi-chart-line' },
|
||||
{ title: 'جریان نقدی', value: 'mdi-cash-flow' },
|
||||
{ title: 'بودجه', value: 'mdi-chart-box' },
|
||||
{ title: 'پیشبینی', value: 'mdi-chart-timeline-variant' },
|
||||
{ title: 'دوره مالی', value: 'mdi-calendar-clock' },
|
||||
{ title: 'سال مالی', value: 'mdi-calendar-range' },
|
||||
{ title: 'بستن دوره', value: 'mdi-calendar-check' },
|
||||
{ title: 'تنظیمات مالی', value: 'mdi-cog-outline' },
|
||||
{ title: 'تعریف حساب', value: 'mdi-account-cog' },
|
||||
{ title: 'تعریف مرکز هزینه', value: 'mdi-account-group-outline' },
|
||||
{ title: 'تعریف پروژه', value: 'mdi-briefcase-outline' }
|
||||
],
|
||||
colors: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'success',
|
||||
'info',
|
||||
'warning',
|
||||
'error'
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadShortcuts()
|
||||
},
|
||||
methods: {
|
||||
loadShortcuts() {
|
||||
const savedShortcuts = localStorage.getItem('customShortcuts')
|
||||
if (savedShortcuts) {
|
||||
this.customShortcuts = JSON.parse(savedShortcuts)
|
||||
}
|
||||
},
|
||||
isInternalPath(path) {
|
||||
return path.startsWith('/acc/') || path.startsWith('/')
|
||||
},
|
||||
navigateTo(path) {
|
||||
if (!this.showActions) {
|
||||
if (this.isInternalPath(path)) {
|
||||
this.$router.push(path)
|
||||
} else {
|
||||
window.open(path, '_blank')
|
||||
}
|
||||
}
|
||||
},
|
||||
saveShortcuts() {
|
||||
localStorage.setItem('customShortcuts', JSON.stringify(this.customShortcuts))
|
||||
},
|
||||
getRandomColor(index) {
|
||||
return this.colors[index % this.colors.length]
|
||||
},
|
||||
async saveShortcut() {
|
||||
this.saving = true
|
||||
try {
|
||||
if (!this.newShortcut.path) {
|
||||
throw new Error('مسیر نمیتواند خالی باشد')
|
||||
}
|
||||
|
||||
if (!this.isInternalPath(this.newShortcut.path) && !this.newShortcut.path.startsWith('http')) {
|
||||
throw new Error('برای آدرسهای خارجی باید از http یا https استفاده کنید')
|
||||
}
|
||||
|
||||
const shortcutToSave = {
|
||||
name: this.newShortcut.name,
|
||||
path: this.newShortcut.path,
|
||||
icon: this.newShortcut.icon.value
|
||||
}
|
||||
|
||||
if (this.editingIndex === null) {
|
||||
this.customShortcuts.push(shortcutToSave)
|
||||
} else {
|
||||
this.customShortcuts[this.editingIndex] = shortcutToSave
|
||||
}
|
||||
this.saveShortcuts()
|
||||
this.showAddDialog = false
|
||||
this.resetNewShortcut()
|
||||
} catch (error) {
|
||||
this.$toast.error(error.message)
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
editShortcut(index) {
|
||||
this.editingIndex = index
|
||||
this.newShortcut = { ...this.customShortcuts[index] }
|
||||
this.showAddDialog = true
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
},
|
||||
deleteShortcut(index) {
|
||||
this.customShortcuts.splice(index, 1)
|
||||
this.saveShortcuts()
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
},
|
||||
resetNewShortcut() {
|
||||
this.newShortcut = {
|
||||
name: '',
|
||||
path: '',
|
||||
icon: 'mdi-link'
|
||||
}
|
||||
this.editingIndex = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.shortcuts-menu {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.shortcut-item {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.shortcut-item:hover {
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
}
|
||||
|
||||
.shortcut-actions {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.shortcut-actions.always-show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.shortcut-item:hover .shortcut-actions:not(.always-show) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.shortcut-icon {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.shortcut-item:hover .shortcut-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.shortcut-btn {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shortcut-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.shortcut-btn:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.edit-mode-item {
|
||||
min-height: 36px !important;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.edit-mode-switch {
|
||||
transform: scale(0.8);
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
font-size: 0.75rem;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.shortcut-dialog {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.shortcut-dialog .v-card-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.shortcut-dialog .v-card-text {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.shortcut-dialog .v-btn {
|
||||
min-width: 100px;
|
||||
}
|
||||
</style>
|
|
@ -2,10 +2,17 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios';
|
||||
|
||||
interface NotificationItem {
|
||||
id: number;
|
||||
message: string;
|
||||
date: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "notifications_btn",
|
||||
data: () => ({
|
||||
items: [],
|
||||
items: [] as NotificationItem[],
|
||||
timeoutId: null as number | null, // برای ذخیره ID تایمر
|
||||
}),
|
||||
components: {},
|
||||
|
@ -19,7 +26,7 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
jump(item) {
|
||||
jump(item: NotificationItem) {
|
||||
axios.post('/api/notifications/read/' + item.id).then((response) => {
|
||||
if (item.url.startsWith('http')) {
|
||||
window.location.href = item.url;
|
||||
|
@ -46,7 +53,7 @@ export default defineComponent({
|
|||
|
||||
<template>
|
||||
<v-menu location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<template #activator="{ props }">
|
||||
<v-btn v-bind="props" stacked>
|
||||
<v-badge color="error" :content="items.length">
|
||||
<v-icon icon="mdi-bell"></v-icon>
|
||||
|
@ -56,14 +63,14 @@ export default defineComponent({
|
|||
<v-card prepend-icon="mdi-bell" :subtitle="$t('dialog.unread_notifications')" :title="$t('dialog.notifications')">
|
||||
<v-list>
|
||||
<v-list-item v-for="(item, i) in items" :key="i" :value="item" @click="jump(item)">
|
||||
<template v-slot:prepend>
|
||||
<template #prepend>
|
||||
<v-icon color="primary" icon="mdi-alert-box-outline"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-primary" v-text="item.message"></v-list-item-title>
|
||||
<v-list-item-subtitle v-text="item.date"></v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="items.length == 0">
|
||||
<template v-slot:prepend>
|
||||
<template #prepend>
|
||||
<v-icon color="primary" icon="mdi-cards-heart"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-primary" v-text="$t('dialog.no_notification')"></v-list-item-title>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,10 +34,29 @@
|
|||
v-for="item in filteredItems"
|
||||
:key="item.id"
|
||||
@click="selectItem(item)"
|
||||
class="mb-1"
|
||||
class="mb-2 search-result-item"
|
||||
:class="{ 'selected-item': selectedItem?.id === item.id }"
|
||||
>
|
||||
<v-list-item-title class="text-right">{{ item.nikename }}</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-right">{{ item.code }}</v-list-item-subtitle>
|
||||
<div class="d-flex flex-column w-100">
|
||||
<div class="d-flex align-center justify-space-between mb-1">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="small" color="primary" class="mr-1">mdi-cellphone</v-icon>
|
||||
<span class="text-caption text-primary-dark">{{ item.mobile || 'بدون موبایل' }}</span>
|
||||
</div>
|
||||
<span class="text-caption text-grey-darken-1">{{ item.code }}</span>
|
||||
</div>
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<span class="text-body-2 font-weight-medium text-primary-dark">{{ item.nikename }}</span>
|
||||
<v-chip
|
||||
size="small"
|
||||
:color="getBalanceColor(item.balance)"
|
||||
class="balance-chip"
|
||||
:class="getBalanceTextColor(item.balance)"
|
||||
>
|
||||
{{ formatBalance(item.balance) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
@ -609,6 +628,23 @@ export default {
|
|||
if (!this.loading && this.filteredItems.length === 0) {
|
||||
this.showAddDialog = true;
|
||||
}
|
||||
},
|
||||
getBalanceColor(balance) {
|
||||
if (!balance) return 'grey-lighten-3';
|
||||
return balance > 0 ? 'green-lighten-5' : balance < 0 ? 'red-lighten-5' : 'grey-lighten-3';
|
||||
},
|
||||
getBalanceTextColor(balance) {
|
||||
if (!balance) return 'text-grey-darken-2';
|
||||
return balance > 0 ? 'text-green-darken-2' : balance < 0 ? 'text-red-darken-2' : 'text-grey-darken-2';
|
||||
},
|
||||
formatBalance(balance) {
|
||||
if (!balance) return 'بدون تراز';
|
||||
return balance > 0 ? `بستانکار: ${this.formatNumber(balance)}` :
|
||||
balance < 0 ? `بدهکار: ${this.formatNumber(Math.abs(balance))}` :
|
||||
'بدون تراز';
|
||||
},
|
||||
formatNumber(number) {
|
||||
return new Intl.NumberFormat('fa-IR').format(number);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -676,4 +712,74 @@ export default {
|
|||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(var(--v-theme-outline), 0.1);
|
||||
background-color: rgb(var(--v-theme-surface));
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.search-result-item:hover {
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
border-color: rgba(var(--v-theme-primary), 0.2);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
background-color: rgba(var(--v-theme-primary), 0.08);
|
||||
border-color: rgba(var(--v-theme-primary), 0.5);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.balance-chip {
|
||||
min-width: 110px;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:deep(.v-list-item__content) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
:deep(.v-list-item) {
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
:deep(.v-chip) {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:deep(.text-green-darken-2) {
|
||||
color: #1b5e20 !important;
|
||||
}
|
||||
|
||||
:deep(.text-red-darken-2) {
|
||||
color: #b71c1c !important;
|
||||
}
|
||||
|
||||
:deep(.text-grey-darken-2) {
|
||||
color: #424242 !important;
|
||||
}
|
||||
|
||||
:deep(.text-primary-dark) {
|
||||
color: rgb(var(--v-theme-primary-dark)) !important;
|
||||
}
|
||||
|
||||
:deep(.v-list) {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
:deep(.v-card) {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.v-card-text) {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -86,6 +86,8 @@ const en_lang = {
|
|||
cheque_output: "Cheque Output",
|
||||
presells: "Presells",
|
||||
presell_view: "View Presell",
|
||||
hrm: 'HR & Payroll',
|
||||
hrm_docs: 'Payroll Document',
|
||||
},
|
||||
};
|
||||
export default en_lang
|
||||
|
|
|
@ -190,6 +190,8 @@ const fa_lang = {
|
|||
plugins_invoices: "صورت حسابها",
|
||||
repservice: "مدیریت تعمیرگاه",
|
||||
repservice_reqs: "درخواستها",
|
||||
hrm: 'منابع انسانی',
|
||||
hrm_docs: 'سند حقوق',
|
||||
},
|
||||
time: {
|
||||
month: "{id} ماه",
|
||||
|
@ -494,6 +496,32 @@ const fa_lang = {
|
|||
presell_info: "اطلاعات پیش فاکتور",
|
||||
financial_info: "اطلاعات مالی",
|
||||
invoice_items: "اقلام فاکتور",
|
||||
hrm: {
|
||||
title: "سند حقوق",
|
||||
date: "تاریخ",
|
||||
description: "توضیحات",
|
||||
person: "شخص",
|
||||
base_salary: "حقوق پایه",
|
||||
overtime: "اضافه کار",
|
||||
shift: "حق شیفت",
|
||||
night: "شب کاری",
|
||||
total: "جمع کل",
|
||||
row_description: "شرح",
|
||||
add_new_row: "افزودن سطر جدید",
|
||||
no_data: "هیچ دادهای ثبت نشده است",
|
||||
delete_confirm: "آیا مطمئن هستید که میخواهید این سند را حذف کنید؟",
|
||||
save_success: "سند با موفقیت ثبت شد",
|
||||
edit_success: "سند با موفقیت ویرایش شد",
|
||||
delete_success: "سند با موفقیت حذف شد",
|
||||
load_error: "خطا در دریافت اطلاعات",
|
||||
save_error: "خطا در ذخیره اطلاعات",
|
||||
delete_error: "خطا در حذف سند",
|
||||
required_fields: {
|
||||
date: "تاریخ الزامی است",
|
||||
description: "توضیحات الزامی است",
|
||||
person: "انتخاب شخص الزامی است"
|
||||
}
|
||||
}
|
||||
},
|
||||
app: {
|
||||
loading: "در حال بارگذاری...",
|
||||
|
|
|
@ -764,6 +764,12 @@ const router = createRouter({
|
|||
component: () =>
|
||||
import('../views/acc/plugins/resamap/intro.vue'),
|
||||
},
|
||||
{
|
||||
path: 'plugins/hrm/intro',
|
||||
name: 'plugin_hrm_intro',
|
||||
component: () =>
|
||||
import('../views/acc/plugins/hrm/intro.vue'),
|
||||
},
|
||||
{
|
||||
path: 'plugins/noghre/intro',
|
||||
name: 'plugin_noghre_intro',
|
||||
|
@ -926,6 +932,24 @@ const router = createRouter({
|
|||
component: () =>
|
||||
import('../views/acc/shareholder/list.vue'),
|
||||
},
|
||||
{
|
||||
path: 'hrm/docs/list',
|
||||
name: 'hrm_docs_list',
|
||||
component: () =>
|
||||
import('../views/acc/plugins/hrm/docs/list.vue'),
|
||||
},
|
||||
{
|
||||
path: 'hrm/docs/mod/:id?',
|
||||
name: 'hrm_docs_mod',
|
||||
component: () =>
|
||||
import('../views/acc/plugins/hrm/docs/mod.vue'),
|
||||
},
|
||||
{
|
||||
path: 'hrm/docs/view/:id?',
|
||||
name: 'hrm_docs_view',
|
||||
component: () =>
|
||||
import('../views/acc/plugins/hrm/docs/view.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
14
webUI/src/types/vue3-qrcode-reader.d.ts
vendored
Normal file
14
webUI/src/types/vue3-qrcode-reader.d.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
declare module 'vue3-qrcode-reader' {
|
||||
import { DefineComponent } from 'vue'
|
||||
|
||||
export const QrcodeStream: DefineComponent<{
|
||||
camera?: string
|
||||
torch?: boolean
|
||||
track?: (location: any, ctx: CanvasRenderingContext2D) => void
|
||||
}>
|
||||
|
||||
export const QrcodeCapture: DefineComponent<{
|
||||
camera?: string
|
||||
torch?: boolean
|
||||
}>
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
<div class="fof">
|
||||
<h1>
|
||||
<v-empty-state :headline="$t('static.not_found')" title="404" :text="$t('static.not_found_info')"
|
||||
image="/img/logo-blue.png"></v-empty-state>
|
||||
image="/u/img/logo-blue.png"></v-empty-state>
|
||||
<v-btn color="success" to="/" prepend-icon="mdi-home">{{ $t('static.home_page') }}</v-btn>
|
||||
</h1>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,7 @@ import Currency_cob from '@/components/application/combobox/currency_cob.vue';
|
|||
import clock from '@/components/application/clock.vue';
|
||||
import CalculatorButton from '@/components/application/buttons/CalculatorButton.vue'
|
||||
import SecretDialog from '@/components/application/buttons/SecretDialog.vue';
|
||||
import ShortcutsButton from '@/components/application/buttons/ShortcutsButton.vue';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -175,7 +176,8 @@ export default {
|
|||
{ path: '/acc/archive/order/list', key: '.', label: this.$t('drawer.archive_log'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
||||
{ path: '/acc/plugin-center/list', key: '/', label: this.$t('drawer.plugins_list'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
||||
{ path: '/acc/plugin-center/my', key: '\\', label: this.$t('drawer.my_plugins'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
||||
{ path: '/acc/plugin-center/invoice', key: '`', label: this.$t('drawer.plugins_invoices'), ctrl: true, shift: true, permission: () => this.permissions.owner }
|
||||
{ path: '/acc/plugin-center/invoice', key: '`', label: this.$t('drawer.plugins_invoices'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
||||
{ path: '/acc/hrm/docs/list', key: 'H', label: this.$t('drawer.hrm_docs'), ctrl: true, shift: true, permission: () => this.isPluginActive('hrm') && this.permissions.plugHrmDocs },
|
||||
];
|
||||
},
|
||||
restorePermissions(shortcuts) {
|
||||
|
@ -283,14 +285,15 @@ export default {
|
|||
Currency_cob,
|
||||
clock,
|
||||
CalculatorButton,
|
||||
SecretDialog
|
||||
SecretDialog,
|
||||
ShortcutsButton
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-system-bar color="primaryLight2">
|
||||
<v-avatar image="/img/logo-blue.png" size="20" class="me-2 d-none d-sm-flex" />
|
||||
<v-avatar image="/u/img/logo-blue.png" size="20" class="me-2 d-none d-sm-flex" />
|
||||
<span class="d-none d-sm-flex">{{ siteSlogan }}</span>
|
||||
<v-avatar :image="apiUrl + '/front/avatar/file/get/' + business.id" size="20" class="me-2 d-flex d-sm-none" />
|
||||
<span class="d-flex d-sm-none">{{ business.name }}</span>
|
||||
|
@ -784,6 +787,26 @@ export default {
|
|||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<v-list-group v-show="isPluginActive('hrm') && permissions.plugHrmDocs">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item class="text-dark" v-bind="props" :title="$t('drawer.hrm')">
|
||||
<template v-slot:prepend><v-icon icon="mdi-account-cash" color="primary"></v-icon></template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item to="/acc/hrm/docs/list">
|
||||
<v-list-item-title>
|
||||
{{ $t('drawer.hrm_docs') }}
|
||||
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/hrm/docs/list') }}</span>
|
||||
</v-list-item-title>
|
||||
<template v-slot:append v-if="permissions.plugHrmDocs">
|
||||
<v-tooltip :text="$t('dialog.add_new')" location="end">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-plus-box" variant="plain" to="/acc/hrm/docs/mod/" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<v-list-item class="text-dark" v-if="permissions.owner" to="/acc/sms/panel">
|
||||
<template v-slot:prepend><v-icon icon="mdi-message-cog" color="primary"></v-icon></template>
|
||||
<v-list-item-title>
|
||||
|
@ -884,10 +907,11 @@ export default {
|
|||
<v-tooltip text="جادوگر" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="" stacked v-bind="props" to="/acc/wizard/home">
|
||||
<v-icon>mdi-robot</v-icon>
|
||||
</v-btn>
|
||||
<v-icon>mdi-robot</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<ShortcutsButton />
|
||||
<CalculatorButton />
|
||||
<SecretDialog />
|
||||
<v-dialog v-model="showShortcutsDialog" max-width="800" scrollable>
|
||||
|
|
|
@ -120,8 +120,38 @@
|
|||
|
||||
<v-divider class="my-2"></v-divider>
|
||||
|
||||
<!-- فیلتر بازه زمانی -->
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-dark mb-2">
|
||||
</v-list-item-title>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-checkbox
|
||||
v-model="timeFilters.find(f => f.value === 'custom').checked"
|
||||
label="بازه زمانی"
|
||||
@change="handleCustomDateFilterChange"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" v-if="timeFilters.find(f => f.value === 'custom').checked">
|
||||
<Hdatepicker
|
||||
v-model="dateRange.from"
|
||||
label="از تاریخ"
|
||||
@update:model-value="handleDateRangeChange"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" v-if="timeFilters.find(f => f.value === 'custom').checked">
|
||||
<Hdatepicker
|
||||
v-model="dateRange.to"
|
||||
label="تا تاریخ"
|
||||
@update:model-value="handleDateRangeChange"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list-item>
|
||||
|
||||
<!-- فیلترهای زمانی -->
|
||||
<v-list-item v-for="(filter, index) in timeFilters" :key="index" class="text-dark">
|
||||
<v-list-item v-for="(filter, index) in timeFilters.filter(f => f.value !== 'custom')" :key="index" class="text-dark">
|
||||
<template v-slot:title>
|
||||
<v-checkbox v-model="filter.checked" :label="filter.label" @change="applyTimeFilter(filter.value)"
|
||||
hide-details />
|
||||
|
@ -242,6 +272,7 @@ import { debounce } from 'lodash';
|
|||
import { getApiUrl } from '/src/hesabixConfig';
|
||||
import moment from 'jalali-moment';
|
||||
import HesabdariTreeView from '@/components/forms/HesabdariTreeView.vue';
|
||||
import Hdatepicker from '@/components/forms/Hdatepicker.vue';
|
||||
|
||||
const apiUrl = getApiUrl();
|
||||
axios.defaults.baseURL = apiUrl;
|
||||
|
@ -255,12 +286,17 @@ const searchQuery = ref('');
|
|||
const timeFilter = ref('all');
|
||||
const expanded = ref([]);
|
||||
const selectedAccountId = ref('67');
|
||||
const dateRange = ref({
|
||||
from: moment().locale('fa').subtract(1, 'days').format('YYYY/MM/DD'),
|
||||
to: moment().locale('fa').format('YYYY/MM/DD')
|
||||
});
|
||||
|
||||
// فیلترهای زمانی
|
||||
const timeFilters = ref([
|
||||
{ label: 'امروز', value: 'today', checked: false },
|
||||
{ label: 'این هفته', value: 'week', checked: false },
|
||||
{ label: 'این ماه', value: 'month', checked: false },
|
||||
{ label: 'بازه زمانی', value: 'custom', checked: false },
|
||||
{ label: 'همه', value: 'all', checked: true },
|
||||
]);
|
||||
|
||||
|
@ -317,6 +353,93 @@ const resetAccountFilter = () => {
|
|||
fetchData();
|
||||
};
|
||||
|
||||
// دیبونس برای جستجو
|
||||
const debouncedSearch = debounce(() => fetchData(), 500);
|
||||
|
||||
// دیبونس برای تغییر تاریخ
|
||||
const debouncedFetchData = debounce(() => {
|
||||
fetchData();
|
||||
}, 500);
|
||||
|
||||
// اصلاح متد handleDateRangeChange
|
||||
const handleDateRangeChange = () => {
|
||||
if (dateRange.value.from && dateRange.value.to) {
|
||||
// تبدیل تاریخهای شمسی به آبجکت moment
|
||||
const fromDate = moment(dateRange.value.from, 'jYYYY/jMM/jDD').locale('fa');
|
||||
const toDate = moment(dateRange.value.to, 'jYYYY/jMM/jDD').locale('fa');
|
||||
|
||||
// بررسی اعتبار بازه زمانی
|
||||
if (fromDate.isAfter(toDate)) {
|
||||
Swal.fire({
|
||||
text: 'تاریخ شروع نمیتواند بعد از تاریخ پایان باشد',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول'
|
||||
});
|
||||
// پاک کردن تاریخها
|
||||
dateRange.value = {
|
||||
from: null,
|
||||
to: null
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// ارسال درخواست با تاخیر
|
||||
debouncedFetchData();
|
||||
}
|
||||
};
|
||||
|
||||
// اصلاح متد handleCustomDateFilterChange
|
||||
const handleCustomDateFilterChange = (checked) => {
|
||||
if (checked) {
|
||||
// غیرفعال کردن سایر فیلترها
|
||||
timeFilters.value.forEach(filter => {
|
||||
if (filter.value !== 'custom') {
|
||||
filter.checked = false;
|
||||
}
|
||||
});
|
||||
timeFilter.value = 'custom';
|
||||
|
||||
// تنظیم تاریخهای پیشفرض
|
||||
dateRange.value = {
|
||||
from: moment().locale('fa').subtract(1, 'days').format('YYYY/MM/DD'),
|
||||
to: moment().locale('fa').format('YYYY/MM/DD')
|
||||
};
|
||||
|
||||
// ارسال درخواست با تاریخهای پیشفرض
|
||||
debouncedFetchData();
|
||||
} else {
|
||||
// اگر چکباکس بازه زمانی غیرفعال شد، فیلتر "همه" را فعال کن
|
||||
timeFilters.value.forEach(filter => {
|
||||
filter.checked = filter.value === 'all';
|
||||
});
|
||||
timeFilter.value = 'all';
|
||||
// پاک کردن تاریخها
|
||||
dateRange.value = {
|
||||
from: null,
|
||||
to: null
|
||||
};
|
||||
debouncedFetchData();
|
||||
}
|
||||
};
|
||||
|
||||
// اصلاح متد applyTimeFilter
|
||||
const applyTimeFilter = (value) => {
|
||||
timeFilters.value.forEach((filter) => {
|
||||
filter.checked = filter.value === value;
|
||||
});
|
||||
timeFilter.value = value;
|
||||
|
||||
// اگر فیلتر بازه زمانی غیرفعال شد، تاریخها را پاک کن
|
||||
if (value !== 'custom') {
|
||||
dateRange.value = {
|
||||
from: null,
|
||||
to: null
|
||||
};
|
||||
}
|
||||
|
||||
fetchData();
|
||||
};
|
||||
|
||||
// فچ کردن دادهها از سرور
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
|
@ -329,20 +452,37 @@ const fetchData = async () => {
|
|||
if (timeFilter.value) {
|
||||
filters.timeFilter = timeFilter.value;
|
||||
|
||||
const today = moment().locale('fa').format('YYYY/MM/DD');
|
||||
switch (timeFilter.value) {
|
||||
case 'today':
|
||||
filters.dateFrom = today;
|
||||
filters.dateTo = today;
|
||||
break;
|
||||
case 'week':
|
||||
filters.dateFrom = moment().locale('fa').subtract(6, 'days').format('YYYY/MM/DD');
|
||||
filters.dateTo = today;
|
||||
break;
|
||||
case 'month':
|
||||
filters.dateFrom = moment().locale('fa').startOf('jMonth').format('YYYY/MM/DD');
|
||||
filters.dateTo = today;
|
||||
break;
|
||||
if (timeFilter.value === 'custom' && dateRange.value.from && dateRange.value.to) {
|
||||
// تبدیل تاریخهای شمسی به فرمت مورد نیاز
|
||||
const fromDate = moment(dateRange.value.from, 'jYYYY/jMM/jDD').locale('fa').format('YYYY/MM/DD');
|
||||
const toDate = moment(dateRange.value.to, 'jYYYY/jMM/jDD').locale('fa').format('YYYY/MM/DD');
|
||||
|
||||
filters.date = {
|
||||
from: fromDate,
|
||||
to: toDate
|
||||
};
|
||||
} else {
|
||||
const today = moment().locale('fa').format('YYYY/MM/DD');
|
||||
switch (timeFilter.value) {
|
||||
case 'today':
|
||||
filters.date = {
|
||||
from: today,
|
||||
to: today
|
||||
};
|
||||
break;
|
||||
case 'week':
|
||||
filters.date = {
|
||||
from: moment().locale('fa').subtract(6, 'days').format('YYYY/MM/DD'),
|
||||
to: today
|
||||
};
|
||||
break;
|
||||
case 'month':
|
||||
filters.date = {
|
||||
from: moment().locale('fa').startOf('jMonth').format('YYYY/MM/DD'),
|
||||
to: today
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,6 +508,8 @@ const fetchData = async () => {
|
|||
},
|
||||
};
|
||||
|
||||
console.log('Request payload:', payload); // برای دیباگ
|
||||
|
||||
const response = await axios.post('/api/cost/list/search', {
|
||||
type: 'cost',
|
||||
...payload,
|
||||
|
@ -396,18 +538,6 @@ const fetchData = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
// دیبونس برای جستجو
|
||||
const debouncedSearch = debounce(() => fetchData(), 500);
|
||||
|
||||
// اعمال فیلتر زمانی
|
||||
const applyTimeFilter = (value) => {
|
||||
timeFilters.value.forEach((filter) => {
|
||||
filter.checked = filter.value === value;
|
||||
});
|
||||
timeFilter.value = value;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
// حذف یک آیتم
|
||||
const deleteItem = async (code) => {
|
||||
const result = await Swal.fire({
|
||||
|
@ -589,6 +719,13 @@ const deleteGroup = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
// اضافه کردن watch برای تغییرات تاریخها
|
||||
watch([() => dateRange.value.from, () => dateRange.value.to], () => {
|
||||
if (timeFilter.value === 'custom') {
|
||||
handleDateRangeChange();
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
// Watchers
|
||||
watch(() => serverOptions.value.page, () => {
|
||||
selectedItems.value.clear();
|
||||
|
|
|
@ -7,7 +7,9 @@ export default defineComponent({
|
|||
siteName:''
|
||||
}},
|
||||
created(){
|
||||
this.siteName = getSiteName();
|
||||
getSiteName().then((name) => {
|
||||
this.siteName = name;
|
||||
});
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
@ -15,7 +17,7 @@ export default defineComponent({
|
|||
<template>
|
||||
<main id="main-container p-0 m-0">
|
||||
<!-- Hero -->
|
||||
<div class="bg-image" style="background-image: url('/img/plugins/accpro/intro.png');">
|
||||
<div class="bg-image" style="background-image: url('/u/img/plugins/accpro/intro.png');">
|
||||
<div class="bg-black-75">
|
||||
<div class="content content-top content-full text-center">
|
||||
<h1 class="text-white"><i class="fa fa-shop"></i></h1>
|
||||
|
|
70
webUI/src/views/acc/plugins/hrm/docs/list.vue
Normal file
70
webUI/src/views/acc/plugins/hrm/docs/list.vue
Normal file
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
{{ $t('drawer.hrm_docs') }}
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" to="/acc/hrm/docs/mod/">
|
||||
{{ $t('dialog.add_new') }}
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
class="elevation-1"
|
||||
>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-eye"
|
||||
variant="text"
|
||||
size="small"
|
||||
:to="'/acc/hrm/docs/view/' + item.id"
|
||||
></v-btn>
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
variant="text"
|
||||
size="small"
|
||||
:to="'/acc/hrm/docs/mod/' + item.id"
|
||||
></v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
headers: [
|
||||
{ title: this.$t('field.id'), key: 'id' },
|
||||
{ title: this.$t('field.date'), key: 'date' },
|
||||
{ title: this.$t('field.employee'), key: 'employee' },
|
||||
{ title: this.$t('field.amount'), key: 'amount' },
|
||||
{ title: this.$t('field.status'), key: 'status' },
|
||||
{ title: this.$t('field.actions'), key: 'actions', sortable: false }
|
||||
],
|
||||
items: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const response = await this.$axios.post('/api/hrm/docs/list')
|
||||
this.items = response.data
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
393
webUI/src/views/acc/plugins/hrm/docs/mod.vue
Normal file
393
webUI/src/views/acc/plugins/hrm/docs/mod.vue
Normal file
|
@ -0,0 +1,393 @@
|
|||
<template>
|
||||
<v-toolbar color="toolbar" flat class="rounded-t mb-4">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" @click="$router.back()" variant="text" icon="mdi-arrow-right" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
<v-toolbar-title class="text-h6">{{ isEdit ? $t('dialog.edit') : $t('dialog.add_new') }}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-tooltip :text="$t('dialog.save')" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" variant="text" icon="mdi-content-save" color="success" @click="validateAndSave"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip v-if="isEdit" :text="$t('dialog.delete')" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" variant="text" icon="mdi-delete" color="error" @click="deleteDialog = true"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-toolbar>
|
||||
<v-container>
|
||||
<v-form ref="form" v-model="valid">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="6">
|
||||
<Hdatepicker v-model="form.date" :label="$t('dialog.hrm.date')"
|
||||
:rules="[v => !!v || $t('dialog.hrm.required_fields.date')]" required />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="6">
|
||||
<v-text-field v-model="form.description" :label="$t('dialog.hrm.description')"
|
||||
:rules="[v => !!v || $t('dialog.hrm.required_fields.description')]" required></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-table class="border rounded d-none d-sm-table" style="width: 100%;">
|
||||
<thead>
|
||||
<tr style="background-color: #0D47A1; color: white;">
|
||||
<th class="text-center pa-1" style="width: 100px;">شخص</th>
|
||||
<th class="text-center pa-1">حقوق پایه</th>
|
||||
<th class="text-center pa-1">اضافه کار</th>
|
||||
<th class="text-center pa-1">حق شیفت</th>
|
||||
<th class="text-center pa-1">شب کاری</th>
|
||||
<th class="text-center pa-1" style="width: 150px;">جمع کل</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(row, index) in tableItems" :key="index">
|
||||
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '48px' }">
|
||||
<td class="text-center pa-1" style="width: 170px;">
|
||||
<Hpersonsearch v-model="row.person" label="شخص" density="compact" hide-details class="my-0" style="font-size: 0.8rem;"></Hpersonsearch>
|
||||
</td>
|
||||
<td class="text-center pa-1" style="width: 120px;">
|
||||
<Hnumberinput v-model="row.baseSalary" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
|
||||
</td>
|
||||
<td class="text-center pa-1" style="width: 120px;">
|
||||
<Hnumberinput v-model="row.overtime" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
|
||||
</td>
|
||||
<td class="text-center pa-1" style="width: 120px;">
|
||||
<Hnumberinput v-model="row.shift" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
|
||||
</td>
|
||||
<td class="text-center pa-1" style="width: 120px;">
|
||||
<Hnumberinput v-model="row.night" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
|
||||
</td>
|
||||
<td class="text-center font-weight-bold pa-1" style="width: 120px;">
|
||||
{{ calculateTotal(row).toLocaleString('fa-IR') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '48px' }">
|
||||
<td colspan="4" class="pa-1">
|
||||
<v-text-field v-model="row.description" density="compact" hide-details
|
||||
:placeholder="$t('dialog.hrm.row_description')" class="my-0" style="font-size: 0.8rem;"></v-text-field>
|
||||
</td>
|
||||
<td class="text-center pa-1" style="width: 120px;">
|
||||
<v-tooltip text="حذف" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-delete" variant="text" size="small" color="error" @click="removeRow(index)"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="tableItems.length === 0">
|
||||
<td colspan="6" class="text-center text-grey pa-1">{{ $t('dialog.hrm.no_data') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center pa-1" style="height: 48px;">
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" size="small" @click="addRow">{{ $t('dialog.hrm.add_new_row') }}</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="tableItems.length > 0" style="background-color: #E3F2FD;">
|
||||
<td class="text-center pa-1 font-weight-bold">جمع کل</td>
|
||||
<td class="text-center pa-1 font-weight-bold">{{ calculateColumnTotal('baseSalary').toLocaleString('fa-IR') }}</td>
|
||||
<td class="text-center pa-1 font-weight-bold">{{ calculateColumnTotal('overtime').toLocaleString('fa-IR') }}</td>
|
||||
<td class="text-center pa-1 font-weight-bold">{{ calculateColumnTotal('shift').toLocaleString('fa-IR') }}</td>
|
||||
<td class="text-center pa-1 font-weight-bold">{{ calculateColumnTotal('night').toLocaleString('fa-IR') }}</td>
|
||||
<td class="text-center pa-1 font-weight-bold">{{ calculateColumnTotal('total').toLocaleString('fa-IR') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
<v-snackbar v-model="showSuccess" color="success" timeout="3000">
|
||||
{{ successMessage }}
|
||||
</v-snackbar>
|
||||
<v-snackbar v-model="showError" color="error" timeout="3000">
|
||||
{{ errorMessage }}
|
||||
</v-snackbar>
|
||||
<v-dialog v-model="deleteDialog" max-width="400">
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">{{ $t('dialog.hrm.title') }}</v-card-title>
|
||||
<v-card-text>{{ $t('dialog.hrm.delete_confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey-darken-1" variant="text" @click="deleteDialog = false">{{ $t('dialog.cancel') }}</v-btn>
|
||||
<v-btn color="error" variant="text" @click="confirmDelete" :loading="loading">{{ $t('dialog.delete') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Hdatepicker from '@/components/forms/Hdatepicker.vue';
|
||||
import Hpersonsearch from '@/components/forms/Hpersonsearch.vue';
|
||||
import Hnumberinput from '@/components/forms/Hnumberinput.vue';
|
||||
export default {
|
||||
components: { Hdatepicker, Hpersonsearch, Hnumberinput },
|
||||
data() {
|
||||
return {
|
||||
valid: false,
|
||||
isEdit: false,
|
||||
loading: false,
|
||||
deleteDialog: false,
|
||||
showSuccess: false,
|
||||
showError: false,
|
||||
successMessage: '',
|
||||
errorMessage: '',
|
||||
form: {
|
||||
date: '',
|
||||
description: ''
|
||||
},
|
||||
tableItems: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const id = this.$route.params.id
|
||||
if (id) {
|
||||
this.isEdit = true
|
||||
this.loadData(id)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadData(id) {
|
||||
try {
|
||||
const response = await this.$axios.post('/api/hrm/docs/get/' + id)
|
||||
this.form = response.data
|
||||
} catch (error) {
|
||||
this.errorMessage = 'خطا در دریافت اطلاعات';
|
||||
this.showError = true;
|
||||
}
|
||||
},
|
||||
validateAndSave() {
|
||||
if (!this.$refs.form.validate()) {
|
||||
this.errorMessage = this.$t('validator.form_invalid');
|
||||
this.showError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// بررسی وجود حداقل یک سطر در جدول
|
||||
if (this.tableItems.length === 0) {
|
||||
this.errorMessage = this.$t('dialog.hrm.no_data');
|
||||
this.showError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// بررسی انتخاب شخص در تمام سطرها
|
||||
const hasInvalidPerson = this.tableItems.some(row => !row.person);
|
||||
if (hasInvalidPerson) {
|
||||
this.errorMessage = this.$t('dialog.hrm.required_fields.person');
|
||||
this.showError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.save();
|
||||
},
|
||||
async save() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const url = this.isEdit ? '/api/hrm/docs/update' : '/api/hrm/docs/insert'
|
||||
await this.$axios.post(url, this.form)
|
||||
this.successMessage = this.isEdit ? this.$t('dialog.hrm.edit_success') : this.$t('dialog.hrm.save_success');
|
||||
this.showSuccess = true;
|
||||
setTimeout(() => {
|
||||
this.$router.push('/acc/hrm/docs/list')
|
||||
}, 1200)
|
||||
} catch (error) {
|
||||
this.errorMessage = this.$t('dialog.hrm.save_error');
|
||||
this.showError = true;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async confirmDelete() {
|
||||
try {
|
||||
this.loading = true;
|
||||
await this.$axios.post('/api/hrm/docs/delete', { id: this.$route.params.id })
|
||||
this.successMessage = 'سند با موفقیت حذف شد';
|
||||
this.showSuccess = true;
|
||||
setTimeout(() => {
|
||||
this.$router.push('/acc/hrm/docs/list')
|
||||
}, 1200)
|
||||
} catch (error) {
|
||||
this.errorMessage = 'خطا در حذف سند';
|
||||
this.showError = true;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.deleteDialog = false;
|
||||
}
|
||||
},
|
||||
addRow() {
|
||||
this.tableItems.push({
|
||||
person: null,
|
||||
description: '',
|
||||
baseSalary: 0,
|
||||
overtime: 0,
|
||||
shift: 0,
|
||||
night: 0
|
||||
});
|
||||
},
|
||||
removeRow(index) {
|
||||
this.tableItems.splice(index, 1);
|
||||
},
|
||||
calculateTotal(row) {
|
||||
const base = Number(row.baseSalary) || 0;
|
||||
const overtime = Number(row.overtime) || 0;
|
||||
const shift = Number(row.shift) || 0;
|
||||
const night = Number(row.night) || 0;
|
||||
return base + overtime + shift + night;
|
||||
},
|
||||
recalculateTotals() {
|
||||
// Implementation of recalculateTotals method
|
||||
},
|
||||
calculateColumnTotal(column) {
|
||||
return this.tableItems.reduce((sum, row) => {
|
||||
if (column === 'total') {
|
||||
return sum + this.calculateTotal(row);
|
||||
}
|
||||
return sum + (Number(row[column]) || 0);
|
||||
}, 0);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bank-card {
|
||||
border-right: 4px solid #1976D2 !important;
|
||||
}
|
||||
|
||||
.cashdesk-card {
|
||||
border-right: 4px solid #4CAF50 !important;
|
||||
}
|
||||
|
||||
.salary-card {
|
||||
border-right: 4px solid #FF9800 !important;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.payment-card :deep(.v-card-item) {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.payment-card :deep(.v-card-title) {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.payment-card :deep(.v-card-text) {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.v-overlay__content) {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
:deep(.v-menu__content) {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
:deep(.v-dialog) {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
:deep(.v-date-picker) {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
.settings-section-card {
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.settings-section-card:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.settings-section-title {
|
||||
background-color: #f5f5f5;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.settings-section-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.settings-section-card :deep(.v-switch) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settings-section-card :deep(.v-switch .v-label) {
|
||||
font-size: 0.9rem;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
.settings-section-card :deep(.v-switch.v-input--disabled) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.settings-section-card :deep(.v-select) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.settings-section-card :deep(.v-divider) {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.draft-dialog {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.draft-dialog-title {
|
||||
background-color: #f5f5f5;
|
||||
padding: 16px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.draft-dialog-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.draft-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.draft-message p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.draft-dialog-actions {
|
||||
padding: 16px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
:deep(.v-btn) {
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.v-btn--elevated) {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:deep(.v-btn--outlined) {
|
||||
border-width: 1px;
|
||||
}
|
||||
</style>
|
89
webUI/src/views/acc/plugins/hrm/docs/view.vue
Normal file
89
webUI/src/views/acc/plugins/hrm/docs/view.vue
Normal file
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
{{ $t('drawer.hrm_docs') }}
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="primary"
|
||||
prepend-icon="mdi-pencil"
|
||||
:to="'/acc/hrm/docs/mod/' + $route.params.id"
|
||||
>
|
||||
{{ $t('dialog.edit') }}
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row v-if="!loading">
|
||||
<v-col cols="12" md="6">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-account"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t('field.employee') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.employee }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-currency-usd"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t('field.amount') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.amount }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-calendar"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t('field.date') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.date }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-check-circle"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t('field.status') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.status }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-progress-circular
|
||||
v-else
|
||||
indeterminate
|
||||
color="primary"
|
||||
></v-progress-circular>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" @click="$router.back()">
|
||||
{{ $t('dialog.back') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
item: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
const response = await this.$axios.post('/api/hrm/docs/get/' + this.$route.params.id)
|
||||
this.item = response.data
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
127
webUI/src/views/acc/plugins/hrm/intro.vue
Normal file
127
webUI/src/views/acc/plugins/hrm/intro.vue
Normal file
|
@ -0,0 +1,127 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
import {getApiUrl, getSiteName} from '@/hesabixConfig'
|
||||
export default defineComponent({
|
||||
name: "intro",
|
||||
data:()=>{return{
|
||||
siteName:''
|
||||
}},
|
||||
async created(){
|
||||
this.siteName = await getSiteName();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main id="main-container pt-0 mt-o">
|
||||
<!-- Hero -->
|
||||
<div class="bg-image" style="background-image: url('/u/img/plugins/hrm/hrm.jpg');">
|
||||
<div class="bg-black-75">
|
||||
<div class="content content-top content-full text-center">
|
||||
<h1 class="text-white"><i class="fa fa-users"></i></h1>
|
||||
<h1 class="fw-bold text-white mt-5 mb-3"> افزونه مدیریت منابع انسانی </h1>
|
||||
<h2 class="h3 fw-normal text-white-75 mb-5">مدیریت هوشمند پرسنل، حقوق و دستمزد و حضور و غیاب با یک افزونه قدرتمند</h2>
|
||||
<RouterLink to="/acc/plugin-center/view-end/hrm">
|
||||
<span class="badge rounded-pill bg-primary fs-base px-3 py-2 me-2 m-1">
|
||||
<i class="fa fa-shopping-cart me-1"></i> خرید نسخه آزمایشی </span>
|
||||
</RouterLink>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Hero -->
|
||||
|
||||
<!-- Page Content -->
|
||||
<div class="content content-full">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-11 py-2">
|
||||
<!-- Story -->
|
||||
<article class="story justify-content-between">
|
||||
<div class="alert alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i>
|
||||
این افزونه در حال توسعه و آزمایشی است. در نسخه اولیه، تنها از سند حقوق کارکنان پشتیبانی میکند. خرید شما باعث سرعت بیشتر در توسعه و بهبود قابلیتهای آن میشود.
|
||||
</div>
|
||||
<p class="justify-content-between">
|
||||
مدیریت منابع انسانی یکی از مهمترین بخشهای هر سازمان است که نیاز به دقت و نظم بالایی دارد. افزونه مدیریت منابع انسانی حسابیکس با ارائه قابلیتهای متنوع، مدیریت پرسنل، محاسبه حقوق و دستمزد و کنترل حضور و غیاب را به سادهترین شکل ممکن فراهم میکند.
|
||||
</p>
|
||||
<p>
|
||||
<strong>قابلیتهای نسخه اولیه:</strong>
|
||||
</p>
|
||||
<ul class="list-group list-group-flush mb-4">
|
||||
<li class="list-group-item">
|
||||
<i class="fa fa-check text-success me-2"></i>
|
||||
ثبت سند حقوق کارکنان با در نظر گرفتن موارد قانونی
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>قابلیتهای در دست توسعه:</strong>
|
||||
</p>
|
||||
<ul class="list-group list-group-flush mb-4">
|
||||
<li class="list-group-item">
|
||||
<i class="fa fa-clock text-warning me-2"></i>
|
||||
سیستم حضور و غیاب هوشمند
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="fa fa-clock text-warning me-2"></i>
|
||||
مدیریت مرخصیها و غیبتها
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="fa fa-clock text-warning me-2"></i>
|
||||
ثبت مدارک و سوابق کاری
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
این افزونه به صورت کامل با سیستم حسابداری حسابیکس یکپارچه شده و تمامی عملیات مالی مربوط به حقوق و دستمزد را به صورت خودکار در سیستم حسابداری ثبت میکند. همچنین امکان صدور فیش حقوقی و گزارشهای مالی متنوع را فراهم میکند.
|
||||
</p>
|
||||
<p>
|
||||
با استفاده از این افزونه، دیگر نیازی به نرم افزارهای جداگانه برای مدیریت منابع انسانی نخواهید داشت. تمامی اطلاعات در یک سیستم یکپارچه ذخیره میشود و دسترسی به آنها از هر مکان و در هر زمان امکانپذیر است.
|
||||
</p>
|
||||
</article>
|
||||
<!-- END Story -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Page Content -->
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bg-image {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bg-black-75 {
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.story {
|
||||
line-height: 1.8;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
border: none;
|
||||
padding: 0.75rem 1.25rem;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list-group-item i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -9,7 +9,9 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.siteName = getSiteName();
|
||||
getSiteName().then(name => {
|
||||
this.siteName = name;
|
||||
});
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
@ -17,7 +19,7 @@ export default defineComponent({
|
|||
<template>
|
||||
<main id="main-container p-0 m-0">
|
||||
<!-- Hero -->
|
||||
<div class="bg-image" style="background-image: url('/img/plugins/repservice.png');">
|
||||
<div class="bg-image" style="background-image: url('/u/img/plugins/repservice.png');">
|
||||
<div class="bg-black-75">
|
||||
<div class="content content-top content-full text-center">
|
||||
<h1 class="text-white"><i class="fa fa-shop"></i></h1>
|
||||
|
@ -30,8 +32,6 @@ export default defineComponent({
|
|||
<span class="badge rounded-pill bg-primary fs-base px-3 py-2 me-2 m-1">
|
||||
<i class="fa fa-user-circle me-1"></i> خرید </span>
|
||||
</RouterLink>
|
||||
<br>
|
||||
<i class="fa fa-arrow-down text-white"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -216,15 +216,24 @@
|
|||
</template>
|
||||
</v-data-table-server>
|
||||
<div class="footer-summary">
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">جمع کل فاکتورهای صفحه:</span>
|
||||
<span class="summary-value">{{ $filters.formatNumber(sumTotal) }}</span>
|
||||
<div class="summary-items">
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">جمع کل فاکتورهای صفحه:</span>
|
||||
<span class="summary-value">{{ $filters.formatNumber(sumTotal) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">جمع موارد انتخاب شده:</span>
|
||||
<span class="summary-value">{{ $filters.formatNumber(sumSelected) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">جمع سود موارد انتخاب شده:</span>
|
||||
<span class="summary-value" :class="{'text-success': sumSelectedProfit >= 0, 'text-danger': sumSelectedProfit < 0}">
|
||||
{{ $filters.formatNumber(Math.abs(sumSelectedProfit)) }}
|
||||
<span v-if="sumSelectedProfit < 0">(زیان)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">جمع موارد انتخاب شده:</span>
|
||||
<span class="summary-value">{{ $filters.formatNumber(sumSelected) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-dialog v-model="showColumnDialog" max-width="500px">
|
||||
<v-card>
|
||||
<v-toolbar dark>
|
||||
|
@ -344,6 +353,7 @@ export default defineComponent({
|
|||
},
|
||||
plugins: {},
|
||||
sumSelected: 0,
|
||||
sumSelectedProfit: 0,
|
||||
sumTotal: 0,
|
||||
itemsSelected: [],
|
||||
searchValue: '',
|
||||
|
@ -693,6 +703,7 @@ export default defineComponent({
|
|||
itemsSelected: {
|
||||
handler(val) {
|
||||
this.sumSelected = 0;
|
||||
this.sumSelectedProfit = 0;
|
||||
this.itemsSelected.forEach((code) => {
|
||||
const selectedItem = this.items.find(item => item.code === code);
|
||||
if (selectedItem) {
|
||||
|
@ -702,6 +713,7 @@ export default defineComponent({
|
|||
} else {
|
||||
this.sumSelected += amount;
|
||||
}
|
||||
this.sumSelectedProfit += selectedItem.profit || 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -715,21 +727,29 @@ export default defineComponent({
|
|||
.footer-summary {
|
||||
background-color: #f5f5f5;
|
||||
padding: 12px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.summary-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
|
@ -737,6 +757,18 @@ export default defineComponent({
|
|||
color: #1976d2;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.summary-items {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
min-width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.data-table-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -46,35 +46,46 @@
|
|||
<mostdes v-model="invoiceDescription" :submitData="{ id: null, des: invoiceDescription }" type="sell" label=""></mostdes>
|
||||
</template>
|
||||
</v-text-field>
|
||||
<v-table class="border rounded d-none d-sm-table" style="width: 100%;">
|
||||
<v-table class="border rounded d-none d-sm-table" style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="background-color: #0D47A1; color: white;">
|
||||
<th class="text-center px-2" style="width: 50px;">ردیف</th>
|
||||
<th class="text-center">نام کالا</th>
|
||||
<th class="text-center">تعداد</th>
|
||||
<th class="text-center">قیمت</th>
|
||||
<th class="text-center">تخفیف</th>
|
||||
<th class="text-center" style="width: 150px;">جمع کل</th>
|
||||
<th class="text-center">جمع کل</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(item, index) in items" :key="index">
|
||||
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '64px' }">
|
||||
<td class="text-center" style="min-width: 200px;">
|
||||
<td class="text-center px-2">
|
||||
<span class="text-subtitle-2">{{ index + 1 }}</span>
|
||||
</td>
|
||||
<td class="text-center px-2">
|
||||
<Hcommoditysearch v-model="item.name" density="compact" hide-details class="my-0" style="font-size: 0.8rem;" return-object @update:modelValue="handleCommodityChange(item)"></Hcommoditysearch>
|
||||
</td>
|
||||
<td class="text-center" style="width: 100px;">
|
||||
<td class="text-center px-2">
|
||||
<Hnumberinput v-model="item.count" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
|
||||
</td>
|
||||
<td class="text-center" style="width: 120px;">
|
||||
<Hnumberinput v-model="item.price" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
|
||||
<td class="text-center px-2">
|
||||
<div class="d-flex align-center justify-center">
|
||||
<Hnumberinput v-model="item.price" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
|
||||
<v-tooltip v-if="item.name && item.price < item.name.priceBuy" text="قیمت فروش کمتر از قیمت خرید است" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon v-bind="props" color="warning" size="small" class="mr-1">mdi-alert</v-icon>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" style="width: 150px;">
|
||||
<td class="text-center px-2">
|
||||
<div class="d-flex align-center">
|
||||
<Hnumberinput v-if="item.showPercentDiscount" v-model="item.discountPercent" density="compact" suffix="%" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip text="تخفیف درصدی" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" style="margin: 0; padding: 0;" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
@ -83,14 +94,14 @@
|
|||
<template v-slot:prepend>
|
||||
<v-tooltip text="تخفیف مبلغی" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" style="margin: 0; padding: 0;" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</Hnumberinput>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center font-weight-bold" style="width: 120px;">
|
||||
<td class="text-center font-weight-bold px-2">
|
||||
{{ item.total.toLocaleString('fa-IR') }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -127,35 +138,42 @@
|
|||
<v-card-text>
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span class="text-subtitle-2 font-weight-bold">ردیف:</span>
|
||||
<span>{{ index + 1 }}</span>
|
||||
<span class="text-subtitle-2">{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Hcommoditysearch v-model="item.name" density="compact" label="نام کالا" hide-details class="my-0" style="font-size: 0.8rem;" return-object @update:modelValue="handleCommodityChange(item)"></Hcommoditysearch>
|
||||
<Hcommoditysearch v-model="item.name" density="compact" label="نام کالا" hide-details class="my-0" style="font-size: 0.8rem; margin: 0; padding: 0;" return-object @update:modelValue="handleCommodityChange(item)"></Hcommoditysearch>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-2">
|
||||
<div style="width: 48%;">
|
||||
<Hnumberinput v-model="item.count" density="compact" label="تعداد" hide-details class="my-0" style="font-size: 0.8rem;" @update:modelValue="recalculateTotals"></Hnumberinput>
|
||||
<div class="flex-grow-1 mr-2">
|
||||
<Hnumberinput v-model="item.count" density="compact" label="تعداد" hide-details class="my-0" style="font-size: 0.8rem; margin: 0; padding: 0;" @update:modelValue="recalculateTotals"></Hnumberinput>
|
||||
</div>
|
||||
<div style="width: 48%;">
|
||||
<Hnumberinput v-model="item.price" density="compact" label="قیمت" hide-details class="my-0" style="font-size: 0.8rem;" @update:modelValue="recalculateTotals"></Hnumberinput>
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-center">
|
||||
<Hnumberinput v-model="item.price" density="compact" label="قیمت" hide-details class="my-0" style="font-size: 0.8rem; margin: 0; padding: 0;" @update:modelValue="recalculateTotals"></Hnumberinput>
|
||||
<v-tooltip v-if="item.name && item.price < item.name.priceBuy" text="قیمت فروش کمتر از قیمت خرید است" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon v-bind="props" color="warning" size="small" class="mr-1" style="margin: 0; padding: 0;">mdi-alert</v-icon>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="d-flex align-center">
|
||||
<Hnumberinput v-if="item.showPercentDiscount" v-model="item.discountPercent" density="compact" label="تخفیف" suffix="%" hide-details @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;">
|
||||
<Hnumberinput v-if="item.showPercentDiscount" v-model="item.discountPercent" density="compact" label="تخفیف" suffix="%" hide-details @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem; margin: 0; padding: 0;">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip text="تخفیف درصدی" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" style="margin: 0; padding: 0;" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</Hnumberinput>
|
||||
<Hnumberinput v-else v-model="item.discountAmount" density="compact" label="تخفیف" hide-details @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;">
|
||||
<Hnumberinput v-else v-model="item.discountAmount" density="compact" label="تخفیف" hide-details @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem; margin: 0; padding: 0;">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip text="تخفیف مبلغی" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
<v-checkbox v-bind="props" v-model="item.showPercentDiscount" hide-details density="compact" color="primary" class="mt-0" style="margin: 0; padding: 0;" @update:modelValue="handleDiscountTypeChange(item)"></v-checkbox>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
@ -1293,6 +1311,36 @@ export default {
|
|||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.v-table) {
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
:deep(.v-table__wrapper) {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:deep(.v-table__wrapper::-webkit-scrollbar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.v-table .v-field) {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
:deep(.v-table .v-field__input) {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
:deep(.v-table .v-field__outline) {
|
||||
--v-field-border-width: 1px;
|
||||
}
|
||||
|
||||
:deep(.v-table .v-field--variant-outlined) {
|
||||
--v-field-border-opacity: 0.12;
|
||||
}
|
||||
|
||||
:deep(.v-overlay__content) {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
|
|
@ -493,6 +493,32 @@
|
|||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="isPluginActive('hrm')" class="mt-4">
|
||||
<v-col cols="12">
|
||||
<v-card-title class="text-h6 font-weight-bold mb-4">افزونه مدیریت منابع انسانی</v-card-title>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-card variant="outlined" class="h-100">
|
||||
<v-card-text>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch
|
||||
v-model="info.plugHrmDocs"
|
||||
label="مدیریت اسناد حقوق و دستمزد"
|
||||
@change="savePerms('plugHrmDocs')"
|
||||
hide-details
|
||||
color="success"
|
||||
density="comfortable"
|
||||
:loading="loadingSwitches.plugHrmDocs"
|
||||
:disabled="loadingSwitches.plugHrmDocs"
|
||||
></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="mt-4">
|
||||
<v-col v-if="isPluginActive('noghre')" cols="12" md="4">
|
||||
<v-card-title class="text-h6 font-weight-bold mb-4">افزونه کارگاه نقره سازی</v-card-title>
|
||||
|
@ -625,7 +651,8 @@ export default {
|
|||
plugRepservice: false,
|
||||
plugNoghreAdmin: false,
|
||||
plugNoghreSell: false,
|
||||
plugCCAdmin: false
|
||||
plugCCAdmin: false,
|
||||
plugHrmDocs: false
|
||||
};
|
||||
|
||||
axios.post('/api/business/get/user/permissions',
|
||||
|
|
|
@ -1,87 +1,100 @@
|
|||
<script>
|
||||
import {defineComponent} from 'vue'
|
||||
import axios from "axios";
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from "axios"
|
||||
|
||||
export default defineComponent({
|
||||
name: "plugin-world",
|
||||
data: ()=>{return{
|
||||
plugins:{}
|
||||
}},
|
||||
methods:{
|
||||
loadData(){
|
||||
axios.post('/api/plugin/get/all').then((response)=>{
|
||||
this.plugins = response.data;
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
}
|
||||
const plugins = ref([])
|
||||
|
||||
const loadData = () => {
|
||||
axios.post('/api/plugin/get/all').then((response) => {
|
||||
plugins.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block block-content-full">
|
||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light" >
|
||||
<h3 class="block-title text-primary-dark">
|
||||
<button @click="$router.back()" type="button" class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
||||
<i class="fa fw-bold fa-arrow-right"></i>
|
||||
</button>
|
||||
<i class="fa fa-cog"></i>
|
||||
فهرست افزونهها
|
||||
</h3>
|
||||
<div class="block-options">
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-content pb-3">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div v-for="plugin in plugins" class="col-md-6 col-xl-4">
|
||||
<!-- House -->
|
||||
<div class="block block-rounded border">
|
||||
<div class="block-content p-0 overflow-hidden">
|
||||
<a class="img-link img-fluid-100" data-action="side_overlay_open" data-toggle="layout" href="javascript:void(0)">
|
||||
<img alt="" class="img-fluid rounded-top" :src="'/img/plugins/' + plugin.icon">
|
||||
</a>
|
||||
</div>
|
||||
<div class="block-content">
|
||||
<h4 class="h6 mb-2">
|
||||
<i class="fa fa-plug-circle-plus"></i>
|
||||
{{plugin.name}}
|
||||
</h4>
|
||||
<h5 class="h2 fw-light push">{{Intl.NumberFormat('en-US').format(plugin.price)}} تومان<br><span class="fs-3 text-muted"><i class="fa fa-clock"></i> {{plugin.timelabel}} </span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="block-content p-0">
|
||||
<div class="row text-center m-0 border-top border-bottom bg-body-light">
|
||||
<div class="col-6 border-end">
|
||||
<p class="py-3 mb-0">
|
||||
<i class="fa fa-fw fa-ticket text-muted me-1"></i> پشتیبانی دارد </p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="py-3 mb-0">
|
||||
<i class="fa fa-fw fa-users-rectangle text-muted me-1"></i> کاربر نامحدود </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-content block-content-full">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<RouterLink class="btn btn-sm btn-primary w-100" :to="'/acc/plugin-center/view-end/' + plugin.code"> خرید </RouterLink>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<RouterLink :to="'/acc/plugins/' + plugin.code +'/intro'" class="btn btn-sm btn-alt-primary w-100"> کاتالوگ </RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-toolbar title="فهرست افزونهها" flat color="toolbar">
|
||||
<template v-slot:prepend>
|
||||
<v-btn icon @click="$router.back()" class="me-2 d-none d-md-flex" variant="text">
|
||||
<v-icon>mdi-arrow-right</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-toolbar>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="plugin in plugins"
|
||||
:key="plugin.code"
|
||||
cols="12"
|
||||
md="6"
|
||||
lg="4"
|
||||
xl="4"
|
||||
>
|
||||
<v-card class="mb-6 elevation-3" rounded="lg">
|
||||
<v-img
|
||||
:src="'/u/img/plugins/' + plugin.icon"
|
||||
height="200"
|
||||
class="rounded-t-lg"
|
||||
cover
|
||||
></v-img>
|
||||
<v-card-title class="d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<v-icon class="me-2" color="primary">mdi-power-plug</v-icon>
|
||||
<span class="font-weight-bold">{{ plugin.name }}</span>
|
||||
</div>
|
||||
<!-- END House -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</v-card-title>
|
||||
<v-card-subtitle class="d-flex align-center justify-space-between mb-2">
|
||||
<v-chip color="success" text-color="white" size="small">
|
||||
{{ Intl.NumberFormat('en-US').format(plugin.price) }} تومان
|
||||
</v-chip>
|
||||
<v-chip color="info" text-color="white" size="small">
|
||||
<v-icon size="small" class="me-1">mdi-clock-outline</v-icon>
|
||||
{{ plugin.timelabel }}
|
||||
</v-chip>
|
||||
</v-card-subtitle>
|
||||
<v-divider></v-divider>
|
||||
<v-row class="text-center py-2 bg-grey-lighten-4">
|
||||
<v-col cols="6" class="border-e">
|
||||
<v-icon class="me-1" color="grey">mdi-ticket</v-icon>
|
||||
پشتیبانی دارد
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<v-icon class="me-1" color="grey">mdi-account-group</v-icon>
|
||||
کاربر نامحدود
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions class="justify-center">
|
||||
<RouterLink :to="'/acc/plugin-center/view-end/' + plugin.code">
|
||||
<v-btn
|
||||
color="success"
|
||||
class="mx-2 px-4"
|
||||
elevation="1"
|
||||
variant="flat"
|
||||
>
|
||||
<v-icon start class="ms-1">mdi-cart</v-icon>
|
||||
خرید
|
||||
</v-btn>
|
||||
</RouterLink>
|
||||
<RouterLink :to="'/acc/plugins/' + plugin.code + '/intro'">
|
||||
<v-btn
|
||||
color="info"
|
||||
class="mx-2 px-4"
|
||||
elevation="1"
|
||||
variant="outlined"
|
||||
>
|
||||
<v-icon start class="ms-1">mdi-book-open-page-variant</v-icon>
|
||||
کاتالوگ
|
||||
</v-btn>
|
||||
</RouterLink>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -12,13 +12,27 @@ export default defineComponent({
|
|||
name: '',
|
||||
price: 0,
|
||||
timelabel: 0,
|
||||
icon: '',
|
||||
},
|
||||
}),
|
||||
created() {
|
||||
axios.post('/api/plugin/get/info/' + this.$route.params.id).then((response) => {
|
||||
this.item = response.data;
|
||||
this.loading = false;
|
||||
});
|
||||
axios.post('/api/plugin/get/info/' + this.$route.params.id)
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
data.id = Number(data.id);
|
||||
data.price = Number(data.price);
|
||||
this.item = data;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
Swal.fire({
|
||||
text: 'خطا در دریافت اطلاعات افزونه!',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'باشه',
|
||||
});
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
insert() {
|
||||
|
@ -51,51 +65,158 @@ export default defineComponent({
|
|||
|
||||
<template>
|
||||
<!-- هدر -->
|
||||
<v-toolbar color="toolbar" title="جزئیات خرید افزونه">
|
||||
<v-toolbar title="جزئیات خرید افزونه" flat color="toolbar">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||
<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" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-btn icon @click="$router.back()" class="me-2 d-none d-md-flex" variant="text">
|
||||
<v-icon>mdi-arrow-right</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-toolbar>
|
||||
|
||||
<!-- محتوای اصلی -->
|
||||
<v-card :loading="loading" elevation="2" class="pa-4 ma-2" color="lighten-5">
|
||||
<v-card-title class="text-center">
|
||||
<v-icon size="x-large" color="grey">mdi-toy-brick-outline</v-icon>
|
||||
</v-card-title>
|
||||
<v-card-text class="text-center">
|
||||
<!-- نام افزونه -->
|
||||
<h3 class="text-h5 text-dark mt-3">{{ item.name }}</h3>
|
||||
|
||||
<!-- مدت اعتبار -->
|
||||
<p class="text-body-1 text-muted mb-3">
|
||||
مدت اعتبار افزونه:
|
||||
<span class="text-primary">{{ item.timelabel }}</span>
|
||||
</p>
|
||||
|
||||
<!-- قیمت اصلی -->
|
||||
<v-chip color="primary" class="mb-3">
|
||||
{{ formattedPrice }} تومان
|
||||
</v-chip>
|
||||
|
||||
<!-- مبلغ قابل پرداخت -->
|
||||
<p class="text-body-1 font-weight-bold text-muted">
|
||||
مبلغ قابل پرداخت (با احتساب مالیات بر ارزش افزوده و کارمزد درگاه واسط):
|
||||
</p>
|
||||
<h3 class="text-success">{{ totalPrice }} تومان</h3>
|
||||
</v-card-text>
|
||||
|
||||
<!-- دکمه پرداخت -->
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn variant="tonal" block color="primary" @click="insert">
|
||||
پرداخت آنلاین از طریق زرینپال
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<v-container class="d-flex justify-center align-center" style="min-height: 80vh;">
|
||||
<v-row justify="center" align="center">
|
||||
<v-col cols="12" md="7" lg="5">
|
||||
<div v-if="loading" class="d-flex flex-column align-center justify-center py-16">
|
||||
<v-progress-circular indeterminate color="primary" size="48" />
|
||||
<div class="mt-4 text-body-2 text-muted">در حال بارگذاری اطلاعات...</div>
|
||||
</div>
|
||||
<v-card v-else elevation="6" class="pa-7 rounded-xl pay-card clean-card">
|
||||
<v-img
|
||||
:src="item.icon ? '/u/img/plugins/' + item.icon : '/images/plugin-default.png'"
|
||||
height="200"
|
||||
class="rounded-t-lg plugin-image mb-5"
|
||||
cover
|
||||
alt="عکس افزونه"
|
||||
/>
|
||||
<v-card-text class="text-center mt-3">
|
||||
<h2 class="text-h5 font-weight-bold mb-4 main-title">{{ item.name }}</h2>
|
||||
<div class="mb-4">
|
||||
<span class="label">مدت اعتبار:</span>
|
||||
<span class="value">{{ item.timelabel }}</span>
|
||||
</div>
|
||||
<v-divider class="my-4" />
|
||||
<div class="mb-3">
|
||||
<span class="label">قیمت افزونه:</span>
|
||||
<span class="value price">{{ formattedPrice }} تومان</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="label">مبلغ قابل پرداخت (با مالیات و کارمزد):</span>
|
||||
<span class="value total">{{ totalPrice }} تومان</span>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center mt-8 mb-2 pay-btn-wrapper">
|
||||
<v-btn
|
||||
color="success"
|
||||
size="x-large"
|
||||
class="rounded-3 px-12 pay-btn"
|
||||
@click="insert"
|
||||
prepend-icon="mdi-credit-card-outline"
|
||||
elevation="1"
|
||||
>
|
||||
پرداخت آنلاین
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
color="grey-darken-2"
|
||||
size="large"
|
||||
class="cancel-btn rounded-3"
|
||||
@click="$router.back()"
|
||||
prepend-icon="mdi-close-circle-outline"
|
||||
>
|
||||
انصراف
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.text-h5 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.pay-card.clean-card {
|
||||
background: #fff;
|
||||
border: 1px solid #f1f1f1;
|
||||
box-shadow: 0 4px 24px 0 rgba(60, 72, 88, 0.08);
|
||||
min-height: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.main-title {
|
||||
color: #222;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.label {
|
||||
color: #888;
|
||||
font-size: 1rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.value {
|
||||
color: #222;
|
||||
font-size: 1.08rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.price {
|
||||
color: #1976d2;
|
||||
font-weight: bold;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.total {
|
||||
color: #388e3c;
|
||||
font-weight: bold;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.pay-btn-wrapper {
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.pay-btn {
|
||||
background: #43a047 !important;
|
||||
color: #fff !important;
|
||||
font-size: 1.15rem;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 8px rgba(67, 160, 71, 0.13);
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
.pay-btn:hover {
|
||||
transform: scale(1.04);
|
||||
box-shadow: 0 6px 24px rgba(67, 160, 71, 0.18);
|
||||
}
|
||||
.cancel-btn {
|
||||
border: 1px solid #bdbdbd !important;
|
||||
color: #757575 !important;
|
||||
background: #fff !important;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.cancel-btn:hover {
|
||||
color: #ff9800 !important;
|
||||
border-color: #ff9800 !important;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.pay-card.clean-card {
|
||||
min-height: 340px;
|
||||
padding: 1.5rem !important;
|
||||
}
|
||||
.main-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.pay-btn-wrapper {
|
||||
bottom: 12px;
|
||||
}
|
||||
}
|
||||
.plugin-image {
|
||||
display: block;
|
||||
margin-bottom: 1.5rem;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 2px 12px rgba(60,72,88,0.10);
|
||||
background: #f8f8f8;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
|
@ -69,6 +69,11 @@
|
|||
import { getSiteName } from "@/hesabixConfig";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
interface BeforeInstallPromptEvent extends Event {
|
||||
prompt: () => Promise<void>;
|
||||
userChoice: Promise<{ outcome: string }>;
|
||||
}
|
||||
|
||||
const installPromptEvent = ref<Event | null>(null);
|
||||
const browserName = ref<string>("");
|
||||
const chromeBanner = ref<boolean>(false);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<v-tabs v-model="activeTab" color="primary" grow>
|
||||
<v-tab value="personal" :text="$t('tabs.personal_info')"></v-tab>
|
||||
<v-tab value="marketing" :text="$t('tabs.marketing_info')"></v-tab>
|
||||
<v-tab value="suggestions">{{ $t('tabs.suggestions') }}</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-window v-model="activeTab">
|
||||
|
@ -109,15 +108,6 @@
|
|||
</v-card-text>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
|
||||
<!-- تب پیشنهادات -->
|
||||
<v-window-item value="suggestions">
|
||||
<v-card class="ma-4">
|
||||
<v-card-text>
|
||||
<!-- این بخش فعلاً خالی است -->
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
|
||||
<!-- دیالوگها -->
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-system-bar color="primaryLight2">
|
||||
<v-avatar :image="getbase() + 'img/logo-blue.png'" size="20" class="me-2" />
|
||||
<v-avatar :image="getbase() + 'u/img/logo-blue.png'" size="20" class="me-2" />
|
||||
<span>{{ siteSlogan }}</span>
|
||||
<v-spacer />
|
||||
</v-system-bar>
|
||||
|
@ -10,7 +10,7 @@
|
|||
{{ siteName }}
|
||||
</template>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar :image="getbase() + 'img/favw.png'" />
|
||||
<v-avatar :image="getbase() + 'u/img/favw.png'" />
|
||||
</template>
|
||||
</v-card>
|
||||
<v-list class="px-0 pt-0">
|
||||
|
@ -68,7 +68,7 @@ import { applicationStore } from "@/stores/applicationStore";
|
|||
import { useUserStore } from "@/stores/userStore";
|
||||
import { ref, defineComponent } from "vue";
|
||||
import { mapActions, mapState, mapStores } from "pinia";
|
||||
import Change_lang from "/src/components/application/buttons/change_lang.vue";
|
||||
import Change_lang from "@/components/application/buttons/change_lang.vue";
|
||||
|
||||
export default defineComponent({
|
||||
// eslint-disable-next-line vue/multi-word-component-names,vue/no-reserved-component-names
|
||||
|
|
Loading…
Reference in a new issue