0
0
Fork 0

Merge branch 'dev' into feature/TicketVolume

This commit is contained in:
Javier Segarra 2024-07-01 14:48:12 +02:00
commit 9bb75b8305
35 changed files with 339 additions and 153 deletions

View File

@ -262,7 +262,7 @@ defineExpose({
<template> <template>
<div class="column items-center full-width"> <div class="column items-center full-width">
<QForm <QForm
v-if="formData"
@submit="save" @submit="save"
@reset="reset" @reset="reset"
class="q-pa-md" class="q-pa-md"
@ -270,11 +270,13 @@ defineExpose({
> >
<QCard> <QCard>
<slot <slot
v-if="formData"
name="form" name="form"
:data="formData" :data="formData"
:validate="validate" :validate="validate"
:filter="filter" :filter="filter"
/> />
<SkeletonForm v-else/>
</QCard> </QCard>
</QForm> </QForm>
</div> </div>
@ -337,7 +339,7 @@ defineExpose({
</QBtnGroup> </QBtnGroup>
</div> </div>
</Teleport> </Teleport>
<SkeletonForm v-if="!formData" />
<QInnerLoading <QInnerLoading
:showing="isLoading" :showing="isLoading"
:label="t('globals.pleaseWait')" :label="t('globals.pleaseWait')"

View File

@ -70,14 +70,11 @@ const makeInvoice = async () => {
}); });
}); });
if (!response) { if (!response) {
console.log('entra cuando no checkbox');
return; return;
} }
} }
console.log('params: ', params);
const { data } = await axios.post('InvoiceOuts/transferInvoice', params); const { data } = await axios.post('InvoiceOuts/transferInvoice', params);
console.log('data: ', data);
notify(t('Transferred invoice'), 'positive'); notify(t('Transferred invoice'), 'positive');
const id = data?.[0]; const id = data?.[0];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } }); if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });

View File

@ -187,15 +187,10 @@ function existSummary(routes) {
color: lighten($primary, 20%); color: lighten($primary, 20%);
} }
.q-checkbox { .q-checkbox {
display: flex;
margin-bottom: 9px;
& .q-checkbox__label { & .q-checkbox__label {
margin-left: 31px;
color: var(--vn-text-color); color: var(--vn-text-color);
} }
& .q-checkbox__inner { & .q-checkbox__inner {
position: absolute;
left: 0;
color: var(--vn-label-color); color: var(--vn-label-color);
} }
} }

View File

@ -1,20 +1,14 @@
<template> <template>
<div class="q-pa-md"> <div class="row q-gutter-md q-mb-md">
<div class="row q-gutter-md q-mb-md"> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> </div>
</div> <div class="row q-gutter-md q-mb-md">
<div class="row q-gutter-md q-mb-md"> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> </div>
</div> <div class="row q-gutter-md q-mb-md">
<div class="row q-gutter-md q-mb-md"> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square />
</div>
<div class="row q-gutter-md">
<QSkeleton type="QBtn" />
<QSkeleton type="QBtn" />
</div>
</div> </div>
</template> </template>

View File

