0
0
Fork 0

Compare commits

...

53 Commits

Author SHA1 Message Date
William Buezas 4bfbd1925e test: refs #8162 ticket list e2e tests 2024-11-11 15:18:29 -03:00
Javier Segarra c22c8d9f11 Merge pull request 'Improve required attrs for VnInput*' (!886) from improve_isRequired_input into dev
Reviewed-on: verdnatura/salix-front#886
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2024-11-08 10:47:56 +00:00
Javier Segarra ead326c66b Merge branch 'improve_isRequired_input' of https://gitea.verdnatura.es/verdnatura/salix-front into improve_isRequired_input 2024-11-08 08:39:29 +01:00
Alex Moreno dce57f4638 Merge branch 'dev' into improve_isRequired_input 2024-11-08 07:32:07 +00:00
Alex Moreno 0c6d6d6925 Merge pull request 'refs: #7782 Cypress features' (!891) from cypress_features into dev
Reviewed-on: verdnatura/salix-front#891
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2024-11-08 07:31:46 +00:00
Javier Segarra 0a5c3bfe5b Merge branch 'dev' into improve_isRequired_input 2024-11-08 07:29:54 +00:00
Javier Segarra 570c09bab5 Merge branch 'dev' into cypress_features 2024-11-08 07:27:26 +00:00
Alex Moreno 624ba2c13d Merge pull request 'FIX: #6943 Customer issues' (!885) from fix_customer_issues into dev
Reviewed-on: verdnatura/salix-front#885
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2024-11-07 16:20:22 +00:00
Alex Moreno 0b5be9e67f chore: correct checkNotification 2024-11-07 17:19:35 +01:00
Javier Segarra e210ad7de4 Merge branch 'dev' into fix_customer_issues 2024-11-07 13:06:33 +01:00
Javier Segarra 489df51d64 Merge branch 'dev' into fix_customer_issues 2024-11-07 12:54:19 +01:00
Javier Segarra 2bf7fa46ca perf: remove appendParams 2024-11-07 12:53:17 +01:00
Alex Moreno 5d0c91187e Merge pull request 'fix(InvoiceOutGlobal): parallelism' (!900) from warmFix_invoiceOut_Global into dev
Reviewed-on: verdnatura/salix-front#900
Reviewed-by: Jorge Penadés <jorgep@verdnatura.es>
2024-11-07 09:43:18 +00:00
Alex Moreno 18cc3888e5 Merge branch 'dev' into warmFix_invoiceOut_Global 2024-11-07 09:42:09 +00:00
Alex Moreno cc236cac03 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev 2024-11-07 10:19:02 +01:00
Alex Moreno dd2ee2a7de Merge pull request 'fix(worker): add searchurls' (!901) from warmFix_worker_searchUrls into test
Reviewed-on: verdnatura/salix-front#901
Reviewed-by: Jorge Penadés <jorgep@verdnatura.es>
2024-11-07 09:17:18 +00:00
Alex Moreno 6c9627c6c5 fix(worker): add searchurls 2024-11-06 17:51:21 +01:00
Alex Moreno 2b02c9fbd3 Merge branch 'dev' into warmFix_invoiceOut_Global 2024-11-06 12:54:18 +00:00
Alex Moreno ead241e7da chore: remove console.log 2024-11-06 13:53:58 +01:00
Alex Moreno a2bbf4474d fix(InvoiceOutGlobal): parallelism 2024-11-06 13:52:59 +01:00
Alex Moreno 473b0808e8 Merge pull request 'feat(VnInput): empty to null' (!898) from 6766-vnInput_emptyToNull into dev
Reviewed-on: verdnatura/salix-front#898
Reviewed-by: Jorge Penadés <jorgep@verdnatura.es>
2024-11-05 14:05:01 +00:00
Alex Moreno abd7b76636 feat(VnInput): empty to null 2024-11-05 13:50:25 +01:00
Jorge Penadés ec88bcbc44 Merge pull request '#7273 fixItemTags' (!895) from 7273-fixItemTags into dev
Reviewed-on: verdnatura/salix-front#895
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2024-11-05 11:56:14 +00:00
Jorge Penadés 9959fbf005 fix: refs #7273 use same filter 2024-11-05 12:23:11 +01:00
Jorge Penadés 4a95a0e39a chore: refs #7273 sticky add btn & refactor 2024-11-05 11:59:38 +01:00
Jorge Penadés 9ee05721a4 Merge pull request '#8078 enable multi choice' (!848) from 8078-enableMultiSelection into dev
Reviewed-on: verdnatura/salix-front#848
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2024-11-05 10:20:04 +00:00
Jorge Penadés ca9a7f2932 Merge branch 'dev' into 8078-enableMultiSelection 2024-11-05 10:18:46 +00:00
Alex Moreno 14498a78b2 Merge pull request '6920-invoiceInBasicData_useVnDms' (!892) from 6920-invoiceInBasicData_useVnDms into dev
Reviewed-on: verdnatura/salix-front#892
Reviewed-by: Jorge Penadés <jorgep@verdnatura.es>
2024-11-05 09:57:31 +00:00
Jorge Penadés 86e6bef90f chore: refs #8078 fiz tests 2024-11-05 10:37:35 +01:00
Jorge Penadés a4a4190e4e Merge branch '8078-enableMultiSelection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8078-enableMultiSelection 2024-11-05 10:02:33 +01:00
Jorge Penadés 1f9d850fbe chore: refs #8078 rollback ref 2024-11-05 10:02:29 +01:00
Jorge Penadés a542f69b0f Merge branch 'dev' of https: refs #8078//gitea.verdnatura.es/verdnatura/salix-front into 8078-enableMultiSelection 2024-11-05 10:02:09 +01:00
Alex Moreno e050a077d4 test: refactor e2e 2024-11-04 13:41:37 +01:00
Alex Moreno 08204aa5f0 refactor(InvoiceInBasicData): use VnDms 2024-11-04 13:02:39 +01:00
Javier Segarra 1761cc23e0 feat: use composable to unify logic 2024-10-30 08:29:04 +01:00
Javier Segarra 4de23c31b3 feat: use composable to unify logic 2024-10-29 15:01:49 +01:00
Alex Moreno f4bdefd4e7 Merge branch 'dev' into improve_isRequired_input 2024-10-29 13:13:59 +00:00
Alex Moreno fa5750bce4 Merge branch 'dev' into 8078-enableMultiSelection 2024-10-29 11:44:04 +00:00
Jorge Penadés e39f85ff4b fix: refs #8078 improve handleSelection 2024-10-29 09:56:06 +01:00
Jorge Penadés 19a7e52605 fix: refs #8078 handleSelection 2024-10-29 09:39:56 +01:00
Jorge Penadés 77cb2d84be fix: refs #8078 handleSelection 2024-10-29 09:38:40 +01:00
Jorge Penadés 71420da586 Merge branch 'dev' of https: refs #8078//gitea.verdnatura.es/verdnatura/salix-front into 8078-enableMultiSelection 2024-10-29 09:27:30 +01:00
Javier Segarra d2680b0a1f test: fix arrayData 2024-10-29 02:40:47 +01:00
Javier Segarra 4b00ab877c perf: use required instead :required="true" 2024-10-29 02:26:35 +01:00
Javier Segarra 0b2c404ab3 perf: minor bugs detected 2024-10-29 02:15:21 +01:00
Javier Segarra 0de4dfd4f8 feat: apply changes for customerModule 2024-10-29 02:15:02 +01:00
Jorge Penadés 1e16e3312e fix: refs #8078 improve cy command 2024-10-28 16:21:00 +01:00
Jorge Penadés 57488503aa fix: refs #8078 e2e #7970 2024-10-28 16:19:24 +01:00
Jorge Penadés 7e3e194b46 fix: refs #8078 e2e #7970 2024-10-28 16:19:13 +01:00
Jorge Penadés d5639471cf feat: refs #8078 add tests 2024-10-28 15:40:36 +01:00
Jorge Penadés 5ef5285abc Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8078-enableMultiSelection 2024-10-28 14:45:40 +01:00
Jorge Penadés 526b7d91de Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8078-enableMultiSelection 2024-10-28 13:26:27 +01:00
Jorge Penadés 0cc0e82aaa feat: refs #8078 add shortcut multi selection 2024-10-27 14:48:44 +01:00
53 changed files with 652 additions and 561 deletions

View File

@ -272,6 +272,7 @@ defineExpose({
hasChanges,
reset,
fetch,
formData,
});
</script>
<template>

View File

