progress in view invoice sell

This commit is contained in:
Hesabix 2025-03-29 13:03:41 +00:00
parent 474a1eff10
commit 6d63f427ac
5 changed files with 920 additions and 841 deletions

View file

@ -0,0 +1,134 @@
<script lang="ts">
import { defineComponent } from 'vue';
import axios from 'axios';
export default defineComponent({
name: 'PrintOptions',
props: {
invoiceId: { type: String, required: true },
},
data: () => ({
dialog: false,
loading: false,
printOptions: {
pays: true,
note: true,
bidInfo: true,
taxInfo: true,
discountInfo: true,
paper: 'A4-L',
},
}),
methods: {
printInvoice(pdf = true, cloudPrinters = true) {
this.loading = true;
axios
.post('/api/sell/print/invoice', {
code: this.$props.invoiceId,
pdf,
printers: cloudPrinters,
printOptions: this.printOptions,
})
.then((response) => {
window.open(`${this.$API_URL}/front/print/${response.data.id}`, '_blank', 'noreferrer');
this.dialog = false;
})
.catch((error) => {
console.error('خطا در چاپ:', error);
})
.finally(() => {
this.loading = false;
});
},
// متد برای جلوگیری از بسته شدن دیالوگ با کلیک خارج
preventClose() {
// هیچ عملی انجام نمیشود تا دیالوگ بسته نشود
},
},
});
</script>
<template>
<v-btn icon color="primary" class="ml-2" @click="dialog = true">
<v-icon>mdi-printer</v-icon>
<v-tooltip activator="parent" location="bottom">چاپ فاکتور</v-tooltip>
</v-btn>
<v-dialog
v-model="dialog"
max-width="500"
persistent
@click:outside="preventClose"
>
<v-card>
<v-toolbar color="grey-lighten-4" flat>
<v-toolbar-title>
<v-icon color="primary" left>mdi-printer</v-icon>
چاپ فاکتور
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn
icon
color="green"
:loading="loading"
@click="printInvoice"
class="mx-1"
>
<v-icon>mdi-printer</v-icon>
<v-tooltip activator="parent" location="bottom">چاپ فاکتور</v-tooltip>
</v-btn>
<v-btn icon @click="dialog = false" :disabled="loading">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text class="pt-4">
<p class="mb-2">برای تغییر تنظیمات پیشفرض به بخش تنظیمات چاپ مراجعه نمایید</p>
<v-select
v-model="printOptions.paper"
:items="[
{ title: 'A4 افقی', value: 'A4-L' },
{ title: 'A4 عمودی', value: 'A4' },
{ title: 'A5 افقی', value: 'A5-L' },
{ title: 'A5 عمودی', value: 'A5' },
]"
label="سایز کاغذ و حالت چاپ"
variant="outlined"
></v-select>
<v-switch
v-model="printOptions.bidInfo"
label="اطلاعات کسب‌وکار"
class="my-1"
hide-details
></v-switch>
<v-switch
v-model="printOptions.pays"
label="نمایش پرداخت‌های فاکتور"
class="my-1"
hide-details
></v-switch>
<v-switch
v-model="printOptions.note"
label="یادداشت پایین فاکتور"
class="my-1"
hide-details
></v-switch>
<v-switch
v-model="printOptions.taxInfo"
label="مالیات به تفکیک اقلام"
class="my-1"
hide-details
></v-switch>
<v-switch
v-model="printOptions.discountInfo"
label="تخفیف به تفکیک اقلام"
class="my-1"
hide-details
></v-switch>
</v-card-text>
</v-card>
</v-dialog>
</template>
<style scoped>
/* استایل‌های اضافی در صورت نیاز */
</style>

View file

@ -0,0 +1,157 @@
<script lang="ts">
import { defineComponent, ref } from 'vue';
import axios from 'axios';
export default defineComponent({
name: 'ShareOptions',
props: {
shortlinkUrl: { type: String, required: true },
mobile: { type: String, required: true },
invoiceId: { type: Number, required: true },
},
data: () => ({
dialog: false,
internalMobile: '',
copyLabel: ref('کپی'),
messageHint: ref('ارسال'),
hintColor: ref('grey'),
loading: ref(false),
}),
created() {
this.internalMobile = this.mobile;
},
methods: {
copyToClipboard() {
navigator.clipboard.writeText(this.shortlinkUrl);
this.copyLabel = 'کپی شد!';
},
sendSMS() {
this.loading = true;
const regex = new RegExp('^(\\+98|0)?9\\d{9}$');
if (!regex.test(this.internalMobile)) {
this.messageHint = 'شماره موبایل نامعتبر است.';
this.hintColor = 'red';
this.loading = false;
return;
}
this.messageHint = 'در حال ارسال...';
this.hintColor = 'grey';
axios
.post(`/api/sms/send/sell-invoice/${this.invoiceId}/${this.internalMobile}`)
.then((response) => {
if (response.data.result == 2) {
this.messageHint = 'اعتبار سرویس پیامک کافی نیست.';
this.hintColor = 'red';
} else if (response.data.result == 1) {
this.messageHint = 'پیامک ارسال شد!';
this.hintColor = 'green';
}
this.loading = false;
})
.catch((error) => {
console.error('خطا در ارسال پیامک:', error);
this.messageHint = 'خطا در ارسال پیامک رخ داد.';
this.hintColor = 'red';
this.loading = false;
});
},
},
});
</script>
<template>
<v-btn icon color="success" class="ml-2" @click="dialog = true">
<v-icon>mdi-share-variant</v-icon>
<v-tooltip activator="parent" location="bottom">اشتراکگذاری</v-tooltip>
</v-btn>
<v-dialog v-model="dialog" max-width="500" persistent>
<v-card>
<v-toolbar color="grey-lighten-4" flat>
<v-toolbar-title>
<v-icon color="success" left>mdi-share-variant</v-icon>
اشتراکگذاری
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="dialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-text-field
v-model="shortlinkUrl"
readonly
append-inner-icon="mdi-content-copy"
@click:append-inner="copyToClipboard"
label="پیوند فاکتور"
variant="outlined"
:hint="copyLabel"
></v-text-field>
<v-text-field
class="mt-4"
v-model="internalMobile"
label="ارسال پیامک"
variant="outlined"
:hint="messageHint"
:color="hintColor"
persistent-hint
>
<template v-slot:append-inner>
<v-btn
color="primary"
:loading="loading"
@click="sendSMS"
>
ارسال
</v-btn>
</template>
</v-text-field>
<div class="mt-3">
<v-icon left>mdi-share-variant</v-icon>
اشتراکگذاری در شبکههای اجتماعی
<div class="mt-2">
<!-- تلگرام -->
<a
:href="'https://t.me/share/url?url=' + encodeURIComponent(shortlinkUrl)"
target="_blank"
>
<img src="/img/icons/telegram.png" class="m-3" style="max-width: 30px;" />
</a>
<!-- ایتا -->
<a
:href="'https://eitaa.com/?text=' + encodeURIComponent(shortlinkUrl)"
target="_blank"
>
<img src="/img/icons/eitaa.jpeg" class="m-3" style="max-width: 30px;" />
</a>
<!-- بله -->
<a
:href="'https://ble.ir/share/url?url=' + encodeURIComponent(shortlinkUrl)"
target="_blank"
>
<img src="/img/icons/bale-logo.png" class="m-3" style="max-width: 30px;" />
</a>
<!-- روبیکا -->
<a
:href="'https://rubika.ir/app/share?text=' + encodeURIComponent(shortlinkUrl)"
target="_blank"
>
<img src="/img/icons/robika.png" class="m-3" style="max-width: 30px;" />
</a>
<!-- واتساپ -->
<a
:href="'https://api.whatsapp.com/send?text=' + encodeURIComponent(shortlinkUrl)"
target="_blank"
>
<v-icon class="m-3" size="30" color="green">mdi-whatsapp</v-icon>
</a>
</div>
</div>
</v-card-text>
</v-card>
</v-dialog>
</template>
<style scoped>
/* استایل‌های اضافی در صورت نیاز */
</style>