@ -7,8 +7,11 @@ import toDate from 'filters/toDate';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
const { t } = useI18n(); const { t } = useI18n();
const params = defineModel({ default: {}, required: true, type: Object });
const $props = defineProps({ const $props = defineProps({
modelValue: {
type: Object,
default: () => {}
},
dataKey: { dataKey: {
type: String, type: String,
required: true, required: true,
@ -60,13 +63,14 @@ const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
const arrayData = useArrayData($props.dataKey, { const arrayData = useArrayData($props.dataKey, {
exprBuilder: $props.exprBuilder, exprBuilder: $props.exprBuilder,
searchUrl: $props.searchUrl, searchUrl: $props.searchUrl,
navigate: {}, navigate: $props.redirect ? {} : null,
}); });
const route = useRoute(); const route = useRoute();
const store = arrayData.store; const store = arrayData.store;
const userParams = ref({})
onMounted(() => { onMounted(() => {
emit('init', { params: params.value }); userParams.value = $props.modelValue ?? {}
emit('init', { params: userParams.value });
}); });
function setUserParams(watchedParams) { function setUserParams(watchedParams) {
@ -75,7 +79,7 @@ function setUserParams(watchedParams) {
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; watchedParams = { ...watchedParams, ...watchedParams.filter?.where };
delete watchedParams.filter; delete watchedParams.filter;
params.value = { ...params.value, ...watchedParams }; userParams.value = { ...userParams.value, ...watchedParams };
} }
watch( watch(
@ -94,12 +98,12 @@ async function search(evt) {
store.filter.where = {}; store.filter.where = {};
isLoading.value = true; isLoading.value = true;
const filter = { ...params.value }; const filter = { ...userParams.value };
store.userParamsChanged = true; store.userParamsChanged = true;
store.filter.skip = 0; store.filter.skip = 0;
store.skip = 0; store.skip = 0;
const { params: newParams } = await arrayData.addFilter({ params: params.value }); const { params: newParams } = await arrayData.addFilter({ params: userParams.value });
params.value = newParams; userParams.value = newParams;
if (!$props.showAll && !Object.values(filter).length) store.data = []; if (!$props.showAll && !Object.values(filter).length) store.data = [];
@ -109,7 +113,7 @@ async function search(evt) {
async function reload() { async function reload() {
isLoading.value = true; isLoading.value = true;
const params = Object.values(params.value).filter((param) => param); const params = Object.values(userParams.value).filter((param) => param);
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
if (!$props.showAll && !params.length) store.data = []; if (!$props.showAll && !params.length) store.data = [];
@ -123,17 +127,17 @@ async function clearFilters() {
store.filter.skip = 0; store.filter.skip = 0;
store.skip = 0; store.skip = 0;
// Filtrar los params no removibles // Filtrar los params no removibles
const removableFilters = Object.keys(params.value).filter((param) => const removableFilters = Object.keys(userParams.value).filter((param) =>
$props.unremovableParams.includes(param) $props.unremovableParams.includes(param)
); );
const newParams = {}; const newParams = {};
// Conservar solo los params que no son removibles // Conservar solo los params que no son removibles
for (const key of removableFilters) { for (const key of removableFilters) {
newParams[key] = params.value[key]; newParams[key] = userParams.value[key];
} }
params.value = {}; userParams.value = {};
params.value = { ...newParams }; // Actualizar los params con los removibles userParams.value = { ...newParams }; // Actualizar los params con los removibles
await arrayData.applyFilter({ params: params.value }); await arrayData.applyFilter({ params: userParams.value });
if (!$props.showAll) { if (!$props.showAll) {
store.data = []; store.data = [];
@ -145,8 +149,8 @@ async function clearFilters() {
const tagsList = computed(() => { const tagsList = computed(() => {
const tagList = []; const tagList = [];
for (const key of Object.keys(params.value)) { for (const key of Object.keys(userParams.value)) {
const value = params.value[key]; const value = userParams.value[key];
if (value == null || ($props.hiddenTags || []).includes(key)) continue; if (value == null || ($props.hiddenTags || []).includes(key)) continue;
tagList.push({ label: key, value }); tagList.push({ label: key, value });
} }
@ -161,7 +165,7 @@ const customTags = computed(() =>
); );
async function remove(key) { async function remove(key) {
params.value[key] = undefined; userParams.value[key] = undefined;
search(); search();
emit('remove', key); emit('remove', key);
} }
@ -236,7 +240,7 @@ function formatValue(value) {
<slot <slot
v-if="$slots.customTags" v-if="$slots.customTags"
name="customTags" name="customTags"
:params="params" :params="userParams"
:tags="customTags" :tags="customTags"
:format-fn="formatValue" :format-fn="formatValue"
:search-fn="search" :search-fn="search"
@ -246,7 +250,7 @@ function formatValue(value) {
<QSeparator /> <QSeparator />
</QList> </QList>
<QList dense class="list q-gutter-y-sm q-mt-sm"> <QList dense class="list q-gutter-y-sm q-mt-sm">
<slot name="body" :params="params" :search-fn="search"></slot> <slot name="body" :params="userParams" :search-fn="search"></slot>
</QList> </QList>
<template v-if="$props.searchButton"> <template v-if="$props.searchButton">
<QItem> <QItem>

View File

@ -2,6 +2,7 @@
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
import { computed } from 'vue';
const $props = defineProps({ const $props = defineProps({
label: { type: String, default: null }, label: { type: String, default: null },
@ -24,52 +25,67 @@ function copyValueText() {
}, },
}); });
} }
const val = computed(() => $props.value);
</script> </script>
<style scoped>
.label,
.value {
white-space: pre-line;
word-wrap: break-word;
}
</style>
<template> <template>
<div class="vn-label-value"> <div class="vn-label-value">
<div v-if="$props.label || $slots.label" class="label"> <QCheckbox
<slot name="label"> v-if="typeof value === 'boolean'"
<span>{{ $props.label }}</span> v-model="val"
</slot> :label="label"
</div> disable
<div class="value"> dense
<slot name="value"> />
<span :title="$props.value"> <template v-else>
{{ $props.dash ? dashIfEmpty($props.value) : $props.value }} <div v-if="label || $slots.label" class="label">
</span> <slot name="label">
</slot> <span>{{ label }}</span>
</div> </slot>
<div class="info" v-if="$props.info"> </div>
<QIcon name="info" class="cursor-pointer" size="xs" color="grey"> <div class="value">
<QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]"> <slot name="value">
{{ $props.info }} <span :title="value">
</QTooltip> {{ dash ? dashIfEmpty(value) : value }}
</QIcon> </span>
</div> </slot>
<div class="copy" v-if="$props.copy && $props.value" @click="copyValueText()"> </div>
<QIcon name="Content_Copy" color="primary"> <div class="info" v-if="info">
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip> <QIcon name="info" class="cursor-pointer" size="xs" color="grey">
</QIcon> <QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]">
</div> {{ info }}
</QTooltip>
</QIcon>
</div>
<div class="copy" v-if="copy && value" @click="copyValueText()">
<QIcon name="Content_Copy" color="primary">
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip>
</QIcon>
</div>
</template>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.vn-label-value:hover .copy { .vn-label-value {
visibility: visible; &:hover .copy {
cursor: pointer; visibility: visible;
cursor: pointer;
}
.label,
.value {
white-space: pre-line;
word-wrap: break-word;
}
.copy {
visibility: hidden;
}
.info {
margin-left: 5px;
}
} }
.copy {
visibility: hidden; :deep(.q-checkbox.disabled) {
} opacity: 1 !important;
.info {
margin-left: 5px;
} }
</style> </style>

View File

@ -21,7 +21,7 @@ const currentUser = ref(state.getUser());
const newNote = ref(''); const newNote = ref('');
const vnPaginateRef = ref(); const vnPaginateRef = ref();
function handleKeyUp(event) { function handleKeyUp(event) {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
if (!event.shiftKey) insert(); if (!event.shiftKey) insert();
} }
@ -78,6 +78,7 @@ async function insert() {
ref="vnPaginateRef" ref="vnPaginateRef"
class="show" class="show"
v-bind="$attrs" v-bind="$attrs"
search-url="notes"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<TransitionGroup name="list" tag="div" class="column items-center full-width"> <TransitionGroup name="list" tag="div" class="column items-center full-width">

View File

@ -29,7 +29,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const filter = params?.filter; const filter = params?.filter;
delete params.filter; delete params.filter;
store.userParams = { ...params, ...store.userParams }; store.userParams = { ...params, ...store.userParams };
store.userFilter = { ...JSON.parse(filter), ...store.userFilter }; store.userFilter = { ...JSON.parse(filter ?? '{}'), ...store.userFilter };
} }
}); });
@ -73,7 +73,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const filter = { const filter = {
order: store.order, order: store.order,
limit: store.limit, limit: store.limit,
skip: store.skip,
}; };
let exprFilter; let exprFilter;
@ -88,7 +87,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
} }
Object.assign(filter, store.userFilter, exprFilter); Object.assign(filter, store.userFilter, exprFilter);
Object.assign(store.filter, filter); Object.assign(store.filter, { ...filter, skip: store.skip });
const params = { const params = {
filter: JSON.stringify(store.filter), filter: JSON.stringify(store.filter),
}; };

View File

@ -152,6 +152,15 @@ select:-webkit-autofill {
color: var(--vn-label-color); color: var(--vn-label-color);
} }
.disabled {
& .q-checkbox__label {
color: var(--vn-text-color);
}
& .q-checkbox__inner {
color: var(--vn-label-color);
}
}
.q-chip, .q-chip,
.q-notification__message, .q-notification__message,
.q-notification__icon { .q-notification__icon {

View File

@ -445,6 +445,7 @@ ticket:
notes: Notes notes: Notes
sale: Sale sale: Sale
volume: Volume volume: Volume
observation: Notes
ticketAdvance: Advance tickets ticketAdvance: Advance tickets
futureTickets: Future tickets futureTickets: Future tickets
purchaseRequest: Purchase request purchaseRequest: Purchase request

View File

@ -444,6 +444,7 @@ ticket:
notes: Notas notes: Notas
sale: Lineas del pedido sale: Lineas del pedido
volume: Volumen volume: Volumen
observation: Notas
ticketAdvance: Adelantar tickets ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro futureTickets: Tickets a futuro
purchaseRequest: Petición de compra purchaseRequest: Petición de compra

View File

@ -15,6 +15,10 @@ const $props = defineProps({
required: false, required: false,
default: null, default: null,
}, },
summary: {
type: Object,
default: null,
},
}); });
const route = useRoute(); const route = useRoute();
@ -60,14 +64,14 @@ const removeRole = () => {
<template> <template>
<CardDescriptor <CardDescriptor
ref="descriptor" :url="`VnRoles/${entityId}`"
:url="`VnRoles`"
:filter="filter" :filter="filter"
module="Role" module="Role"
@on-fetch="setData" @on-fetch="setData"
data-key="accountData" data-key="accountData"
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
:summary="$props.summary"
> >
<template #menu> <template #menu>
<QItem v-ripple clickable @click="removeRole()"> <QItem v-ripple clickable @click="removeRole()">

View File

@ -0,0 +1,17 @@
<script setup>
import RoleDescriptor from './RoleDescriptor.vue';
import RoleSummary from './RoleSummary.vue';
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
</script>
<template>
<QPopupProxy>
<RoleDescriptor v-if="$props.id" :id="$props.id" :summary="RoleSummary" />
</QPopupProxy>
</template>

View File

@ -30,6 +30,7 @@ const filter = {
:url="`VnRoles`" :url="`VnRoles`"
:filter="filter" :filter="filter"
@on-fetch="(data) => (role = data)" @on-fetch="(data) => (role = data)"
data-key="RoleSummary"
> >
<template #header> {{ role.id }} - {{ role.name }} </template> <template #header> {{ role.id }} - {{ role.name }} </template>
<template #body> <template #body>

View File

@ -91,7 +91,6 @@ const tableColumnComponents = {
props: (prop) => ({ props: (prop) => ({
disable: true, disable: true,
'model-value': prop.value, 'model-value': prop.value,
class: 'disabled-checkbox',
}), }),
event: () => {}, event: () => {},
}, },

View File

@ -119,7 +119,7 @@ const departments = ref();
emit-value emit-value
hide-selected hide-selected
map-options map-options
option-label="country" option-label="name"
option-value="id" option-value="id"
outlined outlined
rounded rounded

View File

@ -150,21 +150,19 @@ const downloadCSV = async () => {
> >
<template #body-cell-clientId="{ row }"> <template #body-cell-clientId="{ row }">
<QTd> <QTd>
<QBtn flat dense color="blue"> {{ row.clientId }}</QBtn> <QBtn flat dense class="link"> {{ row.clientId }}</QBtn>
<CustomerDescriptorProxy :id="row.clientId" /> <CustomerDescriptorProxy :id="row.clientId" />
</QTd> </QTd>
</template> </template>
<template #body-cell-ticketId="{ row }"> <template #body-cell-ticketId="{ row }">
<QTd> <QTd>
<QBtn flat dense color="blue"> {{ row.ticketFk }}</QBtn> <QBtn flat dense class="link"> {{ row.ticketFk }}</QBtn>
<TicketDescriptorProxy :id="row.ticketFk" /> <TicketDescriptorProxy :id="row.ticketFk" />
</QTd> </QTd>
</template> </template>
<template #body-cell-worker="{ row }"> <template #body-cell-worker="{ row }">
<QTd> <QTd>
<QBtn class="no-uppercase" flat dense color="blue">{{ <QBtn class="no-uppercase link" flat dense>{{ row.workerName }}</QBtn>
row.workerName
}}</QBtn>
<WorkerDescriptorProxy :id="row.comercialId" /> <WorkerDescriptorProxy :id="row.comercialId" />
</QTd> </QTd>
</template> </template>

View File

@ -20,6 +20,7 @@ const props = defineProps({
:data-key="props.dataKey" :data-key="props.dataKey"
:search-button="true" :search-button="true"
:unremovable-params="['from', 'to']" :unremovable-params="['from', 'to']"
:hidden-tags="['from', 'to']"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">

View File

@ -156,22 +156,18 @@ const onOrderFieldChange = (value, params) => {
case 'Relevancy': case 'Relevancy':
tagObj.field = value + ' DESC, name'; tagObj.field = value + ' DESC, name';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
case 'ColorAndPrice': case 'ColorAndPrice':
tagObj.field = 'showOrder, price'; tagObj.field = 'showOrder, price';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
case 'Name': case 'Name':
tagObj.field = 'name'; tagObj.field = 'name';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
case 'Price': case 'Price':
tagObj.field = 'price'; tagObj.field = 'price';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
} }
}; };
@ -219,6 +215,7 @@ const useLang = (values) => {
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:custom-tags="['tagGroups']" :custom-tags="['tagGroups']"
@remove="clearFilter" @remove="clearFilter"
:redirect="false"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'categoryFk'"> <strong v-if="tag.label === 'categoryFk'">

View File

@ -122,8 +122,6 @@ const orderFilter = {
const onClientChange = async (clientId) => { const onClientChange = async (clientId) => {
try { try {
const { data } = await axios.get(`Clients/${clientId}`); const { data } = await axios.get(`Clients/${clientId}`);
console.log('info cliente: ', data);
await fetchAddressList(data.defaultAddressFk); await fetchAddressList(data.defaultAddressFk);
} catch (error) { } catch (error) {
console.error('Error al cambiar el cliente:', error); console.error('Error al cambiar el cliente:', error);

View File

@ -104,10 +104,14 @@ const totalEntryPrice = (rows) => {
for (const row of rows) { for (const row of rows) {
let total = 0; let total = 0;
let quantity = 0; let quantity = 0;
for (const buy of row.buys) {
total = total + buy.total; if (row.buys) {
quantity = quantity + buy.quantity; for (const buy of row.buys) {
total = total + buy.total;
quantity = quantity + buy.quantity;
}
} }
row.total = total; row.total = total;
row.quantity = quantity; row.quantity = quantity;
totalPrice = totalPrice + total; totalPrice = totalPrice + total;

View File

@ -50,7 +50,7 @@ const itemCategoriesOptions = ref([]);
@on-fetch="(data) => (itemCategoriesOptions = data)" @on-fetch="(data) => (itemCategoriesOptions = data)"
auto-load auto-load
/> />
<VnFilterPanel :data-key="props.dataKey" :search-button="true"> <VnFilterPanel :data-key="props.dataKey" :search-button="true" :redirect="false">
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ t(`params.${tag.label}`) }}: </strong>

View File

@ -71,7 +71,7 @@ const filter = {
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => const setData = (entity) =>
(data.value = useCardDescription(entity.client.name, entity.id)); (data.value = useCardDescription(entity.client?.name, entity.id));
</script> </script>
<template> <template>
@ -92,7 +92,7 @@ const setData = (entity) =>
<template #value> <template #value>
<span class="link"> <span class="link">
{{ entity.clientFk }} {{ entity.clientFk }}
<CustomerDescriptorProxy :id="entity.client.id" /> <CustomerDescriptorProxy :id="entity.client?.id" />
</span> </span>
</template> </template>
</VnLv> </VnLv>
@ -109,8 +109,8 @@ const setData = (entity) =>
<VnLv :label="t('ticket.summary.salesPerson')"> <VnLv :label="t('ticket.summary.salesPerson')">
<template #value> <template #value>
<VnUserLink <VnUserLink
:name="entity.client.salesPersonUser?.name" :name="entity.client?.salesPersonUser?.name"
:worker-id="entity.client.salesPersonFk" :worker-id="entity.client?.salesPersonFk"
/> />
</template> </template>
</VnLv> </VnLv>

View File

@ -0,0 +1,106 @@
<script setup>
import { ref, watch, computed, reactive } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CrudModel from 'components/CrudModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useArrayData } from 'src/composables/useArrayData';
const route = useRoute();
const { t } = useI18n();
const ticketNotesCrudRef = ref(null);
const observationTypes = ref([]);
const arrayData = useArrayData('TicketNotes');
const { store } = arrayData;
const crudModelFilter = reactive({
where: { ticketFk: route.params.id },
fields: ['id', 'ticketFk', 'observationTypeFk', 'description'],
});
const crudModelRequiredData = computed(() => ({ ticketFk: route.params.id }));
watch(
() => route.params.id,
async () => {
crudModelFilter.where.ticketFk = route.params.id;
store.filter = crudModelFilter;
await ticketNotesCrudRef.value.reload();
}
);
</script>
<template>
<FetchData
@on-fetch="(data) => (observationTypes = data)"
auto-load
url="ObservationTypes"
/>
<div class="flex justify-center">
<CrudModel
ref="ticketNotesCrudRef"
data-key="TicketNotes"
url="TicketObservations"
model="TicketNotes"
:filter="crudModelFilter"
:data-required="crudModelRequiredData"
:default-remove="false"
auto-load
style="max-width: 800px"
>
<template #body="{ rows }">
<QCard class="q-px-lg q-py-md">
<div
v-for="(row, index) in rows"
:key="index"
class="q-mb-md row items-center q-gutter-x-md"
>
<VnSelect
:label="t('ticketNotes.observationType')"
:options="observationTypes"
hide-selected
option-label="description"
option-value="id"
v-model="row.observationTypeFk"
:disable="!!row.id"
/>
<VnInput
:label="t('ticketNotes.description')"
v-model="row.description"
class="col"
/>
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary"
@click="ticketNotesCrudRef.remove([row])"
>
<QTooltip>
{{ t('ticketNotes.removeNote') }}
</QTooltip>
</QIcon>
</div>
<VnRow v-if="observationTypes.length > rows.length">
<QIcon
name="add_circle"
class="fill-icon-on-hover q-ml-md"
size="sm"
color="primary"
@click="ticketNotesCrudRef.insert()"
>
<QTooltip>
{{ t('ticketNotes.addNote') }}
</QTooltip>
</QIcon>
</VnRow>
</QCard>
</template>
</CrudModel>
</div>
</template>

View File

@ -9,6 +9,11 @@ volume:
volumeQuantity: m³ per quantity volumeQuantity: m³ per quantity
type: Type type: Type
volume: Volume volume: Volume
ticketNotes:
observationType: Observation type
description: Description
removeNote: Remove note
addNote: Add note
ticketSale: ticketSale:
id: Id id: Id
visible: Visible visible: Visible

View File

@ -9,6 +9,11 @@ volume:
volumeQuantity: m³ por cantidad volumeQuantity: m³ por cantidad
type: Tipo type: Tipo
volume: Volumen volume: Volumen
ticketNotes:
observationType: Tipo de observación
description: Descripción
removeNote: Quitar nota
addNote: Añadir nota
purchaseRequest: purchaseRequest:
Id: Id Id: Id
description: Descripción description: Descripción

View File

@ -7,6 +7,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.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 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -20,6 +21,7 @@ const warehousesOptions = ref([]);
const continentsOptions = ref([]); const continentsOptions = ref([]);
const agenciesOptions = ref([]); const agenciesOptions = ref([]);
const suppliersOptions = ref([]); const suppliersOptions = ref([]);
const warehousesByContinent = ref({});
const add = (paramsObj, key) => { const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) { if (paramsObj[key] === undefined) {
@ -34,6 +36,28 @@ const decrement = (paramsObj, key) => {
paramsObj[key]--; paramsObj[key]--;
}; };
const warehouses = async () => {
const warehousesResponse = await axios.get('Warehouses');
const countriesResponse = await axios.get('Countries');
const continentsResponse = await axios.get('Continents');
const countryContinentMap = countriesResponse.data.reduce((acc, country) => {
acc[country.id] = country.continentFk;
return acc;
}, {});
continentsResponse.data.forEach((continent) => {
const countriesInContinent = Object.keys(countryContinentMap).filter(
(countryId) => countryContinentMap[countryId] === continent.id.toString()
);
warehousesByContinent.value[continent.code] = warehousesResponse.data.filter(
(warehouse) => countriesInContinent.includes(warehouse.countryFk.toString())
);
});
};
warehouses();
</script> </script>
<template> <template>
@ -116,7 +140,6 @@ const decrement = (paramsObj, key) => {
<VnSelect <VnSelect
:label="t('params.agencyModeFk')" :label="t('params.agencyModeFk')"
v-model="params.agencyModeFk" v-model="params.agencyModeFk"
@update:model-value="searchFn()"
:options="agenciesOptions" :options="agenciesOptions"
option-value="agencyFk" option-value="agencyFk"
option-label="name" option-label="name"
@ -147,12 +170,26 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem v-if="warehousesByContinent[params.continent]">
<QItemSection>
<VnSelect
:label="t('params.warehouseOutFk')"
v-model="params.warehouseOutFk"
:options="warehousesByContinent[params.continent]"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem v-else>
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="t('params.warehouseOutFk')" :label="t('params.warehouseOutFk')"
v-model="params.warehouseOutFk" v-model="params.warehouseOutFk"
@update:model-value="searchFn()"
:options="warehousesOptions" :options="warehousesOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
@ -168,7 +205,6 @@ const decrement = (paramsObj, key) => {
<VnSelect <VnSelect
:label="t('params.warehouseInFk')" :label="t('params.warehouseInFk')"
v-model="params.warehouseInFk" v-model="params.warehouseInFk"
@update:model-value="searchFn()"
:options="warehousesOptions" :options="warehousesOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
@ -184,7 +220,6 @@ const decrement = (paramsObj, key) => {
<VnSelect <VnSelect
:label="t('supplier.pageTitles.supplier')" :label="t('supplier.pageTitles.supplier')"
v-model="params.cargoSupplierFk" v-model="params.cargoSupplierFk"
@update:model-value="searchFn()"
:options="suppliersOptions" :options="suppliersOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
@ -200,7 +235,6 @@ const decrement = (paramsObj, key) => {
<VnSelect <VnSelect
:label="t('params.continent')" :label="t('params.continent')"
v-model="params.continent" v-model="params.continent"
@update:model-value="searchFn()"
:options="continentsOptions" :options="continentsOptions"
option-value="code" option-value="code"
option-label="name" option-label="name"

View File

@ -56,7 +56,6 @@ const swapEntry = (from, to, key) => {
}; };
function setNotifications(data) { function setNotifications(data) {
console.log('data: ', data);
active.value = new Map(data.active); active.value = new Map(data.active);
available.value = new Map(data.available); available.value = new Map(data.available);
} }

View File

@ -9,6 +9,7 @@ import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -161,7 +162,14 @@ const filter = {
<VnTitle :text="t('worker.summary.userData')" /> <VnTitle :text="t('worker.summary.userData')" />
<VnLv :label="t('worker.summary.userId')" :value="worker.user.id" /> <VnLv :label="t('worker.summary.userId')" :value="worker.user.id" />
<VnLv :label="t('worker.card.name')" :value="worker.user.nickname" /> <VnLv :label="t('worker.card.name')" :value="worker.user.nickname" />
<VnLv :label="t('worker.summary.role')" :value="worker.user.role.name" /> <VnLv :label="t('worker.summary.role')">
<template #value>
<span class="link">
{{ worker.user.role.name }}
<RoleDescriptorProxy :id="worker.user.role.id" />
</span>
</template>
</VnLv>
<VnLv :value="worker?.sip?.extension"> <VnLv :value="worker?.sip?.extension">
<template #label> <template #label>
{{ t('worker.summary.sipExtension') }} {{ t('worker.summary.sipExtension') }}

View File

@ -20,6 +20,7 @@ export default {
'TicketLog', 'TicketLog',
'TicketPurchaseRequest', 'TicketPurchaseRequest',
'TicketVolume', 'TicketVolume',
'TicketNotes',
], ],
}, },
children: [ children: [
@ -157,6 +158,15 @@ export default {
}, },
component: () => import('src/pages/Ticket/Card/TicketVolume.vue'), component: () => import('src/pages/Ticket/Card/TicketVolume.vue'),
}, },
{
path: 'observation',
name: 'TicketNotes',
meta: {
title: 'notes',
icon: 'vn:notes',
},
component: () => import('src/pages/Ticket/Card/TicketNotes.vue'),
},
], ],
}, },
], ],

View File

@ -2,13 +2,10 @@ describe('AgencyWorkCenter', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1920, 1080); cy.viewport(1920, 1080);
cy.login('developer'); cy.login('developer');
cy.visit(`/#/agency`); cy.visit(`/#/agency/11/workCenter`);
}); });
it('assign workCenter', () => { it('assign workCenter', () => {
cy.visit(`/#/agency`);
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get( cy.get(
'.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'
@ -17,8 +14,6 @@ describe('AgencyWorkCenter', () => {
}); });
it('delete workCenter', () => { it('delete workCenter', () => {
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
cy.get('.q-item__section--side > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-item__section--side > .q-btn > .q-btn__content > .q-icon').click();
cy.get('.q-notification__message').should( cy.get('.q-notification__message').should(
'have.text', 'have.text',
@ -27,9 +22,6 @@ describe('AgencyWorkCenter', () => {
}); });
it('error on duplicate workCenter', () => { it('error on duplicate workCenter', () => {
cy.visit(`/#/agency`);
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get( cy.get(
'.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'

View File

@ -6,15 +6,14 @@ describe('InvoiceInCorrective', () => {
const saveDialog = '.q-card > .q-card__actions > .q-btn--standard '; const saveDialog = '.q-card > .q-card__actions > .q-btn--standard ';
it('should create a correcting invoice', () => { it('should create a correcting invoice', () => {
cy.viewport(1280, 720);
cy.login('developer'); cy.login('developer');
cy.visit(`/#/invoice-in/1/summary?limit=10`); cy.visit(`/#/invoice-in/1/summary?limit=10`);
cy.openLeftMenu();
cy.openActionsDescriptor(); cy.openActionsDescriptor();
cy.get(createRectificative).click(); cy.get(createRectificative).click();
cy.get(saveDialog).click(); cy.get(saveDialog).click();
cy.openLeftMenu();
cy.get(rectificativeSection).click(); cy.get(rectificativeSection).click();
cy.get('tbody > tr:visible').should('have.length', 1); cy.get('tbody > tr:visible').should('have.length', 1);
}); });

View File

@ -1,24 +1,20 @@
describe('InvoiceInDescriptor', () => { describe('InvoiceInDescriptor', () => {
const dialogBtns = '.q-card__actions button'; const dialogBtns = '.q-card__actions button';
const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section'; const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section';
const isBookedField = const isBookedField = '.q-card:nth-child(3) .vn-label-value:nth-child(5) .q-checkbox';
'.q-card:nth-child(3) .vn-label-value:nth-child(5) > .value > span';
it('should booking and unbooking the invoice properly', () => { it('should booking and unbooking the invoice properly', () => {
cy.viewport(1280, 720);
cy.login('developer'); cy.login('developer');
cy.visit(`/#/invoice-in/1/summary?limit=10`); cy.visit('/#/invoice-in/1/summary');
cy.openLeftMenu();
cy.openActionsDescriptor(); cy.openActionsDescriptor();
cy.get(firstDescritorOpt).click(); cy.get(firstDescritorOpt).click();
cy.get(dialogBtns).eq(1).click(); cy.get(dialogBtns).eq(1).click();
cy.get('.fullscreen').first().click(); cy.get(isBookedField).should('have.attr', 'aria-checked', 'true');
cy.get(isBookedField).should('have.attr', 'title', 'true');
cy.openLeftMenu();
cy.openActionsDescriptor();
cy.get(firstDescritorOpt).click(); cy.get(firstDescritorOpt).click();
cy.get(dialogBtns).eq(1).click(); cy.get(dialogBtns).eq(1).click();
cy.get(isBookedField).should('have.attr', 'title', 'false'); cy.get(isBookedField).should('have.attr', 'aria-checked', 'false');
}); });
}); });

View File

@ -3,13 +3,10 @@ describe('VnSearchBar', () => {
const employeeId = ' #1'; const employeeId = ' #1';
const salesPersonId = ' #18'; const salesPersonId = ' #18';
const idGap = '.q-item > .q-item__label'; const idGap = '.q-item > .q-item__label';
const cardList = '.vn-card-list'; const vnTableRow = '.q-virtual-scroll__content';
let url;
beforeEach(() => { beforeEach(() => {
cy.login('developer'); cy.login('developer');
cy.visit('#/customer/list'); cy.visit('#/customer/list');
cy.url().then((currentUrl) => (url = currentUrl));
}); });
it('should redirect to customer summary page', () => { it('should redirect to customer summary page', () => {
@ -19,12 +16,12 @@ describe('VnSearchBar', () => {
it('should stay on the list page if there are several results or none', () => { it('should stay on the list page if there are several results or none', () => {
cy.writeSearchbar('salesA{enter}'); cy.writeSearchbar('salesA{enter}');
checkCardListAndUrl(2); checkTableLength(2);
cy.clearSearchbar(); cy.clearSearchbar();
cy.writeSearchbar('0{enter}'); cy.writeSearchbar('0{enter}');
checkCardListAndUrl(0); checkTableLength(0);
}); });
const searchAndCheck = (searchTerm, expectedText) => { const searchAndCheck = (searchTerm, expectedText) => {
@ -33,10 +30,7 @@ describe('VnSearchBar', () => {
cy.get(idGap).should('have.text', expectedText); cy.get(idGap).should('have.text', expectedText);
}; };
const checkCardListAndUrl = (expectedLength) => { const checkTableLength = (expectedLength) => {
cy.get(cardList).then(($cardList) => { cy.get(vnTableRow).find('tr').should('have.length', expectedLength);
expect($cardList.find('.q-card').length).to.equal(expectedLength);
cy.url().then((currentUrl) => expect(currentUrl).to.contain(url));
});
}; };
}); });