291 lines
8.1 KiB
Vue
291 lines
8.1 KiB
Vue
<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>
|