Merge branch 'dev' into 7391-defaulterMods
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Carlos Satorres 2024-05-27 11:58:11 +00:00
commit 4162e040cc
36 changed files with 355 additions and 271 deletions

12
Jenkinsfile vendored
View File

@ -54,7 +54,6 @@ pipeline {
} }
environment { environment {
PROJECT_NAME = 'lilium' PROJECT_NAME = 'lilium'
STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
} }
stages { stages {
stage('Install') { stage('Install') {
@ -104,15 +103,18 @@ pipeline {
when { when {
expression { PROTECTED_BRANCH } expression { PROTECTED_BRANCH }
} }
environment {
DOCKER_HOST = "${env.SWARM_HOST}"
}
steps { steps {
script { script {
def packageJson = readJSON file: 'package.json' def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version env.VERSION = packageJson.version
} }
sh "docker stack deploy --with-registry-auth --compose-file docker-compose.yml ${env.STACK_NAME}" withKubeConfig([
serverUrl: "$KUBERNETES_API",
credentialsId: 'kubernetes',
namespace: 'lilium'
]) {
sh 'kubectl set image deployment/lilium-$BRANCH_NAME lilium-$BRANCH_NAME=$REGISTRY/salix-frontend:$VERSION'
}
} }
} }
} }

View File

@ -1,17 +1,7 @@
version: '3.7' version: '3.7'
services: services:
main: main:
image: registry.verdnatura.es/salix-frontend:${BRANCH_NAME:?} image: registry.verdnatura.es/salix-frontend:${VERSION:?}
build: build:
context: . context: .
dockerfile: ./Dockerfile dockerfile: ./Dockerfile
ports:
- 4000
deploy:
replicas: ${FRONT_REPLICAS:?}
placement:
constraints:
- node.role == worker
resources:
limits:
memory: 1G

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.24.0", "version": "24.24.1",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",

View File

@ -48,7 +48,11 @@ const onDataSaved = async (formData, requestResponse) => {
/> />
<FetchData <FetchData
url="Tickets" url="Tickets"
:filter="{ fields: ['id', 'nickname'], order: 'shipped DESC', limit: 30 }" :filter="{
fields: ['id', 'nickname'],
where: { refFk: null },
order: 'shipped DESC',
}"
@on-fetch="(data) => (ticketsOptions = data)" @on-fetch="(data) => (ticketsOptions = data)"
auto-load auto-load
/> />

View File

@ -2,6 +2,7 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import isValidDate from 'filters/isValidDate'; import isValidDate from 'filters/isValidDate';
import VnInput from 'components/common/VnInput.vue';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -74,7 +75,7 @@ const styleAttrs = computed(() => {
@click="isPopupOpen = true" @click="isPopupOpen = true"
> >
<template #append> <template #append>
<QIcon name="schedule" class="cursor-pointer"> <QIcon name="Schedule" class="cursor-pointer">
<QPopupProxy <QPopupProxy
v-model="isPopupOpen" v-model="isPopupOpen"
cover cover

View File

@ -1,5 +1,7 @@
<script setup> <script setup>
defineProps({ import { computed } from 'vue';
const $props = defineProps({
maxLength: { maxLength: {
type: Number, type: Number,
required: true, required: true,
@ -8,53 +10,40 @@ defineProps({
type: Object, type: Object,
required: true, required: true,
}, },
tag: {
type: String,
required: false,
default: 'tag',
},
value: {
type: String,
required: false,
default: 'value',
},
});
const tags = computed(() => {
return Object.keys($props.item)
.filter((i) => i.startsWith(`${$props.tag}`))
.reduce((acc, tag) => {
const n = tag.split(`${$props.tag}`)[1];
const key = `${$props.tag}${n}`;
const value = `${$props.value}${n}`;
acc[$props.item[key] ?? key] = $props.item[value] ?? '';
return acc;
}, {});
}); });
</script> </script>
<template> <template>
<div class="fetchedTags"> <div class="fetchedTags">
<div class="wrap"> <div class="wrap">
<div <div
v-for="(val, key) in tags"
:key="key"
class="inline-tag" class="inline-tag"
:class="{ empty: !$props.item.value5 }" :title="`${key}: ${val}`"
:title="$props.item.tag5 + ': ' + $props.item.value5" :class="{ empty: !val }"
> >
{{ $props.item.value5 }} {{ val }}
</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> </div>
</div> </div>
@ -72,7 +61,7 @@ defineProps({
.inline-tag { .inline-tag {
height: 1rem; height: 1rem;
margin: 0.05rem; margin: 0.05rem;
color: $secondary; color: $color-font-secondary;
text-align: center; text-align: center;
font-size: smaller; font-size: smaller;
padding: 1px; padding: 1px;
@ -83,9 +72,8 @@ defineProps({
min-width: 4rem; min-width: 4rem;
max-width: 4rem; max-width: 4rem;
} }
.empty { .empty {
border: 1px solid $color-spacer-light; border: 1px solid #2b2b2b;
} }
} }
</style> </style>

View File

@ -59,12 +59,10 @@ const containerClasses = computed(() => {
// Clases para modificar el color de fecha seleccionada en componente QCalendarMonth // Clases para modificar el color de fecha seleccionada en componente QCalendarMonth
.q-dark div .q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button { .q-dark div .q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button {
background-color: $primary !important; background-color: $primary !important;
color: white !important;
} }
.q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button { .q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button {
background-color: $primary !important; background-color: $primary !important;
color: white !important;
} }
.q-calendar-month__head--weekday { .q-calendar-month__head--weekday {
@ -112,7 +110,6 @@ const containerClasses = computed(() => {
cursor: pointer; cursor: pointer;
} }
} }
.q-calendar-month__week--days > div:nth-child(6), .q-calendar-month__week--days > div:nth-child(6),
.q-calendar-month__week--days > div:nth-child(7) { .q-calendar-month__week--days > div:nth-child(7) {
// Cambia el color de los días sábado y domingo // Cambia el color de los días sábado y domingo
@ -150,7 +147,7 @@ const containerClasses = computed(() => {
.q-calendar-month__head--workweek, .q-calendar-month__head--workweek,
.q-calendar-month__head--weekday.q-calendar__center.q-calendar__ellipsis { .q-calendar-month__head--weekday.q-calendar__center.q-calendar__ellipsis {
text-transform: capitalize; text-transform: capitalize;
color: #777; color: var(---color-font-secondary);
font-weight: bold; font-weight: bold;
font-size: 0.8rem; font-size: 0.8rem;
text-align: center; text-align: center;

View File

@ -169,6 +169,13 @@ select:-webkit-autofill {
/* q-notification row items-stretch q-notification--standard bg-negative text-white */ /* q-notification row items-stretch q-notification--standard bg-negative text-white */
.q-card,
.q-table,
.q-table__bottom,
.q-drawer {
background-color: var(--vn-section-color);
}
input[type='number'] { input[type='number'] {
-moz-appearance: textfield; -moz-appearance: textfield;
} }

View File

@ -32,7 +32,7 @@ $primary-light: lighten($primary, 35%);
$dark-shadow-color: black; $dark-shadow-color: black;
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d; $layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
$spacing-md: 16px; $spacing-md: 16px;
$color-font-secondary: #777;
.bg-success { .bg-success {
background-color: $positive; background-color: $positive;
} }

View File

@ -866,7 +866,7 @@ worker:
sex: Sexo sex: Sexo
seniority: Antigüedad seniority: Antigüedad
fi: DNI/NIE/NIF fi: DNI/NIE/NIF
birth: Cumpleaños birth: Fecha de nacimiento
isFreelance: Autónomo isFreelance: Autónomo
isSsDiscounted: Bonificación SS isSsDiscounted: Bonificación SS
hasMachineryAuthorized: Autorizado para llevar maquinaria hasMachineryAuthorized: Autorizado para llevar maquinaria

View File

@ -410,7 +410,7 @@ const lockIconType = (groupingMode, mode) => {
<span v-if="props.row.item.subName" class="subName"> <span v-if="props.row.item.subName" class="subName">
{{ props.row.item.subName }} {{ props.row.item.subName }}
</span> </span>
<fetched-tags :item="props.row.item" :max-length="5" /> <FetchedTags :item="props.row.item" :max-length="5" />
</QTd> </QTd>
</QTr> </QTr>
<!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys --> <!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys -->

View File

@ -338,7 +338,7 @@ const fetchEntryBuys = async () => {
<span v-if="row.item.subName" class="subName"> <span v-if="row.item.subName" class="subName">
{{ row.item.subName }} {{ row.item.subName }}
</span> </span>
<fetched-tags :item="row.item" :max-length="5" /> <FetchedTags :item="row.item" :max-length="5" />
</QTd> </QTd>
</QTr> </QTr>
<!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys --> <!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys -->

View File

@ -707,7 +707,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</template> </template>
<template #body-cell-tags="{ row }"> <template #body-cell-tags="{ row }">
<QTd> <QTd>
<fetched-tags :item="row" :max-length="6" /> <FetchedTags :item="row" :max-length="6" />
</QTd> </QTd>
</template> </template>
<template #body-cell-entryFk="{ row }"> <template #body-cell-entryFk="{ row }">

View File

@ -459,7 +459,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
{{ row.name }} {{ row.name }}
</span> </span>
<ItemDescriptorProxy :id="row.itemFk" /> <ItemDescriptorProxy :id="row.itemFk" />
<fetched-tags :item="row" :max-length="6" /> <FetchedTags :item="row" :max-length="6" />
</QTd> </QTd>
</template> </template>
<template #body-cell-groupingPrice="props"> <template #body-cell-groupingPrice="props">

View File

@ -521,7 +521,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<template #body-cell-description="{ row }"> <template #body-cell-description="{ row }">
<QTd class="col"> <QTd class="col">
<span>{{ row.name }} {{ row.subName }}</span> <span>{{ row.name }} {{ row.subName }}</span>
<fetched-tags :item="row" :max-length="6" /> <FetchedTags :item="row" :max-length="6" />
</QTd> </QTd>
</template> </template>
<template #body-cell-isActive="{ row }"> <template #body-cell-isActive="{ row }">

View File

@ -81,3 +81,10 @@ itemTags:
searchbar: searchbar:
label: Search item label: Search item
info: Search by item id info: Search by item id
itemType:
shared:
code: Code
name: Name
worker: Worker
category: Category
temperature: Temperature

View File

@ -81,3 +81,10 @@ itemTags:
searchbar: searchbar:
label: Buscar artículo label: Buscar artículo
info: Buscar por id de artículo info: Buscar por id de artículo
itemType:
shared:
code: Código
name: Nombre
worker: Trabajador
category: Reino
temperature: Temperatura

View File

@ -176,10 +176,7 @@ const detailsColumns = ref([
{{ props.row.item.subName }} {{ props.row.item.subName }}
</span> </span>
</div> </div>
<fetched-tags <FetchedTags :item="props.row.item" :max-length="5" />
:item="props.row.item"
:max-length="5"
/>
</QTd> </QTd>
<QTd key="quantity" :props="props"> <QTd key="quantity" :props="props">
{{ props.row.quantity }} {{ props.row.quantity }}

View File

@ -160,7 +160,7 @@ async function confirmOrder() {
<span class="text-uppercase subname"> <span class="text-uppercase subname">
{{ row.item.subName }} {{ row.item.subName }}
</span> </span>
<fetched-tags :item="row.item" :max-length="5" /> <FetchedTags :item="row.item" :max-length="5" />
</div> </div>
<VnLv :label="t('item')" :value="String(row.item.id)" /> <VnLv :label="t('item')" :value="String(row.item.id)" />
<VnLv <VnLv

View File

@ -77,7 +77,7 @@ const loadVolumes = async (rows) => {
> >
<template #list-items> <template #list-items>
<div class="q-mb-sm"> <div class="q-mb-sm">
<fetched-tags :item="row.item" :max-length="5" /> <FetchedTags :item="row.item" :max-length="5" />
</div> </div>
<VnLv :label="t('item')" :value="row.item.id" /> <VnLv :label="t('item')" :value="row.item.id" />
<VnLv :label="t('subName')"> <VnLv :label="t('subName')">

View File

@ -12,6 +12,7 @@ import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue'; import VnInputTime from 'components/common/VnInputTime.vue';
import axios from 'axios'; import axios from 'axios';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue'; import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue'; import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue'; import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
@ -19,6 +20,7 @@ import { useSession } from 'composables/useSession';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue'; import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useArrayData } from 'composables/useArrayData';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -26,10 +28,7 @@ const { validate } = useValidator();
const quasar = useQuasar(); const quasar = useQuasar();
const session = useSession(); const session = useSession();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const visibleColumns = ref([]);
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const selectedRows = ref([]); const selectedRows = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -83,14 +82,14 @@ const columns = computed(() => [
}, },
{ {
name: 'started', name: 'started',
label: t('Hour started'), label: t('hourStarted'),
field: (row) => toHour(row.started), field: (row) => toHour(row.started),
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'finished', name: 'finished',
label: t('Hour finished'), label: t('hourFinished'),
field: (row) => toHour(row.finished), field: (row) => toHour(row.finished),
sortable: true, sortable: true,
align: 'left', align: 'left',
@ -109,7 +108,10 @@ const columns = computed(() => [
align: 'right', align: 'right',
}, },
]); ]);
const arrayData = useArrayData('EntryLatestBuys', {
url: 'Buys/latestBuysFilter',
order: ['itemFk DESC'],
});
const refreshKey = ref(0); const refreshKey = ref(0);
const workers = ref([]); const workers = ref([]);
const agencyList = ref([]); const agencyList = ref([]);
@ -121,7 +123,7 @@ const updateRoute = async (route) => {
return err; return err;
} }
}; };
const allColumnNames = ref([]);
const confirmationDialog = ref(false); const confirmationDialog = ref(false);
const startingDate = ref(null); const startingDate = ref(null);
@ -174,6 +176,13 @@ const openTicketsDialog = (id) => {
}) })
.onOk(() => refreshKey.value++); .onOk(() => refreshKey.value++);
}; };
onMounted(async () => {
stateStore.rightDrawer = true;
allColumnNames.value = columns.value.map((col) => col.name);
await arrayData.fetch({ append: false });
});
onUnmounted(() => (stateStore.rightDrawer = false));
</script> </script>
<template> <template>
@ -231,7 +240,16 @@ const openTicketsDialog = (id) => {
<FetchData url="AgencyModes" @on-fetch="(data) => (agencyList = data)" auto-load /> <FetchData url="AgencyModes" @on-fetch="(data) => (agencyList = data)" auto-load />
<FetchData url="Vehicles" @on-fetch="(data) => (vehicleList = data)" auto-load /> <FetchData url="Vehicles" @on-fetch="(data) => (vehicleList = data)" auto-load />
<QPage class="column items-center"> <QPage class="column items-center">
<VnSubToolbar class="justify-end"> <VnSubToolbar>
<template #st-data>
<TableVisibleColumns
class="LeftIcon"
:all-columns="allColumnNames"
table-code="routesList"
labels-traductions-path="globals"
@on-config-saved="visibleColumns = [...$event]"
/>
</template>
<template #st-actions> <template #st-actions>
<QBtn <QBtn
icon="vn:clone" icon="vn:clone"
@ -267,7 +285,7 @@ const openTicketsDialog = (id) => {
:key="refreshKey" :key="refreshKey"
data-key="RouteList" data-key="RouteList"
url="Routes/filter" url="Routes/filter"
:order="['created DESC', 'id DESC']" :order="['created ASC', 'started ASC', 'id ASC']"
:limit="20" :limit="20"
auto-load auto-load
> >
@ -281,9 +299,10 @@ const openTicketsDialog = (id) => {
row-key="id" row-key="id"
selection="multiple" selection="multiple"
:rows-per-page-options="[0]" :rows-per-page-options="[0]"
:visible-columns="visibleColumns"
hide-pagination hide-pagination
:pagination="{ sortBy: 'ID', descending: true }"
:no-data-label="t('globals.noResults')" :no-data-label="t('globals.noResults')"
style="max-height: 82vh"
> >
<template #body-cell-worker="{ row }"> <template #body-cell-worker="{ row }">
<QTd class="table-input-cell"> <QTd class="table-input-cell">
@ -336,7 +355,7 @@ const openTicketsDialog = (id) => {
</QTd> </QTd>
</template> </template>
<template #body-cell-vehicle="{ row }"> <template #body-cell-vehicle="{ row }">
<QTd class="table-input-cell"> <QTd class="table-input-cell small-column">
<VnSelect <VnSelect
:label="t('Vehicle')" :label="t('Vehicle')"
v-model="row.vehicleFk" v-model="row.vehicleFk"
@ -353,7 +372,7 @@ const openTicketsDialog = (id) => {
</QTd> </QTd>
</template> </template>
<template #body-cell-date="{ row }"> <template #body-cell-date="{ row }">
<QTd class="table-input-cell"> <QTd class="table-input-cell small-column">
<VnInputDate <VnInputDate
v-model="row.created" v-model="row.created"
hide-bottom-space hide-bottom-space
@ -378,10 +397,10 @@ const openTicketsDialog = (id) => {
</QTd> </QTd>
</template> </template>
<template #body-cell-started="{ row }"> <template #body-cell-started="{ row }">
<QTd class="table-input-cell"> <QTd class="table-input-cell small-column">
<VnInputTime <VnInputTime
v-model="row.started" v-model="row.started"
:label="t('Hour started')" :label="t('hourStarted')"
:rules="validate('route.started')" :rules="validate('route.started')"
:is-clearable="false" :is-clearable="false"
hide-bottom-space hide-bottom-space
@ -391,11 +410,11 @@ const openTicketsDialog = (id) => {
</QTd> </QTd>
</template> </template>
<template #body-cell-finished="{ row }"> <template #body-cell-finished="{ row }">
<QTd class="table-input-cell"> <QTd class="table-input-cell small-column">
<VnInputTime <VnInputTime
v-model="row.finished" v-model="row.finished"
autofocus autofocus
:label="t('Hour finished')" :label="t('hourFinished')"
:rules="validate('route.finished')" :rules="validate('route.finished')"
:is-clearable="false" :is-clearable="false"
hide-bottom-space hide-bottom-space
@ -405,7 +424,7 @@ const openTicketsDialog = (id) => {
</QTd> </QTd>
</template> </template>
<template #body-cell-isServed="props"> <template #body-cell-isServed="props">
<QTd> <QTd class="table-input-cell small-column">
<QCheckbox v-model="props.value" disable> <QCheckbox v-model="props.value" disable>
<QTooltip> <QTooltip>
{{ {{
@ -486,15 +505,18 @@ const openTicketsDialog = (id) => {
.table-actions { .table-actions {
gap: 12px; gap: 12px;
} }
th:last-child,
.lock-icon-cell { td:last-child {
text-align: center; background-color: var(--vn-section-color);
margin-left: -20%; position: sticky;
right: 0;
} }
</style> </style>
<i18n> <i18n>
en: en:
newRoute: New Route newRoute: New Route
hourStarted: Started hour
hourFinished: Finished hour
es: es:
ID: ID ID: ID
Worker: Trabajador Worker: Trabajador

View File

@ -1,8 +1,10 @@
<script setup> <script setup>
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue'; import LeftMenu from 'src/components/LeftMenu.vue';
import { onMounted } from 'vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
onMounted(() => (stateStore.leftDrawer = false));
</script> </script>
<template> <template>

View File

@ -204,7 +204,7 @@ onMounted(async () => {
<QTd no-hover> <QTd no-hover>
<span>{{ buy.subName }}</span> <span>{{ buy.subName }}</span>
<fetched-tags :item="buy" :max-length="5" /> <FetchedTags :item="buy" :max-length="5" />
</QTd> </QTd>
<QTd no-hover> {{ dashIfEmpty(buy.quantity) }}</QTd> <QTd no-hover> {{ dashIfEmpty(buy.quantity) }}</QTd>
<QTd no-hover> {{ dashIfEmpty(buy.price) }}</QTd> <QTd no-hover> {{ dashIfEmpty(buy.price) }}</QTd>

View File

@ -201,6 +201,6 @@ es:
isDisable: Trabajador desactivado isDisable: Trabajador desactivado
fi: DNI/NIE/NIF fi: DNI/NIE/NIF
sex: Sexo sex: Sexo
birth: Cumpleaños birth: Fecha de Nacimiento
isSsDiscounted: Bonificación SS isSsDiscounted: Bonificación SS
</i18n> </i18n>

View File

@ -82,6 +82,7 @@ const onFetchAbsences = (data) => {
type: type.code, type: type.code,
absenceId: absence.id, absenceId: absence.id,
isFestive: false, isFestive: false,
isHoliday: false,
}); });
}); });
} }

View File

@ -126,7 +126,7 @@ const handleEventSelected = (event, { year, month, day }) => {
} }
const date = new Date(year, month - 1, day); const date = new Date(year, month - 1, day);
if (!event.absenceId) createEvent(date); if (!event?.absenceId) createEvent(date);
else if (event.type == props.absenceType.code) deleteEvent(event, date); else if (event.type == props.absenceType.code) deleteEvent(event, date);
else editEvent(event); else editEvent(event);
}; };
@ -136,24 +136,31 @@ const getEventByTimestamp = ({ year, month, day }) => {
return props.events[stamp] || null; return props.events[stamp] || null;
}; };
const isFestive = (timestamp) => {
const event = getEventByTimestamp(timestamp);
if (!event) return false;
const { isFestive } = event;
return isFestive;
};
const getEventAttrs = (timestamp) => { const getEventAttrs = (timestamp) => {
const event = getEventByTimestamp(timestamp); const event = getEventByTimestamp(timestamp);
if (!event) return {}; if (!event) return {};
const { name, color, isFestive } = event; const { name, color, isFestive, type } = event;
// Atributos a asignar a cada slot que representa un evento en el calendario // Atributos a asignar a cada slot que representa un evento en el calendario
const attrs = { const attrs = {
title: name, title: name,
style: color ? `background-color: ${color};` : '', style: color ? `background-color: ${color};` : ``,
label: timestamp.day, label: timestamp.day,
}; };
if (isFestive) { if (isFestive) {
attrs.class = '--festive'; attrs.class = '--festive';
attrs.label = event.absenceId ? timestamp.day : ''; attrs.label = event.absenceId ?? timestamp.day;
} } else attrs.class = `--${type}`;
return attrs; return attrs;
}; };
@ -162,7 +169,6 @@ const isToday = (timestamp) => {
const { year, month, day } = timestamp; const { year, month, day } = timestamp;
return todayTimestamp.value === new Date(year, month - 1, day).getTime(); return todayTimestamp.value === new Date(year, month - 1, day).getTime();
}; };
onBeforeMount(() => { onBeforeMount(() => {
updateSelectedDate(_year.value); updateSelectedDate(_year.value);
}); });
@ -203,7 +209,6 @@ watch(_year, (newValue) => {
<template #day="{ scope: { timestamp } }"> <template #day="{ scope: { timestamp } }">
<!-- Este slot representa cada día del calendario y muestra un botón representando el correspondiente evento --> <!-- Este slot representa cada día del calendario y muestra un botón representando el correspondiente evento -->
<QBtn <QBtn
v-if="getEventByTimestamp(timestamp)"
v-bind="{ ...getEventAttrs(timestamp) }" v-bind="{ ...getEventAttrs(timestamp) }"
@click=" @click="
handleEventSelected(getEventByTimestamp(timestamp), timestamp) handleEventSelected(getEventByTimestamp(timestamp), timestamp)
@ -223,6 +228,11 @@ watch(_year, (newValue) => {
</template> </template>
<style lang="scss"> <style lang="scss">
.q-calendar-month__day:has(.q-calendar-month__day--content):has(.q-btn.--festive)
.q-calendar-month__day--label__wrapper
button {
color: transparent;
}
.calendar-event { .calendar-event {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -231,14 +241,19 @@ watch(_year, (newValue) => {
font-size: 13px; font-size: 13px;
line-height: 1.715em; line-height: 1.715em;
cursor: pointer; cursor: pointer;
color: white;
&.--today { &.--today {
border: 2px solid $info; border: 2px solid $info;
} }
&.--festive { &.--festive {
border: 2px solid $negative; color: $negative;
}
&.--holiday {
& > span:nth-child(2) .block {
color: white;
}
} }
&:hover { &:hover {

View File

@ -71,52 +71,34 @@ function reloadData() {
bordered bordered
:key="row.id" :key="row.id"
v-for="row of rows" v-for="row of rows"
class="card q-pt-xs q-mb-sm" class="card q-px-md q-mb-sm container"
> >
<QItem>
<QItemSection side-left>
<VnRow> <VnRow>
<QField <VnInput
:label="t('worker.pda.currentPDA')" :label="t('worker.pda.currentPDA')"
:model-value="row?.deviceProductionFk" :model-value="row?.deviceProductionFk"
disable disable
> />
<template #control> <VnInput
<div tabindex="0" style="padding: none"> :label="t('Model')"
<span>Id: </span> :model-value="row?.deviceProduction?.modelFk"
<span> disable
{{ row?.deviceProductionFk }}&nbsp; />
</span> <VnInput
<span>{{ t('Model') }}: </span> :label="t('Serial number')"
<span> :model-value="row?.deviceProduction?.serialNumber"
{{ row?.deviceProduction?.modelFk }}&nbsp; disable
</span> />
<span>{{ t('SIM serial number') }}: </span> <VnInput
<span>
{{
row?.deviceProduction?.serialNumber
}}&nbsp;
</span>
</div>
</template>
</QField>
<QField
:label="t('Current SIM')" :label="t('Current SIM')"
:model-value="row?.simSerialNumber" :model-value="row?.simSerialNumber"
disable disable
> />
<template #control> <QBtn
<div tabindex="0">{{ row?.simSerialNumber }}</div> flat
</template> icon="delete"
</QField>
</VnRow>
</QItemSection>
<QItemSection side>
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary" color="primary"
class="btn-delete"
@click=" @click="
openConfirmationModal( openConfirmationModal(
t(`Remove PDA`), t(`Remove PDA`),
@ -128,9 +110,8 @@ function reloadData() {
<QTooltip> <QTooltip>
{{ t('worker.pda.removePDA') }} {{ t('worker.pda.removePDA') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</QItemSection> </VnRow>
</QItem>
</QCard> </QCard>
</template> </template>
</VnPaginate> </VnPaginate>
@ -187,26 +168,20 @@ function reloadData() {
</QPage> </QPage>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.centerCard { .btn-delete {
padding: 5%; max-width: 4%;
width: 100%; margin-top: 30px;
max-width: 70%;
margin: 0 auto;
}
.label {
color: red;
}
.q-field {
height: 65px;
} }
</style> </style>
<i18n> <i18n>
es: es:
Model: Modelo
Serial number: Número de serie
Current SIM: SIM actual
Add new device: Añadir nuevo dispositivo
PDA deallocated: PDA desasignada
Remove PDA: Eliminar PDA Remove PDA: Eliminar PDA
Do you want to remove this PDA?: ¿Desea eliminar este PDA? Do you want to remove this PDA?: ¿Desea eliminar este PDA?
PDA deallocated: PDA desasignada You can only have one PDA: Solo puedes tener un PDA si no eres autonomo
SIM serial number: Número de serie de la SIM
Model: Modelo
This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario
Add new device: Añadir nuevo dispositivo
</i18n> </i18n>

View File

@ -73,7 +73,7 @@ const filter = {
<template #body="{ entity: worker }"> <template #body="{ entity: worker }">
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle <VnTitle
:url="workerUrl + `basic-data`" :url="`#/worker/${entityId}/basic-data`"
:text="t('worker.summary.basicData')" :text="t('worker.summary.basicData')"
/> />
<VnLv :label="t('worker.card.name')" :value="worker.user?.nickname" /> <VnLv :label="t('worker.card.name')" :value="worker.user?.nickname" />
@ -113,7 +113,7 @@ const filter = {
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle <VnTitle
:url="workerUrl + `basic-data`" :url="`#/worker/${entityId}/basic-data`"
:text="t('worker.summary.basicData')" :text="t('worker.summary.basicData')"
/> />
<VnLv <VnLv

View File

@ -35,7 +35,7 @@ const searchBarDataKeys = {
base-url="Zones" base-url="Zones"
:descriptor="ZoneDescriptor" :descriptor="ZoneDescriptor"
searchbar-data-key="ZoneList" searchbar-data-key="ZoneList"
searchbar-url="Zones/filter" searchbar-url="Zones"
searchbar-label="Search zones" searchbar-label="Search zones"
searchbar-info="You can search by zone reference" searchbar-info="You can search by zone reference"
/> />

View File

@ -6,8 +6,6 @@ import { toCurrency } from 'src/filters';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import ZoneSummary from 'src/pages/Zone/Card/ZoneSummary.vue'; import ZoneSummary from 'src/pages/Zone/Card/ZoneSummary.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import ZoneFilterPanel from './ZoneFilterPanel.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { toTimeFormat } from 'src/filters/date'; import { toTimeFormat } from 'src/filters/date';
@ -71,35 +69,6 @@ const columns = computed(() => [
}, },
]); ]);
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'agencyModeFk':
return {
agencyModeFk: value,
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
name: {
like: `%${value}%`,
},
};
}
}
}
};
async function clone(id) { async function clone(id) {
const { data } = await axios.post(`Zones/${id}/clone`); const { data } = await axios.post(`Zones/${id}/clone`);
notify(t('globals.dataSaved'), 'positive'); notify(t('globals.dataSaved'), 'positive');
@ -116,25 +85,6 @@ onMounted(() => (stateStore.rightDrawer = true));
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="ZoneList"
url="Zones"
:filter="{
include: { relation: 'agencyMode', scope: { fields: ['name'] } },
}"
:expr-builder="exprBuilder"
:label="t('list.searchZone')"
:info="t('list.searchInfo')"
/>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<ZoneFilterPanel data-key="ZoneList" :expr-builder="exprBuilder" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<div class="vn-card-list"> <div class="vn-card-list">
<VnPaginate <VnPaginate

View File

@ -1,11 +1,72 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue'; import LeftMenu from 'src/components/LeftMenu.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import ZoneFilterPanel from './ZoneFilterPanel.vue';
const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute();
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'agencyModeFk':
return {
agencyModeFk: value,
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
name: {
like: `%${value}%`,
},
};
}
}
}
};
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="ZoneList"
url="Zones"
:filter="{
include: { relation: 'agencyMode', scope: { fields: ['name'] } },
}"
:expr-builder="exprBuilder"
:label="t('list.searchZone')"
:info="t('list.searchInfo')"
custom-route-redirect-name="ZoneSummary"
/>
</Teleport>
</template>
<QDrawer
v-if="route.name === 'ZoneList'"
v-model="stateStore.rightDrawer"
side="right"
:width="256"
show-if-above
>
<QScrollArea class="fit text-grey-8">
<ZoneFilterPanel data-key="ZoneList" :expr-builder="exprBuilder" />
</QScrollArea>
</QDrawer>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<LeftMenu /> <LeftMenu />

View File

@ -1,53 +1,85 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, onMounted } from 'vue';
import ZoneFilterPanel from 'components/InvoiceOutNegativeFilter.vue';
import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
import FetchData from 'components/FetchData.vue';
import { toDateFormat } from 'src/filters/date.js';
import { useWeekdayStore } from 'src/stores/useWeekdayStore';
const { t } = useI18n(); const { t } = useI18n();
const arrayData = ref(null); const weekdayStore = useWeekdayStore();
const rows = computed(() => arrayData.value.store.data);
const details = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('Province'), label: t('upcomingDeliveries.province'),
//field: '', name: 'province',
//name: '', field: 'name',
align: 'left', align: 'left',
}, },
{ {
label: t('Closing'), label: t('upcomingDeliveries.closing'),
//field: '', name: 'closing',
//name: '', field: 'hour',
align: 'left', align: 'left',
}, },
{ {
label: t('Id'), label: t('upcomingDeliveries.id'),
//field: '', name: 'id',
//name: '', field: 'zoneFk',
align: 'left', align: 'left',
}, },
]); ]);
function getWeekDay(jsonDate) { const getWeekDayName = (date) => {
const weekDay = new Date(jsonDate).getDay(); const weekDay = new Date(date).getDay();
return t(`weekdays.${weekdayStore.weekdays[weekDay].code}`);
};
return this.days[weekDay].locale; const getHeaderTitle = (date) => {
} return `${getWeekDayName(date)} - ${toDateFormat(date)}`;
};
onMounted(() => weekdayStore.initStore());
</script> </script>
<template> <template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <FetchData
<QScrollArea class="fit text-grey-8"> url="Zones/getUpcomingDeliveries"
<ZoneFilterPanel data-key="ZoneUpcoming" /> @on-fetch="(data) => (details = data)"
</QScrollArea> auto-load
</QDrawer> />
<VnSubToolbar /> <VnSubToolbar />
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<span> <QCard class="full-width q-pa-md">
{{ t(`${getWeekDay(/*detail.shipped*/)}`) }} - <div
{{ t /*'detail.shipped'*/() }} v-for="(detail, index) in details"
:key="index"
class="full-width flex q-mb-lg"
>
<span class="header">
{{ getHeaderTitle(detail.shipped) }}
</span> </span>
<QTable :columns="columns" :rows="rows" class="full-width q-mt-md"> </QTable> <QTable flat :columns="columns" :rows="detail.lines" class="full-width" />
</div>
</QCard>
</QPage> </QPage>
</template> </template>
<style scoped lang="scss">
.header {
min-width: 100% !important;
display: flex;
align-items: center;
padding-left: 8px;
height: 35px;
border-bottom: 1px solid $primary;
background-color: var(--vn-page-color);
font-size: 19px;
font-weight: bold;
text-transform: uppercase;
}
</style>

View File

@ -5,7 +5,7 @@ zone:
zoneCreate: Create zone zoneCreate: Create zone
locations: Locations locations: Locations
deliveryDays: Delivery days deliveryDays: Delivery days
upcomingList: Upcoming deliveries upcomingDeliveries: Upcoming deliveries
warehouses: Warehouses warehouses: Warehouses
list: list:
clone: Clone clone: Clone
@ -64,3 +64,7 @@ warehouses:
deleteSubtitle: Are you sure you want to continue? deleteSubtitle: Are you sure you want to continue?
warehouse: Warehouse warehouse: Warehouse
add: Add add: Add
upcomingDeliveries:
province: Province
closing: Closing
id: Id

View File

@ -5,7 +5,7 @@ zone:
zoneCreate: Nueva zona zoneCreate: Nueva zona
locations: Localizaciones locations: Localizaciones
deliveryDays: Días de entrega deliveryDays: Días de entrega
upcomingList: Próximos repartos upcomingDeliveries: Próximos repartos
warehouses: Almacenes warehouses: Almacenes
list: list:
clone: Clonar clone: Clonar
@ -66,3 +66,7 @@ warehouses:
deleteSubtitle: ¿Seguro que quieres continuar? deleteSubtitle: ¿Seguro que quieres continuar?
warehouse: Almacén warehouse: Almacén
add: Añadir add: Añadir
upcomingDeliveries:
province: Provincia
closing: Cierre
id: Id

View File

@ -11,7 +11,7 @@ export default {
component: RouterView, component: RouterView,
redirect: { name: 'ZoneMain' }, redirect: { name: 'ZoneMain' },
menus: { menus: {
main: ['ZoneList', 'ZoneDeliveryDays', 'ZoneUpcomingList'], main: ['ZoneList', 'ZoneDeliveryDays', 'ZoneUpcomingDeliveries'],
card: ['ZoneBasicData', 'ZoneWarehouses', 'ZoneHistory', 'ZoneLocations'], card: ['ZoneBasicData', 'ZoneWarehouses', 'ZoneHistory', 'ZoneLocations'],
}, },
children: [ children: [
@ -57,6 +57,24 @@ export default {
}, },
component: () => import('src/pages/Zone/ZoneCreate.vue'), component: () => import('src/pages/Zone/ZoneCreate.vue'),
}, },
// {
// path: 'counter',
// name: 'ZoneCounter',
// meta: {
// title: 'zoneCounter',
// icon: 'add_circle',
// },
// component: () => import('src/pages/Zone/ZoneCounter.vue'),
// },
{
name: 'ZoneUpcomingDeliveries',
path: 'upcoming-deliveries',
meta: {
title: 'upcomingDeliveries',
icon: 'vn:calendar',
},
component: () => import('src/pages/Zone/ZoneUpcoming.vue'),
},
], ],
}, },
{ {

View File

@ -15,7 +15,7 @@ describe('WorkerPda', () => {
}); });
it('delete pda', () => { it('delete pda', () => {
cy.get('.q-card > .q-item > .q-item__section--side > .q-icon').click(); cy.get('.btn-delete').click();
cy.get( cy.get(
'.q-card__actions > .q-btn--unelevated > .q-btn__content > .block' '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block'
).click(); ).click();