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

This commit is contained in:
Jon Elias 2024-10-18 11:20:43 +00:00
commit 64c0496f7a
41 changed files with 614 additions and 248 deletions

View File

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

View File

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

View File

@ -134,6 +134,7 @@ const splittedColumns = ref({ columns: [] });
const columnsVisibilitySkipped = ref();
const createForm = ref();
const tableFilterRef = ref([]);
const tableRef = ref();
const tableModes = [
{
@ -321,6 +322,13 @@ function handleOnDataSaved(_) {
if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value });
else $props.create.onDataSaved(_);
}
function handleScroll() {
const tMiddle = tableRef.value.$el.querySelector('.q-table__middle');
const { scrollHeight, scrollTop, clientHeight } = tMiddle;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40;
if (isAtBottom) CrudModelRef.value.vnPaginateRef.paginate();
}
</script>
<template>
<QDrawer
@ -405,6 +413,7 @@ function handleOnDataSaved(_) {
</template>
<template #body="{ rows }">
<QTable
ref="tableRef"
v-bind="table"
class="vnTable"
:columns="splittedColumns.columns"
@ -416,12 +425,7 @@ function handleOnDataSaved(_) {
flat
:style="isTableMode && `max-height: ${tableHeight}`"
:virtual-scroll="isTableMode"
@virtual-scroll="
(event) =>
event.index > rows.length - 2 &&
($props.crudModel?.paginate ?? true) &&
CrudModelRef.vnPaginateRef.paginate()
"
@virtual-scroll="handleScroll"
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
@update:selected="emit('update:selected', $event)"
>

View File

@ -0,0 +1,29 @@
<script setup>
const model = defineModel({ type: [String, Number], required: true });
</script>
<template>
<QDate v-model="model" :today-btn="true" :options="$attrs.options" />
</template>
<style lang="scss" scoped>
.q-date {
width: 245px;
min-width: unset;
:deep(.q-date__calendar) {
padding-bottom: 0;
}
:deep(.q-date__view) {
min-height: 245px;
padding: 8px;
}
:deep(.q-date__calendar-days-container) {
min-height: 160px;
height: unset;
}
:deep(.q-date__header) {
padding: 2px 2px 5px 12px;
height: 60px;
}
}
</style>

View File

@ -3,6 +3,7 @@ import { onMounted, watch, computed, ref } from 'vue';
import { date } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useAttrs } from 'vue';
import VnDate from './VnDate.vue';
const model = defineModel({ type: [String, Date] });
const $props = defineProps({
@ -87,6 +88,11 @@ const styleAttrs = computed(() => {
}
: {};
});
const manageDate = (date) => {
formattedDate.value = date;
isPopupOpen.value = false;
};
</script>
<template>
@ -129,6 +135,7 @@ const styleAttrs = computed(() => {
/>
</template>
<QMenu
v-if="$q.screen.gt.xs"
transition-show="scale"
transition-hide="scale"
v-model="isPopupOpen"
@ -137,19 +144,11 @@ const styleAttrs = computed(() => {
:no-focus="true"
:no-parent-event="true"
>
<QDate
v-model="popupDate"
:landscape="true"
:today-btn="true"
:options="$attrs.options"
@update:model-value="
(date) => {
formattedDate = date;
isPopupOpen = false;
}
"
/>
<VnDate v-model="popupDate" @update:model-value="manageDate" />
</QMenu>
<QDialog v-else v-model="isPopupOpen">
<VnDate v-model="popupDate" @update:model-value="manageDate" />
</QDialog>
</QInput>
</div>
</template>

View File

@ -3,6 +3,8 @@ import { computed, ref, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import { date } from 'quasar';
import { useValidator } from 'src/composables/useValidator';
import VnTime from './VnTime.vue';
const { validations } = useValidator();
const $attrs = useAttrs();
const model = defineModel({ type: String });
@ -107,6 +109,7 @@ function dateToTime(newDate) {
/>
</template>
<QMenu
v-if="$q.screen.gt.xs"
transition-show="scale"
transition-hide="scale"
v-model="isPopupOpen"
@ -115,8 +118,11 @@ function dateToTime(newDate) {
:no-focus="true"
:no-parent-event="true"
>
<QTime v-model="formattedTime" mask="HH:mm" landscape now-btn />
<VnTime v-model="formattedTime" />
</QMenu>
<QDialog v-else v-model="isPopupOpen">
<VnTime v-model="formattedTime" />
</QDialog>
</QInput>
</div>
</template>

View File

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

View File

@ -0,0 +1,16 @@
<script setup>
const model = defineModel({ type: [String, Number], required: true });
</script>
<template>
<QTime v-model="model" now-btn mask="HH:mm" />
</template>
<style lang="scss" scoped>
.q-time {
width: 230px;
min-width: unset;
:deep(.q-time__header) {
min-height: unset;
height: 50px;
}
}
</style>

View File

@ -58,7 +58,7 @@ defineExpose({
:class="{ zoomIn: zoom }"
:src="getUrl()"
v-bind="$attrs"
@click.stop="show = $props.zoom ? true : false"
@click.stop="show = $props.zoom"
spinner-color="primary"
/>
<QDialog v-if="$props.zoom" v-model="show">

View File

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

View File

@ -277,6 +277,7 @@ globals:
medical: Mutual
RouteExtendedList: Router
wasteRecalc: Waste recaclulate
operator: Operator
supplier: Supplier
created: Created
worker: Worker
@ -743,6 +744,7 @@ worker:
locker: Locker
balance: Balance
medical: Medical
operator: Operator
list:
name: Name
email: Email
@ -840,6 +842,18 @@ worker:
debit: Debt
credit: Have
concept: Concept
operator:
numberOfWagons: Number of wagons
train: Train
itemPackingType: Item packing type
warehouse: Warehouse
sector: Sector
labeler: Printer
linesLimit: Lines limit
volumeLimit: Volume limit
sizeLimit: Size limit
isOnReservationMode: Reservation mode
machine: Machine
wagon:
pageTitles:
wagons: Wagons

View File

@ -281,6 +281,7 @@ globals:
serial: Facturas por serie
medical: Mutua
wasteRecalc: Recalcular mermas
operator: Operario
supplier: Proveedor
created: Fecha creación
worker: Trabajador
@ -750,6 +751,7 @@ worker:
balance: Balance
formation: Formación
medical: Mutua
operator: Operario
list:
name: Nombre
email: Email
@ -838,6 +840,19 @@ worker:
debit: Debe
credit: Haber
concept: Concepto
operator:
numberOfWagons: Número de vagones
train: tren
itemPackingType: Tipo de embalaje
warehouse: Almacén
sector: Sector
labeler: Impresora
linesLimit: Líneas límite
volumeLimit: Volumen límite
sizeLimit: Tamaño límite
isOnReservationMode: Modo de reserva
machine: Máquina
wagon:
pageTitles:
wagons: Vagones

View File

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

View File

@ -25,19 +25,31 @@ const { notify } = useNotify();
const { t } = useI18n();
const newObservation = ref(null);
const obsId = ref(null);
const onSubmit = async () => {
try {
const data = $props.clients.map((item) => {
return { clientFk: item.clientFk, text: newObservation.value };
});
await axios.post('ClientObservations', data);
if (!obsId.value)
obsId.value = (
await axios.get('ObservationTypes/findOne', {
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,
observation: newObservation.value,
};
await axios.post('Defaulters/observationEmail', payload);
await axios.post('Defaulters/observationEmail', bodyObsMail);
await $props.promise();

View File

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

View File

@ -6,7 +6,7 @@ import { useRoute, useRouter } from 'vue-router';
import { date } from 'quasar';
import { toDateFormat } from 'src/filters/date.js';
import { toCurrency } from 'src/filters';
import { dashIfEmpty, toCurrency } from 'src/filters';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import TicketSummary from 'src/pages/Ticket/Card/TicketSummary.vue';
@ -32,6 +32,16 @@ const filter = {
},
{ relation: 'invoiceOut', scope: { fields: ['id'] } },
{ relation: 'agencyMode', scope: { fields: ['name'] } },
{
relation: 'ticketSales',
scope: {
fields: ['id', 'concept', 'itemFk'],
include: { relation: 'item' },
scope: {
fields: ['id', 'name', 'itemPackingTypeFk'],
},
},
},
],
where: { clientFk: route.params.id },
order: ['shipped DESC', 'id'],
@ -87,7 +97,12 @@ const columns = computed(() => [
label: t('Total'),
name: 'total',
},
{
align: 'left',
name: 'itemPackingTypeFk',
label: t('ticketSale.packaging'),
format: (row) => getItemPackagingType(row),
},
{
align: 'right',
label: '',
@ -135,6 +150,15 @@ const setShippedColor = (date) => {
if (difference == 0) return 'warning';
if (difference < 0) return 'success';
};
const getItemPackagingType = (row) => {
const packagingType = row?.ticketSales
.map((sale) => sale.item?.itemPackingTypeFk || '-')
.filter((value) => value !== '-')
.join(', ');
return dashIfEmpty(packagingType);
};
</script>
<template>

View File

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

View File

@ -15,25 +15,10 @@ const props = defineProps({
required: true,
},
});
const workers = ref();
const workersCopy = ref();
const states = ref();
function setWorkers(data) {
workers.value = data;
workersCopy.value = data;
}
</script>
<template>
<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">
<template #tags="{ tag, formatFn }">
<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 { QBtn } from 'quasar';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import InvoiceOutFilter from './InvoiceOutFilter.vue';
const { t } = useI18n();
const stateStore = useStateStore();
@ -182,6 +184,11 @@ watchEffect(selectedRows);
:label="t('searchInvoice')"
data-key="invoiceOut"
/>
<RightMenu>
<template #right-panel>
<InvoiceOutFilter data-key="invoiceOut" />
</template>
</RightMenu>
<VnSubToolbar>
<template #st-actions>
<QBtn
@ -206,6 +213,7 @@ watchEffect(selectedRows);
active: true,
},
}"
:right-search="false"
v-model:selected="selectedRows"
order="id DESC"
:columns="columns"

View File

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

View File

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

View File

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

View File

@ -25,7 +25,11 @@ const stateOpts = ref([]);
const zoneOpts = ref([]);
const visibleColumns = ref([]);
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) {
switch (param) {

View File

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

View File

@ -17,10 +17,6 @@ const props = defineProps({
const agencyFilter = { fields: ['id', 'name'] };
const agencyList = ref(null);
const salesPersonFilter = {
fields: ['id', 'nickname'],
};
const salesPersonList = ref(null);
const sourceList = ref([]);
</script>
@ -32,14 +28,6 @@ const sourceList = ref([]);
auto-load
@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
url="Orders/getSourceValues"
:filter="{ fields: ['value'] }"

View File

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

View File

@ -99,7 +99,11 @@ const setWireTransfer = async () => {
:key="index"
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>
<QIcon name="info" class="cursor-info">
<QTooltip>{{ t('components.iban_tooltip') }}</QTooltip>
@ -113,6 +117,7 @@ const setWireTransfer = async () => {
option-label="bic"
option-value="id"
hide-selected
:required="true"
:roles-allowed-to-create="['financial']"
>
<template #form>

View File

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

View File

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

View File

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

View File

@ -95,6 +95,7 @@ const columns = computed(() => [
columnField: {
component: null,
},
columnClass: 'expand',
format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson),
},
{
@ -153,11 +154,6 @@ const columns = computed(() => [
},
columnClass: 'expand',
},
{
align: 'left',
name: 'refFk',
label: t('ticketList.ref'),
},
{
align: 'left',
name: 'zoneFk',
@ -191,6 +187,12 @@ const columns = computed(() => [
},
format: (row) => toCurrency(row.totalWithVat),
},
{
align: 'left',
name: 'packing',
label: t('ticketSale.packaging'),
format: (row, dashIfEmpty) => dashIfEmpty(row.packing),
},
{
align: 'right',
name: 'tableActions',
@ -548,7 +550,7 @@ function setReference(data) {
</template>
<template #column-salesPersonFk="{ row }">
<span class="link" @click.stop>
{{ row.salesPerson }}
{{ dashIfEmpty(row.salesPerson) }}
<CustomerDescriptorProxy :id="row.salesPersonFk" />
</span>
</template>
@ -577,16 +579,16 @@ function setReference(data) {
{{ row.state }}
</QChip>
</span>
<span v-else-if="row.state === 'Entregado'">
<span class="link" @click.stop>
{{ row.refFk }}
<InvoiceOutDescriptorProxy :id="row.invoiceOutId" />
</span>
</span>
<span v-else>
{{ row.state }}
</span>
</template>
<template #column-refFk="{ row }">
<span class="link" @click.stop>
{{ dashIfEmpty(row.refFk) }}
<InvoiceOutDescriptorProxy :id="row.invoiceOutId" />
</span>
</template>
<template #column-zoneFk="{ row }">
<span class="link" @click.stop>
{{ dashIfEmpty(row.zoneName) }}

View File

@ -3,9 +3,11 @@ import WorkerEventLabel from 'pages/Worker/Card/WorkerEventLabel.vue';
import FetchData from 'components/FetchData.vue';
import { useI18n } from 'vue-i18n';
import VnSelect from 'components/common/VnSelect.vue';
import useNotify from 'src/composables/useNotify';
import { useRoute } from 'vue-router';
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import { toDateFormat } from '../../../filters/date';
const { notify } = useNotify();
const { t } = useI18n();
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 selectedBusinessFk = computed({
@ -53,12 +62,22 @@ const selectedAbsenceType = computed({
},
});
const generateYears = () => {
function generateYears() {
const now = Date.vnNew();
const maxYear = now.getFullYear() + 1;
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 contractList = ref([]);

View File

@ -0,0 +1,204 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { ref, computed } from 'vue';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import CrudModel from 'components/CrudModel.vue';
import axios from 'axios';
const { t } = useI18n();
const crudModelRef = ref();
const warehousesData = ref([]);
const itemPackingTypesData = ref([]);
const sectorsData = ref([]);
const trainsData = ref([]);
const machinesData = ref([]);
const route = useRoute();
const routeId = computed(() => route.params.id);
const initialData = computed(() => {
return {
workerFk: routeId.value,
numberOfWagons: 2,
trainFk: 1,
itemPackingTypeFk: 'H',
warehouseFk: 60,
sectorFk: null,
labelerFk: null,
linesLimit: 20,
volumenLimit: 0.5,
sizeLimit: null,
isOnReservationMode: 0,
machineFk: null,
};
});
async function insert() {
await axios.post('Operators', initialData.value);
crudModelRef.value.reload();
}
</script>
<template>
<QPage class="column items-center q-pa-md centerCard">
<FetchData url="Trains" @on-fetch="(data) => (trainsData = data)" auto-load />
<FetchData
url="ItemPackingTypes"
@on-fetch="(data) => (itemPackingTypesData = data)"
auto-load
/>
<FetchData
url="Warehouses"
@on-fetch="(data) => (warehousesData = data)"
auto-load
/>
<FetchData url="Printers" @on-fetch="(data) => (PrintersData = data)" auto-load />
<FetchData url="Sectors" @on-fetch="(data) => (sectorsData = data)" auto-load />
<FetchData url="Machines" @on-fetch="(data) => (machinesData = data)" auto-load />
<CrudModel
data-key="workerOperator"
url="Operators"
primary-key="workerFk"
:filter="{ where: { workerFk: route.params.id } }"
:data-required="{ workerFk: route.params.id }"
ref="crudModelRef"
search-url="operator"
auto-load
>
<template #body="{ rows }">
<div v-if="rows.length">
<QCard
flat
bordered
:key="row.$index"
v-for="row of rows"
class="card q-px-md q-mb-sm container"
>
<VnRow>
<VnInput
:label="t('worker.operator.numberOfWagons')"
v-model="row.numberOfWagons"
/>
<VnSelect
:label="t('worker.operator.train')"
:options="trainsData"
hide-selected
v-model="row.trainFk"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('worker.operator.itemPackingType')"
:options="itemPackingTypesData"
hide-selected
option-label="code"
option-value="code"
v-model="row.itemPackingTypeFk"
/>
<VnSelect
:label="t('worker.operator.warehouse')"
:options="warehousesData"
hide-selected
v-model="row.warehouseFk"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('worker.operator.sector')"
:options="sectorsData"
hide-selected
option-label="description"
v-model="row.sectorFk"
/>
<VnSelect
:label="t('worker.operator.labeler')"
:options="PrintersData"
hide-selected
option-label="name"
v-model="row.labelerFk"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
>ID: {{ scope.opt?.id }}</QItemLabel
>
<QItemLabel caption>
{{ scope.opt?.id }},
{{ scope.opt?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</VnRow>
<VnRow>
<VnInput
:label="t('worker.operator.linesLimit')"
v-model="row.linesLimit"
lazy-rules
/>
<VnInput
:label="t('worker.operator.volumeLimit')"
v-model="row.volumeLimit"
lazy-rules
/>
</VnRow>
<VnRow>
<VnInput
:label="t('worker.operator.sizeLimit')"
v-model="row.sizeLimit"
lazy-rules
/>
<VnInput
:label="t('worker.operator.isOnReservationMode')"
v-model="row.isOnReservationMode"
lazy-rules
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('worker.operator.machine')"
:options="machinesData"
hide-selected
option-label="plate"
v-model="row.machineFk"
/>
</VnRow>
</QCard>
</div>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
fab
color="primary"
icon="add"
@click="insert()"
v-if="!crudModelRef?.formData?.length"
/>
</QPageSticky>
</QPage>
</template>
<style lang="scss" scoped>
.btn-delete {
max-width: 4%;
margin-top: 30px;
}
</style>
<i18n>
es:
Model: Modelo
Serial number: Número de serie
Current SIM: SIM actual
Add new device: Añadir nuevo dispositivo
PDA deallocated: PDA desasignada
Remove PDA: Eliminar PDA
Do you want to remove this PDA?: ¿Desea eliminar este PDA?
You can only have one PDA: Solo puedes tener un PDA si no eres autonomo
This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario
</i18n>

View File

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

View File

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

View File

@ -27,6 +27,7 @@ export default {
'WorkerBalance',
'WorkerFormation',
'WorkerMedical',
'WorkerOperator',
],
},
children: [
@ -208,6 +209,15 @@ export default {
},
component: () => import('src/pages/Worker/Card/WorkerMedical.vue'),
},
{
name: 'WorkerOperator',
path: 'operator',
meta: {
title: 'operator',
icon: 'person',
},
component: () => import('src/pages/Worker/Card/WorkerOperator.vue'),
},
],
},
],

View File

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

View File

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

View File

@ -11,7 +11,7 @@ describe('EntryMy when is supplier', () => {
it('should open buyLabel when is supplier', () => {
cy.get(
'[to="/null/2"] > .q-card > .column > .q-btn > .q-btn__content > .q-icon'
'[to="/null/3"] > .q-card > .column > .q-btn > .q-btn__content > .q-icon'
).click();
cy.get('.q-card__actions > .q-btn').click();
cy.window().its('open').should('be.called');

View File

@ -1,7 +1,8 @@
/// <reference types="cypress" />
describe('Ticket descriptor', () => {
const toCloneOpt = '[role="menu"] .q-list > :nth-child(8)';
const setWeightOpt = '[role="menu"] .q-list > :nth-child(13)';
const listItem = '[role="menu"] .q-list .q-item';
const toCloneOpt = 'To clone ticket';
const setWeightOpt = 'Set weight';
const warehouseValue = ':nth-child(1) > :nth-child(6) > .value > span';
const summaryHeader = '.summaryHeader > div';
const weight = 25;
@ -14,7 +15,7 @@ describe('Ticket descriptor', () => {
it('should clone the ticket without warehouse', () => {
cy.visit('/#/ticket/1/summary');
cy.openActionsDescriptor();
cy.get(toCloneOpt).click();
cy.contains(listItem, toCloneOpt).click();
cy.clickConfirm();
cy.get(warehouseValue).contains('Warehouse One');
cy.get(summaryHeader)
@ -28,7 +29,7 @@ describe('Ticket descriptor', () => {
it('should set the weight of the ticket', () => {
cy.visit('/#/ticket/10/summary');
cy.openActionsDescriptor();
cy.get(setWeightOpt).click();
cy.contains(listItem, setWeightOpt).click();
cy.intercept('POST', /\/api\/Tickets\/\d+\/setWeight/).as('weight');
cy.get('.q-dialog input').type(weight);
cy.clickConfirm();

View File

@ -64,7 +64,7 @@ describe('VnLocation', () => {
`${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(3) > .q-icon`
).click();
cy.get(
`#q-portal--dialog--4 > .q-dialog > ${createForm.prefix} > .vn-row > .q-select > ${createForm.sufix} > :nth-child(1) input`
`#q-portal--dialog--5 > .q-dialog > ${createForm.prefix} > .vn-row > .q-select > ${createForm.sufix} > :nth-child(1) input`
).should('have.value', province);
});
});
@ -133,6 +133,8 @@ describe('VnLocation', () => {
);
cy.get('.q-mt-lg > .q-btn--standard').click();
cy.get(`${createForm.prefix}`).should('not.exist');
cy.waitForElement('.q-form');
checkVnLocation(postCode, province);
});
it('Create city', () => {
@ -144,10 +146,12 @@ describe('VnLocation', () => {
cy.get(
`${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(2) > .q-icon`
).click();
cy.selectOption('#q-portal--dialog--2 .q-select', 'one');
cy.get('#q-portal--dialog--2 .q-input').type(province);
cy.get('#q-portal--dialog--2 .q-btn--standard').click();
cy.selectOption('#q-portal--dialog--3 .q-select', 'one');
cy.get('#q-portal--dialog--3 .q-input').type(province);
cy.get('#q-portal--dialog--3 .q-btn--standard').click();
cy.get('#q-portal--dialog--1 .q-btn--standard').click();
cy.waitForElement('.q-form');
checkVnLocation(postCode, province);
});