forked from verdnatura/salix-front
Merge branch 'dev' into 6303-smsDialogFix
This commit is contained in:
commit
23747f5029
|
@ -64,7 +64,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['test/cypress/**/*.spec.{js,ts}'],
|
files: ['test/cypress/**/*.*'],
|
||||||
extends: [
|
extends: [
|
||||||
// Add Cypress-specific lint rules, globals and Cypress plugin
|
// Add Cypress-specific lint rules, globals and Cypress plugin
|
||||||
// See https://github.com/cypress-io/eslint-plugin-cypress#rules
|
// See https://github.com/cypress-io/eslint-plugin-cypress#rules
|
||||||
|
|
|
@ -7,7 +7,7 @@ module.exports = defineConfig({
|
||||||
screenshotsFolder: 'test/cypress/screenshots',
|
screenshotsFolder: 'test/cypress/screenshots',
|
||||||
supportFile: 'test/cypress/support/index.js',
|
supportFile: 'test/cypress/support/index.js',
|
||||||
videosFolder: 'test/cypress/videos',
|
videosFolder: 'test/cypress/videos',
|
||||||
video: true,
|
video: false,
|
||||||
specPattern: 'test/cypress/integration/*.spec.js',
|
specPattern: 'test/cypress/integration/*.spec.js',
|
||||||
experimentalRunAllSpecs: true,
|
experimentalRunAllSpecs: true,
|
||||||
component: {
|
component: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "salix-front",
|
"name": "salix-front",
|
||||||
"version": "23.36.01",
|
"version": "23.40.01",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "salix-front",
|
"name": "salix-front",
|
||||||
"version": "23.36.01",
|
"version": "23.40.01",
|
||||||
"description": "Salix frontend",
|
"description": "Salix frontend",
|
||||||
"productName": "Salix",
|
"productName": "Salix",
|
||||||
"author": "Verdnatura",
|
"author": "Verdnatura",
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
"lint": "eslint --ext .js,.vue ./",
|
"lint": "eslint --ext .js,.vue ./",
|
||||||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||||
"test:e2e": "cypress open",
|
"test:e2e": "cypress open",
|
||||||
"test:e2e:ci": "cypress run --browser chromium",
|
"test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run --browser chromium",
|
||||||
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
|
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest",
|
||||||
"test:unit:ci": "vitest run"
|
"test:unit:ci": "vitest run"
|
||||||
|
|
|
@ -46,7 +46,7 @@ const onResponseError = (error) => {
|
||||||
message = responseError.message;
|
message = responseError.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (response.status) {
|
switch (response?.status) {
|
||||||
case 500:
|
case 500:
|
||||||
message = 'errors.statusInternalServerError';
|
message = 'errors.statusInternalServerError';
|
||||||
break;
|
break;
|
||||||
|
@ -58,7 +58,7 @@ const onResponseError = (error) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.isLoggedIn() && response.status === 401) {
|
if (session.isLoggedIn() && response?.status === 401) {
|
||||||
session.destroy();
|
session.destroy();
|
||||||
const hash = window.location.hash;
|
const hash = window.location.hash;
|
||||||
const url = hash.slice(1);
|
const url = hash.slice(1);
|
||||||
|
|
|
@ -0,0 +1,324 @@
|
||||||
|
<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';
|
||||||
|
import { tMobile } from 'src/composables/tMobile';
|
||||||
|
|
||||||
|
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 saveButtonRef = ref(null);
|
||||||
|
const formUrl = computed(() => $props.url);
|
||||||
|
|
||||||
|
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
reload,
|
||||||
|
insert,
|
||||||
|
remove,
|
||||||
|
onSubmit,
|
||||||
|
reset,
|
||||||
|
hasChanges,
|
||||||
|
saveChanges,
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
emit('saveChanges', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'),
|
||||||
|
});
|
||||||
|
|
||||||
|
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="moreBeforeActions" />
|
||||||
|
<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')"
|
||||||
|
ref="saveButtonRef"
|
||||||
|
color="primary"
|
||||||
|
icon="save"
|
||||||
|
@click="onSubmit"
|
||||||
|
:disable="!hasChanges"
|
||||||
|
:title="t('globals.save')"
|
||||||
|
v-if="$props.defaultSave"
|
||||||
|
/>
|
||||||
|
<slot name="moreAfterActions" />
|
||||||
|
</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 { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { useValidator } from 'src/composables/useValidator';
|
import { useValidator } from 'src/composables/useValidator';
|
||||||
import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const { t } = useI18n();
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const { t } = useI18n();
|
||||||
const { validate } = useValidator();
|
const { validate } = useValidator();
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
|
@ -29,6 +31,10 @@ const $props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
defaultActions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch']);
|
const emit = defineEmits(['onFetch']);
|
||||||
|
@ -45,17 +51,21 @@ onUnmounted(() => {
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const hasChanges = ref(false);
|
const hasChanges = ref(false);
|
||||||
const formData = computed(() => state.get($props.model));
|
|
||||||
const originalData = ref();
|
const originalData = ref();
|
||||||
|
const formData = computed(() => state.get($props.model));
|
||||||
const formUrl = computed(() => $props.url);
|
const formUrl = computed(() => $props.url);
|
||||||
|
|
||||||
|
function tMobile(...args) {
|
||||||
|
if (!quasar.platform.is.mobile) return t(...args);
|
||||||
|
}
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const { data } = await axios.get($props.url, {
|
const { data } = await axios.get($props.url, {
|
||||||
params: { filter: $props.filter },
|
params: { filter: $props.filter },
|
||||||
});
|
});
|
||||||
|
|
||||||
state.set($props.model, data);
|
state.set($props.model, data);
|
||||||
originalData.value = Object.assign({}, data);
|
originalData.value = data && JSON.parse(JSON.stringify(data));
|
||||||
|
|
||||||
watch(formData.value, () => (hasChanges.value = true));
|
watch(formData.value, () => (hasChanges.value = true));
|
||||||
|
|
||||||
|
@ -72,13 +82,18 @@ async function save() {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
await axios.patch($props.urlUpdate || $props.url, formData.value);
|
await axios.patch($props.urlUpdate || $props.url, formData.value);
|
||||||
|
|
||||||
originalData.value = formData.value;
|
originalData.value = JSON.parse(JSON.stringify(formData.value));
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
state.set($props.model, originalData.value);
|
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;
|
hasChanges.value = false;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line vue/no-dupe-keys
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
|
@ -109,20 +124,31 @@ watch(formUrl, async () => {
|
||||||
</QBanner>
|
</QBanner>
|
||||||
<QForm v-if="formData" @submit="save" @reset="reset" class="q-pa-md">
|
<QForm v-if="formData" @submit="save" @reset="reset" class="q-pa-md">
|
||||||
<slot name="form" :data="formData" :validate="validate" :filter="filter"></slot>
|
<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>
|
</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"
|
||||||
|
:disable="!hasChanges"
|
||||||
|
:title="t('globals.save')"
|
||||||
|
/>
|
||||||
|
</QBtnGroup>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
<SkeletonForm v-if="!formData" />
|
<SkeletonForm v-if="!formData" />
|
||||||
<QInnerLoading
|
<QInnerLoading
|
||||||
:showing="isLoading"
|
:showing="isLoading"
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
<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([]);
|
||||||
|
const vnSelectRef = ref(null);
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
(ref) => {
|
||||||
|
if (val !== '' && ref.options.length > 0) {
|
||||||
|
ref.setOptionIndex(-1);
|
||||||
|
ref.moveOptionSelection(1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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"
|
||||||
|
hide-selected
|
||||||
|
fill-input
|
||||||
|
ref="vnSelectRef"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="close" @click.stop="value = null" class="cursor-pointer" />
|
||||||
|
</template>
|
||||||
|
<template v-for="(_, slotName) in $slots" #[slotName]="slotData">
|
||||||
|
<slot :name="slotName" v-bind="slotData" />
|
||||||
|
</template>
|
||||||
|
</QSelect>
|
||||||
|
</template>
|
|
@ -29,12 +29,14 @@ const $props = defineProps({
|
||||||
|
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const entity = ref();
|
||||||
|
|
||||||
onMounted(() => fetch());
|
onMounted(async () => {
|
||||||
|
await fetch();
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch']);
|
const emit = defineEmits(['onFetch']);
|
||||||
|
|
||||||
const entity = ref();
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const params = {};
|
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,
|
type: Number,
|
||||||
default: 500,
|
default: 500,
|
||||||
},
|
},
|
||||||
|
skeleton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'onPaginate']);
|
const emit = defineEmits(['onFetch', 'onPaginate']);
|
||||||
|
@ -137,14 +141,9 @@ async function onLoad(...params) {
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="store.data && store.data.length === 0 && !isLoading"
|
v-if="props.skeleton && props.autoLoad && !store.data"
|
||||||
class="info-row q-pa-md text-center"
|
class="card-list q-gutter-y-md"
|
||||||
>
|
>
|
||||||
<h5>
|
|
||||||
{{ t('No results found') }}
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div v-if="props.autoLoad && !store.data" class="card-list q-gutter-y-md">
|
|
||||||
<QCard class="card" v-for="$index in $props.limit" :key="$index">
|
<QCard class="card" v-for="$index in $props.limit" :key="$index">
|
||||||
<QItem v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
|
<QItem v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
|
||||||
<QItemSection class="q-pa-md">
|
<QItemSection class="q-pa-md">
|
||||||
|
@ -164,7 +163,7 @@ async function onLoad(...params) {
|
||||||
</QCard>
|
</QCard>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<slot name="body" :rows="store.data"></slot>
|
||||||
<div v-if="isLoading" class="info-row q-pa-md text-center">
|
<div v-if="isLoading" class="info-row q-pa-md text-center">
|
||||||
<QSpinner color="orange" size="md" />
|
<QSpinner color="orange" size="md" />
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
export function tMobile(...args) {
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const { t } = useI18n();
|
||||||
|
if (!quasar.platform.is.mobile) return t(...args);
|
||||||
|
}
|
|
@ -38,11 +38,11 @@ export function useArrayData(key, userOptions) {
|
||||||
'limit',
|
'limit',
|
||||||
'skip',
|
'skip',
|
||||||
'userParams',
|
'userParams',
|
||||||
'userFilter'
|
'userFilter',
|
||||||
];
|
];
|
||||||
if (typeof userOptions === 'object') {
|
if (typeof userOptions === 'object') {
|
||||||
for (const option in userOptions) {
|
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 (isEmpty || !allowedOptions.includes(option)) continue;
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
||||||
|
@ -73,7 +73,7 @@ export function useArrayData(key, userOptions) {
|
||||||
|
|
||||||
Object.assign(params, store.userParams);
|
Object.assign(params, store.userParams);
|
||||||
|
|
||||||
store.isLoading = true
|
store.isLoading = true;
|
||||||
const response = await axios.get(store.url, {
|
const response = await axios.get(store.url, {
|
||||||
signal: canceller.signal,
|
signal: canceller.signal,
|
||||||
params,
|
params,
|
||||||
|
@ -94,7 +94,7 @@ export function useArrayData(key, userOptions) {
|
||||||
updateStateParams();
|
updateStateParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
store.isLoading = false
|
store.isLoading = false;
|
||||||
|
|
||||||
canceller = null;
|
canceller = null;
|
||||||
}
|
}
|
||||||
|
@ -153,8 +153,8 @@ export function useArrayData(key, userOptions) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalRows = computed(() => store.data && store.data.length || 0);
|
const totalRows = computed(() => (store.data && store.data.length) || 0);
|
||||||
const isLoading = computed(() => store.isLoading || false)
|
const isLoading = computed(() => store.isLoading || false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetch,
|
fetch,
|
||||||
|
@ -167,6 +167,6 @@ export function useArrayData(key, userOptions) {
|
||||||
hasMoreData,
|
hasMoreData,
|
||||||
totalRows,
|
totalRows,
|
||||||
updateStateParams,
|
updateStateParams,
|
||||||
isLoading
|
isLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,13 @@ import { useI18n } from 'vue-i18n';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
|
||||||
|
|
||||||
const models = ref(null);
|
const models = ref(null);
|
||||||
|
|
||||||
export function useValidator() {
|
export function useValidator() {
|
||||||
if (!models.value) fetch();
|
if (!models.value) fetch();
|
||||||
|
|
||||||
function fetch() {
|
function fetch() {
|
||||||
axios.get('Schemas/ModelInfo')
|
axios.get('Schemas/ModelInfo').then((response) => (models.value = response.data));
|
||||||
.then(response => models.value = response.data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate(propertyRule) {
|
function validate(propertyRule) {
|
||||||
|
@ -38,19 +36,18 @@ export function useValidator() {
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const validations = function (validation) {
|
const validations = function (validation) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
presence: (value) => {
|
presence: (value) => {
|
||||||
let message = `Value can't be empty`;
|
let message = `Value can't be empty`;
|
||||||
if (validation.message)
|
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) => {
|
length: (value) => {
|
||||||
const options = {
|
const options = {
|
||||||
min: validation.min || validation.is,
|
min: validation.min || validation.is,
|
||||||
max: validation.max || validation.is
|
max: validation.max || validation.is,
|
||||||
};
|
};
|
||||||
|
|
||||||
value = String(value);
|
value = String(value);
|
||||||
|
@ -69,14 +66,14 @@ export function useValidator() {
|
||||||
},
|
},
|
||||||
numericality: (value) => {
|
numericality: (value) => {
|
||||||
if (validation.int)
|
if (validation.int)
|
||||||
return validator.isInt(value) || 'Value should be integer'
|
return validator.isInt(value) || 'Value should be integer';
|
||||||
return validator.isNumeric(value) || 'Value should be a number'
|
return validator.isNumeric(value) || 'Value should be a number';
|
||||||
},
|
},
|
||||||
custom: (value) => validation.bindedFunction(value) || 'Invalid value'
|
custom: (value) => validation.bindedFunction(value) || 'Invalid value',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
validate
|
validate,
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -32,10 +32,16 @@ body.body--light {
|
||||||
--vn-text: #000000;
|
--vn-text: #000000;
|
||||||
--vn-gray: #f5f5f5;
|
--vn-gray: #f5f5f5;
|
||||||
--vn-label: #5f5f5f;
|
--vn-label: #5f5f5f;
|
||||||
|
--vn-dark: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.body--dark {
|
body.body--dark {
|
||||||
--vn-text: #ffffff;
|
--vn-text: #ffffff;
|
||||||
--vn-gray: #313131;
|
--vn-gray: #313131;
|
||||||
--vn-label: #a8a8a8;
|
--vn-label: #a8a8a8;
|
||||||
|
--vn-dark: #292929;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-vn-dark {
|
||||||
|
background-color: var(--vn-dark);
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,6 +266,7 @@ export default {
|
||||||
lines: 'Lines',
|
lines: 'Lines',
|
||||||
rma: 'RMA',
|
rma: 'RMA',
|
||||||
photos: 'Photos',
|
photos: 'Photos',
|
||||||
|
development: 'Development',
|
||||||
log: 'Audit logs',
|
log: 'Audit logs',
|
||||||
notes: 'Notes',
|
notes: 'Notes',
|
||||||
},
|
},
|
||||||
|
|
|
@ -264,6 +264,7 @@ export default {
|
||||||
basicData: 'Datos básicos',
|
basicData: 'Datos básicos',
|
||||||
lines: 'Líneas',
|
lines: 'Líneas',
|
||||||
rma: 'RMA',
|
rma: 'RMA',
|
||||||
|
development: 'Trazabilidad',
|
||||||
photos: 'Fotos',
|
photos: 'Fotos',
|
||||||
log: 'Registros de auditoría',
|
log: 'Registros de auditoría',
|
||||||
notes: 'Notas',
|
notes: 'Notas',
|
||||||
|
|
|
@ -44,17 +44,6 @@ onMounted(async () => {
|
||||||
<LeftMenu source="card" />
|
<LeftMenu source="card" />
|
||||||
<QSeparator />
|
<QSeparator />
|
||||||
<QList>
|
<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
|
<QItem
|
||||||
active-class="text-primary"
|
active-class="text-primary"
|
||||||
clickable
|
clickable
|
||||||
|
@ -68,8 +57,13 @@ onMounted(async () => {
|
||||||
</QScrollArea>
|
</QScrollArea>
|
||||||
</QDrawer>
|
</QDrawer>
|
||||||
<QPageContainer>
|
<QPageContainer>
|
||||||
<QPage class="q-pa-md">
|
<QPage>
|
||||||
<RouterView></RouterView>
|
<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>
|
</QPage>
|
||||||
</QPageContainer>
|
</QPageContainer>
|
||||||
</template>
|
</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
|
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
|
||||||
Details: Detalles
|
Details: Detalles
|
||||||
Notes: Notas
|
Notes: Notas
|
||||||
Development: Trazabilidad
|
|
||||||
Action: Acción
|
Action: Acción
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { ref, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
|
||||||
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||||
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
|
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
|
||||||
|
@ -19,6 +21,7 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const state = useState();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const entityId = computed(() => {
|
const entityId = computed(() => {
|
||||||
|
@ -67,6 +70,7 @@ function stateColor(code) {
|
||||||
const data = ref(useCardDescription());
|
const data = ref(useCardDescription());
|
||||||
const setData = (entity) => {
|
const setData = (entity) => {
|
||||||
data.value = useCardDescription(entity.client.name, entity.id);
|
data.value = useCardDescription(entity.client.name, entity.id);
|
||||||
|
state.set('ClaimDescriptor', entity);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } 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';
|
||||||
|
import { getUrl } from 'composables/getUrl';
|
||||||
|
import { tMobile } from 'composables/tMobile';
|
||||||
|
|
||||||
|
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 insertButtonRef = ref();
|
||||||
|
let salixUrl;
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
salixUrl = await getUrl(`claim/${route.params.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
|
tabIndex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'claimResult',
|
||||||
|
label: t('Result'),
|
||||||
|
field: (row) => row.claimResultFk,
|
||||||
|
sortable: true,
|
||||||
|
options: claimResults.value,
|
||||||
|
required: true,
|
||||||
|
model: 'claimResultFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'description',
|
||||||
|
tabIndex: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'claimResponsible',
|
||||||
|
label: t('Responsible'),
|
||||||
|
field: (row) => row.claimResponsibleFk,
|
||||||
|
sortable: true,
|
||||||
|
options: claimResponsibles.value,
|
||||||
|
required: true,
|
||||||
|
model: 'claimResponsibleFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'description',
|
||||||
|
tabIndex: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'worker',
|
||||||
|
label: t('Worker'),
|
||||||
|
field: (row) => row.workerFk,
|
||||||
|
sortable: true,
|
||||||
|
options: workers.value,
|
||||||
|
model: 'workerFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'nickname',
|
||||||
|
tabIndex: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'claimRedelivery',
|
||||||
|
label: t('Redelivery'),
|
||||||
|
field: (row) => row.claimRedeliveryFk,
|
||||||
|
sortable: true,
|
||||||
|
options: claimRedeliveries.value,
|
||||||
|
required: true,
|
||||||
|
model: 'claimRedeliveryFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'description',
|
||||||
|
tabIndex: 5,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function goToAction() {
|
||||||
|
location.href = `${salixUrl}/action`;
|
||||||
|
}
|
||||||
|
</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/search"
|
||||||
|
:where="{ active: 1 }"
|
||||||
|
order="name ASC"
|
||||||
|
@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
|
||||||
|
@save-changes="goToAction"
|
||||||
|
:default-save="false"
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
@keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
|
||||||
|
>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="col.label"
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:options="col.options"
|
||||||
|
:option-value="col.optionValue"
|
||||||
|
:option-label="col.optionLabel"
|
||||||
|
:autofocus="col.tabIndex == 1"
|
||||||
|
input-debounce="0"
|
||||||
|
>
|
||||||
|
<template #option="scope" v-if="col.name == 'worker'">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
|
||||||
|
<QItemLabel caption>
|
||||||
|
{{ scope.opt?.nickname }}
|
||||||
|
{{ scope.opt?.code }}
|
||||||
|
</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #item="props">
|
||||||
|
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
||||||
|
<QCard
|
||||||
|
bordered
|
||||||
|
flat
|
||||||
|
@keyup.ctrl.enter.stop="claimDevelopmentForm?.saveChanges()"
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
input-debounce="0"
|
||||||
|
:autofocus="col.tabIndex == 1"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</QList>
|
||||||
|
</QCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</template>
|
||||||
|
<template #moreAfterActions>
|
||||||
|
<QBtn
|
||||||
|
:label="tMobile('globals.save')"
|
||||||
|
ref="saveButtonRef"
|
||||||
|
color="primary"
|
||||||
|
icon="save"
|
||||||
|
:disable="!claimDevelopmentForm?.hasChanges"
|
||||||
|
@click="claimDevelopmentForm?.onSubmit"
|
||||||
|
:title="t('globals.save')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</CrudModel>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
|
<QBtn
|
||||||
|
ref="insertButtonRef"
|
||||||
|
fab
|
||||||
|
color="primary"
|
||||||
|
icon="add"
|
||||||
|
@click="claimDevelopmentForm.insert()"
|
||||||
|
@keydown.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
|
||||||
|
@keydown.enter.stop
|
||||||
|
/>
|
||||||
|
</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 { useRoute } from 'vue-router';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
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 FetchData from 'components/FetchData.vue';
|
||||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
|
||||||
|
|
||||||
import { toDate, toCurrency, toPercentage } from 'filters/index';
|
import { toDate, toCurrency, toPercentage } from 'filters/index';
|
||||||
import VnDiscount from 'components/common/vnDiscount.vue';
|
import VnDiscount from 'components/common/vnDiscount.vue';
|
||||||
|
@ -17,6 +16,7 @@ import ClaimLinesImport from './ClaimLinesImport.vue';
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const arrayData = useArrayData('ClaimLines');
|
const arrayData = useArrayData('ClaimLines');
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
|
@ -36,16 +36,17 @@ const linesFilter = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const claimLinesForm = ref();
|
||||||
const claim = ref(null);
|
const claim = ref(null);
|
||||||
async function onFetchClaim(data) {
|
async function onFetchClaim(data) {
|
||||||
claim.value = data;
|
claim.value = data;
|
||||||
|
|
||||||
fetchMana();
|
fetchMana();
|
||||||
}
|
}
|
||||||
|
|
||||||
const amount = ref(0);
|
const amount = ref(0);
|
||||||
const amountClaimed = ref(0);
|
const amountClaimed = ref(0);
|
||||||
async function onFetch(rows) {
|
async function onFetch(rows) {
|
||||||
|
if (!rows || rows.length) return;
|
||||||
amount.value = rows.reduce(
|
amount.value = rows.reduce(
|
||||||
(acumulator, { sale }) => acumulator + sale.price * sale.quantity,
|
(acumulator, { sale }) => acumulator + sale.price * sale.quantity,
|
||||||
0
|
0
|
||||||
|
@ -141,60 +142,20 @@ 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() {
|
function showImportDialog() {
|
||||||
quasar
|
quasar
|
||||||
.dialog({
|
.dialog({
|
||||||
component: ClaimLinesImport,
|
component: ClaimLinesImport,
|
||||||
|
componentProps: {
|
||||||
|
ticketId: claim.value.ticketFk,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.onOk(() => arrayData.refresh());
|
.onOk(() => claimLinesForm.value.reload());
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<QPageSticky position="top" :offset="[0, 0]" expand>
|
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()">
|
||||||
<QToolbar class="bg-dark text-white">
|
<QToolbar class="bg-dark text-white">
|
||||||
<QToolbarTitle> {{ t('Claimed lines') }} </QToolbarTitle>
|
|
||||||
<QSpace />
|
|
||||||
<div class="row q-gutter-md">
|
<div class="row q-gutter-md">
|
||||||
<div>
|
<div>
|
||||||
{{ t('Amount') }}
|
{{ t('Amount') }}
|
||||||
|
@ -211,7 +172,7 @@ function showImportDialog() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</QToolbar>
|
</QToolbar>
|
||||||
</QPageSticky>
|
</Teleport>
|
||||||
|
|
||||||
<FetchData
|
<FetchData
|
||||||
:url="`Claims/${route.params.id}`"
|
:url="`Claims/${route.params.id}`"
|
||||||
|
@ -221,11 +182,16 @@ function showImportDialog() {
|
||||||
/>
|
/>
|
||||||
<div class="column items-center">
|
<div class="column items-center">
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<VnPaginate
|
<CrudModel
|
||||||
data-key="ClaimLines"
|
data-key="ClaimLines"
|
||||||
|
ref="claimLinesForm"
|
||||||
:url="`Claims/${route.params.id}/lines`"
|
:url="`Claims/${route.params.id}/lines`"
|
||||||
|
save-url="ClaimBeginnings/crud"
|
||||||
:filter="linesFilter"
|
:filter="linesFilter"
|
||||||
@on-fetch="onFetch"
|
@on-fetch="onFetch"
|
||||||
|
v-model:selected="selected"
|
||||||
|
:default-save="false"
|
||||||
|
:default-reset="false"
|
||||||
auto-load
|
auto-load
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #body="{ rows }">
|
||||||
|
@ -361,46 +327,12 @@ function showImportDialog() {
|
||||||
</template>
|
</template>
|
||||||
</QTable>
|
</QTable>
|
||||||
</template>
|
</template>
|
||||||
</VnPaginate>
|
</CrudModel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Teleport
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
v-if="stateStore.isHeaderMounted() && !$q.screen.lt.sm"
|
<QBtn fab color="primary" icon="add" @click="showImportDialog()" />
|
||||||
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>
|
</QPageSticky>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -421,7 +353,6 @@ en:
|
||||||
You are about to remove <strong>{count}</strong> row |
|
You are about to remove <strong>{count}</strong> row |
|
||||||
You are about to remove <strong>{count}</strong> rows'
|
You are about to remove <strong>{count}</strong> rows'
|
||||||
es:
|
es:
|
||||||
Claimed lines: Líneas reclamadas
|
|
||||||
Delivered: Entregado
|
Delivered: Entregado
|
||||||
Quantity: Cantidad
|
Quantity: Cantidad
|
||||||
Claimed: Reclamada
|
Claimed: Reclamada
|
||||||
|
|
|
@ -14,6 +14,13 @@ const route = useRoute();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
ticketId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'delivered',
|
name: 'delivered',
|
||||||
|
@ -99,7 +106,7 @@ function cancel() {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
<FetchData
|
||||||
url="Sales/getClaimableFromTicket?ticketFk=16"
|
:url="`Sales/getClaimableFromTicket?ticketFk=${$props.ticketId}`"
|
||||||
@on-fetch="(data) => (claimableSales = data)"
|
@on-fetch="(data) => (claimableSales = data)"
|
||||||
auto-load
|
auto-load
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -25,7 +25,7 @@ const body = {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="col items-center">
|
<div class="column items-center">
|
||||||
<VnNotes
|
<VnNotes
|
||||||
:add-note="true"
|
:add-note="true"
|
||||||
:id="id"
|
:id="id"
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { ref, computed } from 'vue';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
|
||||||
import { useSession } from 'composables/useSession';
|
import { useSession } from 'composables/useSession';
|
||||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
@ -12,7 +11,6 @@ import FetchData from 'components/FetchData.vue';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const stateStore = useStateStore();
|
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const token = session.getToken();
|
const token = session.getToken();
|
||||||
|
|
||||||
|
@ -237,59 +235,20 @@ function onDrag() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Teleport
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
v-if="stateStore.isHeaderMounted() && !quasar.platform.is.mobile"
|
<label for="fileInput">
|
||||||
to="#actions-prepend"
|
<QBtn fab @click="inputFile.nativeEl.click()" icon="add" color="primary">
|
||||||
>
|
<QInput
|
||||||
<div class="row q-gutter-x-sm">
|
ref="inputFile"
|
||||||
<label for="fileInput">
|
type="file"
|
||||||
<QBtn
|
style="display: none"
|
||||||
@click="inputFile.nativeEl.click()"
|
multiple
|
||||||
icon="add"
|
v-model="files"
|
||||||
color="primary"
|
@update:model-value="create()"
|
||||||
dense
|
/>
|
||||||
rounded
|
<QTooltip bottom> {{ t('globals.add') }} </QTooltip>
|
||||||
>
|
</QBtn>
|
||||||
<QInput
|
</label>
|
||||||
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>
|
</QPageSticky>
|
||||||
|
|
||||||
<!-- MULTIMEDIA DIALOG START-->
|
<!-- MULTIMEDIA DIALOG START-->
|
||||||
|
|
|
@ -1,48 +1,34 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { ref } from 'vue';
|
import { watch, ref, computed, onUnmounted, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useRoute } from 'vue-router';
|
import CrudModel from 'components/CrudModel.vue';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useState } from 'src/composables/useState';
|
||||||
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 { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const route = useRoute();
|
const state = useState();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const stateStore = useStateStore();
|
const selected = ref([]);
|
||||||
const arrayData = useArrayData('ClaimRma');
|
const claimRmaRef = ref();
|
||||||
|
const claim = computed(() => state.get('ClaimDescriptor'));
|
||||||
|
|
||||||
const claim = ref();
|
const claimRmaFilter = {
|
||||||
const claimFilter = {
|
include: {
|
||||||
fields: ['rma'],
|
relation: 'worker',
|
||||||
};
|
scope: {
|
||||||
|
include: {
|
||||||
async function onFetch(data) {
|
relation: 'user',
|
||||||
claim.value = data;
|
|
||||||
|
|
||||||
const filter = {
|
|
||||||
include: {
|
|
||||||
relation: 'worker',
|
|
||||||
scope: {
|
|
||||||
include: {
|
|
||||||
relation: 'user',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
order: 'created DESC',
|
},
|
||||||
where: {
|
order: 'created DESC',
|
||||||
code: claim.value.rma,
|
where: {
|
||||||
},
|
code: claim.value?.rma,
|
||||||
};
|
},
|
||||||
|
};
|
||||||
arrayData.applyFilter({ filter });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addRow() {
|
async function addRow() {
|
||||||
if (!claim.value.rma) {
|
if (!claim.value.rma) {
|
||||||
|
@ -56,7 +42,7 @@ async function addRow() {
|
||||||
};
|
};
|
||||||
|
|
||||||
await axios.post(`ClaimRmas`, formData);
|
await axios.post(`ClaimRmas`, formData);
|
||||||
await arrayData.refresh();
|
await claimRmaRef.value.reload();
|
||||||
|
|
||||||
quasar.notify({
|
quasar.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
|
@ -65,38 +51,33 @@ async function addRow() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmRemove(id) {
|
onMounted(() => {
|
||||||
quasar
|
if (claim.value) claimRmaRef.value.reload();
|
||||||
.dialog({
|
});
|
||||||
component: VnConfirm,
|
watch(
|
||||||
componentProps: {
|
claim,
|
||||||
data: { id },
|
() => {
|
||||||
promise: remove,
|
claimRmaRef.value.reload();
|
||||||
},
|
},
|
||||||
})
|
{ deep: true }
|
||||||
.onOk(async () => await arrayData.refresh());
|
);
|
||||||
}
|
|
||||||
|
|
||||||
async function remove({ id }) {
|
|
||||||
await axios.delete(`ClaimRmas/${id}`);
|
|
||||||
quasar.notify({
|
|
||||||
type: 'positive',
|
|
||||||
message: t('globals.rowRemoved'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
|
||||||
:url="`Claims/${route.params.id}`"
|
|
||||||
:filter="claimFilter"
|
|
||||||
@on-fetch="onFetch"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<div class="column items-center">
|
<div class="column items-center">
|
||||||
<div class="list">
|
<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 }">
|
<template #body="{ rows }">
|
||||||
<QCard class="card">
|
<QCard>
|
||||||
<template v-for="(row, index) of rows" :key="row.id">
|
<template v-for="(row, index) of rows" :key="row.id">
|
||||||
<QItem class="q-pa-none items-start">
|
<QItem class="q-pa-none items-start">
|
||||||
<QItemSection class="q-pa-md">
|
<QItemSection class="q-pa-md">
|
||||||
|
@ -107,7 +88,7 @@ async function remove({ id }) {
|
||||||
{{ t('claim.rma.user') }}
|
{{ t('claim.rma.user') }}
|
||||||
</QItemLabel>
|
</QItemLabel>
|
||||||
<QItemLabel>
|
<QItemLabel>
|
||||||
{{ row.worker.user.name }}
|
{{ row?.worker?.user?.name }}
|
||||||
</QItemLabel>
|
</QItemLabel>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
|
@ -133,7 +114,7 @@ async function remove({ id }) {
|
||||||
round
|
round
|
||||||
color="orange"
|
color="orange"
|
||||||
icon="vn:bin"
|
icon="vn:bin"
|
||||||
@click="confirmRemove(row.id)"
|
@click="claimRmaRef.remove([row])"
|
||||||
>
|
>
|
||||||
<QTooltip>{{ t('globals.remove') }}</QTooltip>
|
<QTooltip>{{ t('globals.remove') }}</QTooltip>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
|
@ -143,33 +124,11 @@ async function remove({ id }) {
|
||||||
</template>
|
</template>
|
||||||
</QCard>
|
</QCard>
|
||||||
</template>
|
</template>
|
||||||
</VnPaginate>
|
</CrudModel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
<Teleport
|
<QBtn fab color="primary" icon="add" @click="addRow()" />
|
||||||
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>
|
</QPageSticky>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -178,16 +137,6 @@ async function remove({ id }) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 60em;
|
max-width: 60em;
|
||||||
}
|
}
|
||||||
.q-toolbar {
|
|
||||||
background-color: $grey-9;
|
|
||||||
}
|
|
||||||
.sticky-page {
|
|
||||||
padding-top: 66px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-page-sticky {
|
|
||||||
z-index: 2998;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import CustomerDescriptor from './CustomerDescriptor.vue';
|
import CustomerDescriptor from './CustomerDescriptor.vue';
|
||||||
import LeftMenu from 'components/LeftMenu.vue';
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||||
|
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -25,8 +27,13 @@ const { t } = useI18n();
|
||||||
</QScrollArea>
|
</QScrollArea>
|
||||||
</QDrawer>
|
</QDrawer>
|
||||||
<QPageContainer>
|
<QPageContainer>
|
||||||
<QPage class="q-pa-md">
|
<QPage>
|
||||||
<RouterView></RouterView>
|
<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>
|
</QPage>
|
||||||
</QPageContainer>
|
</QPageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -25,8 +25,13 @@ const { t } = useI18n();
|
||||||
</QScrollArea>
|
</QScrollArea>
|
||||||
</QDrawer>
|
</QDrawer>
|
||||||
<QPageContainer>
|
<QPageContainer>
|
||||||
<QPage class="q-pa-md">
|
<QPage>
|
||||||
<RouterView></RouterView>
|
<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>
|
</QPage>
|
||||||
</QPageContainer>
|
</QPageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -25,8 +25,13 @@ const { t } = useI18n();
|
||||||
</QScrollArea>
|
</QScrollArea>
|
||||||
</QDrawer>
|
</QDrawer>
|
||||||
<QPageContainer>
|
<QPageContainer>
|
||||||
<QPage class="q-pa-md">
|
<QPage>
|
||||||
<RouterView></RouterView>
|
<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>
|
</QPage>
|
||||||
</QPageContainer>
|
</QPageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -20,7 +20,7 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
const entityId = computed(() => $props.id || route.params.id);
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
|
|
||||||
let wagonTypes;
|
let wagonTypes = [];
|
||||||
let originalData = {};
|
let originalData = {};
|
||||||
const wagon = ref({});
|
const wagon = ref({});
|
||||||
const filteredWagonTypes = ref(wagonTypes);
|
const filteredWagonTypes = ref(wagonTypes);
|
||||||
|
|
|
@ -25,8 +25,13 @@ const { t } = useI18n();
|
||||||
</QScrollArea>
|
</QScrollArea>
|
||||||
</QDrawer>
|
</QDrawer>
|
||||||
<QPageContainer>
|
<QPageContainer>
|
||||||
<QPage class="q-pa-md">
|
<QPage>
|
||||||
<RouterView></RouterView>
|
<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>
|
</QPage>
|
||||||
</QPageContainer>
|
</QPageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import axios from 'axios';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { ref, onMounted, computed, onUpdated } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import CardSummary from 'components/ui/CardSummary.vue';
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default {
|
||||||
'ClaimPhotos',
|
'ClaimPhotos',
|
||||||
'ClaimLog',
|
'ClaimLog',
|
||||||
'ClaimNotes',
|
'ClaimNotes',
|
||||||
|
'ClaimDevelopment',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
@ -101,6 +102,16 @@ export default {
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'),
|
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',
|
name: 'ClaimLog',
|
||||||
path: 'log',
|
path: 'log',
|
||||||
|
|
|
@ -30,6 +30,13 @@ export const useStateStore = defineStore('stateStore', () => {
|
||||||
return rightDrawer.value;
|
return rightDrawer.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSubToolbarShown() {
|
||||||
|
return (
|
||||||
|
!!document.querySelector('#st-data') &&
|
||||||
|
!!document.querySelector('#st-actions')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
leftDrawer,
|
leftDrawer,
|
||||||
rightDrawer,
|
rightDrawer,
|
||||||
|
@ -39,5 +46,6 @@ export const useStateStore = defineStore('stateStore', () => {
|
||||||
toggleRightDrawer,
|
toggleRightDrawer,
|
||||||
isLeftDrawerShown,
|
isLeftDrawerShown,
|
||||||
isRightDrawerShown,
|
isRightDrawerShown,
|
||||||
|
isSubToolbarShown,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/// <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.value', 'Prisas');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edit line', () => {
|
||||||
|
cy.selectOption(firstLineReason, 'Novato');
|
||||||
|
|
||||||
|
cy.saveCard();
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/claim/${claimId}/development`);
|
||||||
|
|
||||||
|
cy.getValue(firstLineReason).should('have.value', 'Novato');
|
||||||
|
|
||||||
|
//Restart data
|
||||||
|
cy.selectOption(firstLineReason, 'Prisas');
|
||||||
|
cy.saveCard();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add and remove new line', () => {
|
||||||
|
cy.addCard();
|
||||||
|
cy.get(thirdRow).should('exist');
|
||||||
|
|
||||||
|
const rowData = [false, 'Novato', 'Roces', 'Compradores', 'employeeNick', 'Tour'];
|
||||||
|
cy.fillRow(thirdRow, rowData);
|
||||||
|
|
||||||
|
cy.saveCard();
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/claim/${claimId}/development`);
|
||||||
|
|
||||||
|
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,90 @@ Cypress.Commands.add('login', (user) => {
|
||||||
window.localStorage.setItem('token', response.body.token);
|
window.localStorage.setItem('token', response.body.token);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('waitForElement', (element) => {
|
||||||
|
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 > input'
|
||||||
|
);
|
||||||
|
} 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();
|
||||||
|
});
|
||||||
|
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.value', value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
// registerCommands();
|
// 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()', () => {
|
||||||
|
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', () => {
|
describe('ClaimLines', () => {
|
||||||
let vm;
|
let vm;
|
||||||
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
vm = createWrapper(ClaimLines, {
|
vm = createWrapper(ClaimLines, {
|
||||||
global: {
|
global: {
|
||||||
|
@ -13,25 +12,26 @@ describe('ClaimLines', () => {
|
||||||
mocks: {
|
mocks: {
|
||||||
fetch: vi.fn(),
|
fetch: vi.fn(),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}).vm;
|
}).vm;
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vm.claim = {
|
vm.claim = {
|
||||||
id: 1,
|
id: 1,
|
||||||
ticketFk: 1
|
ticketFk: 1,
|
||||||
}
|
};
|
||||||
vm.store.data = [
|
vm.store.data = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
sale: {
|
sale: {
|
||||||
id: 1, discount: 0
|
id: 1,
|
||||||
}
|
discount: 0,
|
||||||
}
|
},
|
||||||
]
|
},
|
||||||
})
|
];
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
@ -42,13 +42,17 @@ describe('ClaimLines', () => {
|
||||||
vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
|
vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
|
||||||
vi.spyOn(vm.quasar, 'notify');
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
|
||||||
const canceller = new AbortController()
|
const canceller = new AbortController();
|
||||||
await vm.updateDiscount({ saleFk: 1, discount: 5, canceller });
|
await vm.updateDiscount({ saleFk: 1, discount: 5, canceller });
|
||||||
|
|
||||||
const expectedData = { salesIds: [1], newDiscount: 5 }
|
const expectedData = { salesIds: [1], newDiscount: 5 };
|
||||||
expect(axios.post).toHaveBeenCalledWith('Tickets/1/updateDiscount', expectedData, {
|
expect(axios.post).toHaveBeenCalledWith(
|
||||||
signal: canceller.signal
|
'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 () => {
|
it('should make a POST request and then set the discount on the original row', async () => {
|
||||||
vi.spyOn(vm.quasar, 'notify');
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
|
||||||
|
|
||||||
vm.onUpdateDiscount({ discount: 5, rowIndex: 0 });
|
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(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
message: 'Discount updated',
|
message: 'Discount updated',
|
||||||
type: 'positive'
|
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'
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,9 +7,11 @@ describe('WagonCreate', () => {
|
||||||
const entityId = 1;
|
const entityId = 1;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
vmEdit = createWrapper(WagonCreate, {propsData: {
|
vmEdit = createWrapper(WagonCreate, {
|
||||||
|
propsData: {
|
||||||
id: entityId,
|
id: entityId,
|
||||||
}}).vm;
|
},
|
||||||
|
}).vm;
|
||||||
vmCreate = createWrapper(WagonCreate).vm;
|
vmCreate = createWrapper(WagonCreate).vm;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -29,9 +31,7 @@ describe('WagonCreate', () => {
|
||||||
|
|
||||||
await vmCreate.onSubmit();
|
await vmCreate.onSubmit();
|
||||||
|
|
||||||
expect(axios.patch).toHaveBeenCalledWith(
|
expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmCreate.wagon);
|
||||||
`Wagons`, vmCreate.wagon
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update a wagon', async () => {
|
it('should update a wagon', async () => {
|
||||||
|
@ -46,9 +46,7 @@ describe('WagonCreate', () => {
|
||||||
|
|
||||||
await vmEdit.onSubmit();
|
await vmEdit.onSubmit();
|
||||||
|
|
||||||
expect(axios.patch).toHaveBeenCalledWith(
|
expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmEdit.wagon);
|
||||||
`Wagons`, vmEdit.wagon
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,16 +86,12 @@ describe('WagonCreate', () => {
|
||||||
|
|
||||||
describe('fetch()', () => {
|
describe('fetch()', () => {
|
||||||
it('should fetch data', async () => {
|
it('should fetch data', async () => {
|
||||||
vi.spyOn(axios, 'get').mockResolvedValue({ data: true });
|
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
|
||||||
|
|
||||||
await vmEdit.fetch();
|
await vmEdit.fetch();
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(
|
expect(axios.get).toHaveBeenCalledWith(`WagonTypes`);
|
||||||
`WagonTypes`
|
expect(axios.get).toHaveBeenCalledWith(`Wagons/${entityId}`);
|
||||||
);
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(
|
|
||||||
`Wagons/${entityId}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { vi } from 'vitest';
|
||||||
import { i18n } from 'src/boot/i18n';
|
import { i18n } from 'src/boot/i18n';
|
||||||
import { Notify, Dialog } from 'quasar';
|
import { Notify, Dialog } from 'quasar';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import * as useValidator from 'src/composables/useValidator';
|
||||||
|
|
||||||
installQuasarPlugin({
|
installQuasarPlugin({
|
||||||
plugins: {
|
plugins: {
|
||||||
|
@ -34,6 +35,10 @@ vi.mock('vue-router', () => ({
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.spyOn(useValidator, 'useValidator').mockImplementation(() => {
|
||||||
|
return { validate: vi.fn(), fetch: vi.fn() };
|
||||||
|
});
|
||||||
|
|
||||||
class FormDataMock {
|
class FormDataMock {
|
||||||
append() {
|
append() {
|
||||||
vi.fn();
|
vi.fn();
|
||||||
|
@ -64,6 +69,10 @@ export function createWrapper(component, options) {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, pinia],
|
plugins: [i18n, pinia],
|
||||||
},
|
},
|
||||||
|
mocks: {
|
||||||
|
t: (tKey) => tKey,
|
||||||
|
$t: (tKey) => tKey,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mountOptions = Object.assign({}, defaultOptions);
|
const mountOptions = Object.assign({}, defaultOptions);
|
||||||
|
|
Loading…
Reference in New Issue