Initial commit
This commit is contained in:
290
app/components/accounting/TransactionInvoice.vue
Normal file
290
app/components/accounting/TransactionInvoice.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<div class="content-transaction-invoice">
|
||||
<div class="container is-fluid px-4">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<label class="label">Link<b class="ml-1 has-text-danger">*</b></label>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label">Mã tra cứu<b class="ml-1 has-text-danger">*</b></label>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label">Số tiền<b class="ml-1 has-text-danger">*</b></label>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label">Loại tiền<b class="ml-1 has-text-danger">*</b></label>
|
||||
</div>
|
||||
<div class="column is-1"></div>
|
||||
</div>
|
||||
<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"
|
||||
type="text"
|
||||
placeholder="Nhập link tra cứu"
|
||||
v-model="invoice.link"
|
||||
@blur="
|
||||
validateField({
|
||||
value: invoice.link,
|
||||
type: 'link',
|
||||
index,
|
||||
field: 'errorLink',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<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
|
||||
class="input has-text-centered has-text-weight-bold has-text-left"
|
||||
type="text"
|
||||
placeholder="Nhập mã tra cứu"
|
||||
v-model="invoice.ref_code"
|
||||
@blur="
|
||||
validateField({
|
||||
value: invoice.ref_code,
|
||||
type: 'text',
|
||||
index,
|
||||
field: 'errorCode',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<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
|
||||
class="input has-text-centered has-text-weight-bold has-text-right"
|
||||
type="number"
|
||||
placeholder="Số tiền"
|
||||
v-model="invoice.amount"
|
||||
@blur="
|
||||
validateField({
|
||||
value: invoice.amount,
|
||||
type: 'text',
|
||||
index,
|
||||
field: 'errorAmount',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<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
|
||||
v-model="invoice.note"
|
||||
style="width: 100%; height: var(--bulma-control-height)"
|
||||
@blur="
|
||||
validateField({
|
||||
value: invoice.note,
|
||||
type: 'text',
|
||||
index,
|
||||
field: 'errorType',
|
||||
})
|
||||
"
|
||||
>
|
||||
<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>
|
||||
</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)">
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'white', size: 20 }"></SvgIcon>
|
||||
</span>
|
||||
</button>
|
||||
<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">
|
||||
<span class="icon">
|
||||
<SvgIcon v-bind="{ name: 'view.svg', type: 'white', size: 20 }"></SvgIcon>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { $snackbar, $getdata, $insertapi, $store, $updateapi, $deleteapi, $formatNumber } = useNuxtApp();
|
||||
|
||||
const isVietnamese = computed(() => $store.lang.toLowerCase() === "vi");
|
||||
|
||||
const invoices = ref([{}]);
|
||||
const delInvoices = ref([]);
|
||||
const emit = defineEmits(["close"]);
|
||||
const props = defineProps({
|
||||
row: Object,
|
||||
});
|
||||
const resInvoice = await $getdata("invoice", { payment: props.row.id }, undefined, false);
|
||||
|
||||
const validateField = ({ value, type = "text", index, field }) => {
|
||||
if (index < 0 || index >= invoices.value.length) return false;
|
||||
|
||||
const val = value?.toString().trim();
|
||||
let isInvalid = false;
|
||||
|
||||
// 1. Không được bỏ trống (áp dụng cho tất cả)
|
||||
if (!val) {
|
||||
isInvalid = true;
|
||||
}
|
||||
|
||||
// 2. Validate theo type
|
||||
if (!isInvalid && type === "link") {
|
||||
isInvalid = !/^https:\/\//.test(val);
|
||||
}
|
||||
|
||||
// set lỗi
|
||||
invoices.value[index][field] = isInvalid;
|
||||
|
||||
return !isInvalid;
|
||||
};
|
||||
|
||||
if (resInvoice.length) {
|
||||
const error = {
|
||||
errorLink: false,
|
||||
errorCode: false,
|
||||
errorAmount: false,
|
||||
errorType: false,
|
||||
};
|
||||
const formatData = resInvoice.map((invoice) => ({ ...invoice, amount: $formatNumber(invoice.amount), ...error }));
|
||||
|
||||
invoices.value = formatData;
|
||||
}
|
||||
const add = () => invoices.value.push({});
|
||||
|
||||
const validateAll = () => {
|
||||
const errors = [];
|
||||
|
||||
invoices.value.forEach((inv, index) => {
|
||||
const checks = [
|
||||
{
|
||||
value: inv.link,
|
||||
type: "link",
|
||||
field: "errorLink",
|
||||
label: "Link",
|
||||
},
|
||||
{
|
||||
value: inv.ref_code,
|
||||
type: "number",
|
||||
field: "errorCode",
|
||||
label: "Mã tham chiếu",
|
||||
},
|
||||
{
|
||||
value: inv.amount,
|
||||
type: "number",
|
||||
field: "errorAmount",
|
||||
label: "Số tiền",
|
||||
},
|
||||
{
|
||||
value: inv.note,
|
||||
type: "number",
|
||||
field: "errorType",
|
||||
label: "Số tiền",
|
||||
},
|
||||
];
|
||||
|
||||
checks.forEach(({ value, type, field, label }) => {
|
||||
const isValid = validateField({
|
||||
value,
|
||||
type,
|
||||
index,
|
||||
field,
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
errors.push({
|
||||
index,
|
||||
field,
|
||||
label,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
};
|
||||
|
||||
const handlerUpdate = async () => {
|
||||
try {
|
||||
// 1. Insert / Update
|
||||
if (!validateAll()?.valid) {
|
||||
$snackbar("Dữ liệu chưa hợp lệ");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const invoice of invoices.value) {
|
||||
let res;
|
||||
|
||||
if (invoice.id) {
|
||||
res = await $updateapi("invoice", invoice, undefined, false);
|
||||
} else {
|
||||
const dataSend = {
|
||||
...invoice,
|
||||
payment: props.row.id,
|
||||
};
|
||||
res = await $insertapi("invoice", dataSend, undefined, false);
|
||||
}
|
||||
|
||||
if (!res || res === "error") {
|
||||
throw new Error("Save invoice failed");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Delete
|
||||
for (const id of delInvoices.value) {
|
||||
const res = await $deleteapi("invoice", id);
|
||||
if (!res || res === "error") {
|
||||
throw new Error("Delete invoice failed");
|
||||
}
|
||||
}
|
||||
|
||||
$snackbar("Lưu hóa đơn thành công");
|
||||
emit("close");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$snackbar("Có lỗi khi lưu hóa đơn");
|
||||
}
|
||||
};
|
||||
|
||||
const handlerRemove = (index) => {
|
||||
if (index < 0 || index >= invoices.value.length) return;
|
||||
|
||||
const [removed] = invoices.value.splice(index, 1);
|
||||
|
||||
if (removed?.id && !delInvoices.value.includes(removed.id)) {
|
||||
delInvoices.value.push(removed.id);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content-transaction-invoice input,
|
||||
select {
|
||||
border-radius: 5px;
|
||||
border-color: gray;
|
||||
}
|
||||
|
||||
.content-transaction-invoice input[type="number"]::-webkit-inner-spin-button,
|
||||
.content-transaction-invoice input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content-transaction-invoice input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user