feat: add seller filtering and enhance quotation forms with sellerId
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s

This commit is contained in:
puriphatt 2025-07-07 14:34:39 +07:00
parent f08c83c98b
commit 1e34f18366
9 changed files with 41 additions and 5 deletions

View file

@ -106,6 +106,7 @@ watch(
:persistent="isDateSelect" :persistent="isDateSelect"
> >
<div class="q-pa-sm"> <div class="q-pa-sm">
<slot name="prepend"></slot>
<div class="text-weight-medium"> <div class="text-weight-medium">
{{ $t('general.advanceSearch') }} {{ $t('general.advanceSearch') }}
</div> </div>

View file

@ -768,6 +768,7 @@ export default {
}, },
quotation: { quotation: {
ownOnly: 'View Own Quotation Only',
quotationDate: 'Quotation Date', quotationDate: 'Quotation Date',
seller: 'Seller', seller: 'Seller',
paymentChannels: 'Payment Channels', paymentChannels: 'Payment Channels',

View file

@ -766,6 +766,7 @@ export default {
}, },
quotation: { quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา', quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย', seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน', paymentChannels: 'ช่องทางชำระเงิน',

View file

@ -15,6 +15,7 @@ import { useQuotationForm } from './form';
import { hslaColors } from './constants'; import { hslaColors } from './constants';
import { pageTabs, columnQuotation } from './constants'; import { pageTabs, columnQuotation } from './constants';
import { toCamelCase, canAccess } from 'stores/utils'; import { toCamelCase, canAccess } from 'stores/utils';
import { getUserId } from 'src/services/keycloak';
// NOTE Import Types // NOTE Import Types
import { CustomerBranchCreate, CustomerType } from 'stores/customer/types'; import { CustomerBranchCreate, CustomerType } from 'stores/customer/types';
@ -104,6 +105,7 @@ const pageState = reactive({
fieldSelected: [''], fieldSelected: [''],
gridView: false, gridView: false,
total: 0, total: 0,
sellerId: '',
currentTab: 'Issued', currentTab: 'Issued',
addModal: false, addModal: false,
@ -297,6 +299,7 @@ onMounted(async () => {
pageSize: quotationPageSize.value, pageSize: quotationPageSize.value,
status: 'Issued', status: 'Issued',
urgentFirst: true, urgentFirst: true,
sellerId: pageState.sellerId || undefined,
}); });
if (ret) { if (ret) {
@ -331,6 +334,7 @@ async function fetchQuotationList(mobileFetch?: boolean) {
urgentFirst: true, urgentFirst: true,
startDate: pageState.searchDate[0], startDate: pageState.searchDate[0],
endDate: pageState.searchDate[1], endDate: pageState.searchDate[1],
sellerId: pageState.sellerId || undefined,
}); });
if (ret) { if (ret) {
@ -406,6 +410,11 @@ async function storeDataLocal(id: string) {
window.open(url, '_blank'); window.open(url, '_blank');
} }
async function filterBySellerId() {
pageState.sellerId = pageState.sellerId ? '' : getUserId();
await fetchQuotationList();
}
</script> </script>
<template> <template>
@ -529,7 +538,18 @@ async function storeDataLocal(id: string) {
</template> </template>
<template v-slot:append> <template v-slot:append>
<q-separator vertical inset class="q-mr-xs" /> <q-separator vertical inset class="q-mr-xs" />
<AdvanceSearch v-model="pageState.searchDate" /> <AdvanceSearch v-model="pageState.searchDate">
<template #prepend>
<div class="text-weight-medium q-mb-sm">
<q-checkbox
size="xs"
:model-value="!!pageState.sellerId"
@click="filterBySellerId"
/>
{{ $t('quotation.ownOnly') }}
</div>
</template>
</AdvanceSearch>
</template> </template>
</q-input> </q-input>

View file

@ -2,6 +2,7 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { QSelect, useQuasar } from 'quasar'; import { QSelect, useQuasar } from 'quasar';
import { getUserId } from 'src/services/keycloak';
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { import {
baseUrl, baseUrl,
@ -78,7 +79,6 @@ import { api } from 'src/boot/axios';
import { RouterLink, useRoute } from 'vue-router'; import { RouterLink, useRoute } from 'vue-router';
import { initLang, initTheme, Lang } from 'src/utils/ui'; import { initLang, initTheme, Lang } from 'src/utils/ui';
import { convertTemplate } from 'src/utils/string-template'; import { convertTemplate } from 'src/utils/string-template';
import { getRole } from 'src/services/keycloak';
type Node = { type Node = {
[key: string]: any; [key: string]: any;
@ -616,6 +616,7 @@ async function convertDataToFormSubmit() {
discount: quotationFormData.value.discount, discount: quotationFormData.value.discount,
remark: quotationFormData.value.remark || '', remark: quotationFormData.value.remark || '',
agentPrice: agentPrice.value, agentPrice: agentPrice.value,
sellerId: quotationFormData.value.sellerId,
}; };
newWorkerList.value = []; newWorkerList.value = [];
@ -1015,6 +1016,7 @@ onMounted(async () => {
quotationFormData.value.customerBranchId = parsed.customerBranchId; quotationFormData.value.customerBranchId = parsed.customerBranchId;
currentQuotationId.value = parsed.quotationId; currentQuotationId.value = parsed.quotationId;
agentPrice.value = parsed.agentPrice; agentPrice.value = parsed.agentPrice;
quotationFormData.value.sellerId = getUserId();
await fetchQuotation(); await fetchQuotation();
await assignWorkerToSelectedWorker(); await assignWorkerToSelectedWorker();
sessionData.value = parsed; sessionData.value = parsed;
@ -1506,6 +1508,7 @@ function covertToNode() {
v-model:contactor="quotationFormData.contactName" v-model:contactor="quotationFormData.contactName"
v-model:telephone="quotationFormData.contactTel" v-model:telephone="quotationFormData.contactTel"
v-model:due-date="quotationFormData.dueDate" v-model:due-date="quotationFormData.dueDate"
v-model:seller-id="quotationFormData.sellerId"
> >
<template #issue-info> <template #issue-info>
<FormAbout <FormAbout
@ -2110,8 +2113,6 @@ function covertToNode() {
installmentAmount = props.row.amount; installmentAmount = props.row.amount;
view = View.Invoice; view = View.Invoice;
console.log(code);
} }
} }
" "

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import DatePicker from 'src/components/shared/DatePicker.vue'; import DatePicker from 'src/components/shared/DatePicker.vue';
import SelectUser from 'src/components/shared/select/SelectUser.vue';
defineProps<{ defineProps<{
readonly: boolean; readonly: boolean;
@ -13,6 +14,7 @@ const contactor = defineModel<string>('contactor', { required: true });
const telephone = defineModel<string>('telephone', { required: true }); const telephone = defineModel<string>('telephone', { required: true });
const dueDate = defineModel<Date | string>('dueDate', { required: true }); const dueDate = defineModel<Date | string>('dueDate', { required: true });
const createdAt = defineModel<Date | string>('createdAt'); const createdAt = defineModel<Date | string>('createdAt');
const sellerId = defineModel<string>('sellerId', { required: true });
</script> </script>
<template> <template>
@ -95,5 +97,11 @@ const createdAt = defineModel<Date | string>('createdAt');
dense dense
outlined outlined
/> />
<SelectUser
:label="$t('preview.seller')"
v-model:value="sellerId"
:readonly
class="col-12 col-md-2"
/>
</div> </div>
</template> </template>

View file

@ -8,6 +8,7 @@ import {
QuotationPayload, QuotationPayload,
QuotationFull, QuotationFull,
EmployeeWorker, EmployeeWorker,
PayCondition,
} from 'src/stores/quotations/types'; } from 'src/stores/quotations/types';
import { Employee } from 'src/stores/employee/types'; import { Employee } from 'src/stores/employee/types';
@ -29,7 +30,7 @@ export const DEFAULT_DATA: QuotationPayload = {
payBillDate: new Date(), payBillDate: new Date(),
paySplit: [], paySplit: [],
paySplitCount: 0, paySplitCount: 0,
payCondition: 'Full', payCondition: PayCondition.Full,
dueDate: new Date(Date.now() + 86400000), dueDate: new Date(Date.now() + 86400000),
discount: 0, discount: 0,
contactTel: '', contactTel: '',
@ -40,6 +41,7 @@ export const DEFAULT_DATA: QuotationPayload = {
status: 'CREATED', status: 'CREATED',
remark: '#[quotation-labor]<br/><br/>#[quotation-payment]', remark: '#[quotation-labor]<br/><br/>#[quotation-payment]',
agentPrice: false, agentPrice: false,
sellerId: '',
}; };
const DEFAULT_DATA_INVOICE: InvoicePayload = { const DEFAULT_DATA_INVOICE: InvoicePayload = {

View file

@ -78,6 +78,7 @@ export const useQuotationStore = defineStore('quotation-store', () => {
cancelIncludeDebitNote?: boolean; cancelIncludeDebitNote?: boolean;
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
sellerId?: string;
}) { }) {
const res = await api.get<PaginationResult<Quotation>>('/quotation', { const res = await api.get<PaginationResult<Quotation>>('/quotation', {
params, params,

View file

@ -356,6 +356,7 @@ export type QuotationPayload = {
_count: { worker: number }; _count: { worker: number };
discount?: number; discount?: number;
payBillDate?: Date | null; payBillDate?: Date | null;
sellerId?: string;
paySplit: { paySplit: {
no: number; no: number;
amount: number; amount: number;