chore: install prettier

This commit is contained in:
Viet An
2026-05-04 15:22:27 +07:00
parent 93d29ca7d8
commit bd58e2b847
267 changed files with 22950 additions and 13581 deletions

View File

@@ -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>

View File

@@ -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 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 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 ) 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: &quot;Courier New&quot;, 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>

View File

@@ -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>

View File

@@ -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 },
},
})
}),
);
},
},

View File

@@ -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>

View File

@@ -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>

View File

@@ -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ố 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ố 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>

View File

@@ -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"> trước:</label>
<div class="control">
{{ $numtoString(record.balance_before) }}
</div>
</div>
</div>
<div class="column is-3">
<div class="field">
<label class="label"> sau:</label>
<div class="control">
{{ $numtoString(record.balance_after) }}
</div>
</div>
</div>
<div class="column is-3">
<div class="field">
<label class="label"> 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"> 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"> trước:</label>
<div class="control">
{{ $numtoString(record.balance_before) }}
</div>
</div>
</div>
<div class="column is-3">
<div class="field">
<label class="label"> sau:</label>
<div class="control">
{{ $numtoString(record.balance_after) }}
</div>
</div>
</div>
<div class="column is-3">
<div class="field">
<label class="label"> 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"> 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"> 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 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"> 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 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>

View File

@@ -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ố để đ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>

View File

@@ -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"> tra cứu không được bỏ trống</p>
<p
v-if="invoice.errorCode"
class="help is-danger"
>
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">&nbsp;</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"
>&nbsp;</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;
}