5673-claim-development & crudModel #71
|
@ -64,7 +64,7 @@ module.exports = {
|
|||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['test/cypress/**/*.spec.{js,ts}'],
|
||||
files: ['test/cypress/**/*.*'],
|
||||
extends: [
|
||||
// Add Cypress-specific lint rules, globals and Cypress plugin
|
||||
// See https://github.com/cypress-io/eslint-plugin-cypress#rules
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = defineConfig({
|
|||
screenshotsFolder: 'test/cypress/screenshots',
|
||||
supportFile: 'test/cypress/support/index.js',
|
||||
videosFolder: 'test/cypress/videos',
|
||||
video: true,
|
||||
video: false,
|
||||
specPattern: 'test/cypress/integration/*.spec.js',
|
||||
experimentalRunAllSpecs: true,
|
||||
component: {
|
||||
|
|
|
@ -0,0 +1,322 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useValidator } from 'src/composables/useValidator';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import SkeletonTable from 'components/ui/SkeletonTable.vue';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const { validate } = useValidator();
|
||||
|
||||
const $props = defineProps({
|
||||
model: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
saveUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
primaryKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
dataRequired: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
defaultSave: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defaultReset: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defaultRemove: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
selected: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
saveFn: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
const hasChanges = ref(false);
|
||||
const originalData = ref();
|
||||
const vnPaginateRef = ref();
|
||||
const formData = ref();
|
||||
const formUrl = computed(() => $props.url);
|
||||
|
||||
const emit = defineEmits(['onFetch', 'update:selected']);
|
||||
|
||||
defineExpose({
|
||||
reload,
|
||||
insert,
|
||||
remove,
|
||||
onSubmit,
|
||||
reset,
|
||||
hasChanges,
|
||||
});
|
||||
|
||||
function tMobile(...args) {
|
||||
if (!quasar.platform.is.mobile) return t(...args);
|
||||
}
|
||||
|
||||
async function fetch(data) {
|
||||
if (data && Array.isArray(data)) {
|
||||
let $index = 0;
|
||||
data.map((d) => (d.$index = $index++));
|
||||
}
|
||||
|
||||
originalData.value = data && JSON.parse(JSON.stringify(data));
|
||||
formData.value = data && JSON.parse(JSON.stringify(data));
|
||||
watch(formData, () => (hasChanges.value = true), { deep: true });
|
||||
|
||||
emit('onFetch', data);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await fetch(originalData.value);
|
||||
hasChanges.value = false;
|
||||
}
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
function filter(value, update, filterOptions) {
|
||||
update(
|
||||
() => {
|
||||
const { options, filterFn, field } = filterOptions;
|
||||
|
||||
options.value = filterFn(options, value, field);
|
||||
},
|
||||
(ref) => {
|
||||
ref.setOptionIndex(-1);
|
||||
ref.moveOptionSelection(1, true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (!hasChanges.value) {
|
||||
return quasar.notify({
|
||||
type: 'negative',
|
||||
message: t('globals.noChanges'),
|
||||
});
|
||||
}
|
||||
isLoading.value = true;
|
||||
await saveChanges();
|
||||
}
|
||||
|
||||
async function saveChanges(data) {
|
||||
if ($props.saveFn) return $props.saveFn(data, getChanges);
|
||||
const changes = data || getChanges();
|
||||
try {
|
||||
await axios.post($props.saveUrl || $props.url + '/crud', changes);
|
||||
} catch (e) {
|
||||
return (isLoading.value = false);
|
||||
}
|
||||
originalData.value = JSON.parse(JSON.stringify(formData.value));
|
||||
if (changes.creates?.length) await vnPaginateRef.value.fetch();
|
||||
|
||||
hasChanges.value = false;
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
async function insert() {
|
||||
const $index = formData.value.length
|
||||
? formData.value[formData.value.length - 1].$index + 1
|
||||
: 0;
|
||||
formData.value.push(Object.assign({ $index }, $props.dataRequired));
|
||||
hasChanges.value = true;
|
||||
}
|
||||
|
||||
async function remove(data) {
|
||||
if (!data.length)
|
||||
return quasar.notify({
|
||||
type: 'warning',
|
||||
message: t('globals.noChanges'),
|
||||
alexm marked this conversation as resolved
|
||||
});
|
||||
|
||||
const pk = $props.primaryKey;
|
||||
let ids = data.map((d) => d[pk]).filter(Boolean);
|
||||
let preRemove = data.map((d) => (d[pk] ? null : d.$index)).filter(Boolean);
|
||||
let newData = formData.value;
|
||||
|
||||
if (preRemove.length) {
|
||||
newData = newData.filter(
|
||||
(form) => !preRemove.some((index) => index == form.$index)
|
||||
);
|
||||
const changes = getChanges();
|
||||
if (!changes.creates?.length && !changes.updates?.length)
|
||||
hasChanges.value = false;
|
||||
fetch(newData);
|
||||
}
|
||||
if (ids.length) {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('confirmDeletion'),
|
||||
message: t('confirmDeletionMessage'),
|
||||
newData,
|
||||
ids,
|
||||
},
|
||||
})
|
||||
.onOk(async () => {
|
||||
await saveChanges({ deletes: ids });
|
||||
newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
|
||||
fetch(newData);
|
||||
});
|
||||
}
|
||||
emit('update:selected', []);
|
||||
}
|
||||
|
||||
function getChanges() {
|
||||
const updates = [];
|
||||
const creates = [];
|
||||
|
||||
const pk = $props.primaryKey;
|
||||
|
||||
for (const [i, row] of formData.value.entries()) {
|
||||
if (!row[pk]) {
|
||||
creates.push(row);
|
||||
} else if (originalData.value) {
|
||||
const data = getDifferences(originalData.value[i], row);
|
||||
if (!isEmpty(data)) {
|
||||
updates.push({
|
||||
data,
|
||||
where: { [pk]: row[pk] },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const changes = { updates, creates };
|
||||
|
||||
for (let prop in changes) {
|
||||
if (changes[prop].length === 0) changes[prop] = undefined;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
function getDifferences(obj1, obj2) {
|
||||
let diff = {};
|
||||
delete obj1.$index;
|
||||
delete obj2.$index;
|
||||
|
||||
for (let key in obj1) {
|
||||
if (obj2[key] && obj1[key] !== obj2[key]) {
|
||||
diff[key] = obj2[key];
|
||||
}
|
||||
}
|
||||
for (let key in obj2) {
|
||||
if (obj1[key] === undefined || obj1[key] !== obj2[key]) {
|
||||
diff[key] = obj2[key];
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
function isEmpty(obj) {
|
||||
if (obj == null) return true;
|
||||
if (obj === undefined) return true;
|
||||
if (Object.keys(obj).length === 0) return true;
|
||||
|
||||
if (obj.length > 0) return false;
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
vnPaginateRef.value.fetch();
|
||||
}
|
||||
|
||||
watch(formUrl, async () => {
|
||||
originalData.value = null;
|
||||
reset();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VnPaginate
|
||||
:url="url"
|
||||
v-bind="$attrs"
|
||||
@on-fetch="fetch"
|
||||
:skeleton="false"
|
||||
ref="vnPaginateRef"
|
||||
>
|
||||
<template #body v-if="formData">
|
||||
<slot
|
||||
name="body"
|
||||
:rows="formData"
|
||||
:validate="validate"
|
||||
:filter="filter"
|
||||
></slot>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<SkeletonTable v-if="!formData" />
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
<slot name="moreActions" />
|
||||
<QBtn
|
||||
:label="tMobile('globals.remove')"
|
||||
color="primary"
|
||||
icon="delete"
|
||||
flat
|
||||
@click="remove(selected)"
|
||||
:disable="!selected?.length"
|
||||
:title="t('globals.remove')"
|
||||
v-if="$props.defaultRemove"
|
||||
/>
|
||||
<QBtn
|
||||
:label="tMobile('globals.reset')"
|
||||
color="primary"
|
||||
icon="restart_alt"
|
||||
flat
|
||||
@click="reset"
|
||||
:disable="!hasChanges"
|
||||
:title="t('globals.reset')"
|
||||
v-if="$props.defaultReset"
|
||||
/>
|
||||
<QBtn
|
||||
:label="tMobile('globals.save')"
|
||||
color="primary"
|
||||
icon="save"
|
||||
@click="onSubmit"
|
||||
:disable="!hasChanges"
|
||||
:title="t('globals.save')"
|
||||
v-if="$props.defaultSave"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</Teleport>
|
||||
<QInnerLoading
|
||||
:showing="isLoading"
|
||||
:label="t && t('globals.pleaseWait')"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"confirmDeletion": "Confirm deletion",
|
||||
"confirmDeletionMessage": "Are you sure you want to delete this?"
|
||||
},
|
||||
"es": {
|
||||
"confirmDeletion": "Confirmar eliminación",
|
||||
"confirmDeletionMessage": "Seguro que quieres eliminar?"
|
||||
}
|
||||
}
|
||||
</i18n>
|
|
@ -4,12 +4,14 @@ import { onMounted, onUnmounted, computed, ref, watch } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useValidator } from 'src/composables/useValidator';
|
||||
import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const state = useState();
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const { validate } = useValidator();
|
||||
|
||||
const $props = defineProps({
|
||||
|
@ -29,6 +31,10 @@ const $props = defineProps({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
defaultActions: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
@ -45,17 +51,21 @@ onUnmounted(() => {
|
|||
|
||||
const isLoading = ref(false);
|
||||
const hasChanges = ref(false);
|
||||
const formData = computed(() => state.get($props.model));
|
||||
const originalData = ref();
|
||||
const formData = computed(() => state.get($props.model));
|
||||
const formUrl = computed(() => $props.url);
|
||||
|
||||
function tMobile(...args) {
|
||||
if (!quasar.platform.is.mobile) return t(...args);
|
||||
}
|
||||
|
||||
async function fetch() {
|
||||
const { data } = await axios.get($props.url, {
|
||||
params: { filter: $props.filter },
|
||||
});
|
||||
|
||||
state.set($props.model, data);
|
||||
originalData.value = Object.assign({}, data);
|
||||
originalData.value = data && JSON.parse(JSON.stringify(data));
|
||||
|
||||
watch(formData.value, () => (hasChanges.value = true));
|
||||
|
||||
|
@ -72,13 +82,18 @@ async function save() {
|
|||
isLoading.value = true;
|
||||
await axios.patch($props.urlUpdate || $props.url, formData.value);
|
||||
|
||||
originalData.value = formData.value;
|
||||
originalData.value = JSON.parse(JSON.stringify(formData.value));
|
||||
hasChanges.value = false;
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
state.set($props.model, originalData.value);
|
||||
originalData.value = JSON.parse(JSON.stringify(originalData.value));
|
||||
|
||||
watch(formData.value, () => (hasChanges.value = true));
|
||||
|
||||
emit('onFetch', state.get($props.model));
|
||||
hasChanges.value = false;
|
||||
}
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
|
@ -109,20 +124,31 @@ watch(formUrl, async () => {
|
|||
</QBanner>
|
||||
<QForm v-if="formData" @submit="save" @reset="reset" class="q-pa-md">
|
||||
<slot name="form" :data="formData" :validate="validate" :filter="filter"></slot>
|
||||
<div class="q-mt-lg">
|
||||
<slot name="actions">
|
||||
<QBtn :label="t('globals.save')" type="submit" color="primary" />
|
||||
<QBtn
|
||||
:label="t('globals.reset')"
|
||||
type="reset"
|
||||
class="q-ml-sm"
|
||||
color="primary"
|
||||
flat
|
||||
:disable="!hasChanges"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</QForm>
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<div v-if="$props.defaultActions">
|
||||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
<slot name="moreActions" />
|
||||
<QBtn
|
||||
:label="tMobile('globals.reset')"
|
||||
color="primary"
|
||||
icon="restart_alt"
|
||||
flat
|
||||
@click="reset"
|
||||
:disable="!hasChanges"
|
||||
:title="t('globals.reset')"
|
||||
/>
|
||||
<QBtn
|
||||
:label="tMobile('globals.save')"
|
||||
color="primary"
|
||||
icon="save"
|
||||
@click="save"
|
||||
alexm
commented
$props.saveFn que le puedas pasar una función(data) $props.saveFn que le puedas pasar una función(data)
|
||||
:disable="!hasChanges"
|
||||
:title="t('globals.save')"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</div>
|
||||
</Teleport>
|
||||
<SkeletonForm v-if="!formData" />
|
||||
<QInnerLoading
|
||||
:showing="isLoading"
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<script setup>
|
||||
import { ref, toRefs, watch, computed } from 'vue';
|
||||
const emit = defineEmits(['update:modelValue', 'update:options']);
|
||||
|
||||
const $props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
optionLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const { optionLabel, options } = toRefs($props);
|
||||
const myOptions = ref([]);
|
||||
const myOptionsOriginal = ref([]);
|
||||
function setOptions(data) {
|
||||
myOptions.value = JSON.parse(JSON.stringify(data));
|
||||
myOptionsOriginal.value = JSON.parse(JSON.stringify(data));
|
||||
}
|
||||
setOptions(options.value);
|
||||
|
||||
const filter = (val, options) => {
|
||||
const search = val.toLowerCase();
|
||||
|
||||
if (val === '') return options;
|
||||
return options.filter((row) => {
|
||||
const id = row.id;
|
||||
const name = row[$props.optionLabel].toLowerCase();
|
||||
|
||||
const idMatches = id == search;
|
||||
const nameMatches = name.indexOf(search) > -1;
|
||||
|
||||
return idMatches || nameMatches;
|
||||
});
|
||||
};
|
||||
|
||||
const filterHandler = (val, update) => {
|
||||
update(() => {
|
||||
myOptions.value = filter(val, myOptionsOriginal.value);
|
||||
});
|
||||
};
|
||||
|
||||
watch(options, (newValue) => {
|
||||
setOptions(newValue);
|
||||
});
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
return $props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QSelect
|
||||
v-model="value"
|
||||
:options="myOptions"
|
||||
:option-label="optionLabel"
|
||||
v-bind="$attrs"
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
@filter="filterHandler"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
/>
|
||||
</template>
|
|
@ -29,12 +29,14 @@ const $props = defineProps({
|
|||
|
||||
const slots = useSlots();
|
||||
const { t } = useI18n();
|
||||
const entity = ref();
|
||||
|
||||
onMounted(() => fetch());
|
||||
onMounted(async () => {
|
||||
await fetch();
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
const entity = ref();
|
||||
async function fetch() {
|
||||
const params = {};
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<div class="q-pa-md w">
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<div class="col-1">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
|
||||
<div class="col-1">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.w {
|
||||
width: 80vw;
|
||||
}
|
||||
</style>
|
|
@ -46,6 +46,10 @@ const props = defineProps({
|
|||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
skeleton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFetch', 'onPaginate']);
|
||||
|
@ -144,7 +148,10 @@ async function onLoad(...params) {
|
|||
{{ t('No results found') }}
|
||||
</h5>
|
||||
</div>
|
||||
<div v-if="props.autoLoad && !store.data" class="card-list q-gutter-y-md">
|
||||
<div
|
||||
v-if="props.skeleton && props.autoLoad && !store.data"
|
||||
class="card-list q-gutter-y-md"
|
||||
>
|
||||
<QCard class="card" v-for="$index in $props.limit" :key="$index">
|
||||
<QItem v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
|
||||
<QItemSection class="q-pa-md">
|
||||
|
@ -164,7 +171,7 @@ async function onLoad(...params) {
|
|||
</QCard>
|
||||
</div>
|
||||
</div>
|
||||
<QInfiniteScroll v-if="store.data" @load="onLoad" :offset="offset">
|
||||
<QInfiniteScroll v-if="store.data" @load="onLoad" :offset="offset" class="full-width">
|
||||
<slot name="body" :rows="store.data"></slot>
|
||||
<div v-if="isLoading" class="info-row q-pa-md text-center">
|
||||
<QSpinner color="orange" size="md" />
|
||||
|
|
|
@ -38,11 +38,11 @@ export function useArrayData(key, userOptions) {
|
|||
'limit',
|
||||
'skip',
|
||||
'userParams',
|
||||
'userFilter'
|
||||
'userFilter',
|
||||
];
|
||||
if (typeof userOptions === 'object') {
|
||||
for (const option in userOptions) {
|
||||
const isEmpty = userOptions[option] == null || userOptions[option] == ''
|
||||
const isEmpty = userOptions[option] == null || userOptions[option] == '';
|
||||
if (isEmpty || !allowedOptions.includes(option)) continue;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
||||
|
@ -73,7 +73,7 @@ export function useArrayData(key, userOptions) {
|
|||
|
||||
Object.assign(params, store.userParams);
|
||||
|
||||
store.isLoading = true
|
||||
store.isLoading = true;
|
||||
const response = await axios.get(store.url, {
|
||||
signal: canceller.signal,
|
||||
params,
|
||||
|
@ -94,7 +94,7 @@ export function useArrayData(key, userOptions) {
|
|||
updateStateParams();
|
||||
}
|
||||
|
||||
store.isLoading = false
|
||||
store.isLoading = false;
|
||||
|
||||
canceller = null;
|
||||
}
|
||||
|
@ -153,8 +153,8 @@ export function useArrayData(key, userOptions) {
|
|||
});
|
||||
}
|
||||
|
||||
const totalRows = computed(() => store.data && store.data.length || 0);
|
||||
const isLoading = computed(() => store.isLoading || false)
|
||||
const totalRows = computed(() => (store.data && store.data.length) || 0);
|
||||
const isLoading = computed(() => store.isLoading || false);
|
||||
|
||||
return {
|
||||
fetch,
|
||||
|
@ -167,6 +167,6 @@ export function useArrayData(key, userOptions) {
|
|||
hasMoreData,
|
||||
totalRows,
|
||||
updateStateParams,
|
||||
isLoading
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,15 +3,13 @@ import { useI18n } from 'vue-i18n';
|
|||
import axios from 'axios';
|
||||
import validator from 'validator';
|
||||
|
||||
|
||||
const models = ref(null);
|
||||
|
||||
export function useValidator() {
|
||||
if (!models.value) fetch();
|
||||
|
||||
function fetch() {
|
||||
axios.get('Schemas/ModelInfo')
|
||||
.then(response => models.value = response.data)
|
||||
axios.get('Schemas/ModelInfo').then((response) => (models.value = response.data));
|
||||
}
|
||||
|
||||
function validate(propertyRule) {
|
||||
|
@ -38,19 +36,18 @@ export function useValidator() {
|
|||
|
||||
const { t } = useI18n();
|
||||
const validations = function (validation) {
|
||||
|
||||
return {
|
||||
presence: (value) => {
|
||||
let message = `Value can't be empty`;
|
||||
if (validation.message)
|
||||
message = t(validation.message) || validation.message
|
||||
message = t(validation.message) || validation.message;
|
||||
|
||||
return !validator.isEmpty(value ? String(value) : '') || message
|
||||
return !validator.isEmpty(value ? String(value) : '') || message;
|
||||
},
|
||||
length: (value) => {
|
||||
const options = {
|
||||
min: validation.min || validation.is,
|
||||
max: validation.max || validation.is
|
||||
max: validation.max || validation.is,
|
||||
};
|
||||
|
||||
value = String(value);
|
||||
|
@ -69,14 +66,14 @@ export function useValidator() {
|
|||
},
|
||||
numericality: (value) => {
|
||||
if (validation.int)
|
||||
return validator.isInt(value) || 'Value should be integer'
|
||||
return validator.isNumeric(value) || 'Value should be a number'
|
||||
return validator.isInt(value) || 'Value should be integer';
|
||||
return validator.isNumeric(value) || 'Value should be a number';
|
||||
},
|
||||
custom: (value) => validation.bindedFunction(value) || 'Invalid value'
|
||||
custom: (value) => validation.bindedFunction(value) || 'Invalid value',
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
validate
|
||||
validate,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,10 +32,16 @@ body.body--light {
|
|||
--vn-text: #000000;
|
||||
--vn-gray: #f5f5f5;
|
||||
--vn-label: #5f5f5f;
|
||||
--vn-dark: white;
|
||||
}
|
||||
|
||||
body.body--dark {
|
||||
--vn-text: #ffffff;
|
||||
--vn-gray: #313131;
|
||||
--vn-label: #a8a8a8;
|
||||
--vn-dark: #292929;
|
||||
}
|
||||
|
||||
.bg-vn-dark {
|
||||
background-color: var(--vn-dark);
|
||||
}
|
||||
|
|
|
@ -266,6 +266,7 @@ export default {
|
|||
lines: 'Lines',
|
||||
rma: 'RMA',
|
||||
photos: 'Photos',
|
||||
development: 'Development',
|
||||
log: 'Audit logs',
|
||||
notes: 'Notes',
|
||||
},
|
||||
|
|
|
@ -264,6 +264,7 @@ export default {
|
|||
basicData: 'Datos básicos',
|
||||
lines: 'Líneas',
|
||||
rma: 'RMA',
|
||||
development: 'Trazabilidad',
|
||||
photos: 'Fotos',
|
||||
log: 'Registros de auditoría',
|
||||
notes: 'Notas',
|
||||
|
|
|
@ -44,17 +44,6 @@ onMounted(async () => {
|
|||
<LeftMenu source="card" />
|
||||
<QSeparator />
|
||||
<QList>
|
||||
<QItem
|
||||
active-class="text-primary"
|
||||
clickable
|
||||
v-ripple
|
||||
:href="`${salixUrl}/development`"
|
||||
>
|
||||
<QItemSection avatar>
|
||||
<QIcon name="vn:traceability"></QIcon>
|
||||
</QItemSection>
|
||||
<QItemSection>{{ t('Development') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem
|
||||
active-class="text-primary"
|
||||
clickable
|
||||
|
@ -68,8 +57,13 @@ onMounted(async () => {
|
|||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage class="q-pa-md">
|
||||
<RouterView></RouterView>
|
||||
<QPage>
|
||||
<QToolbar class="bg-vn-dark justify-end">
|
||||
<div id="st-data"></div>
|
||||
<QSpace />
|
||||
<div id="st-actions"></div>
|
||||
</QToolbar>
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
@ -80,6 +74,5 @@ es:
|
|||
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
|
||||
Details: Detalles
|
||||
Notes: Notas
|
||||
Development: Trazabilidad
|
||||
Action: Acción
|
||||
</i18n>
|
||||
|
|
|
@ -3,6 +3,8 @@ import { ref, computed } from 'vue';
|
|||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { toDate } from 'src/filters';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
|
||||
|
@ -19,6 +21,7 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const route = useRoute();
|
||||
const state = useState();
|
||||
const { t } = useI18n();
|
||||
|
||||
const entityId = computed(() => {
|
||||
|
@ -67,6 +70,7 @@ function stateColor(code) {
|
|||
const data = ref(useCardDescription());
|
||||
const setData = (entity) => {
|
||||
data.value = useCardDescription(entity.client.name, entity.id);
|
||||
state.set('ClaimDescriptor', entity);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const claimDevelopmentForm = ref();
|
||||
const claimReasons = ref([]);
|
||||
const claimResults = ref([]);
|
||||
const claimResponsibles = ref([]);
|
||||
const claimRedeliveries = ref([]);
|
||||
const workers = ref([]);
|
||||
const selected = ref([]);
|
||||
|
||||
const developmentsFilter = {
|
||||
fields: [
|
||||
'id',
|
||||
'claimFk',
|
||||
'claimReasonFk',
|
||||
'claimResultFk',
|
||||
'claimResponsibleFk',
|
||||
'workerFk',
|
||||
'claimRedeliveryFk',
|
||||
],
|
||||
where: {
|
||||
claimFk: route.params.id,
|
||||
},
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'claimReason',
|
||||
label: t('Reason'),
|
||||
field: (row) => row.claimReasonFk,
|
||||
sortable: true,
|
||||
options: claimReasons.value,
|
||||
required: true,
|
||||
model: 'claimReasonFk',
|
||||
optionValue: 'id',
|
||||
optionLabel: 'description',
|
||||
},
|
||||
{
|
||||
name: 'claimResult',
|
||||
label: t('Result'),
|
||||
field: (row) => row.claimResultFk,
|
||||
sortable: true,
|
||||
options: claimResults.value,
|
||||
required: true,
|
||||
model: 'claimResultFk',
|
||||
optionValue: 'id',
|
||||
optionLabel: 'description',
|
||||
},
|
||||
{
|
||||
name: 'claimResponsible',
|
||||
label: t('Responsible'),
|
||||
field: (row) => row.claimResponsibleFk,
|
||||
sortable: true,
|
||||
options: claimResponsibles.value,
|
||||
required: true,
|
||||
model: 'claimResponsibleFk',
|
||||
optionValue: 'id',
|
||||
optionLabel: 'description',
|
||||
},
|
||||
{
|
||||
name: 'worker',
|
||||
label: t('Worker'),
|
||||
field: (row) => row.workerFk,
|
||||
sortable: true,
|
||||
options: workers.value,
|
||||
model: 'workerFk',
|
||||
optionValue: 'id',
|
||||
optionLabel: 'nickname',
|
||||
},
|
||||
{
|
||||
name: 'claimRedelivery',
|
||||
label: t('Redelivery'),
|
||||
field: (row) => row.claimRedeliveryFk,
|
||||
sortable: true,
|
||||
options: claimRedeliveries.value,
|
||||
required: true,
|
||||
model: 'claimRedeliveryFk',
|
||||
optionValue: 'id',
|
||||
optionLabel: 'description',
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
url="ClaimReasons"
|
||||
order="description"
|
||||
@on-fetch="(data) => (claimReasons = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="ClaimResults"
|
||||
order="description"
|
||||
@on-fetch="(data) => (claimResults = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="ClaimResponsibles"
|
||||
order="description"
|
||||
@on-fetch="(data) => (claimResponsibles = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="ClaimRedeliveries"
|
||||
order="description"
|
||||
@on-fetch="(data) => (claimRedeliveries = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Workers/activeWithInheritedRole"
|
||||
:where="{ role: 'employee' }"
|
||||
@on-fetch="(data) => (workers = data)"
|
||||
auto-load
|
||||
/>
|
||||
<CrudModel
|
||||
data-key="ClaimDevelopments"
|
||||
url="ClaimDevelopments"
|
||||
model="claimDevelopment"
|
||||
:filter="developmentsFilter"
|
||||
ref="claimDevelopmentForm"
|
||||
:data-required="{ claimFk: route.params.id }"
|
||||
v-model:selected="selected"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
row-key="$index"
|
||||
selection="multiple"
|
||||
hide-pagination
|
||||
v-model:selected="selected"
|
||||
:grid="$q.screen.lt.md"
|
||||
>
|
||||
<template #body-cell="{ row, col }">
|
||||
<QTd auto-width>
|
||||
<VnSelectFilter
|
||||
:label="col.label"
|
||||
v-model="row[col.model]"
|
||||
:options="col.options"
|
||||
:option-value="col.optionValue"
|
||||
:option-label="col.optionLabel"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #item="props">
|
||||
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
||||
<QCard bordered flat>
|
||||
<QCardSection>
|
||||
<QCheckbox v-model="props.selected" dense />
|
||||
</QCardSection>
|
||||
<QSeparator />
|
||||
<QList dense>
|
||||
<QItem v-for="col in props.cols" :key="col.name">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="col.label"
|
||||
v-model="props.row[col.model]"
|
||||
:options="col.options"
|
||||
:option-value="col.optionValue"
|
||||
:option-label="col.optionLabel"
|
||||
dense
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</CrudModel>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||
<QBtn fab color="primary" icon="add" @click="claimDevelopmentForm.insert()" />
|
||||
</QPageSticky>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grid-style-transition {
|
||||
transition: transform 0.28s, background-color 0.28s;
|
||||
}
|
||||
.maxwidth {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Reason: Motivo
|
||||
Result: Consecuencia
|
||||
Responsible: Responsable
|
||||
Worker: Trabajador
|
||||
Redelivery: Devolución
|
||||
</i18n>
|
|
@ -6,9 +6,8 @@ import { useQuasar } from 'quasar';
|
|||
import { useRoute } from 'vue-router';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
|
||||
import { toDate, toCurrency, toPercentage } from 'filters/index';
|
||||
import VnDiscount from 'components/common/vnDiscount.vue';
|
||||
|
@ -17,6 +16,7 @@ import ClaimLinesImport from './ClaimLinesImport.vue';
|
|||
const quasar = useQuasar();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const arrayData = useArrayData('ClaimLines');
|
||||
const store = arrayData.store;
|
||||
|
@ -36,6 +36,7 @@ const linesFilter = {
|
|||
},
|
||||
};
|
||||
|
||||
const claimLinesForm = ref();
|
||||
const claim = ref(null);
|
||||
async function onFetchClaim(data) {
|
||||
claim.value = data;
|
||||
|
@ -46,6 +47,7 @@ async function onFetchClaim(data) {
|
|||
const amount = ref(0);
|
||||
const amountClaimed = ref(0);
|
||||
async function onFetch(rows) {
|
||||
if (!rows || rows.length) return;
|
||||
amount.value = rows.reduce(
|
||||
(acumulator, { sale }) => acumulator + sale.price * sale.quantity,
|
||||
0
|
||||
|
@ -141,47 +143,6 @@ function onUpdateDiscount(response) {
|
|||
});
|
||||
}
|
||||
|
||||
async function confirmRemove() {
|
||||
const rows = selected.value;
|
||||
const count = rows.length;
|
||||
|
||||
if (count === 0) {
|
||||
return quasar.notify({
|
||||
message: 'You must select at least one row',
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Delete claimed sales'),
|
||||
message: t('You are about to remove {count} rows', count, { count }),
|
||||
data: { rows },
|
||||
promise: remove,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
for (const row of rows) {
|
||||
const orgData = store.data;
|
||||
const index = orgData.findIndex((item) => item.id === row.id);
|
||||
store.data.splice(index, 1);
|
||||
selected.value = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function remove({ rows }) {
|
||||
if (!rows.length) return;
|
||||
const body = { deletes: rows.map((row) => row.id) };
|
||||
await axios.post(`ClaimBeginnings/crud`, body);
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
message: t('globals.rowRemoved'),
|
||||
});
|
||||
}
|
||||
|
||||
function showImportDialog() {
|
||||
quasar
|
||||
.dialog({
|
||||
|
@ -191,10 +152,8 @@ function showImportDialog() {
|
|||
}
|
||||
</script>
|
||||
<template>
|
||||
<QPageSticky position="top" :offset="[0, 0]" expand>
|
||||
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()">
|
||||
<QToolbar class="bg-dark text-white">
|
||||
<QToolbarTitle> {{ t('Claimed lines') }} </QToolbarTitle>
|
||||
<QSpace />
|
||||
<div class="row q-gutter-md">
|
||||
<div>
|
||||
{{ t('Amount') }}
|
||||
|
@ -211,7 +170,7 @@ function showImportDialog() {
|
|||
</div>
|
||||
</div>
|
||||
</QToolbar>
|
||||
</QPageSticky>
|
||||
</Teleport>
|
||||
|
||||
<FetchData
|
||||
:url="`Claims/${route.params.id}`"
|
||||
|
@ -221,11 +180,16 @@ function showImportDialog() {
|
|||
/>
|
||||
<div class="column items-center">
|
||||
<div class="list">
|
||||
<VnPaginate
|
||||
<CrudModel
|
||||
data-key="ClaimLines"
|
||||
ref="claimLinesForm"
|
||||
:url="`Claims/${route.params.id}/lines`"
|
||||
save-url="ClaimBeginnings/crud"
|
||||
:filter="linesFilter"
|
||||
@on-fetch="onFetch"
|
||||
v-model:selected="selected"
|
||||
:default-save="false"
|
||||
:default-reset="false"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
|
@ -361,46 +325,12 @@ function showImportDialog() {
|
|||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</CrudModel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Teleport
|
||||
v-if="stateStore.isHeaderMounted() && !$q.screen.lt.sm"
|
||||
to="#actions-prepend"
|
||||
>
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
v-if="selected.length > 0"
|
||||
@click="confirmRemove"
|
||||
icon="delete"
|
||||
color="primary"
|
||||
flat
|
||||
dense
|
||||
rounded
|
||||
>
|
||||
<QTooltip bottom> {{ t('globals.remove') }} </QTooltip>
|
||||
</QBtn>
|
||||
<QBtn @click="showImportDialog" icon="add" color="primary" flat dense rounded>
|
||||
<QTooltip bottom> {{ t('globals.add') }} </QTooltip>
|
||||
</QBtn>
|
||||
<QSeparator vertical />
|
||||
</div>
|
||||
</Teleport>
|
||||
<!-- v-if="quasar.platform.is.mobile" -->
|
||||
<QPageSticky v-if="$q.screen.lt.sm" position="bottom" :offset="[0, 0]" expand>
|
||||
<QToolbar class="bg-primary text-white q-pa-none">
|
||||
<QTabs class="full-width" align="justify" inline-label narrow-indicator>
|
||||
<QTab @click="showImportDialog" icon="add" :label="t('globals.add')" />
|
||||
<QSeparator vertical inset />
|
||||
<QTab
|
||||
@click="confirmRemove"
|
||||
icon="delete"
|
||||
:label="t('globals.remove')"
|
||||
:disable="selected.length === 0"
|
||||
/>
|
||||
</QTabs>
|
||||
</QToolbar>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||
<QBtn fab color="primary" icon="add" @click="showImportDialog()" />
|
||||
</QPageSticky>
|
||||
</template>
|
||||
|
||||
|
@ -421,7 +351,6 @@ en:
|
|||
You are about to remove <strong>{count}</strong> row |
|
||||
You are about to remove <strong>{count}</strong> rows'
|
||||
es:
|
||||
Claimed lines: Líneas reclamadas
|
||||
Delivered: Entregado
|
||||
Quantity: Cantidad
|
||||
Claimed: Reclamada
|
||||
|
|
|
@ -25,7 +25,7 @@ const body = {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="col items-center">
|
||||
<div class="column items-center">
|
||||
<VnNotes
|
||||
:add-note="true"
|
||||
:id="id"
|
||||
|
|
|
@ -4,7 +4,6 @@ import { ref, computed } from 'vue';
|
|||
import { useQuasar } from 'quasar';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
@ -12,7 +11,6 @@ import FetchData from 'components/FetchData.vue';
|
|||
const router = useRouter();
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
const session = useSession();
|
||||
const token = session.getToken();
|
||||
|
||||
|
@ -237,59 +235,20 @@ function onDrag() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Teleport
|
||||
v-if="stateStore.isHeaderMounted() && !quasar.platform.is.mobile"
|
||||
to="#actions-prepend"
|
||||
>
|
||||
<div class="row q-gutter-x-sm">
|
||||
<label for="fileInput">
|
||||
<QBtn
|
||||
@click="inputFile.nativeEl.click()"
|
||||
icon="add"
|
||||
color="primary"
|
||||
dense
|
||||
rounded
|
||||
>
|
||||
<QInput
|
||||
ref="inputFile"
|
||||
type="file"
|
||||
style="display: none"
|
||||
multiple
|
||||
v-model="files"
|
||||
@update:model-value="create()"
|
||||
/>
|
||||
<QTooltip bottom> {{ t('globals.add') }} </QTooltip>
|
||||
</QBtn>
|
||||
</label>
|
||||
<QSeparator vertical />
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<QPageSticky
|
||||
v-if="quasar.platform.is.mobile"
|
||||
position="bottom"
|
||||
:offset="[0, 0]"
|
||||
expand
|
||||
>
|
||||
<QToolbar class="bg-primary text-white q-pa-none">
|
||||
<QTabs class="full-width" align="justify" inline-label narrow-indicator>
|
||||
<QTab
|
||||
@click="inputFile.nativeEl.click()"
|
||||
icon="add_circle"
|
||||
:label="t('globals.add')"
|
||||
>
|
||||
<QInput
|
||||
ref="inputFile"
|
||||
type="file"
|
||||
style="display: none"
|
||||
multiple
|
||||
v-model="files"
|
||||
@update:model-value="create()"
|
||||
/>
|
||||
<QTooltip bottom> {{ t('globals.add') }} </QTooltip>
|
||||
</QTab>
|
||||
</QTabs>
|
||||
</QToolbar>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||
<label for="fileInput">
|
||||
<QBtn fab @click="inputFile.nativeEl.click()" icon="add" color="primary">
|
||||
<QInput
|
||||
ref="inputFile"
|
||||
type="file"
|
||||
style="display: none"
|
||||
multiple
|
||||
v-model="files"
|
||||
@update:model-value="create()"
|
||||
/>
|
||||
<QTooltip bottom> {{ t('globals.add') }} </QTooltip>
|
||||
</QBtn>
|
||||
</label>
|
||||
</QPageSticky>
|
||||
|
||||
<!-- MULTIMEDIA DIALOG START-->
|
||||
|
|
|
@ -1,48 +1,34 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { ref } from 'vue';
|
||||
import { watch, ref, computed, onUnmounted, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
import { toDate } from 'src/filters';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const route = useRoute();
|
||||
const state = useState();
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
const arrayData = useArrayData('ClaimRma');
|
||||
const selected = ref([]);
|
||||
const claimRmaRef = ref();
|
||||
const claim = computed(() => state.get('ClaimDescriptor'));
|
||||
|
||||
const claim = ref();
|
||||
const claimFilter = {
|
||||
fields: ['rma'],
|
||||
};
|
||||
|
||||
async function onFetch(data) {
|
||||
claim.value = data;
|
||||
|
||||
const filter = {
|
||||
include: {
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
include: {
|
||||
relation: 'user',
|
||||
},
|
||||
const claimRmaFilter = {
|
||||
include: {
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
include: {
|
||||
relation: 'user',
|
||||
},
|
||||
},
|
||||
order: 'created DESC',
|
||||
where: {
|
||||
code: claim.value.rma,
|
||||
},
|
||||
};
|
||||
|
||||
arrayData.applyFilter({ filter });
|
||||
}
|
||||
},
|
||||
order: 'created DESC',
|
||||
where: {
|
||||
code: claim.value?.rma,
|
||||
},
|
||||
};
|
||||
|
||||
async function addRow() {
|
||||
if (!claim.value.rma) {
|
||||
|
@ -56,7 +42,7 @@ async function addRow() {
|
|||
};
|
||||
|
||||
await axios.post(`ClaimRmas`, formData);
|
||||
await arrayData.refresh();
|
||||
await claimRmaRef.value.reload();
|
||||
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
|
@ -65,38 +51,33 @@ async function addRow() {
|
|||
});
|
||||
}
|
||||
|
||||
function confirmRemove(id) {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
data: { id },
|
||||
promise: remove,
|
||||
},
|
||||
})
|
||||
.onOk(async () => await arrayData.refresh());
|
||||
}
|
||||
|
||||
async function remove({ id }) {
|
||||
await axios.delete(`ClaimRmas/${id}`);
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
message: t('globals.rowRemoved'),
|
||||
});
|
||||
}
|
||||
onMounted(() => {
|
||||
if (claim.value) claimRmaRef.value.reload();
|
||||
});
|
||||
watch(
|
||||
claim,
|
||||
() => {
|
||||
claimRmaRef.value.reload();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
:url="`Claims/${route.params.id}`"
|
||||
:filter="claimFilter"
|
||||
@on-fetch="onFetch"
|
||||
auto-load
|
||||
/>
|
||||
<div class="column items-center">
|
||||
<div class="list">
|
||||
<VnPaginate data-key="ClaimRma" url="ClaimRmas">
|
||||
<CrudModel
|
||||
data-key="ClaimRma"
|
||||
url="ClaimRmas"
|
||||
model="ClaimRma"
|
||||
:filter="claimRmaFilter"
|
||||
v-model:selected="selected"
|
||||
ref="claimRmaRef"
|
||||
:default-save="false"
|
||||
:default-reset="false"
|
||||
:default-remove="false"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard class="card">
|
||||
<QCard>
|
||||
<template v-for="(row, index) of rows" :key="row.id">
|
||||
<QItem class="q-pa-none items-start">
|
||||
<QItemSection class="q-pa-md">
|
||||
|
@ -107,7 +88,7 @@ async function remove({ id }) {
|
|||
{{ t('claim.rma.user') }}
|
||||
</QItemLabel>
|
||||
<QItemLabel>
|
||||
{{ row.worker.user.name }}
|
||||
{{ row?.worker?.user?.name }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
@ -133,7 +114,7 @@ async function remove({ id }) {
|
|||
round
|
||||
color="orange"
|
||||
icon="vn:bin"
|
||||
@click="confirmRemove(row.id)"
|
||||
@click="claimRmaRef.remove([row])"
|
||||
>
|
||||
<QTooltip>{{ t('globals.remove') }}</QTooltip>
|
||||
</QBtn>
|
||||
|
@ -143,33 +124,11 @@ async function remove({ id }) {
|
|||
</template>
|
||||
</QCard>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</CrudModel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Teleport
|
||||
v-if="stateStore.isHeaderMounted() && !quasar.platform.is.mobile"
|
||||
to="#actions-prepend"
|
||||
>
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn @click="addRow()" icon="add" color="primary" dense rounded>
|
||||
<QTooltip bottom> {{ t('globals.add') }} </QTooltip>
|
||||
</QBtn>
|
||||
<QSeparator vertical />
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<QPageSticky
|
||||
v-if="quasar.platform.is.mobile"
|
||||
position="bottom"
|
||||
:offset="[0, 0]"
|
||||
expand
|
||||
>
|
||||
<QToolbar class="bg-primary text-white q-pa-none">
|
||||
<QTabs class="full-width" align="justify" inline-label narrow-indicator>
|
||||
<QTab @click="addRow()" icon="add_circle" :label="t('globals.add')" />
|
||||
</QTabs>
|
||||
</QToolbar>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||
<QBtn fab color="primary" icon="add" @click="addRow()" />
|
||||
</QPageSticky>
|
||||
</template>
|
||||
|
||||
|
@ -178,16 +137,6 @@ async function remove({ id }) {
|
|||
width: 100%;
|
||||
max-width: 60em;
|
||||
}
|
||||
.q-toolbar {
|
||||
background-color: $grey-9;
|
||||
}
|
||||
.sticky-page {
|
||||
padding-top: 66px;
|
||||
}
|
||||
|
||||
.q-page-sticky {
|
||||
z-index: 2998;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useRoute } from 'vue-router';
|
||||
import CustomerDescriptor from './CustomerDescriptor.vue';
|
||||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
<template>
|
||||
|
@ -25,8 +27,13 @@ const { t } = useI18n();
|
|||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage class="q-pa-md">
|
||||
<RouterView></RouterView>
|
||||
<QPage>
|
||||
<QToolbar class="bg-vn-dark justify-end">
|
||||
<div id="st-data"></div>
|
||||
<QSpace />
|
||||
<div id="st-actions"></div>
|
||||
</QToolbar>
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -25,8 +25,13 @@ const { t } = useI18n();
|
|||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage class="q-pa-md">
|
||||
<RouterView></RouterView>
|
||||
<QPage>
|
||||
<QToolbar class="bg-vn-dark justify-end">
|
||||
<div id="st-data"></div>
|
||||
<QSpace />
|
||||
<div id="st-actions"></div>
|
||||
</QToolbar>
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -25,8 +25,13 @@ const { t } = useI18n();
|
|||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage class="q-pa-md">
|
||||
<RouterView></RouterView>
|
||||
<QPage>
|
||||
<QToolbar class="bg-vn-dark justify-end">
|
||||
<div id="st-data"></div>
|
||||
<QSpace />
|
||||
<div id="st-actions"></div>
|
||||
</QToolbar>
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -20,7 +20,7 @@ const $props = defineProps({
|
|||
});
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
|
||||
let wagonTypes;
|
||||
let wagonTypes = [];
|
||||
let originalData = {};
|
||||
const wagon = ref({});
|
||||
const filteredWagonTypes = ref(wagonTypes);
|
||||
|
|
|
@ -25,8 +25,13 @@ const { t } = useI18n();
|
|||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage class="q-pa-md">
|
||||
<RouterView></RouterView>
|
||||
<QPage>
|
||||
<QToolbar class="bg-vn-dark justify-end">
|
||||
<div id="st-data"></div>
|
||||
<QSpace />
|
||||
<div id="st-actions"></div>
|
||||
</QToolbar>
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { ref, onMounted, computed, onUpdated } from 'vue';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
|
|
|
@ -18,6 +18,7 @@ export default {
|
|||
'ClaimPhotos',
|
||||
'ClaimLog',
|
||||
'ClaimNotes',
|
||||
'ClaimDevelopment',
|
||||
],
|
||||
},
|
||||
children: [
|
||||
|
@ -101,6 +102,16 @@ export default {
|
|||
},
|
||||
component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'),
|
||||
},
|
||||
{
|
||||
name: 'ClaimDevelopment',
|
||||
path: 'development',
|
||||
meta: {
|
||||
title: 'development',
|
||||
icon: 'vn:traceability',
|
||||
roles: ['claimManager'],
|
||||
},
|
||||
component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'),
|
||||
},
|
||||
{
|
||||
name: 'ClaimLog',
|
||||
path: 'log',
|
||||
|
|
|
@ -30,6 +30,13 @@ export const useStateStore = defineStore('stateStore', () => {
|
|||
return rightDrawer.value;
|
||||
}
|
||||
|
||||
function isSubToolbarShown() {
|
||||
return (
|
||||
!!document.querySelector('#st-data') &&
|
||||
!!document.querySelector('#st-actions')
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
leftDrawer,
|
||||
rightDrawer,
|
||||
|
@ -39,5 +46,6 @@ export const useStateStore = defineStore('stateStore', () => {
|
|||
toggleRightDrawer,
|
||||
isLeftDrawerShown,
|
||||
isRightDrawerShown,
|
||||
isSubToolbarShown,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/// <reference types="cypress" />
|
||||
describe('ClaimDevelopment', () => {
|
||||
const claimId = 1;
|
||||
const firstLineReason = 'tbody > :nth-child(1) > :nth-child(2)';
|
||||
const thirdRow = 'tbody > :nth-child(3)';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.login('developer');
|
||||
cy.visit(`/#/claim/${claimId}/development`);
|
||||
});
|
||||
|
||||
it('should reset line', () => {
|
||||
cy.selectOption(firstLineReason, 'Novato');
|
||||
cy.resetCard();
|
||||
cy.getValue(firstLineReason).should('have.text', 'Prisas');
|
||||
});
|
||||
|
||||
it('should edit line', () => {
|
||||
cy.selectOption(firstLineReason, 'Novato');
|
||||
cy.saveCard();
|
||||
|
||||
cy.reload();
|
||||
cy.getValue(firstLineReason).should('have.text', 'Novato');
|
||||
|
||||
//Restart data
|
||||
cy.selectOption(firstLineReason, 'Prisas');
|
||||
cy.saveCard();
|
||||
});
|
||||
|
||||
it('should add and remove new line', () => {
|
||||
//add row
|
||||
cy.addCard();
|
||||
cy.get(thirdRow).should('exist');
|
||||
|
||||
const rowData = [false, 'Novato', 'Roces', 'Compradores', 'employeeNick', 'Tour'];
|
||||
cy.fillRow(thirdRow, rowData);
|
||||
cy.saveCard();
|
||||
cy.validateRow(thirdRow, rowData);
|
||||
|
||||
cy.reload();
|
||||
cy.validateRow(thirdRow, rowData);
|
||||
|
||||
//remove row
|
||||
cy.fillRow(thirdRow, [true]);
|
||||
cy.removeCard();
|
||||
cy.clickConfirm();
|
||||
cy.get(thirdRow).should('not.exist');
|
||||
|
||||
cy.reload();
|
||||
cy.get(thirdRow).should('not.exist');
|
||||
});
|
||||
});
|
|
@ -40,4 +40,91 @@ Cypress.Commands.add('login', (user) => {
|
|||
window.localStorage.setItem('token', response.body.token);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('waitForElement', (element) => {
|
||||
alexm
commented
He fet tots estos comandos per a facilitar el fer els e2e i te funcions com getValue que ja te mira que tipo de componente es i teu trau. Soles els he adaptat per als QSelects i QCheckbox que era el cas que necesitava si me para adaptar mes componentes ja se me fea mes jaleo pq no els gaste realment en un e2e He fet tots estos comandos per a facilitar el fer els e2e i te funcions com getValue que ja te mira que tipo de componente es i teu trau.
Soles els he adaptat per als QSelects i QCheckbox que era el cas que necesitava si me para adaptar mes componentes ja se me fea mes jaleo pq no els gaste realment en un e2e
|
||||
cy.get(element, { timeout: 2000 }).should('be.visible');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getValue', (selector) => {
|
||||
cy.get(selector).then(($el) => {
|
||||
if ($el.find('.q-checkbox__inner').length > 0) {
|
||||
return cy.get(selector + '.q-checkbox__inner');
|
||||
}
|
||||
// Si es un QSelect
|
||||
else if ($el.find('.q-select__dropdown-icon').length) {
|
||||
return cy.get(
|
||||
selector +
|
||||
'> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > span'
|
||||
);
|
||||
} else {
|
||||
// Puedes añadir un log o lanzar un error si el elemento no es reconocido
|
||||
cy.log('Elemento no soportado');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Fill Inputs
|
||||
Cypress.Commands.add('selectOption', (selector, option) => {
|
||||
cy.get(selector).find('.q-select__dropdown-icon').click();
|
||||
cy.get('.q-menu .q-item').contains(option).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('checkOption', (selector) => {
|
||||
cy.wrap(selector).find('.q-checkbox__inner').click();
|
||||
});
|
||||
|
||||
// Global buttons
|
||||
Cypress.Commands.add('saveCard', () => {
|
||||
cy.get('[title="Save"]').click();
|
||||
cy.get('[title="Save"]').should('have.class', 'disabled');
|
||||
});
|
||||
Cypress.Commands.add('resetCard', () => {
|
||||
cy.get('[title="Reset"]').click();
|
||||
});
|
||||
Cypress.Commands.add('removeCard', () => {
|
||||
cy.get('[title="Remove"]').click();
|
||||
});
|
||||
Cypress.Commands.add('addCard', () => {
|
||||
cy.waitForElement('tbody');
|
||||
cy.get('.q-page-sticky > div > .q-btn').click();
|
||||
});
|
||||
Cypress.Commands.add('clickConfirm', () => {
|
||||
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fillRow', (rowSelector, data) => {
|
||||
// Usar el selector proporcionado para obtener la fila deseada
|
||||
cy.waitForElement('tbody');
|
||||
cy.get(rowSelector).as('currentRow');
|
||||
|
||||
data.forEach((value, index) => {
|
||||
if (value === null) return;
|
||||
cy.get('@currentRow')
|
||||
.find('td')
|
||||
.eq(index)
|
||||
.then((td) => {
|
||||
if (td.find('.q-select__dropdown-icon').length) {
|
||||
cy.selectOption(td, value);
|
||||
}
|
||||
if (td.find('.q-checkbox__inner').length && value) {
|
||||
cy.checkOption(td);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
|
||||
cy.waitForElement('tbody');
|
||||
cy.get(rowSelector).within(() => {
|
||||
for (const [index, value] of expectedValues.entries()) {
|
||||
cy.log('CHECKING ', index, value);
|
||||
if (typeof value == 'boolean') {
|
||||
const prefix = value ? '' : 'not.';
|
||||
cy.getValue(`:nth-child(${index + 1})`).should(`${prefix}be.checked`);
|
||||
continue;
|
||||
}
|
||||
cy.getValue(`:nth-child(${index + 1})`).should('have.text', value);
|
||||
}
|
||||
});
|
||||
});
|
||||
// registerCommands();
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
import { createWrapper } from 'app/test/vitest/helper';
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
describe('CrudModel', () => {
|
||||
let vm;
|
||||
beforeAll(() => {
|
||||
vm = createWrapper(CrudModel, {
|
||||
global: {
|
||||
stubs: [
|
||||
'vnPaginate',
|
||||
'useState',
|
||||
'arrayData',
|
||||
'useStateStore',
|
||||
'vue-i18n',
|
||||
],
|
||||
mocks: {
|
||||
validate: vi.fn(),
|
||||
},
|
||||
},
|
||||
propsData: {
|
||||
dataRequired: {
|
||||
fk: 1,
|
||||
},
|
||||
dataKey: 'crudModelKey',
|
||||
model: 'crudModel',
|
||||
url: 'crudModelUrl',
|
||||
},
|
||||
}).vm;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm.fetch([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('insert()', () => {
|
||||
it('should new element in list with index 0 if formData not has data', () => {
|
||||
vm.insert();
|
||||
|
||||
expect(vm.formData.length).toEqual(1);
|
||||
expect(vm.formData[0].fk).toEqual(1);
|
||||
expect(vm.formData[0].$index).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChanges()', () => {
|
||||
it('should return correct updates and creates', async () => {
|
||||
vm.fetch([
|
||||
{ id: 1, name: 'New name one' },
|
||||
{ id: 2, name: 'New name two' },
|
||||
{ id: 3, name: 'Bruce Wayne' },
|
||||
]);
|
||||
|
||||
vm.originalData = [
|
||||
{ id: 1, name: 'Tony Starks' },
|
||||
{ id: 2, name: 'Jessica Jones' },
|
||||
{ id: 3, name: 'Bruce Wayne' },
|
||||
];
|
||||
|
||||
vm.insert();
|
||||
const result = vm.getChanges();
|
||||
|
||||
const expected = {
|
||||
creates: [
|
||||
{
|
||||
$index: 3,
|
||||
fk: 1,
|
||||
},
|
||||
],
|
||||
updates: [
|
||||
{
|
||||
data: {
|
||||
name: 'New name one',
|
||||
},
|
||||
where: {
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
name: 'New name two',
|
||||
},
|
||||
where: {
|
||||
id: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDifferences()', () => {
|
||||
alexm
commented
Soles me ha faltat este test. Pq com es el dialogo al donarli a Acceptar qui borra. Hi ha que mockejar el componente de dialogo i tal i no me ha donat temps. Soles me ha faltat este test. Pq com es el dialogo al donarli a Acceptar qui borra. Hi ha que mockejar el componente de dialogo i tal i no me ha donat temps.
|
||||
it('should return the differences between two objects', async () => {
|
||||
const obj1 = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
};
|
||||
const obj2 = {
|
||||
a: null,
|
||||
b: 4,
|
||||
d: 5,
|
||||
};
|
||||
|
||||
const result = vm.getDifferences(obj1, obj2);
|
||||
|
||||
expect(result).toEqual({
|
||||
a: null,
|
||||
b: 4,
|
||||
d: 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,6 @@ import ClaimLines from 'pages/Claim/Card/ClaimLines.vue';
|
|||
describe('ClaimLines', () => {
|
||||
let vm;
|
||||
|
||||
|
||||
beforeAll(() => {
|
||||
vm = createWrapper(ClaimLines, {
|
||||
global: {
|
||||
|
@ -13,25 +12,26 @@ describe('ClaimLines', () => {
|
|||
mocks: {
|
||||
fetch: vi.fn(),
|
||||
},
|
||||
}
|
||||
},
|
||||
}).vm;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm.claim = {
|
||||
id: 1,
|
||||
ticketFk: 1
|
||||
}
|
||||
ticketFk: 1,
|
||||
};
|
||||
vm.store.data = [
|
||||
{
|
||||
id: 1,
|
||||
quantity: 10,
|
||||
sale: {
|
||||
id: 1, discount: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
id: 1,
|
||||
discount: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
@ -42,13 +42,17 @@ describe('ClaimLines', () => {
|
|||
vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
|
||||
vi.spyOn(vm.quasar, 'notify');
|
||||
|
||||
const canceller = new AbortController()
|
||||
const canceller = new AbortController();
|
||||
await vm.updateDiscount({ saleFk: 1, discount: 5, canceller });
|
||||
|
||||
const expectedData = { salesIds: [1], newDiscount: 5 }
|
||||
expect(axios.post).toHaveBeenCalledWith('Tickets/1/updateDiscount', expectedData, {
|
||||
signal: canceller.signal
|
||||
})
|
||||
const expectedData = { salesIds: [1], newDiscount: 5 };
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'Tickets/1/updateDiscount',
|
||||
expectedData,
|
||||
{
|
||||
signal: canceller.signal,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -56,37 +60,14 @@ describe('ClaimLines', () => {
|
|||
it('should make a POST request and then set the discount on the original row', async () => {
|
||||
vi.spyOn(vm.quasar, 'notify');
|
||||
|
||||
|
||||
vm.onUpdateDiscount({ discount: 5, rowIndex: 0 });
|
||||
const firstRow = vm.store.data[0]
|
||||
const firstRow = vm.store.data[0];
|
||||
|
||||
expect(firstRow.sale.discount).toEqual(5)
|
||||
expect(firstRow.sale.discount).toEqual(5);
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Discount updated',
|
||||
type: 'positive'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove()', () => {
|
||||
it('should make a POST request and then call to the quasar notify() method', async () => {
|
||||
vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
|
||||
vi.spyOn(vm.quasar, 'notify');
|
||||
|
||||
await vm.remove({
|
||||
rows: [
|
||||
{ id: 1 }
|
||||
]
|
||||
});
|
||||
const expectedData = { deletes: [1] }
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith('ClaimBeginnings/crud', expectedData)
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Row removed',
|
||||
type: 'positive'
|
||||
type: 'positive',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,9 +7,11 @@ describe('WagonCreate', () => {
|
|||
const entityId = 1;
|
||||
|
||||
beforeAll(() => {
|
||||
vmEdit = createWrapper(WagonCreate, {propsData: {
|
||||
vmEdit = createWrapper(WagonCreate, {
|
||||
propsData: {
|
||||
id: entityId,
|
||||
}}).vm;
|
||||
},
|
||||
}).vm;
|
||||
vmCreate = createWrapper(WagonCreate).vm;
|
||||
});
|
||||
|
||||
|
@ -29,9 +31,7 @@ describe('WagonCreate', () => {
|
|||
|
||||
await vmCreate.onSubmit();
|
||||
|
||||
expect(axios.patch).toHaveBeenCalledWith(
|
||||
`Wagons`, vmCreate.wagon
|
||||
);
|
||||
expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmCreate.wagon);
|
||||
});
|
||||
|
||||
it('should update a wagon', async () => {
|
||||
|
@ -46,9 +46,7 @@ describe('WagonCreate', () => {
|
|||
|
||||
await vmEdit.onSubmit();
|
||||
|
||||
expect(axios.patch).toHaveBeenCalledWith(
|
||||
`Wagons`, vmEdit.wagon
|
||||
);
|
||||
expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmEdit.wagon);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -88,16 +86,12 @@ describe('WagonCreate', () => {
|
|||
|
||||
describe('fetch()', () => {
|
||||
it('should fetch data', async () => {
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({ data: true });
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
|
||||
|
||||
await vmEdit.fetch();
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`WagonTypes`
|
||||
);
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`Wagons/${entityId}`
|
||||
);
|
||||
expect(axios.get).toHaveBeenCalledWith(`WagonTypes`);
|
||||
expect(axios.get).toHaveBeenCalledWith(`Wagons/${entityId}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import { vi } from 'vitest';
|
|||
import { i18n } from 'src/boot/i18n';
|
||||
import { Notify, Dialog } from 'quasar';
|
||||
import axios from 'axios';
|
||||
import * as useValidator from 'src/composables/useValidator';
|
||||
|
||||
installQuasarPlugin({
|
||||
plugins: {
|
||||
|
@ -34,6 +35,10 @@ vi.mock('vue-router', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
vi.spyOn(useValidator, 'useValidator').mockImplementation(() => {
|
||||
return { validate: vi.fn(), fetch: vi.fn() };
|
||||
});
|
||||
|
||||
class FormDataMock {
|
||||
append() {
|
||||
vi.fn();
|
||||
|
@ -64,6 +69,10 @@ export function createWrapper(component, options) {
|
|||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
},
|
||||
mocks: {
|
||||
t: (tKey) => tKey,
|
||||
$t: (tKey) => tKey,
|
||||
},
|
||||
};
|
||||
|
||||
const mountOptions = Object.assign({}, defaultOptions);
|
||||
|
|
Loading…
Reference in New Issue
Esto lo dejo asi por si en un futuro se quiere hacer que puedas borrar una linea en especifico (como esta en salix que al final de la linea esta el boton de borrar) Pero teniendo los checkbox de momento no lo veo necesario