forked from verdnatura/salix-front
Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 6930_scopedToken_Multimedia
This commit is contained in:
commit
efb3ae6e2a
|
@ -1,5 +1,6 @@
|
|||
FROM node:stretch-slim
|
||||
RUN npm install -g @quasar/cli
|
||||
RUN corepack enable pnpm
|
||||
RUN pnpm install -g @quasar/cli
|
||||
WORKDIR /app
|
||||
COPY dist/spa ./
|
||||
CMD ["quasar", "serve", "./", "--history", "--hostname", "0.0.0.0"]
|
||||
|
|
|
@ -62,7 +62,7 @@ pipeline {
|
|||
NODE_ENV = ""
|
||||
}
|
||||
steps {
|
||||
sh 'npm install --prefer-offline'
|
||||
sh 'pnpm install --prefer-offline'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
|
@ -73,7 +73,7 @@ pipeline {
|
|||
NODE_ENV = ""
|
||||
}
|
||||
steps {
|
||||
sh 'npm run test:unit:ci'
|
||||
sh 'pnpm run test:unit:ci'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
|
|
19
package.json
19
package.json
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.10.0",
|
||||
"version": "24.12.0",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.15.1",
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue ./",
|
||||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
|
@ -16,12 +17,12 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@quasar/cli": "^2.3.0",
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"@quasar/extras": "^1.16.9",
|
||||
"axios": "^1.4.0",
|
||||
"chromium": "^3.0.3",
|
||||
"croppie": "^2.6.5",
|
||||
"pinia": "^2.1.3",
|
||||
"quasar": "^2.12.0",
|
||||
"quasar": "^2.14.5",
|
||||
"validator": "^13.9.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
|
@ -30,11 +31,11 @@
|
|||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@quasar/app-vite": "^1.4.3",
|
||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.3.0",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"@quasar/app-vite": "^1.7.3",
|
||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cypress": "^12.13.0",
|
||||
"cypress": "^13.6.6",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-cypress": "^2.13.3",
|
||||
|
@ -50,8 +51,8 @@
|
|||
"bun": ">= 1.0.25"
|
||||
},
|
||||
"overrides": {
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"vite": "^4.3.5",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"vite": "^5.1.4",
|
||||
"vitest": "^0.31.1"
|
||||
}
|
||||
}
|
||||
|
|
758
pnpm-lock.yaml
758
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -67,7 +67,7 @@ module.exports = configure(function (/* ctx */) {
|
|||
// analyze: true,
|
||||
// env: {},
|
||||
rawDefine: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
},
|
||||
// ignorePublicFolder: true,
|
||||
// minify: false,
|
||||
|
@ -92,7 +92,7 @@ module.exports = configure(function (/* ctx */) {
|
|||
vitePlugins: [
|
||||
[
|
||||
VueI18nPlugin({
|
||||
runtimeOnly: false
|
||||
runtimeOnly: false,
|
||||
}),
|
||||
{
|
||||
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
|
||||
|
@ -123,9 +123,6 @@ module.exports = configure(function (/* ctx */) {
|
|||
framework: {
|
||||
config: {
|
||||
config: {
|
||||
brand: {
|
||||
primary: 'orange',
|
||||
},
|
||||
dark: 'auto',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { reactive, ref, onMounted, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
|
@ -16,9 +16,8 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const emit = defineEmits(['onDataSaved']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const bicInputRef = ref(null);
|
||||
const bankEntityFormData = reactive({
|
||||
name: null,
|
||||
bic: null,
|
||||
|
@ -32,9 +31,14 @@ const countriesFilter = {
|
|||
|
||||
const countriesOptions = ref([]);
|
||||
|
||||
const onDataSaved = (data) => {
|
||||
emit('onDataSaved', data);
|
||||
const onDataSaved = (formData, requestResponse) => {
|
||||
emit('onDataSaved', formData, requestResponse);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
bicInputRef.value.focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -50,7 +54,7 @@ const onDataSaved = (data) => {
|
|||
:title="t('title')"
|
||||
:subtitle="t('subtitle')"
|
||||
:form-initial-data="bankEntityFormData"
|
||||
@on-data-saved="onDataSaved($event)"
|
||||
@on-data-saved="onDataSaved"
|
||||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
|
@ -64,6 +68,7 @@ const onDataSaved = (data) => {
|
|||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
ref="bicInputRef"
|
||||
:label="t('swift')"
|
||||
v-model="data.bic"
|
||||
:required="true"
|
||||
|
|
|
@ -71,13 +71,9 @@ const closeForm = () => {
|
|||
<span ref="closeButton" class="close-icon" v-close-popup>
|
||||
<QIcon name="close" size="sm" />
|
||||
</span>
|
||||
<h1 class="title">
|
||||
{{
|
||||
t('editBuyTitle', {
|
||||
buysAmount: rows.length,
|
||||
})
|
||||
}}
|
||||
</h1>
|
||||
<span class="title">{{ t('Edit') }}</span>
|
||||
<span class="countLines">{{ ` ${rows.length} ` }}</span>
|
||||
<span class="title">{{ t('buy(s)') }}</span>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
|
@ -94,23 +90,23 @@ const closeForm = () => {
|
|||
</div>
|
||||
</VnRow>
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
type="submit"
|
||||
color="primary"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
type="reset"
|
||||
color="primary"
|
||||
flat
|
||||
class="q-ml-sm"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
v-close-popup
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
type="submit"
|
||||
color="primary"
|
||||
class="q-ml-sm"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
</QCard>
|
||||
</QForm>
|
||||
|
@ -129,13 +125,18 @@ const closeForm = () => {
|
|||
right: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.countLines {
|
||||
font-size: 24px;
|
||||
color: $primary;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
editBuyTitle: Edit {buysAmount} buy(s)
|
||||
es:
|
||||
editBuyTitle: Editar {buysAmount} compra(s)
|
||||
Field to edit: Campo a editar
|
||||
Value: Valor
|
||||
</i18n>
|
||||
es:
|
||||
Edit: Editar
|
||||
buy(s): compra(s)
|
||||
Field to edit: Campo a editar
|
||||
Value: Valor
|
||||
</i18n>
|
||||
|
|
|
@ -10,6 +10,7 @@ import { useValidator } from 'src/composables/useValidator';
|
|||
import useNotify from 'src/composables/useNotify.js';
|
||||
import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
||||
import VnConfirm from './ui/VnConfirm.vue';
|
||||
import { tMobile } from 'src/composables/tMobile';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const state = useState();
|
||||
|
@ -43,6 +44,10 @@ const $props = defineProps({
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defaultButtons: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
autoLoad: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -61,6 +66,10 @@ const $props = defineProps({
|
|||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
clearStoreOnUnmount: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
saveFn: {
|
||||
type: Function,
|
||||
default: null,
|
||||
|
@ -81,7 +90,7 @@ onMounted(async () => {
|
|||
});
|
||||
|
||||
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
|
||||
state.set($props.model, $props.formInitialData ?? {});
|
||||
state.set($props.model, $props.formInitialData);
|
||||
if ($props.autoLoad && !$props.formInitialData) {
|
||||
await fetch();
|
||||
}
|
||||
|
@ -109,7 +118,12 @@ onBeforeRouteLeave((to, from, next) => {
|
|||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
state.unset($props.model);
|
||||
// Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas.
|
||||
if (hasChanges.value) {
|
||||
state.set($props.model, originalData.value);
|
||||
return;
|
||||
}
|
||||
if ($props.clearStoreOnUnmount) state.unset($props.model);
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
@ -119,7 +133,19 @@ const hasChanges = ref(!$props.observeFormChanges);
|
|||
const originalData = ref({ ...$props.formInitialData });
|
||||
const formData = computed(() => state.get($props.model));
|
||||
const formUrl = computed(() => $props.url);
|
||||
|
||||
const defaultButtons = computed(() => ({
|
||||
save: {
|
||||
color: 'primary',
|
||||
icon: 'restart_alt',
|
||||
label: 'globals.save',
|
||||
},
|
||||
reset: {
|
||||
color: 'primary',
|
||||
icon: 'save',
|
||||
label: 'globals.reset',
|
||||
},
|
||||
...$props.defaultButtons,
|
||||
}));
|
||||
const startFormWatcher = () => {
|
||||
watch(
|
||||
() => formData.value,
|
||||
|
@ -131,10 +157,6 @@ const startFormWatcher = () => {
|
|||
);
|
||||
};
|
||||
|
||||
function tMobile(...args) {
|
||||
if (!quasar.platform.is.mobile) return t(...args);
|
||||
}
|
||||
|
||||
async function fetch() {
|
||||
const { data } = await axios.get($props.url, {
|
||||
params: { filter: JSON.stringify($props.filter) },
|
||||
|
@ -233,21 +255,21 @@ watch(formUrl, async () => {
|
|||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
<slot name="moreActions" />
|
||||
<QBtn
|
||||
:label="tMobile('globals.reset')"
|
||||
color="primary"
|
||||
icon="restart_alt"
|
||||
:label="tMobile(defaultButtons.reset.label)"
|
||||
:color="defaultButtons.reset.color"
|
||||
:icon="defaultButtons.reset.icon"
|
||||
flat
|
||||
@click="reset"
|
||||
:disable="!hasChanges"
|
||||
:title="t('globals.reset')"
|
||||
:title="t(defaultButtons.reset.label)"
|
||||
/>
|
||||
<QBtn
|
||||
:label="tMobile('globals.save')"
|
||||
color="primary"
|
||||
icon="save"
|
||||
:label="tMobile(defaultButtons.save.label)"
|
||||
:color="defaultButtons.save.color"
|
||||
:icon="defaultButtons.save.icon"
|
||||
@click="save"
|
||||
:disable="!hasChanges"
|
||||
:title="t('globals.save')"
|
||||
:title="t(defaultButtons.save.label)"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</div>
|
||||
|
|
|
@ -42,8 +42,8 @@ const { t } = useI18n();
|
|||
const closeButton = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
||||
const onDataSaved = (dataSaved) => {
|
||||
emit('onDataSaved', dataSaved);
|
||||
const onDataSaved = (formData, requestResponse) => {
|
||||
emit('onDataSaved', formData, requestResponse);
|
||||
closeForm();
|
||||
};
|
||||
|
||||
|
@ -59,7 +59,7 @@ const closeForm = () => {
|
|||
:default-actions="false"
|
||||
:url-create="urlCreate"
|
||||
:model="model"
|
||||
@on-data-saved="onDataSaved($event)"
|
||||
@on-data-saved="onDataSaved"
|
||||
>
|
||||
<template #form="{ data, validate }">
|
||||
<span ref="closeButton" class="close-icon" v-close-popup>
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
<script setup>
|
||||
import {useDialogPluginComponent} from 'quasar';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {computed, ref} from 'vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import axios from 'axios';
|
||||
import useNotify from "composables/useNotify";
|
||||
|
||||
const MESSAGE_MAX_LENGTH = 160;
|
||||
|
||||
const {t} = useI18n();
|
||||
const {notify} = useNotify();
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
destination: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
destinationFk: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([...useDialogPluginComponent.emits, 'sent']);
|
||||
const {dialogRef, onDialogHide} = useDialogPluginComponent();
|
||||
|
||||
const smsRules = [
|
||||
(val) => (val && val.length > 0) || t("The message can't be empty"),
|
||||
(val) =>
|
||||
(val && new Blob([val]).size <= MESSAGE_MAX_LENGTH) ||
|
||||
t("The message it's too long"),
|
||||
];
|
||||
|
||||
const message = ref('');
|
||||
|
||||
const charactersRemaining = computed(
|
||||
() => MESSAGE_MAX_LENGTH - new Blob([message.value]).size
|
||||
);
|
||||
|
||||
const charactersChipColor = computed(() => {
|
||||
if (charactersRemaining.value < 0) {
|
||||
return 'negative';
|
||||
}
|
||||
if (charactersRemaining.value <= 25) {
|
||||
return 'warning';
|
||||
}
|
||||
return 'primary';
|
||||
});
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!props.destination) {
|
||||
throw new Error(`The destination can't be empty`);
|
||||
}
|
||||
|
||||
if (!message.value) {
|
||||
throw new Error(`The message can't be empty`);
|
||||
}
|
||||
|
||||
if (charactersRemaining.value < 0) {
|
||||
throw new Error(`The message it's too long`);
|
||||
}
|
||||
|
||||
const response = await axios.post(props.url, {
|
||||
destination: props.destination,
|
||||
destinationFk: props.destinationFk,
|
||||
message: message.value,
|
||||
...props.data,
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
emit('sent', response.data);
|
||||
notify('globals.smsSent', 'positive');
|
||||
}
|
||||
emit('ok', response.data);
|
||||
emit('hide', response.data);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QDialog ref="dialogRef" @hide="onDialogHide">
|
||||
<QCard class="full-width dialog">
|
||||
<QCardSection class="row">
|
||||
<span v-if="title" class="text-h6">{{ title }}</span>
|
||||
<QSpace />
|
||||
<QBtn icon="close" flat round dense v-close-popup />
|
||||
</QCardSection>
|
||||
<QForm @submit="onSubmit">
|
||||
<QCardSection>
|
||||
<VnInput
|
||||
v-model="message"
|
||||
type="textarea"
|
||||
:rules="smsRules"
|
||||
:label="t('Message')"
|
||||
:placeholder="t('Message')"
|
||||
:rows="5"
|
||||
required
|
||||
clearable
|
||||
no-error-icon
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="info">
|
||||
<QTooltip>
|
||||
{{
|
||||
t(
|
||||
'Special characters like accents counts as a multiple'
|
||||
)
|
||||
}}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnInput>
|
||||
<p class="q-mb-none q-mt-md">
|
||||
{{ t('Characters remaining') }}:
|
||||
<QChip :color="charactersChipColor">
|
||||
{{ charactersRemaining }}
|
||||
</QChip>
|
||||
</p>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn type="button" flat v-close-popup class="text-primary">
|
||||
{{ t('globals.cancel') }}
|
||||
</QBtn>
|
||||
<QBtn type="submit" color="primary">{{ t('Send') }}</QBtn>
|
||||
</QCardActions>
|
||||
</QForm>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog {
|
||||
max-width: 450px;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
Message: Mensaje
|
||||
Send: Enviar
|
||||
Characters remaining: Carácteres restantes
|
||||
Special characters like accents counts as a multiple: Carácteres especiales como los acentos cuentan como varios
|
||||
The destination can't be empty: El destinatario no puede estar vacio
|
||||
The message can't be empty: El mensaje no puede estar vacio
|
||||
The message it's too long: El mensaje es demasiado largo
|
||||
</i18n>
|
|
@ -0,0 +1,34 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useCapitalize } from 'src/composables/useCapitalize';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const amount = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:modelValue', val);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VnInput
|
||||
v-model="amount"
|
||||
type="number"
|
||||
step="any"
|
||||
:label="useCapitalize(t('amount'))"
|
||||
/>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
amount: importe
|
||||
</i18n>
|
|
@ -27,6 +27,10 @@ const $props = defineProps({
|
|||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const warehouses = ref();
|
||||
|
@ -65,14 +69,15 @@ function mapperDms(data) {
|
|||
}
|
||||
|
||||
function getUrl() {
|
||||
if ($props.url) return $props.url;
|
||||
if ($props.formInitialData) return 'dms/' + $props.formInitialData.id + '/updateFile';
|
||||
return `${$props.model}/${route.params.id}/uploadFile`;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const body = mapperDms(dms.value);
|
||||
await axios.post(getUrl(), body[0], body[1]);
|
||||
emit('onDataSaved', body[1].params);
|
||||
const response = await axios.post(getUrl(), body[0], body[1]);
|
||||
emit('onDataSaved', body[1].params, response);
|
||||
}
|
||||
|
||||
function defaultData() {
|
||||
|
@ -111,7 +116,7 @@ function addDefaultData(data) {
|
|||
<FormModelPopup
|
||||
:title="formInitialData ? t('globals.edit') : t('globals.create')"
|
||||
model="dms"
|
||||
:form-initial-data="formInitialData"
|
||||
:form-initial-data="formInitialData ?? {}"
|
||||
:save-fn="save"
|
||||
>
|
||||
<template #form-inputs>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']);
|
||||
|
@ -17,7 +17,7 @@ const $props = defineProps({
|
|||
|
||||
const { t } = useI18n();
|
||||
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
|
||||
|
||||
const vnInputRef = ref(null);
|
||||
const value = computed({
|
||||
get() {
|
||||
return $props.modelValue;
|
||||
|
@ -40,6 +40,14 @@ const styleAttrs = computed(() => {
|
|||
const onEnterPress = () => {
|
||||
emit('keyup.enter');
|
||||
};
|
||||
|
||||
const focus = () => {
|
||||
vnInputRef.value.focus();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
focus,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { toDate } from 'src/filters';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import isValidDate from "filters/isValidDate";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -17,12 +18,25 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const joinDateAndTime = (date, time) => {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
if (!time) {
|
||||
return new Date(date).toISOString();
|
||||
}
|
||||
const [year, month, day] = date.split('/');
|
||||
return new Date(`${year}-${month}-${day}T${time}`).toISOString();
|
||||
};
|
||||
|
||||
const time = computed(() => (props.modelValue ? props.modelValue.split('T')?.[1] : null));
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value ? new Date(value).toISOString() : null);
|
||||
emit('update:modelValue', joinDateAndTime(value, time.value));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -40,6 +54,16 @@ const formatDate = (dateString) => {
|
|||
date.getDate()
|
||||
)}`;
|
||||
};
|
||||
const displayDate = (dateString) => {
|
||||
if (!dateString || !isValidDate(dateString)) {
|
||||
return '';
|
||||
}
|
||||
return new Date(dateString).toLocaleDateString([], {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
const styleAttrs = computed(() => {
|
||||
return props.isOutlined
|
||||
|
@ -53,12 +77,12 @@ const styleAttrs = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<QInput
|
||||
<VnInput
|
||||
class="vn-input-date"
|
||||
rounded
|
||||
readonly
|
||||
:model-value="toDate(value)"
|
||||
:model-value="displayDate(value)"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
readonly
|
||||
@click="isPopupOpen = true"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
|
@ -76,7 +100,7 @@ const styleAttrs = computed(() => {
|
|||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QInput>
|
||||
</VnInput>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { toHour } from 'src/filters';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import isValidDate from 'filters/isValidDate';
|
||||
import VnInput from "components/common/VnInput.vue";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -26,8 +26,8 @@ const value = computed({
|
|||
},
|
||||
set(value) {
|
||||
const [hours, minutes] = value.split(':');
|
||||
const date = new Date();
|
||||
date.setUTCHours(
|
||||
const date = new Date(props.modelValue);
|
||||
date.setHours(
|
||||
Number.parseInt(hours) || 0,
|
||||
Number.parseInt(minutes) || 0,
|
||||
0,
|
||||
|
@ -37,6 +37,7 @@ const value = computed({
|
|||
},
|
||||
});
|
||||
|
||||
const isPopupOpen = ref(false);
|
||||
const onDateUpdate = (date) => {
|
||||
internalValue.value = date;
|
||||
};
|
||||
|
@ -45,7 +46,7 @@ const save = () => {
|
|||
value.value = internalValue.value;
|
||||
};
|
||||
const formatTime = (dateString) => {
|
||||
if (!isValidDate(dateString)) {
|
||||
if (!dateString || !isValidDate(dateString)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -70,16 +71,17 @@ const styleAttrs = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<QInput
|
||||
<VnInput
|
||||
class="vn-input-time"
|
||||
rounded
|
||||
readonly
|
||||
:model-value="toHour(value)"
|
||||
:model-value="formatTime(value)"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
@click="isPopupOpen = true"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
<QPopupProxy
|
||||
v-model="isPopupOpen"
|
||||
cover
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
|
@ -109,7 +111,7 @@ const styleAttrs = computed(() => {
|
|||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QInput>
|
||||
</VnInput>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -1036,7 +1036,6 @@ en:
|
|||
claimStateFk: Claim State
|
||||
workerFk: Worker
|
||||
clientFk: Customer
|
||||
rma: RMA
|
||||
responsibility: Responsibility
|
||||
packages: Packages
|
||||
es:
|
||||
|
@ -1046,7 +1045,7 @@ es:
|
|||
tooltips:
|
||||
search: Buscar por identificador o concepto
|
||||
changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo.
|
||||
Audit logs: Registros de auditoría
|
||||
Audit logs: Historial
|
||||
Property: Propiedad
|
||||
Before: Antes
|
||||
After: Después
|
||||
|
@ -1076,7 +1075,6 @@ es:
|
|||
claimStateFk: Estado de la reclamación
|
||||
workerFk: Trabajador
|
||||
clientFk: Cliente
|
||||
rma: RMA
|
||||
responsibility: Responsabilidad
|
||||
packages: Bultos
|
||||
</i18n>
|
||||
|
|
|
@ -59,6 +59,9 @@ const toggleForm = () => {
|
|||
:name="actionIcon"
|
||||
:size="actionIcon === 'add' ? 'xs' : 'sm'"
|
||||
:class="['default-icon', { '--add-icon': actionIcon === 'add' }]"
|
||||
:style="{
|
||||
'font-variation-settings': `'FILL' ${1}`,
|
||||
}"
|
||||
>
|
||||
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
|
||||
</QIcon>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
const $props = defineProps({
|
||||
url: {
|
||||
|
@ -35,6 +36,8 @@ const $props = defineProps({
|
|||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const state = useState();
|
||||
const slots = useSlots();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
@ -64,6 +67,7 @@ async function getData() {
|
|||
isLoading.value = true;
|
||||
try {
|
||||
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
|
||||
state.set($props.dataKey, data);
|
||||
emit('onFetch', data);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
|
@ -171,6 +175,7 @@ const emit = defineEmits(['onFetch']);
|
|||
|
||||
<style lang="scss">
|
||||
.body {
|
||||
background-color: var(--vn-gray);
|
||||
.text-h5 {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
|
@ -188,7 +193,8 @@ const emit = defineEmits(['onFetch']);
|
|||
.label {
|
||||
color: var(--vn-label);
|
||||
font-size: 12px;
|
||||
::after {
|
||||
|
||||
&:not(:has(a))::after {
|
||||
content: ':';
|
||||
}
|
||||
}
|
||||
|
@ -223,8 +229,6 @@ const emit = defineEmits(['onFetch']);
|
|||
margin-bottom: 15px;
|
||||
}
|
||||
.list-box {
|
||||
background-color: var(--vn-gray);
|
||||
|
||||
.q-item__label {
|
||||
color: var(--vn-label);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
|
||||
onMounted(() => fetch());
|
||||
|
||||
const entity = ref();
|
||||
const props = defineProps({
|
||||
url: {
|
||||
|
@ -16,14 +15,25 @@ const props = defineProps({
|
|||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
entityId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['onFetch']);
|
||||
const route = useRoute();
|
||||
const isSummary = ref();
|
||||
|
||||
defineExpose({
|
||||
entity,
|
||||
fetch,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
isSummary.value = String(route.path).endsWith('/summary');
|
||||
fetch();
|
||||
});
|
||||
|
||||
async function fetch() {
|
||||
const params = {};
|
||||
|
||||
|
@ -46,9 +56,19 @@ watch(props, async () => {
|
|||
<QCard class="cardSummary">
|
||||
<SkeletonSummary v-if="!entity" />
|
||||
<template v-if="entity">
|
||||
<div class="summaryHeader bg-primary q-pa-md text-weight-bolder">
|
||||
<div class="summaryHeader bg-primary q-pa-sm text-weight-bolder">
|
||||
<slot name="header-left">
|
||||
<span></span>
|
||||
<router-link
|
||||
v-if="!isSummary && route.meta.moduleName"
|
||||
class="header link"
|
||||
:to="{
|
||||
name: `${route.meta.moduleName}Summary`,
|
||||
params: { id: entityId || entity.id },
|
||||
}"
|
||||
>
|
||||
<QIcon name="open_in_new" color="white" size="sm" />
|
||||
</router-link>
|
||||
<span v-else></span>
|
||||
</slot>
|
||||
<slot name="header" :entity="entity">
|
||||
<VnLv :label="`${entity.id} -`" :value="entity.name" />
|
||||
|
@ -85,8 +105,8 @@ watch(props, async () => {
|
|||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background-color: var(--vn-gray);
|
||||
|
||||
> .q-card.vn-one {
|
||||
|
@ -105,14 +125,14 @@ watch(props, async () => {
|
|||
> .q-card {
|
||||
width: 100%;
|
||||
background-color: var(--vn-gray);
|
||||
padding: 15px;
|
||||
padding: 7px;
|
||||
font-size: 16px;
|
||||
min-width: 275px;
|
||||
|
||||
.vn-label-value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 5px;
|
||||
margin-top: 2px;
|
||||
.label {
|
||||
color: var(--vn-label);
|
||||
width: 8em;
|
||||
|
@ -131,13 +151,26 @@ watch(props, async () => {
|
|||
.header {
|
||||
color: $primary;
|
||||
font-weight: bold;
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
.header.link:hover {
|
||||
color: lighten($primary, 20%);
|
||||
}
|
||||
.q-checkbox {
|
||||
display: flex;
|
||||
margin-bottom: 9px;
|
||||
& .q-checkbox__label {
|
||||
margin-left: 31px;
|
||||
color: var(--vn-text);
|
||||
}
|
||||
& .q-checkbox__inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--vn-label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,49 @@ defineProps({
|
|||
<template>
|
||||
<div class="fetchedTags">
|
||||
<div class="wrap">
|
||||
<div class="inline-tag" :class="{ empty: !$props.item.value5 }">{{ $props.item.value5 }}</div>
|
||||
<div class="inline-tag" :class="{ empty: !$props.item.value6 }">{{ $props.item.value6 }}</div>
|
||||
<div class="inline-tag" :class="{ empty: !$props.item.value7 }">{{ $props.item.value7 }}</div>
|
||||
<div class="inline-tag" :class="{ empty: !$props.item.value8 }">{{ $props.item.value8 }}</div>
|
||||
<div class="inline-tag" :class="{ empty: !$props.item.value9 }">{{ $props.item.value9 }}</div>
|
||||
<div class="inline-tag" :class="{ empty: !$props.item.value10 }">{{ $props.item.value10 }}</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value5 }"
|
||||
:title="$props.item.tag5 + ': ' + $props.item.value5"
|
||||
>
|
||||
{{ $props.item.value5 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.tag6 }"
|
||||
:title="$props.item.tag6 + ': ' + $props.item.value6"
|
||||
>
|
||||
{{ $props.item.value6 }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value7 }"
|
||||
:title="$props.item.tag7 + ': ' + $props.item.value7"
|
||||
>
|
||||
{{ $props.item.value7 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value8 }"
|
||||
:title="$props.item.tag8 + ': ' + $props.item.value8"
|
||||
>
|
||||
{{ $props.item.value8 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value9 }"
|
||||
:title="$props.item.tag9 + ': ' + $props.item.value9"
|
||||
>
|
||||
{{ $props.item.value9 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value10 }"
|
||||
:title="$props.item.tag10 + ': ' + $props.item.value10"
|
||||
>
|
||||
{{ $props.item.value10 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClipboard } from 'src/composables/useClipboard';
|
||||
|
@ -16,7 +15,6 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const isBooleanValue = computed(() => typeof $props.value === 'boolean');
|
||||
const { copyText } = useClipboard();
|
||||
|
||||
function copyValueText() {
|
||||
|
@ -42,14 +40,7 @@ function copyValueText() {
|
|||
</slot>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span v-if="isBooleanValue">
|
||||
<QIcon
|
||||
:name="$props.value ? `check` : `close`"
|
||||
:color="$props.value ? `positive` : `negative`"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
<slot v-else name="value">
|
||||
<slot name="value">
|
||||
<span :title="$props.value">
|
||||
{{ $props.dash ? dashIfEmpty($props.value) : $props.value }}
|
||||
</span>
|
||||
|
|
|
@ -8,7 +8,6 @@ import VnPaginate from './VnPaginate.vue';
|
|||
import VnUserLink from '../ui/VnUserLink.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: { type: String, required: true },
|
||||
url: { type: String, default: null },
|
||||
filter: { type: Object, default: () => {} },
|
||||
body: { type: Object, default: () => {} },
|
||||
|
@ -28,7 +27,7 @@ async function insert() {
|
|||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center full-height">
|
||||
<div class="column items-center full-height full-width">
|
||||
<VnPaginate
|
||||
:data-key="$props.url"
|
||||
:url="$props.url"
|
||||
|
@ -39,28 +38,38 @@ async function insert() {
|
|||
ref="vnPaginateRef"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard class="q-pa-xs q-mb-md" v-for="(note, index) in rows" :key="index">
|
||||
<QCardSection horizontal>
|
||||
<slot name="picture">
|
||||
<VnAvatar :descriptor="false" :worker-id="note.workerFk" />
|
||||
</slot>
|
||||
<QItem class="full-width justify-between items-start">
|
||||
<VnUserLink
|
||||
:name="`${note.worker.user.nickname}`"
|
||||
:worker-id="note.worker.id"
|
||||
/>
|
||||
|
||||
<slot name="actions">
|
||||
{{ toDateHour(note.created) }}
|
||||
<div class="column items-center full-width">
|
||||
<QCard
|
||||
class="q-pa-xs q-mb-sm full-width"
|
||||
v-for="(note, index) in rows"
|
||||
:key="index"
|
||||
>
|
||||
<QCardSection horizontal>
|
||||
<slot name="picture">
|
||||
<VnAvatar
|
||||
:descriptor="false"
|
||||
:worker-id="note.workerFk"
|
||||
size="md"
|
||||
/>
|
||||
</slot>
|
||||
</QItem>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pa-sm">
|
||||
<slot name="text">
|
||||
{{ note.text }}
|
||||
</slot>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
<div class="full-width row justify-between q-pa-xs">
|
||||
<VnUserLink
|
||||
:name="`${note.worker.user.nickname}`"
|
||||
:worker-id="note.worker.id"
|
||||
/>
|
||||
|
||||
<slot name="actions">
|
||||
{{ toDateHour(note.created) }}
|
||||
</slot>
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pa-xs q-my-none q-py-none">
|
||||
<slot name="text">
|
||||
{{ note.text }}
|
||||
</slot>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]" v-if="addNote">
|
||||
|
@ -108,8 +117,10 @@ async function insert() {
|
|||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.q-card {
|
||||
max-width: 80em;
|
||||
|
||||
width: 90%;
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
width: 100%;
|
||||
}
|
||||
&__section {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ const props = defineProps({
|
|||
},
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
default: 0,
|
||||
},
|
||||
skeleton: {
|
||||
type: Boolean,
|
||||
|
@ -54,6 +54,10 @@ const props = defineProps({
|
|||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
disableInfiniteScroll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFetch', 'onPaginate']);
|
||||
|
@ -122,10 +126,11 @@ async function paginate() {
|
|||
emit('onPaginate');
|
||||
}
|
||||
|
||||
async function onLoad(...params) {
|
||||
if (!store.data) return;
|
||||
async function onLoad(index, done) {
|
||||
if (!store.data) {
|
||||
return done();
|
||||
}
|
||||
|
||||
const done = params[1];
|
||||
if (store.data.length === 0 || !props.url) return done(false);
|
||||
|
||||
pagination.value.page = pagination.value.page + 1;
|
||||
|
@ -138,7 +143,7 @@ async function onLoad(...params) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="full-width">
|
||||
<div
|
||||
v-if="!props.autoLoad && !store.data && !isLoading"
|
||||
class="info-row q-pa-md text-center"
|
||||
|
@ -174,7 +179,9 @@ async function onLoad(...params) {
|
|||
v-if="store.data"
|
||||
@load="onLoad"
|
||||
:offset="offset"
|
||||
class="full-width full-height"
|
||||
:disable="disableInfiniteScroll || !arrayData.hasMoreData.value"
|
||||
class="full-width"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot name="body" :rows="store.data"></slot>
|
||||
<div v-if="isLoading" class="info-row q-pa-md text-center">
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
<template>
|
||||
<div id="row" class="q-gutter-md">
|
||||
<div id="row" class="q-gutter-md q-mb-md">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scopped>
|
||||
#row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
display: flex;
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
#row {
|
||||
grid-template-columns: 1fr;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -81,8 +81,9 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
async function search() {
|
||||
const staticParams = Object.entries(store.userParams)
|
||||
.filter(([key, value]) => value && (props.staticParams || []).includes(key));
|
||||
const staticParams = Object.entries(store.userParams).filter(
|
||||
([key, value]) => value && (props.staticParams || []).includes(key)
|
||||
);
|
||||
await arrayData.applyFilter({
|
||||
params: {
|
||||
...Object.fromEntries(staticParams),
|
||||
|
@ -117,7 +118,12 @@ async function search() {
|
|||
autofocus
|
||||
>
|
||||
<template #prepend>
|
||||
<QIcon name="search" v-if="!quasar.platform.is.mobile" />
|
||||
<QIcon
|
||||
v-if="!quasar.platform.is.mobile"
|
||||
class="cursor-pointer"
|
||||
name="search"
|
||||
@click="search"
|
||||
/>
|
||||
</template>
|
||||
<template #append>
|
||||
<QIcon
|
||||
|
@ -155,11 +161,9 @@ async function search() {
|
|||
.cursor-info {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.body--light #searchbar {
|
||||
#searchbar {
|
||||
.q-field--standout.q-field--highlighted .q-field__control {
|
||||
background-color: $grey-7;
|
||||
color: #333;
|
||||
background-color: var(--vn-text);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { useQuasar } from 'quasar';
|
||||
|
||||
export default function() {
|
||||
const quasar = useQuasar();
|
||||
return quasar.screen.gt.xs ? 'q-pa-md': 'q-pa-xs';
|
||||
|
||||
}
|
|
@ -1,17 +1,59 @@
|
|||
// app global css in SCSS form
|
||||
@import './icons.scss';
|
||||
|
||||
body.body--light {
|
||||
--fount-color: black;
|
||||
--vn-sectionColor: #ffffff;
|
||||
--vn-pageColor: #e0e0e0;
|
||||
background-color: var(--vn-pageColor);
|
||||
.q-header .q-toolbar {
|
||||
color: var(--fount-color);
|
||||
}
|
||||
--vn-text: var(--fount-color);
|
||||
--vn-gray: var(--vn-sectionColor);
|
||||
--vn-label: #5f5f5f;
|
||||
--vn-dark: var(--vn-sectionColor);
|
||||
--vn-light-gray: #e7e3e3;
|
||||
}
|
||||
|
||||
body.body--dark {
|
||||
--vn-pageColor: #222;
|
||||
--vn-SectionColor: #3c3b3b;
|
||||
background-color: var(--vn-pageColor);
|
||||
--vn-text: white;
|
||||
--vn-gray: var(--vn-SectionColor);
|
||||
--vn-label: #a8a8a8;
|
||||
--vn-dark: var(--vn-SectionColor);
|
||||
--vn-light-gray: #424242;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $primary;
|
||||
color: $color-link;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tx-color-link {
|
||||
color: $color-link !important;
|
||||
}
|
||||
|
||||
.header-link {
|
||||
color: $color-link !important;
|
||||
cursor: pointer;
|
||||
border-bottom: solid $primary;
|
||||
border-width: 2px;
|
||||
width: 100%;
|
||||
.q-icon {
|
||||
float: right;
|
||||
}
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: $orange-4;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
// Removes chrome autofill background
|
||||
|
@ -24,26 +66,6 @@ select:-webkit-autofill {
|
|||
background-clip: text !important;
|
||||
}
|
||||
|
||||
body.body--light {
|
||||
.q-header .q-toolbar {
|
||||
background-color: $white;
|
||||
color: #555;
|
||||
}
|
||||
--vn-text: #000000;
|
||||
--vn-gray: #f5f5f5;
|
||||
--vn-label: #5f5f5f;
|
||||
--vn-dark: white;
|
||||
--vn-light-gray: #e7e3e3;
|
||||
}
|
||||
|
||||
body.body--dark {
|
||||
--vn-text: #ffffff;
|
||||
--vn-gray: #313131;
|
||||
--vn-label: #a8a8a8;
|
||||
--vn-dark: #292929;
|
||||
--vn-light-gray: #424242;
|
||||
}
|
||||
|
||||
.bg-vn-dark {
|
||||
background-color: var(--vn-dark);
|
||||
}
|
||||
|
@ -75,6 +97,11 @@ body.body--dark {
|
|||
background-color: var(--vn-light-gray);
|
||||
}
|
||||
|
||||
.vn-table-separation-row {
|
||||
height: 16px !important;
|
||||
background-color: var(--vn-gray) !important;
|
||||
}
|
||||
|
||||
/* Estilo para el asterisco en campos requeridos */
|
||||
.q-field.required .q-field__label:after {
|
||||
content: ' *';
|
||||
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 173 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -1,10 +1,10 @@
|
|||
@font-face {
|
||||
font-family: 'icon';
|
||||
src: url('fonts/icon.eot?7zbcv0');
|
||||
src: url('fonts/icon.eot?7zbcv0#iefix') format('embedded-opentype'),
|
||||
url('fonts/icon.ttf?7zbcv0') format('truetype'),
|
||||
url('fonts/icon.woff?7zbcv0') format('woff'),
|
||||
url('fonts/icon.svg?7zbcv0#icon') format('svg');
|
||||
src: url('fonts/icon.eot?2omjsr');
|
||||
src: url('fonts/icon.eot?2omjsr#iefix') format('embedded-opentype'),
|
||||
url('fonts/icon.ttf?2omjsr') format('truetype'),
|
||||
url('fonts/icon.woff?2omjsr') format('woff'),
|
||||
url('fonts/icon.svg?2omjsr#icon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
@ -28,6 +28,9 @@
|
|||
.icon-100:before {
|
||||
content: "\e926";
|
||||
}
|
||||
.icon-Client_unpaid:before {
|
||||
content: "\e925";
|
||||
}
|
||||
.icon-History:before {
|
||||
content: "\e964";
|
||||
}
|
||||
|
@ -46,9 +49,15 @@
|
|||
.icon-addperson:before {
|
||||
content: "\e929";
|
||||
}
|
||||
.icon-agency:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
.icon-agency-term:before {
|
||||
content: "\e92b";
|
||||
}
|
||||
.icon-albaran:before {
|
||||
content: "\e92c";
|
||||
}
|
||||
.icon-anonymous:before {
|
||||
content: "\e92d";
|
||||
}
|
||||
|
@ -172,6 +181,9 @@
|
|||
.icon-funeral:before {
|
||||
content: "\e95f";
|
||||
}
|
||||
.icon-grafana:before {
|
||||
content: "\e931";
|
||||
}
|
||||
.icon-greenery:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
|
@ -355,6 +367,9 @@
|
|||
.icon-traceability:before {
|
||||
content: "\e919";
|
||||
}
|
||||
.icon-transaction:before {
|
||||
content: "\e93b";
|
||||
}
|
||||
.icon-treatments:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
|
|
|
@ -11,26 +11,32 @@
|
|||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding.
|
||||
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
||||
|
||||
// Tip: to add new colors https://quasar.dev/style/color-palette/#adding-your-own-colors
|
||||
$primary: #ec8916;
|
||||
$primary-light: lighten($primary, 35%);
|
||||
$secondary: #26a69a;
|
||||
$accent: #9c27b0;
|
||||
$white: #fff;
|
||||
|
||||
$secondary: $primary;
|
||||
$positive: #21ba45;
|
||||
$negative: #c10015;
|
||||
$info: #31ccec;
|
||||
$warning: #f2c037;
|
||||
$vnColor: #8ebb27;
|
||||
|
||||
// Pendiente de cuadrar con la base de datos
|
||||
$success: $positive;
|
||||
$alert: $negative;
|
||||
$white: #fff;
|
||||
$dark: #3c3b3b;
|
||||
// custom
|
||||
$color-link: #66bfff;
|
||||
$color-spacer-light: #a3a3a31f;
|
||||
$color-spacer: #7979794d;
|
||||
$border-thin-light: 1px solid $color-spacer-light;
|
||||
$primary-light: lighten($primary, 35%);
|
||||
$dark-shadow-color: black;
|
||||
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
|
||||
$spacing-md: 16px;
|
||||
|
||||
.bg-success {
|
||||
background-color: $positive;
|
||||
}
|
||||
|
||||
.bg-notice {
|
||||
background-color: $info;
|
||||
}
|
||||
|
@ -40,12 +46,3 @@ $alert: $negative;
|
|||
.bg-alert {
|
||||
background-color: $negative;
|
||||
}
|
||||
|
||||
$color-spacer-light: rgba(255, 255, 255, 0.12);
|
||||
$color-spacer: rgba(255, 255, 255, 0.3);
|
||||
$border-thin-light: 1px solid $color-spacer-light;
|
||||
|
||||
$dark-shadow-color: #000;
|
||||
$dark: #292929;
|
||||
$layout-shadow-dark: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0px 10px rgba(0, 0, 0, 0.24);
|
||||
$spacing-md: 16px;
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Checks if a given date is valid.
|
||||
*
|
||||
* @param {number|string|Date} date - The date to be checked.
|
||||
* @returns {boolean} True if the date is valid, false otherwise.
|
||||
*
|
||||
* @example
|
||||
* // returns true
|
||||
* isValidDate(new Date());
|
||||
*
|
||||
* @example
|
||||
* // returns false
|
||||
* isValidDate('invalid date');
|
||||
*/
|
||||
export function isValidDate(date) {
|
||||
return date && !isNaN(new Date(date).getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given date to a specific format.
|
||||
*
|
||||
* @param {number|string|Date} date - The date to be formatted.
|
||||
* @returns {string} The formatted date as a string in 'dd/mm/yyyy' format. If the provided date is not valid, an empty string is returned.
|
||||
*
|
||||
* @example
|
||||
* // returns "02/12/2022"
|
||||
* toDateFormat(new Date(2022, 11, 2));
|
||||
*/
|
||||
export function toDateFormat(date) {
|
||||
if (!isValidDate(date)) {
|
||||
return '';
|
||||
}
|
||||
return new Date(date).toLocaleDateString('es-ES', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given date to a specific time format.
|
||||
*
|
||||
* @param {number|string|Date} date - The date to be formatted.
|
||||
* @param {boolean} [showSeconds=false] - Whether to include seconds in the output format.
|
||||
* @returns {string} The formatted time as a string in 'hh:mm:ss' format. If the provided date is not valid, an empty string is returned. If showSeconds is false, seconds are not included in the output format.
|
||||
*
|
||||
* @example
|
||||
* // returns "00:00"
|
||||
* toTimeFormat(new Date(2022, 11, 2));
|
||||
*
|
||||
* @example
|
||||
* // returns "00:00:00"
|
||||
* toTimeFormat(new Date(2022, 11, 2), true);
|
||||
*/
|
||||
export function toTimeFormat(date, showSeconds = false) {
|
||||
if (!isValidDate(date)) {
|
||||
return '';
|
||||
}
|
||||
return new Date(date).toLocaleDateString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: showSeconds ? '2-digit' : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given date to a specific date and time format.
|
||||
*
|
||||
* @param {number|string|Date} date - The date to be formatted.
|
||||
* @param {boolean} [showSeconds=false] - Whether to include seconds in the output format.
|
||||
* @returns {string} The formatted date as a string in 'dd/mm/yyyy, hh:mm:ss' format. If the provided date is not valid, an empty string is returned. If showSeconds is false, seconds are not included in the output format.
|
||||
*
|
||||
* @example
|
||||
* // returns "02/12/2022, 00:00"
|
||||
* toDateTimeFormat(new Date(2022, 11, 2));
|
||||
*
|
||||
* @example
|
||||
* // returns "02/12/2022, 00:00:00"
|
||||
* toDateTimeFormat(new Date(2022, 11, 2), true);
|
||||
*/
|
||||
export function toDateTimeFormat(date, showSeconds = false) {
|
||||
if (!isValidDate(date)) {
|
||||
return '';
|
||||
}
|
||||
return new Date(date).toLocaleDateString('es-ES', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: showSeconds ? '2-digit' : undefined,
|
||||
});
|
||||
}
|
|
@ -31,6 +31,7 @@ export default {
|
|||
close: 'Close',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
assign: 'Assign',
|
||||
back: 'Back',
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
|
@ -73,6 +74,7 @@ export default {
|
|||
company: 'Company',
|
||||
fieldRequired: 'Field required',
|
||||
allowedFilesText: 'Allowed file types: { allowedContentTypes }',
|
||||
smsSent: 'SMS sent',
|
||||
confirmDeletion: 'Confirm deletion',
|
||||
confirmDeletionMessage: 'Are you sure you want to delete this?',
|
||||
description: 'Description',
|
||||
|
@ -82,6 +84,7 @@ export default {
|
|||
file: 'File',
|
||||
selectFile: 'Select a file',
|
||||
copyClipboard: 'Copy on clipboard',
|
||||
salesPerson: 'SalesPerson',
|
||||
},
|
||||
errors: {
|
||||
statusUnauthorized: 'Access denied',
|
||||
|
@ -172,6 +175,7 @@ export default {
|
|||
hasDebt: 'Customer has debt',
|
||||
notChecked: 'Customer not checked',
|
||||
noWebAccess: 'Web access is disabled',
|
||||
businessTypeFk: 'Business type',
|
||||
},
|
||||
summary: {
|
||||
basicData: 'Basic data',
|
||||
|
@ -437,6 +441,7 @@ export default {
|
|||
shipped: 'Shipped',
|
||||
warehouse: 'Warehouse',
|
||||
customerCard: 'Customer card',
|
||||
alias: 'Alias',
|
||||
},
|
||||
boxing: {
|
||||
expedition: 'Expedition',
|
||||
|
@ -494,11 +499,9 @@ export default {
|
|||
claims: 'Claims',
|
||||
list: 'List',
|
||||
createClaim: 'Create claim',
|
||||
rmaList: 'RMA',
|
||||
summary: 'Summary',
|
||||
basicData: 'Basic Data',
|
||||
lines: 'Lines',
|
||||
rma: 'RMA',
|
||||
photos: 'Photos',
|
||||
development: 'Development',
|
||||
log: 'Audit logs',
|
||||
|
@ -515,10 +518,6 @@ export default {
|
|||
code: 'Code',
|
||||
records: 'records',
|
||||
},
|
||||
rma: {
|
||||
user: 'User',
|
||||
created: 'Created',
|
||||
},
|
||||
card: {
|
||||
claimId: 'Claim ID',
|
||||
assignedTo: 'Assigned',
|
||||
|
@ -527,6 +526,8 @@ export default {
|
|||
ticketId: 'Ticket ID',
|
||||
customerSummary: 'Customer summary',
|
||||
claimedTicket: 'Claimed ticket',
|
||||
saleTracking: 'Sale tracking',
|
||||
ticketTracking: 'Ticket tracking',
|
||||
commercial: 'Commercial',
|
||||
province: 'Province',
|
||||
zone: 'Zone',
|
||||
|
@ -557,7 +558,6 @@ export default {
|
|||
responsible: 'Responsible',
|
||||
worker: 'Worker',
|
||||
redelivery: 'Redelivery',
|
||||
returnOfMaterial: 'RMA',
|
||||
},
|
||||
basicData: {
|
||||
customer: 'Customer',
|
||||
|
@ -565,7 +565,6 @@ export default {
|
|||
created: 'Created',
|
||||
state: 'State',
|
||||
picked: 'Picked',
|
||||
returnOfMaterial: 'Return of material authorization (RMA)',
|
||||
},
|
||||
photo: {
|
||||
fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}',
|
||||
|
@ -836,6 +835,7 @@ export default {
|
|||
notifications: 'Notifications',
|
||||
workerCreate: 'New worker',
|
||||
department: 'Department',
|
||||
pda: 'PDA',
|
||||
},
|
||||
list: {
|
||||
name: 'Name',
|
||||
|
@ -877,6 +877,13 @@ export default {
|
|||
subscribed: 'Subscribed to the notification',
|
||||
unsubscribed: 'Unsubscribed from the notification',
|
||||
},
|
||||
pda: {
|
||||
newPDA: 'New PDA',
|
||||
currentPDA: 'Current PDA',
|
||||
model: 'Model',
|
||||
serialNumber: 'Serial number',
|
||||
removePDA: 'Deallocate PDA',
|
||||
},
|
||||
create: {
|
||||
name: 'Name',
|
||||
lastName: 'Last name',
|
||||
|
@ -942,6 +949,22 @@ export default {
|
|||
uncompleteTrays: 'There are incomplete trays',
|
||||
},
|
||||
},
|
||||
'route/roadmap': {
|
||||
pageTitles: {
|
||||
roadmap: 'Roadmap',
|
||||
summary: 'Summary',
|
||||
basicData: 'Basic Data',
|
||||
stops: 'Stops'
|
||||
},
|
||||
},
|
||||
roadmap: {
|
||||
pageTitles: {
|
||||
roadmap: 'Roadmap',
|
||||
summary: 'Summary',
|
||||
basicData: 'Basic Data',
|
||||
stops: 'Stops'
|
||||
},
|
||||
},
|
||||
route: {
|
||||
pageTitles: {
|
||||
routes: 'Routes',
|
||||
|
@ -950,6 +973,11 @@ export default {
|
|||
create: 'Create',
|
||||
basicData: 'Basic Data',
|
||||
summary: 'Summary',
|
||||
RouteRoadmap: 'Roadmaps',
|
||||
RouteRoadmapCreate: 'Create roadmap',
|
||||
tickets: 'Tickets',
|
||||
log: 'Log',
|
||||
autonomous: 'Autonomous',
|
||||
},
|
||||
cmr: {
|
||||
list: {
|
||||
|
|
|
@ -31,6 +31,7 @@ export default {
|
|||
close: 'Cerrar',
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Confirmar',
|
||||
assign: 'Asignar',
|
||||
back: 'Volver',
|
||||
yes: 'Si',
|
||||
no: 'No',
|
||||
|
@ -73,6 +74,7 @@ export default {
|
|||
company: 'Empresa',
|
||||
fieldRequired: 'Campo requerido',
|
||||
allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }',
|
||||
smsSent: 'SMS enviado',
|
||||
confirmDeletion: 'Confirmar eliminación',
|
||||
confirmDeletionMessage: '¿Seguro que quieres eliminar?',
|
||||
description: 'Descripción',
|
||||
|
@ -82,6 +84,7 @@ export default {
|
|||
file: 'Fichero',
|
||||
selectFile: 'Seleccione un fichero',
|
||||
copyClipboard: 'Copiar en portapapeles',
|
||||
salesPerson: 'Comercial',
|
||||
},
|
||||
errors: {
|
||||
statusUnauthorized: 'Acceso denegado',
|
||||
|
@ -171,6 +174,7 @@ export default {
|
|||
hasDebt: 'El cliente tiene riesgo',
|
||||
notChecked: 'El cliente no está comprobado',
|
||||
noWebAccess: 'El acceso web está desactivado',
|
||||
businessTypeFk: 'Tipo de negocio',
|
||||
},
|
||||
summary: {
|
||||
basicData: 'Datos básicos',
|
||||
|
@ -307,8 +311,8 @@ export default {
|
|||
reference: 'Referencia',
|
||||
invoiceNumber: 'Núm. factura',
|
||||
ordered: 'Pedida',
|
||||
confirmed: 'Confirmado',
|
||||
booked: 'Asentado',
|
||||
confirmed: 'Confirmada',
|
||||
booked: 'Contabilizada',
|
||||
raid: 'Redada',
|
||||
excludedFromAvailable: 'Inventario',
|
||||
travelReference: 'Referencia',
|
||||
|
@ -436,6 +440,7 @@ export default {
|
|||
shipped: 'Enviado',
|
||||
warehouse: 'Almacén',
|
||||
customerCard: 'Ficha del cliente',
|
||||
alias: 'Alias',
|
||||
},
|
||||
boxing: {
|
||||
expedition: 'Expedición',
|
||||
|
@ -493,14 +498,12 @@ export default {
|
|||
claims: 'Reclamaciones',
|
||||
list: 'Listado',
|
||||
createClaim: 'Crear reclamación',
|
||||
rmaList: 'RMA',
|
||||
summary: 'Resumen',
|
||||
basicData: 'Datos básicos',
|
||||
lines: 'Líneas',
|
||||
rma: 'RMA',
|
||||
development: 'Trazabilidad',
|
||||
photos: 'Fotos',
|
||||
log: 'Registros de auditoría',
|
||||
log: 'Historial',
|
||||
notes: 'Notas',
|
||||
action: 'Acción',
|
||||
},
|
||||
|
@ -514,10 +517,6 @@ export default {
|
|||
code: 'Código',
|
||||
records: 'registros',
|
||||
},
|
||||
rma: {
|
||||
user: 'Usuario',
|
||||
created: 'Creado',
|
||||
},
|
||||
card: {
|
||||
claimId: 'ID reclamación',
|
||||
assignedTo: 'Asignada a',
|
||||
|
@ -526,6 +525,8 @@ export default {
|
|||
ticketId: 'ID ticket',
|
||||
customerSummary: 'Resumen del cliente',
|
||||
claimedTicket: 'Ticket reclamado',
|
||||
saleTracking: 'Líneas preparadas',
|
||||
ticketTracking: 'Estados del ticket',
|
||||
commercial: 'Comercial',
|
||||
province: 'Provincia',
|
||||
zone: 'Zona',
|
||||
|
@ -556,7 +557,6 @@ export default {
|
|||
responsible: 'Responsable',
|
||||
worker: 'Trabajador',
|
||||
redelivery: 'Devolución',
|
||||
returnOfMaterial: 'RMA',
|
||||
},
|
||||
basicData: {
|
||||
customer: 'Cliente',
|
||||
|
@ -564,7 +564,6 @@ export default {
|
|||
created: 'Creada',
|
||||
state: 'Estado',
|
||||
picked: 'Recogida',
|
||||
returnOfMaterial: 'Autorización de retorno de materiales (RMA)',
|
||||
},
|
||||
photo: {
|
||||
fileDescription:
|
||||
|
@ -723,7 +722,7 @@ export default {
|
|||
create: 'Crear',
|
||||
summary: 'Resumen',
|
||||
basicData: 'Datos básicos',
|
||||
log: 'Registros de auditoría',
|
||||
log: 'Historial',
|
||||
},
|
||||
list: {
|
||||
parking: 'Parking',
|
||||
|
@ -755,7 +754,7 @@ export default {
|
|||
dueDay: 'Vencimiento',
|
||||
intrastat: 'Intrastat',
|
||||
corrective: 'Rectificativa',
|
||||
log: 'Registros de auditoría',
|
||||
log: 'Historial',
|
||||
},
|
||||
list: {
|
||||
ref: 'Referencia',
|
||||
|
@ -836,6 +835,7 @@ export default {
|
|||
notifications: 'Notificaciones',
|
||||
workerCreate: 'Nuevo trabajador',
|
||||
department: 'Departamentos',
|
||||
pda: 'PDA',
|
||||
},
|
||||
list: {
|
||||
name: 'Nombre',
|
||||
|
@ -877,6 +877,13 @@ export default {
|
|||
subscribed: 'Se ha suscrito a la notificación',
|
||||
unsubscribed: 'Se ha dado de baja de la notificación',
|
||||
},
|
||||
pda: {
|
||||
newPDA: 'Nueva PDA',
|
||||
currentPDA: 'PDA Actual',
|
||||
model: 'Modelo',
|
||||
serialNumber: 'Número de serie',
|
||||
removePDA: 'Desasignar PDA',
|
||||
},
|
||||
create: {
|
||||
name: 'Nombre',
|
||||
lastName: 'Apellido',
|
||||
|
@ -942,6 +949,22 @@ export default {
|
|||
uncompleteTrays: 'Hay bandejas sin completar',
|
||||
},
|
||||
},
|
||||
'route/roadmap': {
|
||||
pageTitles: {
|
||||
roadmap: 'Troncales',
|
||||
summary: 'Resumen',
|
||||
basicData: 'Datos básicos',
|
||||
stops: 'Paradas'
|
||||
},
|
||||
},
|
||||
roadmap: {
|
||||
pageTitles: {
|
||||
roadmap: 'Troncales',
|
||||
summary: 'Resumen',
|
||||
basicData: 'Datos básicos',
|
||||
stops: 'Paradas'
|
||||
},
|
||||
},
|
||||
route: {
|
||||
pageTitles: {
|
||||
routes: 'Rutas',
|
||||
|
@ -949,7 +972,12 @@ export default {
|
|||
RouteList: 'Listado',
|
||||
create: 'Crear',
|
||||
basicData: 'Datos básicos',
|
||||
summary: 'Summary',
|
||||
summary: 'Resumen',
|
||||
RouteRoadmap: 'Troncales',
|
||||
RouteRoadmapCreate: 'Crear troncal',
|
||||
tickets: 'Tickets',
|
||||
log: 'Historial',
|
||||
autonomous: 'Autónomos',
|
||||
},
|
||||
cmr: {
|
||||
list: {
|
||||
|
|
|
@ -40,7 +40,7 @@ const langs = ['en', 'es'];
|
|||
|
||||
<template>
|
||||
<QLayout view="hHh LpR fFf">
|
||||
<QHeader reveal class="bg-dark">
|
||||
<QHeader reveal class="bg-vn-dark">
|
||||
<QToolbar class="justify-end">
|
||||
<QBtn
|
||||
id="switchLanguage"
|
||||
|
|
|
@ -24,7 +24,6 @@ const claimFilter = {
|
|||
'workerFk',
|
||||
'claimStateFk',
|
||||
'packages',
|
||||
'rma',
|
||||
'hasToPickUp',
|
||||
],
|
||||
include: [
|
||||
|
@ -169,13 +168,6 @@ const statesFilter = {
|
|||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.rma"
|
||||
:label="t('claim.basicData.returnOfMaterial')"
|
||||
:rules="validate('claim.rma')"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useStateStore } from 'stores/useStateStore';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import ClaimDescriptor from './ClaimDescriptor.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
|
@ -28,7 +29,9 @@ const { t } = useI18n();
|
|||
<QPageContainer>
|
||||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
<div :class="useCardSize()">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -105,7 +105,6 @@ onMounted(async () => {
|
|||
<ClaimDescriptorMenu :claim="entity" />
|
||||
</template>
|
||||
<template #body="{ entity }">
|
||||
<VnLv :label="t('claim.card.created')" :value="toDate(entity.created)" />
|
||||
<VnLv v-if="entity.claimState" :label="t('claim.card.state')">
|
||||
<template #value>
|
||||
<QBadge :color="stateColor(entity.claimState.code)" dense>
|
||||
|
@ -113,13 +112,13 @@ onMounted(async () => {
|
|||
</QBadge>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('claim.card.ticketId')">
|
||||
<VnLv :label="t('claim.card.created')" :value="toDate(entity.created)" />
|
||||
<VnLv :label="t('claim.card.commercial')">
|
||||
<template #value>
|
||||
<span class="link">
|
||||
{{ entity.ticketFk }}
|
||||
|
||||
<TicketDescriptorProxy :id="entity.ticketFk" />
|
||||
</span>
|
||||
<VnUserLink
|
||||
:name="entity.client?.salesPersonUser?.name"
|
||||
:worker-id="entity.client?.salesPersonFk"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
|
@ -134,19 +133,20 @@ onMounted(async () => {
|
|||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('claim.card.commercial')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="entity.client?.salesPersonUser?.name"
|
||||
:worker-id="entity.client?.salesPersonFk"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('claim.card.zone')" :value="entity.ticket?.zone?.name" />
|
||||
<VnLv
|
||||
:label="t('claim.card.province')"
|
||||
:value="entity.ticket?.address?.province?.name"
|
||||
/>
|
||||
<VnLv :label="t('claim.card.zone')" :value="entity.ticket?.zone?.name" />
|
||||
<VnLv :label="t('claim.card.ticketId')">
|
||||
<template #value>
|
||||
<span class="link">
|
||||
{{ entity.ticketFk }}
|
||||
|
||||
<TicketDescriptorProxy :id="entity.ticketFk" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('claimRate')"
|
||||
:value="toPercentage(entity.client?.claimsRatio?.claimingRate)"
|
||||
|
@ -176,6 +176,7 @@ onMounted(async () => {
|
|||
color="primary"
|
||||
:href="salixUrl + 'ticket/' + entity.ticketFk + '/sale-tracking'"
|
||||
>
|
||||
<QTooltip>{{ t('claim.card.saleTracking') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
size="md"
|
||||
|
@ -183,6 +184,7 @@ onMounted(async () => {
|
|||
color="primary"
|
||||
:href="salixUrl + 'ticket/' + entity.ticketFk + '/tracking/index'"
|
||||
>
|
||||
<QTooltip>{{ t('claim.card.ticketTracking') }}</QTooltip>
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</template>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import VnNotes from 'src/components/ui/VnNotes.vue';
|
||||
|
@ -6,14 +7,15 @@ import VnNotes from 'src/components/ui/VnNotes.vue';
|
|||
const route = useRoute();
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
const id = route.params.id;
|
||||
|
||||
const $props = defineProps({
|
||||
id: { type: Number, default: null },
|
||||
addNote: { type: Boolean, default: true },
|
||||
});
|
||||
const claimId = computed(() => $props.id || route.params.id);
|
||||
|
||||
const claimFilter = {
|
||||
where: { claimFk: id },
|
||||
where: { claimFk: claimId.value },
|
||||
fields: ['created', 'workerFk', 'text'],
|
||||
include: {
|
||||
relation: 'worker',
|
||||
|
@ -30,19 +32,16 @@ const claimFilter = {
|
|||
};
|
||||
|
||||
const body = {
|
||||
claimFk: id,
|
||||
claimFk: claimId.value,
|
||||
workerFk: user.value.id,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center">
|
||||
<VnNotes
|
||||
style="overflow-y: scroll"
|
||||
:add-note="$props.addNote"
|
||||
:id="id"
|
||||
url="claimObservations"
|
||||
:filter="claimFilter"
|
||||
:body="body"
|
||||
/>
|
||||
</div>
|
||||
<VnNotes
|
||||
style="overflow-y: auto"
|
||||
:add-note="$props.addNote"
|
||||
url="claimObservations"
|
||||
:filter="claimFilter"
|
||||
:body="body"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { watch, ref, computed, onUnmounted, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
import { toDate } from 'src/filters';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const state = useState();
|
||||
const { t } = useI18n();
|
||||
const selected = ref([]);
|
||||
const claimRmaRef = ref();
|
||||
const claim = computed(() => state.get('ClaimDescriptor'));
|
||||
|
||||
const claimRmaFilter = {
|
||||
include: {
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
include: {
|
||||
relation: 'user',
|
||||
},
|
||||
},
|
||||
},
|
||||
order: 'created DESC',
|
||||
where: {
|
||||
code: claim.value?.rma,
|
||||
},
|
||||
};
|
||||
|
||||
async function addRow() {
|
||||
if (!claim.value.rma) {
|
||||
return quasar.notify({
|
||||
message: `This claim is not associated to any RMA`,
|
||||
type: 'negative',
|
||||
});
|
||||
}
|
||||
const formData = {
|
||||
code: claim.value.rma,
|
||||
};
|
||||
|
||||
await axios.post(`ClaimRmas`, formData);
|
||||
await claimRmaRef.value.reload();
|
||||
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
message: t('globals.rowAdded'),
|
||||
icon: 'check',
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (claim.value) claimRmaRef.value.reload();
|
||||
});
|
||||
watch(
|
||||
claim,
|
||||
() => {
|
||||
claimRmaRef.value.reload();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center">
|
||||
<div class="list">
|
||||
<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>
|
||||
<template v-for="(row, index) of rows" :key="row.id">
|
||||
<QItem class="q-pa-none items-start">
|
||||
<QItemSection class="q-pa-md">
|
||||
<QList>
|
||||
<QItem class="q-pa-none">
|
||||
<QItemSection>
|
||||
<QItemLabel caption>
|
||||
{{ t('claim.rma.user') }}
|
||||
</QItemLabel>
|
||||
<QItemLabel>
|
||||
{{ row?.worker?.user?.name }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-pa-none">
|
||||
<QItemSection>
|
||||
<QItemLabel caption>
|
||||
{{ t('claim.rma.created') }}
|
||||
</QItemLabel>
|
||||
<QItemLabel>
|
||||
{{
|
||||
toDate(row.created, {
|
||||
timeStyle: 'medium',
|
||||
})
|
||||
}}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QItemSection>
|
||||
<QCardActions vertical class="justify-between">
|
||||
<QBtn
|
||||
flat
|
||||
round
|
||||
color="orange"
|
||||
icon="vn:bin"
|
||||
@click="claimRmaRef.remove([row])"
|
||||
>
|
||||
<QTooltip>{{ t('globals.remove') }}</QTooltip>
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QItem>
|
||||
<QSeparator v-if="index !== rows.length - 1" />
|
||||
</template>
|
||||
</QCard>
|
||||
</template>
|
||||
</CrudModel>
|
||||
</div>
|
||||
</div>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||
<QBtn fab color="primary" icon="add" @click="addRow()" />
|
||||
</QPageSticky>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
width: 100%;
|
||||
max-width: 60em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
This claim is not associated to any RMA: Esta reclamación no está asociada a ninguna ARM
|
||||
</i18n>
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { toDate, toCurrency } from 'src/filters';
|
||||
|
@ -70,7 +70,7 @@ const detailsColumns = ref([
|
|||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'claim.summary.description',
|
||||
label: 'globals.description',
|
||||
field: (row) => row.sale.concept,
|
||||
},
|
||||
{
|
||||
|
@ -172,6 +172,7 @@ function openDialog(dmsId) {
|
|||
<CardSummary
|
||||
ref="summary"
|
||||
:url="`Claims/${entityId}/getSummary`"
|
||||
:entity-id="entityId"
|
||||
@on-fetch="getClaimDms"
|
||||
>
|
||||
<template #header="{ entity: { claim } }">
|
||||
|
@ -179,9 +180,9 @@ function openDialog(dmsId) {
|
|||
</template>
|
||||
<template #body="{ entity: { claim, salesClaimed, developments } }">
|
||||
<QCard class="vn-one">
|
||||
<a class="header" :href="`#/claim/${entityId}/basic-data`">
|
||||
<a class="header header-link" :href="`#/claim/${entityId}/basic-data`">
|
||||
{{ t('claim.pageTitles.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv
|
||||
:label="t('claim.summary.created')"
|
||||
|
@ -194,19 +195,19 @@ function openDialog(dmsId) {
|
|||
</QChip>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('claim.summary.assignedTo')">
|
||||
<VnLv :label="t('globals.salesPerson')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="claim.worker?.user?.nickname"
|
||||
:worker-id="claim.workerFk"
|
||||
:name="claim.client?.salesPersonUser?.name"
|
||||
:worker-id="claim.client?.salesPersonFk"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('claim.summary.attendedBy')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="claim.client?.salesPersonUser?.name"
|
||||
:worker-id="claim.client?.salesPersonFk"
|
||||
:name="claim.worker?.user?.nickname"
|
||||
:worker-id="claim.workerFk"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
|
@ -218,26 +219,37 @@ function openDialog(dmsId) {
|
|||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('claim.summary.returnOfMaterial')" :value="claim.rma" />
|
||||
<QCheckbox
|
||||
:align-items="right"
|
||||
:label="t('claim.basicData.picked')"
|
||||
v-model="claim.hasToPickUp"
|
||||
:disable="true"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-three claimVnNotes full-height">
|
||||
<a class="header" :href="`#/claim/${entityId}/notes`">
|
||||
<QCard class="vn-three">
|
||||
<a class="header header-link" :href="`#/claim/${entityId}/notes`">
|
||||
{{ t('claim.summary.notes') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<ClaimNotes :add-note="false" style="height: 350px" order="created ASC" />
|
||||
<ClaimNotes
|
||||
:id="entityId"
|
||||
:add-note="false"
|
||||
style="max-height: 300px"
|
||||
order="created ASC"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-two" v-if="salesClaimed.length > 0">
|
||||
<a class="header" :href="`#/claim/${entityId}/lines`">
|
||||
<a class="header header-link" :href="`#/claim/${entityId}/lines`">
|
||||
{{ t('claim.summary.details') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable :columns="detailsColumns" :rows="salesClaimed" flat>
|
||||
<QTable
|
||||
:columns="detailsColumns"
|
||||
:rows="salesClaimed"
|
||||
flat
|
||||
dense
|
||||
:rows-per-page-options="[0]"
|
||||
hide-bottom
|
||||
>
|
||||
<template #header="props">
|
||||
<QTr :props="props">
|
||||
<QTh v-for="col in props.cols" :key="col.name" :props="props">
|
||||
|
@ -268,11 +280,19 @@ function openDialog(dmsId) {
|
|||
</QTable>
|
||||
</QCard>
|
||||
<QCard class="vn-two" v-if="developments.length > 0">
|
||||
<a class="header" :href="claimUrl + 'development'">
|
||||
<a class="header header-link" :href="claimUrl + 'development'">
|
||||
{{ t('claim.summary.development') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable :columns="developmentColumns" :rows="developments" flat>
|
||||
|
||||
<QTable
|
||||
:columns="developmentColumns"
|
||||
:rows="developments"
|
||||
flat
|
||||
dense
|
||||
:rows-per-page-options="[0]"
|
||||
hide-bottom
|
||||
>
|
||||
<template #header="props">
|
||||
<QTr :props="props">
|
||||
<QTh v-for="col in props.cols" :key="col.name" :props="props">
|
||||
|
@ -283,9 +303,9 @@ function openDialog(dmsId) {
|
|||
</QTable>
|
||||
</QCard>
|
||||
<QCard class="vn-max" v-if="claimDms.length > 0">
|
||||
<a class="header" :href="`#/claim/${entityId}/photos`">
|
||||
<a class="header header-link" :href="`#/claim/${entityId}/photos`">
|
||||
{{ t('claim.summary.photos') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<div class="container">
|
||||
<div
|
||||
|
@ -302,7 +322,7 @@ function openDialog(dmsId) {
|
|||
v-if="media.isVideo"
|
||||
@click.stop="openDialog(media.dmsFk)"
|
||||
>
|
||||
<QTooltip>Video</QTooltip>
|
||||
<QTooltip>Video</QTooltip>header
|
||||
</QIcon>
|
||||
<QCard class="multimedia relative-position">
|
||||
<QImg
|
||||
|
@ -326,9 +346,9 @@ function openDialog(dmsId) {
|
|||
</QCard>
|
||||
|
||||
<QCard class="vn-max">
|
||||
<a class="header" :href="claimUrl + 'action'">
|
||||
<a class="header header-link" :href="claimUrl + 'action'">
|
||||
{{ t('claim.summary.actions') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" class="link" />
|
||||
</a>
|
||||
<div id="slider-container" class="q-px-xl q-py-md">
|
||||
<QSlider
|
||||
|
@ -336,7 +356,7 @@ function openDialog(dmsId) {
|
|||
label
|
||||
:label-value="t('claim.summary.responsibility')"
|
||||
label-always
|
||||
color="primary"
|
||||
color="var()"
|
||||
markers
|
||||
:marker-labels="[
|
||||
{ value: 1, label: t('claim.summary.company') },
|
||||
|
@ -390,13 +410,7 @@ function openDialog(dmsId) {
|
|||
</template>
|
||||
</CardSummary>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.claimVnNotes {
|
||||
.q-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-dialog__inner--minimized > div {
|
||||
max-width: 80%;
|
||||
|
@ -406,7 +420,6 @@ function openDialog(dmsId) {
|
|||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
flex-basis: 30%;
|
||||
}
|
||||
.multimedia-container {
|
||||
flex: 1 0 21%;
|
||||
|
|
|
@ -115,12 +115,6 @@ function navigate(event, id) {
|
|||
</VnLv>
|
||||
</template>
|
||||
<template #actions>
|
||||
<QBtn
|
||||
:label="t('components.smartCard.openCard')"
|
||||
@click.stop="navigate(row.id)"
|
||||
class="bg-vn-dark"
|
||||
outline
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.description')"
|
||||
@click.stop
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import axios from 'axios';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
||||
const arrayData = useArrayData('ClaimRmaList');
|
||||
const isLoading = ref(false);
|
||||
const input = ref();
|
||||
|
||||
const newRma = ref({
|
||||
code: '',
|
||||
crated: Date.vnNew(),
|
||||
});
|
||||
|
||||
function onInputUpdate(value) {
|
||||
newRma.value.code = value.toUpperCase();
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
const formData = newRma.value;
|
||||
if (formData.code === '') return;
|
||||
|
||||
isLoading.value = true;
|
||||
await axios.post('ClaimRmas', formData);
|
||||
await arrayData.refresh();
|
||||
isLoading.value = false;
|
||||
input.value.$el.focus();
|
||||
|
||||
newRma.value = {
|
||||
code: '',
|
||||
created: Date.vnNew(),
|
||||
};
|
||||
}
|
||||
|
||||
function confirm(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'),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QPage class="column items-center q-pa-md sticky">
|
||||
<QPageSticky expand position="top" :offset="[16, 16]">
|
||||
<QCard class="card q-pa-md">
|
||||
<QForm @submit="submit">
|
||||
<VnInput
|
||||
ref="input"
|
||||
v-model="newRma.code"
|
||||
:label="t('claim.rmaList.code')"
|
||||
@update:model-value="onInputUpdate"
|
||||
class="q-mb-md"
|
||||
:readonly="isLoading"
|
||||
:loading="isLoading"
|
||||
autofocus
|
||||
/>
|
||||
<div class="text-caption">
|
||||
{{ arrayData.totalRows }} {{ t('claim.rmaList.records') }}
|
||||
</div>
|
||||
</QForm>
|
||||
</QCard>
|
||||
</QPageSticky>
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
data-key="ClaimRmaList"
|
||||
url="ClaimRmas"
|
||||
order="id DESC"
|
||||
:offset="50"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard class="card">
|
||||
<template v-if="isLoading">
|
||||
<QItem class="q-pa-none items-start">
|
||||
<QItemSection class="q-pa-md">
|
||||
<QList>
|
||||
<QItem class="q-pa-none">
|
||||
<QItemSection>
|
||||
<QItemLabel caption>
|
||||
<QSkeleton />
|
||||
</QItemLabel>
|
||||
<QItemLabel>
|
||||
<QSkeleton type="text" />
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QItemSection>
|
||||
<QCardActions vertical class="justify-between">
|
||||
<QSkeleton
|
||||
type="circle"
|
||||
class="q-mb-md"
|
||||
size="40px"
|
||||
/>
|
||||
</QCardActions>
|
||||
</QItem>
|
||||
<QSeparator />
|
||||
</template>
|
||||
<template v-for="row of rows" :key="row.id">
|
||||
<QItem class="q-pa-none items-start">
|
||||
<QItemSection class="q-pa-md">
|
||||
<QList>
|
||||
<QItem class="q-pa-none">
|
||||
<QItemSection>
|
||||
<QItemLabel caption>{{
|
||||
t('claim.rmaList.code')
|
||||
}}</QItemLabel>
|
||||
<QItemLabel>{{ row.code }}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QItemSection>
|
||||
<QCardActions vertical class="justify-between">
|
||||
<QBtn
|
||||
flat
|
||||
round
|
||||
color="primary"
|
||||
icon="vn:bin"
|
||||
@click="confirm(row.id)"
|
||||
>
|
||||
<QTooltip>{{ t('globals.remove') }}</QTooltip>
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QItem>
|
||||
<QSeparator />
|
||||
</template>
|
||||
</QCard>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sticky {
|
||||
padding-top: 156px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 60em;
|
||||
}
|
||||
|
||||
.q-page-sticky {
|
||||
z-index: 2998;
|
||||
}
|
||||
</style>
|
|
@ -6,6 +6,7 @@ import CustomerDescriptor from './CustomerDescriptor.vue';
|
|||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
|
@ -30,7 +31,9 @@ const { t } = useI18n();
|
|||
<QPageContainer>
|
||||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
<div :class="useCardSize()">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -40,6 +40,15 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
|
|||
data-key="customerData"
|
||||
>
|
||||
<template #body="{ entity }">
|
||||
<VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
|
||||
|
||||
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
|
||||
<VnLv
|
||||
:label="t('customer.card.securedCredit')"
|
||||
:value="toCurrency(entity.creditInsurance)"
|
||||
/>
|
||||
|
||||
<VnLv :label="t('customer.card.debt')" :value="toCurrency(entity.debt)" />
|
||||
<VnLv v-if="entity.salesPersonUser" :label="t('customer.card.salesPerson')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
|
@ -48,13 +57,10 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
|
|||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
|
||||
<VnLv
|
||||
:label="t('customer.card.securedCredit')"
|
||||
:value="toCurrency(entity.creditInsurance)"
|
||||
:label="t('customer.card.businessTypeFk')"
|
||||
:value="entity.businessTypeFk"
|
||||
/>
|
||||
<VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
|
||||
<VnLv :label="t('customer.card.debt')" :value="toCurrency(entity.debt)" />
|
||||
</template>
|
||||
<template #icons="{ entity }">
|
||||
<QCardActions>
|
||||
|
|
|
@ -62,9 +62,9 @@ const creditWarning = computed(() => {
|
|||
<CardSummary ref="summary" :url="`Clients/${entityId}/summary`">
|
||||
<template #body="{ entity }">
|
||||
<QCard class="vn-one">
|
||||
<a class="header" :href="clientUrl + `basic-data`">
|
||||
<a class="header header-link" :href="`#/customer/${entityId}/basic-data`">
|
||||
{{ t('customer.summary.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv :label="t('customer.summary.customerId')" :value="entity.id" />
|
||||
<VnLv :label="t('customer.summary.name')" :value="entity.name" />
|
||||
|
@ -96,9 +96,12 @@ const creditWarning = computed(() => {
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<a class="header" :href="clientUrl + `fiscal-data`">
|
||||
<a
|
||||
class="header header-link"
|
||||
:href="`#/customer/${entityId}/fiscal-data`"
|
||||
>
|
||||
{{ t('customer.summary.fiscalAddress') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv
|
||||
:label="t('customer.summary.socialName')"
|
||||
|
@ -121,37 +124,58 @@ const creditWarning = computed(() => {
|
|||
<VnLv :label="t('customer.summary.street')" :value="entity.street" />
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<a class="header link" :href="clientUrl + `fiscal-data`" link>
|
||||
<a
|
||||
class="header header-link"
|
||||
:href="`#/customer/${entityId}/fiscal-data`"
|
||||
link
|
||||
>
|
||||
{{ t('customer.summary.fiscalData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.isEqualizated')"
|
||||
:value="entity.isEqualizated"
|
||||
v-model="entity.isEqualizated"
|
||||
:disable="true"
|
||||
/>
|
||||
<VnLv :label="t('customer.summary.isActive')" :value="entity.isActive" />
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.isActive')"
|
||||
v-model="entity.isActive"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.invoiceByAddress')"
|
||||
:value="entity.hasToInvoiceByAddress"
|
||||
v-model="entity.hasToInvoiceByAddress"
|
||||
:disable="true"
|
||||
/>
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.verifiedData')"
|
||||
:value="entity.isTaxDataChecked"
|
||||
v-model="entity.isTaxDataChecked"
|
||||
:disable="true"
|
||||
/>
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.hasToInvoice')"
|
||||
:value="entity.hasToInvoice"
|
||||
v-model="entity.hasToInvoice"
|
||||
:disable="true"
|
||||
/>
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.notifyByEmail')"
|
||||
:value="entity.isToBeMailed"
|
||||
v-model="entity.isToBeMailed"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.vies')"
|
||||
v-model="entity.isVies"
|
||||
:disable="true"
|
||||
/>
|
||||
<VnLv :label="t('customer.summary.vies')" :value="entity.isVies" />
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<a class="header link" :href="clientUrl + `billing-data`" link>
|
||||
<a
|
||||
class="header header-link"
|
||||
:href="`#/customer/${entityId}/billing-data`"
|
||||
link
|
||||
>
|
||||
{{ t('customer.summary.billingData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv
|
||||
:label="t('customer.summary.payMethod')"
|
||||
|
@ -159,20 +183,32 @@ const creditWarning = computed(() => {
|
|||
/>
|
||||
<VnLv :label="t('customer.summary.bankAccount')" :value="entity.iban" />
|
||||
<VnLv :label="t('customer.summary.dueDay')" :value="entity.dueDay" />
|
||||
<VnLv :label="t('customer.summary.hasLcr')" :value="entity.hasLcr" />
|
||||
<VnLv
|
||||
:label="t('customer.summary.hasCoreVnl')"
|
||||
:value="entity.hasCoreVnl"
|
||||
<QCheckbox
|
||||
style="padding: 0"
|
||||
:label="t('customer.summary.hasLcr')"
|
||||
v-model="entity.hasLcr"
|
||||
:disable="true"
|
||||
/>
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.hasCoreVnl')"
|
||||
v-model="entity.hasCoreVnl"
|
||||
:disable="true"
|
||||
/>
|
||||
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.hasB2BVnl')"
|
||||
:value="entity.hasSepaVnl"
|
||||
v-model="entity.hasSepaVnl"
|
||||
:disable="true"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one" v-if="entity.defaultAddress">
|
||||
<a class="header link" :href="clientUrl + `address/index`" link>
|
||||
<a
|
||||
class="header header-link"
|
||||
:href="`#/customer/${entityId}/consignees`"
|
||||
link
|
||||
>
|
||||
{{ t('customer.summary.consignee') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv
|
||||
:label="t('customer.summary.addressName')"
|
||||
|
@ -188,21 +224,22 @@ const creditWarning = computed(() => {
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one" v-if="entity.account">
|
||||
<a class="header link" :href="clientUrl + `web-access`">
|
||||
<a class="header header-link" :href="`#/customer/${entityId}/web-access`">
|
||||
{{ t('customer.summary.webAccess') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv
|
||||
:label="t('customer.summary.username')"
|
||||
:value="entity.account.name"
|
||||
/>
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('customer.summary.webAccess')"
|
||||
:value="entity.account.active"
|
||||
v-model="entity.account.active"
|
||||
:disable="true"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one" v-if="entity.account">
|
||||
<div class="header">
|
||||
<div class="header header-link">
|
||||
{{ t('customer.summary.businessData') }}
|
||||
</div>
|
||||
<VnLv
|
||||
|
@ -230,13 +267,12 @@ const creditWarning = computed(() => {
|
|||
</QCard>
|
||||
<QCard class="vn-one" v-if="entity.account">
|
||||
<a
|
||||
class="header link"
|
||||
class="header header-link"
|
||||
:href="`https://grafana.verdnatura.es/d/40buzE4Vk/comportamiento-pagos-clientes?orgId=1&var-clientFk=${entityId}`"
|
||||
link
|
||||
>
|
||||
{{ t('customer.summary.financialData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<!-- Pendiente de añadir el icono <QIcon name="vn:grafana" color="primary" /> -->
|
||||
<QIcon name="vn:grafana" />
|
||||
</a>
|
||||
<VnLv
|
||||
:label="t('customer.summary.risk')"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -12,10 +12,6 @@ const props = defineProps({
|
|||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
function isValidNumber(value) {
|
||||
return /^(\d|\d+(\.|,)?\d+)$/.test(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -51,28 +47,9 @@ function isValidNumber(value) {
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('Amount')"
|
||||
v-model="params.amount"
|
||||
is-outlined
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
if (value.includes(','))
|
||||
params.amount = params.amount.replace(',', '.');
|
||||
}
|
||||
"
|
||||
:rules="[
|
||||
(val) => isValidNumber(val) || !val || 'Please type a number',
|
||||
]"
|
||||
lazy-rules
|
||||
>
|
||||
<template #prepend>
|
||||
<QIcon name="euro" size="sm" />
|
||||
</template>
|
||||
</VnInput>
|
||||
<VnCurrency v-model="params.amount" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInputDate v-model="params.from" :label="t('From')" is-outlined />
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import VnLocation from 'src/components/common/VnLocation.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
|
@ -20,13 +20,9 @@ const router = useRouter();
|
|||
|
||||
const formInitialData = reactive({ isDefaultAddress: false });
|
||||
|
||||
const townsFetchDataRef = ref(null);
|
||||
const postcodeFetchDataRef = ref(null);
|
||||
const urlCreate = ref('');
|
||||
|
||||
const postcodesOptions = ref([]);
|
||||
const citiesLocationOptions = ref([]);
|
||||
const provincesLocationOptions = ref([]);
|
||||
const agencyModes = ref([]);
|
||||
const incoterms = ref([]);
|
||||
const customsAgents = ref([]);
|
||||
|
@ -36,14 +32,6 @@ onBeforeMount(() => {
|
|||
getCustomsAgents();
|
||||
});
|
||||
|
||||
const onPostcodeCreated = async ({ code, provinceFk, townFk }, formData) => {
|
||||
await postcodeFetchDataRef.value.fetch();
|
||||
await townsFetchDataRef.value.fetch();
|
||||
formData.postalCode = code;
|
||||
formData.provinceFk = provinceFk;
|
||||
formData.city = citiesLocationOptions.value.find((town) => town.id === townFk).name;
|
||||
};
|
||||
|
||||
const getCustomsAgents = async () => {
|
||||
const { data } = await axios.get('CustomsAgents');
|
||||
customsAgents.value = data;
|
||||
|
@ -61,26 +49,16 @@ const toCustomerConsignees = () => {
|
|||
},
|
||||
});
|
||||
};
|
||||
function handleLocation(data, location) {
|
||||
const { town, code, provinceFk, countryFk } = location ?? {};
|
||||
data.postalCode = code;
|
||||
data.city = town;
|
||||
data.provinceFk = provinceFk;
|
||||
data.countryFk = countryFk;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (postcodesOptions = data)"
|
||||
auto-load
|
||||
ref="postcodeFetchDataRef"
|
||||
url="Postcodes/location"
|
||||
/>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (citiesLocationOptions = data)"
|
||||
auto-load
|
||||
ref="townsFetchDataRef"
|
||||
url="Towns/location"
|
||||
/>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (provincesLocationOptions = data)"
|
||||
auto-load
|
||||
url="Provinces/location"
|
||||
/>
|
||||
<fetch-data
|
||||
@on-fetch="(data) => (agencyModes = data)"
|
||||
auto-load
|
||||
|
@ -113,83 +91,17 @@ const toCustomerConsignees = () => {
|
|||
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectDialog
|
||||
:label="t('Postcode')"
|
||||
:options="postcodesOptions"
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
<VnLocation
|
||||
:rules="validate('Worker.postcode')"
|
||||
hide-selected
|
||||
option-label="code"
|
||||
option-value="code"
|
||||
v-model="data.postalCode"
|
||||
>
|
||||
<template #form>
|
||||
<CustomerCreateNewPostcode
|
||||
@on-data-saved="onPostcodeCreated($event, data)"
|
||||
/>
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection v-if="scope.opt">
|
||||
<QItemLabel>{{ scope.opt.code }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ scope.opt.code }} -
|
||||
{{ scope.opt.town.name }}
|
||||
({{ scope.opt.town.province.name }},
|
||||
{{ scope.opt.town.province.country.country }})
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</div>
|
||||
<div class="col">
|
||||
<!-- ciudades -->
|
||||
<VnSelectFilter
|
||||
:label="t('City')"
|
||||
:options="citiesLocationOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="name"
|
||||
v-model="data.city"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{
|
||||
`${scope.opt.name}, ${scope.opt.province.name} (${scope.opt.province.country.country})`
|
||||
}}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
:options="postcodesOptions"
|
||||
v-model="data.location"
|
||||
@update:model-value="(location) => handleLocation(data, location)"
|
||||
></VnLocation>
|
||||
</div>
|
||||
</VnRow>
|
||||
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Province')"
|
||||
:options="provincesLocationOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.provinceFk"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{
|
||||
`${scope.opt.name} (${scope.opt.country.country})`
|
||||
}}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Agency')"
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useRoute } from 'vue-router';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import VnLocation from 'src/components/common/VnLocation.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
|
@ -17,13 +17,8 @@ import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCus
|
|||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const townsFetchDataRef = ref(null);
|
||||
const postcodeFetchDataRef = ref(null);
|
||||
const urlUpdate = ref('');
|
||||
|
||||
const postcodesOptions = ref([]);
|
||||
const citiesLocationOptions = ref([]);
|
||||
const provincesLocationOptions = ref([]);
|
||||
const agencyModes = ref([]);
|
||||
const incoterms = ref([]);
|
||||
const customsAgents = ref([]);
|
||||
|
@ -34,14 +29,6 @@ onBeforeMount(() => {
|
|||
urlUpdate.value = `Clients/${route.params.id}/updateAddress/${route.params.consigneeId}`;
|
||||
});
|
||||
|
||||
const onPostcodeCreated = async ({ code, provinceFk, townFk }, formData) => {
|
||||
await postcodeFetchDataRef.value.fetch();
|
||||
await townsFetchDataRef.value.fetch();
|
||||
formData.postalCode = code;
|
||||
formData.provinceFk = provinceFk;
|
||||
formData.city = citiesLocationOptions.value.find((town) => town.id === townFk).name;
|
||||
};
|
||||
|
||||
const getData = async (observations) => {
|
||||
observationTypes.value = observations;
|
||||
|
||||
|
@ -97,26 +84,16 @@ const onDataSaved = () => {
|
|||
};
|
||||
axios.post('AddressObservations/crud', payload);
|
||||
};
|
||||
function handleLocation(data, location) {
|
||||
const { town, code, provinceFk, countryFk } = location ?? {};
|
||||
data.postalCode = code;
|
||||
data.city = town;
|
||||
data.provinceFk = provinceFk;
|
||||
data.countryFk = countryFk;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="postcodeFetchDataRef"
|
||||
@on-fetch="(data) => (postcodesOptions = data)"
|
||||
auto-load
|
||||
url="Postcodes/location"
|
||||
/>
|
||||
<FetchData
|
||||
ref="townsFetchDataRef"
|
||||
@on-fetch="(data) => (citiesLocationOptions = data)"
|
||||
auto-load
|
||||
url="Towns/location"
|
||||
/>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (provincesLocationOptions = data)"
|
||||
auto-load
|
||||
url="Provinces/location"
|
||||
/>
|
||||
<fetch-data
|
||||
@on-fetch="(data) => (agencyModes = data)"
|
||||
auto-load
|
||||
|
@ -168,83 +145,17 @@ const onDataSaved = () => {
|
|||
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectDialog
|
||||
:label="t('Postcode')"
|
||||
:options="postcodesOptions"
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
<VnLocation
|
||||
:rules="validate('Worker.postcode')"
|
||||
hide-selected
|
||||
option-label="code"
|
||||
option-value="code"
|
||||
v-model="data.postalCode"
|
||||
>
|
||||
<template #form>
|
||||
<CustomerCreateNewPostcode
|
||||
@on-data-saved="onPostcodeCreated($event, data)"
|
||||
/>
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection v-if="scope.opt">
|
||||
<QItemLabel>{{ scope.opt.code }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ scope.opt.code }} -
|
||||
{{ scope.opt.town.name }}
|
||||
({{ scope.opt.town.province.name }},
|
||||
{{ scope.opt.town.province.country.country }})
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</div>
|
||||
<div class="col">
|
||||
<!-- ciudades -->
|
||||
<VnSelectFilter
|
||||
:label="t('City')"
|
||||
:options="citiesLocationOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="name"
|
||||
v-model="data.city"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{
|
||||
`${scope.opt.name}, ${scope.opt.province.name} (${scope.opt.province.country.country})`
|
||||
}}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
:options="postcodesOptions"
|
||||
v-model="data.location"
|
||||
@update:model-value="(location) => handleLocation(data, location)"
|
||||
></VnLocation>
|
||||
</div>
|
||||
</VnRow>
|
||||
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Province')"
|
||||
:options="provincesLocationOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.provinceFk"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{
|
||||
`${scope.opt.name} (${scope.opt.country.country})`
|
||||
}}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Agency')"
|
||||
|
|
|
@ -112,7 +112,7 @@ const onDataSaved = async () => {
|
|||
:filter="filterBanks"
|
||||
@on-fetch="(data) => (bankOptions = data)"
|
||||
auto-load
|
||||
url="Banks"
|
||||
url="Accountings"
|
||||
/>
|
||||
<fetch-data
|
||||
:filter="filterClientFindOne"
|
||||
|
|
|
@ -36,9 +36,12 @@ onMounted(async () => {
|
|||
</template>
|
||||
<template #body="{ entity: department }">
|
||||
<QCard class="column">
|
||||
<a class="header" :href="department + `basic-data`">
|
||||
<a
|
||||
class="header header-link"
|
||||
:href="`#/department/department/${entityId}/basic-data`"
|
||||
>
|
||||
{{ t('Basic data') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<div class="full-width row wrap justify-between content-between">
|
||||
<div class="column" style="min-width: 50%">
|
||||
|
|
|
@ -61,6 +61,7 @@ const onFilterTravelSelected = (formData, id) => {
|
|||
:url-update="`Entries/${route.params.id}`"
|
||||
model="entry"
|
||||
auto-load
|
||||
:clear-store-on-unmount="false"
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
|
|
|
@ -43,7 +43,7 @@ const tableColumnComponents = computed(() => ({
|
|||
item: {
|
||||
component: QBtn,
|
||||
props: {
|
||||
color: 'blue',
|
||||
color: 'primary',
|
||||
flat: true,
|
||||
},
|
||||
event: () => ({}),
|
||||
|
@ -54,6 +54,7 @@ const tableColumnComponents = computed(() => ({
|
|||
type: 'number',
|
||||
min: 0,
|
||||
class: 'input-number',
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -67,6 +68,7 @@ const tableColumnComponents = computed(() => ({
|
|||
'use-input': true,
|
||||
'hide-selected': true,
|
||||
options: packagingsOptions.value,
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -76,6 +78,7 @@ const tableColumnComponents = computed(() => ({
|
|||
type: 'number',
|
||||
min: 0,
|
||||
class: 'input-number',
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -84,6 +87,7 @@ const tableColumnComponents = computed(() => ({
|
|||
props: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -92,6 +96,7 @@ const tableColumnComponents = computed(() => ({
|
|||
props: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -100,6 +105,7 @@ const tableColumnComponents = computed(() => ({
|
|||
props: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -108,6 +114,7 @@ const tableColumnComponents = computed(() => ({
|
|||
props: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -116,6 +123,7 @@ const tableColumnComponents = computed(() => ({
|
|||
props: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -124,6 +132,7 @@ const tableColumnComponents = computed(() => ({
|
|||
props: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
dense: true,
|
||||
},
|
||||
event: getInputEvents,
|
||||
},
|
||||
|
@ -276,7 +285,7 @@ const toggleGroupingMode = async (buy, mode) => {
|
|||
}
|
||||
};
|
||||
|
||||
const showLockIcon = (groupingMode, mode) => {
|
||||
const lockIconType = (groupingMode, mode) => {
|
||||
if (mode === 'packing') {
|
||||
return groupingMode === 2 ? 'lock' : 'lock_open';
|
||||
} else {
|
||||
|
@ -320,17 +329,21 @@ const showLockIcon = (groupingMode, mode) => {
|
|||
:columns="entriesTableColumns"
|
||||
selection="multiple"
|
||||
row-key="id"
|
||||
hide-bottom
|
||||
class="full-width q-mt-md"
|
||||
:grid="$q.screen.lt.md"
|
||||
v-model:selected="rowsSelected"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
>
|
||||
<template #body="props">
|
||||
<QTr>
|
||||
<QTd>
|
||||
<QCheckbox v-model="props.selected" />
|
||||
</QTd>
|
||||
<QTd v-for="col in props.cols" :key="col.name">
|
||||
<QTd
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
style="max-width: 100px"
|
||||
>
|
||||
<component
|
||||
:is="tableColumnComponents[col.name].component"
|
||||
v-bind="tableColumnComponents[col.name].props"
|
||||
|
@ -350,7 +363,7 @@ const showLockIcon = (groupingMode, mode) => {
|
|||
>
|
||||
<QBtn
|
||||
:icon="
|
||||
showLockIcon(props.row.groupingMode, col.name)
|
||||
lockIconType(props.row.groupingMode, col.name)
|
||||
"
|
||||
@click="toggleGroupingMode(props.row, col.name)"
|
||||
class="cursor-pointer"
|
||||
|
@ -359,6 +372,16 @@ const showLockIcon = (groupingMode, mode) => {
|
|||
dense
|
||||
unelevated
|
||||
push
|
||||
:style="{
|
||||
'font-variation-settings': `'FILL' ${
|
||||
lockIconType(
|
||||
props.row.groupingMode,
|
||||
col.name
|
||||
) === 'lock'
|
||||
? 1
|
||||
: 0
|
||||
}`,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template
|
||||
|
@ -397,7 +420,7 @@ const showLockIcon = (groupingMode, mode) => {
|
|||
</QTr>
|
||||
<!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys -->
|
||||
<QTr v-if="props.rowIndex !== rows.length - 1" class="separation-row">
|
||||
<QTd colspan="12" style="height: 24px" />
|
||||
<QTd colspan="12" class="vn-table-separation-row" />
|
||||
</QTr>
|
||||
</template>
|
||||
<template #item="props">
|
||||
|
@ -448,9 +471,6 @@ const showLockIcon = (groupingMode, mode) => {
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.separation-row {
|
||||
background-color: var(--vn-gray) !important;
|
||||
}
|
||||
.grid-style-transition {
|
||||
transition: transform 0.28s, background-color 0.28s;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|||
import EntryDescriptor from './EntryDescriptor.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
|
@ -33,7 +34,9 @@ const stateStore = useStateStore();
|
|||
<QPage>
|
||||
<VnSubToolbar />
|
||||
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
<div :class="useCardSize()">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
@ -7,6 +7,7 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
|||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import useCardDescription from 'src/composables/useCardDescription';
|
||||
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { toDate } from 'src/filters';
|
||||
import { usePrintService } from 'composables/usePrintService';
|
||||
|
||||
|
@ -25,6 +26,8 @@ const $props = defineProps({
|
|||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { openReport } = usePrintService();
|
||||
const state = useState();
|
||||
const entryDescriptorRef = ref(null);
|
||||
|
||||
const entryFilter = {
|
||||
include: [
|
||||
|
@ -71,6 +74,8 @@ const data = ref(useCardDescription());
|
|||
const setData = (entity) =>
|
||||
(data.value = useCardDescription(entity.supplier.nickname, entity.id));
|
||||
|
||||
const currentEntry = computed(() => state.get('entry'));
|
||||
|
||||
const getEntryRedirectionFilter = (entry) => {
|
||||
let entryTravel = entry && entry.travel;
|
||||
|
||||
|
@ -95,17 +100,20 @@ const getEntryRedirectionFilter = (entry) => {
|
|||
const showEntryReport = () => {
|
||||
openReport(`Entries/${route.params.id}/entry-order-pdf`);
|
||||
};
|
||||
|
||||
watch;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardDescriptor
|
||||
ref="entryDescriptorRef"
|
||||
module="Entry"
|
||||
:url="`Entries/${entityId}`"
|
||||
:filter="entryFilter"
|
||||
:title="data.title"
|
||||
:subtitle="data.subtitle"
|
||||
@on-fetch="setData"
|
||||
data-key="entryData"
|
||||
data-key="entry"
|
||||
>
|
||||
<template #menu="{ entity }">
|
||||
<QItem v-ripple clickable @click="showEntryReport(entity)">
|
||||
|
@ -126,17 +134,17 @@ const showEntryReport = () => {
|
|||
:value="entity.travel?.warehouseOut?.name"
|
||||
/>
|
||||
</template>
|
||||
<template #icons="{ entity }">
|
||||
<template #icons>
|
||||
<QCardActions class="q-gutter-x-md">
|
||||
<QIcon
|
||||
v-if="entity.isExcludedFromAvailable"
|
||||
v-if="currentEntry.isExcludedFromAvailable"
|
||||
name="vn:inventory"
|
||||
color="primary"
|
||||
size="xs"
|
||||
>
|
||||
<QTooltip>{{ t('Inventory entry') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="entity.isRaid" name="vn:web" color="primary" size="xs">
|
||||
<QIcon v-if="currentEntry.isRaid" name="vn:net" color="primary" size="xs">
|
||||
<QTooltip>{{ t('Virtual entry') }}</QTooltip>
|
||||
</QIcon>
|
||||
</QCardActions>
|
||||
|
|
|
@ -50,25 +50,23 @@ onMounted(() => {
|
|||
:key="index"
|
||||
class="row q-gutter-md q-mb-md"
|
||||
>
|
||||
<div class="col-3">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.notes.observationType')"
|
||||
v-model="row.observationTypeFk"
|
||||
:options="entryObservationsOptions"
|
||||
:disable="!!row.id"
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
hide-selected
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('globals.description')"
|
||||
v-model="row.description"
|
||||
:rules="validate('EntryObservation.description')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-1 row justify-center items-center">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.notes.observationType')"
|
||||
v-model="row.observationTypeFk"
|
||||
:options="entryObservationsOptions"
|
||||
:disable="!!row.id"
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
hide-selected
|
||||
/>
|
||||
|
||||
<VnInput
|
||||
:label="t('globals.description')"
|
||||
v-model="row.description"
|
||||
:rules="validate('EntryObservation.description')"
|
||||
/>
|
||||
|
||||
<div class="row justify-center items-center">
|
||||
<QIcon
|
||||
name="delete"
|
||||
size="sm"
|
||||
|
@ -82,19 +80,17 @@ onMounted(() => {
|
|||
</QIcon>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<QIcon
|
||||
name="add"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
@click="entryObservationsRef.insert()"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Add note') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</VnRow>
|
||||
<QIcon
|
||||
name="add"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
@click="entryObservationsRef.insert()"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Add note') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</QCard>
|
||||
</template>
|
||||
</CrudModel>
|
||||
|
|
|
@ -39,30 +39,47 @@ onMounted(async () => {
|
|||
const tableColumnComponents = {
|
||||
quantity: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
},
|
||||
stickers: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
packagingFk: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
weight: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
packing: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
grouping: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
buyingValue: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
amount: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
pvp: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -148,156 +165,149 @@ const fetchEntryBuys = async () => {
|
|||
@on-fetch="(data) => setEntryData(data)"
|
||||
>
|
||||
<template #header-left>
|
||||
<a class="header link" :href="entryUrl">
|
||||
<router-link
|
||||
v-if="route.name !== 'EntrySummary'"
|
||||
:to="{ name: 'EntrySummary', params: { id: entityId } }"
|
||||
class="header link"
|
||||
:href="entryUrl"
|
||||
>
|
||||
<QIcon name="open_in_new" color="white" size="sm" />
|
||||
</a>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #header>
|
||||
<span>{{ entry.id }} - {{ entry.supplier.nickname }}</span>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<QCard class="vn-one">
|
||||
<a class="header link" :href="entryUrl">
|
||||
<router-link
|
||||
:to="{ name: 'EntryBasicData', params: { id: entityId } }"
|
||||
class="header header-link"
|
||||
>
|
||||
{{ t('globals.summary.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
</a>
|
||||
<VnRow>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.commission')"
|
||||
:value="entry.commission"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.currency')"
|
||||
:value="entry.currency.name"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.company')"
|
||||
:value="entry.company.code"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.reference')"
|
||||
:value="entry.reference"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.invoiceNumber')"
|
||||
:value="entry.invoiceNumber"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.ordered')"
|
||||
:value="entry.isOrdered"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.confirmed')"
|
||||
:value="entry.isConfirmed"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.booked')"
|
||||
:value="entry.isBooked"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv :label="t('entry.summary.raid')" :value="entry.isRaid" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.excludedFromAvailable')"
|
||||
:value="entry.isExcludedFromAvailable"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<QIcon name="open_in_new" />
|
||||
</router-link>
|
||||
|
||||
<VnLv :label="t('entry.summary.commission')" :value="entry.commission" />
|
||||
|
||||
<VnLv :label="t('entry.summary.currency')" :value="entry.currency.name" />
|
||||
|
||||
<VnLv :label="t('entry.summary.company')" :value="entry.company.code" />
|
||||
|
||||
<VnLv :label="t('entry.summary.reference')" :value="entry.reference" />
|
||||
|
||||
<VnLv
|
||||
:label="t('entry.summary.invoiceNumber')"
|
||||
:value="entry.invoiceNumber"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<a class="header link" :href="entryUrl">
|
||||
<router-link
|
||||
:to="{ name: 'EntryBasicData', params: { id: entityId } }"
|
||||
class="header header-link"
|
||||
>
|
||||
{{ t('globals.summary.basicData') }}
|
||||
<QIcon name="open_in_new" />
|
||||
</router-link>
|
||||
|
||||
<VnLv :label="t('entry.summary.travelReference')">
|
||||
<template #value>
|
||||
<span class="link">
|
||||
{{ entry.travel.ref }}
|
||||
<TravelDescriptorProxy :id="entry.travel.id" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelAgency')"
|
||||
:value="entry.travel.agency.name"
|
||||
/>
|
||||
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelShipped')"
|
||||
:value="toDate(entry.travel.shipped)"
|
||||
/>
|
||||
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelWarehouseOut')"
|
||||
:value="entry.travel.warehouseOut.name"
|
||||
/>
|
||||
|
||||
<QCheckbox
|
||||
:label="t('entry.summary.travelDelivered')"
|
||||
v-model="entry.travel.isDelivered"
|
||||
:disable="true"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelLanded')"
|
||||
:value="toDate(entry.travel.landed)"
|
||||
/>
|
||||
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelWarehouseIn')"
|
||||
:value="entry.travel.warehouseIn.name"
|
||||
/>
|
||||
|
||||
<QCheckbox
|
||||
:label="t('entry.summary.travelReceived')"
|
||||
v-model="entry.travel.isReceived"
|
||||
:disable="true"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<router-link
|
||||
:to="{ name: 'TravelSummary', params: { id: entry.travel.id } }"
|
||||
class="header header-link"
|
||||
>
|
||||
{{ t('Travel data') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
</a>
|
||||
<VnRow>
|
||||
<div class="col">
|
||||
<VnLv :label="t('entry.summary.travelReference')">
|
||||
<template #value>
|
||||
<span class="link">
|
||||
{{ entry.travel.ref }}
|
||||
<TravelDescriptorProxy :id="entry.travel.id" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelAgency')"
|
||||
:value="entry.travel.agency.name"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelShipped')"
|
||||
:value="toDate(entry.travel.shipped)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelWarehouseOut')"
|
||||
:value="entry.travel.warehouseOut.name"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelDelivered')"
|
||||
:value="entry.travel.isDelivered"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelLanded')"
|
||||
:value="toDate(entry.travel.landed)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelWarehouseIn')"
|
||||
:value="entry.travel.warehouseIn.name"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnLv
|
||||
:label="t('entry.summary.travelReceived')"
|
||||
:value="entry.travel.isReceived"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<QIcon name="open_in_new" />
|
||||
</router-link>
|
||||
<QCheckbox
|
||||
:label="t('entry.summary.ordered')"
|
||||
v-model="entry.isOrdered"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('entry.summary.confirmed')"
|
||||
v-model="entry.isConfirmed"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('entry.summary.booked')"
|
||||
v-model="entry.isBooked"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('entry.summary.raid')"
|
||||
v-model="entry.isRaid"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('entry.summary.excludedFromAvailable')"
|
||||
v-model="entry.isExcludedFromAvailable"
|
||||
:disable="true"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-two" style="min-width: 100%">
|
||||
<a class="header">
|
||||
<a class="header header-link">
|
||||
{{ t('entry.summary.buys') }}
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable
|
||||
:rows="entryBuys"
|
||||
:columns="entriesTableColumns"
|
||||
hide-bottom
|
||||
row-key="index"
|
||||
class="full-width q-mt-md"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
>
|
||||
<template #body="{ cols, row, rowIndex }">
|
||||
<QTr no-hover>
|
||||
<QTd v-for="col in cols" :key="col.name">
|
||||
<component
|
||||
:is="tableColumnComponents[col.name].component()"
|
||||
:is="tableColumnComponents[col.name].component(props)"
|
||||
v-bind="tableColumnComponents[col.name].props(props)"
|
||||
@click="tableColumnComponents[col.name].event(props)"
|
||||
class="col-content"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
|
@ -334,11 +344,8 @@ const fetchEntryBuys = async () => {
|
|||
</QTd>
|
||||
</QTr>
|
||||
<!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys -->
|
||||
<QTr
|
||||
v-if="rowIndex !== entryBuys.length - 1"
|
||||
class="separation-row"
|
||||
>
|
||||
<QTd colspan="10" style="height: 24px" />
|
||||
<QTr v-if="rowIndex !== entryBuys.length - 1">
|
||||
<QTd colspan="10" class="vn-table-separation-row" />
|
||||
</QTr>
|
||||
</template>
|
||||
</QTable>
|
||||
|
@ -347,13 +354,7 @@ const fetchEntryBuys = async () => {
|
|||
</CardSummary>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.separation-row {
|
||||
background-color: var(--vn-gray) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Travel data: 'Datos envío'
|
||||
Travel data: Datos envío
|
||||
</i18n>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { onMounted, ref, computed, reactive, onUnmounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
|
@ -8,11 +8,16 @@ import FetchedTags from 'components/ui/FetchedTags.vue';
|
|||
import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue';
|
||||
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
|
||||
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
|
||||
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { toDate, toCurrency } from 'src/filters';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
|
||||
const router = useRouter();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
|
@ -21,11 +26,72 @@ const stateStore = useStateStore();
|
|||
const { t } = useI18n();
|
||||
|
||||
const rowsFetchDataRef = ref(null);
|
||||
const itemTypesOptions = ref([]);
|
||||
const originsOptions = ref([]);
|
||||
const itemFamiliesOptions = ref([]);
|
||||
const intrastatOptions = ref([]);
|
||||
const packagingsOptions = ref([]);
|
||||
const editTableCellDialogRef = ref(null);
|
||||
const visibleColumns = ref([]);
|
||||
const allColumnNames = ref([]);
|
||||
const rows = ref([]);
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
case 'id':
|
||||
case 'size':
|
||||
case 'weightByPiece':
|
||||
case 'isActive':
|
||||
case 'family':
|
||||
case 'minPrice':
|
||||
case 'packingOut':
|
||||
return { [`i.${param}`]: value };
|
||||
case 'name':
|
||||
case 'description':
|
||||
return { [`i.${param}`]: { like: `%${value}%` } };
|
||||
case 'code':
|
||||
return { 'it.code': value };
|
||||
case 'intrastat':
|
||||
return { 'intr.description': value };
|
||||
case 'origin':
|
||||
return { 'ori.code': value };
|
||||
case 'landing':
|
||||
return { [`lb.${param}`]: value };
|
||||
case 'packing':
|
||||
case 'grouping':
|
||||
case 'quantity':
|
||||
case 'entryFk':
|
||||
case 'buyingValue':
|
||||
case 'freightValue':
|
||||
case 'comissionValue':
|
||||
case 'packageValue':
|
||||
case 'isIgnored':
|
||||
case 'price2':
|
||||
case 'price3':
|
||||
case 'ektFk':
|
||||
case 'weight':
|
||||
case 'packagingFk':
|
||||
return { [`b.${param}`]: value };
|
||||
}
|
||||
};
|
||||
|
||||
const params = reactive({});
|
||||
const arrayData = useArrayData('EntryLatestBuys', {
|
||||
url: 'Buys/latestBuysFilter',
|
||||
order: ['itemFk DESC'],
|
||||
exprBuilder: exprBuilder,
|
||||
});
|
||||
const store = arrayData.store;
|
||||
const rows = computed(() => store.data);
|
||||
const rowsSelected = ref([]);
|
||||
|
||||
const getInputEvents = (col) => {
|
||||
return col.columnFilter.type === 'select'
|
||||
? { 'update:modelValue': () => applyColumnFilter(col) }
|
||||
: {
|
||||
'keyup.enter': () => applyColumnFilter(col),
|
||||
};
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
label: t('entry.latestBuys.picture'),
|
||||
|
@ -37,12 +103,32 @@ const columns = computed(() => [
|
|||
name: 'itemFk',
|
||||
field: 'itemFk',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.packing'),
|
||||
field: 'packing',
|
||||
name: 'packing',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
|
@ -50,6 +136,16 @@ const columns = computed(() => [
|
|||
field: 'grouping',
|
||||
name: 'grouping',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
|
@ -57,12 +153,32 @@ const columns = computed(() => [
|
|||
field: 'quantity',
|
||||
name: 'quantity',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('globals.description'),
|
||||
field: 'description',
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
|
@ -70,35 +186,104 @@ const columns = computed(() => [
|
|||
field: 'size',
|
||||
name: 'size',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.tags'),
|
||||
name: 'tags',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.type'),
|
||||
field: 'code',
|
||||
name: 'type',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnSelectFilter,
|
||||
type: 'select',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
options: itemTypesOptions.value,
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.intrastat'),
|
||||
field: 'intrastat',
|
||||
name: 'intrastat',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnSelectFilter,
|
||||
type: 'select',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
options: intrastatOptions.value,
|
||||
'option-value': 'description',
|
||||
'option-label': 'description',
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.origin'),
|
||||
field: 'origin',
|
||||
name: 'origin',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnSelectFilter,
|
||||
type: 'select',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
options: originsOptions.value,
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.weightByPiece'),
|
||||
field: 'weightByPiece',
|
||||
name: 'weightByPiece',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
|
@ -106,24 +291,67 @@ const columns = computed(() => [
|
|||
field: 'isActive',
|
||||
name: 'isActive',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.family'),
|
||||
field: 'family',
|
||||
name: 'family',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnSelectFilter,
|
||||
type: 'select',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
options: itemFamiliesOptions.value,
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.entryFk'),
|
||||
field: 'entryFk',
|
||||
name: 'entryFk',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.buyingValue'),
|
||||
field: 'buyingValue',
|
||||
name: 'buyingValue',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
|
@ -131,6 +359,16 @@ const columns = computed(() => [
|
|||
field: 'freightValue',
|
||||
name: 'freightValue',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
|
@ -138,6 +376,16 @@ const columns = computed(() => [
|
|||
field: 'comissionValue',
|
||||
name: 'comissionValue',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
|
@ -145,6 +393,16 @@ const columns = computed(() => [
|
|||
field: 'packageValue',
|
||||
name: 'packageValue',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
|
@ -152,12 +410,33 @@ const columns = computed(() => [
|
|||
field: 'isIgnored',
|
||||
name: 'isIgnored',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.price2'),
|
||||
field: 'price2',
|
||||
name: 'price2',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
|
@ -165,6 +444,16 @@ const columns = computed(() => [
|
|||
field: 'price3',
|
||||
name: 'price3',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
|
@ -172,6 +461,16 @@ const columns = computed(() => [
|
|||
field: 'minPrice',
|
||||
name: 'minPrice',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
|
@ -179,6 +478,16 @@ const columns = computed(() => [
|
|||
field: 'ektFk',
|
||||
name: 'ektFk',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
|
@ -186,18 +495,51 @@ const columns = computed(() => [
|
|||
field: 'weight',
|
||||
name: 'weight',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.packagingFk'),
|
||||
field: 'packagingFk',
|
||||
name: 'packagingFk',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnSelectFilter,
|
||||
type: 'select',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
options: packagingsOptions.value,
|
||||
'option-value': 'id',
|
||||
'option-label': 'id',
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('entry.latestBuys.packingOut'),
|
||||
field: 'packingOut',
|
||||
name: 'packingOut',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
|
@ -205,6 +547,16 @@ const columns = computed(() => [
|
|||
field: 'landing',
|
||||
name: 'landing',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => toDate(val),
|
||||
},
|
||||
]);
|
||||
|
@ -234,20 +586,55 @@ const redirectToEntryBuys = (entryFk) => {
|
|||
router.push({ name: 'EntryBuys', params: { id: entryFk } });
|
||||
};
|
||||
|
||||
const applyColumnFilter = async (col) => {
|
||||
try {
|
||||
params[col.field] = col.columnFilter.filterValue;
|
||||
await arrayData.addFilter({ params });
|
||||
} catch (err) {
|
||||
console.error('Error applying column filter', err);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
stateStore.rightDrawer = true;
|
||||
const filteredColumns = columns.value.filter((col) => col.name !== 'picture');
|
||||
allColumnNames.value = filteredColumns.map((col) => col.name);
|
||||
await arrayData.fetch({ append: false });
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="rowsFetchDataRef"
|
||||
url="Buys/latestBuysFilter"
|
||||
:filter="{ order: 'itemFk DESC', limit: 20 }"
|
||||
@on-fetch="(data) => (rows = data)"
|
||||
url="ItemTypes"
|
||||
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (itemTypesOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Origins"
|
||||
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (originsOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="ItemFamilies"
|
||||
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (itemFamiliesOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Packagings"
|
||||
:filter="{ fields: ['id'], order: 'id ASC', limit: 30 }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (packagingsOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Intrastats"
|
||||
:filter="{ fields: ['description'], order: 'description ASC', limit: 30 }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (intrastatOptions = data)"
|
||||
/>
|
||||
<QToolbar class="bg-vn-dark justify-end">
|
||||
<div id="st-data">
|
||||
|
@ -261,19 +648,43 @@ onMounted(async () => {
|
|||
<QSpace />
|
||||
<div id="st-actions"></div>
|
||||
</QToolbar>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<EntryLatestBuysFilter data-key="EntryLatestBuys" :tags="tags" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<QTable
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
hide-bottom
|
||||
selection="multiple"
|
||||
row-key="id"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
class="full-width q-mt-md"
|
||||
:visible-columns="visibleColumns"
|
||||
v-model:selected="rowsSelected"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
@row-click="(_, row) => redirectToEntryBuys(row.entryFk)"
|
||||
>
|
||||
<template #top-row="{ cols }">
|
||||
<QTr>
|
||||
<QTd />
|
||||
<QTd
|
||||
v-for="(col, index) in cols"
|
||||
:key="index"
|
||||
style="max-width: 100px"
|
||||
>
|
||||
<component
|
||||
:is="col.columnFilter.component"
|
||||
v-if="col.name !== 'picture'"
|
||||
v-model="col.columnFilter.filterValue"
|
||||
v-bind="col.columnFilter.attrs"
|
||||
v-on="col.columnFilter.event(col)"
|
||||
dense
|
||||
/>
|
||||
</QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
<template #body-cell-picture="{ row }">
|
||||
<QTd>
|
||||
<QImg
|
||||
|
@ -288,9 +699,10 @@ onMounted(async () => {
|
|||
</template>
|
||||
<template #body-cell-itemFk="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QBtn flat color="blue">
|
||||
<QBtn flat color="primary">
|
||||
{{ row.itemFk }}
|
||||
</QBtn>
|
||||
<ItemDescriptorProxy :id="row.itemFk" />
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-tags="{ row }">
|
||||
|
@ -300,7 +712,7 @@ onMounted(async () => {
|
|||
</template>
|
||||
<template #body-cell-entryFk="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QBtn flat color="blue">
|
||||
<QBtn flat color="primary">
|
||||
<EntryDescriptorProxy :id="row.entryFk" />
|
||||
{{ row.entryFk }}
|
||||
</QBtn>
|
||||
|
|
|
@ -0,0 +1,474 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const itemCategories = ref([]);
|
||||
const selectedCategoryFk = ref(null);
|
||||
const selectedTypeFk = ref(null);
|
||||
const itemTypesOptions = ref([]);
|
||||
const itemTypeWorkersOptions = ref([]);
|
||||
const suppliersOptions = ref([]);
|
||||
const tagOptions = ref([]);
|
||||
const tagValues = ref([]);
|
||||
|
||||
const categoryList = computed(() => {
|
||||
return (itemCategories.value || [])
|
||||
.filter((category) => category.display)
|
||||
.map((category) => ({
|
||||
...category,
|
||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||
}));
|
||||
});
|
||||
|
||||
const selectedCategory = computed(() =>
|
||||
(itemCategories.value || []).find(
|
||||
(category) => category?.id === selectedCategoryFk.value
|
||||
)
|
||||
);
|
||||
|
||||
const selectedType = computed(() => {
|
||||
return (itemTypesOptions.value || []).find(
|
||||
(type) => type?.id === selectedTypeFk.value
|
||||
);
|
||||
});
|
||||
|
||||
const selectCategory = async (params, categoryId, search) => {
|
||||
if (params.categoryFk === categoryId) {
|
||||
resetCategory(params);
|
||||
search();
|
||||
return;
|
||||
}
|
||||
selectedCategoryFk.value = categoryId;
|
||||
params.categoryFk = categoryId;
|
||||
await fetchItemTypes(categoryId);
|
||||
search();
|
||||
};
|
||||
|
||||
const resetCategory = (params) => {
|
||||
selectedCategoryFk.value = null;
|
||||
itemTypesOptions.value = null;
|
||||
if (params) {
|
||||
params.categoryFk = null;
|
||||
params.typeFk = null;
|
||||
}
|
||||
};
|
||||
|
||||
const applyTags = (params, search) => {
|
||||
params.tags = tagValues.value
|
||||
.filter((tag) => tag.selectedTag && tag.value)
|
||||
.map((tag) => ({
|
||||
tagFk: tag.selectedTag.id,
|
||||
tagName: tag.selectedTag.name,
|
||||
value: tag.value,
|
||||
}));
|
||||
search();
|
||||
};
|
||||
|
||||
const fetchItemTypes = async (id) => {
|
||||
try {
|
||||
const filter = {
|
||||
fields: ['id', 'name', 'categoryFk'],
|
||||
where: { categoryFk: id },
|
||||
include: 'category',
|
||||
order: 'name ASC',
|
||||
};
|
||||
const { data } = await axios.get('ItemTypes', {
|
||||
params: { filter: JSON.stringify(filter) },
|
||||
});
|
||||
itemTypesOptions.value = data;
|
||||
} catch (err) {
|
||||
console.error('Error fetching item types', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryClass = (category, params) => {
|
||||
if (category.id === params?.categoryFk) {
|
||||
return 'active';
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedTagValues = async (tag) => {
|
||||
try {
|
||||
tag.value = null;
|
||||
const filter = {
|
||||
fields: ['value'],
|
||||
order: 'value ASC',
|
||||
limit: 30,
|
||||
};
|
||||
|
||||
const params = { filter: JSON.stringify(filter) };
|
||||
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
|
||||
params,
|
||||
});
|
||||
tag.valueOptions = data;
|
||||
} catch (err) {
|
||||
console.error('Error getting selected tag values');
|
||||
}
|
||||
};
|
||||
|
||||
const removeTag = (index, params, search) => {
|
||||
(tagValues.value || []).splice(index, 1);
|
||||
applyTags(params, search);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="ItemCategories"
|
||||
limit="30"
|
||||
auto-load
|
||||
@on-fetch="(data) => (itemCategories = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="TicketRequests/getItemTypeWorker"
|
||||
limit="30"
|
||||
auto-load
|
||||
:filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }"
|
||||
@on-fetch="(data) => (itemTypeWorkersOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
limit="30"
|
||||
auto-load
|
||||
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
|
||||
@on-fetch="(data) => (suppliersOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Tags"
|
||||
:filter="{ fields: ['id', 'name', 'isFree'] }"
|
||||
auto-load
|
||||
limit="30"
|
||||
@on-fetch="(data) => (tagOptions = data)"
|
||||
/>
|
||||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:expr-builder="exprBuilder"
|
||||
:custom-tags="['tags']"
|
||||
@init="onFilterInit"
|
||||
@remove="clearFilter"
|
||||
>
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<strong v-if="tag.label === 'categoryFk'">
|
||||
{{ t(selectedCategory?.name || '') }}
|
||||
</strong>
|
||||
<strong v-else-if="tag.label === 'typeFk'">
|
||||
{{ t(selectedType?.name || '') }}
|
||||
</strong>
|
||||
<div v-else class="q-gutter-x-xs">
|
||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||
<span>{{ formatFn(tag.value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #customTags="{ tags: customTags, params }">
|
||||
<template v-for="tag in customTags" :key="tag.label">
|
||||
<VnFilterPanelChip
|
||||
v-for="chip in tag.value"
|
||||
:key="chip"
|
||||
removable
|
||||
@remove="removeTagChip(chip, params, searchFn)"
|
||||
>
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ chip.tagName }}: </strong>
|
||||
<span>"{{ chip.value }}"</span>
|
||||
</div>
|
||||
</VnFilterPanelChip>
|
||||
</template>
|
||||
</template>
|
||||
<template #body="{ params, searchFn }">
|
||||
<QItem class="category-filter q-mt-md">
|
||||
<QBtn
|
||||
dense
|
||||
flat
|
||||
round
|
||||
v-for="category in categoryList"
|
||||
:key="category.name"
|
||||
:class="['category', getCategoryClass(category, params)]"
|
||||
:icon="category.icon"
|
||||
@click="selectCategory(params, category.id, searchFn)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t(category.name) }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</QItem>
|
||||
<QItem class="q-my-md">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="t('params.typeFk')"
|
||||
v-model="params.typeFk"
|
||||
:options="itemTypesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
use-input
|
||||
:disable="!selectedCategoryFk"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
selectedTypeFk = value;
|
||||
searchFn();
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.categoryName }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QSeparator />
|
||||
<QItem class="q-my-md">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="t('params.salesPersonFk')"
|
||||
v-model="params.salesPersonFk"
|
||||
:options="itemTypeWorkersOptions"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
use-input
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-md">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="t('params.supplier')"
|
||||
v-model="params.supplierFk"
|
||||
:options="suppliersOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
use-input
|
||||
@update:model-value="searchFn()"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-md">
|
||||
<QItemSection>
|
||||
<VnInputDate
|
||||
:label="t('params.from')"
|
||||
v-model="params.from"
|
||||
is-outlined
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-md">
|
||||
<QItemSection>
|
||||
<VnInputDate
|
||||
:label="t('params.to')"
|
||||
v-model="params.to"
|
||||
is-outlined
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<QCheckbox
|
||||
:label="t('params.active')"
|
||||
v-model="params.active"
|
||||
toggle-indeterminate
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItemSection>
|
||||
<QCheckbox
|
||||
:label="t('params.visible')"
|
||||
v-model="params.visible"
|
||||
toggle-indeterminate
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<QCheckbox
|
||||
:label="t('params.floramondo')"
|
||||
v-model="params.floramondo"
|
||||
toggle-indeterminate
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
||||
<QItem
|
||||
v-for="(value, index) in tagValues"
|
||||
:key="value"
|
||||
class="q-mt-md filter-value"
|
||||
>
|
||||
<QItemSection class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('params.tag')"
|
||||
v-model="value.selectedTag"
|
||||
:options="tagOptions"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:emit-value="false"
|
||||
use-input
|
||||
:is-clearable="false"
|
||||
@update:model-value="getSelectedTagValues(value)"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItemSection class="col">
|
||||
<VnSelectFilter
|
||||
v-if="!value?.selectedTag?.isFree && value.valueOptions"
|
||||
:label="t('params.value')"
|
||||
v-model="value.value"
|
||||
:options="value.valueOptions || []"
|
||||
option-value="value"
|
||||
option-label="value"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
emit-value
|
||||
use-input
|
||||
:disable="!value"
|
||||
:is-clearable="false"
|
||||
class="filter-input"
|
||||
@update:model-value="applyTags(params, searchFn)"
|
||||
/>
|
||||
<VnInput
|
||||
v-else
|
||||
v-model="value.value"
|
||||
:label="t('params.value')"
|
||||
:disable="!value"
|
||||
is-outlined
|
||||
class="filter-input"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="applyTags(params, searchFn)"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QIcon
|
||||
name="delete"
|
||||
class="filter-icon"
|
||||
@click="removeTag(index, params, searchFn)"
|
||||
/>
|
||||
</QItem>
|
||||
<QItem class="q-mt-lg">
|
||||
<QIcon
|
||||
name="add_circle"
|
||||
class="filter-icon"
|
||||
@click="tagValues.push({})"
|
||||
/>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.category-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
.category {
|
||||
padding: 8px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 1.4rem;
|
||||
background-color: var(--vn-light-gray);
|
||||
|
||||
&.active {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
font-size: 24px;
|
||||
color: $primary;
|
||||
padding: 0 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
params:
|
||||
supplier: Supplier
|
||||
from: From
|
||||
to: To
|
||||
active: Is active
|
||||
visible: Is visible
|
||||
floramondo: Is floramondo
|
||||
salesPersonFk: Buyer
|
||||
categoryFk: Category
|
||||
typeFk: Type
|
||||
tag: Tag
|
||||
value: Value
|
||||
es:
|
||||
params:
|
||||
supplier: Proveedor
|
||||
from: Desde
|
||||
to: Hasta
|
||||
active: Activo
|
||||
visible: Visible
|
||||
floramondo: Floramondo
|
||||
salesPersonFk: Comprador
|
||||
categoryFk: Categoría
|
||||
typeFk: Tipo
|
||||
tag: Etiqueta
|
||||
value: Valor
|
||||
Plant: Planta
|
||||
Flower: Flor
|
||||
Handmade: Confección
|
||||
Green: Verde
|
||||
Accessories: Complemento
|
||||
Fruit: Fruta
|
||||
</i18n>
|
|
@ -74,7 +74,7 @@ onMounted(async () => {
|
|||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row.isRaid"
|
||||
name="vn:web"
|
||||
name="vn:net"
|
||||
color="primary"
|
||||
size="xs"
|
||||
>
|
||||
|
|
|
@ -8,6 +8,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { onMounted, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
|
@ -74,7 +75,9 @@ watch(
|
|||
<QPageContainer>
|
||||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
<div :class="useCardSize()">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import InvoiceInDescriptor from "pages/InvoiceIn/Card/InvoiceInDescriptor.vue";
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<QPopupProxy>
|
||||
<InvoiceInDescriptor v-if="$props.id" :id="$props.id" />
|
||||
</QPopupProxy>
|
||||
</template>
|
|
@ -8,6 +8,7 @@ import { useArrayData } from 'src/composables/useArrayData';
|
|||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
@ -74,7 +75,12 @@ async function insert() {
|
|||
}
|
||||
</script>
|
||||
<template>
|
||||
<FetchData url="Banks" auto-load limit="30" @on-fetch="(data) => (banks = data)" />
|
||||
<FetchData
|
||||
url="Accountings"
|
||||
auto-load
|
||||
limit="30"
|
||||
@on-fetch="(data) => (banks = data)"
|
||||
/>
|
||||
<CrudModel
|
||||
v-if="invoiceIn"
|
||||
ref="invoiceInFormRef"
|
||||
|
@ -158,7 +164,12 @@ async function insert() {
|
|||
</template>
|
||||
<template #body-cell-amount="{ row }">
|
||||
<QTd>
|
||||
<QInput v-model="row.amount" clearable clear-icon="close" />
|
||||
<VnCurrency
|
||||
v-model="row.amount"
|
||||
:is-outlined="false"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-foreignvalue="{ row }">
|
||||
|
|
|
@ -209,9 +209,9 @@ function getLink(param) {
|
|||
<!--Basic Data-->
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<a class="header" :href="getLink('basic-data')">
|
||||
<a class="header header-link" :href="getLink('basic-data')">
|
||||
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
</QCardSection>
|
||||
<VnLv
|
||||
|
@ -233,9 +233,9 @@ function getLink(param) {
|
|||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<a class="header" :href="getLink('basic-data')">
|
||||
<a class="header header-link" :href="getLink('basic-data')">
|
||||
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
</QCardSection>
|
||||
<VnLv
|
||||
|
@ -258,9 +258,9 @@ function getLink(param) {
|
|||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<a class="header" :href="getLink('basic-data')">
|
||||
<a class="header header-link" :href="getLink('basic-data')">
|
||||
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
</QCardSection>
|
||||
<VnLv
|
||||
|
@ -275,16 +275,17 @@ function getLink(param) {
|
|||
:label="t('invoiceIn.summary.company')"
|
||||
:value="invoiceIn.company?.code"
|
||||
/>
|
||||
<VnLv
|
||||
<QCheckbox
|
||||
:label="t('invoiceIn.summary.booked')"
|
||||
:value="invoiceIn.isBooked"
|
||||
v-model="invoiceIn.isBooked"
|
||||
:disable="true"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<a class="header" :href="getLink('basic-data')">
|
||||
<a class="header header-link" :href="getLink('basic-data')">
|
||||
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pa-none">
|
||||
|
@ -318,9 +319,9 @@ function getLink(param) {
|
|||
</QCard>
|
||||
<!--Vat-->
|
||||
<QCard v-if="invoiceIn.invoiceInTax.length">
|
||||
<a class="header" :href="getLink('vat')">
|
||||
<a class="header header-link" :href="getLink('vat')">
|
||||
{{ t('invoiceIn.card.vat') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable
|
||||
:columns="vatColumns"
|
||||
|
@ -351,9 +352,9 @@ function getLink(param) {
|
|||
</QCard>
|
||||
<!--Due Day-->
|
||||
<QCard v-if="invoiceIn.invoiceInDueDay.length">
|
||||
<a class="header" :href="getLink('due-day')">
|
||||
<a class="header header-link" :href="getLink('due-day')">
|
||||
{{ t('invoiceIn.card.dueDay') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable
|
||||
class="full-width"
|
||||
|
@ -381,9 +382,9 @@ function getLink(param) {
|
|||
</QCard>
|
||||
<!--Intrastat-->
|
||||
<QCard v-if="invoiceIn.invoiceInIntrastat.length">
|
||||
<a class="header" :href="getUrl('intrastat')">
|
||||
<a class="header header-link" :href="getLink('intrastat')">
|
||||
{{ t('invoiceIn.card.intrastat') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable
|
||||
:columns="intrastatColumns"
|
||||
|
|
|
@ -9,6 +9,7 @@ import { toCurrency } from 'src/filters';
|
|||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
@ -225,7 +226,7 @@ async function addExpense() {
|
|||
</template>
|
||||
<template #body-cell-taxablebase="{ row }">
|
||||
<QTd>
|
||||
<QInput
|
||||
<VnCurrency
|
||||
:class="{
|
||||
'no-pointer-events': isNotEuro(invoiceIn.currency.code),
|
||||
}"
|
||||
|
@ -234,11 +235,7 @@ async function addExpense() {
|
|||
clear-icon="close"
|
||||
v-model="row.taxableBase"
|
||||
clearable
|
||||
>
|
||||
<template #prepend>
|
||||
<QIcon name="euro" size="xs" flat />
|
||||
</template>
|
||||
</QInput>
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-sageiva="{ row, col }">
|
||||
|
@ -328,7 +325,7 @@ async function addExpense() {
|
|||
</VnSelectFilter>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QInput
|
||||
<VnCurrency
|
||||
:label="t('Taxable base')"
|
||||
:class="{
|
||||
'no-pointer-events': isNotEuro(
|
||||
|
@ -340,11 +337,7 @@ async function addExpense() {
|
|||
clear-icon="close"
|
||||
v-model="props.row.taxableBase"
|
||||
clearable
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="euro" size="xs" flat />
|
||||
</template>
|
||||
</QInput>
|
||||
/>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<VnSelectFilter
|
||||
|
|
|
@ -8,6 +8,7 @@ import FetchData from 'components/FetchData.vue';
|
|||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import { useCapitalize } from 'src/composables/useCapitalize';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -137,16 +138,7 @@ const suppliersRef = ref();
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('Amount')"
|
||||
v-model="params.amount"
|
||||
is-outlined
|
||||
lazy-rules
|
||||
>
|
||||
<template #prepend>
|
||||
<QIcon name="euro" size="sm"></QIcon>
|
||||
</template>
|
||||
</VnInput>
|
||||
<VnCurrency v-model="params.amount" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-mb-md">
|
||||
|
|
|
@ -5,6 +5,7 @@ import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
|
|||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
|
@ -28,7 +29,9 @@ const { t } = useI18n();
|
|||
<QPageContainer>
|
||||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
<div :class="useCardSize()">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -95,15 +95,20 @@ const ticketsColumns = ref([
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<CardSummary ref="summary" :url="`InvoiceOuts/${entityId}/summary`">
|
||||
<CardSummary
|
||||
ref="summary"
|
||||
:url="`InvoiceOuts/${entityId}/summary`"
|
||||
:entity-id="entityId"
|
||||
>
|
||||
<template #header="{ entity: { invoiceOut } }">
|
||||
<div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div>
|
||||
</template>
|
||||
<template #body="{ entity: { invoiceOut } }">
|
||||
<QCard class="vn-one">
|
||||
<div class="header">
|
||||
<a class="header header-link">
|
||||
{{ t('invoiceOut.pageTitles.basicData') }}
|
||||
</div>
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<VnLv
|
||||
:label="t('invoiceOut.summary.issued')"
|
||||
:value="toDate(invoiceOut.issued)"
|
||||
|
@ -126,9 +131,10 @@ const ticketsColumns = ref([
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-three">
|
||||
<div class="header">
|
||||
<a class="header header-link">
|
||||
{{ t('invoiceOut.summary.taxBreakdown') }}
|
||||
</div>
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable :columns="taxColumns" :rows="invoiceOut.taxesBreakdown" flat>
|
||||
<template #header="props">
|
||||
<QTr :props="props">
|
||||
|
@ -140,9 +146,10 @@ const ticketsColumns = ref([
|
|||
</QTable>
|
||||
</QCard>
|
||||
<QCard class="vn-three">
|
||||
<div class="header">
|
||||
<a class="header header-link">
|
||||
{{ t('invoiceOut.summary.tickets') }}
|
||||
</div>
|
||||
<QIcon name="open_in_new" />
|
||||
</a>
|
||||
<QTable v-if="tickets" :columns="ticketsColumns" :rows="tickets" flat>
|
||||
<template #header="props">
|
||||
<QTr :props="props">
|
||||
|
|
|
@ -6,6 +6,7 @@ import FetchData from 'components/FetchData.vue';
|
|||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -57,7 +58,11 @@ function setWorkers(data) {
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput :label="t('Amount')" v-model="params.amount" is-outlined />
|
||||
<VnCurrency
|
||||
:label="t('Amount')"
|
||||
v-model="params.amount"
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -84,7 +85,7 @@ const props = defineProps({
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
<VnCurrency
|
||||
v-model="params.amount"
|
||||
:label="t('invoiceOut.negativeBases.amount')"
|
||||
is-outlined
|
||||
|
|
|
@ -4,6 +4,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|||
import ItemDescriptor from './ItemDescriptor.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
</script>
|
||||
|
@ -19,7 +20,9 @@ const stateStore = useStateStore();
|
|||
<QPage>
|
||||
<VnSubToolbar />
|
||||
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
<div :class="useCardSize()">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
|
|
@ -76,12 +76,12 @@ async function onSubmit() {
|
|||
<template>
|
||||
<QForm @submit="onSubmit" class="q-gutter-y-md q-pa-lg formCard">
|
||||
<VnLogo alt="Logo" fit="contain" :ratio="16 / 9" class="q-mb-md" />
|
||||
|
||||
<VnInput
|
||||
v-model="username"
|
||||
:label="t('login.username')"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
|
||||
color="primary"
|
||||
/>
|
||||
<VnInput
|
||||
type="password"
|
||||
|
@ -89,9 +89,8 @@ async function onSubmit() {
|
|||
:label="t('login.password')"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
|
||||
class="red"
|
||||
/>
|
||||
<QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
|
||||
|
||||
<div>
|
||||
<QBtn
|
||||
:label="t('login.submit')"
|
||||
|
@ -102,6 +101,7 @@ async function onSubmit() {
|
|||
unelevated
|
||||
/>
|
||||
</div>
|
||||
<QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
|
||||
</QForm>
|
||||
</template>
|
||||
|
||||
|
@ -111,6 +111,9 @@ async function onSubmit() {
|
|||
min-width: 300px;
|
||||
}
|
||||
|
||||
.q-input {
|
||||
color: $primary;
|
||||
}
|
||||
@media (max-width: $breakpoint-xs-max) {
|
||||
.formCard {
|
||||
min-width: 100%;
|
||||
|
|
|
@ -99,6 +99,8 @@ onMounted(async () => {
|
|||
</i18n>
|
||||
|
||||
<style lang="scss">
|
||||
$vnColor: #8ebb27;
|
||||
|
||||
.formCard {
|
||||
max-width: 1500px;
|
||||
min-width: 700px;
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['search']);
|
||||
|
||||
const agencyList = ref([]);
|
||||
const agencyAgreementList = ref([]);
|
||||
const supplierList = ref([]);
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
case 'agencyModeFk':
|
||||
return { 'a.agencyModeFk': value };
|
||||
case 'supplierFk':
|
||||
return { 'a.supplierName': value };
|
||||
case 'routeFk':
|
||||
return { 'a.routeFk': value };
|
||||
case 'created':
|
||||
case 'agencyFk':
|
||||
case 'packages':
|
||||
case 'm3':
|
||||
case 'kmTotal':
|
||||
case 'price':
|
||||
case 'invoiceInFk':
|
||||
return { [`a.${param}`]: value };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="AgencyModes"
|
||||
:filter="{ fields: ['id', 'name'] }"
|
||||
sort-by="name ASC"
|
||||
limit="30"
|
||||
@on-fetch="(data) => (agencyList = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Agencies"
|
||||
:filter="{ fields: ['id', 'name'] }"
|
||||
sort-by="name ASC"
|
||||
limit="30"
|
||||
@on-fetch="(data) => (agencyAgreementList = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
:filter="{ fields: ['name'] }"
|
||||
sort-by="name ASC"
|
||||
limit="30"
|
||||
@on-fetch="(data) => (supplierList = data)"
|
||||
auto-load
|
||||
/>
|
||||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:expr-builder="exprBuilder"
|
||||
search-button
|
||||
@search="emit('search')"
|
||||
>
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||
<span>{{ formatFn(tag.value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body="{ params }">
|
||||
<QList dense>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput v-model="params.routeFk" :label="t('ID')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm" v-if="agencyList">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="t('Agency route')"
|
||||
v-model="params.agencyModeFk"
|
||||
:options="agencyList"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
is-clearable
|
||||
:input-debounce="0"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm" v-if="agencyAgreementList">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="t('Agency agreement')"
|
||||
v-model="params.agencyFk"
|
||||
:options="agencyAgreementList"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
is-clearable
|
||||
:input-debounce="0"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm" v-if="supplierList">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="t('Autonomous')"
|
||||
v-model="params.supplierFk"
|
||||
:options="supplierList"
|
||||
option-value="name"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
is-clearable
|
||||
:input-debounce="0"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInputDate
|
||||
v-model="params.created"
|
||||
:label="t('Date')"
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInputDate
|
||||
v-model="params.from"
|
||||
:label="t('From')"
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInputDate
|
||||
v-model="params.to"
|
||||
:label="t('To')"
|
||||
is-outlined
|
||||
is-clearable
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput v-model="params.packages" :label="t('Packages')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput v-model="params.m3" :label="t('m3')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput v-model="params.kmTotal" :label="t('Km')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput v-model="params.price" :label="t('Price')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput v-model="params.invoiceInFk" :label="t('Received')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
params:
|
||||
agencyModeFk: Agency route
|
||||
m3: m³
|
||||
from: From
|
||||
to: To
|
||||
date: Date
|
||||
agencyFk: Agency agreement
|
||||
packages: Packages
|
||||
price: Price
|
||||
invoiceInFk: Received
|
||||
supplierFk: Autonomous
|
||||
es:
|
||||
params:
|
||||
agencyModeFk: Agencia ruta
|
||||
m3: m³
|
||||
from: Desde
|
||||
to: Hasta
|
||||
date: Fecha
|
||||
agencyFk: Agencia Acuerdo
|
||||
packages: Bultos
|
||||
price: Precio
|
||||
invoiceInFk: Recibida
|
||||
supplierFk: Autónomos
|
||||
From: Desde
|
||||
To: Hasta
|
||||
Date: Fecha
|
||||
Agency route: Agencia Ruta
|
||||
Agency agreement: Agencia Acuerdo
|
||||
Packages: Bultos
|
||||
Price: Precio
|
||||
Received: Recibida
|
||||
Autonomous: Autónomos
|
||||
</i18n>
|
|
@ -11,12 +11,15 @@ import VnInputDate from 'components/common/VnInputDate.vue';
|
|||
import VnInput from 'components/common/VnInput.vue';
|
||||
import axios from 'axios';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import RouteSearchbar from "pages/Route/Card/RouteSearchbar.vue";
|
||||
import {useStateStore} from "stores/useStateStore";
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const shelvingId = route.params?.id || null;
|
||||
const isNew = Boolean(!shelvingId);
|
||||
const stateStore = useStateStore();
|
||||
const shelvingId = ref(route.params?.id || null);
|
||||
const isNew = Boolean(!shelvingId.value);
|
||||
const defaultInitialData = {
|
||||
agencyModeFk: null,
|
||||
created: null,
|
||||
|
@ -78,6 +81,11 @@ const onSave = (data, response) => {
|
|||
</script>
|
||||
<template>
|
||||
<VnSubToolbar />
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<RouteSearchbar />
|
||||
</Teleport>
|
||||
</template>
|
||||
<FetchData
|
||||
url="Workers/search"
|
||||
:filter="{ fields: ['id', 'nickname'] }"
|
||||
|
@ -89,7 +97,7 @@ const onSave = (data, response) => {
|
|||
<FetchData
|
||||
url="AgencyModes/isActive"
|
||||
:filter="{ fields: ['id', 'name'] }"
|
||||
sort-by="name ASC"
|
||||
sort-by="name"
|
||||
limit="30"
|
||||
@on-fetch="(data) => (agencyList = data)"
|
||||
auto-load
|
||||
|
@ -103,13 +111,13 @@ const onSave = (data, response) => {
|
|||
auto-load
|
||||
/>
|
||||
<FormModel
|
||||
:url="isNew ? null : `Routes/${shelvingId}`"
|
||||
:url="isNew ? null : `Routes/${route.params?.id}`"
|
||||
:url-create="isNew ? 'Routes' : null"
|
||||
:observe-form-changes="!isNew"
|
||||
:filter="routeFilter"
|
||||
model="route"
|
||||
:auto-load="!isNew"
|
||||
:form-initial-data="defaultInitialData"
|
||||
:form-initial-data="isNew ? defaultInitialData : null"
|
||||
@on-data-saved="onSave"
|
||||
>
|
||||
<template #form="{ data }">
|
||||
|
@ -131,7 +139,7 @@ const onSave = (data, response) => {
|
|||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }},{{ opt.code }}
|
||||
{{ opt.nickname }}, {{ opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
@ -212,6 +220,8 @@ const onSave = (data, response) => {
|
|||
<VnInput
|
||||
v-model="data.description"
|
||||
:label="t('Description')"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
@ -230,4 +240,5 @@ es:
|
|||
Hour finished: Hora fin
|
||||
Description: Descripción
|
||||
Is served: Se ha servido
|
||||
Created: Creado
|
||||
</i18n>
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
<script setup>
|
||||
import { useDialogPluginComponent } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import axios from 'axios';
|
||||
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([...useDialogPluginComponent.emits]);
|
||||
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
name: 'ticket',
|
||||
label: t('Ticket'),
|
||||
field: (row) => row.id,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'client',
|
||||
label: t('Client'),
|
||||
field: (row) => row.nickname,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'province',
|
||||
label: t('Province'),
|
||||
field: (row) => row?.address?.province?.name,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
label: t('City'),
|
||||
field: (row) => row?.address?.city,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'pc',
|
||||
label: t('PC'),
|
||||
field: (row) => row?.address?.postalCode,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
label: t('Address'),
|
||||
field: (row) => row?.address?.street,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'zone',
|
||||
label: t('Zone'),
|
||||
field: (row) => row?.zone?.name,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: '',
|
||||
sortable: false,
|
||||
align: 'right',
|
||||
},
|
||||
]);
|
||||
|
||||
const refreshKey = ref(0);
|
||||
const selectedRows = ref([]);
|
||||
const isLoading = ref(true);
|
||||
const ticketList = ref([]);
|
||||
|
||||
const onDataFetched = (data) => {
|
||||
ticketList.value = data;
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
const selectedTicket = ref(null);
|
||||
const unlinkZone = async (ticket) => {
|
||||
await axios.post('Routes/unlink', {
|
||||
agencyModeId: ticket?.agencyModeFk,
|
||||
zoneId: ticket.zoneFk,
|
||||
});
|
||||
selectedTicket.value = null;
|
||||
refreshKey.value++;
|
||||
};
|
||||
|
||||
const setTicketsRoute = async () => {
|
||||
if (!selectedRows.value?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
(selectedRows.value || [])
|
||||
.filter((ticket) => ticket?.id)
|
||||
.map((ticket) =>
|
||||
axios.patch(`Routes/${$props.id}/insertTicket`, { ticketId: ticket.id })
|
||||
)
|
||||
);
|
||||
await axios.post(`Routes/${$props.id}/updateVolume`);
|
||||
emit('ok');
|
||||
emit('hide');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
:key="refreshKey"
|
||||
:url="`Routes/${$props.id}/getSuggestedTickets`"
|
||||
@on-fetch="onDataFetched"
|
||||
auto-load
|
||||
/>
|
||||
<QDialog
|
||||
:model-value="Boolean(selectedTicket)"
|
||||
@update:model-value="(value) => (!value ? (selectedTicket = null) : null)"
|
||||
class="confirmation-dialog"
|
||||
>
|
||||
<QCard style="min-width: 350px">
|
||||
<QCardSection>
|
||||
<p class="text-h6 q-ma-none">{{ t('Unlink selected zone?') }}</p>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn
|
||||
flat
|
||||
:label="t('globals.cancel')"
|
||||
v-close-popup
|
||||
class="text-primary"
|
||||
/>
|
||||
<QBtn color="primary" v-close-popup @click="unlinkZone(selectedTicket)">
|
||||
{{ t('Unlink') }}
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
<QDialog ref="dialogRef" @hide="onDialogHide">
|
||||
<QCard class="full-width dialog">
|
||||
<QCardSection class="row items-center q-pb-none">
|
||||
<div class="text-h6">{{ t('Tickets to add') }}</div>
|
||||
<QSpace />
|
||||
<QBtn icon="close" flat round dense v-close-popup />
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<QTable
|
||||
v-model:selected="selectedRows"
|
||||
:columns="columns"
|
||||
:loading="isLoading"
|
||||
:rows="ticketList"
|
||||
flat
|
||||
row-key="id"
|
||||
selection="multiple"
|
||||
:rows-per-page-options="[0]"
|
||||
hide-pagination
|
||||
>
|
||||
<template #body-cell-ticket="props">
|
||||
<QTd :props="props">
|
||||
<span class="link">
|
||||
{{ props.value }}
|
||||
<TicketDescriptorProxy :id="props?.row?.id" />
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-client="props">
|
||||
<QTd :props="props">
|
||||
<span class="link">
|
||||
{{ props.value }}
|
||||
<CustomerDescriptorProxy :id="props?.row?.clientFk" />
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-actions="props">
|
||||
<QTd :props="props">
|
||||
<QIcon
|
||||
name="link_off"
|
||||
size="xs"
|
||||
color="primary"
|
||||
class="cursor-pointer"
|
||||
@click="selectedTicket = props?.row"
|
||||
>
|
||||
<QTooltip
|
||||
>{{
|
||||
t('Unlink zone', {
|
||||
zone: props?.row?.zone?.name,
|
||||
agency: props?.row?.agencyMode?.name,
|
||||
})
|
||||
}}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn flat v-close-popup class="text-primary"
|
||||
>{{ t('globals.cancel') }}
|
||||
</QBtn>
|
||||
<QBtn
|
||||
color="primary"
|
||||
:disable="!selectedRows?.length"
|
||||
@click="setTicketsRoute"
|
||||
>{{ t('globals.add') }}
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.confirmation-dialog {
|
||||
z-index: 6001 !important;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
max-width: 1280px;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
Unlink zone: Unlink zone {zone} from agency {agency}
|
||||
es:
|
||||
Tickets to add: Tickets a añadir
|
||||
Client: Cliente
|
||||
Province: Provincia
|
||||
City: Población
|
||||
PC: CP
|
||||
Address: Dirección
|
||||
Zone: Zona
|
||||
Unlink zone: Desvincular zona {zone} de agencia {agency}
|
||||
Unlink: Desvincular
|
||||
</i18n>
|
|
@ -9,6 +9,7 @@ const { t } = useI18n();
|
|||
data-key="RouteList"
|
||||
:label="t('Search route')"
|
||||
:info="t('You can search by route reference')"
|
||||
custom-route-redirect-name="RouteList"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ import VnLv from 'components/ui/VnLv.vue';
|
|||
import { QIcon } from 'quasar';
|
||||
import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters';
|
||||
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import axios from 'axios';
|
||||
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
|
||||
import { openBuscaman } from 'src/utils/buscaman';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -104,7 +105,7 @@ const ticketColumns = ref([
|
|||
label: t('route.summary.ticket'),
|
||||
field: (row) => row?.id,
|
||||
sortable: false,
|
||||
align: 'right',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'observations',
|
||||
|
@ -114,30 +115,20 @@ const ticketColumns = ref([
|
|||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
const openBuscaman = async (route, ticket) => {
|
||||
if (!route.vehicleFk) throw new Error(`The route doesn't have a vehicle`);
|
||||
const response = await axios.get(`Routes/${route.vehicleFk}/getDeliveryPoint`);
|
||||
if (!response.data)
|
||||
throw new Error(`The route's vehicle doesn't have a delivery point`);
|
||||
|
||||
const address = `${response.data}+to:${ticket.postalCode} ${ticket.city} ${ticket.street}`;
|
||||
window.open(
|
||||
'https://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=' +
|
||||
encodeURI(address),
|
||||
'_blank'
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<CardSummary ref="summary" :url="`Routes/${entityId}/summary`">
|
||||
<template #header-left>
|
||||
<RouterLink :to="{ name: `RouteSummary`, params: { id: entityId } }">
|
||||
<QIcon name="open_in_new" color="white" size="sm" />
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<RouteSearchbar />
|
||||
</Teleport>
|
||||
</template>
|
||||
<div class="q-pa-md full-width">
|
||||
<CardSummary
|
||||
ref="summary"
|
||||
:url="`Routes/${entityId}/summary`"
|
||||
:entity-id="entityId"
|
||||
>
|
||||
<template #header="{ entity }">
|
||||
<span>{{ `${entity?.route.id} - ${entity?.route?.description}` }}</span>
|
||||
</template>
|
||||
|
@ -207,9 +198,10 @@ const openBuscaman = async (route, ticket) => {
|
|||
</QCard>
|
||||
|
||||
<QCard class="vn-max">
|
||||
<div class="header">
|
||||
<a class="header" :href="`#/route/${entityId}/tickets`">
|
||||
{{ t('route.summary.tickets') }}
|
||||
</div>
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
</a>
|
||||
<QTable
|
||||
:columns="ticketColumns"
|
||||
:rows="entity?.tickets"
|
||||
|
@ -222,7 +214,7 @@ const openBuscaman = async (route, ticket) => {
|
|||
<QTd auto-width>
|
||||
<span
|
||||
class="text-primary cursor-pointer"
|
||||
@click="openBuscaman(entity?.route, row)"
|
||||
@click="openBuscaman(entity?.route?.vehicleFk, [row])"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
|
@ -237,7 +229,7 @@ const openBuscaman = async (route, ticket) => {
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-ticket="{ value, row }">
|
||||
<QTd auto-width>
|
||||
<QTd auto-width class="text-center">
|
||||
<span class="text-primary cursor-pointer">
|
||||
{{ value }}
|
||||
<TicketDescriptorProxy :id="row?.id" />
|
||||
|
|
|
@ -91,6 +91,24 @@ function downloadPdfs() {
|
|||
}
|
||||
</script>
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
|
||||
<Teleport to="#actions-append">
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
flat
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
<div class="column items-center">
|
||||
<div class="list">
|
||||
<VnPaginate
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<script setup>
|
||||
import { useDialogPluginComponent } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { reactive, ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import RoadmapAddStopForm from 'pages/Route/Roadmap/RoadmapAddStopForm.vue';
|
||||
|
||||
const emit = defineEmits([...useDialogPluginComponent.emits]);
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
roadmapFk: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
const roadmapStopForm = reactive({
|
||||
roadmapFk: Number(props.roadmapFk) || 0,
|
||||
warehouseFk: null,
|
||||
eta: new Date().toISOString(),
|
||||
description: '',
|
||||
});
|
||||
|
||||
const updateDefaultStop = (data) => {
|
||||
const eta = new Date(data.etd);
|
||||
eta.setDate(eta.getDate() + 1);
|
||||
roadmapStopForm.eta = eta.toISOString();
|
||||
};
|
||||
|
||||
const onSave = async () => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
await axios.post('ExpeditionTrucks', { ...roadmapStopForm });
|
||||
emit('ok');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
emit('hide');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
:url="`Roadmaps/${roadmapFk}`"
|
||||
auto-load
|
||||
:filter="{ fields: ['etd'] }"
|
||||
@on-fetch="updateDefaultStop"
|
||||
/>
|
||||
<QDialog ref="dialogRef" @hide="onDialogHide">
|
||||
<QCard class="q-pa-lg">
|
||||
<QCardSection class="row absolute absolute-top z-fullscreen">
|
||||
<QSpace />
|
||||
<QBtn icon="close" flat round dense v-close-popup />
|
||||
</QCardSection>
|
||||
<RoadmapAddStopForm
|
||||
:roadmap-fk="roadmapFk"
|
||||
:form-data="roadmapStopForm"
|
||||
layout="dialog"
|
||||
/>
|
||||
<QCardActions align="right">
|
||||
<QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup />
|
||||
<QBtn
|
||||
:label="t('globals.confirm')"
|
||||
color="primary"
|
||||
:loading="isLoading"
|
||||
@click="onSave"
|
||||
unelevated
|
||||
/>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.card-section {
|
||||
gap: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Warehouse: Almacén
|
||||
ETA date: Fecha ETA
|
||||
ETA time: Hora ETA
|
||||
Description: Descripción
|
||||
</i18n>
|
|
@ -0,0 +1,104 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
|
||||
const props = defineProps({
|
||||
formData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
roadmapFk: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const warehouseList = ref([]);
|
||||
const form = computed(() => props.formData);
|
||||
const isDialog = computed(() => props.layout === 'dialog');
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
url="Warehouses"
|
||||
auto-load
|
||||
:filter="{ fields: ['id', 'name'] }"
|
||||
sort-by="name"
|
||||
limit="30"
|
||||
@on-fetch="(data) => (warehouseList = data)"
|
||||
/>
|
||||
<div :class="[isDialog ? 'column' : 'form-gap', 'full-width flex']">
|
||||
<QCardSection class="flex-grow q-px-none flex-1">
|
||||
<VnSelectFilter
|
||||
v-model.number="form.warehouseFk"
|
||||
class="full-width"
|
||||
:label="t('Warehouse')"
|
||||
:options="warehouseList"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
hide-selected
|
||||
autofocus
|
||||
:input-debounce="0"
|
||||
/>
|
||||
</QCardSection>
|
||||
<div :class="[!isDialog ? 'flex-2' : null, 'form-gap flex no-wrap']">
|
||||
<QCardSection class="row q-px-none flex-1">
|
||||
<VnInputDate
|
||||
v-model="form.eta"
|
||||
class="full-width"
|
||||
:label="t('ETA date')"
|
||||
/>
|
||||
</QCardSection>
|
||||
<QCardSection class="row q-px-none flex-1">
|
||||
<VnInputTime
|
||||
v-model="form.eta"
|
||||
class="full-width"
|
||||
:label="t('ETA hour')"
|
||||
/>
|
||||
</QCardSection>
|
||||
</div>
|
||||
<QCardSection class="q-px-none flex-1">
|
||||
<VnInput
|
||||
v-model="form.description"
|
||||
class="full-width"
|
||||
:label="t('Description')"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
/>
|
||||
</QCardSection>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.form-gap {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flex-2 {
|
||||
flex: 2;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
Warehouse: Almacén
|
||||
ETA date: Fecha ETA
|
||||
ETA hour: Hora ETA
|
||||
Description: Descripción
|
||||
Remove stop: Eliminar parada
|
||||
Add stop: Añadir parada
|
||||
</i18n>
|
|
@ -0,0 +1,143 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import {ref} from "vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const supplierList = ref([]);
|
||||
const filter = { include: [{ relation: 'supplier' }] };
|
||||
const onSave = (data, response) => {
|
||||
router.push({ name: 'RoadmapSummary', params: { id: response?.id } });
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<VnSubToolbar />
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
auto-load
|
||||
:filter="{ fields: ['id', 'nickname'] }"
|
||||
sort-by="nickname"
|
||||
limit="30"
|
||||
@on-fetch="(data) => (supplierList = data)"
|
||||
/>
|
||||
<FormModel
|
||||
:url="`Roadmaps/${route.params?.id}`"
|
||||
observe-form-changes
|
||||
:filter="filter"
|
||||
model="roadmap"
|
||||
auto-load
|
||||
@on-data-saved="onSave"
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput v-model="data.name" :label="t('Roadmap')" clearable />
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputDate v-model="data.etd" :label="t('ETD date')" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputTime v-model="data.etd" :label="t('ETD hour')" />
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.tractorPlate"
|
||||
:label="t('Tractor plate')"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.trailerPlate"
|
||||
:label="t('Trailer plate')"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Carrier')"
|
||||
v-model="data.supplierFk"
|
||||
:options="supplierList"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
:input-debounce="0"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel
|
||||
>{{ opt.id }} - {{ opt.nickname }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.price"
|
||||
:label="t('Price')"
|
||||
type="number"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.driverName"
|
||||
:label="t('Driver name')"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.phone"
|
||||
:label="t('Phone')"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.observations"
|
||||
:label="t('Observations')"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Roadmap: Troncal
|
||||
ETD date: Fecha ETD
|
||||
ETD hour: Hora ETD
|
||||
Tractor plate: Matrícula tractor
|
||||
Trailer plate: Matrícula trailer
|
||||
Carrier: Transportista
|
||||
Price: Precio
|
||||
Driver name: Nombre del conductor
|
||||
Phone: Teléfono
|
||||
Observations: Observaciones
|
||||
</i18n>
|
|
@ -0,0 +1,21 @@
|
|||
<script setup>
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import RoadmapDescriptor from "pages/Route/Roadmap/RoadmapDescriptor.vue";
|
||||
|
||||
const stateStore = useStateStore();
|
||||
</script>
|
||||
<template>
|
||||
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
|
||||
<QScrollArea class="fit">
|
||||
<RoadmapDescriptor />
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage>
|
||||
<RouterView></RouterView>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
|
@ -0,0 +1,56 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const defaultInitialData = {
|
||||
etd: Date.vnNew().toISOString(),
|
||||
};
|
||||
const onSave = (data, response) => {
|
||||
router.push({ name: 'RoadmapSummary', params: { id: response?.id } });
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<VnSubToolbar />
|
||||
<FormModel
|
||||
:url="null"
|
||||
url-create="Roadmaps"
|
||||
model="roadmap"
|
||||
:observe-form-changes="false"
|
||||
:auto-load="false"
|
||||
:form-initial-data="defaultInitialData"
|
||||
@on-data-saved="onSave"
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.name"
|
||||
:label="t('Roadmap')"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputDate v-model="data.etd" :label="t('ETD date')" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputTime v-model="data.etd" :label="t('ETD hour')" />
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Roadmap: Troncal
|
||||
ETD date: Fecha ETD
|
||||
ETD hour: Hora ETD
|
||||
</i18n>
|
|
@ -0,0 +1,63 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import useCardDescription from 'composables/useCardDescription';
|
||||
import {dashIfEmpty, toDateHour} from 'src/filters';
|
||||
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||
import RoadmapDescriptorMenu from "pages/Route/Roadmap/RoadmapDescriptorMenu.vue";
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const entityId = computed(() => {
|
||||
return $props.id || route.params.id;
|
||||
});
|
||||
|
||||
const filter = { include: [{ relation: 'supplier' }] };
|
||||
const data = ref(useCardDescription());
|
||||
const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardDescriptor
|
||||
module="Roadmap"
|
||||
:url="`Roadmaps/${entityId}`"
|
||||
:filter="filter"
|
||||
:title="data.title"
|
||||
:subtitle="data.subtitle"
|
||||
data-key="Roadmap"
|
||||
@on-fetch="setData"
|
||||
>
|
||||
<template #body="{ entity }">
|
||||
<VnLv :label="t('Roadmap')" :value="entity?.name" />
|
||||
<VnLv :label="t('ETD')" :value="toDateHour(entity?.etd)" />
|
||||
<VnLv :label="t('Carrier')">
|
||||
<template #value>
|
||||
<span class="link" v-if="entity?.supplier?.id">
|
||||
{{ dashIfEmpty(entity?.supplier?.nickname) }}
|
||||
<SupplierDescriptorProxy :id="entity?.supplier?.id" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
</template>
|
||||
<template #menu="{ entity }">
|
||||
<RoadmapDescriptorMenu :route="entity" />
|
||||
</template>
|
||||
</CardDescriptor>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Roadmap: Troncal
|
||||
Carrier: Transportista
|
||||
</i18n>
|
|
@ -0,0 +1,57 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
|
||||
const props = defineProps({
|
||||
route: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
||||
function confirmRemove() {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Confirm deletion'),
|
||||
message: t('Are you sure you want to delete this roadmap?'),
|
||||
promise: remove,
|
||||
},
|
||||
})
|
||||
.onOk(async () => await router.push({ name: 'RouteRoadmap' }));
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (!props.route.id) {
|
||||
return;
|
||||
}
|
||||
await axios.delete(`Roadmaps/${props.route.id}`);
|
||||
quasar.notify({
|
||||
message: t('globals.dataDeleted'),
|
||||
type: 'positive',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<QItem @click="confirmRemove" v-ripple clickable>
|
||||
<QItemSection avatar>
|
||||
<QIcon name="delete" />
|
||||
</QItemSection>
|
||||
<QItemSection>{{ t('Delete roadmap') }}</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Confirm deletion: Confirmar eliminación
|
||||
Are you sure you want to delete this roadmap?: ¿Estás seguro de que quieres eliminar esta troncal?
|
||||
Delete roadmap: Eliminar troncal
|
||||
</i18n>
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import RoadmapDescriptor from 'pages/Route/Roadmap/RoadmapDescriptor.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<QPopupProxy>
|
||||
<RoadmapDescriptor v-if="$props.id" :id="$props.id" />
|
||||
</QPopupProxy>
|
||||
</template>
|
|
@ -0,0 +1,183 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['search']);
|
||||
|
||||
const supplierList = ref([]);
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
case 'tractorPlate':
|
||||
case 'trailerPlate':
|
||||
case 'driverName':
|
||||
case 'phone':
|
||||
return { [param]: { like: `%${value}%` } };
|
||||
case 'supplierFk':
|
||||
case 'price':
|
||||
return { [param]: value };
|
||||
case 'from':
|
||||
return { etd: { gte: value } };
|
||||
case 'to':
|
||||
return { etd: { lte: value } };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
:filter="{ fields: ['id', 'nickname'] }"
|
||||
sort-by="nickname"
|
||||
limit="30"
|
||||
@on-fetch="(data) => (supplierList = data)"
|
||||
auto-load
|
||||
/>
|
||||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:search-button="true"
|
||||
:expr-builder="exprBuilder"
|
||||
@search="emit('search')"
|
||||
>
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||
<span>{{ formatFn(tag.value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body="{ params }">
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInputDate v-model="params.from" :label="t('From')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInputDate v-model="params.to" :label="t('To')" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
v-model="params.tractorPlate"
|
||||
:label="t('Tractor Plate')"
|
||||
is-outlined
|
||||
clearable
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
v-model="params.trailerPlate"
|
||||
:label="t('Trailer Plate')"
|
||||
is-outlined
|
||||
clearable
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem v-if="supplierList" class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
:label="t('Carrier')"
|
||||
v-model="params.supplierFk"
|
||||
:options="supplierList"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
:input-debounce="0"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel
|
||||
>{{ opt.id }} - {{ opt.nickname }}</QItemLabel
|
||||
>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
v-model="params.price"
|
||||
:label="t('Price')"
|
||||
type="number"
|
||||
is-outlined
|
||||
clearable
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
v-model="params.driverName"
|
||||
:label="t('Driver name')"
|
||||
is-outlined
|
||||
clearable
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
v-model="params.phone"
|
||||
:label="t('Phone')"
|
||||
is-outlined
|
||||
clearable
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
params:
|
||||
tractorPlate: Tractor Plate
|
||||
trailerPlate: Trailer Plate
|
||||
supplierFk: Carrier
|
||||
price: Price
|
||||
driverName: Driver name
|
||||
phone: Phone
|
||||
from: From
|
||||
to: To
|
||||
es:
|
||||
params:
|
||||
tractorPlate: Matrícula del tractor
|
||||
trailerPlate: Matrícula del trailer
|
||||
supplierFk: Transportista
|
||||
price: Precio
|
||||
driverName: Nombre del conductor
|
||||
phone: Teléfono
|
||||
from: Desde
|
||||
to: Hasta
|
||||
From: Desde
|
||||
To: Hasta
|
||||
Tractor Plate: Matrícula del tractor
|
||||
Trailer Plate: Matrícula del trailer
|
||||
Carrier: Transportista
|
||||
Price: Precio
|
||||
Driver name: Nombre del conductor
|
||||
Phone: Teléfono
|
||||
</i18n>
|
|
@ -0,0 +1,95 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import RoadmapAddStopForm from "pages/Route/Roadmap/RoadmapAddStopForm.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const roadmapStopsCrudRef = ref(null);
|
||||
|
||||
const defaultStop = ref({
|
||||
roadmapFk: Number(route.params?.id) || 0,
|
||||
eta: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const updateDefaultStop = (data) => {
|
||||
const eta = new Date(data.etd);
|
||||
eta.setDate(eta.getDate() + 1);
|
||||
defaultStop.value.eta = eta.toISOString();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (roadmapStopsCrudRef.value) roadmapStopsCrudRef.value.reload();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VnSubToolbar />
|
||||
<FetchData
|
||||
:url="`Roadmaps/${route.params?.id}`"
|
||||
auto-load
|
||||
:filter="{ fields: ['etd'] }"
|
||||
@on-fetch="updateDefaultStop"
|
||||
/>
|
||||
<div class="q-pa-lg">
|
||||
<CrudModel
|
||||
ref="roadmapStopsCrudRef"
|
||||
data-key="ExpeditionTrucks"
|
||||
url="ExpeditionTrucks"
|
||||
model="ExpeditionTrucks"
|
||||
:filter="{ where: { roadmapFk: route.params?.id } }"
|
||||
:default-remove="false"
|
||||
:data-required="defaultStop"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard class="q-pa-md">
|
||||
<QCardSection
|
||||
v-for="(row, index) in rows"
|
||||
:key="index"
|
||||
class="row no-wrap"
|
||||
>
|
||||
<RoadmapAddStopForm :roadmap-fk="route.params?.id" :form-data="row" />
|
||||
<div class="col-1 row justify-center items-center">
|
||||
<QIcon
|
||||
name="delete"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
@click="roadmapStopsCrudRef.remove([row])"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Remove stop') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<QIcon
|
||||
name="add"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
@click="roadmapStopsCrudRef.insert()"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Add stop') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</template>
|
||||
</CrudModel>
|
||||
</div>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Warehouse: Almacén
|
||||
ETA date: Fecha ETA
|
||||
ETA hour: Hora ETA
|
||||
Description: Descripción
|
||||
Remove stop: Eliminar parada
|
||||
Add stop: Añadir parada
|
||||
</i18n>
|
|
@ -0,0 +1,172 @@
|
|||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { QIcon, useQuasar } from 'quasar';
|
||||
import { dashIfEmpty, toDateHour } from 'src/filters';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||
import VnLinkPhone from 'components/ui/VnLinkPhone.vue';
|
||||
import RoadmapAddStopDialog from 'pages/Route/Roadmap/RoadmapAddStopDialog.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const quasar = useQuasar();
|
||||
|
||||
const summary = ref(null);
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
const isDialog = Boolean($props.id);
|
||||
const hideRightDrawer = () => {
|
||||
if (!isDialog) {
|
||||
stateStore.rightDrawer = false;
|
||||
}
|
||||
};
|
||||
onMounted(hideRightDrawer);
|
||||
onUnmounted(hideRightDrawer);
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
name: 'warehouse',
|
||||
label: t('Warehouse'),
|
||||
field: (row) => dashIfEmpty(row?.warehouse?.name),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'ETA',
|
||||
label: t('ETA'),
|
||||
field: (row) => toDateHour(row?.eta),
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
const filter = {
|
||||
include: [
|
||||
{ relation: 'supplier' },
|
||||
{ relation: 'worker' },
|
||||
{
|
||||
relation: 'expeditionTruck',
|
||||
scope: { include: [{ relation: 'warehouse' }] },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const openAddStopDialog = () => {
|
||||
quasar
|
||||
.dialog({
|
||||
component: RoadmapAddStopDialog,
|
||||
componentProps: { roadmapFk: entityId.value },
|
||||
})
|
||||
.onOk(() => summary.value.fetch());
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="q-pa-md full-width">
|
||||
<CardSummary ref="summary" :url="`Roadmaps/${entityId}`" :filter="filter">
|
||||
<template #header-left>
|
||||
<RouterLink :to="{ name: `RoadmapSummary`, params: { id: entityId } }">
|
||||
<QIcon name="open_in_new" color="white" size="sm" />
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #header="{ entity }">
|
||||
<span>{{ `${entity?.id} - ${entity?.name}` }}</span>
|
||||
</template>
|
||||
<template #body="{ entity }">
|
||||
<QCard class="vn-one">
|
||||
<VnLv :label="t('Carrier')">
|
||||
<template #value>
|
||||
<span class="link" v-if="entity?.supplier?.id">
|
||||
{{ dashIfEmpty(entity?.supplier?.nickname) }}
|
||||
<SupplierDescriptorProxy :id="entity?.supplier?.id" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('ETD')" :value="toDateHour(entity?.etd)" />
|
||||
<VnLv
|
||||
:label="t('Tractor Plate')"
|
||||
:value="dashIfEmpty(entity?.tractorPlate)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('Trailer Plate')"
|
||||
:value="dashIfEmpty(entity?.trailerPlate)"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<VnLv :label="t('Phone')" :value="dashIfEmpty(entity?.phone)">
|
||||
<template #value>
|
||||
<span>
|
||||
{{ dashIfEmpty(entity?.phone) }}
|
||||
<VnLinkPhone :phone-number="entity?.phone" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('Worker')"
|
||||
:value="
|
||||
entity?.worker?.firstName
|
||||
? `${entity?.worker?.firstName} ${entity?.worker?.lastName}`
|
||||
: '-'
|
||||
"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('Observations')"
|
||||
:value="dashIfEmpty(entity?.observations)"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-max">
|
||||
<div class="flex items-center">
|
||||
<a class="header" :href="`#/route/roadmap/${entityId}/stops`">
|
||||
{{ t('Stops') }}
|
||||
<QIcon name="open_in_new" color="primary">
|
||||
<QTooltip>
|
||||
{{ t('Go to stops') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</a>
|
||||
<QIcon
|
||||
name="add_circle"
|
||||
color="primary"
|
||||
class="q-ml-lg header cursor-pointer"
|
||||
@click.stop="openAddStopDialog"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Add stop') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:rows="entity?.expeditionTruck"
|
||||
:rows-per-page-options="[0]"
|
||||
row-key="id"
|
||||
flat
|
||||
hide-pagination
|
||||
/>
|
||||
</QCard>
|
||||
</template>
|
||||
</CardSummary>
|
||||
</div>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Carrier: Transportista
|
||||
Tractor Plate: Matrícula tractor
|
||||
Trailer Plate: Matrícula trailer
|
||||
Phone: Teléfono
|
||||
Worker: Trabajador
|
||||
Observations: Observaciones
|
||||
Stops: Paradas
|
||||
Warehouse: Almacén
|
||||
Go to stops: Ir a paradas
|
||||
Add stop: Añadir parada
|
||||
</i18n>
|
|
@ -0,0 +1,344 @@
|
|||
<script setup>
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import RouteDescriptorProxy from 'pages/Route/Card/RouteDescriptorProxy.vue';
|
||||
import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
|
||||
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import useNotify from 'composables/useNotify';
|
||||
import RouteAutonomousFilter from 'pages/Route/Card/RouteAutonomousFilter.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
|
||||
import { useSummaryDialog } from 'composables/useSummaryDialog';
|
||||
import VnDms from 'components/common/VnDms.vue';
|
||||
import { useState } from 'composables/useState';
|
||||
import axios from 'axios';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
const router = useRouter();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
||||
onMounted(() => (stateStore.rightDrawer = true));
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
const dmsDialog = ref({
|
||||
show: false,
|
||||
initialForm: {},
|
||||
rowsToCreateInvoiceIn: [],
|
||||
});
|
||||
const selectedRows = ref([]);
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'ID',
|
||||
label: t('ID'),
|
||||
field: (row) => row.routeFk,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: t('Date'),
|
||||
field: (row) => toDate(row.created),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'agency-route',
|
||||
label: t('Agency route'),
|
||||
field: (row) => row?.agencyModeName,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'agency-agreement',
|
||||
label: t('Agency agreement'),
|
||||
field: (row) => row?.agencyAgreement,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'packages',
|
||||
label: t('Packages'),
|
||||
field: (row) => dashIfEmpty(row.packages),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'volume',
|
||||
label: 'm³',
|
||||
field: (row) => dashIfEmpty(row.m3),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'km',
|
||||
label: t('Km'),
|
||||
field: (row) => dashIfEmpty(row?.kmTotal),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: t('Price'),
|
||||
field: (row) => (row?.price ? toCurrency(row.price) : '-'),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'received',
|
||||
label: t('Received'),
|
||||
field: (row) => row?.invoiceInFk,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'autonomous',
|
||||
label: t('Autonomous'),
|
||||
field: (row) => row?.supplierName,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: '',
|
||||
sortable: false,
|
||||
align: 'right',
|
||||
},
|
||||
]);
|
||||
|
||||
const refreshKey = ref(0);
|
||||
|
||||
const total = computed(() => selectedRows.value.reduce((item) => item?.price || 0, 0));
|
||||
|
||||
const openDmsUploadDialog = async () => {
|
||||
dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value
|
||||
.filter(
|
||||
(agencyTerm) => agencyTerm.supplierFk === selectedRows.value?.[0].supplierFk
|
||||
)
|
||||
.map((agencyTerm) => ({
|
||||
routeFk: agencyTerm.routeFk,
|
||||
supplierFk: agencyTerm.supplierFk,
|
||||
created: agencyTerm.created,
|
||||
totalPrice: total.value,
|
||||
}));
|
||||
|
||||
if (dmsDialog.value.rowsToCreateInvoiceIn.length !== selectedRows.value.length) {
|
||||
dmsDialog.value.rowsToCreateInvoiceIn = [];
|
||||
dmsDialog.value.initialForm = null;
|
||||
return notify(t('Two autonomous cannot be counted at the same time'), 'negative');
|
||||
}
|
||||
|
||||
const dmsType = await axios
|
||||
.get('DmsTypes/findOne', {
|
||||
filter: {
|
||||
where: { code: 'invoiceIn' },
|
||||
},
|
||||
})
|
||||
.then((res) => res.data);
|
||||
|
||||
dmsDialog.value.initialForm = {
|
||||
description: selectedRows.value?.[0].supplierName,
|
||||
companyFk: user.value.companyFk,
|
||||
warehouseFk: user.value.warehouseFk,
|
||||
dmsTypeFk: dmsType?.id,
|
||||
};
|
||||
dmsDialog.value.show = true;
|
||||
};
|
||||
|
||||
const onDmsSaved = async (dms, response) => {
|
||||
if (response) {
|
||||
await axios.post('AgencyTerms/createInvoiceIn', {
|
||||
rows: dmsDialog.value.rowsToCreateInvoiceIn,
|
||||
dms: response.data,
|
||||
});
|
||||
}
|
||||
dmsDialog.value.show = false;
|
||||
dmsDialog.value.initialForm = null;
|
||||
dmsDialog.value.rowsToCreateInvoiceIn = [];
|
||||
refreshKey.value++;
|
||||
};
|
||||
|
||||
function navigateToRouteSummary(event, row) {
|
||||
router.push({ name: 'RouteSummary', params: { id: row.routeFk } });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<VnSearchbar
|
||||
data-key="RouteAutonomousList"
|
||||
:label="t('Search autonomous')"
|
||||
:info="t('You can search by autonomous reference')"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport to="#actions-append">
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
flat
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<RouteAutonomousFilter data-key="RouteAutonomousList" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPage class="column items-center">
|
||||
<div class="route-list">
|
||||
<div class="q-pa-md">
|
||||
<QCard class="vn-one q-py-sm q-px-lg">
|
||||
<VnLv class="flex">
|
||||
<template #label>
|
||||
<span class="text-h6">{{ t('Total') }}</span>
|
||||
</template>
|
||||
<template #value>
|
||||
<span class="text-h6 q-ml-md">{{ toCurrency(total) }}</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
</QCard>
|
||||
</div>
|
||||
<VnPaginate
|
||||
:key="refreshKey"
|
||||
data-key="RouteAutonomousList"
|
||||
url="AgencyTerms/filter"
|
||||
:limit="20"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="q-pa-md">
|
||||
<QTable
|
||||
v-model:selected="selectedRows"
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
flat
|
||||
row-key="routeFk"
|
||||
selection="multiple"
|
||||
:rows-per-page-options="[0]"
|
||||
hide-pagination
|
||||
@row-click="navigateToRouteSummary"
|
||||
>
|
||||
<template #body-cell-ID="props">
|
||||
<QTd :props="props">
|
||||
<span class="link" @click.stop>
|
||||
{{ props.value }}
|
||||
<RouteDescriptorProxy :id="props.value" />
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-received="props">
|
||||
<QTd :props="props">
|
||||
<span :class="props.value && 'link'" @click.stop>
|
||||
{{ dashIfEmpty(props.value) }}
|
||||
<InvoiceInDescriptorProxy
|
||||
v-if="props.value"
|
||||
:id="props.value"
|
||||
/>
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-autonomous="props">
|
||||
<QTd :props="props">
|
||||
<span class="link" @click.stop>
|
||||
{{ props.value }}
|
||||
<SupplierDescriptorProxy
|
||||
v-if="props.row?.supplierFk"
|
||||
:id="props.row?.supplierFk"
|
||||
/>
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-actions="props">
|
||||
<QTd :props="props">
|
||||
<div class="table-actions">
|
||||
<QIcon
|
||||
name="preview"
|
||||
size="xs"
|
||||
color="primary"
|
||||
@click.stop="
|
||||
viewSummary(
|
||||
props?.row?.routeFk,
|
||||
RouteSummary
|
||||
)
|
||||
"
|
||||
>
|
||||
<QTooltip>{{ t('Preview') }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QPageSticky :offset="[20, 20]" v-if="selectedRows?.length">
|
||||
<QBtn
|
||||
fab
|
||||
icon="vn:invoice-in-create"
|
||||
color="primary"
|
||||
@click="openDmsUploadDialog"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Create invoiceIn') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
<QDialog v-model="dmsDialog.show">
|
||||
<VnDms
|
||||
url="dms/uploadFile"
|
||||
model="AgencyTerms"
|
||||
:form-initial-data="dmsDialog.initialForm"
|
||||
@on-data-saved="onDmsSaved"
|
||||
/>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.route-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
Search autonomous: Buscar autónomos
|
||||
You can search by autonomous reference: Puedes buscar por referencia del autónomo
|
||||
Create invoiceIn: Crear factura recibida
|
||||
Two autonomous cannot be counted at the same time: Dos autonónomos no pueden ser contabilizados al mismo tiempo
|
||||
Date: Fecha
|
||||
Agency route: Agencia Ruta
|
||||
Agency agreement: Agencia Acuerdo
|
||||
Packages: Bultos
|
||||
Price: Precio
|
||||
Received: Recibida
|
||||
Autonomous: Autónomos
|
||||
Preview: Vista previa
|
||||
</i18n>
|
|
@ -2,9 +2,8 @@
|
|||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { dashIfEmpty, toDate, toHour } from 'src/filters';
|
||||
import { dashIfEmpty, toHour } from 'src/filters';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { useValidator } from 'composables/useValidator';
|
||||
|
@ -18,10 +17,13 @@ import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
|
|||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const { validate } = useValidator();
|
||||
const quasar = useQuasar();
|
||||
const session = useSession();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
||||
|
@ -35,7 +37,7 @@ const columns = computed(() => [
|
|||
label: t('ID'),
|
||||
field: (row) => row.id,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'worker',
|
||||
|
@ -70,7 +72,7 @@ const columns = computed(() => [
|
|||
label: 'm³',
|
||||
field: (row) => dashIfEmpty(row.m3),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
|
@ -113,24 +115,6 @@ const updateRoute = async (route) => {
|
|||
}
|
||||
};
|
||||
|
||||
const updateVehicle = (row, vehicle) => {
|
||||
row.vehicleFk = vehicle.id;
|
||||
row.vehiclePlateNumber = vehicle.numberPlate;
|
||||
updateRoute(row);
|
||||
};
|
||||
|
||||
const updateAgency = (row, agency) => {
|
||||
row.agencyModeFk = agency.id;
|
||||
row.agencyName = agency.name;
|
||||
updateRoute(row);
|
||||
};
|
||||
|
||||
const updateWorker = (row, worker) => {
|
||||
row.workerFk = worker.id;
|
||||
row.workerUserName = worker.name;
|
||||
updateRoute(row);
|
||||
};
|
||||
|
||||
const confirmationDialog = ref(false);
|
||||
const startingDate = ref(null);
|
||||
|
||||
|
@ -169,6 +153,20 @@ const markAsServed = () => {
|
|||
refreshKey.value++;
|
||||
startingDate.value = null;
|
||||
};
|
||||
|
||||
const openTicketsDialog = (id) => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
quasar
|
||||
.dialog({
|
||||
component: RouteListTicketsDialog,
|
||||
componentProps: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
.onOk(() => refreshKey.value++);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -278,199 +276,136 @@ const markAsServed = () => {
|
|||
:rows-per-page-options="[0]"
|
||||
hide-pagination
|
||||
:pagination="{ sortBy: 'ID', descending: true }"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
>
|
||||
<template #body-cell-worker="props">
|
||||
<QTd :props="props">
|
||||
{{ props.row?.workerUserName }}
|
||||
<QPopupEdit
|
||||
:model-value="props.row.workerFk"
|
||||
v-slot="scope"
|
||||
buttons
|
||||
@update:model-value="
|
||||
(worker) => updateWorker(props.row, worker)
|
||||
"
|
||||
<template #body-cell-worker="{ row }">
|
||||
<QTd>
|
||||
<VnSelectFilter
|
||||
:label="t('Worker')"
|
||||
v-model="row.workerFk"
|
||||
:options="workers"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
hide-selected
|
||||
dense
|
||||
:emit-value="false"
|
||||
:rules="validate('Route.workerFk')"
|
||||
:is-clearable="false"
|
||||
@update:model-value="updateRoute(row)"
|
||||
>
|
||||
<VnSelectFilter
|
||||
:label="t('Worker')"
|
||||
v-model="scope.value"
|
||||
:options="workers"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
autofocus
|
||||
:emit-value="false"
|
||||
:rules="validate('Route.workerFk')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
>
|
||||
<template #option="{ opt, itemProps }">
|
||||
<QItem
|
||||
v-bind="itemProps"
|
||||
class="q-pa-xs row items-center"
|
||||
<template #option="{ opt, itemProps }">
|
||||
<QItem
|
||||
v-bind="itemProps"
|
||||
class="q-pa-xs row items-center"
|
||||
>
|
||||
<QItemSection
|
||||
class="col-9 justify-center"
|
||||
>
|
||||
<QItemSection
|
||||
class="col-9 justify-center"
|
||||
>
|
||||
<span>{{ opt.name }}</span>
|
||||
<span class="text-grey">{{
|
||||
opt.nickname
|
||||
}}</span>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</QPopupEdit>
|
||||
<span>{{ opt.name }}</span>
|
||||
<span class="text-grey">{{
|
||||
opt.nickname
|
||||
}}</span>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-agency="props">
|
||||
<QTd :props="props">
|
||||
{{ props.row?.agencyName }}
|
||||
<QPopupEdit
|
||||
:model-value="props.row.agencyModeFk"
|
||||
v-slot="scope"
|
||||
buttons
|
||||
@update:model-value="
|
||||
(agency) => updateAgency(props.row, agency)
|
||||
"
|
||||
>
|
||||
<VnSelectFilter
|
||||
:label="t('Agency')"
|
||||
v-model="scope.value"
|
||||
:options="agencyList"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
autofocus
|
||||
:emit-value="false"
|
||||
:rules="validate('route.agencyFk')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
<template #body-cell-agency="{ row }">
|
||||
<QTd>
|
||||
<VnSelectFilter
|
||||
:label="t('Agency')"
|
||||
v-model="row.agencyModeFk"
|
||||
:options="agencyList"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
dense
|
||||
:emit-value="false"
|
||||
:rules="validate('route.agencyFk')"
|
||||
:is-clearable="false"
|
||||
@update:model-value="updateRoute(row)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-vehicle="props">
|
||||
<QTd :props="props">
|
||||
{{ props.row?.vehiclePlateNumber }}
|
||||
<QPopupEdit
|
||||
:model-value="props.row.vehicleFk"
|
||||
v-slot="scope"
|
||||
buttons
|
||||
@update:model-value="
|
||||
(vehicle) => updateVehicle(props.row, vehicle)
|
||||
"
|
||||
>
|
||||
<VnSelectFilter
|
||||
:label="t('Vehicle')"
|
||||
v-model="scope.value"
|
||||
:options="vehicleList"
|
||||
option-value="id"
|
||||
option-label="numberPlate"
|
||||
hide-selected
|
||||
autofocus
|
||||
:emit-value="false"
|
||||
:rules="validate('route.vehicleFk')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
<template #body-cell-vehicle="{ row }">
|
||||
<QTd>
|
||||
<VnSelectFilter
|
||||
:label="t('Vehicle')"
|
||||
v-model="row.vehicleFk"
|
||||
:options="vehicleList"
|
||||
option-value="id"
|
||||
option-label="numberPlate"
|
||||
hide-selected
|
||||
dense
|
||||
:emit-value="false"
|
||||
:rules="validate('route.vehicleFk')"
|
||||
:is-clearable="false"
|
||||
@update:model-value="updateRoute(row)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-date="props">
|
||||
<QTd :props="props">
|
||||
{{ toDate(props.row?.created) }}
|
||||
<QPopupEdit
|
||||
v-model="props.row.created"
|
||||
v-slot="scope"
|
||||
@update:model-value="updateRoute(props.row)"
|
||||
buttons
|
||||
>
|
||||
<VnInputDate
|
||||
v-model="scope.value"
|
||||
autofocus
|
||||
:label="t('Date')"
|
||||
:rules="validate('route.created')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
<template #body-cell-date="{ row }">
|
||||
<QTd class="table-input-cell">
|
||||
<VnInputDate
|
||||
v-model="row.created"
|
||||
hide-bottom-space
|
||||
dense
|
||||
:label="t('Date')"
|
||||
:rules="validate('route.created')"
|
||||
:is-clearable="false"
|
||||
@update:model-value="updateRoute(row)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-description="props">
|
||||
<QTd :props="props">
|
||||
{{ props.row?.description }}
|
||||
<QPopupEdit
|
||||
v-model="props.row.description"
|
||||
v-slot="scope"
|
||||
@update:model-value="updateRoute(props.row)"
|
||||
buttons
|
||||
>
|
||||
<VnInput
|
||||
v-model="scope.value"
|
||||
autofocus
|
||||
:label="t('Description')"
|
||||
:rules="validate('route.description')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
<template #body-cell-description="{ row }">
|
||||
<QTd class="table-input-cell">
|
||||
<VnInput
|
||||
v-model="row.description"
|
||||
:label="t('Description')"
|
||||
:rules="validate('route.description')"
|
||||
:is-clearable="false"
|
||||
dense
|
||||
@update:model-value="updateRoute(row)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-started="props">
|
||||
<QTd :props="props">
|
||||
{{ toHour(props.row.started) }}
|
||||
<QPopupEdit
|
||||
v-model="props.row.started"
|
||||
v-slot="scope"
|
||||
buttons
|
||||
@update:model-value="updateRoute(props.row)"
|
||||
>
|
||||
<VnInputTime
|
||||
v-model="scope.value"
|
||||
autofocus
|
||||
:label="t('Hour started')"
|
||||
:rules="validate('route.started')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
<template #body-cell-started="{ row }">
|
||||
<QTd class="table-input-cell">
|
||||
<VnInputTime
|
||||
v-model="row.started"
|
||||
:label="t('Hour started')"
|
||||
:rules="validate('route.started')"
|
||||
:is-clearable="false"
|
||||
hide-bottom-space
|
||||
dense
|
||||
@update:model-value="updateRoute(row)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-finished="props">
|
||||
<QTd :props="props">
|
||||
{{ toHour(props.row.finished) }}
|
||||
<QPopupEdit
|
||||
v-model="props.row.finished"
|
||||
v-slot="scope"
|
||||
buttons
|
||||
@update:model-value="updateRoute(props.row)"
|
||||
>
|
||||
<VnInputTime
|
||||
v-model="scope.value"
|
||||
autofocus
|
||||
:label="t('Hour finished')"
|
||||
:rules="validate('route.finished')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
<template #body-cell-finished="{ row }">
|
||||
<QTd class="table-input-cell">
|
||||
<VnInputTime
|
||||
v-model="row.finished"
|
||||
autofocus
|
||||
:label="t('Hour finished')"
|
||||
:rules="validate('route.finished')"
|
||||
:is-clearable="false"
|
||||
hide-bottom-space
|
||||
dense
|
||||
@update:model-value="updateRoute(row)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-actions="props">
|
||||
<QTd :props="props">
|
||||
<div class="flex items-center table-actions">
|
||||
<div class="flex items-center no-wrap table-actions">
|
||||
<QIcon
|
||||
name="vn:ticketAdd"
|
||||
size="xs"
|
||||
color="primary"
|
||||
class="cursor-pointer"
|
||||
@click="openTicketsDialog(props?.row?.id)"
|
||||
>
|
||||
<QTooltip>{{ t('Add ticket') }}</QTooltip>
|
||||
</QIcon>
|
||||
|
@ -485,6 +420,20 @@ const markAsServed = () => {
|
|||
>
|
||||
<QTooltip>{{ t('Preview') }}</QTooltip>
|
||||
</QIcon>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'RouteSummary',
|
||||
params: { id: props?.row?.id },
|
||||
}"
|
||||
>
|
||||
<QIcon
|
||||
name="vn:eye"
|
||||
size="xs"
|
||||
color="primary"
|
||||
>
|
||||
<QTooltip>{{ t('Summary') }}</QTooltip>
|
||||
</QIcon>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
|
@ -505,8 +454,13 @@ const markAsServed = () => {
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-input-cell {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.route-list {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
|
@ -535,4 +489,5 @@ es:
|
|||
Download selected routes as PDF: Descargar rutas seleccionadas como PDF
|
||||
Add ticket: Añadir tickets
|
||||
Preview: Vista previa
|
||||
Summary: Resumen
|
||||
</i18n>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
</script>
|
||||
<template>
|
||||
<VnLog model="Route" url="/RouteLogs"></VnLog>
|
||||
</template>
|
|
@ -0,0 +1,296 @@
|
|||
<script setup>
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { dashIfEmpty, toDateHour } from 'src/filters';
|
||||
import { useQuasar } from 'quasar';
|
||||
import toCurrency from 'filters/toCurrency';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import RoadmapFilter from 'pages/Route/Roadmap/RoadmapFilter.vue';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import axios from 'axios';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import {useSummaryDialog} from "composables/useSummaryDialog";
|
||||
import RoadmapSummary from "pages/Route/Roadmap/RoadmapSummary.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const quasar = useQuasar();
|
||||
const router = useRouter();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
||||
const to = Date.vnNew();
|
||||
to.setDate(to.getDate() + 1);
|
||||
to.setHours(0, 0, 0, 0);
|
||||
|
||||
const from = Date.vnNew();
|
||||
from.setDate(from.getDate());
|
||||
from.setHours(0, 0, 0, 0);
|
||||
|
||||
onMounted(() => (stateStore.rightDrawer = true));
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
const selectedRows = ref([]);
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'roadmap',
|
||||
label: t('Roadmap'),
|
||||
field: (row) => row.name,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'ETD',
|
||||
label: t('ETD'),
|
||||
field: (row) => toDateHour(row.etd),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'carrier',
|
||||
label: t('Carrier'),
|
||||
field: (row) => row.supplier?.nickname,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'plate',
|
||||
label: t('Plate'),
|
||||
field: (row) => row.tractorPlate,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: t('Price'),
|
||||
field: (row) => toCurrency(row.price),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'observations',
|
||||
label: t('Observations'),
|
||||
field: (row) => dashIfEmpty(row.observations),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: '',
|
||||
sortable: false,
|
||||
align: 'right',
|
||||
},
|
||||
]);
|
||||
|
||||
const refreshKey = ref(0);
|
||||
const isCloneDialogOpen = ref(false);
|
||||
const etdDate = ref(null);
|
||||
|
||||
const filter = {
|
||||
include: { relation: 'supplier', scope: { fields: ['nickname'] } },
|
||||
where: {
|
||||
and: [{ etd: { gte: from } }, { etd: { lte: to } }],
|
||||
},
|
||||
};
|
||||
|
||||
const cloneSelection = async () => {
|
||||
await axios.post('Roadmaps/clone', {
|
||||
etd: etdDate.value,
|
||||
ids: selectedRows.value.map((row) => row?.id),
|
||||
});
|
||||
refreshKey.value++;
|
||||
etdDate.value = null;
|
||||
};
|
||||
|
||||
const removeSelection = async () => {
|
||||
await Promise.all(
|
||||
selectedRows.value.map((roadmap) => {
|
||||
axios.delete(`Roadmaps/${roadmap.id}`);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
function confirmRemove() {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Selected roadmaps will be removed'),
|
||||
message: t('Are you sure you want to continue?'),
|
||||
promise: removeSelection,
|
||||
},
|
||||
})
|
||||
.onOk(() => refreshKey.value++);
|
||||
}
|
||||
|
||||
function navigateToRoadmapSummary(event, row) {
|
||||
router.push({ name: 'RoadmapSummary', params: { id: row.id } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<VnSearchbar
|
||||
data-key="RoadmapList"
|
||||
:label="t('Search roadmaps')"
|
||||
:info="t('You can search by roadmap reference')"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport to="#actions-append">
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
flat
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<RoadmapFilter data-key="RoadmapList" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QDialog v-model="isCloneDialogOpen">
|
||||
<QCard style="min-width: 350px">
|
||||
<QCardSection>
|
||||
<p class="text-h6 q-ma-none">
|
||||
{{ t('Select the estimated date of departure (ETD)') }}
|
||||
</p>
|
||||
</QCardSection>
|
||||
|
||||
<QCardSection class="q-pt-none">
|
||||
<VnInputDate :label="t('ETD')" v-model="etdDate" autofocus />
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
|
||||
<QBtn color="primary" v-close-popup @click="cloneSelection">
|
||||
{{ t('Clone') }}
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
<QPage class="column items-center">
|
||||
<VnSubToolbar class="bg-vn-dark justify-end">
|
||||
<template #st-actions>
|
||||
<QBtn
|
||||
icon="vn:clone"
|
||||
color="primary"
|
||||
class="q-mr-sm"
|
||||
:disable="!selectedRows?.length"
|
||||
@click="isCloneDialogOpen = true"
|
||||
>
|
||||
<QTooltip>{{ t('Clone Selected Routes') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
icon="delete"
|
||||
color="primary"
|
||||
class="q-mr-sm"
|
||||
:disable="!selectedRows?.length"
|
||||
@click="confirmRemove"
|
||||
>
|
||||
<QTooltip>{{ t('Delete roadmap(s)') }}</QTooltip>
|
||||
</QBtn>
|
||||
</template>
|
||||
</VnSubToolbar>
|
||||
<div class="route-list">
|
||||
<VnPaginate
|
||||
:key="refreshKey"
|
||||
data-key="RoadmapList"
|
||||
url="Roadmaps"
|
||||
:limit="20"
|
||||
:filter="filter"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="q-pa-md">
|
||||
<QTable
|
||||
v-model:selected="selectedRows"
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
flat
|
||||
row-key="id"
|
||||
selection="multiple"
|
||||
:rows-per-page-options="[0]"
|
||||
hide-pagination
|
||||
:pagination="{ sortBy: 'ID', descending: true }"
|
||||
@row-click="navigateToRoadmapSummary"
|
||||
>
|
||||
<template #body-cell-carrier="props">
|
||||
<QTd :props="props">
|
||||
<span v-if="props.value" class="link" @click.stop>
|
||||
{{ props.value }}
|
||||
<SupplierDescriptorProxy
|
||||
:id="props.row?.supplier?.id"
|
||||
/>
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-actions="props">
|
||||
<QTd :props="props">
|
||||
<div class="flex items-center table-actions">
|
||||
<QIcon
|
||||
name="preview"
|
||||
size="xs"
|
||||
color="primary"
|
||||
@click.stop="viewSummary(props?.row?.id, RoadmapSummary)"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<QTooltip>{{ t('Preview') }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QPageSticky :offset="[20, 20]">
|
||||
<RouterLink :to="{ name: 'RouteRoadmapCreate' }">
|
||||
<QBtn fab icon="add" color="primary" />
|
||||
<QTooltip>
|
||||
{{ t('Create roadmap') }}
|
||||
</QTooltip>
|
||||
</RouterLink>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.route-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
Search roadmaps: Buscar troncales
|
||||
You can search by roadmap reference: Puedes buscar por referencia de la troncal
|
||||
Delete roadmap(s): Eliminar troncal(es)
|
||||
Selected roadmaps will be removed: Las troncales seleccionadas serán eliminadas
|
||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||
The date can't be empty: La fecha no puede estar vacía
|
||||
Clone Selected Routes: Clonar rutas seleccionadas
|
||||
Create roadmap: Crear troncal
|
||||
Roadmap: Troncal
|
||||
Carrier: Transportista
|
||||
Plate: Matrícula
|
||||
Price: Precio
|
||||
Observations: Observaciones
|
||||
Preview: Vista previa
|
||||
</i18n>
|
|
@ -0,0 +1,453 @@
|
|||
<script setup>
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import axios from 'axios';
|
||||
import { QIcon, useQuasar } from 'quasar';
|
||||
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
|
||||
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { openBuscaman } from 'src/utils/buscaman';
|
||||
import SendSmsDialog from 'components/common/SendSmsDialog.vue';
|
||||
import RouteSearchbar from "pages/Route/Card/RouteSearchbar.vue";
|
||||
import {useStateStore} from "stores/useStateStore";
|
||||
|
||||
const { t } = useI18n();
|
||||
const quasar = useQuasar();
|
||||
const route = useRoute();
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const selectedRows = ref([]);
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'order',
|
||||
label: t('Order'),
|
||||
field: (row) => dashIfEmpty(row?.priority),
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'street',
|
||||
label: t('Street'),
|
||||
field: (row) => row?.street,
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
label: t('City'),
|
||||
field: (row) => row?.city,
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'pc',
|
||||
label: t('PC'),
|
||||
field: (row) => row?.postalCode,
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'client',
|
||||
label: t('Client'),
|
||||
field: (row) => row?.nickname,
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'warehouse',
|
||||
label: t('Warehouse'),
|
||||
field: (row) => row?.warehouseName,
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'packages',
|
||||
label: t('Packages'),
|
||||
field: (row) => row?.packages,
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'volume',
|
||||
label: 'm³',
|
||||
field: (row) => row?.volume,
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'packaging',
|
||||
label: t('Packaging'),
|
||||
field: (row) => row?.ipt,
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'ticket',
|
||||
label: t('Ticket'),
|
||||
field: (row) => row?.id,
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'observations',
|
||||
label: '',
|
||||
field: (row) => row?.ticketObservation,
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
const refreshKey = ref(0);
|
||||
const confirmationDialog = ref(false);
|
||||
const startingDate = ref(null);
|
||||
const routeEntity = ref(null);
|
||||
const ticketList = ref([]);
|
||||
|
||||
const cloneRoutes = () => {
|
||||
axios.post('Routes/clone', {
|
||||
created: startingDate.value,
|
||||
ids: selectedRows.value.map((row) => row?.id),
|
||||
});
|
||||
refreshKey.value++;
|
||||
startingDate.value = null;
|
||||
};
|
||||
|
||||
const deletePriorities = async () => {
|
||||
try {
|
||||
await Promise.all(
|
||||
selectedRows.value.map((ticket) =>
|
||||
axios.patch(`Tickets/${ticket?.id}/`, { priority: null })
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
refreshKey.value++;
|
||||
}
|
||||
};
|
||||
|
||||
const setOrderedPriority = async () => {
|
||||
try {
|
||||
await Promise.all(
|
||||
ticketList.value.map((ticket, index) =>
|
||||
axios.patch(`Tickets/${ticket?.id}/`, { priority: index + 1 })
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
refreshKey.value++;
|
||||
}
|
||||
};
|
||||
|
||||
const sortRoutes = async () => {
|
||||
await axios.get(`Routes/${route.params?.id}/guessPriority/`);
|
||||
refreshKey.value++;
|
||||
};
|
||||
|
||||
const updatePriority = async (ticket, priority = null) => {
|
||||
return axios.patch(`Tickets/${ticket?.id}/`, {
|
||||
priority: priority || ticket?.priority,
|
||||
});
|
||||
};
|
||||
|
||||
const setHighestPriority = async (ticket, ticketList) => {
|
||||
const highestPriority = Math.max(...ticketList.map((item) => item.priority)) + 1;
|
||||
if (highestPriority - 1 !== ticket.priority) {
|
||||
const response = await updatePriority(ticket, highestPriority);
|
||||
ticket.priority = response.data.priority;
|
||||
}
|
||||
};
|
||||
|
||||
const goToBuscaman = async (ticket = null) => {
|
||||
await openBuscaman(
|
||||
routeEntity.value?.vehicleFk,
|
||||
ticket ? [ticket] : selectedRows.value
|
||||
);
|
||||
};
|
||||
|
||||
const openTicketsDialog = () => {
|
||||
quasar
|
||||
.dialog({
|
||||
component: RouteListTicketsDialog,
|
||||
componentProps: {
|
||||
id: route.params.id,
|
||||
},
|
||||
})
|
||||
.onOk(() => refreshKey.value++);
|
||||
};
|
||||
|
||||
const removeTicket = async (ticket) => {
|
||||
await axios.patch(`Tickets/${ticket?.id}/`, { routeFk: null });
|
||||
await axios.post(`Routes/${route?.params?.id}/updateVolume`);
|
||||
refreshKey.value++;
|
||||
};
|
||||
|
||||
const confirmRemove = (ticket) => {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Confirm removal from route'),
|
||||
message: t('Are you sure you want to remove this ticket from the route?'),
|
||||
promise: () => removeTicket(ticket),
|
||||
},
|
||||
})
|
||||
.onOk(() => refreshKey.value++);
|
||||
};
|
||||
|
||||
const openSmsDialog = async () => {
|
||||
const clientsId = [];
|
||||
const clientsPhone = [];
|
||||
|
||||
for (let ticket of selectedRows.value) {
|
||||
clientsId.push(ticket?.clientFk);
|
||||
const { data: client } = await axios.get(`Clients/${ticket?.clientFk}`);
|
||||
clientsPhone.push(client.phone);
|
||||
}
|
||||
|
||||
quasar.dialog({
|
||||
component: SendSmsDialog,
|
||||
componentProps: {
|
||||
title: t('Send SMS to the selected tickets'),
|
||||
url: 'Routes/sendSms',
|
||||
destinationFk: clientsId.toString(),
|
||||
destination: clientsPhone.toString(),
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<RouteSearchbar />
|
||||
</Teleport>
|
||||
</template>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (routeEntity = data)"
|
||||
auto-load
|
||||
:url="`Routes/${route.params?.id}`"
|
||||
:filter="{
|
||||
fields: ['vehicleFk'],
|
||||
}"
|
||||
/>
|
||||
<QDialog v-model="confirmationDialog">
|
||||
<QCard style="min-width: 350px">
|
||||
<QCardSection>
|
||||
<p class="text-h6 q-ma-none">{{ t('Select the starting date') }}</p>
|
||||
</QCardSection>
|
||||
|
||||
<QCardSection class="q-pt-none">
|
||||
<VnInputDate
|
||||
:label="t('Stating date')"
|
||||
v-model="startingDate"
|
||||
autofocus
|
||||
/>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
|
||||
<QBtn color="primary" v-close-popup @click="cloneRoutes">
|
||||
{{ t('Clone') }}
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
<QPage class="column items-center">
|
||||
<QToolbar class="bg-vn-dark justify-end">
|
||||
<div id="st-actions" class="q-pa-sm">
|
||||
<QBtn icon="vn:wand" color="primary" class="q-mr-sm" @click="sortRoutes">
|
||||
<QTooltip>{{ t('Sort routes') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
icon="vn:buscaman"
|
||||
color="primary"
|
||||
class="q-mr-sm"
|
||||
:disable="!selectedRows?.length"
|
||||
@click="goToBuscaman()"
|
||||
>
|
||||
<QTooltip>{{ t('Open buscaman') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
icon="filter_alt"
|
||||
color="primary"
|
||||
class="q-mr-sm filled-icon"
|
||||
:disable="!selectedRows?.length"
|
||||
@click="deletePriorities"
|
||||
>
|
||||
<QTooltip>{{ t('Delete priority') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
icon="format_list_numbered"
|
||||
color="primary"
|
||||
class="q-mr-sm"
|
||||
@click="setOrderedPriority"
|
||||
>
|
||||
<QTooltip
|
||||
>{{
|
||||
t('Renumber all tickets in the order you see on the screen')
|
||||
}}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
icon="sms"
|
||||
color="primary"
|
||||
class="q-mr-sm filled-icon"
|
||||
:disable="!selectedRows?.length"
|
||||
@click="openSmsDialog"
|
||||
>
|
||||
<QTooltip>{{ t('Send SMS to all clients') }}</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</QToolbar>
|
||||
<div class="route-list">
|
||||
<VnPaginate
|
||||
:key="refreshKey"
|
||||
data-key="RouteTicketList"
|
||||
url="Routes/getTickets"
|
||||
:filter="{ id: route.params.id }"
|
||||
:order="['priority ASC']"
|
||||
auto-load
|
||||
@on-fetch="(data) => (ticketList = data)"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="q-pa-md">
|
||||
<QTable
|
||||
v-model:selected="selectedRows"
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:rows-per-page-options="[0]"
|
||||
row-key="id"
|
||||
flat
|
||||
hide-pagination
|
||||
selection="multiple"
|
||||
>
|
||||
<template #body-cell-order="{ row }">
|
||||
<QTd class="order-field">
|
||||
<div class="flex no-wrap items-center">
|
||||
<QIcon
|
||||
name="low_priority"
|
||||
size="xs"
|
||||
class="q-mr-md cursor-pointer"
|
||||
color="primary"
|
||||
@click="setHighestPriority(row, rows)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Assign highest priority') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<VnInput
|
||||
v-model="row.priority"
|
||||
dense
|
||||
@blur="updatePriority(row)"
|
||||
/>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-city="{ value, row }">
|
||||
<QTd auto-width>
|
||||
<span
|
||||
class="text-primary cursor-pointer"
|
||||
@click="goToBuscaman(row)"
|
||||
>
|
||||
{{ value }}
|
||||
<QTooltip>{{ t('Open buscaman') }}</QTooltip>
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-client="{ value, row }">
|
||||
<QTd auto-width>
|
||||
<span class="text-primary cursor-pointer">
|
||||
{{ value }}
|
||||
<CustomerDescriptorProxy :id="row?.clientFk" />
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-ticket="{ value, row }">
|
||||
<QTd auto-width class="text-center">
|
||||
<span class="text-primary cursor-pointer">
|
||||
{{ value }}
|
||||
<TicketDescriptorProxy :id="row?.id" />
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-observations="{ value, row }">
|
||||
<QTd auto-width>
|
||||
<div class="flex items-center no-wrap table-actions">
|
||||
<QIcon
|
||||
name="delete"
|
||||
color="primary"
|
||||
class="cursor-pointer"
|
||||
size="xs"
|
||||
@click="confirmRemove(row)"
|
||||
>
|
||||
<QTooltip>{{ t('globals.remove') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="value"
|
||||
name="vn:notes"
|
||||
color="primary"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<QTooltip>{{ value }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QPageSticky :offset="[20, 20]">
|
||||
<QBtn fab icon="add" color="primary" @click="openTicketsDialog">
|
||||
<QTooltip>
|
||||
{{ t('Add ticket') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.route-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.order-field {
|
||||
max-width: 140px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filled-icon {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
Order: Orden
|
||||
Street: Dirección fiscal
|
||||
City: Población
|
||||
PC: CP
|
||||
Client: Cliente
|
||||
Warehouse: Almacén
|
||||
Packages: Bultos
|
||||
Packaging: Encajado
|
||||
Confirm removal from route: Quitar de la ruta
|
||||
Are you sure you want to remove this ticket from the route?: ¿Seguro que quieres quitar este ticket de la ruta?
|
||||
Sort routes: Ordenar rutas
|
||||
Open buscaman: Abrir buscaman
|
||||
Delete priority: Borrar orden
|
||||
Renumber all tickets in the order you see on the screen: Renumerar todos los tickets con el orden que ves por pantalla
|
||||
Assign highest priority: Asignar máxima prioridad
|
||||
Send SMS to all clients: Mandar sms a todos los clientes de las rutas
|
||||
Send SMS to the selected tickets: Enviar SMS a los tickets seleccionados
|
||||
Add ticket: Añadir ticket
|
||||
</i18n>
|
|
@ -6,7 +6,7 @@ import { useStateStore } from 'stores/useStateStore';
|
|||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue';
|
||||
import VnUserLink from "components/ui/VnUserLink.vue";
|
||||
import VnUserLink from 'components/ui/VnUserLink.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -71,11 +71,11 @@ const filter = {
|
|||
<template #body="{ entity }">
|
||||
<QCard class="vn-one">
|
||||
<RouterLink
|
||||
class="header"
|
||||
class="header header-link"
|
||||
:to="{ name: 'ShelvingBasicData', params: { id: entityId } }"
|
||||
>
|
||||
{{ t('shelving.pageTitles.basicData') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
<QIcon name="open_in_new" />
|
||||
</RouterLink>
|
||||
<VnLv :label="t('shelving.summary.code')" :value="entity.code" />
|
||||
<VnLv
|
||||
|
|
|
@ -18,13 +18,16 @@ const quasar = useQuasar();
|
|||
const { notify } = useNotify();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const bankEntitiesRef = ref(null);
|
||||
const supplier = ref(null);
|
||||
const supplierAccountRef = ref(null);
|
||||
const wireTransferFk = ref(null);
|
||||
const bankEntitiesOptions = ref([]);
|
||||
|
||||
const onBankEntityCreated = (data) => {
|
||||
bankEntitiesOptions.value.push(data);
|
||||
const onBankEntityCreated = async (dataSaved, rowData) => {
|
||||
await bankEntitiesRef.value.fetch();
|
||||
rowData.bankEntityFk = dataSaved.id;
|
||||
};
|
||||
|
||||
const onChangesSaved = () => {
|
||||
|
@ -63,6 +66,7 @@ onMounted(() => {
|
|||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
ref="bankEntitiesRef"
|
||||
url="BankEntities"
|
||||
@on-fetch="(data) => (bankEntitiesOptions = data)"
|
||||
auto-load
|
||||
|
@ -114,13 +118,16 @@ onMounted(() => {
|
|||
:label="t('worker.create.bankEntity')"
|
||||
v-model="row.bankEntityFk"
|
||||
:options="bankEntitiesOptions"
|
||||
option-label="name"
|
||||
option-label="bic"
|
||||
option-value="id"
|
||||
hide-selected
|
||||
>
|
||||
<template #form>
|
||||
<CreateBankEntityForm
|
||||
@on-data-saved="onBankEntityCreated($event)"
|
||||
@on-data-saved="
|
||||
(_, requestResponse) =>
|
||||
onBankEntityCreated(requestResponse, row)
|
||||
"
|
||||
:show-entity-field="false"
|
||||
/>
|
||||
</template>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue