WIP: feat: #8115 created new function for adding new parkings massively #1476

Draft
provira wants to merge 8 commits from 8115-parkingMassiveCreate into dev
9 changed files with 266 additions and 27 deletions

View File

@ -345,6 +345,7 @@ globals:
operator: Operator operator: Operator
parking: Parking parking: Parking
vehicleList: Vehicles vehicleList: Vehicles
parkingCreate: New parking
vehicle: Vehicle vehicle: Vehicle
entryPreAccount: Pre-account entryPreAccount: Pre-account
unsavedPopup: unsavedPopup:

View File

@ -350,6 +350,7 @@ globals:
vehicleList: Vehículos vehicleList: Vehículos
vehicle: Vehículo vehicle: Vehículo
entryPreAccount: Precontabilizar entryPreAccount: Precontabilizar
parkingCreate: Nuevo parking
unsavedPopup: unsavedPopup:
title: Los cambios que no haya guardado se perderán title: Los cambios que no haya guardado se perderán
subtitle: ¿Seguro que quiere salir sin guardar? subtitle: ¿Seguro que quiere salir sin guardar?

View File

@ -0,0 +1,92 @@
<script setup>
import { computed, ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue';
import FormModel from 'components/FormModel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import axios from 'axios';
const route = useRoute();
const router = useRouter();
const entityId = computed(() => route.params.id ?? null);
const isNew = Boolean(!entityId.value);
const sectors = ref([]);
const defaultInitialData = {
sector: null,
block: null,
streetFrom: null,
streetTo: null,
numberFrom: null,
numberTo: null,
};
const onSave = (parking, newParking) => {
if (isNew) {
router.push({ name: 'ParkingSummary', params: { id: newParking?.id } });
}
};
</script>
<template>
<VnSubToolbar v-if="isNew" />
<FormModel
:url-create="isNew ? 'Parkings/multipleParkings' : null"
:filter="filter"
model="Parking"
:auto-load="!isNew"
:form-initial-data="isNew ? defaultInitialData : null"
>
<template #form="{ data, validate }">
<VnRow>
<VnSelect
v-model="data.sector"
url="Sectors"
option-value="id"
option-label="description"
:filter-options="['id', 'description']"
:fields="['id', 'description']"
:label="$t('parking.sector')"
/>
</VnRow>
<VnRow>
<VnInput
v-model="data.block"
:label="$t('parking.block')"
:rules="validate('Parking.block')"
/>
</VnRow>
<VnRow>
<VnInput
v-model.number="data.streetFrom"
type="number"
:label="$t('parking.streetFrom')"
:rules="validate('Parking.streetFrom')"
/>
<VnInput
v-model.number="data.streetTo"
type="number"
:label="$t('parking.streetTo')"
:rules="validate('Parking.streetTo')"
/>
</VnRow>
<VnRow>
<VnInput
v-model.number="data.numberFrom"
type="number"
:label="$t('parking.numberFrom')"
:rules="validate('Parking.numberFrom')"
/>
<VnInput
v-model.number="data.numberTo"
type="number"
:label="$t('parking.numberTo')"
:rules="validate('Parking.numberTo')"
/>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref} from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
@ -16,6 +16,11 @@ defineProps({
const sectors = ref([]); const sectors = ref([]);
const emit = defineEmits(['search']); const emit = defineEmits(['search']);
const updateParams = () => {
emit('search');
};
</script> </script>
<template> <template>
@ -26,7 +31,11 @@ const emit = defineEmits(['search']);
@on-fetch="(data) => (sectors = data)" @on-fetch="(data) => (sectors = data)"
auto-load auto-load
/> />
<VnFilterPanel :data-key="dataKey" :search-button="true" @search="emit('search')"> <VnFilterPanel
:data-key="dataKey"
:search-button="true"
@search="updateParams"
>
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ t(`params.${tag.label}`) }}: </strong>
@ -36,9 +45,56 @@ const emit = defineEmits(['search']);
<template #body="{ params }"> <template #body="{ params }">
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput :label="t('params.code')" v-model="params.code" filled /> <VnInput
:label="t('params.block')"
v-model="params.block"
filled
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<div class="row q-gutter-sm">
<VnInput
:label="t('params.streetFrom')"
v-model.number="params.streetFrom"
type="number"
filled
class="col"
/>
<VnInput
:label="t('params.streetTo')"
v-model.number="params.streetTo"
type="number"
filled
class="col"
/>
</div>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<div class="row q-gutter-sm">
<VnInput
:label="t('params.numberFrom')"
v-model="params.numberFrom"
type="number"
filled
class="col"
/>
<VnInput
:label="t('params.numberTo')"
v-model="params.numberTo"
type="number"
filled
class="col"
/>
</div>
</QItemSection>
</QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelect <VnSelect
@ -64,9 +120,19 @@ en:
code: Code code: Code
sectorFk: Sector sectorFk: Sector
search: General Search search: General Search
block: Block
streetFrom: Street (From)
streetTo: Street (To)
numberFrom: Number (From)
numberTo: Number (To)
es: es:
params: params:
code: Código code: Código
search: Búsqueda general search: Búsqueda general
sectorFk: Sector
</i18n> block: Bloque
streetFrom: Calle (Desde)
streetTo: Calle (Hasta)
numberFrom: Número (Desde)
numberTo: Número (Hasta)
</i18n>

View File