@ -62,7 +62,7 @@ const $props = defineProps({
default: 'flex-one',
},
searchUrl: {
type: String,
type: [String, Boolean],
default: 'table',
},
isEditable: {
@ -331,6 +331,20 @@ function handleScroll() {
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40;
if (isAtBottom) CrudModelRef.value.vnPaginateRef.paginate();
}
function handleSelection({ evt, added, rows: selectedRows }, rows) {
if (evt?.shiftKey && added) {
const rowIndex = selectedRows[0].$index;
const selectedIndexes = new Set(selected.value.map((row) => row.$index));
for (const row of rows) {
if (row.$index == rowIndex) break;
if (!selectedIndexes.has(row.$index)) {
selected.value.push(row);
selectedIndexes.add(row.$index);
}
}
}
}
</script>
<template>
<QDrawer
@ -431,6 +445,7 @@ function handleScroll() {
@virtual-scroll="handleScroll"
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
@update:selected="emit('update:selected', $event)"
@selection="(details) => handleSelection(details, rows)"
>
<template #top-left v-if="!$props.withoutHeader">
<slot name="top-left"></slot>
@ -722,6 +737,7 @@ function handleScroll() {
fab
icon="add"
shortcut="+"
data-testid="vnTableCreateBtn"
/>
<QTooltip self="top right">
{{ createForm?.title }}

View File

@ -31,6 +31,10 @@ const $props = defineProps({
type: String,
default: null,
},
description: {
type: String,
default: null,
},
});
const warehouses = ref();
@ -43,7 +47,8 @@ const dms = ref({});
onMounted(() => {
defaultData();
if (!$props.formInitialData)
dms.value.description = t($props.model + 'Description', dms.value);
dms.value.description =
$props.description ?? t($props.model + 'Description', dms.value);
});
function onFileChange(files) {
dms.value.hasFileAttached = !!files;
@ -54,7 +59,6 @@ function mapperDms(data) {
const formData = new FormData();
const { files } = data;
if (files) formData.append(files?.name, files);
delete data.files;
const dms = {
hasFile: !!data.hasFile,
@ -78,6 +82,7 @@ async function save() {
const body = mapperDms(dms.value);
const response = await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params, response);
delete dms.value.files;
return response;
}
@ -165,6 +170,7 @@ function addDefaultData(data) {
@update:model-value="onFileChange(dms.files)"
class="required"
:display-value="dms.file"
data-cy="VnDms_inputFile"
>
<template #append>
<QIcon

View File

@ -1,8 +1,11 @@
<script setup>
import { computed, ref } from 'vue';
import { computed, ref, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import { useValidator } from 'src/composables/useValidator';
import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const { t } = useI18n();
const emit = defineEmits([
'update:modelValue',
'update:options',
@ -27,11 +30,11 @@ const $props = defineProps({
type: Boolean,
default: true,
},
emptyToNull: {
type: Boolean,
default: true,
},
});
const { validations } = useValidator();
const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const vnInputRef = ref(null);
const value = computed({
@ -39,6 +42,7 @@ const value = computed({
return $props.modelValue;
},
set(value) {
if ($props.emptyToNull && value === '') value = null;
emit('update:modelValue', value);
},
});
@ -60,8 +64,6 @@ const focus = () => {
defineExpose({
focus,
});
import { useAttrs } from 'vue';
const $attrs = useAttrs();
const mixinRules = [
requiredFieldRule,
@ -85,7 +87,7 @@ const mixinRules = [
v-model="value"
v-bind="{ ...$attrs, ...styleAttrs }"
:type="$attrs.type"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
@keyup.enter="emit('keyup.enter')"
:clearable="false"
:rules="mixinRules"

View File

@ -4,8 +4,13 @@ import { date } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useAttrs } from 'vue';
import VnDate from './VnDate.vue';
import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const model = defineModel({ type: [String, Date] });
const { t } = useI18n();
const $props = defineProps({
isOutlined: {
type: Boolean,
@ -16,18 +21,13 @@ const $props = defineProps({
default: true,
},
});
import { useValidator } from 'src/composables/useValidator';
const { validations } = useValidator();
const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const vnInputDateRef = ref(null);
const dateFormat = 'DD/MM/YYYY';
const isPopupOpen = ref();
const hover = ref();
const mask = ref();
const $attrs = useAttrs();
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
@ -104,7 +104,7 @@ const manageDate = (date) => {
:mask="mask"
placeholder="dd/mm/aaaa"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
:rules="mixinRules"
:clearable="false"
@click="isPopupOpen = true"

View File

@ -1,8 +1,13 @@
<script setup>
import VnInput from 'src/components/common/VnInput.vue';
import { ref } from 'vue';
import { useAttrs } from 'vue';
const model = defineModel({ type: [Number, String] });
const $attrs = useAttrs();
const step = ref($attrs.step || 0.01);
</script>
<template>
<VnInput v-bind="$attrs" v-model.number="model" type="number" />
<VnInput v-bind="$attrs" v-model.number="model" type="number" :step="step" />
</template>

View File

@ -2,11 +2,12 @@
import { computed, ref, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import { date } from 'quasar';
import { useValidator } from 'src/composables/useValidator';
import VnTime from './VnTime.vue';
import { useRequired } from 'src/composables/useRequired';
const { validations } = useValidator();
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const { t } = useI18n();
const model = defineModel({ type: String });
const props = defineProps({
timeOnly: {
@ -20,8 +21,6 @@ const props = defineProps({
});
const vnInputTimeRef = ref(null);
const initialDate = ref(model.value ?? Date.vnNew());
const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const dateFormat = 'HH:mm';
const isPopupOpen = ref();
@ -78,7 +77,7 @@ function dateToTime(newDate) {
placeholder="--:--"
v-model="formattedTime"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
style="min-width: 100px"
:rules="mixinRules"
@click="isPopupOpen = false"

View File

@ -3,9 +3,12 @@ import CreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnSelectDialog from 'components/common/VnSelectDialog.vue';
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import { useAttrs } from 'vue';
import { useRequired } from 'src/composables/useRequired';
const { t } = useI18n();
const emit = defineEmits(['update:model-value', 'update:options']);
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const props = defineProps({
location: {
type: Object,
@ -13,6 +16,7 @@ const props = defineProps({
},
});
const mixinRules = [requiredFieldRule];
const locationProperties = [
'postcode',
(obj) =>
@ -69,11 +73,13 @@ const handleModelValue = (data) => {
:label="t('Location')"
:placeholder="t('search_by_postalcode')"
:input-debounce="300"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
v-bind="$attrs"
clearable
:emit-value="false"
:tooltip="t('Create new location')"
:rules="mixinRules"
:lazy-rules="true"
>
<template #form>
<CreateNewPostcode

View File

@ -2,9 +2,11 @@
import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
import { useValidator } from 'src/composables/useValidator';
import { useRequired } from 'src/composables/useRequired';
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
const $attrs = useAttrs();
const { t } = useI18n();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const $props = defineProps({
modelValue: {
type: [String, Number, Object],
@ -87,10 +89,7 @@ const $props = defineProps({
default: false,
},
});
const { validations } = useValidator();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const $attrs = useAttrs();
const { t } = useI18n();
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const { optionLabel, optionValue, optionFilter, optionFilterValue, options, modelValue } =
toRefs($props);
@ -257,7 +256,7 @@ defineExpose({ opts: myOptions });
:fill-input="nullishToTrue($attrs['fill-input'])"
ref="vnSelectRef"
lazy-rules
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
:rules="mixinRules"
virtual-scroll-slice-size="options.length"
hide-bottom-space

View File

@ -8,7 +8,7 @@ defineProps({
<template>
<div :class="$q.screen.gt.md ? 'q-pb-lg' : 'q-pb-md'">
<div class="header-link" :style="{ cursor: url ? 'pointer' : 'default' }">
<a :href="url" :class="url ? 'link' : 'color-vn-text'">
<a :href="url" :class="url ? 'link' : 'color-vn-text'" v-bind="$attrs">
{{ text }}
<QIcon v-if="url" :name="icon" />
</a>

View File

@ -167,6 +167,7 @@ const toModule = computed(() =>
icon="more_vert"
round
size="md"
data-cy="descriptor-more-opts"
>
<QTooltip>
{{ t('components.cardDescriptor.moreOptions') }}

View File

@ -4,6 +4,7 @@ import { useRoute } from 'vue-router';
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { useArrayData } from 'src/composables/useArrayData';
import { isDialogOpened } from 'src/filters';
const props = defineProps({
url: {
@ -58,22 +59,6 @@ async function fetch() {
emit('onFetch', Array.isArray(data) ? data[0] : data);
isLoading.value = false;
}
const showRedirectToSummaryIcon = computed(() => {
const exist = existSummary(route.matched);
return !isSummary.value && route.meta.moduleName && exist;
});
function existSummary(routes) {
const hasSummary = routes.some((r) => r.name === `${route.meta.moduleName}Summary`);
if (hasSummary) return hasSummary;
for (const current of routes) {
if (current.path != '/' && current.children) {
const exist = existSummary(current.children);
if (exist) return exist;
}
}
}
</script>
<template>
@ -84,7 +69,7 @@ function existSummary(routes) {
<div class="summaryHeader bg-primary q-pa-sm text-weight-bolder">
<slot name="header-left">
<router-link
v-if="showRedirectToSummaryIcon"
v-if="isDialogOpened()"
class="header link"
:to="{
name: `${moduleName ?? route.meta.moduleName}Summary`,
@ -118,6 +103,7 @@ function existSummary(routes) {
.cardSummary {
width: 100%;
max-height: 70vh;
.summaryHeader {
text-align: center;
font-size: 20px;

View File

@ -103,6 +103,7 @@ function cancel() {
@click="confirm()"
unelevated
autofocus
data-cy="VnConfirm_confirm"
/>
</QCardActions>
</QCard>

View File

@ -0,0 +1,16 @@
<script setup>
defineProps({ email: { type: [String], default: null } });
</script>
<template>
<QBtn
v-if="email"
flat
round
icon="email"
size="sm"
color="primary"
padding="none"
:href="`mailto:${email}`"
@click.stop
/>
</template>

View File

@ -130,6 +130,7 @@ async function search() {
dense
standout
autofocus
data-testid="vnSearchBar"
>
<template #prepend>
<QIcon

View File

@ -3,6 +3,7 @@ import { useRouter, useRoute } from 'vue-router';
import axios from 'axios';
import { useArrayDataStore } from 'stores/useArrayDataStore';
import { buildFilter } from 'filters/filterPanel';
import { isDialogOpened } from 'src/filters';
const arrayDataStore = useArrayDataStore();
@ -114,8 +115,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
for (const row of response.data) store.data.push(row);
} else {
store.data = response.data;
if (!document.querySelectorAll('[role="dialog"][aria-modal="true"]').length)
updateRouter && updateStateParams();
if (!isDialogOpened()) updateRouter && updateStateParams();
}
store.isLoading = false;
@ -247,9 +247,9 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
}
function updateStateParams() {
if (!route) return;
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);
if (store?.searchUrl)
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);
if (store.navigate) {
const { customRouteRedirectName, searchText } = store.navigate;

View File

@ -0,0 +1,13 @@
import { useValidator } from 'src/composables/useValidator';
export function useRequired($attrs) {
const { validations } = useValidator();
const isRequired = Object.keys($attrs).includes('required');
const requiredFieldRule = (val) => validations().required(isRequired, val);
return {
isRequired,
requiredFieldRule,
};
}

View File

@ -12,8 +12,10 @@ import dateRange from './dateRange';
import toHour from './toHour';
import dashOrCurrency from './dashOrCurrency';
import getParamWhere from './getParamWhere';
import isDialogOpened from './isDialogOpened';
export {
isDialogOpened,
toLowerCase,
toLowerCamel,
toDate,

View File

@ -0,0 +1,3 @@
export default function isDialogOpened(query = '[role="dialog"]') {
return document.querySelectorAll(query).length > 0;
}

View File

@ -27,7 +27,7 @@ const addressFilter = {
'isLogifloraAllowed',
'postalCode',
],
order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'],
order: ['isDefaultAddress DESC', 'isActive DESC', 'id DESC', 'nickname ASC'],
include: [
{
relation: 'observations',

View File

@ -5,7 +5,7 @@ import { useRoute } from 'vue-router';
import { useAcl } from 'src/composables/useAcl';
import axios from 'axios';
import { useQuasar } from 'quasar';
import FetchData from 'components/FetchData.vue';
import { getClientRisk } from '../composables/getClientRisk';
import { toCurrency, toDate, toDateHourMin } from 'src/filters';
import { useState } from 'composables/useState';
@ -16,7 +16,7 @@ import { useVnConfirm } from 'composables/useVnConfirm';
import VnTable from 'components/VnTable/VnTable.vue';
import VnInput from 'components/common/VnInput.vue';
import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnFilter from 'components/VnTable/VnFilter.vue';
import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
@ -25,7 +25,7 @@ const { openConfirmationModal } = useVnConfirm();
const { sendEmail, openReport } = usePrintService();
const { t } = useI18n();
const { hasAny } = useAcl();
const currentBalance = ref({});
const quasar = useQuasar();
const route = useRoute();
const state = useState();
@ -33,28 +33,53 @@ const stateStore = useStateStore();
const user = state.getUser();
const clientRisk = ref([]);
const companies = ref([]);
const tableRef = ref();
const companyId = ref(user.value.companyFk);
const companyId = ref();
const companyUser = ref(user.value.companyFk);
const balances = ref([]);
const vnFilterRef = ref({});
const filter = computed(() => {
return {
clientId: route.params.id,
companyId: companyId.value ?? user.value.companyFk,
companyId: companyId.value ?? companyUser.value,
};
});
const companyFilterColumn = {
align: 'left',
name: 'companyId',
label: t('Company'),
component: 'select',
attrs: {
url: 'Companies',
optionLabel: 'code',
optionValue: 'id',
sortBy: 'code',
},
columnFilter: {
event: {
remove: () => (companyId.value = null),
'update:modelValue': (newCompanyFk) => {
if (!newCompanyFk) return;
vnFilterRef.value.addFilter(newCompanyFk);
companyUser.value = newCompanyFk;
},
blur: () => !companyId.value && (companyId.value = companyUser.value),
},
},
visible: false,
};
const columns = computed(() => [
{
align: 'right',
align: 'left',
name: 'payed',
label: t('Date'),
format: ({ payed }) => toDate(payed),
cardVisible: true,
},
{
align: 'right',
align: 'left',
name: 'created',
label: t('Creation date'),
format: ({ created }) => toDateHourMin(created),
@ -65,7 +90,12 @@ const columns = computed(() => [
label: t('Employee'),
columnField: {
component: 'userLink',
attrs: ({ row }) => ({ workerId: row.workerFk, name: row.userName }),
attrs: ({ row }) => {
return {
workerId: row.workerFk,
name: row.userName,
};
},
},
cardVisible: true,
},
@ -77,13 +107,13 @@ const columns = computed(() => [
class: 'extend',
},
{
align: 'right',
align: 'left',
name: 'bankFk',
label: t('Bank'),
cardVisible: true,
},
{
align: 'right',
align: 'left',
name: 'debit',
label: t('Debit'),
format: ({ debit }) => debit && toCurrency(debit),
@ -136,20 +166,37 @@ const columns = computed(() => [
onBeforeMount(() => {
stateStore.rightDrawer = true;
companyId.value = companyUser.value;
});
async function getCurrentBalance(data) {
currentBalance.value[companyId.value] = {
amount: 0,
code: companies.value.find((c) => c.id === companyId.value)?.code,
async function getClientRisks() {
const filter = {
where: { clientFk: route.params.id, companyFk: companyUser.value },
};
const { data } = await getClientRisk(filter);
clientRisk.value = data;
return clientRisk.value;
}
for (const balance of data) {
currentBalance.value[balance.companyFk] = {
code: balance.company.code,
amount: balance.amount,
};
async function getCurrentBalance() {
const currentBalance = (await getClientRisks()).find((balance) => {
return balance.companyFk === companyId.value;
});
return currentBalance && currentBalance.amount;
}
async function onFetch(data) {
balances.value = [];
for (const [index, balance] of data.entries()) {
if (index === 0) {
balance.balance = await getCurrentBalance();
continue;
}
const previousBalance = data[index - 1];
balance.balance =
previousBalance?.balance - (previousBalance?.debit - previousBalance?.credit);
}
balances.value = data;
}
const showNewPaymentDialog = () => {
@ -169,43 +216,25 @@ const showBalancePdf = ({ id }) => {
</script>
<template>
<FetchData
url="Companies"
auto-load
@on-fetch="(data) => (companies = data)"
></FetchData>
<FetchData
v-if="companies.length > 0"
url="clientRisks"
:filter="{
include: { relation: 'company', scope: { fields: ['code'] } },
where: { clientFk: route.params.id },
}"
auto-load
@on-fetch="getCurrentBalance"
></FetchData>
<VnSubToolbar class="q-mb-md">
<template #st-data>
<div class="column justify-center q-px-md q-py-sm">
<span class="text-bold">{{ t('Total by company') }}</span>
<div class="row justify-center">
{{ currentBalance[companyId]?.code }}:
{{ toCurrency(currentBalance[companyId]?.amount) }}
<div class="row justify-center" v-if="clientRisk?.length">
{{ clientRisk[0].company.code }}:
{{ toCurrency(clientRisk[0].amount) }}
</div>
</div>
</template>
<template #st-actions>
<div>
<VnSelect
:label="t('Company')"
<VnFilter
ref="vnFilterRef"
v-model="companyId"
data-key="CustomerBalance"
:options="companies"
option-label="code"
option-value="id"
></VnSelect>
:column="companyFilterColumn"
search-url="balance"
/>
</div>
</template>
</VnSubToolbar>
@ -219,6 +248,7 @@ const showBalancePdf = ({ id }) => {
:right-search="false"
:is-editable="false"
:column-search="false"
@on-fetch="onFetch"
:disable-option="{ card: true }"
auto-load
>

View File

@ -49,7 +49,9 @@ const columns = computed(() => [
name: 'credit',
create: true,
visible: false,
attrs: {
columnCreate: {
component: 'number',
required: true,
autofocus: true,
},
},

View File

@ -150,7 +150,7 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
</QCardActions>
</template>
<template #actions="{ entity }">
<QCardActions class="flex justify-center">
<QCardActions class="flex justify-center" style="padding-inline: 0">
<QBtn
:to="{
name: 'TicketList',
@ -168,6 +168,23 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
>
<QTooltip>{{ t('Customer ticket list') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'TicketList',
query: {
table: JSON.stringify({
clientFk: entity.id,
}),
createForm: JSON.stringify({ clientId: entity.id }),
},
}"
size="md"
color="primary"
target="_blank"
icon="vn:ticketAdd"
>
<QTooltip>{{ t('New ticket') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'InvoiceOutList',
@ -179,6 +196,23 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
>
<QTooltip>{{ t('Customer invoice out list') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'OrderList',
query: {
table: JSON.stringify({
clientFk: entity.id,
}),
createForm: JSON.stringify({ clientFk: entity.id }),
},
}"
size="md"
target="_blank"
icon="vn:basketadd"
color="primary"
>
<QTooltip>{{ t('New order') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'AccountSummary',
@ -215,6 +249,8 @@ es:
Go to module index: Ir al índice del módulo
Customer ticket list: Listado de tickets del cliente
Customer invoice out list: Listado de facturas del cliente
New order: Nuevo pedido
New ticket: Nuevo ticket
Go to user: Ir al usuario
Go to supplier: Ir al proveedor
Customer unpaid: Cliente impago

View File

@ -8,9 +8,6 @@ import { useQuasar } from 'quasar';
import useNotify from 'src/composables/useNotify';
import VnSmsDialog from 'src/components/common/VnSmsDialog.vue';
import TicketCreateDialog from 'src/pages/Ticket/TicketCreateDialog.vue';
import OrderCreateDialog from 'src/pages/Order/Card/OrderCreateDialog.vue';
import { ref } from 'vue';
const $props = defineProps({
customer: {
@ -43,34 +40,9 @@ const sendSms = async (payload) => {
notify(error.message, 'positive');
}
};
const ticketCreateFormDialog = ref(null);
const openTicketCreateForm = () => {
ticketCreateFormDialog.value.show();
};
const orderCreateFormDialog = ref(null);
const openOrderCreateForm = () => {
orderCreateFormDialog.value.show();
};
</script>
<template>
<QItem v-ripple clickable @click="openTicketCreateForm()">
<QItemSection>
{{ t('globals.pageTitles.createTicket') }}
<QDialog ref="ticketCreateFormDialog">
<TicketCreateDialog />
</QDialog>
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="openOrderCreateForm()">
<QItemSection>
{{ t('globals.pageTitles.createOrder') }}
<QDialog ref="orderCreateFormDialog">
<OrderCreateDialog :client-fk="customer.id" />
</QDialog>
</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection>
</QItem>

View File

@ -53,11 +53,11 @@ function handleLocation(data, location) {
</QIcon>
</template>
</VnInput>
<VnInput :label="t('Tax number')" clearable v-model="data.fi" />
<VnInput :label="t('Tax number')" clearable v-model="data.fi" required />
</VnRow>
<VnRow>
<VnInput :label="t('Street')" clearable v-model="data.street" />
<VnInput :label="t('Street')" clearable v-model="data.street" required />
</VnRow>
<VnRow>
@ -68,6 +68,7 @@ function handleLocation(data, location) {
option-label="vat"
option-value="id"
v-model="data.sageTaxTypeFk"
:required="data.isTaxDataChecked"
/>
<VnSelect
:label="t('Sage transaction type')"
@ -76,6 +77,7 @@ function handleLocation(data, location) {
option-label="transaction"
option-value="id"
v-model="data.sageTransactionTypeFk"
:required="data.isTaxDataChecked"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -96,6 +98,7 @@ function handleLocation(data, location) {
:roles-allowed-to-create="['deliveryAssistant', 'administrative']"
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
:location="data"
:required="true"
@update:model-value="(location) => handleLocation(data, location)"
/>
</VnRow>

View File

@ -80,6 +80,11 @@ const columns = computed(() => [
align: 'left',
name: 'amount',
label: t('Amount'),
columnCreate: {
component: 'number',
autofocus: true,
required: true,
},
format: ({ amount }) => toCurrency(amount),
create: true,
},

View File

@ -56,6 +56,7 @@ const columns = computed(() => [
label: t('Period'),
create: true,
...componentColumn('number'),
required: true,
},
{
align: 'left',

View File

@ -1,13 +1,15 @@
<script setup>
import { computed, ref } from 'vue';
import { computed, ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { toCurrency, toPercentage, toDate } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue';
import { getUrl } from 'src/composables/getUrl';
import VnLv from 'src/components/ui/VnLv.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import VnLinkMail from 'src/components/ui/VnLinkMail.vue';
import CustomerSummaryTable from 'src/pages/Customer/components/CustomerSummaryTable.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import VnRow from 'src/components/ui/VnRow.vue';
@ -25,6 +27,11 @@ const $props = defineProps({
const entityId = computed(() => $props.id || route.params.id);
const customer = computed(() => summary.value.entity);
const summary = ref();
const clientUrl = ref();
onMounted(async () => {
clientUrl.value = (await getUrl('client/')) + entityId.value + '/';
});
const balanceDue = computed(() => {
return (
@ -67,6 +74,7 @@ const sumRisk = ({ clientRisks }) => {
ref="summary"
:url="`Clients/${entityId}/summary`"
data-key="CustomerSummary"
module-name="Customer"
>
<template #body="{ entity }">
<QCard class="vn-one">
@ -89,7 +97,11 @@ const sumRisk = ({ clientRisks }) => {
<VnLinkPhone :phone-number="entity.mobile" />
</template>
</VnLv>
<VnLv :label="t('customer.summary.email')" :value="entity.email" copy />
<VnLv :value="entity.email" copy
><template #label>
{{ t('customer.summary.email') }}
<VnLinkMail email="entity.email"></VnLinkMail> </template
></VnLv>
<VnLv
:label="t('customer.summary.salesPerson')"
:value="entity?.salesPersonUser?.name"
@ -166,7 +178,7 @@ const sumRisk = ({ clientRisks }) => {
<QCard class="vn-one">
<VnTitle
:url="`#/customer/${entityId}/billing-data`"
:text="t('customer.summary.payMethodFk')"
:text="t('customer.summary.billingData')"
/>
<VnLv
:label="t('customer.summary.payMethod')"
@ -222,6 +234,7 @@ const sumRisk = ({ clientRisks }) => {
</QCard>
<QCard class="vn-one" v-if="entity.account">
<VnTitle
target="_blank"
:url="`${grafanaUrl}/d/adjlxzv5yjt34d/analisis-de-clientes-7c-crm?orgId=1&var-clientFk=${entityId}`"
:text="t('customer.summary.businessData')"
icon="vn:grafana"
@ -235,6 +248,7 @@ const sumRisk = ({ clientRisks }) => {
:value="toCurrency(entity?.mana?.mana)"
/>
<VnLv
v-if="entity.claimsRatio"
:label="t('customer.summary.priceIncreasingRate')"
:value="toPercentage(priceIncreasingRate)"
/>
@ -243,12 +257,14 @@ const sumRisk = ({ clientRisks }) => {
:value="toCurrency(entity?.averageInvoiced?.invoiced)"
/>
<VnLv
v-if="entity.claimsRatio"
:label="t('customer.summary.claimRate')"
:value="toPercentage(claimRate)"
/>
</QCard>
<QCard class="vn-one" v-if="entity.account">
<VnTitle
target="_blank"
:url="`${grafanaUrl}/d/40buzE4Vk/comportamiento-pagos-clientes?orgId=1&var-clientFk=${entityId}`"
:text="t('customer.summary.payMethodFk')"
icon="vn:grafana"
@ -268,10 +284,12 @@ const sumRisk = ({ clientRisks }) => {
/>
<VnLv
v-if="entity.creditInsurance"
:label="t('customer.summary.securedCredit')"
:value="toCurrency(entity.creditInsurance)"
:info="t('customer.summary.securedCreditInfo')"
/>
<VnLv
:label="t('customer.summary.balance')"
:value="toCurrency(sumRisk(entity)) || toCurrency(0)"
@ -301,7 +319,7 @@ const sumRisk = ({ clientRisks }) => {
:value="entity.recommendedCredit"
/>
</QCard>
<QCard>
<QCard class="vn-one">
<VnTitle :text="t('Latest tickets')" />
<CustomerSummaryTable />
</QCard>

View File

@ -68,7 +68,6 @@ const columns = computed(() => [
fields: ['id', 'name'],
where: { role: 'salesPerson' },
optionFilter: 'firstName',
useLike: false,
},
create: false,
columnField: {
@ -429,9 +428,10 @@ function handleLocation(data, location) {
:params="{
departmentCodes: ['VT', 'shopping'],
}"
:fields="['id', 'nickname']"
:fields="['id', 'nickname', 'code']"
sort-by="nickname ASC"
:use-like="false"
option-label="nickname"
option-value="id"
emit-value
auto-load
>

View File

@ -85,15 +85,26 @@ function handleLocation(data, location) {
<QCheckbox :label="t('Default')" v-model="data.isDefaultAddress" />
<VnRow>
<VnInput :label="t('Consignee')" clearable v-model="data.nickname" />
<VnInput
:label="t('Consignee')"
required
clearable
v-model="data.nickname"
/>
<VnInput :label="t('Street address')" clearable v-model="data.street" />
<VnInput
:label="t('Street address')"
clearable
v-model="data.street"
required
/>
</VnRow>
<VnLocation
:rules="validate('Worker.postcode')"
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.location"
:required="true"
@update:model-value="(location) => handleLocation(data, location)"
/>

View File

@ -3,7 +3,7 @@ import { onBeforeMount, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import axios from 'axios';
import { getClientRisk } from '../composables/getClientRisk';
import { useDialogPluginComponent } from 'quasar';
import { usePrintService } from 'composables/usePrintService';
@ -158,9 +158,7 @@ async function getAmountPaid() {
},
};
const { data } = await axios(`ClientRisks`, {
params: { filter: JSON.stringify(filter) },
});
const { data } = await getClientRisk(filter);
initialData.amountPaid = (data?.length && data[0].amount) || undefined;
}
</script>
@ -241,7 +239,7 @@ async function getAmountPaid() {
</QItem>
</template>
</VnSelect>
<VnInput
<VnInputNumber
:label="t('Amount')"
:required="true"
@update:model-value="calculateFromAmount($event)"
@ -254,7 +252,7 @@ async function getAmountPaid() {
{{ t('Compensation') }}
</div>
<VnRow>
<VnInput
<VnInputNumber
:label="t('Compensation account')"
clearable
v-model="data.compensationAccount"

View File

@ -63,7 +63,7 @@ const columns = computed(() => [
},
{
align: 'left',
format: (row, dashIfEmpty) => dashIfEmpty(row.agencyMode?.name),
format: (row) => dashIfEmpty(row.agencyMode?.name),
columnClass: 'expand',
label: t('Agency'),
},
@ -111,7 +111,11 @@ const columns = computed(() => [
{
title: t('customer.summary.goToLines'),
icon: 'vn:lines',
action: ({ id }) => router.push({ params: { id }, name: 'TicketSale' }),
action: ({ id }) =>
window.open(
router.resolve({ params: { id }, name: 'TicketSale' }).href,
'_blank'
),
isPrimary: true,
},
{
@ -150,6 +154,8 @@ const setShippedColor = (date) => {
if (difference == 0) return 'warning';
if (difference < 0) return 'success';
};
const rowClick = ({ id }) =>
window.open(router.resolve({ params: { id }, name: 'TicketSummary' }).href, '_blank');
const getItemPackagingType = (ticketSales) => {
if (!ticketSales?.length) return '-';
@ -177,13 +183,14 @@ const getItemPackagingType = (ticketSales) => {
:column-search="false"
url="Tickets"
:columns="columns"
search-url="tickets"
:without-header="true"
auto-load
:row-click="rowClick"
order="shipped DESC, id"
:disable-option="{ card: true, table: true }"
class="full-width"
:disable-infinite-scroll="true"
:search-url="false"
>
<template #column-nickname="{ row }">
<span class="link">

View File

@ -0,0 +1,12 @@
import axios from 'axios';
export async function getClientRisk(_filter) {
const filter = {
..._filter,
include: { relation: 'company', scope: { fields: ['code'] } },
};
return await axios(`ClientRisks`, {
params: { filter: JSON.stringify(filter) },
});
}

View File

@ -3,8 +3,6 @@ import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import { useArrayData } from 'src/composables/useArrayData';
import { downloadFile } from 'src/composables/downloadFile';
import FormModel from 'components/FormModel.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
@ -12,15 +10,15 @@ import FetchData from 'src/components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnDms from 'src/components/common/VnDms.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import axios from 'axios';
const quasar = useQuasar();
const { t } = useI18n();
const dms = ref({});
const route = useRoute();
const quasar = useQuasar();
const editDownloadDisabled = ref(false);
const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data);
const userConfig = ref(null);
const invoiceId = computed(() => +route.params.id);
@ -36,98 +34,25 @@ const warehousesRef = ref();
const allowTypesRef = ref();
const allowedContentTypes = ref([]);
const sageWithholdings = ref([]);
const inputFileRef = ref();
const editDmsRef = ref();
const createDmsRef = ref();
const documentDialogRef = ref({});
const invoiceInRef = ref({});
async function checkFileExists(dmsId) {
if (!dmsId) return;
try {
await axios.get(`Dms/${dmsId}`, { fields: ['id'] });
editDownloadDisabled.value = false;
} catch (e) {
editDownloadDisabled.value = true;
}
}
async function setEditDms(dmsId) {
const { data } = await axios.get(`Dms/${dmsId}`);
dms.value = {
warehouseId: data.warehouseFk,
companyId: data.companyFk,
dmsTypeId: data.dmsTypeFk,
...data,
};
if (!allowedContentTypes.value.length) await allowTypesRef.value.fetch();
editDmsRef.value.show();
}
async function setCreateDms() {
const { data } = await axios.get('DmsTypes/findOne', {
where: { code: 'invoiceIn' },
});
dms.value = {
reference: invoiceIn.value.supplierRef,
warehouseId: userConfig.value.warehouseFk,
companyId: userConfig.value.companyFk,
dmsTypeId: data.id,
description: invoiceIn.value.supplier.name,
hasFile: true,
hasFileAttached: true,
files: null,
};
createDmsRef.value.show();
}
async function onSubmit() {
try {
const isEdit = !!dms.value.id;
const errors = {
companyId: `The company can't be empty`,
warehouseId: `The warehouse can't be empty`,
dmsTypeId: `The DMS Type can't be empty`,
description: `The description can't be empty`,
};
Object.keys(errors).forEach((key) => {
if (!dms.value[key]) throw Error(t(errors[key]));
function deleteFile(dmsFk) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('globals.confirmDeletion'),
message: t('globals.confirmDeletionMessage'),
},
})
.onOk(async () => {
await axios.post(`dms/${dmsFk}/removeFile`);
invoiceInRef.value.formData.dmsFk = null;
invoiceInRef.value.formData.dms = undefined;
invoiceInRef.value.hasChanges = true;
invoiceInRef.value.save();
});
if (!isEdit && !dms.value.files) throw Error(t(`The files can't be empty`));
const formData = new FormData();
if (dms.value.files) {
for (let i = 0; i < dms.value.files.length; i++)
formData.append(dms.value.files[i].name, dms.value.files[i]);
dms.value.hasFileAttached = true;
}
const url = isEdit ? `dms/${dms.value.id}/updateFile` : 'Dms/uploadFile';
const { data } = await axios.post(url, formData, {
params: dms.value,
});
if (data.length) invoiceIn.value.dmsFk = data[0].id;
if (!isEdit) {
createDmsRef.value.hide();
} else {
editDmsRef.value.hide();
}
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
} catch (error) {
quasar.notify({
message: t(`${error.message}`),
type: 'negative',
});
}
}
</script>
<template>
@ -181,10 +106,12 @@ async function onSubmit() {
@on-fetch="(data) => (sageWithholdings = data)"
/>
<FormModel
ref="invoiceInRef"
model="InvoiceIn"
:go-to="`/invoice-in/${invoiceId}/vat`"
auto-load
:url-update="`InvoiceIns/${invoiceId}/updateInvoiceIn`"
@on-fetch="(data) => (documentDialogRef.supplierName = data.supplier.nickname)"
auto-load
>
<template #form="{ data }">
<VnRow>
@ -242,16 +169,22 @@ async function onSubmit() {
</QItem>
</template>
</VnSelect>
<VnInput
:label="t('Document')"
v-model="data.dmsFk"
clearable
clear-icon="close"
@update:model-value="checkFileExists(data.dmsFk)"
>
<template #prepend>
<div class="row no-wrap">
<VnInput
:label="t('Document')"
v-model="data.dmsFk"
clearable
clear-icon="close"
class="full-width"
:disable="true"
/>
<div
v-if="data.dmsFk"
class="row no-wrap q-pa-xs q-gutter-x-xs"
data-cy="dms-buttons"
>
<QBtn
v-if="data.dmsFk"
:class="{
'no-pointer-events': editDownloadDisabled,
}"
@ -262,33 +195,52 @@ async function onSubmit() {
round
@click="downloadFile(data.dmsFk)"
/>
</template>
<template #append>
<QBtn
:class="{
'no-pointer-events': editDownloadDisabled,
}"
:disable="editDownloadDisabled"
v-if="data.dmsFk"
icon="edit"
round
padding="xs"
@click="setEditDms(data.dmsFk)"
@click="
() => {
documentDialogRef.show = true;
documentDialogRef.dms = data.dms;
}
"
>
<QTooltip>{{ t('Edit document') }}</QTooltip>
</QBtn>
<QBtn
v-else
icon="add_circle"
round
shortcut="+"
:class="{
'no-pointer-events': editDownloadDisabled,
}"
:disable="editDownloadDisabled"
icon="delete"
:title="t('Delete file')"
padding="xs"
@click="setCreateDms()"
>
<QTooltip>{{ t('Create document') }}</QTooltip>
</QBtn>
</template>
</VnInput>
round
@click="deleteFile(data.dmsFk)"
/>
</div>
<QBtn
v-else
icon="add_circle"
round
shortcut="+"
padding="xs"
@click="
() => {
documentDialogRef.show = true;
delete documentDialogRef.dms;
}
"
data-cy="dms-create"
>
<QTooltip>{{ t('Create document') }}</QTooltip>
</QBtn>
</div>
</VnRow>
<VnRow>
<VnSelect
@ -319,237 +271,28 @@ async function onSubmit() {
</VnRow>
</template>
</FormModel>
<QDialog ref="editDmsRef">
<QForm @submit="onSubmit()" class="all-pointer-events">
<QCard class="q-pa-sm">
<QCardSection class="row items-center q-pb-none">
<span class="text-primary text-h6">
<QIcon name="edit" class="q-mr-xs" />
{{ t('Edit document') }}
</span>
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<QCardSection class="q-py-none">
<QItem>
<VnInput
class="full-width q-pa-xs"
:label="t('Reference')"
v-model="dms.reference"
clearable
clear-icon="close"
/>
<VnSelect
class="full-width q-pa-xs"
:label="t('Company')"
v-model="dms.companyId"
:options="companies"
option-value="id"
option-label="code"
:required="true"
/>
</QItem>
<QItem>
<VnSelect
class="full-width q-pa-xs"
:label="t('Warehouse')"
v-model="dms.warehouseId"
:options="warehouses"
option-value="id"
option-label="name"
:required="true"
/>
<VnSelect
class="full-width q-pa-xs"
:label="t('Type')"
v-model="dms.dmsTypeId"
:options="dmsTypes"
option-value="id"
option-label="name"
:required="true"
/>
</QItem>
<QItem>
<VnInput
:label="t('Description')"
v-model="dms.description"
:required="true"
type="textarea"
class="full-width q-pa-xs"
size="lg"
autogrow
clearable
clear-icon="close"
/>
</QItem>
<QItem>
<QFile
ref="inputFileRef"
class="full-width q-pa-xs"
:label="t('File')"
v-model="dms.files"
multiple
:accept="allowedContentTypes.join(',')"
clearable
clear-icon="close"
>
<template #append>
<QBtn
icon="attach_file_add"
flat
round
padding="xs"
@click="inputFileRef.pickFiles()"
>
<QTooltip>
{{ t('globals.selectFile') }}
</QTooltip>
</QBtn>
<QBtn icon="info" flat round padding="xs">
<QTooltip max-width="30rem">
{{
`${t(
'Allowed content types'
)}: ${allowedContentTypes.join(', ')}`
}}
</QTooltip>
</QBtn>
</template>
</QFile>
</QItem>
<QItem>
<QCheckbox
:label="t('Generate identifier for original file')"
v-model="dms.hasFile"
/>
</QItem>
</QCardSection>
<QCardActions class="justify-end">
<QBtn
flat
:label="t('globals.close')"
color="primary"
v-close-popup
/>
<QBtn :label="t('globals.save')" color="primary" @click="onSubmit" />
</QCardActions>
</QCard>
</QForm>
</QDialog>
<QDialog ref="createDmsRef">
<QForm @submit="onSubmit()" class="all-pointer-events">
<QCard class="q-pa-sm">
<QCardSection class="row items-center q-pb-none">
<span class="text-primary text-h6">
<QIcon name="edit" class="q-mr-xs" />
{{ t('Create document') }}
</span>
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<QCardSection class="q-pb-none">
<QItem>
<VnInput
class="full-width q-pa-xs"
:label="t('Reference')"
v-model="dms.reference"
/>
<VnSelect
class="full-width q-pa-xs"
:label="`${t('Company')}*`"
v-model="dms.companyId"
:options="companies"
option-value="id"
option-label="code"
:required="true"
/>
</QItem>
<QItem>
<VnSelect
class="full-width q-pa-xs"
:label="`${t('Warehouse')}*`"
v-model="dms.warehouseId"
:options="warehouses"
option-value="id"
option-label="name"
:required="true"
/>
<VnSelect
class="full-width q-pa-xs"
:label="`${t('Type')}*`"
v-model="dms.dmsTypeId"
:options="dmsTypes"
option-value="id"
option-label="name"
:required="true"
/>
</QItem>
<QItem>
<VnInput
class="full-width q-pa-xs"
type="textarea"
size="lg"
autogrow
:label="`${t('Description')}*`"
v-model="dms.description"
clearable
clear-icon="close"
:rules="[(val) => val.length || t('Required field')]"
/>
</QItem>
<QItem>
<QFile
ref="inputFileRef"
class="full-width q-pa-xs"
:label="t('File')"
v-model="dms.files"
multiple
:accept="allowedContentTypes.join(',')"
clearable
clear-icon="close"
>
<template #append>
<QBtn
icon="attach_file_add"
flat
round
padding="xs"
@click="inputFileRef.pickFiles()"
>
<QTooltip>
{{ t('globals.selectFile') }}
</QTooltip>
</QBtn>
<QBtn icon="info" flat round padding="xs">
<QTooltip max-width="30rem">
{{
`${t(
'Allowed content types'
)}: ${allowedContentTypes.join(', ')}`
}}
</QTooltip>
</QBtn>
</template>
</QFile>
</QItem>
<QItem>
<QCheckbox
:label="t('Generate identifier for original file')"
v-model="dms.hasFile"
/>
</QItem>
</QCardSection>
<QCardActions align="right">
<QBtn
flat
:label="t('globals.close')"
color="primary"
v-close-popup
/>
<QBtn :label="t('globals.save')" color="primary" @click="onSubmit" />
</QCardActions>
</QCard>
</QForm>
<QDialog v-model="documentDialogRef.show">
<VnDms
model="dms"
default-dms-code="invoiceIn"
:form-initial-data="documentDialogRef.dms"
:url="
documentDialogRef.dms
? `Dms/${documentDialogRef.dms.id}/updateFile`
: 'Dms/uploadFile'
"
:description="documentDialogRef.supplierName"
@on-data-saved="
(_, { data }) => {
let dmsData = data;
if (Array.isArray(data)) dmsData = data[0];
invoiceInRef.formData.dmsFk = dmsData.id;
invoiceInRef.formData.dms = dmsData;
invoiceInRef.hasChanges = true;
invoiceInRef.save();
}
"
/>
</QDialog>
</template>
<style lang="scss" scoped>

View File

@ -20,6 +20,23 @@ const filter = {
{ relation: 'invoiceInDueDay' },
{ relation: 'company' },
{ relation: 'currency' },
{
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'reference',
'hardCopyNumber',
'workerFk',
'description',
'hasFile',
'file',
'created',
'companyFk',
'warehouseFk',
],
},
},
],
};

View File

@ -13,7 +13,7 @@ const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
// invoiceOutGlobalStore state and getters
const { initialDataLoading, formInitialData, invoicing, status } =
const { initialDataLoading, formInitialData, status } =
storeToRefs(invoiceOutGlobalStore);
// invoiceOutGlobalStore actions
@ -151,9 +151,8 @@ onMounted(async () => {
rounded
/>
</div>
<QBtn
v-if="!invoicing"
v-if="!getStatus || getStatus === 'stopping'"
:label="t('invoiceOut')"
type="submit"
color="primary"
@ -163,7 +162,7 @@ onMounted(async () => {
dense
/>
<QBtn
v-if="invoicing"
v-else
:label="t('stop')"
color="primary"
class="q-mt-md full-width"

View File

@ -109,7 +109,11 @@ const insertTag = (rows) => {
>
<template #body="{ rows, validate }">
<QCard class="q-px-lg q-pt-md q-pb-sm">
<VnRow v-for="(row, index) in rows" :key="index">
<VnRow
v-for="(row, index) in rows"
:key="index"
class="items-center"
>
<VnSelect
:label="t('itemTags.tag')"
:options="tagOptions"
@ -153,13 +157,14 @@ const insertTag = (rows) => {
:required="true"
:rules="validate('itemTag.priority')"
/>
<div class="row justify-center items-center" style="flex: 0">
<div class="row justify-center" style="flex: 0">
<QIcon
@click="itemTagsRef.remove([row])"
class="fill-icon-on-hover"
color="primary"
name="delete"
size="sm"
dense
>
<QTooltip>
{{ t('itemTags.removeTag') }}
@ -167,22 +172,20 @@ const insertTag = (rows) => {
</QIcon>
</div>
</VnRow>
<VnRow class="justify-center items-center">
<QBtn
@click="insertTag(rows)"
class="cursor-pointer"
color="primary"
flat
icon="add"
shortcut="+"
style="flex: 0"
>
<QTooltip>
{{ t('itemTags.addTag') }}
</QTooltip>
</QBtn>
</VnRow>
</QCard>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
@click="insertTag(rows)"
color="primary"
icon="add"
shortcut="+"
fab
>
<QTooltip>
{{ t('itemTags.addTag') }}
</QTooltip>
</QBtn>
</QPageSticky>
</template>
</CrudModel>
</QPage>

View File

@ -12,6 +12,8 @@ import ItemSummary from '../Item/Card/ItemSummary.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
import { cloneItem } from 'src/pages/Item/composables/cloneItem';
import RightMenu from 'src/components/common/RightMenu.vue';
import ItemListFilter from './ItemListFilter.vue';
const entityId = computed(() => route.params.id);
const { openCloneDialog } = cloneItem();
@ -311,6 +313,11 @@ const columns = computed(() => [
:label="t('item.searchbar.label')"
:info="t('You can search by id')"
/>
<RightMenu>
<template #right-panel>
<ItemListFilter data-key="ItemList" />
</template>
</RightMenu>
<VnTable
ref="tableRef"
data-key="ItemList"
@ -329,6 +336,7 @@ const columns = computed(() => [
auto-load
redirect="Item"
:is-editable="false"
:right-search="false"
:filer="itemFilter"
>
<template #column-id="{ row }">

View File

@ -174,7 +174,7 @@ function ticketFilter(ticket) {
<QTooltip>{{ t('Client Frozen') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity.problem.includes('hasRisk')"
v-if="entity?.problem?.includes('hasRisk')"
name="vn:risk"
size="xs"
color="primary"

View File

@ -99,6 +99,7 @@ function toTicketUrl(section) {
ref="summaryRef"
:url="`Tickets/${entityId}/summary`"
data-key="TicketSummary"
data-testid="ticketSummary"
>
<template #header-left>
<VnToSummary

View File

@ -457,6 +457,7 @@ function setReference(data) {
data-key="Ticket"
:label="t('Search ticket')"
:info="t('You can search by ticket id or alias')"
data-testid="ticketListSearchBar"
/>
<RightMenu>
<template #right-panel>
@ -484,6 +485,7 @@ function setReference(data) {
'row-key': 'id',
selection: 'multiple',
}"
data-testid="ticketListTable"
>
<template #column-statusIcons="{ row }">
<div class="q-gutter-x-xs">

View File

@ -3,11 +3,12 @@ import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'src/components/FetchData.vue';
const tableRef = ref();
const payrollComponents = ref([]);
const { t } = useI18n();
const route = useRoute();
const entityId = computed(() => route.params.id);
const columns = computed(() => [
{
align: 'left',
@ -25,8 +26,9 @@ const columns = computed(() => [
create: true,
component: 'select',
attrs: {
url: 'payrollComponents',
fields: ['id', 'name'],
options: payrollComponents,
optionLabel: 'name',
optionValue: 'id',
},
cardVisible: true,
},
@ -73,6 +75,16 @@ const columns = computed(() => [
]);
</script>
<template>
<FetchData
url="PayrollComponents"
:filter="{
fields: ['id', 'name'],
where: { name: { neq: '' } },
order: 'name ASC',
}"
@on-fetch="(data) => (payrollComponents = data)"
auto-load
/>
<VnTable
ref="tableRef"
data-key="WorkerBalance"
@ -94,6 +106,7 @@ const columns = computed(() => [
:is-editable="true"
:use-model="true"
:default-remove="false"
search-url="balance"
/>
</template>
<i18n>

View File

@ -134,6 +134,7 @@ const columns = computed(() => [
:is-editable="true"
:use-model="true"
:default-remove="false"
search-url="formation"
/>
</template>
<style lang="scss" scoped>

View File

@ -100,5 +100,6 @@ const columns = [
:is-editable="true"
:use-model="true"
:default-remove="false"
search-url="medical"
/>
</template>

View File

@ -71,6 +71,7 @@ function setNotifications(data) {
:default-remove="false"
:default-save="false"
@on-fetch="setNotifications"
search-url="notifications"
>
<template #body>
<div

View File

@ -63,6 +63,7 @@ function reloadData() {
url="DeviceProductionUsers"
:filter="{ where: { userFk: routeId } }"
order="id"
search-url="pda"
auto-load
>
<template #body="{ rows }">

View File

@ -93,7 +93,7 @@ export const useInvoiceOutGlobalStore = defineStore({
async makeInvoice(formData, clientsToInvoice) {
this.invoicing = true;
this.status = 'packageInvoicing';
const promises = [];
try {
this.printer = formData.printer;
const params = {
@ -118,10 +118,11 @@ export const useInvoiceOutGlobalStore = defineStore({
);
throw new Error("There aren't addresses to invoice");
}
for (const address of this.addresses) {
await this.invoiceClient(address, formData);
this.status = 'invoicing';
for (let index = 0; index < this.parallelism; index++) {
promises.push(this.invoiceClient(formData, index));
}
await Promise.all(promises);
} catch (err) {
this.handleError(err);
}
@ -171,17 +172,14 @@ export const useInvoiceOutGlobalStore = defineStore({
}
},
async invoiceClient(address, formData) {
async invoiceClient(formData, index) {
const address = this.addresses[index];
if (!address || !this.status || this.status == 'stopping') {
this.status = 'stopping';
this.invoicing = false;
return;
}
try {
if (this.nRequests === this.parallelism || this.isInvoicing) return;
if (this.status === 'stopping') {
if (this.nRequests) return;
this.invoicing = false;
this.status = 'done';
return;
}
const params = {
clientId: address.clientId,
addressId: address.id,
@ -191,13 +189,11 @@ export const useInvoiceOutGlobalStore = defineStore({
serialType: formData.serialType,
};
this.status = 'invoicing';
this.invoicing = true;
const { data } = await axios.post('InvoiceOuts/invoiceClient', params);
if (data) await this.makePdfAndNotify(data, address);
this.addressIndex++;
this.isInvoicing = false;
} catch (err) {
if (err?.response?.status >= 400 && err?.response?.status < 500) {
@ -205,13 +201,16 @@ export const useInvoiceOutGlobalStore = defineStore({
return;
} else {
this.invoicing = false;
this.status = 'done';
notify(
'invoiceOut.globalInvoices.errors.criticalInvoiceError',
'negative'
);
throw new Error('Critical invoicing error, process stopped');
}
} finally {
this.addressIndex++;
if (this.status != 'stopping')
await this.invoiceClient(formData, this.addressIndex);
}
},
@ -234,7 +233,6 @@ export const useInvoiceOutGlobalStore = defineStore({
handleError(err) {
this.invoicing = false;
this.status = null;
throw err;
},
@ -279,7 +277,7 @@ export const useInvoiceOutGlobalStore = defineStore({
return 0;
}
let porcentaje = (state.addressIndex / this.getNAddresses) * 100;
return porcentaje;
return porcentaje?.toFixed(2);
},
getAddressNumber(state) {
return state.addressIndex;

View File

@ -28,7 +28,7 @@ describe('Client list', () => {
cy.get('.q-mt-lg > .q-btn--standard').click();
cy.checkNotification('Data created');
cy.checkNotification('created');
cy.url().should('include', '/summary');
});
it('Client list search client', () => {
@ -43,4 +43,21 @@ describe('Client list', () => {
cy.url().should('include', `/customer/${id}/summary`);
});
});
it('Client founded create ticket', () => {
const search = 'Jessica Jones';
cy.searchByLabel('Name', search);
cy.clickButtonsDescriptor(2);
cy.waitForElement('#formModel');
cy.waitForElement('.q-form');
cy.checkValueForm(1, search);
});
it('Client founded create order', () => {
const search = 'Jessica Jones';
cy.searchByLabel('Name', search);
cy.clickButtonsDescriptor(4);
cy.waitForElement('#formModel');
cy.waitForElement('.q-form');
cy.checkValueForm(2, search);
});
});

View File

@ -2,9 +2,8 @@
describe('InvoiceInBasicData', () => {
const formInputs = '.q-form > .q-card input';
const firstFormSelect = '.q-card > .vn-row:nth-child(1) > .q-select';
const documentBtns = '.q-form .q-field button';
const documentBtns = '[data-cy="dms-buttons"] button';
const dialogInputs = '.q-dialog input';
const dialogActionBtns = '.q-card__actions button';
beforeEach(() => {
cy.login('developer');
@ -21,27 +20,35 @@ describe('InvoiceInBasicData', () => {
cy.get(formInputs).eq(1).invoke('val').should('eq', '4739');
});
it('should edit the dms data', () => {
it('should edit, remove and create the dms data', () => {
const firtsInput = 'Ticket:65';
const secondInput = "I don't know what posting here!";
//edit
cy.get(documentBtns).eq(1).click();
cy.get(dialogInputs).eq(0).type(`{selectall}${firtsInput}`);
cy.get('textarea').type(`{selectall}${secondInput}`);
cy.get(dialogActionBtns).eq(1).click();
cy.get('[data-cy="FormModelPopup_save"]').click();
cy.get(documentBtns).eq(1).click();
cy.get(dialogInputs).eq(0).invoke('val').should('eq', firtsInput);
cy.get('textarea').invoke('val').should('eq', secondInput);
});
cy.get('[data-cy="FormModelPopup_save"]').click();
cy.checkNotification('Data saved');
it('should throw an error creating a new dms if a file is not attached', () => {
cy.get(formInputs).eq(7).type('{selectall}{backspace}');
cy.get(documentBtns).eq(0).click();
cy.get(dialogActionBtns).eq(1).click();
cy.get('.q-notification__message').should(
'have.text',
"The files can't be empty"
//remove
cy.get(documentBtns).eq(2).click();
cy.get('[data-cy="VnConfirm_confirm"]').click();
cy.checkNotification('Data saved');
//create
cy.get('[data-cy="dms-create"]').eq(0).click();
cy.get('[data-cy="VnDms_inputFile"').selectFile(
'test/cypress/fixtures/image.jpg',
{
force: true,
}
);
cy.get('[data-cy="FormModelPopup_save"]').click();
cy.checkNotification('Data saved');
});
});

View File

@ -14,6 +14,8 @@ describe('Ticket descriptor', () => {
it('should clone the ticket without warehouse', () => {
cy.visit('/#/ticket/1/summary');
cy.intercept('GET', /\/api\/Tickets\/\d/).as('ticket');
cy.wait('@ticket');
cy.openActionsDescriptor();
cy.contains(listItem, toCloneOpt).click();
cy.clickConfirm();
@ -28,6 +30,8 @@ describe('Ticket descriptor', () => {
it('should set the weight of the ticket', () => {
cy.visit('/#/ticket/10/summary');
cy.intercept('GET', /\/api\/Tickets\/\d/).as('ticket');
cy.wait('@ticket');
cy.openActionsDescriptor();
cy.contains(listItem, setWeightOpt).click();
cy.intercept('POST', /\/api\/Tickets\/\d+\/setWeight/).as('weight');

View File

@ -0,0 +1,54 @@
/// <reference types="cypress" />
describe('Ticket descriptor', () => {
const firstRow = 'tbody > :nth-child(1)';
beforeEach(() => {
cy.login('developer');
cy.viewport(1920, 1080);
cy.visit('/#/ticket/list');
});
const searchResults = (search) => {
cy.dataCy('vnSearchBar').find('input').focus();
if (search) cy.dataCy('vnSearchBar').find('input').type(search);
cy.dataCy('vnSearchBar').find('input').type('{enter}');
cy.dataCy('ticketListTable').should('exist');
cy.get(firstRow).should('exist');
};
it('should search results', () => {
cy.dataCy('ticketListTable').should('not.exist');
cy.get('.q-field__control').should('exist');
searchResults();
});
it('should open ticket sales', () => {
searchResults();
cy.window().then((win) => {
cy.stub(win, 'open').as('windowOpen');
});
cy.get(firstRow).find('.q-btn:first').click();
cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/);
});
it('should open ticket summary', () => {
searchResults();
cy.get(firstRow).find('.q-btn:last').click();
cy.dataCy('ticketSummary').should('exist');
});
it('Client list create new client', () => {
cy.dataCy('vnTableCreateBtn').should('exist');
cy.dataCy('vnTableCreateBtn').click();
const data = {
Customer: { val: 1, type: 'select' },
Warehouse: { val: 'Warehouse One', type: 'select' },
Address: { val: 'employee: employeeLane, CENTRAL CITY', type: 'select' },
Landed: { val: '01-01-2024', type: 'date' },
};
cy.fillInForm(data);
cy.get('.q-mt-lg > .q-btn--standard').click();
cy.checkNotification('Data created');
cy.url().should('match', /\/ticket\/\d+\/summary/);
});
});

View File

@ -267,7 +267,13 @@ Cypress.Commands.add('openActionDescriptor', (opt) => {
});
Cypress.Commands.add('openActionsDescriptor', () => {
cy.get('.header > :nth-child(3) > .q-btn__content > .q-icon').click();
cy.get('[data-cy="descriptor-more-opts"]').click();
});
Cypress.Commands.add('clickButtonsDescriptor', (id) => {
cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`)
.invoke('removeAttr', 'target')
.click();
});
Cypress.Commands.add('openUserPanel', () => {
@ -290,6 +296,22 @@ Cypress.Commands.add('checkNotification', (text) => {
});
});
Cypress.Commands.add('checkValueForm', (id, search) => {
cy.get(
`.grid-create > :nth-child(${id}) > .q-field__inner>.q-field__control> .q-field__control-container>.q-field__native >.q-field__input`
).should('have.value', search);
});
Cypress.Commands.add('checkValueSelectForm', (id, search) => {
cy.get(
`.grid-create > :nth-child(${id}) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container>.q-field__native>.q-field__input`
).should('have.value', search);
});
Cypress.Commands.add('searchByLabel', (label, value) => {
cy.get(`[label="${label}"] > .q-field > .q-field__inner`).type(`${value}{enter}`);
});
Cypress.Commands.add('dataCy', (dataTestId, attr = 'data-testid') => {
return cy.get(`[${attr}="${dataTestId}"]`);
});

View File

@ -0,0 +1,47 @@
import { describe, expect, it, beforeAll, beforeEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import VnTable from 'src/components/VnTable/VnTable.vue';
describe('VnTable', () => {
let wrapper;
let vm;
beforeAll(() => {
wrapper = createWrapper(VnTable, {
propsData: {
columns: [],
},
});
vm = wrapper.vm;
});
beforeEach(() => (vm.selected = []));
describe('handleSelection()', () => {
const rows = [{ $index: 0 }, { $index: 1 }, { $index: 2 }];
const selectedRows = [{ $index: 1 }];
it('should add rows to selected when shift key is pressed and rows are added except last one', () => {
vm.handleSelection(
{ evt: { shiftKey: true }, added: true, rows: selectedRows },
rows
);
expect(vm.selected).toEqual([{ $index: 0 }]);
});
it('should not add rows to selected when shift key is not pressed', () => {
vm.handleSelection(
{ evt: { shiftKey: false }, added: true, rows: selectedRows },
rows
);
expect(vm.selected).toEqual([]);
});
it('should not add rows to selected when rows are not added', () => {
vm.handleSelection(
{ evt: { shiftKey: true }, added: false, rows: selectedRows },
rows
);
expect(vm.selected).toEqual([]);
});
});
});