Merge branch 'dev' into 7986-workerMoto
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Carlos Satorres 2024-10-16 12:10:22 +00:00
commit 3cdba5dafa
26 changed files with 243 additions and 205 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.42.0", "version": "24.44.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",

View File

@ -31,8 +31,8 @@ const countriesFilter = {
const countriesOptions = ref([]); const countriesOptions = ref([]);
const onDataSaved = (formData, requestResponse) => { const onDataSaved = (...args) => {
emit('onDataSaved', formData, requestResponse); emit('onDataSaved', ...args);
}; };
onMounted(async () => { onMounted(async () => {

View File

@ -227,6 +227,8 @@ function nullishToTrue(value) {
} }
const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val); const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
defineExpose({ opts: myOptions });
</script> </script>
<template> <template>

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { ref } from 'vue'; import { ref, reactive } from 'vue';
import { onBeforeRouteLeave } from 'vue-router'; import { onBeforeRouteLeave } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -12,36 +12,40 @@ import VnPaginate from 'components/ui/VnPaginate.vue';
import VnUserLink from 'components/ui/VnUserLink.vue'; import VnUserLink from 'components/ui/VnUserLink.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import VnAvatar from 'components/ui/VnAvatar.vue'; import VnAvatar from 'components/ui/VnAvatar.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnInput from 'components/common/VnInput.vue';
const $props = defineProps({ const $props = defineProps({
url: { type: String, default: null }, url: { type: String, default: null },
filter: { type: Object, default: () => {} }, filter: { type: Object, default: () => {} },
body: { type: Object, default: () => {} }, body: { type: Object, default: () => {} },
addNote: { type: Boolean, default: false }, addNote: { type: Boolean, default: false },
selectType: { type: Boolean, default: false },
}); });
const { t } = useI18n(); const { t } = useI18n();
const state = useState(); const state = useState();
const quasar = useQuasar(); const quasar = useQuasar();
const currentUser = ref(state.getUser()); const currentUser = ref(state.getUser());
const newNote = ref(''); const newNote = reactive({ text: null, observationTypeFk: null });
const observationTypes = ref([]);
const vnPaginateRef = ref(); const vnPaginateRef = ref();
function handleKeyUp(event) {
if (event.key === 'Enter') {
event.preventDefault();
if (!event.shiftKey) insert();
}
}
async function insert() { async function insert() {
if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return;
const body = $props.body; const body = $props.body;
const newBody = { ...body, ...{ text: newNote.value } }; const newBody = {
...body,
...{ text: newNote.text, observationTypeFk: newNote.observationTypeFk },
};
await axios.post($props.url, newBody); await axios.post($props.url, newBody);
await vnPaginateRef.value.fetch(); await vnPaginateRef.value.fetch();
newNote.value = '';
} }
onBeforeRouteLeave((to, from, next) => { onBeforeRouteLeave((to, from, next) => {
if (newNote.value) if (newNote.text)
quasar.dialog({ quasar.dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
@ -54,6 +58,13 @@ onBeforeRouteLeave((to, from, next) => {
}); });
</script> </script>
<template> <template>
<FetchData
v-if="selectType"
url="ObservationTypes"
:filter="{ fields: ['id', 'description'] }"
auto-load
@on-fetch="(data) => (observationTypes = data)"
/>
<QCard class="q-pa-xs q-mb-xl full-width" v-if="$props.addNote"> <QCard class="q-pa-xs q-mb-xl full-width" v-if="$props.addNote">
<QCardSection horizontal> <QCardSection horizontal>
<VnAvatar :worker-id="currentUser.id" size="md" /> <VnAvatar :worker-id="currentUser.id" size="md" />
@ -62,29 +73,42 @@ onBeforeRouteLeave((to, from, next) => {
{{ t('globals.now') }} {{ t('globals.now') }}
</div> </div>
</QCardSection> </QCardSection>
<QCardSection class="q-pa-xs q-my-none q-py-none" horizontal> <QCardSection class="q-px-xs q-my-none q-py-none">
<QInput <VnRow class="full-width">
v-model="newNote" <VnSelect
class="full-width" :label="t('Observation type')"
type="textarea" v-if="selectType"
:label="t('Add note here...')" url="ObservationTypes"
filled v-model="newNote.observationTypeFk"
size="lg" option-label="description"
autogrow style="flex: 0.15"
autofocus :required="true"
@keyup="handleKeyUp" @keyup.enter.stop="insert"
clearable />
> <VnInput
<template #append> v-model.trim="newNote.text"
<QBtn type="textarea"
:title="t('Save (Enter)')" :label="t('Add note here...')"
icon="save" filled
color="primary" size="lg"
flat autogrow
@click="insert" @keyup.enter.stop="insert"
/> clearable
</template> :required="true"
</QInput> >
<template #append>
<QBtn
:title="t('Save (Enter)')"
icon="save"
color="primary"
flat
@click="insert"
class="q-mb-xs"
dense
/>
</template>
</VnInput>
</VnRow>
</QCardSection> </QCardSection>
</QCard> </QCard>
<VnPaginate <VnPaginate
@ -98,6 +122,10 @@ onBeforeRouteLeave((to, from, next) => {
class="show" class="show"
v-bind="$attrs" v-bind="$attrs"
search-url="notes" search-url="notes"
@on-fetch="
newNote.text = '';
newNote.observationTypeFk = null;
"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<TransitionGroup name="list" tag="div" class="column items-center full-width"> <TransitionGroup name="list" tag="div" class="column items-center full-width">
@ -111,13 +139,28 @@ onBeforeRouteLeave((to, from, next) => {
:descriptor="false" :descriptor="false"
:worker-id="note.workerFk" :worker-id="note.workerFk"
size="md" size="md"
:title="note.worker?.user.nickname"
/> />
<div class="full-width row justify-between q-pa-xs"> <div class="full-width row justify-between q-pa-xs">
<VnUserLink <div>
:name="`${note.worker.user.nickname}`" <VnUserLink
:worker-id="note.worker.id" :name="`${note.worker.user.nickname}`"
/> :worker-id="note.worker.id"
{{ toDateHourMin(note.created) }} />
<QBadge
class="q-ml-xs"
outline
color="grey"
v-if="selectType && note.observationTypeFk"
>
{{
observationTypes.find(
(ot) => ot.id === note.observationTypeFk
)?.description
}}
</QBadge>
</div>
<span v-text="toDateHourMin(note.created)" />
</div> </div>
</QCardSection> </QCardSection>
<QCardSection class="q-pa-xs q-my-none q-py-none"> <QCardSection class="q-pa-xs q-my-none q-py-none">
@ -131,12 +174,6 @@ onBeforeRouteLeave((to, from, next) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.q-card { .q-card {
width: 90%; width: 90%;
@media (max-width: $breakpoint-sm) {
width: 100%;
}
&__section {
word-wrap: break-word;
}
} }
.q-dialog .q-card { .q-dialog .q-card {
width: 400px; width: 400px;
@ -150,11 +187,28 @@ onBeforeRouteLeave((to, from, next) => {
opacity: 0; opacity: 0;
background-color: $primary; background-color: $primary;
} }
.vn-row > :nth-child(2) {
margin-left: 0;
}
@media (max-width: $breakpoint-xs) {
.vn-row > :deep(*) {
margin-left: 0;
}
.q-card {
width: 100%;
&__section {
padding: 0;
}
}
}
</style> </style>
<i18n> <i18n>
es: es:
Add note here...: Añadir nota aquí... Add note here...: Añadir nota aquí...
New note: Nueva nota New note: Nueva nota
Save (Enter): Guardar (Intro) Save (Enter): Guardar (Intro)
Observation type: Tipo de observación
</i18n> </i18n>

View File

@ -22,5 +22,6 @@ const noteFilter = computed(() => {
:filter="noteFilter" :filter="noteFilter"
:body="{ clientFk: route.params.id }" :body="{ clientFk: route.params.id }"
style="overflow-y: auto" style="overflow-y: auto"
:select-type="true"
/> />
</template> </template>

View File

@ -25,19 +25,31 @@ const { notify } = useNotify();
const { t } = useI18n(); const { t } = useI18n();
const newObservation = ref(null); const newObservation = ref(null);
const obsId = ref(null);
const onSubmit = async () => { const onSubmit = async () => {
try { try {
const data = $props.clients.map((item) => { if (!obsId.value)
return { clientFk: item.clientFk, text: newObservation.value }; obsId.value = (
}); await axios.get('ObservationTypes/findOne', {
await axios.post('ClientObservations', data); params: { filter: { where: { description: 'Finance' } } },
})
).data?.id;
const payload = { const bodyObs = $props.clients.map((item) => {
return {
clientFk: item.clientFk,
text: newObservation.value,
observationTypeFk: obsId.value,
};
});
await axios.post('ClientObservations', bodyObs);
const bodyObsMail = {
defaulters: $props.clients, defaulters: $props.clients,
observation: newObservation.value, observation: newObservation.value,
}; };
await axios.post('Defaulters/observationEmail', payload); await axios.post('Defaulters/observationEmail', bodyObsMail);
await $props.promise(); await $props.promise();

View File

@ -240,39 +240,33 @@ function handleLocation(data, location) {
class="row q-gutter-md q-mb-md" class="row q-gutter-md q-mb-md"
v-for="(note, index) in notes" v-for="(note, index) in notes"
> >
<div class="col"> <VnSelect
<VnSelect :label="t('Observation type')"
:label="t('Observation type')" :options="observationTypes"
:options="observationTypes" hide-selected
hide-selected option-label="description"
option-label="description" option-value="id"
option-value="id" v-model="note.observationTypeFk"
v-model="note.observationTypeFk" />
/> <VnInput
</div> :label="t('Description')"
<div class="col"> :rules="validate('route.description')"
<VnInput clearable
:label="t('Description')" v-model="note.description"
:rules="validate('route.description')" />
clearable <QIcon
v-model="note.description" :style="{ flex: 0, 'align-self': $q.screen.gt.xs ? 'end' : 'center' }"
/> @click.stop="deleteNote(note.id, index)"
</div> class="cursor-pointer"
<div class="flex items-center"> color="primary"
<QIcon name="delete"
@click.stop="deleteNote(note.id, index)" size="sm"
class="cursor-pointer" >
color="primary" <QTooltip>
name="delete" {{ t('Remove note') }}
size="sm" </QTooltip>
> </QIcon>
<QTooltip>
{{ t('Remove note') }}
</QTooltip>
</QIcon>
</div>
</VnRow> </VnRow>
<QBtn <QBtn
@click.stop="addNote()" @click.stop="addNote()"
class="cursor-pointer add-icon q-mt-md" class="cursor-pointer add-icon q-mt-md"

View File

@ -1,9 +1,7 @@
<script setup> <script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
@ -11,17 +9,8 @@ import VnSelect from 'src/components/common/VnSelect.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const workersOptions = ref([]);
const clientsOptions = ref([]);
</script> </script>
<template> <template>
<FetchData
url="Workers/search"
@on-fetch="(data) => (workersOptions = data)"
auto-load
/>
<FetchData url="Clients" @on-fetch="(data) => (clientsOptions = data)" auto-load />
<FormModel <FormModel
:url="`Departments/${route.params.id}`" :url="`Departments/${route.params.id}`"
model="department" model="department"
@ -62,7 +51,7 @@ const clientsOptions = ref([]);
<VnSelect <VnSelect
:label="t('department.bossDepartment')" :label="t('department.bossDepartment')"
v-model="data.workerFk" v-model="data.workerFk"
:options="workersOptions" url="Workers/search"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected
@ -72,7 +61,7 @@ const clientsOptions = ref([]);
<VnSelect <VnSelect
:label="t('department.selfConsumptionCustomer')" :label="t('department.selfConsumptionCustomer')"
v-model="data.clientFk" v-model="data.clientFk"
:options="clientsOptions" url="Clients"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected

View File

@ -15,25 +15,10 @@ const props = defineProps({
required: true, required: true,
}, },
}); });
const workers = ref();
const workersCopy = ref();
const states = ref(); const states = ref();
function setWorkers(data) {
workers.value = data;
workersCopy.value = data;
}
</script> </script>
<template> <template>
<FetchData url="ClaimStates" @on-fetch="(data) => (states = data)" auto-load /> <FetchData url="ClaimStates" @on-fetch="(data) => (states = data)" auto-load />
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="setWorkers"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true"> <VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">

View File

@ -13,6 +13,8 @@ import { toCurrency, toDate } from 'src/filters/index';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import InvoiceOutFilter from './InvoiceOutFilter.vue';
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -182,6 +184,11 @@ watchEffect(selectedRows);
:label="t('searchInvoice')" :label="t('searchInvoice')"
data-key="invoiceOut" data-key="invoiceOut"
/> />
<RightMenu>
<template #right-panel>
<InvoiceOutFilter data-key="invoiceOut" />
</template>
</RightMenu>
<VnSubToolbar> <VnSubToolbar>
<template #st-actions> <template #st-actions>
<QBtn <QBtn
@ -206,6 +213,7 @@ watchEffect(selectedRows);
active: true, active: true,
}, },
}" }"
:right-search="false"
v-model:selected="selectedRows" v-model:selected="selectedRows"
order="id DESC" order="id DESC"
:columns="columns" :columns="columns"

View File

@ -170,7 +170,6 @@ const downloadCSV = async () => {
} }
} }
" "
:limit="0"
:columns="columns" :columns="columns"
auto-load auto-load
:is-editable="false" :is-editable="false"

View File

@ -15,7 +15,6 @@ const router = useRouter();
const newItemTypeForm = reactive({}); const newItemTypeForm = reactive({});
const workersOptions = ref([]);
const categoriesOptions = ref([]); const categoriesOptions = ref([]);
const temperaturesOptions = ref([]); const temperaturesOptions = ref([]);
@ -25,12 +24,6 @@ const redirectToItemTypeBasicData = (_, { id }) => {
</script> </script>
<template> <template>
<FetchData
url="Workers"
@on-fetch="(data) => (workersOptions = data)"
:filter="{ order: 'firstName ASC', fields: ['id', 'firstName'] }"
auto-load
/>
<FetchData <FetchData
url="ItemCategories" url="ItemCategories"
@on-fetch="(data) => (categoriesOptions = data)" @on-fetch="(data) => (categoriesOptions = data)"
@ -61,7 +54,9 @@ const redirectToItemTypeBasicData = (_, { id }) => {
<VnSelect <VnSelect
v-model="data.workerFk" v-model="data.workerFk"
:label="t('itemType.shared.worker')" :label="t('itemType.shared.worker')"
:options="workersOptions" url="Workers"
sort-by="firstName ASC"
:fields="['id', 'firstName']"
option-value="id" option-value="id"
option-label="firstName" option-label="firstName"
hide-selected hide-selected

View File

@ -12,17 +12,10 @@ import VnSelect from 'src/components/common/VnSelect.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const workersOptions = ref([]);
const categoriesOptions = ref([]); const categoriesOptions = ref([]);
const temperaturesOptions = ref([]); const temperaturesOptions = ref([]);
</script> </script>
<template> <template>
<FetchData
url="Workers"
@on-fetch="(data) => (workersOptions = data)"
:filter="{ order: 'firstName ASC', fields: ['id', 'firstName'] }"
auto-load
/>
<FetchData <FetchData
url="ItemCategories" url="ItemCategories"
@on-fetch="(data) => (categoriesOptions = data)" @on-fetch="(data) => (categoriesOptions = data)"
@ -50,7 +43,9 @@ const temperaturesOptions = ref([]);
<VnSelect <VnSelect
v-model="data.workerFk" v-model="data.workerFk"
:label="t('shared.worker')" :label="t('shared.worker')"
:options="workersOptions" url="Workers"
sort-by="firstName ASC"
:fields="['id', 'firstName']"
option-value="id" option-value="id"
option-label="firstName" option-label="firstName"
hide-selected hide-selected

View File

@ -25,7 +25,11 @@ const stateOpts = ref([]);
const zoneOpts = ref([]); const zoneOpts = ref([]);
const visibleColumns = ref([]); const visibleColumns = ref([]);
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const [from, to] = dateRange(Date.vnNew()); const from = Date.vnNew();
from.setHours(0, 0, 0, 0);
const to = new Date(from.getTime());
to.setDate(to.getDate() + 1);
to.setHours(23, 59, 59, 999);
function exprBuilder(param, value) { function exprBuilder(param, value) {
switch (param) { switch (param) {

View File

@ -126,12 +126,6 @@ onMounted(async () => {
</script> </script>
<template> <template>
<FetchData
url="addresses"
@on-fetch="(data) => (clientOptions = data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'], order: 'id' }"
auto-load
/>
<FormModelPopup <FormModelPopup
url-create="Orders/new" url-create="Orders/new"
:title="t('Create Order')" :title="t('Create Order')"
@ -165,13 +159,16 @@ onMounted(async () => {
</template> </template>
</VnSelect> </VnSelect>
<VnSelect <VnSelect
ref="addressRef"
:label="t('order.form.addressFk')" :label="t('order.form.addressFk')"
v-model="data.addressId" v-model="data.addressId"
:options="addressList" url="addresses"
:fields="['id', 'nickname', 'defaultAddressFk', 'street', 'city']"
sort-by="id"
option-value="id" option-value="id"
option-label="street" option-label="street"
hide-selected hide-selected
:disable="!addressList?.length" :disable="!$refs.addressRef?.length"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">

View File

@ -17,10 +17,6 @@ const props = defineProps({
const agencyFilter = { fields: ['id', 'name'] }; const agencyFilter = { fields: ['id', 'name'] };
const agencyList = ref(null); const agencyList = ref(null);
const salesPersonFilter = {
fields: ['id', 'nickname'],
};
const salesPersonList = ref(null);
const sourceList = ref([]); const sourceList = ref([]);
</script> </script>
@ -32,14 +28,6 @@ const sourceList = ref([]);
auto-load auto-load
@on-fetch="(data) => (agencyList = data)" @on-fetch="(data) => (agencyList = data)"
/> />
<FetchData
url="Workers/search"
:filter="salesPersonFilter"
sort-by="nickname ASC"
@on-fetch="(data) => (salesPersonList = data)"
:params="{ departmentCodes: ['VT'] }"
auto-load
/>
<FetchData <FetchData
url="Orders/getSourceValues" url="Orders/getSourceValues"
:filter="{ fields: ['value'] }" :filter="{ fields: ['value'] }"

View File

@ -1,7 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
@ -14,22 +12,9 @@ const props = defineProps({
}); });
const emit = defineEmits(['search']); const emit = defineEmits(['search']);
const workers = ref();
function setWorkers(data) {
workers.value = data;
}
</script> </script>
<template> <template>
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
sort-by="firstName ASC"
@on-fetch="setWorkers"
auto-load
/>
<VnFilterPanel <VnFilterPanel
:data-key="props.dataKey" :data-key="props.dataKey"
:search-button="true" :search-button="true"

View File

@ -99,7 +99,11 @@ const setWireTransfer = async () => {
:key="index" :key="index"
class="row q-gutter-md q-mb-md" class="row q-gutter-md q-mb-md"
> >
<VnInput :label="t('supplier.accounts.iban')" v-model="row.iban"> <VnInput
:label="t('supplier.accounts.iban')"
v-model="row.iban"
:required="true"
>
<template #append> <template #append>
<QIcon name="info" class="cursor-info"> <QIcon name="info" class="cursor-info">
<QTooltip>{{ t('components.iban_tooltip') }}</QTooltip> <QTooltip>{{ t('components.iban_tooltip') }}</QTooltip>
@ -113,6 +117,7 @@ const setWireTransfer = async () => {
option-label="bic" option-label="bic"
option-value="id" option-value="id"
hide-selected hide-selected
:required="true"
:roles-allowed-to-create="['financial']" :roles-allowed-to-create="['financial']"
> >
<template #form> <template #form>

View File

@ -3,6 +3,8 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import SupplierListFilter from './SupplierListFilter.vue';
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
@ -93,6 +95,11 @@ const columns = computed(() => [
<template> <template>
<VnSearchbar data-key="SuppliersList" :limit="20" :label="t('Search suppliers')" /> <VnSearchbar data-key="SuppliersList" :limit="20" :label="t('Search suppliers')" />
<RightMenu>
<template #right-panel>
<SupplierListFilter data-key="SuppliersList" />
</template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="SuppliersList" data-key="SuppliersList"
@ -109,6 +116,7 @@ const columns = computed(() => [
return data; return data;
}, },
}" }"
:right-search="false"
order="id ASC" order="id ASC"
:columns="columns" :columns="columns"
auto-load auto-load

View File

@ -27,7 +27,6 @@ const initialFormState = reactive({
warehouseId: user.value.warehouseFk, warehouseId: user.value.warehouseFk,
landed: null, landed: null,
}); });
const clientOptions = ref([]);
const agenciesOptions = ref([]); const agenciesOptions = ref([]);
const addressesOptions = ref([]); const addressesOptions = ref([]);
const warehousesOptions = ref([]); const warehousesOptions = ref([]);
@ -111,12 +110,6 @@ const redirectToTicketList = (_, { id }) => {
</script> </script>
<template> <template>
<FetchData
url="Clients"
@on-fetch="(data) => (clientOptions = data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'], order: 'id' }"
auto-load
/>
<FetchData <FetchData
url="Warehouses" url="Warehouses"
@on-fetch="(data) => (warehousesOptions = data)" @on-fetch="(data) => (warehousesOptions = data)"
@ -137,7 +130,9 @@ const redirectToTicketList = (_, { id }) => {
<VnSelect <VnSelect
:label="t('ticket.create.client')" :label="t('ticket.create.client')"
v-model="data.clientId" v-model="data.clientId"
:options="clientOptions" url="Clients"
:fields="['id', 'name', 'defaultAddressFk']"
sort-by="id"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected

View File

@ -6,6 +6,7 @@ import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -15,7 +16,6 @@ const props = defineProps({
}, },
}); });
const workers = ref([]);
const provinces = ref([]); const provinces = ref([]);
const states = ref([]); const states = ref([]);
const agencies = ref([]); const agencies = ref([]);
@ -27,12 +27,6 @@ const warehouses = ref([]);
<FetchData url="States" @on-fetch="(data) => (states = data)" auto-load /> <FetchData url="States" @on-fetch="(data) => (states = data)" auto-load />
<FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load />
<FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load />
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table"> <VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table">
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
@ -66,23 +60,19 @@ const warehouses = ref([]);
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection v-if="!workers"> <QItemSection>
<QSkeleton type="QInput" class="full-width" /> <VnSelect
</QItemSection>
<QItemSection v-if="workers">
<QSelect
:label="t('Salesperson')" :label="t('Salesperson')"
v-model="params.salesPersonFk" v-model="params.salesPersonFk"
:options="workers" url="Workers/activeWithInheritedRole"
:where="{ role: 'salesPerson' }"
option-value="id" option-value="id"
option-label="name" option-label="firstName"
emit-value :use-like="false"
map-options sort-by="firstName ASC"
use-input
dense dense
outlined outlined
rounded rounded
:input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -3,9 +3,11 @@ import WorkerEventLabel from 'pages/Worker/Card/WorkerEventLabel.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import useNotify from 'src/composables/useNotify';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { computed, ref } from 'vue'; import { computed, ref, watch } from 'vue';
import { toDateFormat } from '../../../filters/date'; import { toDateFormat } from '../../../filters/date';
const { notify } = useNotify();
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -33,6 +35,13 @@ const props = defineProps({
}, },
}); });
watch(
() => props.contractHolidays,
(newValue) => {
checkHolidays(newValue);
},
{ deep: true, immediate: true }
);
const emit = defineEmits(['update:businessFk', 'update:year', 'update:absenceType']); const emit = defineEmits(['update:businessFk', 'update:year', 'update:absenceType']);
const selectedBusinessFk = computed({ const selectedBusinessFk = computed({
@ -53,12 +62,22 @@ const selectedAbsenceType = computed({
}, },
}); });
const generateYears = () => { function generateYears() {
const now = Date.vnNew(); const now = Date.vnNew();
const maxYear = now.getFullYear() + 1; const maxYear = now.getFullYear() + 1;
return Array.from({ length: 5 }, (_, i) => String(maxYear - i)) || []; return Array.from({ length: 5 }, (_, i) => String(maxYear - i)) || [];
}; }
function checkHolidays(contractHolidays) {
if (!contractHolidays) return;
if (
contractHolidays.holidaysEnjoyed > contractHolidays.totalHolidays ||
contractHolidays.hoursEnjoyed > contractHolidays.totalHours
) {
notify(t('Vacation days have been exceeded'), 'negative');
}
}
const absenceTypeList = ref([]); const absenceTypeList = ref([]);
const contractList = ref([]); const contractList = ref([]);

View File

@ -29,6 +29,7 @@ const postcodesOptions = ref([]);
const user = useState().getUser(); const user = useState().getUser();
const defaultPayMethod = ref(); const defaultPayMethod = ref();
const bankEntitiesRef = ref();
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -118,6 +119,12 @@ onBeforeMount(async () => {
).data?.payMethodFk; ).data?.payMethodFk;
}); });
async function handleNewBankEntity(data, resp) {
await bankEntitiesRef.value.fetch();
data.bankEntityFk = resp.id;
bankEntitiesOptions.value.push(resp);
}
function handleLocation(data, location) { function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {}; const { town, code, provinceFk, countryFk } = location ?? {};
data.postcode = code; data.postcode = code;
@ -177,6 +184,7 @@ async function autofillBic(worker) {
auto-load auto-load
/> />
<FetchData <FetchData
ref="bankEntitiesRef"
url="BankEntities" url="BankEntities"
@on-fetch="(data) => (bankEntitiesOptions = data)" @on-fetch="(data) => (bankEntitiesOptions = data)"
auto-load auto-load
@ -344,7 +352,9 @@ async function autofillBic(worker) {
> >
<template #form> <template #form>
<CreateBankEntityForm <CreateBankEntityForm
@on-data-saved="(data) => bankEntitiesOptions.push(data)" @on-data-saved="
(_, resp) => handleNewBankEntity(data, resp)
"
/> />
</template> </template>
<template #option="scope"> <template #option="scope">

View File

@ -94,9 +94,9 @@ watch(
url="Postcodes/location" url="Postcodes/location"
:fields="['geoFk', 'code', 'townFk', 'countryFk']" :fields="['geoFk', 'code', 'townFk', 'countryFk']"
sort-by="code, townFk" sort-by="code, townFk"
option-value="code" option-value="geoFk"
option-label="code" option-label="code"
option-filter="code" :filter-options="['code', 'geoFk']"
hide-selected hide-selected
dense dense
outlined outlined

View File

@ -33,7 +33,8 @@ describe('ClaimDevelopment', () => {
cy.saveCard(); cy.saveCard();
}); });
it('should add and remove new line', () => { // TODO: #8112
xit('should add and remove new line', () => {
cy.wait(['@workers', '@workers']); cy.wait(['@workers', '@workers']);
cy.addCard(); cy.addCard();

View File

@ -1,4 +1,6 @@
describe('ClaimNotes', () => { describe('ClaimNotes', () => {
const saveBtn = '.q-field__append > .q-btn > .q-btn__content > .q-icon';
const firstNote = '.q-infinite-scroll :nth-child(1) > .q-card__section--vert';
beforeEach(() => { beforeEach(() => {
cy.login('developer'); cy.login('developer');
cy.visit(`/#/claim/${2}/notes`); cy.visit(`/#/claim/${2}/notes`);
@ -7,7 +9,7 @@ describe('ClaimNotes', () => {
it('should add a new note', () => { it('should add a new note', () => {
const message = 'This is a new message.'; const message = 'This is a new message.';
cy.get('.q-textarea').type(message); cy.get('.q-textarea').type(message);
cy.get('.q-field__append > .q-btn > .q-btn__content > .q-icon').click(); //save cy.get(saveBtn).click();
cy.get(':nth-child(1) > .q-card__section--vert').should('have.text', message); cy.get(firstNote).should('have.text', message);
}); });
}); });