@@ -69,6 +75,13 @@
+
+
+
+ اسناد تایید شده
+ اسناد در انتظار تایید
+
+
سند حسابداری
+
+
+ mdi-check-decagram
+
+
+
+
+ mdi-cancel
+
+
@@ -205,6 +230,14 @@
{{ $filters.formatNumber(item.amount) }}
+
+
+ {{ getApprovalStatusText(item) }}
+
+
+
+ {{ item.approvedBy?.fullName || '-' }}
+
@@ -285,6 +318,10 @@ const sumTotal = ref(0);
const sumSelected = ref(0);
const sortBy = ref('id');
const sortDesc = ref(true);
+const currentTab = ref('approved');
+const business = ref({ requireTwoStepApproval: false, approvers: { receiveFromPersons: null } });
+const currentUser = ref({ email: '', owner: false });
+const bulkLoading = ref(false);
const allHeaders = reactive([
{ title: '', key: 'select', sortable: false, visible: true, customizable: false },
@@ -295,13 +332,20 @@ const allHeaders = reactive([
{ title: 'تاریخ', key: 'date', sortable: true, visible: true },
{ title: 'شرح', key: 'des', sortable: true, visible: true },
{ title: 'مبلغ', key: 'amount', sortable: true, visible: true },
+ { title: 'وضعیت تایید', key: 'approvalStatus', sortable: true, visible: true },
+ { title: 'تاییدکننده', key: 'approvedBy', sortable: true, visible: true },
]);
const customizableHeaders = computed(() =>
allHeaders.filter(h => h.customizable !== false && h.key !== 'operation')
);
const visibleHeaders = computed(() =>
- allHeaders.filter(h => h.customizable === false || h.visible)
+ allHeaders.filter(h => {
+ if ((h.key === 'approvalStatus' || h.key === 'approvedBy') && !business.value.requireTwoStepApproval) {
+ return false;
+ }
+ return h.customizable === false || h.visible;
+ })
);
const dateFilterOptions = [
@@ -345,6 +389,7 @@ const loadData = async (options = null) => {
itemsPerPage: options?.itemsPerPage || itemsPerPage.value,
search: searchValue.value,
dateFilter: dateFilter.value,
+ approvalFilter: business.value.requireTwoStepApproval ? currentTab.value : 'all',
sortBy: [{
key: sortBy.value,
order: sortDesc.value ? 'desc' : 'asc'
@@ -616,8 +661,141 @@ watch([page, itemsPerPage], () => {
loadData();
}, { deep: true });
+watch(currentTab, () => {
+ loadData();
+});
+
+// Methods for approval functionality
+const checkApprover = () => {
+ return business.value.requireTwoStepApproval && (business.value.approvers.receiveFromPersons == currentUser.value.email || currentUser.value.owner === true);
+};
+
+const getApprovalStatusText = (item) => {
+ if (!business.value?.requireTwoStepApproval) return 'تایید دو مرحلهای غیرفعال';
+
+ if (item.isPreview) return 'در انتظار تایید';
+ if (item.isApproved) return 'تایید شده';
+ return 'تایید شده';
+};
+
+const getApprovalStatusColor = (item) => {
+ if (!business.value?.requireTwoStepApproval) return 'default';
+
+ if (item.isPreview) return 'warning';
+ if (item.isApproved) return 'success';
+ return 'success';
+};
+
+const canShowApprovalButton = (item) => {
+ if (!checkApprover()) return false;
+ if (item?.isApproved) return false;
+ return true;
+};
+
+const canShowUnapproveButton = (item) => {
+ return !canShowApprovalButton(item) && checkApprover();
+};
+
+const approveReceive = async (code) => {
+ try {
+ loading.value = true;
+ const response = await axios.post(`/api/approval/approve/receive/${code}`);
+
+ await loadData();
+
+ if (response.data.success) {
+ Swal.fire({ text: 'سند دریافت تایید شد', icon: 'success', confirmButtonText: 'قبول' });
+ } else {
+ Swal.fire({ text: response.data.message, icon: 'error', confirmButtonText: 'قبول' });
+ }
+ } catch (error) {
+ Swal.fire({ text: 'خطا در تایید سند: ' + (error.response?.data?.message || error.message), icon: 'error', confirmButtonText: 'قبول' });
+ } finally {
+ loading.value = false;
+ }
+};
+
+const unapproveReceive = async (code) => {
+ try {
+ loading.value = true;
+ const response = await axios.post(`/api/approval/unapprove/receive/${code}`);
+
+ await loadData();
+
+ if (response.data.success) {
+ Swal.fire({ text: 'تایید سند لغو شد', icon: 'success', confirmButtonText: 'قبول' });
+ } else {
+ Swal.fire({ text: response.data.message, icon: 'error', confirmButtonText: 'قبول' });
+ }
+ } catch (error) {
+ Swal.fire({ text: 'خطا در لغو تایید سند: ' + (error.response?.data?.message || error.message), icon: 'error', confirmButtonText: 'قبول' });
+ } finally {
+ loading.value = false;
+ }
+};
+
+const approveSelectedReceives = async () => {
+ if (selectedItems.value.length === 0) {
+ Swal.fire({ text: 'هیچ موردی انتخاب نشده است.', icon: 'warning', confirmButtonText: 'قبول' });
+ return;
+ }
+
+ const selectedReceives = items.value.filter(rec => selectedItems.value.some(sel => sel.code === rec.code));
+ if (selectedReceives.some(rec => !(!rec.isApproved && rec.isPreview))) {
+ Swal.fire({ text: 'برخی اسناد انتخابی تایید شده هستند.', icon: 'warning', confirmButtonText: 'قبول' });
+ return;
+ }
+
+ Swal.fire({
+ title: 'تایید اسناد انتخابی',
+ text: 'اسناد انتخابشده تایید خواهند شد.',
+ icon: 'question',
+ showCancelButton: true,
+ confirmButtonText: 'بله',
+ cancelButtonText: 'خیر'
+ }).then(async (r) => {
+ if (!r.isConfirmed) return;
+ bulkLoading.value = true;
+
+ try {
+ await axios.post(`/api/approval/approve/group/receive`, {
+ 'docIds': selectedItems.value.map(item => item.code)
+ });
+ Swal.fire({ text: 'اسناد تایید شدند.', icon: 'success', confirmButtonText: 'قبول' });
+ selectedItems.value = [];
+ await loadData();
+ } catch (e) {
+ Swal.fire({ text: 'خطا در تایید اسناد', icon: 'error', confirmButtonText: 'قبول' });
+ } finally {
+ bulkLoading.value = false;
+ }
+ });
+};
+
+const loadBusinessInfo = async () => {
+ try {
+ const response = await axios.get('/api/business/get/info/' + localStorage.getItem('activeBid'));
+ business.value = response.data || { requireTwoStepApproval: false, approvers: { receiveFromPersons: null } };
+ } catch (error) {
+ console.error('Error loading business info:', error);
+ business.value = { requireTwoStepApproval: false, approvers: { receiveFromPersons: null } };
+ }
+};
+
+const loadCurrentUser = async () => {
+ try {
+ const response = await axios.post('/api/business/get/user/permissions');
+ currentUser.value = response.data || { email: '', owner: false };
+ } catch (error) {
+ console.error('Error loading current user:', error);
+ currentUser.value = { email: '', owner: false };
+ }
+};
+
onMounted(() => {
loadColumnSettings();
+ loadBusinessInfo();
+ loadCurrentUser();
loadData();
});