@ -1,26 +1,29 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted } from 'vue'; import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { usePrintService } from 'src/composables/usePrintService';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import ParkingFilter from './ParkingFilter.vue'; import ParkingFilter from './ParkingFilter.vue';
import exprBuilder from './ParkingExprBuilder.js'; import exprBuilder from './ParkingExprBuilder.js';
import ParkingSummary from './Card/ParkingSummary.vue'; import ParkingSummary from './Card/ParkingSummary.vue';
import { QBtn, QTooltip } from 'quasar';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const { openReport } = usePrintService();
const dataKey = 'ParkingList'; const dataKey = 'ParkingList';
const tableRef = ref();
const selectedRows = ref([]);
const hasSelectedCards = computed(() => selectedRows.value.length > 0);
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder'],
};
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -35,7 +38,7 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'sector', name: 'sector',
label: t('parking.sector'), label: t('parking.sector'),
format: (val) => val.sector.description ?? '', format: (val) => val.sector_description ?? '',
sortable: true, sortable: true,
cardVisible: true, cardVisible: true,
}, },
@ -57,44 +60,89 @@ const columns = computed(() => [
action: (row) => viewSummary(row.id, ParkingSummary), action: (row) => viewSummary(row.id, ParkingSummary),
isPrimary: true, isPrimary: true,
}, },
{
title: t('globals.downloadPdf'),
icon: 'cloud_download',
isPrimary: true,
action: (row) => openPdf(row.id),
},
], ],
}, },
]); ]);
function openPdf(id) {
openReport(`Parkings/${id}/download`);
}
function downloadPdf() {
if (selectedRows.value.length === 0) return;
const selectedCardsArray = selectedRows.value;
if (selectedRows.value.length === 1) {
const [parking] = selectedCardsArray;
openPdf(parking.id);
} else {
const parkingIdsArray = selectedCardsArray.map((parking) => parking.id);
const parkingIds = parkingIdsArray.join(',');
const params = {
ids: parkingIds,
};
openReport(`Parkings/downloadZip`, params);
}
}
</script> </script>
<template> <template>
<VnSection <VnSection
:data-key="dataKey" :data-key="dataKey"
prefix="parking" prefix="parking"
:columns="columns"
:array-data-props="{ :array-data-props="{
url: 'Parkings', url: 'Parkings/filter',
order: ['code'], order: ['code'],
userFilter: filter,
exprBuilder,
}" }"
> >
<template #advanced-menu> <template #advanced-menu>
<ParkingFilter data-key="ParkingList" /> <ParkingFilter data-key="ParkingList" />
</template> </template>
<template #body> <template #body>
<VnSubToolbar>
<template #st-actions>
<QBtn
color="primary"
icon-right="cloud_download"
@click="downloadPdf()"
:disable="!hasSelectedCards"
data-cy="ParkingDownloadPdfBtn"
>
<QTooltip>{{ t('globals.downloadPdf') }}</QTooltip>
</QBtn>
</template>
</VnSubToolbar>
<VnTable <VnTable
ref="tableRef"
:data-key="dataKey" :data-key="dataKey"
:columns="columns" :columns="columns"
:is-editable="false" :is-editable="false"
:right-search="false" :right-search="false"
:use-model="true"
:disable-option="{ table: true }"
redirect="shelving/parking" redirect="shelving/parking"
default-mode="card" v-model:selected="selectedRows"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
> >
<template #actions="{ row }">
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, ParkingSummary)"
color="primary"
/>
</template>
</VnTable> </VnTable>
</template> </template>
</VnSection> </VnSection>
</template> <QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'ParkingCreate' }">
<QBtn fab icon="add" color="primary" v-shortcut="'+'" />
<QTooltip>
{{ $t('shelving.list.newShelving') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</template>

View File

@ -2,4 +2,9 @@ parking:
pickingOrder: Picking order pickingOrder: Picking order
sector: Sector sector: Sector
search: Search parking search: Search parking
searchInfo: You can search by parking code searchInfo: You can search by parking code
block: Block
streetFrom: Street from
streetTo: Street to
numberFrom: Number from
numberTo: Number to

View File

@ -2,4 +2,9 @@ parking:
pickingOrder: Orden de recogida pickingOrder: Orden de recogida
sector: Sector sector: Sector
search: Buscar parking search: Buscar parking
searchInfo: Puedes buscar por código de parking searchInfo: Puedes buscar por código de parking
block: Bloque
streetFrom: Calle desde
streetTo: Calle hasta
numberFrom: Número desde
numberTo: Número hasta

View File

@ -128,6 +128,16 @@ export default {
parkingCard, parkingCard,
], ],
}, },
{
path: 'parking/create',
name: 'ParkingCreate',
meta: {
title: 'parkingCreate',
icon: 'add',
},
component: () => import('src/pages/Shelving/Parking/ParkingCreate.vue'),
},
], ],
}, },
], ],

View File

@ -145,5 +145,16 @@
"allowedContentTypes": [ "allowedContentTypes": [
"application/x-7z-compressed" "application/x-7z-compressed"
] ]
},
"parkingStorage": {
"name": "parkingStorage",
"connector": "loopback-component-storage",
"provider": "filesystem",
"root": "./storage/pdfs/parking",
"maxFileSize": "52428800",
"allowedContentTypes": [
"application/octet-stream",
"application/pdf"
]
} }
} }