chore: install prettier
This commit is contained in:
@@ -1,48 +1,51 @@
|
||||
<template>
|
||||
<div v-if="record">
|
||||
<div class="columns is-multiline mx-0 mt-1" id="printable">
|
||||
<div class="column is-5">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('code') }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.code}` }}
|
||||
<div v-if="record">
|
||||
<div
|
||||
class="columns is-multiline mx-0 mt-1"
|
||||
id="printable"
|
||||
>
|
||||
<div class="column is-5">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("code") }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.code}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-7">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('account-type') }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.type__code} / ${record.type__name}` }}
|
||||
<div class="column is-7">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("account-type") }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.type__code} / ${record.type__name}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-5">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('currency') }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.currency__code} / ${record.currency__name}` }}
|
||||
<div class="column is-5">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("currency") }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.currency__code} / ${record.currency__name}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-7">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('balance') }}:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.balance) }}
|
||||
</div>
|
||||
<!--<p class="help is-findata">{{$vnmoney($formatNumber(record.balance))}}</p>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-5">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('open-date') }}:</label>
|
||||
<div class="control">
|
||||
{{ `${$dayjs(record.create_time).format('DD/MM/YYYY')}` }}
|
||||
<div class="column is-7">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("balance") }}:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.balance) }}
|
||||
</div>
|
||||
<!--<p class="help is-findata">{{$vnmoney($formatNumber(record.balance))}}</p>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="column is-7">
|
||||
<div class="column is-5">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("open-date") }}:</label>
|
||||
<div class="control">
|
||||
{{ `${$dayjs(record.create_time).format("DD/MM/YYYY")}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="column is-7">
|
||||
<div class="field">
|
||||
<label class="label">Chi nhánh:</label>
|
||||
<div class="control">
|
||||
@@ -50,30 +53,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="border-bottom"></div>
|
||||
<div
|
||||
class="mt-5"
|
||||
id="ignore"
|
||||
>
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="$exportpdf('printable', record.code)"
|
||||
>
|
||||
{{ $lang("print") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-bottom"></div>
|
||||
<div class="mt-5" id="ignore">
|
||||
<button class="button is-primary has-text-white" @click="$exportpdf('printable', record.code)">{{$lang('print')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['row'],
|
||||
data() {
|
||||
return {
|
||||
errors: {},
|
||||
record: undefined
|
||||
}
|
||||
export default {
|
||||
props: ["row"],
|
||||
data() {
|
||||
return {
|
||||
errors: {},
|
||||
record: undefined,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.record = await this.$getdata("internalaccount", { id: this.row.account || this.row.id }, undefined, true);
|
||||
},
|
||||
methods: {
|
||||
selected(attr, obj) {
|
||||
this.record[attr] = obj;
|
||||
if (attr === "_type") this.category = obj.category__code;
|
||||
},
|
||||
async created() {
|
||||
this.record = await this.$getdata('internalaccount', {id: this.row.account || this.row.id}, undefined, true)
|
||||
},
|
||||
methods: {
|
||||
selected(attr, obj) {
|
||||
this.record[attr] = obj
|
||||
if(attr==='_type') this.category = obj.category__code
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,50 +1,60 @@
|
||||
<!-- components/dialog/ConfirmDeleteEntry.vue -->
|
||||
<template>
|
||||
<div class="has-text-centered">
|
||||
|
||||
<div class=" mb-3 p-3">
|
||||
<p class="is-size-5 has-text-weight-semibold mb-4">
|
||||
Bạn có chắc chắn muốn xóa bút toán này?
|
||||
</p>
|
||||
|
||||
<p class="mt-3 has-text-danger has-text-weight-semibold">
|
||||
Hành động này <strong>không thể hoàn tác</strong>.<br>
|
||||
<div class="mb-3 p-3">
|
||||
<p class="is-size-5 has-text-weight-semibold mb-4">Bạn có chắc chắn muốn xóa bút toán này?</p>
|
||||
<p class="mt-3 has-text-danger has-text-weight-semibold">
|
||||
Hành động này <strong>không thể hoàn tác</strong>.<br />
|
||||
Dữ liệu liên quan (nếu có) sẽ bị xóa vĩnh viễn.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field is-grouped is-justify-content-center">
|
||||
<!-- Captcha addon group - shown only when captcha is not confirmed -->
|
||||
<p class="control" v-if="!isConfirmed">
|
||||
<div
|
||||
class="control"
|
||||
v-if="!isConfirmed"
|
||||
>
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Nhập mã xác nhận"
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Nhập mã xác nhận"
|
||||
v-model="userInputCaptcha"
|
||||
@keyup.enter="isConfirmed && confirmDelete()"
|
||||
>
|
||||
/>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a class="button is-static has-text-weight-bold has-background-grey-lighter"
|
||||
style="font-family: 'Courier New', monospace; letter-spacing: 2px;">
|
||||
<a
|
||||
class="button is-static has-text-weight-bold has-background-grey-lighter"
|
||||
style="font-family: "Courier New", monospace; letter-spacing: 2px"
|
||||
>
|
||||
{{ captchaCode }}
|
||||
</a>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button" @click="generateCaptcha" title="Tạo mã mới">
|
||||
<button
|
||||
class="button"
|
||||
@click="generateCaptcha"
|
||||
title="Tạo mã mới"
|
||||
>
|
||||
<span class="icon">
|
||||
<SvgIcon name="refresh.svg" type="primary" :size="23" />
|
||||
<SvgIcon
|
||||
name="refresh.svg"
|
||||
type="primary"
|
||||
:size="23"
|
||||
></SvgIcon>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<!-- Confirm button - shown only when captcha IS confirmed -->
|
||||
<p class="control" v-if="isConfirmed">
|
||||
<p
|
||||
class="control"
|
||||
v-if="isConfirmed"
|
||||
>
|
||||
<button
|
||||
class="button is-danger"
|
||||
:class="{ 'is-loading': isDeleting }"
|
||||
@@ -69,75 +79,70 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useNuxtApp } from '#app'
|
||||
import { ref, computed } from "vue";
|
||||
import { useNuxtApp } from "#app";
|
||||
|
||||
const props = defineProps({
|
||||
entryId: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
}
|
||||
})
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'deleted'])
|
||||
const emit = defineEmits(["close", "deleted"]);
|
||||
|
||||
const { $snackbar ,$insertapi} = useNuxtApp()
|
||||
const isDeleting = ref(false)
|
||||
const captchaCode = ref('')
|
||||
const userInputCaptcha = ref('')
|
||||
const { $snackbar, $insertapi } = useNuxtApp();
|
||||
const isDeleting = ref(false);
|
||||
const captchaCode = ref("");
|
||||
const userInputCaptcha = ref("");
|
||||
|
||||
const isConfirmed = computed(() => {
|
||||
return userInputCaptcha.value.toLowerCase() === captchaCode.value.toLowerCase() && userInputCaptcha.value !== ''
|
||||
})
|
||||
return userInputCaptcha.value.toLowerCase() === captchaCode.value.toLowerCase() && userInputCaptcha.value !== "";
|
||||
});
|
||||
|
||||
const generateCaptcha = () => {
|
||||
captchaCode.value = Math.random().toString(36).substring(2, 7).toUpperCase()
|
||||
userInputCaptcha.value = ''
|
||||
}
|
||||
captchaCode.value = Math.random().toString(36).substring(2, 7).toUpperCase();
|
||||
userInputCaptcha.value = "";
|
||||
};
|
||||
|
||||
// Initial generation
|
||||
generateCaptcha()
|
||||
generateCaptcha();
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (isDeleting.value || !isConfirmed.value) return
|
||||
isDeleting.value = true
|
||||
if (isDeleting.value || !isConfirmed.value) return;
|
||||
isDeleting.value = true;
|
||||
|
||||
try {
|
||||
// Gọi API xóa theo đúng endpoint delete-entry/{id}
|
||||
const result = await $insertapi('deleteentry', {id: props.entryId})
|
||||
const result = await $insertapi("deleteentry", { id: props.entryId });
|
||||
|
||||
if (result === 'error' || !result) {
|
||||
throw new Error('API xóa trả về lỗi')
|
||||
if (result === "error" || !result) {
|
||||
throw new Error("API xóa trả về lỗi");
|
||||
}
|
||||
|
||||
$snackbar(
|
||||
`Đã xóa bút toán ID ${props.entryId} thành công`,
|
||||
'Thành công',
|
||||
'Success'
|
||||
)
|
||||
|
||||
emit('deleted', props.entryId)
|
||||
emit('close')
|
||||
$snackbar(`Đã xóa bút toán ID ${props.entryId} thành công`, "Thành công", "Success");
|
||||
|
||||
emit("deleted", props.entryId);
|
||||
emit("close");
|
||||
} catch (err) {
|
||||
console.error('Xóa bút toán thất bại:', err)
|
||||
console.error("Xóa bút toán thất bại:", err);
|
||||
|
||||
let errorMsg = "Không thể xóa bút toán. Vui lòng thử lại.";
|
||||
|
||||
let errorMsg = 'Không thể xóa bút toán. Vui lòng thử lại.'
|
||||
|
||||
// Nếu backend trả về thông báo cụ thể
|
||||
if (err?.response?.data?.detail) {
|
||||
errorMsg = err.response.data.detail
|
||||
errorMsg = err.response.data.detail;
|
||||
} else if (err?.response?.data?.non_field_errors) {
|
||||
errorMsg = err.response.data.non_field_errors.join(' ')
|
||||
errorMsg = err.response.data.non_field_errors.join(" ");
|
||||
}
|
||||
|
||||
$snackbar(errorMsg, 'Lỗi', 'Danger')
|
||||
$snackbar(errorMsg, "Lỗi", "Danger");
|
||||
} finally {
|
||||
isDeleting.value = false
|
||||
isDeleting.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
emit("close");
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
@date="selected('fdate', $event)"
|
||||
></Datepicker>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.issued_date">{{ errors.issued_date }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.issued_date"
|
||||
>
|
||||
{{ errors.issued_date }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
@@ -21,11 +26,19 @@
|
||||
@date="selected('tdate', $event)"
|
||||
></Datepicker>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.issued_date">{{ errors.issued_date }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.issued_date"
|
||||
>
|
||||
{{ errors.issued_date }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataView v-bind="vbind" v-if="vbind" />
|
||||
<DataView
|
||||
v-bind="vbind"
|
||||
v-if="vbind"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
const { $dayjs, $id } = useNuxtApp();
|
||||
@@ -37,9 +50,9 @@ const vbind = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
})
|
||||
});
|
||||
|
||||
function selected(attr, value) {
|
||||
function selected(attr, value) {
|
||||
if (attr === "fdate") fdate.value = value;
|
||||
else tdate.value = value;
|
||||
loadData();
|
||||
@@ -56,18 +69,27 @@ function loadData() {
|
||||
values:
|
||||
"customer__code,customer__fullname,customer__type__name,customer__legal_type__name,customer__legal_code",
|
||||
distinct_values: {
|
||||
sum_sale_price: { type: "Sum", field: "product__prdbk__transaction__sale_price" },
|
||||
sum_received: { type: "Sum", field: "product__prdbk__transaction__amount_received" },
|
||||
sum_remain: { type: "Sum", field: "product__prdbk__transaction__amount_remain" },
|
||||
sum_sale_price: {
|
||||
type: "Sum",
|
||||
field: "product__prdbk__transaction__sale_price",
|
||||
},
|
||||
sum_received: {
|
||||
type: "Sum",
|
||||
field: "product__prdbk__transaction__amount_received",
|
||||
},
|
||||
sum_remain: {
|
||||
type: "Sum",
|
||||
field: "product__prdbk__transaction__amount_remain",
|
||||
},
|
||||
},
|
||||
summary: "annotate",
|
||||
filter: {
|
||||
date__gte: fdate.value,
|
||||
date__lte: tdate.value
|
||||
filter: {
|
||||
date__gte: fdate.value,
|
||||
date__lte: tdate.value,
|
||||
},
|
||||
sort: "-sum_remain",
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
@date="selected('fdate', $event)"
|
||||
></Datepicker>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.issued_date">{{ errors.issued_date }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.issued_date"
|
||||
>
|
||||
{{ errors.issued_date }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
@@ -21,11 +26,19 @@
|
||||
@date="selected('tdate', $event)"
|
||||
></Datepicker>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.issued_date">{{ errors.issued_date }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.issued_date"
|
||||
>
|
||||
{{ errors.issued_date }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataView v-bind="vbind" v-if="vbind" />
|
||||
<DataView
|
||||
v-bind="vbind"
|
||||
v-if="vbind"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
@@ -60,13 +73,21 @@ export default {
|
||||
values:
|
||||
"product,product__prdbk__transaction__amount_received,product__trade_code,product__prdbk__transaction__sale_price,product__zone_type__name,customer,customer__code,customer__fullname",
|
||||
distinct_values: {
|
||||
sumCR: { type: "Sum", filter: { type__code: "CR" }, field: "amount" },
|
||||
sumDR: { type: "Sum", filter: { type__code: "DR" }, field: "amount" },
|
||||
sumCR: {
|
||||
type: "Sum",
|
||||
filter: { type__code: "CR" },
|
||||
field: "amount",
|
||||
},
|
||||
sumDR: {
|
||||
type: "Sum",
|
||||
filter: { type__code: "DR" },
|
||||
field: "amount",
|
||||
},
|
||||
},
|
||||
summary: "annotate",
|
||||
filter: { date__gte: this.fdate, date__lte: this.tdate },
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
pagename: 'debt_report',
|
||||
api: 'transaction',
|
||||
timeopt: { time: 36000, disable: ['add'] },
|
||||
filter: { phase: 3 }
|
||||
filter: { phase: 3 },
|
||||
}"
|
||||
@option="handleTimeOption"
|
||||
@excel="exportExcel"
|
||||
@@ -15,17 +15,26 @@
|
||||
/>
|
||||
|
||||
<!-- Loading -->
|
||||
<div v-if="loading" class="has-text-centered py-6">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="has-text-centered py-6"
|
||||
>
|
||||
<p class="has-text-grey mb-3">Đang tải dữ liệu...</p>
|
||||
<progress class="progress is-small is-primary" max="100"></progress>
|
||||
<progress
|
||||
class="progress is-small is-primary"
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
|
||||
<div class="" v-else>
|
||||
<div
|
||||
class=""
|
||||
v-else
|
||||
>
|
||||
<!-- Table -->
|
||||
<div
|
||||
v-if="filteredRows.length > 0"
|
||||
class="table-container"
|
||||
style="overflow-x: auto; max-width: 100%;"
|
||||
style="overflow-x: auto; max-width: 100%"
|
||||
>
|
||||
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth debt-table">
|
||||
<thead>
|
||||
@@ -37,22 +46,40 @@
|
||||
>
|
||||
STT
|
||||
</th>
|
||||
<th rowspan="2" class="fixed-col has-background-primary has-text-white">
|
||||
<th
|
||||
rowspan="2"
|
||||
class="fixed-col has-background-primary has-text-white"
|
||||
>
|
||||
Mã KH
|
||||
</th>
|
||||
<th rowspan="2" class="fixed-col has-background-primary has-text-white">
|
||||
<th
|
||||
rowspan="2"
|
||||
class="fixed-col has-background-primary has-text-white"
|
||||
>
|
||||
Mã Căn
|
||||
</th>
|
||||
<th rowspan="2" class="fixed-col has-background-primary has-text-white">
|
||||
<th
|
||||
rowspan="2"
|
||||
class="fixed-col has-background-primary has-text-white"
|
||||
>
|
||||
Ngày ký HĐ
|
||||
</th>
|
||||
<th rowspan="2" class="fixed-col has-background-primary has-text-white">
|
||||
<th
|
||||
rowspan="2"
|
||||
class="fixed-col has-background-primary has-text-white"
|
||||
>
|
||||
Giá trị HĐMB
|
||||
</th>
|
||||
<th rowspan="2" class="fixed-col has-background-primary has-text-white">
|
||||
<th
|
||||
rowspan="2"
|
||||
class="fixed-col has-background-primary has-text-white"
|
||||
>
|
||||
Tiền nộp theo HĐV/TTTHNV
|
||||
</th>
|
||||
<th rowspan="2" class="fixed-col has-background-primary has-text-white">
|
||||
<th
|
||||
rowspan="2"
|
||||
class="fixed-col has-background-primary has-text-white"
|
||||
>
|
||||
Tỷ lệ
|
||||
</th>
|
||||
|
||||
@@ -66,13 +93,19 @@
|
||||
{{ sch.label }}
|
||||
</th>
|
||||
|
||||
<th rowspan="2" class="has-background-primary has-text-white">
|
||||
<th
|
||||
rowspan="2"
|
||||
class="has-background-primary has-text-white"
|
||||
>
|
||||
Số tiền quá hạn
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<template v-for="(sch, si) in scheduleHeaders" :key="si">
|
||||
<template
|
||||
v-for="(sch, si) in scheduleHeaders"
|
||||
:key="si"
|
||||
>
|
||||
<th class="has-background-primary has-text-white sub-header">Ngày</th>
|
||||
<th class="has-background-primary has-text-white sub-header">Số tiền</th>
|
||||
<th class="has-background-primary has-text-white sub-header">Lũy kế sang đợt</th>
|
||||
@@ -84,13 +117,20 @@
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="(row, ri) in filteredRows" :key="ri">
|
||||
<tr
|
||||
v-for="(row, ri) in filteredRows"
|
||||
:key="ri"
|
||||
>
|
||||
<!-- Fixed columns -->
|
||||
<td class="fixed-col has-text-centered">{{ ri + 1 }}</td>
|
||||
<td class="fixed-col">{{ row.customer_code }}</td>
|
||||
<td class="fixed-col has-text-weight-semibold has-text-primary">{{ row.trade_code }}</td>
|
||||
<td class="fixed-col has-text-weight-semibold has-text-primary">
|
||||
{{ row.trade_code }}
|
||||
</td>
|
||||
<td class="fixed-col">{{ row.contract_date }}</td>
|
||||
<td class="fixed-col has-text-right">{{ fmt(row.sale_price) }}</td>
|
||||
<td class="fixed-col has-text-right">
|
||||
{{ fmt(row.sale_price) }}
|
||||
</td>
|
||||
<td class="fixed-col has-text-right has-text-weight-semibold has-background-warning-light">
|
||||
{{ fmt(row.ttthnv_paid) }}
|
||||
</td>
|
||||
@@ -99,11 +139,18 @@
|
||||
</td>
|
||||
|
||||
<!-- Scrollable columns -->
|
||||
<template v-for="(sch, si) in scheduleHeaders" :key="si">
|
||||
<template
|
||||
v-for="(sch, si) in scheduleHeaders"
|
||||
:key="si"
|
||||
>
|
||||
<template v-if="row.schedules[si]">
|
||||
<td>{{ row.schedules[si].to_date }}</td>
|
||||
<td class="has-text-right">{{ fmt(row.schedules[si].amount) }}</td>
|
||||
<td class="has-text-right has-text-info">{{ fmt(row.schedules[si].luy_ke_sang_dot) }}</td>
|
||||
<td class="has-text-right">
|
||||
{{ fmt(row.schedules[si].amount) }}
|
||||
</td>
|
||||
<td class="has-text-right has-text-info">
|
||||
{{ fmt(row.schedules[si].luy_ke_sang_dot) }}
|
||||
</td>
|
||||
<td class="has-text-right has-text-weight-semibold has-text-success">
|
||||
{{ fmt(row.schedules[si].thuc_thanh_toan) }}
|
||||
</td>
|
||||
@@ -121,7 +168,13 @@
|
||||
</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<td colspan="6" class="has-text-centered has-text-grey-light" style="font-style: italic">—</td>
|
||||
<td
|
||||
colspan="6"
|
||||
class="has-text-centered has-text-grey-light"
|
||||
style="font-style: italic"
|
||||
>
|
||||
—
|
||||
</td>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -129,25 +182,35 @@
|
||||
class="has-text-right has-text-weight-bold"
|
||||
:class="Number(row.overdue) > 0 ? 'has-background-danger-light' : ''"
|
||||
>
|
||||
{{ Number(row.overdue) > 0 ? fmt(row.overdue) : '—' }}
|
||||
{{ Number(row.overdue) > 0 ? fmt(row.overdue) : "—" }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
<tr class="has-background-light">
|
||||
<td :colspan="7" class="fixed-col has-text-right has-text-weight-bold">TỔNG CỘNG:</td>
|
||||
<td
|
||||
:colspan="7"
|
||||
class="fixed-col has-text-right has-text-weight-bold"
|
||||
>
|
||||
TỔNG CỘNG:
|
||||
</td>
|
||||
|
||||
<template v-for="(sch, si) in scheduleHeaders" :key="si">
|
||||
<template
|
||||
v-for="(sch, si) in scheduleHeaders"
|
||||
:key="si"
|
||||
>
|
||||
<td></td>
|
||||
<td class="has-text-right has-text-weight-semibold">{{ fmt(colSum(si, 'amount')) }}</td>
|
||||
<td class="has-text-right has-text-weight-semibold">
|
||||
{{ fmt(colSum(si, "amount")) }}
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="has-text-right has-text-weight-semibold has-text-success">
|
||||
{{ fmt(colSum(si, 'thuc_thanh_toan')) }}
|
||||
{{ fmt(colSum(si, "thuc_thanh_toan")) }}
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="has-text-right has-text-weight-semibold has-text-danger">
|
||||
{{ fmt(colSum(si, 'amount_remain')) }}
|
||||
{{ fmt(colSum(si, "amount_remain")) }}
|
||||
</td>
|
||||
</template>
|
||||
|
||||
@@ -160,7 +223,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Empty -->
|
||||
<div v-else class="has-text-centered py-6">
|
||||
<div
|
||||
v-else
|
||||
class="has-text-centered py-6"
|
||||
>
|
||||
<p class="has-text-grey">Không có dữ liệu</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,283 +234,279 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import TimeOption from '~/components/datatable/TimeOption'
|
||||
const { $findapi, $getapi, $dayjs, $copy } = useNuxtApp()
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import TimeOption from "~/components/datatable/TimeOption";
|
||||
const { $findapi, $getapi, $dayjs, $copy } = useNuxtApp();
|
||||
|
||||
const loading = ref(false)
|
||||
const rows = ref([])
|
||||
const filteredRows = ref([])
|
||||
const scheduleHeaders = ref([])
|
||||
const loading = ref(false);
|
||||
const rows = ref([]);
|
||||
const filteredRows = ref([]);
|
||||
const scheduleHeaders = ref([]);
|
||||
|
||||
const currentFilter = ref(null)
|
||||
const currentSearch = ref(null)
|
||||
const currentFilter = ref(null);
|
||||
const currentSearch = ref(null);
|
||||
|
||||
function handleTimeOption(option) {
|
||||
if (!option) {
|
||||
currentFilter.value = null
|
||||
currentSearch.value = null
|
||||
applyFilters()
|
||||
return
|
||||
currentFilter.value = null;
|
||||
currentSearch.value = null;
|
||||
applyFilters();
|
||||
return;
|
||||
}
|
||||
|
||||
if (option.filter) {
|
||||
currentFilter.value = option.filter
|
||||
currentSearch.value = null
|
||||
applyFilters()
|
||||
currentFilter.value = option.filter;
|
||||
currentSearch.value = null;
|
||||
applyFilters();
|
||||
} else if (option.filter_or) {
|
||||
currentFilter.value = null
|
||||
currentSearch.value = option.filter_or
|
||||
applyFilters()
|
||||
currentFilter.value = null;
|
||||
currentSearch.value = option.filter_or;
|
||||
applyFilters();
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
let filtered = [...rows.value]
|
||||
let filtered = [...rows.value];
|
||||
|
||||
if (currentFilter.value && currentFilter.value.create_time__date__gte) {
|
||||
const filterDate = new Date(currentFilter.value.create_time__date__gte)
|
||||
filtered = filtered.filter(row => {
|
||||
const contractDate = row.contract_date_raw ? new Date(row.contract_date_raw) : null
|
||||
return contractDate && contractDate >= filterDate
|
||||
})
|
||||
const filterDate = new Date(currentFilter.value.create_time__date__gte);
|
||||
filtered = filtered.filter((row) => {
|
||||
const contractDate = row.contract_date_raw ? new Date(row.contract_date_raw) : null;
|
||||
return contractDate && contractDate >= filterDate;
|
||||
});
|
||||
}
|
||||
|
||||
if (currentSearch.value) {
|
||||
const searchTerms = Object.values(currentSearch.value).map(v =>
|
||||
String(v).toLowerCase().replace('__icontains', '')
|
||||
)
|
||||
filtered = filtered.filter(row => {
|
||||
const searchTerms = Object.values(currentSearch.value).map((v) =>
|
||||
String(v).toLowerCase().replace("__icontains", ""),
|
||||
);
|
||||
filtered = filtered.filter((row) => {
|
||||
const searchableText = [
|
||||
row.customer_code,
|
||||
row.customer_name,
|
||||
row.trade_code,
|
||||
row.contract_date,
|
||||
String(row.sale_price)
|
||||
].join(' ').toLowerCase()
|
||||
return searchTerms.some(term => searchableText.includes(term))
|
||||
})
|
||||
String(row.sale_price),
|
||||
]
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
return searchTerms.some((term) => searchableText.includes(term));
|
||||
});
|
||||
}
|
||||
|
||||
filteredRows.value = filtered
|
||||
filteredRows.value = filtered;
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
rows.value = []
|
||||
filteredRows.value = []
|
||||
scheduleHeaders.value = []
|
||||
loading.value = true;
|
||||
rows.value = [];
|
||||
filteredRows.value = [];
|
||||
scheduleHeaders.value = [];
|
||||
|
||||
try {
|
||||
const txnConn = $copy($findapi('transaction'))
|
||||
const txnConn = $copy($findapi("transaction"));
|
||||
txnConn.params = {
|
||||
filter: { phase: 3 },
|
||||
values: 'id,code,date,customer,customer__code,customer__fullname,sale_price,amount_received,amount_remain,product,product__trade_code,phase',
|
||||
sort: 'id'
|
||||
}
|
||||
values:
|
||||
"id,code,date,customer,customer__code,customer__fullname,sale_price,amount_received,amount_remain,product,product__trade_code,phase",
|
||||
sort: "id",
|
||||
};
|
||||
|
||||
const detailConn = $copy($findapi('reservation'))
|
||||
const detailConn = $copy($findapi("reservation"));
|
||||
detailConn.params = {
|
||||
filter: { transaction__phase: 3, phase: 3 },
|
||||
values: 'id,transaction,phase,amount,amount_received,amount_remaining,status',
|
||||
sort: 'transaction'
|
||||
}
|
||||
values: "id,transaction,phase,amount,amount_received,amount_remaining,status",
|
||||
sort: "transaction",
|
||||
};
|
||||
|
||||
const schConn = $copy($findapi('payment_schedule'))
|
||||
const schConn = $copy($findapi("payment_schedule"));
|
||||
schConn.params = {
|
||||
filter: { txn_detail__phase: 3 },
|
||||
values:
|
||||
'id,code,cycle,to_date,from_date,amount,paid_amount,amount_remain,remain_amount,status,status__name,txn_detail,txn_detail__transaction,txn_detail__phase,txn_detail__amount_received',
|
||||
sort: 'txn_detail__transaction,cycle'
|
||||
}
|
||||
"id,code,cycle,to_date,from_date,amount,paid_amount,amount_remain,remain_amount,status,status__name,txn_detail,txn_detail__transaction,txn_detail__phase,txn_detail__amount_received",
|
||||
sort: "txn_detail__transaction,cycle",
|
||||
};
|
||||
|
||||
const ttthnvConn = $copy($findapi('reservation'))
|
||||
const ttthnvConn = $copy($findapi("reservation"));
|
||||
ttthnvConn.params = {
|
||||
filter: { transaction__phase: 3, phase: 4 },
|
||||
values: 'id,transaction,phase,amount,amount_received,amount_remaining,status',
|
||||
sort: 'transaction'
|
||||
}
|
||||
values: "id,transaction,phase,amount,amount_received,amount_remaining,status",
|
||||
sort: "transaction",
|
||||
};
|
||||
|
||||
const [txnRs, detailRs, schRs, ttthnvRs] = await $getapi([
|
||||
txnConn,
|
||||
detailConn,
|
||||
schConn,
|
||||
ttthnvConn
|
||||
])
|
||||
const [txnRs, detailRs, schRs, ttthnvRs] = await $getapi([txnConn, detailConn, schConn, ttthnvConn]);
|
||||
|
||||
const transactions = txnRs?.data?.rows || []
|
||||
const details = detailRs?.data?.rows || []
|
||||
const schedules = schRs?.data?.rows || []
|
||||
const ttthnvList = ttthnvRs?.data?.rows || []
|
||||
const transactions = txnRs?.data?.rows || [];
|
||||
const details = detailRs?.data?.rows || [];
|
||||
const schedules = schRs?.data?.rows || [];
|
||||
const ttthnvList = ttthnvRs?.data?.rows || [];
|
||||
|
||||
if (!transactions.length) {
|
||||
loading.value = false
|
||||
return
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// TTTHNV map
|
||||
const ttthnvMap = {}
|
||||
ttthnvList.forEach(t => {
|
||||
const tid = t.transaction
|
||||
ttthnvMap[tid] = (ttthnvMap[tid] || 0) + Number(t.amount_received || 0)
|
||||
})
|
||||
const ttthnvMap = {};
|
||||
ttthnvList.forEach((t) => {
|
||||
const tid = t.transaction;
|
||||
ttthnvMap[tid] = (ttthnvMap[tid] || 0) + Number(t.amount_received || 0);
|
||||
});
|
||||
|
||||
// Group schedules by transaction
|
||||
const schByTxn = {}
|
||||
schedules.forEach(s => {
|
||||
const tid = s.txn_detail__transaction
|
||||
if (!schByTxn[tid]) schByTxn[tid] = []
|
||||
schByTxn[tid].push(s)
|
||||
})
|
||||
const schByTxn = {};
|
||||
schedules.forEach((s) => {
|
||||
const tid = s.txn_detail__transaction;
|
||||
if (!schByTxn[tid]) schByTxn[tid] = [];
|
||||
schByTxn[tid].push(s);
|
||||
});
|
||||
|
||||
// Tìm số đợt tối đa
|
||||
let maxCycles = 0
|
||||
Object.values(schByTxn).forEach(list => {
|
||||
const paymentList = list.filter(s => Number(s.cycle) > 0)
|
||||
if (paymentList.length > maxCycles) maxCycles = paymentList.length
|
||||
})
|
||||
let maxCycles = 0;
|
||||
Object.values(schByTxn).forEach((list) => {
|
||||
const paymentList = list.filter((s) => Number(s.cycle) > 0);
|
||||
if (paymentList.length > maxCycles) maxCycles = paymentList.length;
|
||||
});
|
||||
|
||||
scheduleHeaders.value = Array.from({ length: maxCycles }, (_, i) => ({
|
||||
label: `L0${i + 1}`,
|
||||
index: i
|
||||
}))
|
||||
index: i,
|
||||
}));
|
||||
|
||||
rows.value = transactions.map(txn => {
|
||||
rows.value = transactions.map((txn) => {
|
||||
const txnSchedules = (schByTxn[txn.id] || [])
|
||||
.filter(s => Number(s.cycle) > 0)
|
||||
.sort((a, b) => Number(a.cycle) - Number(b.cycle))
|
||||
.filter((s) => Number(s.cycle) > 0)
|
||||
.sort((a, b) => Number(a.cycle) - Number(b.cycle));
|
||||
|
||||
const ttthnvPaid = ttthnvMap[txn.id] || 0
|
||||
const salePriceNum = Number(txn.sale_price || 0)
|
||||
const ttthnvPaid = ttthnvMap[txn.id] || 0;
|
||||
const salePriceNum = Number(txn.sale_price || 0);
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// QUAN TRỌNG: Theo yêu cầu mới nhất của bạn
|
||||
// Lũy kế HĐCN = TTTHNV
|
||||
const luyKe = ttthnvPaid
|
||||
const luyKe = ttthnvPaid;
|
||||
// ───────────────────────────────────────────────
|
||||
|
||||
// Phân bổ TTTHNV dần vào từng đợt → tính lũy kế sang đợt
|
||||
let remainingTTTHNV = ttthnvPaid
|
||||
let remainingTTTHNV = ttthnvPaid;
|
||||
|
||||
const schedulesWithCalc = txnSchedules.map(sch => {
|
||||
const scheduleAmount = Number(sch.amount || 0)
|
||||
const schedulesWithCalc = txnSchedules.map((sch) => {
|
||||
const scheduleAmount = Number(sch.amount || 0);
|
||||
|
||||
// Lũy kế sang đợt = min(remaining TTTHNV, số tiền đợt)
|
||||
const luyKeSangDot = Math.min(remainingTTTHNV, scheduleAmount)
|
||||
const luyKeSangDot = Math.min(remainingTTTHNV, scheduleAmount);
|
||||
|
||||
// Số tiền đã thực thanh toán = paid_amount - lũy kế sang đợt
|
||||
const paidAmountFromSchedule = Number(sch.paid_amount || 0)
|
||||
const thucThanhToan = Math.max(0, paidAmountFromSchedule - luyKeSangDot)
|
||||
const paidAmountFromSchedule = Number(sch.paid_amount || 0);
|
||||
const thucThanhToan = Math.max(0, paidAmountFromSchedule - luyKeSangDot);
|
||||
|
||||
// Dư nợ = số tiền đợt - lũy kế sang đợt - thực thanh toán
|
||||
const amountRemain = Math.max(0, scheduleAmount - luyKeSangDot - thucThanhToan)
|
||||
const amountRemain = Math.max(0, scheduleAmount - luyKeSangDot - thucThanhToan);
|
||||
|
||||
remainingTTTHNV -= luyKeSangDot
|
||||
remainingTTTHNV = Math.max(0, remainingTTTHNV)
|
||||
remainingTTTHNV -= luyKeSangDot;
|
||||
remainingTTTHNV = Math.max(0, remainingTTTHNV);
|
||||
|
||||
return {
|
||||
to_date: sch.to_date ? $dayjs(sch.to_date).format('DD/MM/YYYY') : '—',
|
||||
to_date: sch.to_date ? $dayjs(sch.to_date).format("DD/MM/YYYY") : "—",
|
||||
amount: scheduleAmount,
|
||||
luy_ke_sang_dot: luyKeSangDot,
|
||||
thuc_thanh_toan: thucThanhToan,
|
||||
amount_remain: amountRemain,
|
||||
status: sch.status
|
||||
}
|
||||
})
|
||||
status: sch.status,
|
||||
};
|
||||
});
|
||||
|
||||
// Tính quá hạn
|
||||
const todayDate = new Date()
|
||||
const todayDate = new Date();
|
||||
const overdue = txnSchedules.reduce((sum, sch, idx) => {
|
||||
const toDate = sch.to_date ? new Date(sch.to_date) : null
|
||||
const remain = schedulesWithCalc[idx]?.amount_remain || 0
|
||||
const toDate = sch.to_date ? new Date(sch.to_date) : null;
|
||||
const remain = schedulesWithCalc[idx]?.amount_remain || 0;
|
||||
if (toDate && toDate < todayDate && remain > 0) {
|
||||
return sum + remain
|
||||
return sum + remain;
|
||||
}
|
||||
return sum
|
||||
}, 0)
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
const paddedSchedules = Array.from({ length: maxCycles }, (_, i) => schedulesWithCalc[i] || null)
|
||||
const paddedSchedules = Array.from({ length: maxCycles }, (_, i) => schedulesWithCalc[i] || null);
|
||||
|
||||
return {
|
||||
customer_code: txn.customer__code || '',
|
||||
customer_name: txn.customer__fullname || '',
|
||||
trade_code: txn.product__trade_code || txn.code || '',
|
||||
contract_date: txn.date ? $dayjs(txn.date).format('DD/MM/YYYY') : '—',
|
||||
customer_code: txn.customer__code || "",
|
||||
customer_name: txn.customer__fullname || "",
|
||||
trade_code: txn.product__trade_code || txn.code || "",
|
||||
contract_date: txn.date ? $dayjs(txn.date).format("DD/MM/YYYY") : "—",
|
||||
contract_date_raw: txn.date,
|
||||
sale_price: salePriceNum,
|
||||
ttthnv_paid: ttthnvPaid,
|
||||
luy_ke: luyKe, // ← chính là TTTHNV
|
||||
luy_ke: luyKe, // ← chính là TTTHNV
|
||||
schedules: paddedSchedules,
|
||||
overdue: overdue
|
||||
}
|
||||
})
|
||||
overdue: overdue,
|
||||
};
|
||||
});
|
||||
|
||||
filteredRows.value = rows.value
|
||||
filteredRows.value = rows.value;
|
||||
} catch (e) {
|
||||
console.error('BaoCaoCongNo error:', e)
|
||||
console.error("BaoCaoCongNo error:", e);
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function fmt(val) {
|
||||
const n = Number(val)
|
||||
if (isNaN(n) || (!n && n !== 0)) return '—'
|
||||
return n.toLocaleString('vi-VN')
|
||||
const n = Number(val);
|
||||
if (isNaN(n) || (!n && n !== 0)) return "—";
|
||||
return n.toLocaleString("vi-VN");
|
||||
}
|
||||
|
||||
function pct(num, denom) {
|
||||
const n = Number(num)
|
||||
const d = Number(denom)
|
||||
if (!d || isNaN(n)) return '—'
|
||||
return (n / d * 100).toFixed(1) + '%'
|
||||
const n = Number(num);
|
||||
const d = Number(denom);
|
||||
if (!d || isNaN(n)) return "—";
|
||||
return ((n / d) * 100).toFixed(1) + "%";
|
||||
}
|
||||
|
||||
function pctClass(paid, amount) {
|
||||
const p = Number(paid)
|
||||
const a = Number(amount)
|
||||
if (isNaN(p) || isNaN(a) || !a) return ''
|
||||
const ratio = p / a
|
||||
if (ratio >= 1) return 'has-text-success'
|
||||
if (ratio >= 0.5) return 'has-text-info'
|
||||
return 'has-text-danger'
|
||||
const p = Number(paid);
|
||||
const a = Number(amount);
|
||||
if (isNaN(p) || isNaN(a) || !a) return "";
|
||||
const ratio = p / a;
|
||||
if (ratio >= 1) return "has-text-success";
|
||||
if (ratio >= 0.5) return "has-text-info";
|
||||
return "has-text-danger";
|
||||
}
|
||||
|
||||
function colSum(scheduleIndex, field) {
|
||||
return filteredRows.value.reduce((sum, row) => {
|
||||
const sch = row.schedules[scheduleIndex]
|
||||
return sum + (sch ? Number(sch[field] || 0) : 0)
|
||||
}, 0)
|
||||
const sch = row.schedules[scheduleIndex];
|
||||
return sum + (sch ? Number(sch[field] || 0) : 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
const totalOverdue = computed(() =>
|
||||
filteredRows.value.reduce((s, r) => s + Number(r.overdue || 0), 0)
|
||||
)
|
||||
const totalOverdue = computed(() => filteredRows.value.reduce((s, r) => s + Number(r.overdue || 0), 0));
|
||||
|
||||
function exportExcel() {
|
||||
const headers = [
|
||||
'STT',
|
||||
'Mã KH',
|
||||
'Mã Căn',
|
||||
'Ngày ký HĐ',
|
||||
'Giá trị HĐMB',
|
||||
'Tiền nộp TTTHNV',
|
||||
'Lũy kế tiền về HĐCN',
|
||||
'Tỷ lệ HĐCN'
|
||||
]
|
||||
"STT",
|
||||
"Mã KH",
|
||||
"Mã Căn",
|
||||
"Ngày ký HĐ",
|
||||
"Giá trị HĐMB",
|
||||
"Tiền nộp TTTHNV",
|
||||
"Lũy kế tiền về HĐCN",
|
||||
"Tỷ lệ HĐCN",
|
||||
];
|
||||
|
||||
scheduleHeaders.value.forEach(h => {
|
||||
scheduleHeaders.value.forEach((h) => {
|
||||
headers.push(
|
||||
`${h.label} - Ngày`,
|
||||
`${h.label} - Số tiền`,
|
||||
`${h.label} - Lũy kế sang`,
|
||||
`${h.label} - Số tiền đã thực thanh toán`,
|
||||
`${h.label} - Tỷ lệ`,
|
||||
`${h.label} - Dư nợ`
|
||||
)
|
||||
})
|
||||
`${h.label} - Dư nợ`,
|
||||
);
|
||||
});
|
||||
|
||||
headers.push('Số tiền quá hạn')
|
||||
headers.push("Số tiền quá hạn");
|
||||
|
||||
const data = filteredRows.value.map((row, i) => {
|
||||
const base = [
|
||||
@@ -455,11 +517,11 @@ function exportExcel() {
|
||||
fmt(row.sale_price),
|
||||
fmt(row.ttthnv_paid),
|
||||
fmt(row.luy_ke),
|
||||
pct(row.luy_ke, row.sale_price)
|
||||
]
|
||||
pct(row.luy_ke, row.sale_price),
|
||||
];
|
||||
|
||||
scheduleHeaders.value.forEach((_, si) => {
|
||||
const sch = row.schedules[si]
|
||||
const sch = row.schedules[si];
|
||||
if (sch) {
|
||||
base.push(
|
||||
sch.to_date,
|
||||
@@ -467,31 +529,31 @@ function exportExcel() {
|
||||
fmt(sch.luy_ke_sang_dot),
|
||||
fmt(sch.thuc_thanh_toan),
|
||||
pct(sch.thuc_thanh_toan, sch.amount),
|
||||
fmt(sch.amount_remain)
|
||||
)
|
||||
fmt(sch.amount_remain),
|
||||
);
|
||||
} else {
|
||||
base.push('', '', '', '', '', '')
|
||||
base.push("", "", "", "", "", "");
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
base.push(fmt(row.overdue))
|
||||
return base
|
||||
})
|
||||
base.push(fmt(row.overdue));
|
||||
return base;
|
||||
});
|
||||
|
||||
const csvRows = [headers, ...data]
|
||||
const csv = csvRows.map(r => r.map(c => `"${String(c ?? '').replace(/"/g, '""')}"`).join(',')).join('\n')
|
||||
const csvRows = [headers, ...data];
|
||||
const csv = csvRows.map((r) => r.map((c) => `"${String(c ?? "").replace(/"/g, '""')}"`).join(",")).join("\n");
|
||||
|
||||
const BOM = '\uFEFF'
|
||||
const blob = new Blob([BOM + csv], { type: 'text/csv;charset=utf-8;' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `bao-cao-cong-no-${$dayjs().format('YYYYMMDD')}.csv`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
const BOM = "\uFEFF";
|
||||
const blob = new Blob([BOM + csv], { type: "text/csv;charset=utf-8;" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `bao-cao-cong-no-${$dayjs().format("YYYYMMDD")}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
onMounted(() => loadData());
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -578,4 +640,4 @@ onMounted(() => loadData())
|
||||
.fixed-col {
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,31 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<DataView v-bind="{api: 'internalaccount', setting: store.lang==='en'? 'internal-account-en' : 'internal-account', pagename: pagename,
|
||||
modal: {title: 'Tài khoản', component: 'accounting/AccountView', width: '50%', 'height': '300px'}}" />
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal" />
|
||||
</div>
|
||||
<div>
|
||||
<DataView
|
||||
v-bind="{
|
||||
api: 'internalaccount',
|
||||
setting: store.lang === 'en' ? 'internal-account-en' : 'internal-account',
|
||||
pagename: pagename,
|
||||
modal: {
|
||||
title: 'Tài khoản',
|
||||
component: 'accounting/AccountView',
|
||||
width: '50%',
|
||||
height: '300px',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { useStore } from '~/stores/index'
|
||||
import { useStore } from "~/stores/index";
|
||||
export default {
|
||||
setup() {
|
||||
const store = useStore()
|
||||
return {store}
|
||||
const store = useStore();
|
||||
return { store };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showmodal: undefined,
|
||||
pagename: 'pagedata32'
|
||||
}
|
||||
pagename: "pagedata32",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
deposit() {
|
||||
this.showmodal = {component: 'accounting/InternalDeposit', title: 'Nộp tiền tài khoản nội bộ', width: '40%', height: '300px',
|
||||
vbind: {pagename: this.pagename}}
|
||||
this.showmodal = {
|
||||
component: "accounting/InternalDeposit",
|
||||
title: "Nộp tiền tài khoản nội bộ",
|
||||
width: "40%",
|
||||
height: "300px",
|
||||
vbind: { pagename: this.pagename },
|
||||
};
|
||||
},
|
||||
doClick() {
|
||||
this.$approvalcode()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
this.$approvalcode();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,176 +1,288 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="columns is-multiline mx-0">
|
||||
<div class="column is-8">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('account') }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{api:'internalaccount', field:'label', column:['label'], first: true, optionid: record.account}"
|
||||
:disabled="record.account" @option="selected('_account', $event)" v-if="!record.id"></SearchBox>
|
||||
<span v-else>{{record.account__code}}</span>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors._account">{{ errors._account }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div>
|
||||
<div class="columns is-multiline mx-0">
|
||||
<div class="column is-8">
|
||||
<div class="field">
|
||||
<label class="label"
|
||||
>Ngày hạch toán<b class="ml-1 has-text-danger">*</b></label
|
||||
<label class="label">{{ $lang("account") }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'internalaccount',
|
||||
field: 'label',
|
||||
column: ['label'],
|
||||
first: true,
|
||||
optionid: record.account,
|
||||
}"
|
||||
:disabled="record.account"
|
||||
@option="selected('_account', $event)"
|
||||
v-if="!record.id"
|
||||
></SearchBox>
|
||||
<span v-else>{{ record.account__code }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors._account"
|
||||
>
|
||||
{{ errors._account }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">Ngày hạch toán<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<Datepicker
|
||||
v-bind="{ record: record, attr: 'date', maxdate: new Date()}"
|
||||
v-bind="{ record: record, attr: 'date', maxdate: new Date() }"
|
||||
@date="selected('date', $event)"
|
||||
></Datepicker>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.issued_date">{{ errors.issued_date }}</p>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.issued_date"
|
||||
>
|
||||
{{ errors.issued_date }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">Thu / chi<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{api:'entrytype', field:'name', column:['name'], first: true, optionid: record.type}"
|
||||
:disabled="record.type" @option="selected('_type', $event)" v-if="!record.id"></SearchBox>
|
||||
<span v-else>{{record.type__name}}</span>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors._type">{{ errors._type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">{{$lang('amount-only')}}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<InputNumber v-bind="{record: record, attr: 'amount', placeholder: ''}" @number="selected('amount', $event)"></InputNumber>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.amount">{{errors.amount}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">Phương thức thanh toán<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{api:'entrycategory', field:'name', column:['name'], first: true, optionid: record.type}"
|
||||
:disabled="record.type" @option="selected('_category', $event)" v-if="!record.id"></SearchBox>
|
||||
<span v-else>{{record.type__name}}</span>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors._type">{{ errors._type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label">Sản phẩm<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{
|
||||
api: 'product',
|
||||
field: 'label',
|
||||
searchfield: ['code', 'trade_code', 'type__name', 'land_lot_size', 'zone_type__name'],
|
||||
column: ['code', 'trade_code', 'type__name', 'land_lot_size', 'zone_type__name'],
|
||||
first: true
|
||||
}" @option="selected('product', $event)" />
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors._type">{{ errors._type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label">Khách hàng<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{
|
||||
api: 'customer',
|
||||
field: 'label',
|
||||
searchfield: ['code', 'fullname', 'phone', 'legal_code'],
|
||||
column: ['code', 'fullname', 'phone', 'legal_code'],
|
||||
first: true
|
||||
}" @option="selected('customer', $event)" />
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors._type">{{ errors._type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('content') }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" rows="2" v-model="record.content"></textarea>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.content">{{errors.content}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">Mã tham chiếu</label>
|
||||
<div class="control">
|
||||
<input class="input has-text-black" type="text" placeholder="Tối đa 30 ký tự" v-model="record.ref">
|
||||
<label class="label">Thu / chi<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'entrytype',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: record.type,
|
||||
}"
|
||||
:disabled="record.type"
|
||||
@option="selected('_type', $event)"
|
||||
v-if="!record.id"
|
||||
></SearchBox>
|
||||
<span v-else>{{ record.type__name }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors._type"
|
||||
>
|
||||
{{ errors._type }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("amount-only") }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<InputNumber
|
||||
v-bind="{ record: record, attr: 'amount', placeholder: '' }"
|
||||
@number="selected('amount', $event)"
|
||||
></InputNumber>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.amount"
|
||||
>
|
||||
{{ errors.amount }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">Phương thức thanh toán<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'entrycategory',
|
||||
field: 'name',
|
||||
column: ['name'],
|
||||
first: true,
|
||||
optionid: record.type,
|
||||
}"
|
||||
:disabled="record.type"
|
||||
@option="selected('_category', $event)"
|
||||
v-if="!record.id"
|
||||
></SearchBox>
|
||||
<span v-else>{{ record.type__name }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors._type"
|
||||
>
|
||||
{{ errors._type }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label">Sản phẩm<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'product',
|
||||
field: 'label',
|
||||
searchfield: ['code', 'trade_code', 'type__name', 'land_lot_size', 'zone_type__name'],
|
||||
column: ['code', 'trade_code', 'type__name', 'land_lot_size', 'zone_type__name'],
|
||||
first: true,
|
||||
}"
|
||||
@option="selected('product', $event)"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors._type"
|
||||
>
|
||||
{{ errors._type }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
<label class="label">Khách hàng<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'customer',
|
||||
field: 'label',
|
||||
searchfield: ['code', 'fullname', 'phone', 'legal_code'],
|
||||
column: ['code', 'fullname', 'phone', 'legal_code'],
|
||||
first: true,
|
||||
}"
|
||||
@option="selected('customer', $event)"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors._type"
|
||||
>
|
||||
{{ errors._type }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("content") }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<textarea
|
||||
class="textarea"
|
||||
rows="2"
|
||||
v-model="record.content"
|
||||
></textarea>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.content"
|
||||
>
|
||||
{{ errors.content }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<label class="label">Mã tham chiếu</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input has-text-black"
|
||||
type="text"
|
||||
placeholder="Tối đa 30 ký tự"
|
||||
v-model="record.ref"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="column is-12"
|
||||
v-if="entry"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label">Chứng từ đi kèm (nếu có)</label>
|
||||
<div class="control">
|
||||
<FileGallery
|
||||
v-bind="{
|
||||
row: entry,
|
||||
pagename: pagename,
|
||||
api: 'entryfile',
|
||||
info: false,
|
||||
}"
|
||||
></FileGallery>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mt-5 ml-3"
|
||||
v-if="!entry"
|
||||
>
|
||||
<button
|
||||
:class="['button is-primary has-text-white mr-2', isUpdating && 'is-loading']"
|
||||
@click="confirm()"
|
||||
>
|
||||
{{ $lang("confirm") }}
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
@close="showContractModal = undefined"
|
||||
v-bind="showContractModal"
|
||||
v-if="showContractModal"
|
||||
></Modal>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
@confirm="update()"
|
||||
v-if="showmodal"
|
||||
></Modal>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12" v-if="entry">
|
||||
<div class="field">
|
||||
<label class="label">Chứng từ đi kèm (nếu có)</label>
|
||||
<div class="control">
|
||||
<FileGallery v-bind="{row: entry, pagename: pagename, api: 'entryfile', info: false}"></FileGallery>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 ml-3" v-if="!entry">
|
||||
<button
|
||||
:class="[
|
||||
'button is-primary has-text-white mr-2',
|
||||
isUpdating && 'is-loading'
|
||||
]"
|
||||
@click="confirm()">{{$lang('confirm')}}</button>
|
||||
</div>
|
||||
<Modal @close="showContractModal=undefined" v-bind="showContractModal" v-if="showContractModal"></Modal>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" @confirm="update()" v-if="showmodal"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { useStore } from '~/stores/index'
|
||||
import { useStore } from "~/stores/index";
|
||||
export default {
|
||||
setup() {
|
||||
const store = useStore()
|
||||
return {store}
|
||||
const store = useStore();
|
||||
return { store };
|
||||
},
|
||||
props: ['pagename', 'row', 'option'],
|
||||
props: ["pagename", "row", "option"],
|
||||
data() {
|
||||
return {
|
||||
record: {date: this.$dayjs().format('YYYY-MM-DD')},
|
||||
record: { date: this.$dayjs().format("YYYY-MM-DD") },
|
||||
errors: {},
|
||||
isUpdating: false,
|
||||
showmodal: undefined,
|
||||
showContractModal: undefined,
|
||||
entry: undefined
|
||||
}
|
||||
entry: undefined,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if(!this.option) return
|
||||
this.record.account = this.option.account
|
||||
this.record.type = this.option.type
|
||||
if (!this.option) return;
|
||||
this.record.account = this.option.account;
|
||||
this.record.type = this.option.type;
|
||||
},
|
||||
methods: {
|
||||
selected(attr, obj) {
|
||||
this.record[attr] = obj
|
||||
this.record = this.$copy(this.record)
|
||||
this.record[attr] = obj;
|
||||
this.record = this.$copy(this.record);
|
||||
},
|
||||
checkError() {
|
||||
this.errors = {}
|
||||
if(this.$empty(this.record._account)) this.errors._account = 'Chưa chọn tài khoản'
|
||||
if(this.$empty(this.record._type)) this.errors._type = 'Chưa chọn loại hạch toán'
|
||||
if(this.$empty(this.record.amount)) this.errors.amount = 'Chưa nhập số tiền'
|
||||
else if(this.$formatNumber(this.record.amount)<=0) this.errors.amount = 'Số tiền phải > 0'
|
||||
if(this.$empty(this.record.content)) this.errors.content = 'Chưa nhập nội dung'
|
||||
if(Object.keys(this.errors).length>0) return true
|
||||
if(this.record._type.code==='DR' && (this.record._account.balance<this.$formatNumber(this.record.amount))) {
|
||||
this.errors._account = 'Số dư tài khoản không đủ để trích nợ'
|
||||
this.errors = {};
|
||||
if (this.$empty(this.record._account)) this.errors._account = "Chưa chọn tài khoản";
|
||||
if (this.$empty(this.record._type)) this.errors._type = "Chưa chọn loại hạch toán";
|
||||
if (this.$empty(this.record.amount)) this.errors.amount = "Chưa nhập số tiền";
|
||||
else if (this.$formatNumber(this.record.amount) <= 0) this.errors.amount = "Số tiền phải > 0";
|
||||
if (this.$empty(this.record.content)) this.errors.content = "Chưa nhập nội dung";
|
||||
if (Object.keys(this.errors).length > 0) return true;
|
||||
if (this.record._type.code === "DR" && this.record._account.balance < this.$formatNumber(this.record.amount)) {
|
||||
this.errors._account = "Số dư tài khoản không đủ để trích nợ";
|
||||
}
|
||||
return Object.keys(this.errors).length>0
|
||||
return Object.keys(this.errors).length > 0;
|
||||
},
|
||||
confirm() {
|
||||
if(this.checkError()) return
|
||||
this.showmodal = {component: `dialog/Confirm`,vbind: {content: this.$lang('confirm-action'), duration: 10},
|
||||
title: this.$lang('confirm'), width: '500px', height: '100px'}
|
||||
if (this.checkError()) return;
|
||||
this.showmodal = {
|
||||
component: `dialog/Confirm`,
|
||||
vbind: { content: this.$lang("confirm-action"), duration: 10 },
|
||||
title: this.$lang("confirm"),
|
||||
width: "500px",
|
||||
height: "100px",
|
||||
};
|
||||
},
|
||||
async update() {
|
||||
this.isUpdating = true;
|
||||
@@ -179,28 +291,29 @@ export default {
|
||||
amount: this.record.amount,
|
||||
content: this.record.content,
|
||||
type: this.record._type.code,
|
||||
category: this.record._category ? this.record._category.id : 1, user: this.store.login.id,
|
||||
ref: this.row ? this.row.code : (!this.$empty(this.record.ref) ? this.record.ref.trim() : null),
|
||||
category: this.record._category ? this.record._category.id : 1,
|
||||
user: this.store.login.id,
|
||||
ref: this.row ? this.row.code : !this.$empty(this.record.ref) ? this.record.ref.trim() : null,
|
||||
customer: this.record.customer ? this.record.customer.id : null,
|
||||
product: this.record.product ? this.record.product.id : null,
|
||||
date: this.$empty(this.record.date) ? null : this.record.date
|
||||
}
|
||||
let rs1 = await this.$insertapi('accountentry', obj1, undefined, false)
|
||||
if(rs1==='error') return
|
||||
date: this.$empty(this.record.date) ? null : this.record.date,
|
||||
};
|
||||
let rs1 = await this.$insertapi("accountentry", obj1, undefined, false);
|
||||
if (rs1 === "error") return;
|
||||
|
||||
if (this.record._category.id === 2) {
|
||||
const genDoc = await this.$generateDocument({
|
||||
doc_code: 'PHIEU_THU_TIEN_MAT',
|
||||
doc_code: "PHIEU_THU_TIEN_MAT",
|
||||
entry_id: rs1.id,
|
||||
output_filename: `PHIEU_THU_TIEN_MAT-${rs1.code}`
|
||||
output_filename: `PHIEU_THU_TIEN_MAT-${rs1.code}`,
|
||||
});
|
||||
|
||||
await this.$insertapi('file', {
|
||||
name: genDoc.data.pdf,
|
||||
user: this.store.login.id,
|
||||
type: 4,
|
||||
size: 1000,
|
||||
file: genDoc.data.pdf // or genDoc.data.pdf
|
||||
await this.$insertapi("file", {
|
||||
name: genDoc.data.pdf,
|
||||
user: this.store.login.id,
|
||||
type: 4,
|
||||
size: 1000,
|
||||
file: genDoc.data.pdf, // or genDoc.data.pdf
|
||||
});
|
||||
|
||||
this.showContractModal = {
|
||||
@@ -209,20 +322,27 @@ export default {
|
||||
width: "95%",
|
||||
height: "95vh",
|
||||
vbind: {
|
||||
directDocument: genDoc.data
|
||||
directDocument: genDoc.data,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.entry = rs1
|
||||
if(this.pagename) {
|
||||
let data = await this.$getdata('internalaccount', {code__in: [this.record._account.code]})
|
||||
this.$updatepage(this.pagename, data)
|
||||
|
||||
this.entry = rs1;
|
||||
if (this.pagename) {
|
||||
let data = await this.$getdata("internalaccount", {
|
||||
code__in: [this.record._account.code],
|
||||
});
|
||||
this.$updatepage(this.pagename, data);
|
||||
}
|
||||
this.isUpdating = false;
|
||||
this.$emit('modalevent', {name: 'entry', data: rs1})
|
||||
this.$dialog(`Hạch toán <b>${this.record._type.name}</b> số tiền <b>${this.$numtoString(this.record.amount)}</b> vào tài khoản <b>${this.record._account.code}</b> thành công.`, 'Thành công', 'Success', 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
this.$emit("modalevent", { name: "entry", data: rs1 });
|
||||
this.$dialog(
|
||||
`Hạch toán <b>${this.record._type.name}</b> số tiền <b>${this.$numtoString(this.record.amount)}</b> vào tài khoản <b>${this.record._account.code}</b> thành công.`,
|
||||
"Thành công",
|
||||
"Success",
|
||||
10,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,247 +1,326 @@
|
||||
<template>
|
||||
<div v-if="record" id="printable">
|
||||
<Caption v-bind="{ title: $lang('info') }" />
|
||||
<div class="columns is-multiline is-2 m-0">
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('code') }}:</label>
|
||||
<div class="control">
|
||||
<span>{{ record.code }}</span>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.type">{{ errors.type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Tài khoản:</label>
|
||||
<div class="control">
|
||||
<span>{{ record.account__code }}</span>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.type">{{ errors.type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Ngày hạch toán:</label>
|
||||
<div class="control">
|
||||
{{ $dayjs(record.date).format('DD/MM/YYYY') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('amount-only') }}:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.amount) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Thu / chi:</label>
|
||||
<div class="control">
|
||||
{{ record.type__name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Dư trước:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.balance_before) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Dư sau:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.balance_after) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Mã sản phẩm:</label>
|
||||
<div class="control">
|
||||
{{ record.product__trade_code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Mã khách hàng:</label>
|
||||
<div class="control">
|
||||
{{ record.customer__code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="record"
|
||||
id="printable"
|
||||
>
|
||||
<Caption v-bind="{ title: $lang('info') }" />
|
||||
<div class="columns is-multiline is-2 m-0">
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("code") }}:</label>
|
||||
<div class="control">
|
||||
<span>{{ record.code }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.type"
|
||||
>
|
||||
{{ errors.type }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Tài khoản:</label>
|
||||
<div class="control">
|
||||
<span>{{ record.account__code }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.type"
|
||||
>
|
||||
{{ errors.type }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Ngày hạch toán:</label>
|
||||
<div class="control">
|
||||
{{ $dayjs(record.date).format("DD/MM/YYYY") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("amount-only") }}:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.amount) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Thu / chi:</label>
|
||||
<div class="control">
|
||||
{{ record.type__name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Dư trước:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.balance_before) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Dư sau:</label>
|
||||
<div class="control">
|
||||
{{ $numtoString(record.balance_after) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Mã sản phẩm:</label>
|
||||
<div class="control">
|
||||
{{ record.product__trade_code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Mã khách hàng:</label>
|
||||
<div class="control">
|
||||
{{ record.customer__code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Người hạch toán:</label>
|
||||
<div class="control">
|
||||
{{ `${record.inputer__fullname}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('time') }}:</label>
|
||||
<div class="control">
|
||||
{{ `${$dayjs(record.create_time).format('DD/MM/YYYY')}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Ref:</label>
|
||||
<div class="control">
|
||||
{{ `${record.ref || '/'}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-8">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('content') }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.content}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Người hạch toán:</label>
|
||||
<div class="control">
|
||||
{{ `${record.inputer__fullname}` }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PHẦN THÔNG TIN PHÂN BỔ -->
|
||||
<Caption v-bind="{ title: 'Thông tin phân bổ' }" />
|
||||
<!-- BẢNG CHI TIẾT PHÂN BỔ -->
|
||||
<div v-if="record.allocation_detail && record.allocation_detail.length > 0" class="mt-4">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-striped is-hoverable is-bordered">
|
||||
<thead>
|
||||
<tr class="">
|
||||
<th class="has-background-primary has-text-white has-text-centered">STT</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Mã lịch</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Loại</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Tổng phân bổ</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Gốc</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Phạt</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Miễn lãi</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Ngày phân bổ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in record.allocation_detail" :key="index">
|
||||
<td class="has-text-centered">{{ index + 1 }}</td>
|
||||
<td>
|
||||
<span class="tag is-link is-light">{{ item.schedule_code || item.schedule_id }}</span>
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
<span v-if="item.type === 'REDUCTION'" class="tag is-warning">Miễn lãi</span>
|
||||
<span v-else class="tag is-success">Thanh toán</span>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<strong>{{ $numtoString(item.amount) }}</strong>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<span v-if="item.principal" class="has-text-info has-text-weight-semibold">
|
||||
{{ $numtoString(item.principal) }}
|
||||
</span>
|
||||
<span v-else class="has-text-grey-light">-</span>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<span v-if="item.penalty" class="has-text-danger has-text-weight-semibold">
|
||||
{{ $numtoString(item.penalty) }}
|
||||
</span>
|
||||
<span v-else class="has-text-grey-light">-</span>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<span v-if="item.penalty" class="has-text-danger has-text-weight-semibold">
|
||||
{{ $numtoString(item.penalty_reduce) }}
|
||||
</span>
|
||||
<span v-else class="has-text-grey-light">-</span>
|
||||
</td>
|
||||
<td class="has-text-centered">{{ $dayjs(item.date).format('DD/MM/YYYY HH:mm:ss') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="has-background-light">
|
||||
<td colspan="3" class="has-text-right has-text-weight-bold">Tổng cộng:</td>
|
||||
<td class="has-text-right has-text-weight-bold">{{ $numtoString(totalAllocated) }}</td>
|
||||
<td class="has-text-right has-text-weight-bold has-text-info">{{
|
||||
$numtoString(totalPrincipal) }}</td>
|
||||
<td class="has-text-right has-text-weight-bold has-text-danger">{{
|
||||
$numtoString(totalPenalty) }}</td>
|
||||
<td class="has-text-right has-text-weight-bold has-text-danger">{{
|
||||
$numtoString(totalPenaltyReduce) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("time") }}:</label>
|
||||
<div class="control">
|
||||
{{ `${$dayjs(record.create_time).format("DD/MM/YYYY")}` }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="notification is-info is-light mt-4">
|
||||
<p class="has-text-centered">Chưa có dữ liệu phân bổ cho bút toán này.</p>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label">Ref:</label>
|
||||
<div class="control">
|
||||
{{ `${record.ref || "/"}` }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Caption class="mt-5 " v-bind="{ title: 'Chứng từ' }"></Caption>
|
||||
<FileGallery v-bind="{ row: record, api: 'entryfile' }"></FileGallery>
|
||||
<div class="mt-5" id="ignore">
|
||||
<button class="button is-primary has-text-white mr-2" @click="$exportpdf('printable', record.code, 'a4', 'landscape')">{{ $lang('print') }}</button>
|
||||
<button v-if="record.category === 2" class="button is-light" @click="viewPhieuThuTienMat">Xem phiếu thu</button>
|
||||
</div>
|
||||
<div class="column is-8">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("content") }}:</label>
|
||||
<div class="control">
|
||||
{{ `${record.content}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PHẦN THÔNG TIN PHÂN BỔ -->
|
||||
<Caption v-bind="{ title: 'Thông tin phân bổ' }" />
|
||||
<!-- BẢNG CHI TIẾT PHÂN BỔ -->
|
||||
<div
|
||||
v-if="record.allocation_detail && record.allocation_detail.length > 0"
|
||||
class="mt-4"
|
||||
>
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-striped is-hoverable is-bordered">
|
||||
<thead>
|
||||
<tr class="">
|
||||
<th class="has-background-primary has-text-white has-text-centered">STT</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Mã lịch</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Loại</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Tổng phân bổ</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Gốc</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Phạt</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Miễn lãi</th>
|
||||
<th class="has-background-primary has-text-white has-text-centered">Ngày phân bổ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in record.allocation_detail"
|
||||
:key="index"
|
||||
>
|
||||
<td class="has-text-centered">{{ index + 1 }}</td>
|
||||
<td>
|
||||
<span class="tag is-link is-light">{{ item.schedule_code || item.schedule_id }}</span>
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
<span
|
||||
v-if="item.type === 'REDUCTION'"
|
||||
class="tag is-warning"
|
||||
>Miễn lãi</span
|
||||
>
|
||||
<span
|
||||
v-else
|
||||
class="tag is-success"
|
||||
>Thanh toán</span
|
||||
>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<strong>{{ $numtoString(item.amount) }}</strong>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<span
|
||||
v-if="item.principal"
|
||||
class="has-text-info has-text-weight-semibold"
|
||||
>
|
||||
{{ $numtoString(item.principal) }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="has-text-grey-light"
|
||||
>-</span
|
||||
>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<span
|
||||
v-if="item.penalty"
|
||||
class="has-text-danger has-text-weight-semibold"
|
||||
>
|
||||
{{ $numtoString(item.penalty) }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="has-text-grey-light"
|
||||
>-</span
|
||||
>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<span
|
||||
v-if="item.penalty"
|
||||
class="has-text-danger has-text-weight-semibold"
|
||||
>
|
||||
{{ $numtoString(item.penalty_reduce) }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="has-text-grey-light"
|
||||
>-</span
|
||||
>
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
{{ $dayjs(item.date).format("DD/MM/YYYY HH:mm:ss") }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="has-background-light">
|
||||
<td
|
||||
colspan="3"
|
||||
class="has-text-right has-text-weight-bold"
|
||||
>
|
||||
Tổng cộng:
|
||||
</td>
|
||||
<td class="has-text-right has-text-weight-bold">
|
||||
{{ $numtoString(totalAllocated) }}
|
||||
</td>
|
||||
<td class="has-text-right has-text-weight-bold has-text-info">
|
||||
{{ $numtoString(totalPrincipal) }}
|
||||
</td>
|
||||
<td class="has-text-right has-text-weight-bold has-text-danger">
|
||||
{{ $numtoString(totalPenalty) }}
|
||||
</td>
|
||||
<td class="has-text-right has-text-weight-bold has-text-danger">
|
||||
{{ $numtoString(totalPenaltyReduce) }}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="notification is-info is-light mt-4"
|
||||
>
|
||||
<p class="has-text-centered">Chưa có dữ liệu phân bổ cho bút toán này.</p>
|
||||
</div>
|
||||
|
||||
<Caption
|
||||
class="mt-5"
|
||||
v-bind="{ title: 'Chứng từ' }"
|
||||
></Caption>
|
||||
<FileGallery v-bind="{ row: record, api: 'entryfile' }"></FileGallery>
|
||||
<div
|
||||
class="mt-5"
|
||||
id="ignore"
|
||||
>
|
||||
<button
|
||||
class="button is-primary has-text-white mr-2"
|
||||
@click="$exportpdf('printable', record.code, 'a4', 'landscape')"
|
||||
>
|
||||
{{ $lang("print") }}
|
||||
</button>
|
||||
<button
|
||||
v-if="record.category === 2"
|
||||
class="button is-light"
|
||||
@click="viewPhieuThuTienMat"
|
||||
>
|
||||
Xem phiếu thu
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['row'],
|
||||
data() {
|
||||
return {
|
||||
errors: {},
|
||||
record: undefined
|
||||
}
|
||||
props: ["row"],
|
||||
data() {
|
||||
return {
|
||||
errors: {},
|
||||
record: undefined,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.record = await this.$getdata("internalentry", { code: this.row.code }, undefined, true);
|
||||
},
|
||||
computed: {
|
||||
// Tính tổng số tiền đã phân bổ
|
||||
totalAllocated() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0;
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.amount || 0), 0);
|
||||
},
|
||||
async created() {
|
||||
this.record = await this.$getdata('internalentry', { code: this.row.code }, undefined, true)
|
||||
// Tính tổng gốc
|
||||
totalPrincipal() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0;
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.principal || 0), 0);
|
||||
},
|
||||
computed: {
|
||||
|
||||
// Tính tổng số tiền đã phân bổ
|
||||
totalAllocated() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.amount || 0), 0)
|
||||
},
|
||||
// Tính tổng gốc
|
||||
totalPrincipal() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.principal || 0), 0)
|
||||
},
|
||||
// Tính tổng phạt
|
||||
totalPenalty() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.penalty || 0), 0)
|
||||
},
|
||||
// Tính tổng phạt đã giảm
|
||||
totalPenaltyReduce() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.penalty_reduce || 0), 0)
|
||||
}
|
||||
// Tính tổng phạt
|
||||
totalPenalty() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0;
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.penalty || 0), 0);
|
||||
},
|
||||
methods: {
|
||||
selected(attr, obj) {
|
||||
this.record[attr] = obj
|
||||
this.record = this.$copy(this.record)
|
||||
if (attr === '_type') this.category = obj.category__code
|
||||
},
|
||||
viewPhieuThuTienMat() {
|
||||
const url = `${this.$getpath()}static/contract/PHIEU_THU_TIEN_MAT-${this.record.code}.pdf`;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Tính tổng phạt đã giảm
|
||||
totalPenaltyReduce() {
|
||||
if (!this.record || !this.record.allocation_detail) return 0;
|
||||
return this.record.allocation_detail.reduce((sum, item) => sum + (item.penalty_reduce || 0), 0);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selected(attr, obj) {
|
||||
this.record[attr] = obj;
|
||||
this.record = this.$copy(this.record);
|
||||
if (attr === "_type") this.category = obj.category__code;
|
||||
},
|
||||
viewPhieuThuTienMat() {
|
||||
const url = `${this.$getpath()}static/contract/PHIEU_THU_TIEN_MAT-${this.record.code}.pdf`;
|
||||
window.open(url, "_blank");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.column {
|
||||
padding-inline: 0;
|
||||
padding-inline: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,104 +1,191 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="columns is-multiline mx-0">
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{$lang('source-account')}}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="{api:'internalaccount', field:'label', column:['label'], first: true, optionid: row.id}"
|
||||
@option="selected('_source', $event)" v-if="!record.id"></SearchBox>
|
||||
<span v-else>{{record.account__code}}</span>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.source">{{ errors.source }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{$lang('dest-account')}}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox v-bind="vbind" @option="selected('_target', $event)" v-if="vbind"></SearchBox>
|
||||
<span v-else>{{record.account__code}}</span>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.target">{{ errors.target }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('amount-only') }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<InputNumber v-bind="{record: record, attr: 'amount', placeholder: ''}" @number="selected('amount', $event)"></InputNumber>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("source-account") }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="{
|
||||
api: 'internalaccount',
|
||||
field: 'label',
|
||||
column: ['label'],
|
||||
first: true,
|
||||
optionid: row.id,
|
||||
}"
|
||||
@option="selected('_source', $event)"
|
||||
v-if="!record.id"
|
||||
></SearchBox>
|
||||
<span v-else>{{ record.account__code }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.source"
|
||||
>
|
||||
{{ errors.source }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("dest-account") }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<SearchBox
|
||||
v-bind="vbind"
|
||||
@option="selected('_target', $event)"
|
||||
v-if="vbind"
|
||||
></SearchBox>
|
||||
<span v-else>{{ record.account__code }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.target"
|
||||
>
|
||||
{{ errors.target }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("amount-only") }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<InputNumber
|
||||
v-bind="{ record: record, attr: 'amount', placeholder: '' }"
|
||||
@number="selected('amount', $event)"
|
||||
></InputNumber>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.amount"
|
||||
>
|
||||
{{ errors.amount }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang("content") }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<textarea
|
||||
class="textarea"
|
||||
rows="2"
|
||||
v-model="record.content"
|
||||
></textarea>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.content"
|
||||
>
|
||||
{{ errors.content }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.amount">{{errors.amount}}</p>
|
||||
<div class="mt-5">
|
||||
<button
|
||||
class="button is-primary has-text-white"
|
||||
@click="confirm()"
|
||||
>
|
||||
{{ $lang("confirm") }}
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
@confirm="update()"
|
||||
v-if="showmodal"
|
||||
></Modal>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<div class="field">
|
||||
<label class="label">{{ $lang('content') }}<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" rows="2" v-model="record.content"></textarea>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.content">{{errors.content}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="button is-primary has-text-white" @click="confirm()">{{ $lang('confirm') }}</button>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" @confirm="update()" v-if="showmodal"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename', 'row'],
|
||||
props: ["pagename", "row"],
|
||||
data() {
|
||||
return {
|
||||
record: {},
|
||||
errors: {},
|
||||
showmodal: undefined,
|
||||
vbind: undefined
|
||||
}
|
||||
vbind: undefined,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
selected(attr, obj) {
|
||||
this.record[attr] = obj
|
||||
this.record = this.$copy(this.record)
|
||||
if(attr==='_source') {
|
||||
let currency = obj? obj.currency : undefined
|
||||
this.vbind = undefined
|
||||
setTimeout(()=>this.vbind = {api:'internalaccount', field:'label', column:['label'], first: true, filter: {currency: currency}})
|
||||
this.record[attr] = obj;
|
||||
this.record = this.$copy(this.record);
|
||||
if (attr === "_source") {
|
||||
let currency = obj ? obj.currency : undefined;
|
||||
this.vbind = undefined;
|
||||
setTimeout(
|
||||
() =>
|
||||
(this.vbind = {
|
||||
api: "internalaccount",
|
||||
field: "label",
|
||||
column: ["label"],
|
||||
first: true,
|
||||
filter: { currency: currency },
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
checkError() {
|
||||
this.errors = {}
|
||||
if(this.$empty(this.record._source)) this.errors.source = 'Chưa chọn tài khoản nguồn'
|
||||
if(this.$empty(this.record._target)) this.errors.target = 'Chưa chọn tài khoản đích'
|
||||
if(Object.keys(this.errors).length===0) {
|
||||
if(this.record._source.id===this.record._target.id) this.errors.target = 'Tài khoản nguồn phải khác tài khoản đích'
|
||||
this.errors = {};
|
||||
if (this.$empty(this.record._source)) this.errors.source = "Chưa chọn tài khoản nguồn";
|
||||
if (this.$empty(this.record._target)) this.errors.target = "Chưa chọn tài khoản đích";
|
||||
if (Object.keys(this.errors).length === 0) {
|
||||
if (this.record._source.id === this.record._target.id)
|
||||
this.errors.target = "Tài khoản nguồn phải khác tài khoản đích";
|
||||
}
|
||||
if(this.$empty(this.record.amount)) this.errors.amount = 'Chưa nhập số tiền'
|
||||
else if(this.$formatNumber(this.record.amount)<=0) this.errors.amount = 'Số tiền phải > 0'
|
||||
else if(this.record._source.balance<this.$formatNumber(this.record.amount)) this.errors.source = 'Tài khoản nguồn không đủ số dư để điều chuyển'
|
||||
if(this.$empty(this.record.content)) this.errors.content = 'Chưa nhập nội dung'
|
||||
return Object.keys(this.errors).length>0
|
||||
if (this.$empty(this.record.amount)) this.errors.amount = "Chưa nhập số tiền";
|
||||
else if (this.$formatNumber(this.record.amount) <= 0) this.errors.amount = "Số tiền phải > 0";
|
||||
else if (this.record._source.balance < this.$formatNumber(this.record.amount))
|
||||
this.errors.source = "Tài khoản nguồn không đủ số dư để điều chuyển";
|
||||
if (this.$empty(this.record.content)) this.errors.content = "Chưa nhập nội dung";
|
||||
return Object.keys(this.errors).length > 0;
|
||||
},
|
||||
confirm() {
|
||||
if(this.checkError()) return
|
||||
this.showmodal = {component: `dialog/Confirm`,vbind: {content: this.$lang('confirm-action'), duration: 10},
|
||||
title: this.$lang('confirm'), width: '500px', height: '100px'}
|
||||
if (this.checkError()) return;
|
||||
this.showmodal = {
|
||||
component: `dialog/Confirm`,
|
||||
vbind: { content: this.$lang("confirm-action"), duration: 10 },
|
||||
title: this.$lang("confirm"),
|
||||
width: "500px",
|
||||
height: "100px",
|
||||
};
|
||||
},
|
||||
async update() {
|
||||
let content = `${this.record.content} (${this.record._source.code} -> ${this.record._target.code})`
|
||||
let obj1 = {code: this.record._source.code, amount: this.record.amount, content: content, type: 'DR', category: 2, user: this.$store.login.id}
|
||||
let rs1 = await this.$insertapi('accountentry', obj1, undefined, false)
|
||||
if(rs1==='error') return
|
||||
let obj2 = {code: this.record._target.code, amount: this.record.amount, content: content, type: 'CR', category: 2, user: this.$store.login.id}
|
||||
let rs2 = await this.$insertapi('accountentry', obj2, undefined, false)
|
||||
if(rs2==='error') return
|
||||
let data = await this.$getdata('internalaccount', {code__in: [this.record._source.code, this.record._target.code]})
|
||||
this.$updatepage(this.pagename, data)
|
||||
this.$dialog(`Điều chuyển vốn <b>${this.$numtoString(this.record.amount)}</b> từ <b>${this.record._source.code}</b> tới <b>${this.record._target.code}</b> thành công.`, 'Thành công', 'Success', 10)
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
let content = `${this.record.content} (${this.record._source.code} -> ${this.record._target.code})`;
|
||||
let obj1 = {
|
||||
code: this.record._source.code,
|
||||
amount: this.record.amount,
|
||||
content: content,
|
||||
type: "DR",
|
||||
category: 2,
|
||||
user: this.$store.login.id,
|
||||
};
|
||||
let rs1 = await this.$insertapi("accountentry", obj1, undefined, false);
|
||||
if (rs1 === "error") return;
|
||||
let obj2 = {
|
||||
code: this.record._target.code,
|
||||
amount: this.record.amount,
|
||||
content: content,
|
||||
type: "CR",
|
||||
category: 2,
|
||||
user: this.$store.login.id,
|
||||
};
|
||||
let rs2 = await this.$insertapi("accountentry", obj2, undefined, false);
|
||||
if (rs2 === "error") return;
|
||||
let data = await this.$getdata("internalaccount", {
|
||||
code__in: [this.record._source.code, this.record._target.code],
|
||||
});
|
||||
this.$updatepage(this.pagename, data);
|
||||
this.$dialog(
|
||||
`Điều chuyển vốn <b>${this.$numtoString(this.record.amount)}</b> từ <b>${this.record._source.code}</b> tới <b>${this.record._target.code}</b> thành công.`,
|
||||
"Thành công",
|
||||
"Success",
|
||||
10,
|
||||
);
|
||||
this.$emit("close");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
</div>
|
||||
<div class="column is-1"></div>
|
||||
</div>
|
||||
<div class="columns" v-for="(invoice, index) in invoices">
|
||||
<div
|
||||
class="columns"
|
||||
v-for="(invoice, index) in invoices"
|
||||
>
|
||||
<div class="column">
|
||||
<input
|
||||
class="input has-text-centered has-text-weight-bold has-text-left"
|
||||
@@ -32,7 +35,12 @@
|
||||
})
|
||||
"
|
||||
/>
|
||||
<p v-if="invoice.errorLink" class="help is-danger">Link phải bắt đầu bằng https</p>
|
||||
<p
|
||||
v-if="invoice.errorLink"
|
||||
class="help is-danger"
|
||||
>
|
||||
Link phải bắt đầu bằng https
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<input
|
||||
@@ -49,7 +57,12 @@
|
||||
})
|
||||
"
|
||||
/>
|
||||
<p v-if="invoice.errorCode" class="help is-danger">Mã tra cứu không được bỏ trống</p>
|
||||
<p
|
||||
v-if="invoice.errorCode"
|
||||
class="help is-danger"
|
||||
>
|
||||
Mã tra cứu không được bỏ trống
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<input
|
||||
@@ -66,7 +79,12 @@
|
||||
})
|
||||
"
|
||||
/>
|
||||
<p v-if="invoice.errorAmount" class="help is-danger">Số tiền không được bỏ trống</p>
|
||||
<p
|
||||
v-if="invoice.errorAmount"
|
||||
class="help is-danger"
|
||||
>
|
||||
Số tiền không được bỏ trống
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<select
|
||||
@@ -84,22 +102,44 @@
|
||||
<option value="principal">Tiền gốc</option>
|
||||
<option value="interest">Tiền lãi</option>
|
||||
</select>
|
||||
<p v-if="invoice.errorType" class="help is-danger">Loại tiền không được bỏ trống</p>
|
||||
<p
|
||||
v-if="invoice.errorType"
|
||||
class="help is-danger"
|
||||
>
|
||||
Loại tiền không được bỏ trống
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-narrow is-1">
|
||||
<label class="label" v-if="i === 0"> </label>
|
||||
<div class="buttons is-gap-0.5 is-flex-wrap-nowrap are-small" style="height: 40px">
|
||||
<button class="button is-dark" @click="handlerRemove(index)">
|
||||
<label
|
||||
class="label"
|
||||
v-if="i === 0"
|
||||
> </label
|
||||
>
|
||||
<div
|
||||
class="buttons is-gap-0.5 is-flex-wrap-nowrap are-small"
|
||||
style="height: 40px"
|
||||
>
|
||||
<button
|
||||
class="button is-dark"
|
||||
@click="handlerRemove(index)"
|
||||
>
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'white', size: 20 }"></SvgIcon>
|
||||
</span>
|
||||
</button>
|
||||
<button class="button is-dark" @click="add()">
|
||||
<button
|
||||
class="button is-dark"
|
||||
@click="add()"
|
||||
>
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'add4.svg', type: 'white', size: 20 }"></SvgIcon>
|
||||
</span>
|
||||
</button>
|
||||
<a class="button is-dark" :href="invoice.link" target="_blank">
|
||||
<a
|
||||
class="button is-dark"
|
||||
:href="invoice.link"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'view.svg', type: 'white', size: 20 }"></SvgIcon>
|
||||
</span>
|
||||
@@ -108,8 +148,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 buttons is-right">
|
||||
<button class="button" @click="emit('close')">{{ isVietnamese ? "Hủy" : "Cancel" }}</button>
|
||||
<button class="button is-primary" @click="handlerUpdate">{{ isVietnamese ? "Lưu lại" : "Save" }}</button>
|
||||
<button
|
||||
class="button"
|
||||
@click="emit('close')"
|
||||
>
|
||||
{{ isVietnamese ? "Hủy" : "Cancel" }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="handlerUpdate"
|
||||
>
|
||||
{{ isVietnamese ? "Lưu lại" : "Save" }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,7 +207,11 @@ if (resInvoice.length) {
|
||||
errorAmount: false,
|
||||
errorType: false,
|
||||
};
|
||||
const formatData = resInvoice.map((invoice) => ({ ...invoice, amount: $formatNumber(invoice.amount), ...error }));
|
||||
const formatData = resInvoice.map((invoice) => ({
|
||||
...invoice,
|
||||
amount: $formatNumber(invoice.amount),
|
||||
...error,
|
||||
}));
|
||||
|
||||
invoices.value = formatData;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user