View file

@ -1,81 +1,83 @@
<script lang="ts"> <script lang="ts">
import {defineComponent} from 'vue' import { defineComponent } from 'vue';
import axios from "axios"; import axios from 'axios';
import Swal from "sweetalert2"; import Swal from 'sweetalert2';
export default defineComponent({ export default defineComponent({
name: "archiveUpload", name: 'archiveUpload',
props:{ props: {
doctype: String, doctype: String,
docid: [String ,Number], docid: [String, Number],
cat: String cat: String,
}, },
data() { data() {
return { return {
fileStack:[], dialog: false, // برای باز و بسته کردن دیالوگ
des:'', fileStack: [],
des: '',
media: { media: {
saved: [], saved: [],
added: [], added: [],
removed: [] removed: [],
} },
} };
}, },
mounted() { mounted() {
this.getFilesList(); this.getFilesList();
}, },
methods:{ methods: {
changeMedia(media){ changeMedia(media) {
this.media = media this.media = media;
}, },
addMedia(addedImage, addedMedia){ addMedia(addedImage, addedMedia) {
this.media.added = addedMedia this.media.added = addedMedia;
}, },
removeMedia(removedImage, removedMedia){ removeMedia(removedImage, removedMedia) {
this.media.removed = removedMedia this.media.removed = removedMedia;
}, },
getFilesList(){ getFilesList() {
axios.post('api/archive/files/list',{ axios.post('/api/archive/files/list', {
id:this.$props.docid, id: this.$props.docid,
type:this.$props.doctype type: this.$props.doctype,
}).then((resp)=>{ }).then((resp) => {
this.media.added =[]; this.media.added = [];
this.fileStack = resp.data; this.fileStack = resp.data;
this.fileStack.forEach((item)=>{ this.fileStack.forEach((item) => {
axios.post(this.$filters.getApiUrl() + '/api/archive/file/get/' + item.id, { responseType: "arraybuffer" }) axios
.then((response)=>{ .post(`${this.$filters.getApiUrl()}/api/archive/file/get/${item.id}`, { responseType: 'arraybuffer' })
.then((response) => {
const b64 = btoa(String.fromCharCode(...new Uint8Array(response.data))); const b64 = btoa(String.fromCharCode(...new Uint8Array(response.data)));
item.fileBin = "data:" + response.headers['content-type'] + ";base64," + b64; item.fileBin = `data:${response.headers['content-type']};base64,${b64}`;
});
});
}); });
})
})
}, },
deleteItem(item){ deleteItem(item) {
Swal.fire({ Swal.fire({
text: 'آیا برای حذف فایل مطمئن هستید؟', text: 'آیا برای حذف فایل مطمئن هستید؟',
showCancelButton: true, showCancelButton: true,
confirmButtonText: 'بله', confirmButtonText: 'بله',
cancelButtonText: `خیر`, cancelButtonText: 'خیر',
}).then((result) => { }).then((result) => {
/* Read more about isConfirmed, isDenied below */
if (result.isConfirmed) { if (result.isConfirmed) {
axios.post('api/archive/file/remove/' + item.id).then((response)=>{ axios.post(`/api/archive/file/remove/${item.id}`).then((response) => {
if(response.data.result == 1){ if (response.data.result === 1) {
this.getFilesList(); this.getFilesList();
Swal.fire({ Swal.fire({
text: 'فایل با موفقیت حذف شد.', text: 'فایل با موفقیت حذف شد.',
icon: 'success', icon: 'success',
confirmButtonText: 'قبول' confirmButtonText: 'قبول',
}); });
} }
}) });
} }
}) });
}, },
downloadFile(item){ downloadFile(item) {
axios.post(this.$filters.getApiUrl() + '/api/archive/file/get/' + item.id, { responseType: "arraybuffer" }) axios
.then(response => { .post(`${this.$filters.getApiUrl()}/api/archive/file/get/${item.id}`, { responseType: 'arraybuffer' })
const blob = new Blob([response.data], {type: item.fileType}); .then((response) => {
const blob = new Blob([response.data], { type: item.fileType });
const link = document.createElement('a'); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = item.filename; link.download = item.filename;
@ -83,57 +85,75 @@ export default defineComponent({
URL.revokeObjectURL(link.href); URL.revokeObjectURL(link.href);
}); });
}, },
submitArchive(){ submitArchive() {
let formData = new FormData(document.getElementById('archive-file-upload')) const formData = new FormData(document.getElementById('archive-file-upload'));
axios.post('api/archive/file/save',formData,{ axios
.post('/api/archive/file/save', formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data',
} },
}).then((resp)=>{ })
if(resp.data.result === 'nem'){ .then((resp) => {
if (resp.data.result === 'nem') {
Swal.fire({ Swal.fire({
text: 'فضای کافی وجود ندارد لطفا حساب کاربری خود را شارژ نمایید.', text: 'فضای کافی وجود ندارد لطفا حساب کاربری خود را شارژ نمایید.',
icon: 'success', icon: 'error',
confirmButtonText: 'قبول' confirmButtonText: 'قبول',
}); });
}else{ } else {
Swal.fire({ Swal.fire({
text: 'فایل‌های انتخابی ذخیره شدند.', text: 'فایل‌های انتخابی ذخیره شدند.',
icon: 'success', icon: 'success',
confirmButtonText: 'قبول' confirmButtonText: 'قبول',
}); });
this.getFilesList(); this.getFilesList();
this.des = ''; // ریست کردن توضیحات
} }
})
.catch((error) => {
console.error('خطا در آپلود:', error);
Swal.fire({
text: 'خطایی در بارگذاری فایل رخ داد.',
icon: 'error',
confirmButtonText: 'قبول',
});
}); });
}
}, },
}) },
});
</script> </script>
<template> <template>
<!-- Button trigger modal --> <!-- دکمه در تولبار -->
<button type="button" class="btn btn-sm btn-outline-success mx-2" data-bs-toggle="modal" data-bs-target="#archiveModal"> <v-btn icon color="success" class="ml-2" @click="dialog = true">
<span class="badge text-bg-dark me-2">{{fileStack.length}}</span> <v-badge :content="fileStack.length.toString()" color="dark">
<i class="fa fa-file me-1"></i> <v-icon>mdi-archive</v-icon>
<span class="d-none d-sm-inline-block">آرشیو</span> </v-badge>
</button> <v-tooltip activator="parent" location="bottom">آرشیو</v-tooltip>
<!-- Modal --> </v-btn>
<div class="modal modal-lg fade" data-bs-backdrop="static" data-bs-keyboard="false" id="archiveModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog"> <!-- دیالوگ آرشیو -->
<div class="modal-content"> <v-dialog v-model="dialog" max-width="800" persistent>
<div class="modal-header"> <v-card>
<h1 class="modal-title fs-5" id="exampleModalLabel">آرشیو فایل</h1> <v-toolbar color="grey-lighten-4" flat>
<div class="block-options"> <v-toolbar-title>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <v-icon color="success" left>mdi-archive</v-icon>
</div> آرشیو فایل
</div> </v-toolbar-title>
<div class="modal-body"> <v-spacer></v-spacer>
<h5 class="text-primary-dark mt-3 my-0 py-0">افزودن فایل جدید</h5> <v-btn icon @click="dialog = false">
<div> <v-icon>mdi-close</v-icon>
<form id="archive-file-upload" @submit.prevent="submitArchive()"> </v-btn>
<input type="hidden" name="doctype" :value="$props.doctype"> </v-toolbar>
<input type="hidden" name="docid" :value="$props.docid">
<input type="hidden" name="cat" :value="$props.cat"> <v-card-text>
<!-- بخش افزودن فایل جدید -->
<v-row>
<v-col cols="12">
<form id="archive-file-upload" @submit.prevent="submitArchive">
<input type="hidden" name="doctype" :value="$props.doctype" />
<input type="hidden" name="docid" :value="$props.docid" />
<input type="hidden" name="cat" :value="$props.cat" />
<Uploader <Uploader
:server="$filters.getApiUrl() + '/api/archive/file/upload'" :server="$filters.getApiUrl() + '/api/archive/file/upload'"
:media="media.saved" :media="media.saved"
@ -143,68 +163,67 @@ export default defineComponent({
@change="changeMedia" @change="changeMedia"
:maxFilesize="5" :maxFilesize="5"
/> />
<div class="container-fluid mt-2"> <v-text-field
<div class="row"> v-model="des"
<div class="col-9"> label="توضیحات"
<input class="form-control" type="text" name="des" v-model="des" placeholder="توضیحات" aria-label="توضیحات"> placeholder="توضیحات"
</div> outlined
<div class="col-3"> name="des"
<button type="submit" class="btn btn-success d-flex"> class="mt-2"
<i class="fa fa-save me-2"></i> ></v-text-field>
<v-btn type="submit" color="success" class="mt-2">
<v-icon left>mdi-content-save</v-icon>
بارگذاری فایلها بارگذاری فایلها
</button> </v-btn>
</div>
</div>
</div>
</form> </form>
</div> </v-col>
<hr> </v-row>
<h5 class="text-primary-dark mt-3 mb-0 pb-0">آرشیو فایلها</h5>
<table class="table table-striped table-hover table-borderless table-vcenter fs-sm text-center"> <v-divider class="my-4"></v-divider>
<!-- لیست فایلها -->
<v-row>
<v-col cols="12">
<h5 class="text-primary">آرشیو فایلها</h5>
<v-table class="elevation-1">
<thead> <thead>
<tr class="text-uppercase"> <tr>
<th>پیش نمایش</th> <th class="text-center">پیشنمایش</th>
<th>نام فایل</th> <th class="text-center">نام فایل</th>
<th class="">سایز فایل(مگابایت)</th> <th class="text-center">سایز فایل (مگابایت)</th>
<th class="">تاریخ</th> <th class="text-center">تاریخ</th>
<th>عملیات</th> <th class="text-center">عملیات</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="item in fileStack"> <tr v-for="item in fileStack" :key="item.id">
<td> <td class="text-center">
<img class="img-fluid" :src="item.fileBin" alt="پیش نمایش"> <v-img :src="item.fileBin" max-width="50" max-height="50" class="mx-auto" alt="پیش‌نمایش" />
</td> </td>
<td> <td class="text-center">{{ item.filename }}</td>
<span class="fw-semibold">{{item.filename}}</span> <td class="text-center">{{ item.filesize }}</td>
</td> <td class="text-center">{{ item.dateSubmit }}</td>
<td> <td class="text-center">
<span>{{item.filesize}}</span> <v-btn icon small color="primary" @click="downloadFile(item)">
</td> <v-icon>mdi-download</v-icon>
<td> </v-btn>
<span>{{item.dateSubmit}}</span> <v-btn icon small color="error" class="ml-2" @click="deleteItem(item)">
</td> <v-icon>mdi-trash-can</v-icon>
<td class="text-center text-nowrap fw-medium"> </v-btn>
<a class="btn btn-sm btn-link" href="/" @click.prevent="downloadFile(item)">
<i class="fa fa-download"></i>
</a>
<button @click="deleteItem(item)" class="btn btn-sm ms-2 btn-link text-danger">
<i class="fa fa-trash"></i>
</button>
</td> </td>
</tr> </tr>
<tr v-if="fileStack.length === 0">
<td colspan="5" class="text-center text-muted">فایلی موجود نیست</td>
</tr>
</tbody> </tbody>
</table> </v-table>
</div> </v-col>
<div class="modal-footer"> </v-row>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">بازگشت</button> </v-card-text>
</div> </v-card>
</div> </v-dialog>
</div>
</div>
</template> </template>
<style scoped> <style scoped>
/* استایل‌های اضافی اگه نیاز باشه */
</style> </style>

View file

@ -1,34 +1,37 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { defineComponent } from 'vue'
import Swal from "sweetalert2";
import { ref } from 'vue';
import Loading from 'vue-loading-overlay';
export default defineComponent({ export default defineComponent({
name: "notes", name: 'Notes',
components:{
Loading
},
props: { props: {
stat: Object, stat: Object,
code: String, code: String,
typeNote: String typeNote: String,
}, },
data: () => { data() {
return { return {
loading: ref(true), dialog: false, // برای باز و بسته کردن دیالوگ
loading: false,
items: [], items: [],
des: '', des: '',
} snackbar: false, // برای نمایش اسنکبار
snackbarText: '', // متن پیام اسنکبار
snackbarColor: 'success', // رنگ اسنکبار (success, error)
};
},
mounted() {
this.loadData();
}, },
methods: { methods: {
loadData() { loadData() {
this.loading = true; this.loading = true;
axios.post('/api/notes/list', { axios
.post('/api/notes/list', {
type: this.$props.typeNote, type: this.$props.typeNote,
code: this.$props.code code: this.$props.code,
}).then((response) => { })
.then((response) => {
this.items = response.data; this.items = response.data;
this.$props.stat.count = response.data.length; this.$props.stat.count = response.data.length;
this.loading = false; this.loading = false;
@ -36,92 +39,172 @@ export default defineComponent({
}, },
remove(id) { remove(id) {
this.loading = true; this.loading = true;
axios.post('/api/notes/remove/' + id).then((response) => { axios.post(`/api/notes/remove/${id}`).then((response) => {
this.loading = false; this.loading = false;
Swal.fire({ this.items = this.items.filter(item => item.id !== id); // حذف از لیست محلی
text: ' با موفقیت حذف شد.', this.$props.stat.count = this.items.length; // بهروزرسانی تعداد
icon: 'success', this.snackbarText = 'یادداشت با موفقیت حذف شد.';
confirmButtonText: 'قبول' this.snackbarColor = 'success';
}).then((result) => { this.snackbar = true;
this.loadData(); }).catch((error) => {
this.loading = false;
this.snackbarText = 'خطایی در حذف یادداشت رخ داد.';
this.snackbarColor = 'error';
this.snackbar = true;
console.error('خطا:', error);
}); });
})
}, },
save() { save() {
if (this.des.trim() == '') { if (this.des.trim() === '') {
Swal.fire({ this.snackbarText = 'شرح الزامی است.';
text: 'شرح الزامی است.', this.snackbarColor = 'error';
icon: 'error', this.snackbar = true;
confirmButtonText: 'قبول' } else {
})
}
else {
this.loading = true; this.loading = true;
axios.post('/api/notes/add', { axios
.post('/api/notes/add', {
des: this.des, des: this.des,
type: this.$props.typeNote, type: this.$props.typeNote,
code: this.$route.params.id code: this.$route.params.id,
}).then((response) => {
this.loading = false;
Swal.fire({
text: ' با موفقیت ثبت شد.',
icon: 'success',
confirmButtonText: 'قبول'
}).then((result) => {
this.loadData();
this.des = '';
});
}) })
} .then((response) => {
this.loading = false;
// اضافه کردن یادداشت جدید به لیست محلی
this.items.unshift({
id: response.data.id, // فرض میکنیم سرور id رو برمیگردونه
des: this.des,
});
this.$props.stat.count = this.items.length; // بهروزرسانی تعداد
this.snackbarText = 'یادداشت با موفقیت ثبت شد.';
this.snackbarColor = 'success';
this.snackbar = true;
this.des = ''; // ریست کردن ورودی
})
.catch((error) => {
this.loading = false;
this.snackbarText = 'خطایی در ثبت یادداشت رخ داد.';
this.snackbarColor = 'error';
this.snackbar = true;
console.error('خطا:', error);
});
} }
}, },
mounted() { },
this.loadData(); });
}
})
</script> </script>
<template> <template>
<!-- Modal --> <!-- دکمه در تولبار -->
<div class="modal modal-lg fade" id="notesModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" <v-btn icon color="warning" class="ml-2" @click="dialog = true">
aria-labelledby="notesModalLabel" aria-hidden="true"> <v-badge :content="items.length.toString()" color="dark">
<div class="modal-dialog"> <v-icon>mdi-note</v-icon>
<div class="modal-content"> </v-badge>
<div class="modal-header bg-warning text-white"> <v-tooltip activator="parent" location="bottom">یادداشتها</v-tooltip>
<h1 class="modal-title fs-5" id="notesModalLabel"> </v-btn>
<i class="fa-regular fa-note-sticky me-1"></i>
<!-- دیالوگ یادداشتها -->
<v-dialog v-model="dialog" max-width="600" persistent>
<v-card>
<v-toolbar color="toolbar" flat dark>
<v-toolbar-title>
<v-icon color="warning" left>mdi-note</v-icon>
یادداشتها یادداشتها
</h1> </v-toolbar-title>
<div class="block-options"> <v-spacer></v-spacer>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <v-btn icon @click="dialog = false">
</div> <v-icon>mdi-close</v-icon>
</div> </v-btn>
<div class="modal-body"> </v-toolbar>
<div class="input-group mb-3">
<input v-model="des" type="text" class="form-control" placeholder="شرح" aria-label="افزودن یادداشت" <v-card-text>
aria-describedby="button-addon1"> <!-- فرم افزودن یادداشت -->
<button :disabled="this.loading" @click="save()" class="btn btn-outline-success" type="button" <v-row>
id="button-addon1">ثبت</button> <v-col cols="12">
</div> <v-text-field
<Loading color="blue" loader="dots" v-model:active="loading" :is-full-page="false"/> v-model="des"
<ul :disabled="this.loading" class="list-group"> label="شرح"
<li v-for="item in items" class="list-group-item d-flex justify-content-between align-items-center"> placeholder="شرح یادداشت"
<span class=""> outlined
{{ item.des }} class="mt-2"
</span> :disabled="loading"
<a title="حذف" @click="remove(item.id)" class="text-danger rounded-pill float-start"> @keyup.enter="save"
<i class="fa fa-trash"></i> :loading="loading"
</a> ></v-text-field>
</li> <v-btn
</ul> color="success"
<div v-if="items.length == 0"> @click="save"
نتیجهای یافت نشد class="mt-2"
</div> :loading="loading"
</div> >
</div> <v-icon left>mdi-content-save</v-icon>
</div> ثبت یادداشت
</div> </v-btn>
</v-col>
</v-row>
<v-divider class="my-4"></v-divider>
<!-- لیست یادداشتها با اسکرول -->
<v-row>
<v-col cols="12">
<h5 class="text-primary">یادداشتهای ثبتشده</h5>
<v-list
max-height="300"
class="overflow-y-auto"
>
<v-list-item
v-for="item in items"
:key="item.id"
class="my-1"
>
<v-list-item-title class="d-flex align-center">
<span>{{ item.des }}</span>
<v-spacer></v-spacer>
<v-btn
icon
variant="plain"
:disabled="loading"
@click="remove(item.id)"
>
<v-icon color="error">mdi-trash-can</v-icon>
<v-tooltip activator="parent" location="top">حذف</v-tooltip>
</v-btn>
</v-list-item-title>
</v-list-item>
<v-list-item v-if="items.length === 0">
<v-list-item-title class="text-muted text-center">
یادداشتی موجود نیست
</v-list-item-title>
</v-list-item>
</v-list>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
<!-- اسنکبار برای نمایش پیامها -->
<v-snackbar
v-model="snackbar"
:color="snackbarColor"
timeout="3000"
location="top"
>
{{ snackbarText }}
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="snackbar = false"
>
بستن
</v-btn>
</template>
</v-snackbar>
</template> </template>
<style scoped></style> <style scoped>
.overflow-y-auto {
overflow-y: auto;
}
</style>

View file

@ -1,85 +1,56 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue';
import axios from "axios"; import axios from 'axios';
import Swal from "sweetalert2"; import Swal from 'sweetalert2';
import rec from "../component/rec.vue"; import Rec from '../component/rec.vue';
import recList from "../component/recList.vue"; import RecList from '../component/recList.vue';
import ArchiveUpload from "../component/archive/archiveUpload.vue"; import ArchiveUpload from '../component/archive/archiveUpload.vue';
import type { Header, Item } from "vue3-easy-data-table"; import Notes from '../component/notes.vue';
import { getApiUrl } from "@/hesabixConfig"; import PrintOptions from '@/components/widgets/PrintOptions.vue';
import notes from '../component/notes.vue'; import ShareOptions from '@/components/widgets/ShareOptions.vue';
import { getApiUrl } from '@/hesabixConfig';
export default defineComponent({ export default defineComponent({
name: "viewInvoice", name: 'viewInvoice',
components: { components: {
ArchiveUpload, ArchiveUpload,
rec: rec, Rec,
recList: recList, RecList,
notes Notes,
PrintOptions,
ShareOptions,
}, },
watch: { watch: {
'PayWindowsState.submited'(newValue, oldValue) { 'PayWindowsState.submited'(newValue) {
this.PayWindowsState.submited = false; this.PayWindowsState.submited = false;
if (newValue) { if (newValue) {
this.loadData(); this.loadData();
this.recModal.hide() this.recDialog = false;
} }
}, },
'recListWindowsState.submited'(newValue, oldValue) { 'recListWindowsState.submited'(newValue) {
this.recListWindowsState.submited = false; this.recListWindowsState.submited = false;
if (newValue) { if (newValue) {
this.loadData(); this.loadData();
} }
}
}, },
data: () => {
return {
printOptions: {
pays: true,
note: true,
bidInfo: true,
taxInfo: true,
discountInfo: true,
paper: 'A4-L'
}, },
notes: { data: () => ({
count: 0 recDialog: false,
}, recListDialog: false,
PayWindowsState: { activeTab: 'invoice-info',
submited: false
},
recListWindowsState: {
submited: false
},
recModal: {},
recListModal: {},
loading: ref(true), loading: ref(true),
shortlink_url: '', shortlink_url: '',
copy_label: 'کپی', PayWindowsState: { submited: false },
send_message_label: 'ارسال', recListWindowsState: { submited: false },
bid: { notes: { count: 0 },
legal_name: '', bid: { legal_name: '', shortlinks: false },
shortlinks: false,
},
item: { item: {
doc: { doc: { id: 0, date: null, code: null, des: '', amount: 0, profit: 0, shortLink: null },
id: 0,
date: null,
code: null,
des: '',
amount: 0,
profit: 0,
shortLink: null,
},
relatedDocs: [], relatedDocs: [],
rows: [] rows: [],
},
person: {
nikename: null,
mobile: '',
tel: '',
addres: '',
postalcode: '',
}, },
person: { nikename: null, mobile: '', tel: '', addres: '', postalcode: '' },
commoditys: [], commoditys: [],
totalRec: 0, totalRec: 0,
totalDiscount: 0, totalDiscount: 0,
@ -87,46 +58,63 @@ export default defineComponent({
transferCost: 0, transferCost: 0,
discountAll: 0, discountAll: 0,
mobileHeaders: [ mobileHeaders: [
{ text: "کالا", value: "commodity.name" }, { title: 'کالا', key: 'commodity.name' },
{ text: "تعداد", value: "count" }, { title: 'تعداد', key: 'count' },
{ text: "مبلغ کل", value: "sumTotal" }, { title: 'مبلغ کل', key: 'sumTotal' },
] ],
} }),
computed: {
formattedAmount() {
return this.$filters.formatNumber(this.item.doc.amount);
},
statusText() {
return parseInt(this.item.doc.amount) <= parseInt(this.totalRec) ? 'تسویه شده' : 'تسویه نشده';
},
statusColor() {
return parseInt(this.item.doc.amount) <= parseInt(this.totalRec) ? 'success' : 'error';
},
profitText() {
return this.$filters.formatNumber(Math.abs(this.item.doc.profit));
},
profitLabel() {
return parseInt(this.item.doc.profit) >= 0 ? 'سود فاکتور' : 'زیان فاکتور';
},
profitColor() {
return parseInt(this.item.doc.profit) >= 0 ? 'success' : 'error';
},
formattedTotalTax() {
return this.$filters.formatNumber(this.totalTax);
},
formattedDiscountAll() {
return this.$filters.formatNumber(this.discountAll);
},
formattedTransferCost() {
return this.$filters.formatNumber(this.transferCost);
}, },
setup() {
}, },
methods: { methods: {
copyToCliboard() {
navigator.clipboard.writeText(this.shortlink_url);
this.copy_label = 'کپی شد !';
},
loadData() { loadData() {
this.loading = true; this.loading = true;
this.commoditys = []; this.commoditys = [];
axios.post('/api/accounting/doc/get', { 'code': this.$route.params.id }).then((response) => { axios.post('/api/accounting/doc/get', { code: this.$route.params.id }).then((response) => {
this.item = response.data; this.item = response.data;
if (this.item.doc.shortLink != null) { this.shortlink_url = this.item.doc.shortLink
this.shortlink_url = getApiUrl() + '/sl/sell/' + localStorage.getItem("activeBid") + '/' + this.item.doc.shortLink; ? `${getApiUrl()}/sl/sell/${localStorage.getItem('activeBid')}/${this.item.doc.shortLink}`
} : `${getApiUrl()}/sl/sell/${localStorage.getItem('activeBid')}/${this.item.doc.id}`;
else { this.totalRec = response.data.relatedDocs.reduce((sum: number, rdoc: any) => sum + parseInt(rdoc.amount), 0);
this.shortlink_url = getApiUrl() + '/sl/sell/' + localStorage.getItem("activeBid") + '/' + this.item.doc.id;
}
response.data.relatedDocs.forEach((rdoc: any) => {
this.totalRec += parseInt(rdoc.amount)
});
}); });
axios.get('/api/sell/get/info/' + this.$route.params.id).then((response) => { axios.get(`/api/sell/get/info/${this.$route.params.id}`).then((response) => {
this.person = response.data.person; this.person = response.data.person;
this.discountAll = response.data.discountAll; this.discountAll = response.data.discountAll;
this.transferCost = response.data.transferCost; this.transferCost = response.data.transferCost;
this.item.doc.profit = response.data.profit; this.item.doc.profit = response.data.profit;
response.data.rows.forEach((item: any) => { this.commoditys = response.data.rows
if (item.commodity != null) { .filter((item: any) => item.commodity != null)
.map((item: any) => {
this.totalTax += parseInt(item.tax); this.totalTax += parseInt(item.tax);
this.totalDiscount += parseInt(item.discount); this.totalDiscount += parseInt(item.discount);
this.commoditys.push({ return {
commodity: item.commodity, commodity: item.commodity,
count: item.commodity_count, count: item.commodity_count,
price: parseInt((parseInt(item.bs) - parseInt(item.tax) + parseInt(item.discount)) / parseFloat(item.commodity_count)), price: parseInt((parseInt(item.bs) - parseInt(item.tax) + parseInt(item.discount)) / parseFloat(item.commodity_count)),
@ -138,499 +126,197 @@ export default defineComponent({
tax: item.tax, tax: item.tax,
sumWithoutTax: item.bs - item.tax, sumWithoutTax: item.bs - item.tax,
sumTotal: item.bs, sumTotal: item.bs,
table: 53 table: 53,
};
});
}); });
}
}); axios.post(`/api/business/get/info/${localStorage.getItem('activeBid')}`).then((response) => {
});
axios.post('/api/business/get/info/' + localStorage.getItem('activeBid')).then((response) => {
this.bid = response.data; this.bid = response.data;
this.loading = false; this.loading = false;
}); });
axios.get("/api/printers/options/info").then((response) => {
this.loading = false;
this.printOptions = response.data.sell;
})
}, },
sendSMS() {
this.loading = true;
const regex = new RegExp("^(\\+98|0)?9\\d{9}$");
if (!regex.test(this.person.mobile)) {
Swal.fire({
text: 'شماره موبایل وارد شده نا معتبر است.',
icon: 'error',
confirmButtonText: 'قبول'
});
this.loading = false;
}
else {
this.send_message_label = 'در حال ارسال...';
axios.post('/api/sms/send/sell-invoice/' + this.item.doc.id + '/' + this.person.mobile).then((response) => {
if (response.data.result == 2) {
Swal.fire({
text: 'اعتبار سرویس پیامک کافی نیست.',
icon: 'error',
confirmButtonText: 'قبول'
});
this.send_message_label = 'ارسال';
}
else if (response.data.result == 1) {
Swal.fire({
text: 'پیامک اطلاع رسانی ارسال شد.',
icon: 'success',
confirmButtonText: 'قبول'
});
this.send_message_label = 'ارسال شد!'
}
this.loading = false;
})
}
},
printInvoice(pdf = true, cloudePrinters = true) {
axios.post('/api/sell/print/invoice', {
'code': this.$route.params.id,
'pdf': pdf,
'printers': cloudePrinters,
'printOptions': this.printOptions
}).then((response) => {
window.open(this.$API_URL + '/front/print/' + response.data.id, '_blank', 'noreferrer');
});
}
}, },
mounted() { mounted() {
this.loadData(); this.loadData();
this.recModal = new bootstrap.Modal(document.getElementById('rec-modal')) },
this.recListModal = new bootstrap.Modal(document.getElementById('rec-list-modal')) });
}
})
</script> </script>
<template> <template>
<!-- Print Modal --> <v-toolbar color="toolbar" flat title="مشاهده فاکتور">
<div class="modal fade" id="printModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" <template v-slot:prepend>
aria-labelledby="printModalLabel" aria-hidden="true"> <v-tooltip text="بازگشت" location="bottom">
<div class="modal-dialog"> <template v-slot:activator="{ props }">
<div class="modal-content"> <v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
<div class="modal-header bg-primary-light text-white"> </template>
<h1 class="modal-title fs-5" id="printModalLabel">چاپ فاکتور</h1> </v-tooltip>
<div class="block-options"> </template>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <v-spacer></v-spacer>
</div> <v-btn icon v-if="item.doc.id !== 0">
</div> <archive-upload :docid="item.doc.id" doctype="sell" cat="sell" />
<div class="modal-body"> <v-tooltip activator="parent" location="bottom">آرشیو</v-tooltip>
<p class="mb-2">برای تغییر تنظیمات پیشفرض به بخش تنظیمات چاپ مراجعه نمایید</p> </v-btn>
<div class="form-floating mb-2"> <notes :stat="notes" :code="$route.params.id" type-note="sell" />
<select v-model="printOptions.paper" class="form-select"> <v-btn icon color="error" class="ml-2" v-if="parseInt(item.doc.amount) > parseInt(totalRec)" @click="recDialog = true">
<option value="A4-L">A4 افقی</option> <v-icon>mdi-money</v-icon>
<option value="A4">A4 عمودی</option> <v-tooltip activator="parent" location="bottom">ثبت دریافت</v-tooltip>
<option value="A5-L">A5 افقی</option> </v-btn>
<option value="A5">A5 عمودی</option> <v-btn icon color="info" class="ml-2" @click="recListDialog = true">
</select> <v-icon>mdi-arrow-down-circle</v-icon>
<label>سایز کاغذ و حالت چاپ</label> <v-tooltip activator="parent" location="bottom">دریافتها</v-tooltip>
</div> </v-btn>
<div class="form-check form-switch"> <share-options v-if="bid.shortlinks" :shortlink-url="shortlink_url" :mobile="person.mobile" :invoice-id="item.doc.id" />
<input class="form-check-input" v-model="printOptions.bidInfo" type="checkbox"> <print-options :invoice-id="$route.params.id" />
<label class="form-check-label">اطلاعات کسبوکار</label> </v-toolbar>
</div>
<div class="form-check form-switch">
<input class="form-check-input" v-model="printOptions.pays" type="checkbox">
<label class="form-check-label">نمایش پرداختهای فاکتور</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" v-model="printOptions.note" type="checkbox">
<label class="form-check-label">یادداشت پایین فاکتور</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" v-model="printOptions.taxInfo" type="checkbox">
<label class="form-check-label">مالیات به تفکیک اقلام</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" v-model="printOptions.discountInfo" type="checkbox">
<label class="form-check-label">تخفیف به تفکیک اقلام</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mx-2" @click="printInvoice()" type="button">
<i class="si si-printer me-1"></i>
<span class="">چاپ</span>
</button>
</div>
</div>
</div>
</div>
<!-- End Print Modal -->
<div class="block block-content-full"> <v-tabs v-model="activeTab" color="primary" grow class="mt-0">
<div id="fixed-header" class="block-header block-header-default bg-gray-light"> <v-tab value="person-info">اطلاعات شخص</v-tab>
<h3 class="block-title text-primary-dark"> <v-tab value="invoice-info">اقلام فاکتور</v-tab>
<button @click="$router.back()" type="button" <v-tab value="payments">دریافتها</v-tab>
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning"> </v-tabs>
<i class="fa fw-bold fa-arrow-right"></i> <v-container fluid>
</button> <v-window v-model="activeTab">
<i class="fas fa-file-invoice-dollar"></i> <!-- تب اطلاعات شخص -->
مشاهده فاکتور <v-window-item value="person-info">
</h3> <v-card-text>
<div class="block-options"> <v-row>
<archive-upload v-if="item.doc.id != 0" :docid="item.doc.id" doctype="sell" cat="sell"></archive-upload> <v-col cols="12" sm="6" md="4">
<button type="button" class="btn btn-sm btn-warning text-light me-2" data-bs-toggle="modal" <v-text-field v-model="person.nikename" label="نام" outlined readonly></v-text-field>
data-bs-target="#notesModal"> </v-col>
<span class="badge text-bg-dark me-2">{{ notes.count }}</span> <v-col cols="12" sm="6" md="4">
<i class="fa-regular fa-note-sticky me-1"></i> <v-text-field v-model="person.mobile" label="موبایل" outlined readonly></v-text-field>
<span class="d-none d-sm-inline-block">یادداشتها</span> </v-col>
</button> <v-col cols="12" sm="6" md="4">
<notes :stat="notes" :code="$route.params.id" typeNote="sell" /> <v-text-field v-model="person.tel" label="تلفن" outlined readonly></v-text-field>
<!-- Button trigger modal --> </v-col>
<button v-show="parseInt(item.doc.amount) > parseInt(totalRec)" type="button" class="btn btn-sm btn-danger" <v-col cols="12" sm="6" md="3">
@click="recModal.show()"> <v-text-field v-model="person.postalcode" label="کد پستی" outlined readonly></v-text-field>
<i class="fas fa-money-bill-1-wave"></i> </v-col>
<span class="d-none d-sm-inline-block">ثبت دریافت</span> <v-col cols="12" sm="12" md="9">
</button> <v-text-field v-model="person.addres" label="آدرس" outlined readonly></v-text-field>
<!-- Modal --> </v-col>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="rec-modal" tabindex="-1" </v-row>
aria-labelledby="exampleModalLabel1" aria-hidden="true"> </v-card-text>
<rec ref="submitPay" :windowsState="PayWindowsState" :person="person.id" :original-doc="item.doc.code" </v-window-item>
:total-amount="parseInt(item.doc.amount) - parseInt(totalRec)">
</rec> <!-- تب اطلاعات فاکتور -->
</div> <v-window-item value="invoice-info">
<button type="button" class="btn btn-sm btn-info ms-2" @click="recListModal.show()"> <v-card-text>
<i class="fas fa-arrow-alt-circle-down"></i> <v-row>
<span class="d-none d-sm-inline-block">دریافتها</span> <v-col cols="12" sm="6" md="4">
</button> <v-text-field v-model="item.doc.code" label="شماره" outlined readonly></v-text-field>
<!-- Modal --> </v-col>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="rec-list-modal" tabindex="-1" <v-col cols="12" sm="6" md="4">
aria-hidden="true"> <v-text-field v-model="item.doc.date" label="تاریخ" outlined readonly></v-text-field>
<div class="modal-dialog modal-lg"> </v-col>
<div class="modal-content"> <v-col cols="12" sm="12" md="4">
<div class="modal-header"> <v-text-field v-model="item.doc.des" label="شرح" outlined readonly></v-text-field>
<h1 class="modal-title fs-5" id="exampleModalLabel1"> </v-col>
<i class="fas fa-arrow-alt-circle-down ms-2"></i> </v-row>
دریافتها
</h1> <v-list-subheader>اقلام</v-list-subheader>
<div class="block-options"> <v-data-table :headers="mobileHeaders" :items="commoditys" :loading="loading" show-expand class="elevation-1">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <template v-slot:item.sumTotal="{ item }">
</div> {{ $filters.formatNumber(item.sumTotal) }}
</div>
<div class="modal-body">
<rec-list ref="recListRef" :windowsState="recListWindowsState" :items="item.relatedDocs"></rec-list>
</div>
<div class="modal-footer">
<button type="button" ref="btnCloseModalRec" class="btn btn-secondary btn-close-modal"
data-bs-dismiss="modal">بازگشت</button>
</div>
</div>
</div>
</div>
<!-- Button trigger modal -->
<button v-show="bid.shortlinks" type="button" class="btn btn-sm btn-success ms-2" data-bs-toggle="modal"
data-bs-target="#exampleModal">
<i class="fas fa-share-nodes"></i>
<span class="d-none d-sm-inline-block">اشتراک گذاری</span>
</button>
<!-- Modal -->
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="exampleModal" tabindex="-1"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">
<i class="fas fa-share-nodes"></i>
اشتراک گذاری
</h1>
<div class="block-options">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<div class="input-group mb-2">
<div class="input-group-text">
<i class="fa fa-paperclip me-2"></i>
پیوند فاکتور
</div>
<input :readonly="true" type="text" class="form-control" v-model="shortlink_url">
<button class="btn btn-outline-success" type="button" @click="copyToCliboard()">{{ copy_label
}}</button>
</div>
<div class="input-group">
<div class="input-group-text">
<i class="fa fa-message me-2"></i>
ارسال پیامک
</div>
<input type="text" class="form-control" v-model="person.mobile">
<button :disabled="loading" class="btn btn-outline-success" type="button" @click="sendSMS()">{{
send_message_label }}</button>
</div>
</div>
<div class="mt-3">
<i class="fas fa-share-nodes me-3"></i>
<label>اشتراک گذاری در شبکههای اجتماعی</label>
</div>
<div class="mt-2">
<a target="_blank" :href="'tg://msg?text=' + shortlink_url">
<img src="/img/icons/telegram.png" class="m-3" style="max-width: 30px;" />
</a>
<a target="_blank" :href="'et://msg_url?url=' + shortlink_url">
<img src="/img/icons/eitaa.jpeg" class="m-3" style="max-width: 30px;" />
</a>
<a target="_blank" :href="'https://ble.ir/share/url?url=' + shortlink_url">
<img src="/img/icons/bale-logo.png" class="m-3" style="max-width: 30px;" />
</a>
<a target="_blank" :href="'https://ble.ir/share/url?url=' + shortlink_url">
<img src="/img/icons/robika.png" class="m-3" style="max-width: 30px;" />
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">بازگشت</button>
</div>
</div>
</div>
</div>
<!-- print trigger modal -->
<button type="button" class="btn btn-sm btn-primary mx-2" data-bs-toggle="modal" data-bs-target="#printModal">
<i class="si si-printer me-1"></i>
<span class="d-none d-sm-inline-block">چاپ فاکتور</span>
</button>
</div>
</div>
<div class="block-content py-2 mt-2">
<div class="row">
<div class="col-12">
<div class="row">
<div class="col-sm-6 col-md-4">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">شماره</span>
<input type="text" :readonly="true" v-model="item.doc.code" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">تاریخ</span>
<input type="text" :readonly="true" v-model="item.doc.date" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
<div class="col-sm-12 col-md-4">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">شرح</span>
<input type="text" :readonly="true" v-model="item.doc.des" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
</div>
<b class="ps-2">خریدار</b>
<div class="row">
<div class="col-sm-6 col-md-4">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">نام</span>
<input type="text" :readonly="true" v-model="person.nikename" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">موبایل</span>
<input type="text" :readonly="true" v-model="person.mobile" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">تلفن</span>
<input type="text" :readonly="true" v-model="person.tel" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">کد پستی</span>
<input type="text" :readonly="true" v-model="person.postalcode" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
<div class="col-sm-12 col-md-9">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">آدرس</span>
<input type="text" :readonly="true" v-model="person.address" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
</div>
<b class="ps-2">اقلام</b>
<EasyDataTable table-class-name="customize-table" :headers="mobileHeaders" :items="commoditys" show-index
alternating theme-color="#1d90ff" header-text-direction="center" body-text-direction="center"
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از"
:loading="loading">
<template #item-sumTotal="{ sumTotal }">
{{ $filters.formatNumber(sumTotal) }}
</template> </template>
<template #item-count="{ count, commodity }"> <template v-slot:item.count="{ item }">
{{ count }} {{ commodity.unit }} {{ item.count }} {{ item.commodity.unit }}
</template> </template>
<template #expand="{ tax, discount, des, price }"> <template v-slot:expanded-row="{ item }">
<div class="p-1 m-0"> <v-list dense>
<ul class="list-group"> <v-list-item>
<li class="list-group-item d-flex justify-content-between align-items-center"> <v-list-item-title>قیمت واحد</v-list-item-title>
قیمت واحد <v-list-item-subtitle>{{ $filters.formatNumber(item.price) }}</v-list-item-subtitle>
<span class="badge text-bg-primary rounded-pill"> </v-list-item>
{{ $filters.formatNumber(price) }} <v-list-item>
</span> <v-list-item-title>تخفیف</v-list-item-title>
</li> <v-list-item-subtitle>{{ $filters.formatNumber(item.discount) }}</v-list-item-subtitle>
<li class="list-group-item d-flex justify-content-between align-items-center"> </v-list-item>
تخفیف <v-list-item>
<span class="badge text-bg-primary rounded-pill"> <v-list-item-title>مالیات</v-list-item-title>
{{ $filters.formatNumber(discount) }} <v-list-item-subtitle>{{ $filters.formatNumber(item.tax) }}</v-list-item-subtitle>
</span> </v-list-item>
</li> <v-list-item>
<li class="list-group-item d-flex justify-content-between align-items-center"> <v-list-item-title>شرح</v-list-item-title>
مالیات <v-list-item-subtitle>{{ item.des }}</v-list-item-subtitle>
<span class="badge text-bg-primary rounded-pill"> </v-list-item>
{{ $filters.formatNumber(tax) }} </v-list>
</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
شرح:
{{ des }}
</li>
</ul>
</div>
</template> </template>
</EasyDataTable> </v-data-table>
<div class="row pt-2">
<div class="col-sm-6 col-md-4"> <v-row class="mt-2">
<div class="input-group input-group-sm mb-3"> <v-col cols="12" sm="6" md="4">
<span class="input-group-text" id="inputGroup-sizing-sm">جمع کل</span> <v-text-field v-model="formattedAmount" label="جمع کل" outlined readonly></v-text-field>
<input type="text" :readonly="true" :value="$filters.formatNumber(item.doc.amount)" </v-col>
class="form-control" aria-describedby="inputGroup-sizing-sm"> <v-col cols="12" sm="6" md="4">
</div> <v-text-field v-model="statusText" :color="statusColor" label="وضعیت" outlined readonly></v-text-field>
</div> </v-col>
<div class="col-sm-6 col-md-4"> <v-col cols="12" sm="6" md="4">
<div class="input-group input-group-sm mb-3"> <v-text-field v-model="profitText" :label="profitLabel" :color="profitColor" outlined readonly></v-text-field>
<span class="input-group-text" id="inputGroup-sizing-sm">وضعیت</span> </v-col>
<input v-if="parseInt(item.doc.amount) <= parseInt(totalRec)" type="text" :readonly="true" </v-row>
value="تسویه شده" class="form-control text-success" aria-describedby="inputGroup-sizing-sm">
<input v-else type="text" :readonly="true" value="تسویه نشده" class="form-control text-danger" <v-row>
aria-describedby="inputGroup-sizing-sm"> <v-col cols="12" sm="6" md="4">
</div> <v-text-field v-model="formattedTotalTax" label="مالیات" outlined readonly></v-text-field>
</div> </v-col>
<div class="col-sm-6 col-md-4"> <v-col cols="12" sm="6" md="4">
<div v-if="parseInt(item.doc.profit) >= 0" class="input-group input-group-sm mb-3"> <v-text-field v-model="formattedDiscountAll" label="تخفیف" outlined readonly></v-text-field>
<span class="input-group-text" id="inputGroup-sizing-sm">سود فاکتور</span> </v-col>
<input type="text" :readonly="true" :value="$filters.formatNumber(Math.abs(item.doc.profit))" <v-col cols="12" sm="6" md="4">
class="form-control text-success"> <v-text-field v-model="formattedTransferCost" label="هزینه حمل و نقل" outlined readonly></v-text-field>
</div> </v-col>
<div v-else class="input-group input-group-sm mb-3"> </v-row>
<span class="input-group-text" id="inputGroup-sizing-sm">زیان فاکتور</span> </v-card-text>
<input type="text" :readonly="true" :value="$filters.formatNumber(Math.abs(item.doc.profit))" </v-window-item>
class="form-control text-danger">
</div> <!-- تب دریافتها -->
</div> <v-window-item value="payments">
</div> <v-card-text>
<div class="row"> <v-data-table v-if="item.relatedDocs.length" :headers="[
<div class="col-sm-6 col-md-4"> { title: 'مشاهده', key: 'view' },
<div class="input-group input-group-sm mb-3"> { title: 'شماره', key: 'code' },
<span class="input-group-text" id="inputGroup-sizing-sm">مالیات</span> { title: 'تاریخ', key: 'date' },
<input type="text" :readonly="true" :value="$filters.formatNumber(totalTax)" class="form-control" { title: 'مبلغ', key: 'amount' },
aria-describedby="inputGroup-sizing-sm"> ]" :items="item.relatedDocs">
</div> <template v-slot:item.view="{ item }">
</div> <router-link :to="'/acc/accounting/view/' + item.code">
<div class="col-sm-6 col-md-4"> <v-icon color="success">mdi-eye</v-icon>
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">تخفیف</span>
<input type="text" :readonly="true" :value="$filters.formatNumber(discountAll)" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">هزینه حمل و نقل</span>
<input type="text" :readonly="true" :value="$filters.formatNumber(transferCost)" class="form-control"
aria-describedby="inputGroup-sizing-sm">
</div>
</div>
</div>
<div class="block block-rounded">
<div class="block-header block-header-default">
<h3 class="block-title">دریافتها</h3>
<div class="block-options">
</div>
</div>
<div class="block-content p-0">
<table v-if="item.relatedDocs.length != 0"
class="table border-0 table-borderless table-striped table-vcenter fs-sm">
<thead class="bg-primary text-light">
<tr class="text-center">
<th>مشاهده</th>
<th>شماره</th>
<th>تاریخ</th>
<th>مبلغ</th>
</tr>
</thead>
<tbody>
<tr v-for="rd in item.relatedDocs" class="text-center">
<td>
<router-link :to="'/acc/accounting/view/' + rd.code">
<span class="text-success fa fa-eye"></span>
</router-link> </router-link>
</td> </template>
<td class="fw-semibold" style="width: 100px;"> <template v-slot:item.amount="{ item }">
{{ rd.code }} {{ $filters.formatNumber(item.amount) }}
</td> </template>
<td class="fw-semibold"> </v-data-table>
{{ rd.date }} <v-card-text v-else class="text-center text-danger">
</td> تاکنون سند دریافتی ثبت نشده است
<td class="fw-semibold"> </v-card-text>
{{ $filters.formatNumber(rd.amount) }} </v-card-text>
</td> </v-window-item>
</tr> </v-window>
<tr v-if="item.relatedDocs.length == 0" class="text-center">
<td colspan="4">
سند پرداختی تاکنون ثبت نشده است.
</td>
</tr>
</tbody>
</table>
<div class="text-center py-2 text-danger" v-else>
<span>تاکنون سند دریافتی ثبت نشده است</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<v-dialog v-model="recDialog" max-width="600">
<rec :windows-state="PayWindowsState" :person="person.id" :original-doc="item.doc.code" :total-amount="parseInt(item.doc.amount) - parseInt(totalRec)" />
</v-dialog>
<v-dialog v-model="recListDialog" max-width="800">
<v-card>
<v-card-title>
<v-icon left>mdi-arrow-down-circle</v-icon>
دریافتها
</v-card-title>
<v-card-text>
<rec-list :windows-state="recListWindowsState" :items="item.relatedDocs" />
</v-card-text>
<v-card-actions>
<v-btn color="secondary" @click="recListDialog = false">بازگشت</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template> </template>
<style scoped> <style scoped>
table { /* استایل‌ها */
font-size: small;
border: 1px solid gray;
}
.table-header {
background-color: lightgray;
}
.c-print {
background-color: white;
}
@media print {
@page {
margin-top: 0;
margin-bottom: 0;
}
body {
padding-top: 72px;
padding-bottom: 72px;
}
}
</style> </style>