Merge branch '8944-FixedPriceChanges' of https://gitea.verdnatura.es/verdnatura/salix-front into 8944-FixedPriceChanges
gitea/salix-front/pipeline/pr-dev This commit is unstable Details

This commit is contained in:
Jon Elias 2025-05-06 13:49:32 +02:00
commit a71f383996
60 changed files with 1192 additions and 590 deletions

View File

@ -347,8 +347,8 @@ watch(formUrl, async () => {
<QBtnDropdown <QBtnDropdown
v-if="$props.goTo && $props.defaultSave" v-if="$props.goTo && $props.defaultSave"
@click="onSubmitAndGo" @click="onSubmitAndGo"
:label="tMobile('globals.saveAndContinue')" :label="tMobile('globals.saveAndContinue') + ' ' + t('globals.' + $props.goTo.split('/').pop())"
:title="t('globals.saveAndContinue')" :title="t('globals.saveAndContinue') + ' ' + t('globals.' + $props.goTo.split('/').pop())"
:disable="!hasChanges" :disable="!hasChanges"
color="primary" color="primary"
icon="save" icon="save"

View File

@ -380,8 +380,8 @@ defineExpose({
data-cy="saveAndContinueDefaultBtn" data-cy="saveAndContinueDefaultBtn"
v-if="$props.goTo" v-if="$props.goTo"
@click="saveAndGo" @click="saveAndGo"
:label="tMobile('globals.saveAndContinue')" :label="tMobile('globals.saveAndContinue') + ' ' + t('globals.' + $props.goTo.split('/').pop())"
:title="t('globals.saveAndContinue')" :title="t('globals.saveAndContinue') + ' ' + t('globals.' + $props.goTo.split('/').pop())"
:disable="!hasChanges" :disable="!hasChanges"
color="primary" color="primary"
icon="save" icon="save"

View File

@ -34,7 +34,8 @@ import VnTableFilter from './VnTableFilter.vue';
import { getColAlign } from 'src/composables/getColAlign'; import { getColAlign } from 'src/composables/getColAlign';
import RightMenu from '../common/RightMenu.vue'; import RightMenu from '../common/RightMenu.vue';
import VnScroll from '../common/VnScroll.vue'; import VnScroll from '../common/VnScroll.vue';
import VnMultiCheck from '../common/VnMultiCheck.vue'; import VnCheckboxMenu from '../common/VnCheckboxMenu.vue';
import VnCheckbox from '../common/VnCheckbox.vue';
const arrayData = useArrayData(useAttrs()['data-key']); const arrayData = useArrayData(useAttrs()['data-key']);
const $props = defineProps({ const $props = defineProps({
@ -332,6 +333,7 @@ function stopEventPropagation(event) {
function reload(params) { function reload(params) {
selected.value = []; selected.value = [];
selectAll.value = false;
CrudModelRef.value.reload(params); CrudModelRef.value.reload(params);
} }
@ -644,21 +646,15 @@ const rowCtrlClickFunction = computed(() => {
}; };
return () => {}; return () => {};
}); });
const handleMultiCheck = (value) => { const handleHeaderSelection = (evt, data) => {
if (value) { if (evt === 'updateSelected' && selectAll.value) {
selected.value = tableRef.value.rows; selected.value = tableRef.value.rows;
} else { } else if (evt === 'selectAll') {
selected.value = [];
}
emit('update:selected', selected.value);
};
const handleSelectedAll = (data) => {
if (data) {
selected.value = data; selected.value = data;
} else { } else {
selected.value = []; selected.value = [];
} }
emit('update:selected', selected.value); emit('update:selected', selected.value);
}; };
</script> </script>
@ -686,7 +682,13 @@ const handleSelectedAll = (data) => {
:class="$attrs['class'] ?? 'q-px-md'" :class="$attrs['class'] ?? 'q-px-md'"
:limit="$attrs['limit'] ?? 100" :limit="$attrs['limit'] ?? 100"
ref="CrudModelRef" ref="CrudModelRef"
@on-fetch="(...args) => emit('onFetch', ...args)" @on-fetch="
(...args) => {
selectAll = false;
selected = [];
emit('onFetch', ...args);
}
"
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll="isTableMode" :disable-infinite-scroll="isTableMode"
:before-save-fn="removeTextValue" :before-save-fn="removeTextValue"
@ -724,14 +726,23 @@ const handleSelectedAll = (data) => {
:data-cy :data-cy
> >
<template #header-selection> <template #header-selection>
<VnMultiCheck <div class="flex items-center no-wrap" style="display: flex">
<VnCheckbox
v-model="selectAll"
@click="handleHeaderSelection('updateSelected', $event)"
/>
<VnCheckboxMenu
v-if="selectAll && $props.multiCheck.expand"
:searchUrl="searchUrl" :searchUrl="searchUrl"
:expand="$props.multiCheck.expand"
v-model="selectAll" v-model="selectAll"
:url="$attrs['url']" :url="$attrs['url']"
@update:selected="handleMultiCheck" @update:selected="
@select:all="handleSelectedAll" handleHeaderSelection('updateSelected', $event)
></VnMultiCheck> "
@select:all="handleHeaderSelection('selectAll', $event)"
/>
</div>
</template> </template>
<template #top-left v-if="!$props.withoutHeader"> <template #top-left v-if="!$props.withoutHeader">

View File

@ -0,0 +1,104 @@
<script setup>
import { ref } from 'vue';
import VnCheckbox from './VnCheckbox.vue';
import axios from 'axios';
import { toRaw } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
const route = useRoute();
const { t } = useI18n();
const model = defineModel({ type: [Boolean] });
const props = defineProps({
url: {
type: String,
required: true,
},
searchUrl: {
type: [String, Boolean],
default: 'table',
},
});
const menuRef = ref(null);
const errorMessage = ref(null);
const rows = ref(0);
const onClick = async () => {
errorMessage.value = null;
const { filter } = JSON.parse(route.query[props.searchUrl]);
filter.limit = 0;
const params = {
params: { filter: JSON.stringify(filter) },
};
try {
const { data } = axios.get(props.url, params);
rows.value = data;
} catch (error) {
const response = error.response;
if (response.data.error.name === 'UserError') {
errorMessage.value = t('tooManyResults');
} else {
errorMessage.value = response.data.error.message;
}
}
};
defineEmits(['update:selected', 'select:all']);
</script>
<template>
<QIcon
style="margin-left: -10px"
data-cy="btnMultiCheck"
name="expand_more"
@click="onClick"
class="cursor-pointer"
color="primary"
size="xs"
>
<QMenu
fit
anchor="bottom start"
self="top left"
ref="menuRef"
data-cy="menuMultiCheck"
>
<QList separator>
<QItem
data-cy="selectAll"
v-ripple
clickable
@click="
$refs.menuRef.hide();
$emit('select:all', toRaw(rows));
"
>
<QItemSection>
<QItemLabel>
<span v-text="t('Select all')" />
</QItemLabel>
<QItemLabel overline caption>
<span
v-if="errorMessage"
class="text-negative"
v-text="errorMessage"
/>
<span
v-else
v-text="t('records', { rows: rows.length ?? 0 })"
/>
</QItemLabel>
</QItemSection>
</QItem>
<slot name="more-options"></slot>
</QList>
</QMenu>
</QIcon>
</template>
<i18n lang="yml">
en:
tooManyResults: Too many results. Please narrow down your search.
records: '{rows} records'
es:
Select all: Seleccionar todo
tooManyResults: Demasiados registros. Restringe la búsqueda.
records: '{rows} registros'
</i18n>

View File

@ -1,80 +0,0 @@
<script setup>
import { ref } from 'vue';
import VnCheckbox from './VnCheckbox.vue';
import axios from 'axios';
import { toRaw } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
const route = useRoute();
const { t } = useI18n();
const model = defineModel({ type: [Boolean] });
const props = defineProps({
expand: {
type: Boolean,
default: false,
},
url: {
type: String,
default: null,
required: true,
},
searchUrl: {
type: [String, Boolean],
default: 'table',
},
});
const value = ref(false);
const rows = ref(0);
const onClick = () => {
if (value.value) {
const { filter } = JSON.parse(route.query[props.searchUrl]);
filter.limit = 0;
const params = {
params: { filter: JSON.stringify(filter) },
};
axios
.get(props.url, params)
.then(({ data }) => {
rows.value = data;
})
.catch(console.error);
}
};
defineEmits(['update:selected', 'select:all']);
</script>
<template>
<div style="display: flex">
<VnCheckbox v-model="value" @click="$emit('update:selected', value)" />
<QBtn
v-if="value && $props.expand"
flat
dense
icon="expand_more"
@click="onClick"
>
<QMenu anchor="bottom right" self="top right">
<QList>
<QItem v-ripple clickable @click="$emit('select:all', toRaw(rows))">
{{ t('Select all', { rows: rows.length }) }}
</QItem>
<slot name="more-options"></slot>
</QList>
</QMenu>
</QBtn>
</div>
</template>
<i18n lang="yml">
en:
Select all: 'Select all ({rows})'
fr:
Select all: 'Sélectionner tout ({rows})'
es:
Select all: 'Seleccionar todo ({rows})'
de:
Select all: 'Alle auswählen ({rows})'
it:
Select all: 'Seleziona tutto ({rows})'
pt:
Select all: 'Selecionar tudo ({rows})'
</i18n>

View File

@ -161,7 +161,7 @@ const arrayData = useArrayData(arrayDataKey, {
searchUrl: false, searchUrl: false,
mapKey: $attrs['map-key'], mapKey: $attrs['map-key'],
}); });
const isMenuOpened = ref(false);
const computedSortBy = computed(() => { const computedSortBy = computed(() => {
return $props.sortBy || $props.optionLabel + ' ASC'; return $props.sortBy || $props.optionLabel + ' ASC';
}); });
@ -186,7 +186,9 @@ onMounted(() => {
if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300); if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
}); });
const someIsLoading = computed(() => isLoading.value || !!arrayData?.isLoading?.value); const someIsLoading = computed(
() => (isLoading.value || !!arrayData?.isLoading?.value) && !isMenuOpened.value,
);
function findKeyInOptions() { function findKeyInOptions() {
if (!$props.options) return; if (!$props.options) return;
return filter($props.modelValue, $props.options)?.length; return filter($props.modelValue, $props.options)?.length;
@ -369,6 +371,8 @@ function getCaption(opt) {
:input-debounce="useURL ? '300' : '0'" :input-debounce="useURL ? '300' : '0'"
:loading="someIsLoading" :loading="someIsLoading"
@virtual-scroll="onScroll" @virtual-scroll="onScroll"
@popup-hide="isMenuOpened = false"
@popup-show="isMenuOpened = true"
@keydown="handleKeyDown" @keydown="handleKeyDown"
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'" :data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
:data-url="url" :data-url="url"

View File

@ -132,8 +132,7 @@ const card = toRef(props, 'item');
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
white-space: nowrap;
width: 192px;
p { p {
margin-bottom: 0; margin-bottom: 0;
} }

View File

@ -1,10 +1,11 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { ref, reactive, useAttrs, computed } from 'vue'; import { ref, reactive, useAttrs, computed, onMounted, nextTick, } from 'vue';
import { onBeforeRouteLeave } from 'vue-router'; import { onBeforeRouteLeave , useRouter, useRoute} from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore';
import { tMobile } from 'src/composables/tMobile';
import { toDateHourMin } from 'src/filters'; import { toDateHourMin } from 'src/filters';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
@ -33,10 +34,15 @@ const $props = defineProps({
addNote: { type: Boolean, default: false }, addNote: { type: Boolean, default: false },
selectType: { type: Boolean, default: false }, selectType: { type: Boolean, default: false },
justInput: { type: Boolean, default: false }, justInput: { type: Boolean, default: false },
goTo: { type: String, default: '', },
}); });
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const stateStore = useStateStore();
const router = useRouter();
const route = useRoute();
const componentIsRendered = ref(false);
const newNote = reactive({ text: null, observationTypeFk: null }); const newNote = reactive({ text: null, observationTypeFk: null });
const observationTypes = ref([]); const observationTypes = ref([]);
const vnPaginateRef = ref(); const vnPaginateRef = ref();
@ -45,6 +51,7 @@ const defaultObservationType = computed(() =>
observationTypes.value.find(ot => ot.code === 'salesPerson')?.id observationTypes.value.find(ot => ot.code === 'salesPerson')?.id
); );
let savedNote = false;
let originalText; let originalText;
function handleClick(e) { function handleClick(e) {
@ -68,6 +75,7 @@ async function insert() {
}; };
await axios.post($props.url, newBody); await axios.post($props.url, newBody);
await vnPaginateRef.value.fetch(); await vnPaginateRef.value.fetch();
savedNote = true;
} }
function confirmAndUpdate() { function confirmAndUpdate() {
@ -129,8 +137,29 @@ const handleObservationTypes = (data) => {
} }
}; };
onMounted(() => {
nextTick(() => (componentIsRendered.value = true));
});
async function saveAndGo() {
savedNote = false;
await insert();
await savedNote;
router.push({ path: $props.goTo });
}
</script> </script>
<template> <template>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && componentIsRendered && $props.goTo && !route.path.includes('summary')">
<QBtn
:label="tMobile('globals.saveAndContinue') + ' ' + t('globals.' + $props.goTo.split('/').pop())"
:title="t('globals.saveAndContinue') + ' ' + t('globals.' + $props.goTo.split('/').pop())"
color="primary"
icon="save"
@click="saveAndGo"
data-cy="saveContinueNoteButton"
/>
</Teleport>
<FetchData <FetchData
v-if="selectType" v-if="selectType"
url="ObservationTypes" url="ObservationTypes"
@ -176,7 +205,7 @@ const handleObservationTypes = (data) => {
:required="'required' in originalAttrs" :required="'required' in originalAttrs"
clearable clearable
> >
<template #append> <template #append v-if="!$props.goTo">
<QBtn <QBtn
:title="t('Save (Enter)')" :title="t('Save (Enter)')"
icon="save" icon="save"

View File

@ -146,14 +146,14 @@ const addFilter = async (filter, params) => {
}; };
async function fetch(params) { async function fetch(params) {
useArrayData(props.dataKey, params); arrayData.setOptions(params);
arrayData.resetPagination(); arrayData.resetPagination();
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
return emitStoreData(); return emitStoreData();
} }
async function update(params) { async function update(params) {
useArrayData(props.dataKey, params); arrayData.setOptions(params);
const { limit, skip } = store; const { limit, skip } = store;
store.limit = limit + skip; store.limit = limit + skip;
store.skip = 0; store.skip = 0;

View File

@ -19,7 +19,7 @@ export function useArrayData(key, userOptions) {
let canceller = null; let canceller = null;
onMounted(() => { onMounted(() => {
setOptions(); setOptions(userOptions ?? {});
reset(['skip']); reset(['skip']);
const query = route.query; const query = route.query;
@ -39,9 +39,10 @@ export function useArrayData(key, userOptions) {
setCurrentFilter(); setCurrentFilter();
}); });
if (key && userOptions) setOptions(); if (userOptions) setOptions(userOptions);
function setOptions() { function setOptions(params) {
if (!params) return;
const allowedOptions = [ const allowedOptions = [
'url', 'url',
'filter', 'filter',
@ -57,14 +58,14 @@ export function useArrayData(key, userOptions) {
'mapKey', 'mapKey',
'oneRecord', 'oneRecord',
]; ];
if (typeof userOptions === 'object') { if (typeof params === 'object') {
for (const option in userOptions) { for (const option in params) {
const isEmpty = userOptions[option] == null || userOptions[option] === ''; const isEmpty = params[option] == null || params[option] === '';
if (isEmpty || !allowedOptions.includes(option)) continue; if (isEmpty || !allowedOptions.includes(option)) continue;
if (Object.hasOwn(store, option)) { if (Object.hasOwn(store, option)) {
const defaultOpts = userOptions[option]; const defaultOpts = params[option];
store[option] = userOptions.keepOpts?.includes(option) store[option] = params.keepOpts?.includes(option)
? Object.assign(defaultOpts, store[option]) ? Object.assign(defaultOpts, store[option])
: defaultOpts; : defaultOpts;
if (option === 'userParams') store.defaultParams = store[option]; if (option === 'userParams') store.defaultParams = store[option];
@ -367,5 +368,6 @@ export function useArrayData(key, userOptions) {
deleteOption, deleteOption,
reset, reset,
resetPagination, resetPagination,
setOptions,
}; };
} }

View File

@ -6,6 +6,7 @@ import toDateHourMinSec from './toDateHourMinSec';
import toRelativeDate from './toRelativeDate'; import toRelativeDate from './toRelativeDate';
import toCurrency from './toCurrency'; import toCurrency from './toCurrency';
import toPercentage from './toPercentage'; import toPercentage from './toPercentage';
import toNumber from './toNumber';
import toLowerCamel from './toLowerCamel'; import toLowerCamel from './toLowerCamel';
import dashIfEmpty from './dashIfEmpty'; import dashIfEmpty from './dashIfEmpty';
import dateRange from './dateRange'; import dateRange from './dateRange';
@ -34,6 +35,7 @@ export {
toRelativeDate, toRelativeDate,
toCurrency, toCurrency,
toPercentage, toPercentage,
toNumber,
dashIfEmpty, dashIfEmpty,
dateRange, dateRange,
getParamWhere, getParamWhere,

8
src/filters/toNumber.js Normal file
View File

@ -0,0 +1,8 @@
export default function (value, fractionSize = 2) {
if (isNaN(value)) return value;
return new Intl.NumberFormat('es-ES', {
style: 'decimal',
minimumFractionDigits: 0,
maximumFractionDigits: fractionSize,
}).format(value);
}

View File

@ -24,13 +24,14 @@ globals:
dataDeleted: Data deleted dataDeleted: Data deleted
delete: Delete delete: Delete
search: Search search: Search
lines: Lines
changes: Changes changes: Changes
dataCreated: Data created dataCreated: Data created
add: Add add: Add
create: Create create: Create
edit: Edit edit: Edit
save: Save save: Save
saveAndContinue: Save and continue saveAndContinue: Save and go to
remove: Remove remove: Remove
reset: Reset reset: Reset
close: Close close: Close
@ -107,6 +108,8 @@ globals:
from: From from: From
to: To to: To
notes: Notes notes: Notes
photos: Photos
due-day: Due day
refresh: Refresh refresh: Refresh
item: Item item: Item
ticket: Ticket ticket: Ticket
@ -122,6 +125,7 @@ globals:
producer: Producer producer: Producer
origin: Origin origin: Origin
state: State state: State
total: Total
subtotal: Subtotal subtotal: Subtotal
visible: Visible visible: Visible
price: Price price: Price
@ -347,6 +351,7 @@ globals:
vehicleList: Vehicles vehicleList: Vehicles
vehicle: Vehicle vehicle: Vehicle
entryPreAccount: Pre-account entryPreAccount: Pre-account
management: Worker management
unsavedPopup: unsavedPopup:
title: Unsaved changes will be lost title: Unsaved changes will be lost
subtitle: Are you sure exit without saving? subtitle: Are you sure exit without saving?
@ -395,6 +400,7 @@ errors:
updateUserConfig: Error updating user config updateUserConfig: Error updating user config
tokenConfig: Error fetching token config tokenConfig: Error fetching token config
writeRequest: The requested operation could not be completed writeRequest: The requested operation could not be completed
claimBeginningQuantity: Cannot import a line with a claimed quantity of 0
login: login:
title: Login title: Login
username: Username username: Username

View File

@ -25,12 +25,13 @@ globals:
openDetail: Ver detalle openDetail: Ver detalle
delete: Eliminar delete: Eliminar
search: Buscar search: Buscar
lines: Lineas
changes: Cambios changes: Cambios
add: Añadir add: Añadir
create: Crear create: Crear
edit: Modificar edit: Modificar
save: Guardar save: Guardar
saveAndContinue: Guardar y continuar saveAndContinue: Guardar e ir a
remove: Eliminar remove: Eliminar
reset: Restaurar reset: Restaurar
close: Cerrar close: Cerrar
@ -111,6 +112,8 @@ globals:
from: Desde from: Desde
to: Hasta to: Hasta
notes: Notas notes: Notas
photos: Fotos
due-day: Vencimiento
refresh: Actualizar refresh: Actualizar
item: Artículo item: Artículo
ticket: Ticket ticket: Ticket
@ -126,6 +129,7 @@ globals:
producer: Productor producer: Productor
origin: Origen origin: Origen
state: Estado state: Estado
total: Total
subtotal: Subtotal subtotal: Subtotal
visible: Visible visible: Visible
price: Precio price: Precio
@ -350,6 +354,7 @@ globals:
vehicleList: Vehículos vehicleList: Vehículos
vehicle: Vehículo vehicle: Vehículo
entryPreAccount: Precontabilizar entryPreAccount: Precontabilizar
management: Gestión de trabajadores
unsavedPopup: unsavedPopup:
title: Los cambios que no haya guardado se perderán title: Los cambios que no haya guardado se perderán
subtitle: ¿Seguro que quiere salir sin guardar? subtitle: ¿Seguro que quiere salir sin guardar?
@ -391,6 +396,7 @@ errors:
updateUserConfig: Error al actualizar la configuración de usuario updateUserConfig: Error al actualizar la configuración de usuario
tokenConfig: Error al obtener configuración de token tokenConfig: Error al obtener configuración de token
writeRequest: No se pudo completar la operación solicitada writeRequest: No se pudo completar la operación solicitada
claimBeginningQuantity: No se puede importar una linea sin una cantidad reclamada
login: login:
title: Inicio de sesión title: Inicio de sesión
username: Nombre de usuario username: Nombre de usuario

View File

@ -13,8 +13,10 @@ import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import useNotify from 'src/composables/useNotify.js';
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify();
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const claim = ref(null); const claim = ref(null);
@ -176,12 +178,17 @@ async function save(data) {
} }
async function importToNewRefundTicket() { async function importToNewRefundTicket() {
try{
await post(`ClaimBeginnings/${claimId}/importToNewRefundTicket`); await post(`ClaimBeginnings/${claimId}/importToNewRefundTicket`);
await claimActionsForm.value.reload(); await claimActionsForm.value.reload();
quasar.notify({ quasar.notify({
message: t('globals.dataSaved'), message: t('globals.dataSaved'),
type: 'positive', type: 'positive',
}); });
} catch (error) {
const errorMessage = error.response?.data?.error?.message;
notify( t(errorMessage), 'negative' );
}
} }
async function post(query, params) { async function post(query, params) {

View File

@ -33,6 +33,7 @@ function onBeforeSave(formData, originalData) {
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load /> <FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
<FormModel <FormModel
model="Claim" model="Claim"
:go-to="`/claim/${route.params.id}/notes`"
:url-update="`Claims/updateClaim/${route.params.id}`" :url-update="`Claims/updateClaim/${route.params.id}`"
:mapper="onBeforeSave" :mapper="onBeforeSave"
auto-load auto-load

View File

@ -78,6 +78,8 @@ const columns = computed(() => [
label: t('Quantity'), label: t('Quantity'),
field: ({ sale }) => sale.quantity, field: ({ sale }) => sale.quantity,
sortable: true, sortable: true,
style: 'padding-right: 2%;',
headerStyle: 'padding-right: 2%;'
}, },
{ {
name: 'claimed', name: 'claimed',
@ -110,6 +112,8 @@ const columns = computed(() => [
field: ({ sale }) => totalRow(sale), field: ({ sale }) => totalRow(sale),
format: (value) => toCurrency(value), format: (value) => toCurrency(value),
sortable: true, sortable: true,
style: 'padding-right: 2%;',
headerStyle: 'padding-right: 2%;'
}, },
]); ]);
@ -152,12 +156,33 @@ function showImportDialog() {
.onOk(() => claimLinesForm.value.reload()); .onOk(() => claimLinesForm.value.reload());
} }
async function saveWhenHasChanges() { function fillClaimedQuantities() {
if (claimLinesForm.value.getChanges().updates) { const formData = claimLinesForm.value.formData;
await claimLinesForm.value.onSubmit(); let hasChanges = false;
onFetch(claimLinesForm.value.formData);
const selectedRows = formData.filter(row => selected.value.includes(row));
for (const row of selectedRows) {
if (row.quantity === 0 || row.quantity === null) {
row.quantity = row.sale.quantity;
hasChanges = true;
} }
} }
if (hasChanges) {
quasar.notify({
message: t('Quantities filled automatically'),
type: 'positive',
});
} else {
quasar.notify({
message: t('No quantities to fill'),
type: 'info',
});
}
}
</script> </script>
<template> <template>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> <Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()">
@ -185,15 +210,16 @@ async function saveWhenHasChanges() {
auto-load auto-load
/> />
<div class="q-pa-md"> <div class="q-pa-md">
<CrudModel <CrudModel
data-key="ClaimLines" data-key="claimLines"
ref="claimLinesForm" ref="claimLinesForm"
:go-to="`photos`"
:url="`Claims/${route.params.id}/lines`" :url="`Claims/${route.params.id}/lines`"
save-url="ClaimBeginnings/crud" save-url="ClaimBeginnings/crud"
:user-filter="linesFilter" :user-filter="linesFilter"
@on-fetch="onFetch" @on-fetch="onFetch"
v-model:selected="selected" v-model:selected="selected"
:default-save="false"
:default-reset="false" :default-reset="false"
auto-load auto-load
:limit="0" :limit="0"
@ -214,8 +240,7 @@ async function saveWhenHasChanges() {
v-model.number="row.quantity" v-model.number="row.quantity"
type="number" type="number"
dense dense
@keyup.enter="saveWhenHasChanges()"
@blur="saveWhenHasChanges()"
/> />
</QTd> </QTd>
</template> </template>
@ -272,10 +297,7 @@ async function saveWhenHasChanges() {
type="number" type="number"
dense dense
autofocus autofocus
@keyup.enter="
saveWhenHasChanges()
"
@blur="saveWhenHasChanges()"
/> />
</QItemLabel> </QItemLabel>
</template> </template>
@ -313,6 +335,18 @@ async function saveWhenHasChanges() {
</template> </template>
</QTable> </QTable>
</template> </template>
<template #moreBeforeActions>
<QBtn
color="primary"
text-color="white"
:unelevated="true"
:label="t('Rellenar cantidades')"
:title="t('Rellenar cantidades')"
icon="auto_fix_high"
:disabled="!selected.length"
@click="fillClaimedQuantities"
/>
</template>
</CrudModel> </CrudModel>
</div> </div>
@ -358,6 +392,8 @@ es:
Delete claimed sales: Eliminar ventas reclamadas Delete claimed sales: Eliminar ventas reclamadas
Discount updated: Descuento actualizado Discount updated: Descuento actualizado
Claimed quantity: Cantidad reclamada Claimed quantity: Cantidad reclamada
Quantities filled automatically: Cantidades rellenadas automáticamente
No quantities to fill: No hay cantidades para rellenar
You are about to remove {count} rows: ' You are about to remove {count} rows: '
Vas a eliminar <strong>{count}</strong> línea | Vas a eliminar <strong>{count}</strong> línea |
Vas a eliminar <strong>{count}</strong> líneas' Vas a eliminar <strong>{count}</strong> líneas'

View File

@ -35,9 +35,11 @@ const body = {
workerFk: user.value.id, workerFk: user.value.id,
}; };
</script> </script>
<template> <template>
<VnNotes <VnNotes
url="claimObservations" url="claimObservations"
:go-to="`/claim/${route.params.id}/lines`"
:add-note="$props.addNote" :add-note="$props.addNote"
:user-filter="claimFilter" :user-filter="claimFilter"
:filter="{ where: { claimFk: claimId } }" :filter="{ where: { claimFk: claimId } }"

View File

@ -54,7 +54,7 @@ const detailsColumns = ref([
{ {
name: 'item', name: 'item',
label: 'claim.item', label: 'claim.item',
field: (row) => row.sale.itemFk, field: (row) => dashIfEmpty(row.sale.itemFk),
sortable: true, sortable: true,
}, },
{ {
@ -67,13 +67,13 @@ const detailsColumns = ref([
{ {
name: 'quantity', name: 'quantity',
label: 'claim.quantity', label: 'claim.quantity',
field: (row) => row.sale.quantity, field: (row) => dashIfEmpty(row.sale.quantity),
sortable: true, sortable: true,
}, },
{ {
name: 'claimed', name: 'claimed',
label: 'claim.claimed', label: 'claim.claimed',
field: (row) => row.quantity, field: (row) => dashIfEmpty(row.quantity),
sortable: true, sortable: true,
}, },
{ {
@ -84,7 +84,7 @@ const detailsColumns = ref([
{ {
name: 'price', name: 'price',
label: 'claim.price', label: 'claim.price',
field: (row) => row.sale.price, field: (row) => dashIfEmpty(row.sale.price),
sortable: true, sortable: true,
}, },
{ {
@ -337,23 +337,16 @@ function claimUrl(section) {
</QTh> </QTh>
</QTr> </QTr>
</template> </template>
<template #body="props"> <template #body-cell-description="props">
<QTr :props="props"> <QTd :props="props">
<QTd v-for="col in props.cols" :key="col.name" :props="props"> <span class="link">
<template v-if="col.name === 'description'"> {{ props.value }}
<span class="link">{{ </span>
dashIfEmpty(col.field(props.row))
}}</span>
<ItemDescriptorProxy <ItemDescriptorProxy
:id="props.row.sale.itemFk" :id="props.row.sale.itemFk"
:sale-fk="props.row.saleFk" :sale-fk="props.row.saleFk"
/> />
</template>
<template v-else>
{{ dashIfEmpty(col.field(props.row)) }}
</template>
</QTd> </QTd>
</QTr>
</template> </template>
</QTable> </QTable>
</QCard> </QCard>

View File

@ -2,12 +2,13 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import ModalCloseContract from 'src/pages/Customer/components/ModalCloseContract.vue'; import ModalCloseContract from 'src/pages/Customer/components/ModalCloseContract.vue';
import { toDate } from 'src/filters'; import CustomerCreditContractsCreate from '../components/CustomerCreditContractsCreate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -16,6 +17,7 @@ const quasar = useQuasar();
const vnPaginateRef = ref(null); const vnPaginateRef = ref(null);
const showQPageSticky = ref(true); const showQPageSticky = ref(true);
const showForm = ref();
const filter = { const filter = {
order: 'finished ASC, started DESC', order: 'finished ASC, started DESC',
@ -36,25 +38,21 @@ const fetch = (data) => {
data.forEach((element) => { data.forEach((element) => {
if (!element.finished) { if (!element.finished) {
showQPageSticky.value = false; showQPageSticky.value = false;
return;
} }
}); });
}; };
const toCustomerCreditContractsCreate = () => {
router.push({ name: 'CustomerCreditContractsCreate' });
};
const openDialog = (item) => { const openDialog = (item) => {
quasar.dialog({ quasar.dialog({
component: ModalCloseContract, component: ModalCloseContract,
componentProps: { componentProps: {
id: item.id, id: item.id,
promise: updateData, promise: async () => {
await updateData();
showQPageSticky.value = true;
},
}, },
}); });
updateData();
showQPageSticky.value = true;
}; };
const openViewCredit = (credit) => { const openViewCredit = (credit) => {
@ -66,14 +64,14 @@ const openViewCredit = (credit) => {
}); });
}; };
const updateData = () => { const updateData = async () => {
vnPaginateRef.value?.fetch(); await vnPaginateRef.value?.fetch();
}; };
</script> </script>
<template> <template>
<div class="full-width flex justify-center"> <section class="row justify-center">
<QCard class="card-width q-pa-lg"> <QCard class="q-pa-lg" style="width: 70%">
<VnPaginate <VnPaginate
:user-filter="filter" :user-filter="filter"
@on-fetch="fetch" @on-fetch="fetch"
@ -84,100 +82,84 @@ const updateData = () => {
url="CreditClassifications" url="CreditClassifications"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<div v-if="rows.length"> <div v-if="rows.length" class="q-gutter-y-md">
<QCard <QCard
v-for="(item, index) in rows" v-for="(item, index) in rows"
:key="index" :key="index"
:class="{ :class="{ disabled: item.finished }"
'customer-card': true,
'q-mb-md': index < rows.length - 1,
'is-active': !item.finished,
}"
> >
<QCardSection <QCardSection
class="full-width flex justify-between q-py-none" class="full-width"
> :class="{ 'row justify-between': $q.screen.gt.md }"
<div class="width-state flex">
<div
class="flex items-center cursor-pointer q-mr-md"
v-if="!item.finished"
> >
<div class="width-state row no-wrap">
<QIcon <QIcon
:style="{
visibility: item.finished
? 'hidden'
: 'visible',
}"
@click.stop="openDialog(item)" @click.stop="openDialog(item)"
color="primary" color="primary"
name="lock" name="lock"
data-cy="closeBtn"
size="md" size="md"
class="fill-icon" class="fill-icon q-px-md"
> >
<QTooltip>{{ t('Close contract') }}</QTooltip> <QTooltip>{{ t('Close contract') }}</QTooltip>
</QIcon> </QIcon>
</div>
<div> <div class="column">
<div class="flex q-mb-xs"> <VnLv
<div class="q-mr-sm color-vn-label"> :label="t('Since')"
{{ t('Since') }}: :value="toDate(item.started)"
</div> />
<div class="text-weight-bold"> <VnLv
{{ toDate(item.started) }} :label="t('To')"
</div> :value="toDate(item.finished)"
</div> />
<div class="flex">
<div class="q-mr-sm color-vn-label">
{{ t('To') }}:
</div>
<div class="text-weight-bold">
{{ toDate(item.finished) }}
</div>
</div>
</div> </div>
</div> </div>
<QSeparator vertical /> <QSeparator vertical />
<div class="width-data flex"> <div class="column width-data">
<div <div
class="full-width flex justify-between items-center" class="column"
v-if="item?.insurances.length" v-if="item?.insurances.length"
v-for="insurance in item.insurances"
:key="insurance.id"
> >
<div class="flex"> <div
<div class="color-vn-label q-mr-xs"> :class="{
{{ t('Credit') }}: 'row q-gutter-x-md': $q.screen.gt.sm,
</div> }"
<div class="text-weight-bold"> class="q-mb-sm"
{{ item.insurances[0].credit }} >
<VnLv
:label="t('Credit')"
:value="toCurrency(insurance.credit)"
/>
<VnLv
:label="t('Grade')"
:value="dashIfEmpty(insurance.grade)"
/>
<VnLv
:label="t('Date')"
:value="toDate(insurance.created)"
/>
</div> </div>
</div> </div>
<div class="flex">
<div class="color-vn-label q-mr-xs">
{{ t('Grade') }}:
</div> </div>
<div class="text-weight-bold"> <QBtn
{{ item.insurances[0].grade || '-' }}
</div>
</div>
<div class="flex">
<div class="color-vn-label q-mr-xs">
{{ t('Date') }}:
</div>
<div class="text-weight-bold">
{{ toDate(item.insurances[0].created) }}
</div>
</div>
<div class="flex items-center cursor-pointer">
<QIcon
@click.stop="openViewCredit(item)" @click.stop="openViewCredit(item)"
color="primary" icon="preview"
name="preview"
size="md" size="md"
> :title="t('View credits')"
<QTooltip>{{ data-cy="viewBtn"
t('View credits') color="primary"
}}</QTooltip> flat
</QIcon> />
</div>
</div>
</div>
</QCardSection> </QCardSection>
</QCard> </QCard>
</div> </div>
@ -187,11 +169,12 @@ const updateData = () => {
</template> </template>
</VnPaginate> </VnPaginate>
</QCard> </QCard>
</div> </section>
<QPageSticky :offset="[18, 18]" v-if="showQPageSticky"> <QPageSticky :offset="[18, 18]" v-if="showQPageSticky">
<QBtn <QBtn
@click.stop="toCustomerCreditContractsCreate()" data-cy="createBtn"
@click.stop="showForm = !showForm"
color="primary" color="primary"
fab fab
icon="add" icon="add"
@ -201,24 +184,25 @@ const updateData = () => {
{{ t('New contract') }} {{ t('New contract') }}
</QTooltip> </QTooltip>
</QPageSticky> </QPageSticky>
<QDialog v-model="showForm">
<CustomerCreditContractsCreate @on-data-saved="updateData()" />
</QDialog>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.customer-card {
border: 2px solid var(--vn-light-gray);
border-radius: 10px;
padding: 10px;
display: flex;
justify-content: space-between;
}
.is-active {
background-color: var(--vn-light-gray);
}
.width-state { .width-state {
width: 30%; width: 30%;
} }
.width-data { .width-data {
width: 65%; width: 50%;
}
::v-deep(.label) {
margin-right: 5px;
}
::v-deep(.label)::after {
content: ':';
color: var(--vn-label-color);
} }
</style> </style>

View File

@ -0,0 +1,93 @@
<script setup>
import { computed, onBeforeMount, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { toCurrency, toDate } from 'src/filters';
import VnTable from 'src/components/VnTable/VnTable.vue';
import axios from 'axios';
const { t } = useI18n();
const route = useRoute();
const create = ref(null);
const tableRef = ref();
const columns = computed(() => [
{
align: 'left',
field: 'created',
format: ({ created }) => toDate(created),
label: t('Created'),
name: 'created',
create: true,
columnCreate: {
component: 'date',
},
},
{
align: 'left',
field: 'grade',
label: t('Grade'),
name: 'grade',
create: true,
},
{
align: 'left',
format: ({ credit }) => toCurrency(credit),
label: t('Credit'),
name: 'credit',
create: true,
},
]);
onBeforeMount(async () => {
const query = `CreditClassifications/findOne?filter=${encodeURIComponent(
JSON.stringify({
fields: ['finished'],
where: { id: route.params.creditId },
}),
)}`;
const { data } = await axios(query);
create.value = data.finished
? false
: {
urlCreate: 'CreditInsurances',
title: t('Create Insurance'),
onDataSaved: () => tableRef.value.reload(),
formInitialData: {
created: Date.vnNew(),
creditClassificationFk: route.params.creditId,
},
};
});
</script>
<template>
<VnTable
v-if="create != null"
url="CreditInsurances"
ref="tableRef"
data-key="creditInsurances"
:filter="{
where: {
creditClassificationFk: `${route.params.creditId}`,
},
order: 'created DESC',
}"
:columns="columns"
:right-search="false"
:is-editable="false"
:use-model="true"
:column-search="false"
:disable-option="{ card: true }"
:create
auto-load
/>
</template>
<i18n>
es:
Created: Fecha creación
Grade: Grade
Credit: Crédito
</i18n>

View File

@ -26,6 +26,7 @@ const columns = computed(() => [
url: 'Clients', url: 'Clients',
fields: ['id', 'socialName'], fields: ['id', 'socialName'],
optionLabel: 'socialName', optionLabel: 'socialName',
optionValue: 'socialName',
}, },
}, },
columnClass: 'expand', columnClass: 'expand',
@ -37,8 +38,11 @@ const columns = computed(() => [
name: 'city', name: 'city',
columnFilter: { columnFilter: {
component: 'select', component: 'select',
inWhere: true,
attrs: { attrs: {
url: 'Towns', url: 'Towns',
optionValue: 'name',
optionLabel: 'name',
}, },
}, },
cardVisible: true, cardVisible: true,
@ -89,13 +93,12 @@ const columns = computed(() => [
<CustomerNotificationsCampaignConsumption <CustomerNotificationsCampaignConsumption
:selected-rows="selected.length > 0" :selected-rows="selected.length > 0"
:clients="selected" :clients="selected"
:promise="refreshData"
/> />
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<VnTable <VnTable
:data-key="dataKey" :data-key="dataKey"
url="Clients/filter" url="Clients/extendedListFilter"
:table="{ :table="{
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',

View File

@ -142,13 +142,13 @@ onMounted(async () => {
valentinesDay: Valentine's Day valentinesDay: Valentine's Day
mothersDay: Mother's Day mothersDay: Mother's Day
allSaints: All Saints' Day allSaints: All Saints' Day
Campaign consumption: Campaign consumption ({rows}) Campaign consumption: Campaign consumption - {rows} records
es: es:
params: params:
valentinesDay: Día de San Valentín valentinesDay: Día de San Valentín
mothersDay: Día de la Madre mothersDay: Día de la Madre
allSaints: Día de Todos los Santos allSaints: Día de Todos los Santos
Campaign consumption: Consumo campaña ({rows}) Campaign consumption: Consumo campaña - {rows} registros
Campaign: Campaña Campaign: Campaña
From: Desde From: Desde
To: Hasta To: Hasta

View File

@ -3,44 +3,29 @@ import { reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
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';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const routeId = computed(() => route.params.id); const routeId = computed(() => route.params.id);
const router = useRouter();
const initialData = reactive({ const initialData = reactive({
started: Date.vnNew(), started: Date.vnNew(),
clientFk: routeId.value, clientFk: routeId.value,
}); });
const toCustomerCreditContracts = () => {
router.push({ name: 'CustomerCreditContracts' });
};
</script> </script>
<template> <template>
<FormModel <FormModelPopup
v-on="$attrs"
:form-initial-data="initialData" :form-initial-data="initialData"
:observe-form-changes="false" :observe-form-changes="false"
url-create="creditClassifications/createWithInsurance" url-create="creditClassifications/createWithInsurance"
@on-data-saved="toCustomerCreditContracts()"
> >
<template #moreActions> <template #form-inputs="{ data }">
<QBtn
:label="t('globals.cancel')"
@click="toCustomerCreditContracts"
color="primary"
flat
icon="close"
/>
</template>
<template #form="{ data }">
<VnRow> <VnRow>
<div class="col"> <div class="col">
<VnInput <VnInput
@ -63,7 +48,7 @@ const toCustomerCreditContracts = () => {
</div> </div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModelPopup>
</template> </template>
<i18n> <i18n>

View File

@ -1,63 +0,0 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { toCurrency, toDate } from 'src/filters';
import VnTable from 'src/components/VnTable/VnTable.vue';
const { t } = useI18n();
const route = useRoute();
const filter = {
where: {
creditClassificationFk: `${route.params.creditId}`,
},
limit: 20,
};
const columns = computed(() => [
{
align: 'left',
field: 'created',
format: ({ created }) => toDate(created),
label: t('Created'),
name: 'created',
},
{
align: 'left',
field: 'grade',
label: t('Grade'),
name: 'grade',
},
{
align: 'left',
format: ({ credit }) => toCurrency(credit),
label: t('Credit'),
name: 'credit',
},
]);
</script>
<template>
<VnTable
url="CreditInsurances"
ref="tableRef"
data-key="creditInsurances"
:filter="filter"
:columns="columns"
:right-search="false"
:is-editable="false"
:use-model="true"
:column-search="false"
:disable-option="{ card: true }"
auto-load
></VnTable>
</template>
<i18n>
es:
Created: Fecha creación
Grade: Grade
Credit: Crédito
</i18n>

View File

@ -1,9 +1,9 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { usePrintService } from 'composables/usePrintService';
import { downloadFile } from 'src/composables/downloadFile'; import { downloadFile } from 'src/composables/downloadFile';
import CustomerFileManagementDelete from 'src/pages/Customer/components/CustomerFileManagementDelete.vue'; import CustomerFileManagementDelete from 'src/pages/Customer/components/CustomerFileManagementDelete.vue';
@ -12,7 +12,7 @@ const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { openReport } = usePrintService();
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: Number,
@ -24,7 +24,7 @@ const $props = defineProps({
}, },
}); });
const setDownloadFile = () => downloadFile($props.id); const setDownloadFile = () => openReport(`dms/${$props.id}/downloadFile`, {}, '_blank');
const toCustomerFileManagementEdit = () => { const toCustomerFileManagementEdit = () => {
router.push({ router.push({

View File

@ -15,7 +15,7 @@ import InvoiceOutDescriptorProxy from 'pages/InvoiceOut/Card/InvoiceOutDescripto
import RouteDescriptorProxy from 'src/pages/Route/Card/RouteDescriptorProxy.vue'; import RouteDescriptorProxy from 'src/pages/Route/Card/RouteDescriptorProxy.vue';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue';
import { getItemPackagingType } from '../composables/getItemPackagingType.js';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -161,23 +161,6 @@ const setShippedColor = (date) => {
}; };
const rowClick = ({ id }) => const rowClick = ({ id }) =>
window.open(router.resolve({ params: { id }, name: 'TicketSummary' }).href, '_blank'); window.open(router.resolve({ params: { id }, name: 'TicketSummary' }).href, '_blank');
const getItemPackagingType = (ticketSales) => {
if (!ticketSales?.length) return '-';
const packagingTypes = ticketSales.reduce((types, sale) => {
const { itemPackingTypeFk } = sale.item;
if (
!types.includes(itemPackingTypeFk) &&
(itemPackingTypeFk === 'H' || itemPackingTypeFk === 'V')
) {
types.push(itemPackingTypeFk);
}
return types;
}, []);
return dashIfEmpty(packagingTypes.join(', ') || '-');
};
</script> </script>
<template> <template>

View File

@ -0,0 +1,36 @@
import { describe, it, expect } from 'vitest';
import { getItemPackagingType } from '../getItemPackagingType';
describe('getItemPackagingType', () => {
it('should return "-" if ticketSales is null or undefined', () => {
expect(getItemPackagingType(null)).toBe('-');
expect(getItemPackagingType(undefined)).toBe('-');
});
it('should return "-" if ticketSales does not have a length property', () => {
const ticketSales = { someKey: 'someValue' }; // No tiene propiedad length
expect(getItemPackagingType(ticketSales)).toBe('-');
});
it('should return unique packaging types as a comma-separated string', () => {
const ticketSales = [
{ item: { itemPackingTypeFk: 'H' } },
{ item: { itemPackingTypeFk: 'V' } },
{ item: { itemPackingTypeFk: 'H' } },
];
expect(getItemPackagingType(ticketSales)).toBe('H, V');
});
it('should return unique packaging types as a comma-separated string', () => {
const ticketSales = [
{ item: { itemPackingTypeFk: 'H' } },
{ item: { itemPackingTypeFk: 'V' } },
{ item: { itemPackingTypeFk: 'H' } },
{ item: { itemPackingTypeFk: 'A' } },
];
expect(getItemPackagingType(ticketSales)).toBe('H, V, A');
});
it('should return "-" if ticketSales is an empty array', () => {
expect(getItemPackagingType([])).toBe('-');
});
});

View File

@ -0,0 +1,11 @@
import { dashIfEmpty } from 'src/filters';
export function getItemPackagingType(ticketSales) {
if (!ticketSales?.length) return '-';
const packagingTypes = Array.from(
new Set(ticketSales.map(({ item: { itemPackingTypeFk } }) => itemPackingTypeFk)),
);
return dashIfEmpty(packagingTypes.join(', '));
}

View File

@ -1,22 +1,20 @@
<script setup> <script setup>
import { useRoute } from 'vue-router';
import { computed, onMounted, onUnmounted, ref } from 'vue'; import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import FetchedTags from 'components/ui/FetchedTags.vue';
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import SupplierConsumptionFilter from './SupplierConsumptionFilter.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import { dateRange, toDate } from 'src/filters';
import { dashIfEmpty } from 'src/filters';
import { usePrintService } from 'composables/usePrintService';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios'; import axios from 'axios';
import { dateRange, toCurrency, toNumber, toDateHourMin } from 'src/filters';
import { usePrintService } from 'composables/usePrintService';
import useNotify from 'src/composables/useNotify';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import SupplierConsumptionFilter from './SupplierConsumptionFilter.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
const state = useState(); const state = useState();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -31,7 +29,86 @@ const arrayData = useArrayData('SupplierConsumption', {
order: ['itemTypeFk', 'itemName', 'itemSize'], order: ['itemTypeFk', 'itemName', 'itemSize'],
userFilter: { where: { supplierFk: route.params.id } }, userFilter: { where: { supplierFk: route.params.id } },
}); });
const headerColumns = computed(() => [
{
name: 'id',
label: t('globals.entry'),
align: 'left',
field: 'id',
sortable: true,
},
{
name: 'invoiceNumber',
label: t('globals.params.supplierRef'),
align: 'left',
field: 'invoiceNumber',
sortable: true,
},
{
name: 'shipped',
label: t('globals.shipped'),
align: 'center',
field: 'shipped',
format: toDateHourMin,
sortable: true,
},
{
name: 'quantity',
label: t('item.list.stems'),
align: 'center',
field: 'quantity',
format: (value) => toNumber(value),
sortable: true,
},
{
name: 'total',
label: t('globals.total'),
align: 'center',
field: 'total',
format: (value) => toCurrency(value),
sortable: true,
},
]);
const columns = computed(() => [
{
name: 'itemName',
label: t('globals.item'),
align: 'left',
field: 'itemName',
sortable: true,
},
{
name: 'subName',
align: 'left',
field: 'subName',
sortable: true,
},
{
name: 'quantity',
label: t('globals.quantity'),
align: 'right',
field: 'quantity',
format: (value) => toNumber(value),
sortable: true,
},
{
name: 'price',
label: t('globals.price'),
align: 'right',
field: 'price',
format: (value) => toCurrency(value),
sortable: true,
},
{
name: 'total',
label: t('globals.total'),
align: 'right',
field: 'total',
format: (value) => toCurrency(value),
sortable: true,
},
]);
const store = arrayData.store; const store = arrayData.store;
onUnmounted(() => state.unset('SupplierConsumption')); onUnmounted(() => state.unset('SupplierConsumption'));
@ -40,13 +117,11 @@ const dateRanges = computed(() => {
return { from, to }; return { from, to };
}); });
const reportParams = computed(() => { const reportParams = computed(() => ({
return {
recipientId: Number(route.params.id), recipientId: Number(route.params.id),
to: dateRange(dateRanges.value.to)[1], from: dateRange(dateRanges.value.from)[0].toISOString(),
from: dateRange(dateRanges.value.from)[1], to: dateRange(dateRanges.value.to)[1].toISOString(),
}; }));
});
async function getSupplierConsumptionData() { async function getSupplierConsumptionData() {
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
@ -102,33 +177,34 @@ const sendCampaignMetricsEmail = ({ address }) => {
}; };
const totalEntryPrice = (rows) => { const totalEntryPrice = (rows) => {
let totalPrice = 0; if (!rows) return [];
let totalQuantity = 0; totalRows.value = rows.reduce(
if (!rows) return totalPrice; (acc, row) => {
for (const row of rows) { if (Array.isArray(row.buys)) {
let total = 0; const { total, quantity } = row.buys.reduce(
let quantity = 0; (buyAcc, buy) => {
buyAcc.total += buy.total || 0;
if (row.buys) { buyAcc.quantity += buy.quantity || 0;
for (const buy of row.buys) { return buyAcc;
total = total + buy.total; },
quantity = quantity + buy.quantity; { total: 0, quantity: 0 },
} );
}
row.total = total; row.total = total;
row.quantity = quantity; row.quantity = quantity;
totalPrice = totalPrice + total; acc.totalPrice += total;
totalQuantity = totalQuantity + quantity; acc.totalQuantity += quantity;
} }
totalRows.value = { totalPrice, totalQuantity }; return acc;
},
{ totalPrice: 0, totalQuantity: 0 },
);
return rows; return rows;
}; };
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
await getSupplierConsumptionData(); await getSupplierConsumptionData();
}); });
const expanded = ref([]);
</script> </script>
<template> <template>
@ -162,14 +238,14 @@ onMounted(async () => {
<div> <div>
{{ t('Total entries') }}: {{ t('Total entries') }}:
<QChip :dense="$q.screen.lt.sm" text-color="white"> <QChip :dense="$q.screen.lt.sm" text-color="white">
{{ totalRows.totalPrice }} {{ toCurrency(totalRows.totalPrice) }}
</QChip> </QChip>
</div> </div>
<QSeparator dark vertical /> <QSeparator dark vertical />
<div> <div>
{{ t('Total stems entries') }}: {{ t('Total stems entries') }}:
<QChip :dense="$q.screen.lt.sm" text-color="white"> <QChip :dense="$q.screen.lt.sm" text-color="white">
{{ totalRows.totalQuantity }} {{ toNumber(totalRows.totalQuantity) }}
</QChip> </QChip>
</div> </div>
</div> </div>
@ -179,59 +255,111 @@ onMounted(async () => {
<SupplierConsumptionFilter data-key="SupplierConsumption" /> <SupplierConsumptionFilter data-key="SupplierConsumption" />
</template> </template>
</RightMenu> </RightMenu>
<QCard class="full-width q-pa-md">
<QTable <QTable
flat
bordered
:rows="rows" :rows="rows"
:columns="headerColumns"
row-key="id" row-key="id"
hide-header v-model:expanded="expanded"
class="full-width q-mt-md" :grid="$q.screen.lt.md"
:no-data-label="t('No results')"
> >
<template #body="{ row }"> <template #header="props">
<QTr> <QTr :props="props">
<QTd no-hover> <QTh auto-width />
<span class="label">{{ t('supplier.consumption.entry') }}: </span>
<span>{{ row.id }}</span> <QTh v-for="col in props.cols" :key="col.name" :props="props">
</QTd> <span v-text="col.label" class="tr-header" />
<QTd no-hover> </QTh>
<span class="label">{{ t('globals.date') }}: </span>
<span>{{ toDate(row.shipped) }}</span></QTd
>
<QTd colspan="6" no-hover>
<span class="label">{{ t('globals.reference') }}: </span>
<span>{{ row.invoiceNumber }}</span>
</QTd>
</QTr> </QTr>
<QTr v-for="(buy, index) in row.buys" :key="index"> </template>
<QTd no-hover>
<QBtn flat class="link" dense no-caps>{{ buy.itemName }}</QBtn> <template #body="props">
<ItemDescriptorProxy :id="buy.itemFk" /> <QTr
:props="props"
:key="`movement_${props.row.id}`"
class="bg-vn-page cursor-pointer"
@click="props.expand = !props.expand"
>
<QTd auto-width>
<QIcon
:class="props.expand ? '' : 'rotate-270'"
name="expand_circle_down"
size="md"
:color="props.expand ? 'primary' : 'white'"
/>
</QTd> </QTd>
<QTd no-hover> <QTd v-for="col in props.cols" :key="col.name" :props="props">
<span>{{ buy.subName }}</span> <span @click.stop class="link" v-if="col.name === 'id'">
<FetchedTags :item="buy" /> {{ col.value }}
<EntryDescriptorProxy :id="col.value" />
</span>
<span v-else v-text="col.value" />
</QTd> </QTd>
<QTd no-hover> {{ dashIfEmpty(buy.quantity) }}</QTd>
<QTd no-hover> {{ dashIfEmpty(buy.price) }}</QTd>
<QTd colspan="2" no-hover> {{ dashIfEmpty(buy.total) }}</QTd>
</QTr> </QTr>
<QTr>
<QTd colspan="5" no-hover> <QTr
<span class="label">{{ t('Total entry') }}: </span> v-show="props.expand"
<span>{{ row.total }} </span> :props="props"
</QTd> :key="`expedition_${props.row.id}`"
<QTd no-hover> >
<span class="label">{{ t('Total stems') }}: </span> <QTd colspan="12" style="padding: 1px 0">
<span>{{ row.quantity }}</span> <QTable
color="secondary"
card-class="bg-vn-page text-white "
style-class="height: 30px"
table-header-class="text-white"
:rows="props.row.buys"
:columns="columns"
row-key="id"
virtual-scroll
v-model:expanded="expanded"
>
<template #header="props">
<QTr :props="props">
<QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<span v-text="col.label" class="tr-header" />
</QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props" :key="`m_${props.row.id}`">
<QTd
v-for="col in props.cols"
:key="col.name"
:title="col.label"
:props="props"
>
<span
@click.stop
class="link"
v-if="col.name === 'itemName'"
>
{{ col.value }}
<ItemDescriptorProxy :id="props.row.itemFk" />
</span>
<span v-else v-text="col.value" />
</QTd> </QTd>
</QTr> </QTr>
</template> </template>
</QTable> </QTable>
</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.label { .q-table thead tr,
color: var(--vn-label-color); .q-table tbody td {
height: 30px;
} }
</style> </style>

View File

@ -8,7 +8,6 @@ import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -59,11 +58,25 @@ const zoneWhere = computed(() => {
}); });
async function getLanded(params) { async function getLanded(params) {
getDate(`Agencies/getLanded`, params); const data = await getDate(`Agencies/getLanded`, params);
formData.value.landed = data.landed;
const shippedDate = new Date(params.shipped);
const landedDate = new Date(data.hour);
shippedDate.setHours(
landedDate.getHours(),
landedDate.getMinutes(),
landedDate.getSeconds(),
);
formData.value.shipped = shippedDate.toISOString();
} }
async function getShipped(params) { async function getShipped(params) {
getDate(`Agencies/getShipped`, params); const data = await getDate(`Agencies/getShipped`, params);
formData.value.landed = params.landed;
const [hours, minutes, seconds] = data.hour.split(':').map(Number);
let shippedDate = new Date(data.shipped);
shippedDate.setHours(hours, minutes, seconds);
formData.value.shipped = shippedDate.toISOString();
} }
async function getDate(query, params) { async function getDate(query, params) {
@ -75,15 +88,8 @@ async function getDate(query, params) {
if (!data) return notify(t('basicData.noDeliveryZoneAvailable'), 'negative'); if (!data) return notify(t('basicData.noDeliveryZoneAvailable'), 'negative');
formData.value.zoneFk = data.zoneFk; formData.value.zoneFk = data.zoneFk;
formData.value.landed = data.landed;
const shippedDate = new Date(params.shipped); return data;
const landedDate = new Date(data.hour);
shippedDate.setHours(
landedDate.getHours(),
landedDate.getMinutes(),
landedDate.getSeconds(),
);
formData.value.shipped = shippedDate.toISOString();
} }
const onChangeZone = async (zoneId) => { const onChangeZone = async (zoneId) => {
@ -273,8 +279,6 @@ async function getZone(options) {
<VnSelect <VnSelect
:label="t('ticketList.client')" :label="t('ticketList.client')"
v-model="clientId" v-model="clientId"
option-value="id"
option-label="name"
url="Clients" url="Clients"
:fields="['id', 'name']" :fields="['id', 'name']"
sort-by="id" sort-by="id"
@ -294,7 +298,7 @@ async function getZone(options) {
</template> </template>
</VnSelect> </VnSelect>
<VnSelect <VnSelect
:label="t('ticketList.warehouse')" :label="t('basicData.warehouse')"
v-model="warehouseId" v-model="warehouseId"
option-value="id" option-value="id"
option-label="name" option-label="name"
@ -302,23 +306,38 @@ async function getZone(options) {
hide-selected hide-selected
map-options map-options
:required="true" :required="true"
:rules="validate('ticketList.warehouse')" :rules="validate('basicData.warehouse')"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md no-wrap"> <VnRow class="row q-gutter-md q-mb-md no-wrap">
<VnSelect <VnSelect
:label="t('basicData.address')" :label="t('basicData.address')"
v-model="addressId" v-model="addressId"
option-value="id"
option-label="nickname" option-label="nickname"
:options="addresses" :options="addresses"
hide-selected hide-selected
map-options map-options
:required="true" :required="true"
:sort-by="['isActive DESC']"
:rules="validate('basicData.address')" :rules="validate('basicData.address')"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem
v-bind="scope.itemProps"
:class="{ disabled: !scope.opt.isActive }"
>
<QItemSection style="min-width: min-content" avatar>
<QIcon
v-if="
scope.opt.isActive &&
formData.client.defaultAddressFk === scope.opt.id
"
size="sm"
color="grey"
name="star"
class="fill-icon"
/>
</QItemSection>
<QItemSection> <QItemSection>
<QItemLabel <QItemLabel
:class="{ :class="{
@ -344,6 +363,9 @@ async function getZone(options) {
{{ scope.opt?.agencyMode?.name }}</span {{ scope.opt?.agencyMode?.name }}</span
> >
</QItemLabel> </QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id}` }}
</QItemLabel>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
@ -425,14 +447,6 @@ async function getZone(options) {
:rules="validate('ticketList.shipped')" :rules="validate('ticketList.shipped')"
@update:model-value="setShipped" @update:model-value="setShipped"
/> />
<VnInputTime
:label="t('basicData.shippedHour')"
v-model="formData.shipped"
:required="true"
:rules="validate('basicData.shippedHour')"
disabled
@update:model-value="setShipped"
/>
<VnInputDate <VnInputDate
:label="t('basicData.landed')" :label="t('basicData.landed')"
v-model="formData.landed" v-model="formData.landed"

View File

@ -20,6 +20,7 @@ export default {
'isFreezed', 'isFreezed',
'isTaxDataChecked', 'isTaxDataChecked',
'hasElectronicInvoice', 'hasElectronicInvoice',
'defaultAddressFk',
'credit', 'credit',
], ],
include: [ include: [

View File

@ -137,8 +137,6 @@ const setUserParams = (params) => {
($event) => onCategoryChange($event, searchFn) ($event) => onCategoryChange($event, searchFn)
" "
:options="categoriesOptions" :options="categoriesOptions"
option-value="id"
option-label="name"
hide-selected hide-selected
dense dense
filled filled
@ -152,10 +150,7 @@ const setUserParams = (params) => {
<VnSelect <VnSelect
:label="t('negative.type')" :label="t('negative.type')"
v-model="params.typeFk" v-model="params.typeFk"
@update:model-value="searchFn()"
:options="itemTypesOptions" :options="itemTypesOptions"
option-value="id"
option-label="name"
hide-selected hide-selected
dense dense
filled filled

View File

@ -121,7 +121,7 @@ const ticketColumns = computed(() => [
format: (row, dashIfEmpty) => dashIfEmpty(row.lines), format: (row, dashIfEmpty) => dashIfEmpty(row.lines),
}, },
{ {
align: 'left', align: 'right',
label: t('advanceTickets.import'), label: t('advanceTickets.import'),
name: 'totalWithVat', name: 'totalWithVat',
hidden: true, hidden: true,
@ -172,6 +172,15 @@ const ticketColumns = computed(() => [
headerClass: 'horizontal-separator', headerClass: 'horizontal-separator',
name: 'futureLiters', name: 'futureLiters',
}, },
{
label: t('advanceTickets.preparation'),
name: 'futurePreparation',
field: 'futurePreparation',
align: 'left',
sortable: true,
headerClass: 'horizontal-separator',
columnFilter: false,
},
{ {
align: 'left', align: 'left',
label: t('advanceTickets.futureZone'), label: t('advanceTickets.futureZone'),
@ -196,15 +205,17 @@ const ticketColumns = computed(() => [
label: t('advanceTickets.notMovableLines'), label: t('advanceTickets.notMovableLines'),
headerClass: 'horizontal-separator', headerClass: 'horizontal-separator',
name: 'notMovableLines', name: 'notMovableLines',
class: 'shrink',
}, },
{ {
align: 'left', align: 'left',
label: t('advanceTickets.futureLines'), label: t('advanceTickets.futureLines'),
headerClass: 'horizontal-separator', headerClass: 'horizontal-separator',
name: 'futureLines', name: 'futureLines',
class: 'shrink',
}, },
{ {
align: 'left', align: 'right',
label: t('advanceTickets.futureImport'), label: t('advanceTickets.futureImport'),
name: 'futureTotalWithVat', name: 'futureTotalWithVat',
hidden: true, hidden: true,
@ -399,8 +410,10 @@ watch(
destinationElRef.value.classList.add('text-uppercase', 'color-vn-label'); destinationElRef.value.classList.add('text-uppercase', 'color-vn-label');
originElRef.value.classList.add('text-uppercase', 'color-vn-label'); originElRef.value.classList.add('text-uppercase', 'color-vn-label');
destinationElRef.value.setAttribute('colspan', '10'); originElRef.value.classList.add('advance-icon');
originElRef.value.setAttribute('colspan', '10');
destinationElRef.value.setAttribute('colspan', '9');
originElRef.value.setAttribute('colspan', '11');
destinationElRef.value.textContent = `${t( destinationElRef.value.textContent = `${t(
'advanceTickets.destination', 'advanceTickets.destination',

View File

@ -68,7 +68,7 @@ onMounted(async () => await getItemPackingTypes());
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params }">
<QItem class="q-my-sm"> <QItem class="q-my-sm">
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
@ -96,12 +96,10 @@ onMounted(async () => await getItemPackingTypes());
option-value="code" option-value="code"
option-label="description" option-label="description"
:info="t('iptInfo')" :info="t('iptInfo')"
@update:model-value="searchFn()"
dense dense
filled filled
:use-like="false" :use-like="false"
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -113,12 +111,10 @@ onMounted(async () => await getItemPackingTypes());
option-value="code" option-value="code"
option-label="description" option-label="description"
:info="t('iptInfo')" :info="t('iptInfo')"
@update:model-value="searchFn()"
dense dense
filled filled
:use-like="false" :use-like="false"
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -137,7 +133,6 @@ onMounted(async () => await getItemPackingTypes());
:label="t('params.isFullMovable')" :label="t('params.isFullMovable')"
v-model="params.isFullMovable" v-model="params.isFullMovable"
toggle-indeterminate toggle-indeterminate
@update:model-value="searchFn()"
dense dense
/> />
</QItemSection> </QItemSection>
@ -160,13 +155,9 @@ onMounted(async () => await getItemPackingTypes());
:label="t('params.warehouseFk')" :label="t('params.warehouseFk')"
v-model="params.warehouseFk" v-model="params.warehouseFk"
:options="warehousesOptions" :options="warehousesOptions"
option-value="id"
option-label="name"
@update:model-value="searchFn()"
dense dense
filled filled
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -175,7 +166,6 @@ onMounted(async () => await getItemPackingTypes());
toggle-indeterminate toggle-indeterminate
:label="t('params.onlyWithDestination')" :label="t('params.onlyWithDestination')"
v-model="params.onlyWithDestination" v-model="params.onlyWithDestination"
@update:model-value="searchFn()"
dense dense
/> />
</QItemSection> </QItemSection>

View File

@ -60,7 +60,7 @@ const groupedStates = ref([]);
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params }">
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput v-model="params.clientFk" :label="t('Customer ID')" filled /> <VnInput v-model="params.clientFk" :label="t('Customer ID')" filled />
@ -108,10 +108,7 @@ const groupedStates = ref([]);
<VnSelect <VnSelect
:label="t('State')" :label="t('State')"
v-model="params.stateFk" v-model="params.stateFk"
@update:model-value="searchFn()"
:options="states" :options="states"
option-value="id"
option-label="name"
emit-value emit-value
map-options map-options
use-input use-input
@ -128,7 +125,6 @@ const groupedStates = ref([]);
<VnSelect <VnSelect
:label="t('params.groupedStates')" :label="t('params.groupedStates')"
v-model="params.groupedStates" v-model="params.groupedStates"
@update:model-value="searchFn()"
:options="groupedStates" :options="groupedStates"
option-label="code" option-label="code"
emit-value emit-value
@ -163,7 +159,6 @@ const groupedStates = ref([]);
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
v-model="params.myTeam" v-model="params.myTeam"
@update:model-value="searchFn()"
:label="t('My team')" :label="t('My team')"
toggle-indeterminate toggle-indeterminate
/> />
@ -171,7 +166,6 @@ const groupedStates = ref([]);
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
v-model="params.pending" v-model="params.pending"
@update:model-value="searchFn()"
:label="t('Pending')" :label="t('Pending')"
toggle-indeterminate toggle-indeterminate
/> />
@ -181,7 +175,6 @@ const groupedStates = ref([]);
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
v-model="params.hasInvoice" v-model="params.hasInvoice"
@update:model-value="searchFn()"
:label="t('Invoiced')" :label="t('Invoiced')"
toggle-indeterminate toggle-indeterminate
/> />
@ -189,7 +182,6 @@ const groupedStates = ref([]);
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
v-model="params.hasRoute" v-model="params.hasRoute"
@update:model-value="searchFn()"
:label="t('Routed')" :label="t('Routed')"
toggle-indeterminate toggle-indeterminate
/> />
@ -203,10 +195,7 @@ const groupedStates = ref([]);
<VnSelect <VnSelect
:label="t('Province')" :label="t('Province')"
v-model="params.provinceFk" v-model="params.provinceFk"
@update:model-value="searchFn()"
:options="provinces" :options="provinces"
option-value="id"
option-label="name"
emit-value emit-value
map-options map-options
use-input use-input
@ -223,7 +212,6 @@ const groupedStates = ref([]);
<VnSelect <VnSelect
:label="t('Agency')" :label="t('Agency')"
v-model="params.agencyModeFk" v-model="params.agencyModeFk"
@update:model-value="searchFn()"
:options="agencies" :options="agencies"
emit-value emit-value
map-options map-options
@ -241,10 +229,7 @@ const groupedStates = ref([]);
<VnSelect <VnSelect
:label="t('Warehouse')" :label="t('Warehouse')"
v-model="params.warehouseFk" v-model="params.warehouseFk"
@update:model-value="searchFn()"
:options="warehouses" :options="warehouses"
option-value="id"
option-label="name"
emit-value emit-value
map-options map-options
use-input use-input

View File

@ -85,6 +85,7 @@ const ticketColumns = computed(() => [
label: t('advanceTickets.liters'), label: t('advanceTickets.liters'),
name: 'liters', name: 'liters',
align: 'left', align: 'left',
class: 'shrink',
headerClass: 'horizontal-separator', headerClass: 'horizontal-separator',
}, },
{ {
@ -177,7 +178,12 @@ watch(
if (!$el) return; if (!$el) return;
const head = $el.querySelector('thead'); const head = $el.querySelector('thead');
const firstRow = $el.querySelector('thead > tr'); const firstRow = $el.querySelector('thead > tr');
const headSelectionCol = $el.querySelector(
'thead tr.bg-header th.q-table--col-auto-width',
);
if (headSelectionCol) {
headSelectionCol.classList.add('horizontal-separator');
}
const newRow = document.createElement('tr'); const newRow = document.createElement('tr');
destinationElRef.value = document.createElement('th'); destinationElRef.value = document.createElement('th');
originElRef.value = document.createElement('th'); originElRef.value = document.createElement('th');
@ -185,9 +191,10 @@ watch(
newRow.classList.add('bg-header'); newRow.classList.add('bg-header');
destinationElRef.value.classList.add('text-uppercase', 'color-vn-label'); destinationElRef.value.classList.add('text-uppercase', 'color-vn-label');
originElRef.value.classList.add('text-uppercase', 'color-vn-label'); originElRef.value.classList.add('text-uppercase', 'color-vn-label');
originElRef.value.classList.add('advance-icon');
destinationElRef.value.setAttribute('colspan', '7'); destinationElRef.value.setAttribute('colspan', '9');
originElRef.value.setAttribute('colspan', '9'); originElRef.value.setAttribute('colspan', '7');
originElRef.value.textContent = `${t('advanceTickets.origin')}`; originElRef.value.textContent = `${t('advanceTickets.origin')}`;
destinationElRef.value.textContent = `${t('advanceTickets.destination')}`; destinationElRef.value.textContent = `${t('advanceTickets.destination')}`;
@ -371,4 +378,12 @@ watch(
:deep(.horizontal-bottom-separator) { :deep(.horizontal-bottom-separator) {
border-bottom: 4px solid white !important; border-bottom: 4px solid white !important;
} }
:deep(th.advance-icon::after) {
content: '>>';
font-size: larger;
position: absolute;
text-align: center;
float: 0;
left: 0;
}
</style> </style>

View File

@ -70,7 +70,7 @@ onMounted(async () => {
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params }">
<QItem class="q-my-sm"> <QItem class="q-my-sm">
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
@ -116,11 +116,9 @@ onMounted(async () => {
option-value="code" option-value="code"
option-label="description" option-label="description"
:info="t('iptInfo')" :info="t('iptInfo')"
@update:model-value="searchFn()"
dense dense
filled filled
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -132,11 +130,9 @@ onMounted(async () => {
option-value="code" option-value="code"
option-label="description" option-label="description"
:info="t('iptInfo')" :info="t('iptInfo')"
@update:model-value="searchFn()"
dense dense
filled filled
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -145,13 +141,9 @@ onMounted(async () => {
:label="t('params.state')" :label="t('params.state')"
v-model="params.state" v-model="params.state"
:options="stateOptions" :options="stateOptions"
option-value="id"
option-label="name"
@update:model-value="searchFn()"
dense dense
filled filled
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -160,13 +152,9 @@ onMounted(async () => {
:label="t('params.futureState')" :label="t('params.futureState')"
v-model="params.futureState" v-model="params.futureState"
:options="stateOptions" :options="stateOptions"
option-value="id"
option-label="name"
@update:model-value="searchFn()"
dense dense
filled filled
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -176,7 +164,6 @@ onMounted(async () => {
:label="t('params.problems')" :label="t('params.problems')"
v-model="params.problems" v-model="params.problems"
:toggle-indeterminate="false" :toggle-indeterminate="false"
@update:model-value="searchFn()"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -186,13 +173,9 @@ onMounted(async () => {
:label="t('params.warehouseFk')" :label="t('params.warehouseFk')"
v-model="params.warehouseFk" v-model="params.warehouseFk"
:options="warehousesOptions" :options="warehousesOptions"
option-value="id"
option-label="name"
@update:model-value="searchFn()"
dense dense
filled filled
> />
</VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>

View File

@ -582,7 +582,7 @@ function setReference(data) {
hide-selected hide-selected
required required
@update:model-value="() => onClientSelected(data)" @update:model-value="() => onClientSelected(data)"
:sort-by="'id ASC'" :sort-by="['id ASC']"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -608,7 +608,7 @@ function setReference(data) {
map-options map-options
required required
:disable="!data.clientId" :disable="!data.clientId"
:sort-by="'isActive DESC'" :sort-by="['isActive DESC']"
@update:model-value="() => fetchAvailableAgencies(data)" @update:model-value="() => fetchAvailableAgencies(data)"
> >
<template #option="scope"> <template #option="scope">

View File

@ -120,6 +120,7 @@ basicData:
difference: Difference difference: Difference
total: Total total: Total
price: Price price: Price
warehouse: Warehouse
newPrice: New price newPrice: New price
chargeDifference: Charge difference to chargeDifference: Charge difference to
withoutNegatives: Create without negatives withoutNegatives: Create without negatives

View File

@ -47,6 +47,7 @@ basicData:
difference: Diferencia difference: Diferencia
total: Total total: Total
price: Precio price: Precio
warehouse: Almacén
newPrice: Nuevo precio newPrice: Nuevo precio
chargeDifference: Cargar diferencia a chargeDifference: Cargar diferencia a
withoutNegatives: Crear sin negativos withoutNegatives: Crear sin negativos

View File

@ -13,6 +13,8 @@ export default {
'daysInForward', 'daysInForward',
'availabled', 'availabled',
'awbFk', 'awbFk',
'isDelivered',
'isReceived',
], ],
include: [ include: [
{ {

View File

@ -182,6 +182,7 @@ const columns = computed(() => [
align: 'left', align: 'left',
showValue: false, showValue: false,
sortable: true, sortable: true,
style: 'max-width: 200px;',
}, },
{ {
label: t('globals.packages'), label: t('globals.packages'),
@ -206,6 +207,7 @@ const columns = computed(() => [
align: 'left', align: 'left',
showValue: false, showValue: false,
sortable: true, sortable: true,
style: 'max-width: 75px;',
}, },
{ {
label: t('extraCommunity.physicKg'), label: t('extraCommunity.physicKg'),

View File

@ -280,7 +280,7 @@ const fetchWeekData = async () => {
week: selectedWeekNumber.value, week: selectedWeekNumber.value,
}; };
try { try {
const [{ data: mailData }, { data: countData }] = await Promise.allS([ const [{ data: mailData }, { data: countData }] = await Promise.all([
axios.get(`Workers/${route.params.id}/mail`, { axios.get(`Workers/${route.params.id}/mail`, {
params: { filter: { where } }, params: { filter: { where } },
}), }),
@ -292,8 +292,9 @@ const fetchWeekData = async () => {
state.value = mail?.state; state.value = mail?.state;
reason.value = mail?.reason; reason.value = mail?.reason;
canResend.value = !!countData.count; canResend.value = !!countData.count;
} catch { } catch (error) {
state.value = null; state.value = null;
if (error?.status != 403) throw error;
} }
}; };
@ -407,7 +408,7 @@ const isUnsatisfied = async (reason) => {
const resendEmail = async () => { const resendEmail = async () => {
const params = { const params = {
recipient: worker.value[0]?.user?.emailUser?.email, recipient: worker.value?.user?.emailUser?.email,
week: selectedWeekNumber.value, week: selectedWeekNumber.value,
year: selectedDateYear.value, year: selectedDateYear.value,
workerId: Number(route.params.id), workerId: Number(route.params.id),

View File

@ -33,14 +33,6 @@ const getLocale = (label) => {
</div> </div>
</template> </template>
<template #body="{ params }"> <template #body="{ params }">
<QItem>
<QItemSection>
<VnInput :label="t('FI')" v-model="params.fi" filled
><template #prepend>
<QIcon name="badge" size="xs"></QIcon> </template
></VnInput>
</QItemSection>
</QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput :label="t('First Name')" v-model="params.firstName" filled /> <VnInput :label="t('First Name')" v-model="params.firstName" filled />
@ -112,7 +104,6 @@ es:
lastName: Apellidos lastName: Apellidos
userName: Usuario userName: Usuario
extension: Extensión extension: Extensión
FI: NIF
First Name: Nombre First Name: Nombre
Last Name: Apellidos Last Name: Apellidos
User Name: Usuario User Name: Usuario

View File

@ -0,0 +1,118 @@
<script setup>
import { computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useAcl } from 'src/composables/useAcl';
import VnTable from 'components/VnTable/VnTable.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import WorkerSummary from './Card/WorkerSummary.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import WorkerManagementFilter from './WorkerManagementFilter.vue';
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const dataKey = 'ManagementList';
const columns = computed(() => [
{
align: 'left',
label: t('tableColumns.firstName'),
name: 'firstName',
isTitle: true,
columnFilter: {
name: 'firstName',
},
},
{
align: 'left',
label: t('tableColumns.lastName'),
name: 'lastName',
columnFilter: {
name: 'lastName',
},
},
{
align: 'left',
label: t('management.NIF'),
name: 'fi',
columnFilter: {
name: 'fi',
},
cardVisible: true,
},
{
align: 'left',
label: t('tableColumns.email'),
name: 'email',
columnFilter: {
name: 'email',
},
cardVisible: true,
},
{
align: 'left',
label: t('management.ssn'),
labelAbbreviation: t('management.ssn'),
toolTip: t('management.completeSsn'),
name: 'SSN',
columnFilter: {
name: 'SSN',
},
cardVisible: true,
},
{
align: 'left',
name: 'departmentFk',
label: t('tableColumns.department'),
columnFilter: {
component: 'select',
name: 'departmentFk',
attrs: {
url: 'Departments',
},
},
cardVisible: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.department),
},
{
align: 'right',
label: '',
name: 'tableActions',
actions: [
{
title: t('globals.pageTitles.summary'),
icon: 'preview',
action: (row) => viewSummary(row?.id, WorkerSummary),
isPrimary: true,
},
],
},
]);
onMounted(() => {
const canAccess = useAcl().hasAcl('Worker', 'management', 'WRITE');
});
</script>
<template>
<VnSearchbar
:data-key
url="Workers/filter"
:label="t('management.search')"
:info="t('management.searchInfo')"
/>
<RightMenu>
<template #right-panel>
<WorkerManagementFilter :data-key />
</template>
</RightMenu>
<VnTable
ref="tableRef"
url="Workers/filter"
:columns="columns"
:data-key
:right-search="false"
order="id DESC"
/>
</template>

View File

@ -0,0 +1,99 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t, te } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const departments = ref();
const getLocale = (label) => {
const globalLocale = `globals.params.${label}`;
return te(globalLocale) ? t(globalLocale) : t(`management.${label}`);
};
</script>
<template>
<FetchData url="Departments" @on-fetch="(data) => (departments = data)" auto-load />
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ getLocale(tag.label) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QItem>
<QItemSection>
<VnInput
:label="t('tableColumns.firstName')"
v-model="params.firstName"
filled
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('tableColumns.lastName')"
v-model="params.lastName"
filled
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('management.NIF')"
v-model="params.fi"
filled
data-cy="worker-filter-fi"
>
<template #prepend>
<QIcon name="badge" size="xs" />
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('tableColumns.email')"
v-model="params.email"
filled
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput :label="t('management.ssn')" v-model="params.SSN" filled />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('tableColumns.department')"
v-model="params.departmentFk"
:options="departments"
option-value="id"
option-label="name"
emit-value
map-options
dense
filled
/>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>

View File

@ -13,3 +13,11 @@ tableColumns:
SSN: SSN SSN: SSN
extension: Extension extension: Extension
queue: Queue queue: Queue
management:
search: Search worker
searchInfo: You can search by NIF, NSS or worker name
NIF: NIF
ssn: SSN
completeSsn: National Insurance Number
firstName: First Name
lastName: Last Name

View File

@ -16,3 +16,11 @@ tableColumns:
SSN: NSS SSN: NSS
extension: Extensión extension: Extensión
queue: Cola queue: Cola
management:
search: Buscar trabajador
searchInfo: Puedes buscar por NIF, NSS o nombre del trabajador
NIF: NIF
ssn: Núm. SS
completeSsn: Número de Seguridad Social
firstName: Nombre
lastName: Apellidos

View File

@ -89,7 +89,7 @@ watch(
v-model="formData.geoFk" v-model="formData.geoFk"
url="Postcodes/location" url="Postcodes/location"
:fields="['geoFk', 'code', 'townFk', 'countryFk']" :fields="['geoFk', 'code', 'townFk', 'countryFk']"
:sort-by="'code ASC'" :sort-by="['code ASC']"
option-value="geoFk" option-value="geoFk"
option-label="code" option-label="code"
:filter-options="['code']" :filter-options="['code']"

View File

@ -8,9 +8,9 @@ const claimCard = {
meta: { meta: {
menu: [ menu: [
'ClaimBasicData', 'ClaimBasicData',
'ClaimNotes',
'ClaimLines', 'ClaimLines',
'ClaimPhotos', 'ClaimPhotos',
'ClaimNotes',
'ClaimDevelopment', 'ClaimDevelopment',
'ClaimAction', 'ClaimAction',
'ClaimLog', 'ClaimLog',
@ -36,6 +36,15 @@ const claimCard = {
}, },
component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'), component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'),
}, },
{
path: 'notes',
name: 'ClaimNotes',
meta: {
title: 'notes',
icon: 'draft',
},
component: () => import('src/pages/Claim/Card/ClaimNotes.vue'),
},
{ {
path: 'lines', path: 'lines',
name: 'ClaimLines', name: 'ClaimLines',
@ -54,15 +63,6 @@ const claimCard = {
}, },
component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'), component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'),
}, },
{
path: 'notes',
name: 'ClaimNotes',
meta: {
title: 'notes',
icon: 'draft',
},
component: () => import('src/pages/Claim/Card/ClaimNotes.vue'),
},
{ {
path: 'development', path: 'development',
name: 'ClaimDevelopment', name: 'ClaimDevelopment',

View File

@ -225,7 +225,7 @@ const customerCard = {
name: 'CustomerCreditContractsInsurance', name: 'CustomerCreditContractsInsurance',
component: () => component: () =>
import( import(
'src/pages/Customer/components/CustomerCreditContractsInsurance.vue' 'src/pages/Customer/Card/CustomerCreditContractsInsurance.vue'
), ),
}, },
], ],

View File

@ -239,7 +239,7 @@ export default {
icon: 'vn:worker', icon: 'vn:worker',
moduleName: 'Worker', moduleName: 'Worker',
keyBinding: 'w', keyBinding: 'w',
menu: ['WorkerList', 'WorkerDepartment'], menu: ['WorkerList', 'WorkerDepartment', 'WorkerManagement'],
}, },
component: RouterView, component: RouterView,
redirect: { name: 'WorkerMain' }, redirect: { name: 'WorkerMain' },
@ -283,6 +283,22 @@ export default {
departmentCard, departmentCard,
], ],
}, },
{
path: 'management',
name: 'WorkerManagement',
meta: {
title: 'management',
icon: 'view_list',
acls: [
{
model: 'Worker',
props: 'isRoleAdvanced',
accessType: 'READ',
},
],
},
component: () => import('src/pages/Worker/WorkerManagement.vue'),
},
], ],
}, },
], ],

View File

@ -0,0 +1,25 @@
/// <reference types="cypress" />
describe('ClaimLines', () => {
const claimId = 1;
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/claim/${claimId}/lines`);
});
it('should add new line', () => {
cy.get('.q-page-sticky > div > .q-btn').click();
cy.get('.q-card > .q-table__container > .q-table__middle > .q-table > thead > tr > .q-table--col-auto-width > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg').click();
cy.get('.q-card__actions > .q-btn--unelevated').click();
cy.get('.q-notification__message').should('have.text', 'Lines added to claim');
})
it('should autofill claimed quantity if 0 or null', () => {
cy.get('thead > tr > .q-table--col-auto-width > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg').click();
cy.get('.q-btn--unelevated').click();
cy.checkNotification('Quantities filled automatically');
cy.get('.q-btn--unelevated').click();
cy.checkNotification('No quantities to fill');
})
});

View File

@ -1,5 +1,4 @@
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'; const firstNote = '.q-infinite-scroll :nth-child(1) > .q-card__section--vert';
beforeEach(() => { beforeEach(() => {
cy.login('developer'); cy.login('developer');
@ -11,9 +10,9 @@ describe('ClaimNotes', () => {
cy.get('.q-textarea') cy.get('.q-textarea')
.should('be.visible') .should('be.visible')
.should('not.be.disabled') .should('not.be.disabled')
.type(message); .type(message, "{{enter}}");
cy.get(saveBtn).click(); cy.dataCy("saveContinueNoteButton").click();
cy.get(firstNote).should('have.text', message); cy.get(firstNote).should('have.text', message);
}); });
}); });

View File

@ -7,7 +7,28 @@ describe('Client credit contracts', () => {
timeout: 5000, timeout: 5000,
}); });
}); });
it('Should load layout', () => { it('Should add a new contract and an additional credit', () => {
cy.get('.q-page').should('be.visible'); cy.dataCy('createBtn').click();
cy.dataCy('Credit_input').type(123);
cy.dataCy('Grade_input').type(9);
cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data created');
cy.dataCy('createBtn').should('not.exist');
});
it('Should add an additional credit', () => {
cy.dataCy('viewBtn').eq(0).click();
cy.get('.q-page-sticky > div').click();
cy.dataCy('Credit_input').type(321);
cy.dataCy('Grade_input').type(89);
cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data created');
});
it('Should close a contract', () => {
cy.dataCy('closeBtn').eq(0).click();
cy.get('.q-btn--unelevated').click();
cy.checkNotification('Data saved');
cy.dataCy('createBtn').should('exist');
}); });
}); });

View File

@ -23,7 +23,7 @@ describe('InvoiceOut list', () => {
cy.dataCy('InvoiceOutDownloadPdfBtn').click(); cy.dataCy('InvoiceOutDownloadPdfBtn').click();
}); });
it('should download all pdfs', () => { it.skip('should download all pdfs', () => {
cy.get(columnCheckbox).click(); cy.get(columnCheckbox).click();
cy.dataCy('InvoiceOutDownloadPdfBtn').click(); cy.dataCy('InvoiceOutDownloadPdfBtn').click();
}); });
@ -31,7 +31,7 @@ describe('InvoiceOut list', () => {
it('should open the invoice descriptor from table icon', () => { it('should open the invoice descriptor from table icon', () => {
cy.get(firstSummaryIcon).click(); cy.get(firstSummaryIcon).click();
cy.get('.cardSummary').should('be.visible'); cy.get('.cardSummary').should('be.visible');
cy.get('.summaryHeader > div').should('include.text', 'V10100001'); cy.get('.summaryHeader > div').should('include.text', 'A1111111');
}); });
it('should open the client descriptor', () => { it('should open the client descriptor', () => {

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
describe('OrderCatalog', () => { describe.skip('OrderCatalog', () => {
beforeEach(() => { beforeEach(() => {
cy.login('developer'); cy.login('developer');
cy.viewport(1920, 1080); cy.viewport(1920, 1080);

View File

@ -2,6 +2,7 @@
describe('TicketList', () => { describe('TicketList', () => {
beforeEach(() => { beforeEach(() => {
cy.login('developer'); cy.login('developer');
cy.viewport(1920, 1080);
cy.visit('/#/ticket/list', false); cy.visit('/#/ticket/list', false);
}); });
@ -76,8 +77,8 @@ describe('TicketList', () => {
}); });
}).as('ticket'); }).as('ticket');
cy.get('[data-cy="Warehouse_select"]').type('Warehouse One'); cy.selectOption('[data-cy="Warehouse_select"]', 'Warehouse One');
cy.get('.q-menu .q-item').contains('Warehouse One').click(); cy.searchBtnFilterPanel();
cy.wait('@ticket').then((interception) => { cy.wait('@ticket').then((interception) => {
const data = interception.response.body[0]; const data = interception.response.body[0];
expect(data.hasComponentLack).to.equal(1); expect(data.hasComponentLack).to.equal(1);

View File

@ -0,0 +1,23 @@
describe('WorkerManagement', () => {
const nif = '12091201A';
const searchButton = '.q-scrollarea__content > .q-btn--standard > .q-btn__content';
const url = '/#/worker/management';
it('should not enter the section', () => {
cy.login('salesPerson');
cy.visit(url);
cy.url().should('include', '/dashboard');
});
it('should enter the section and filter by NIF, then open the worker summary popup', () => {
cy.login('hr');
cy.visit(url);
cy.dataCy('worker-filter-fi').type(nif);
cy.get(searchButton).click();
cy.dataCy('tableAction-0').click();
cy.get('.summaryHeader').should('exist');
cy.get('.summaryHeader').should('be.visible');
cy.get('.summaryBody').should('exist');
cy.get('.summaryBody').should('be.visible');
});
});