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

This commit is contained in:
Robert Ferrús 2024-10-29 07:03:50 +00:00
commit 6878a79c44
50 changed files with 520 additions and 263 deletions

View File

@ -1,5 +1,5 @@
<script setup>
import { computed, defineModel } from 'vue';
import { computed } from 'vue';
const model = defineModel(undefined, { required: true });
const $props = defineProps({

View File

@ -30,10 +30,10 @@ const props = defineProps({
},
});
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
const emit = defineEmits(['confirm', 'cancel', ...useDialogPluginComponent.emits]);
const { dialogRef, onDialogOK } = useDialogPluginComponent();
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
const title = props.title || t('Confirm');
const message =
@ -53,9 +53,14 @@ async function confirm() {
}
onDialogOK(props.data);
}
function cancel() {
emit('cancel');
onDialogCancel();
}
</script>
<template>
<QDialog ref="dialogRef">
<QDialog ref="dialogRef" @hide="onDialogHide">
<QCard class="q-pa-sm">
<QCardSection class="row items-center q-pb-none">
<QAvatar
@ -67,7 +72,14 @@ async function confirm() {
/>
<span class="text-h6">{{ title }}</span>
<QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
<QBtn
icon="close"
:disable="isLoading"
flat
round
dense
@click="cancel()"
/>
</QCardSection>
<QCardSection class="q-pb-none">
<span v-if="message !== false" v-html="message" />
@ -81,7 +93,7 @@ async function confirm() {
color="primary"
:disable="isLoading"
flat
v-close-popup
@click="cancel()"
/>
<QBtn
:label="t('globals.confirm')"

View File

@ -1,6 +1,3 @@
<script setup>
defineProps({ wrap: { type: Boolean, default: false } });
</script>
<template>
<div class="vn-row q-gutter-md q-mb-md">
<slot />

View File

@ -0,0 +1,11 @@
import axios from 'axios';
import { useRole } from './useRole';
export async function useAdvancedSummary(model, id, roles = ['hr']) {
if (useRole().hasAny(roles)) {
const { data } = await axios.get(`${model}/advancedSummary`, {
params: { filter: { where: { id } } },
});
return Array.isArray(data) ? data[0] : data;
}
}

View File

@ -278,6 +278,7 @@ globals:
RouteExtendedList: Router
wasteRecalc: Waste recaclulate
operator: Operator
parking: Parking
supplier: Supplier
created: Created
worker: Worker
@ -312,6 +313,7 @@ globals:
changePass: Change password
deleteConfirmTitle: Delete selected elements
changeState: Change state
raid: 'Raid {daysInForward} days'
errors:
statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred
@ -369,7 +371,6 @@ entry:
companyFk: Company
travelFk: Travel
isExcludedFromAvailable: Inventory
isRaid: Raid
invoiceAmount: Import
summary:
commission: Commission
@ -380,7 +381,6 @@ entry:
ordered: Ordered
confirmed: Confirmed
booked: Booked
raid: Raid
excludedFromAvailable: Inventory
travelReference: Reference
travelAgency: Agency
@ -413,7 +413,6 @@ entry:
ordered: Ordered
confirmed: Confirmed
booked: Booked
raid: Raid
excludedFromAvailable: Inventory
agency: Agency
warehouseOut: Warehouse Out
@ -476,7 +475,6 @@ entry:
packingOut: Package out
landing: Landing
isExcludedFromAvailable: Es inventory
isRaid: Raid
ticket:
pageTitles:
tickets: Tickets
@ -663,8 +661,6 @@ parking:
sector: Sector
row: Row
column: Column
pageTitles:
parking: Parking
searchBar:
info: You can search by parking code
label: Search parking...
@ -1042,6 +1038,7 @@ travel:
warehouseIn: Warehouse In
delivered: Delivered
received: Received
daysInForward: Days in forward
thermographs:
code: Code
temperature: Temperature

View File

@ -282,6 +282,7 @@ globals:
medical: Mutua
wasteRecalc: Recalcular mermas
operator: Operario
parking: Parking
supplier: Proveedor
created: Fecha creación
worker: Trabajador
@ -316,6 +317,7 @@ globals:
changePass: Cambiar contraseña
deleteConfirmTitle: Eliminar los elementos seleccionados
changeState: Cambiar estado
raid: 'Redada {daysInForward} días'
errors:
statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor
@ -371,7 +373,6 @@ entry:
companyFk: Empresa
travelFk: Envio
isExcludedFromAvailable: Inventario
isRaid: Redada
invoiceAmount: Importe
summary:
commission: Comisión
@ -382,7 +383,6 @@ entry:
ordered: Pedida
confirmed: Confirmada
booked: Contabilizada
raid: Redada
excludedFromAvailable: Inventario
travelReference: Referencia
travelAgency: Agencia
@ -415,7 +415,6 @@ entry:
ordered: Pedida
confirmed: Confirmado
booked: Asentado
raid: Redada
excludedFromAvailable: Inventario
agency: Agencia
warehouseOut: Alm. salida
@ -478,7 +477,6 @@ entry:
packingOut: Embalaje envíos
landing: Llegada
isExcludedFromAvailable: Es inventario
isRaid: Redada
ticket:
pageTitles:
tickets: Tickets
@ -710,8 +708,6 @@ parking:
pickingOrder: Orden de recogida
row: Fila
column: Columna
pageTitles:
parking: Parking
searchBar:
info: Puedes buscar por código de parking
label: Buscar parking...
@ -1040,6 +1036,7 @@ travel:
warehouseIn: Alm. entrada
delivered: Enviada
received: Recibida
daysInForward: Días redada
thermographs:
code: Código
temperature: Temperatura

View File

@ -168,7 +168,6 @@ const onFilterTravelSelected = (formData, id) => {
v-model="data.isExcludedFromAvailable"
:label="t('entry.basicData.excludedFromAvailable')"
/>
<QCheckbox v-model="data.isRaid" :label="t('entry.basicData.raid')" />
<QCheckbox
v-if="isAdministrative()"
v-model="data.isBooked"

View File

@ -7,7 +7,6 @@ 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';
import { getUrl } from 'src/composables/getUrl';
@ -23,7 +22,6 @@ const $props = defineProps({
const route = useRoute();
const { t } = useI18n();
const { openReport } = usePrintService();
const state = useState();
const entryDescriptorRef = ref(null);
const url = ref();
@ -32,7 +30,14 @@ const entryFilter = {
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'shipped', 'agencyModeFk', 'warehouseOutFk'],
fields: [
'id',
'landed',
'shipped',
'agencyModeFk',
'warehouseOutFk',
'daysInForward',
],
include: [
{
relation: 'agency',
@ -75,8 +80,6 @@ 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;
@ -133,10 +136,10 @@ watch;
:value="entity.travel?.warehouseOut?.name"
/>
</template>
<template #icons>
<template #icons="{ entity }">
<QCardActions class="q-gutter-x-md">
<QIcon
v-if="currentEntry?.isExcludedFromAvailable"
v-if="entity?.isExcludedFromAvailable"
name="vn:inventory"
color="primary"
size="xs"
@ -144,12 +147,18 @@ watch;
<QTooltip>{{ t('Inventory entry') }}</QTooltip>
</QIcon>
<QIcon
v-if="currentEntry?.isRaid"
v-if="entity?.travel?.daysInForward"
name="vn:net"
color="primary"
size="xs"
>
<QTooltip>{{ t('Virtual entry') }}</QTooltip>
<QTooltip>
{{
t('globals.raid', {
daysInForward: entity?.travel?.daysInForward,
})
}}</QTooltip
>
</QIcon>
</QCardActions>
</template>

View File

@ -259,11 +259,6 @@ const fetchEntryBuys = async () => {
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"

View File

@ -121,7 +121,7 @@ const entriesTableColumns = computed(() => [
:loading="isLoading"
@click="
openReport(
`Entries/${props.row.id}/buy-label`
`Entries/${props.row.id}/buy-label-supplier`
)
"
unelevated

View File

@ -41,6 +41,10 @@ const entryFilter = {
};
const columns = computed(() => [
{
name: 'status',
columnFilter: false,
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.id'),
@ -154,27 +158,8 @@ const columns = computed(() => [
cardVisible: true,
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'),
name: 'isExcludedFromAvailable',
chip: {
color: null,
condition: (value) => value,
icon: 'vn:inventory',
},
columnFilter: {
inWhere: true,
},
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isRaid'),
name: 'isRaid',
chip: {
color: null,
condition: (value) => value,
icon: 'vn:net',
},
columnFilter: {
inWhere: true,
},
@ -225,6 +210,26 @@ onMounted(async () => {
auto-load
:right-search="false"
>
<template #column-status="{ row }">
<div class="row q-gutter-xs">
<QIcon
v-if="!!row.isExcludedFromAvailable"
name="vn:inventory"
color="primary"
>
<QTooltip>{{
t('entry.list.tableVisibleColumns.isExcludedFromAvailable')
}}</QTooltip>
</QIcon>
<QIcon v-if="!!row.daysInForward" name="vn:net" color="primary">
<QTooltip>
{{
t('globals.raid', { daysInForward: row.daysInForward })
}}</QTooltip
>
</QIcon>
</div>
</template>
<template #column-supplierFk="{ row }">
<span class="link" @click.stop>
{{ row.supplierName }}

View File

@ -1,7 +1,6 @@
entryList:
list:
inventoryEntry: Inventory entry
virtualEntry: Virtual entry
entryFilter:
filter:
search: General search

View File

@ -4,7 +4,6 @@ You can search by entry reference: Puedes buscar por referencia de la entrada
entryList:
list:
inventoryEntry: Es inventario
virtualEntry: Es una redada
entryFilter:
filter:
search: Búsqueda general

View File

@ -40,6 +40,12 @@ const itemFilter = {
fields: ['id', 'name'],
},
},
{
relation: 'production',
scope: {
fields: ['id', 'name'],
},
},
],
};
const columns = computed(() => [
@ -161,19 +167,13 @@ const columns = computed(() => [
name: 'intrastat',
align: 'left',
component: 'select',
attrs: {
url: 'Intrastats',
optionValue: 'description',
optionLabel: 'description',
},
columnFilter: {
name: 'description',
name: 'intrastat',
attrs: {
url: 'Intrastats',
optionValue: 'description',
optionLabel: 'description',
},
alias: 'intr',
},
columnField: {
component: null,
@ -211,14 +211,19 @@ const columns = computed(() => [
label: t('item.list.userName'),
name: 'userName',
align: 'left',
component: 'select',
columnFilter: {
name: 'workerFk',
attrs: {
url: 'Users',
url: 'VnUsers/preview',
optionValue: 'id',
optionLabel: 'userName',
optionLabel: 'nickname',
},
},
columnField: {
component: null,
},
},
{
label: t('item.list.weightByPiece'),
@ -255,9 +260,13 @@ const columns = computed(() => [
name: 'producer',
align: 'left',
component: 'select',
attrs: {
url: 'Producers',
fields: ['id', 'name'],
columnFilter: {
name: 'producerFk',
attrs: {
url: 'Producers',
optionValue: 'id',
optionLabel: 'name',
},
},
columnField: {
component: null,
@ -331,7 +340,7 @@ const columns = computed(() => [
<template #column-userName="{ row }">
<span class="link" @click.stop>
{{ row.userName }}
<WorkerDescriptorProxy :id="row.buyerFk" />
<WorkerDescriptorProxy :id="row.workerFk" />
</span>
</template>
<template #column-description="{ row }">

View File

@ -70,6 +70,18 @@ const columns = computed(() => [
{
label: t('item.buyRequest.requester'),
name: 'requesterName',
component: 'select',
columnFilter: {
name: 'requesterFk',
attrs: {
url: 'VnUsers/preview',
optionValue: 'id',
optionLabel: 'nickname',
},
},
columnField: {
component: null,
},
columnClass: 'shrink',
},
{
@ -88,6 +100,18 @@ const columns = computed(() => [
label: t('item.buyRequest.attender'),
name: 'attenderName',
align: 'left',
component: 'select',
columnFilter: {
name: 'attenderFk',
attrs: {
url: 'VnUsers/preview',
optionValue: 'id',
optionLabel: 'nickname',
},
},
columnField: {
component: null,
},
columnClass: 'shrink',
},
{

View File

@ -1,7 +1,7 @@
<script setup>
import VnCard from 'components/common/VnCard.vue';
import ItemTypeDescriptor from 'src/pages/ItemType/Card/ItemTypeDescriptor.vue';
import ItemTypeFilter from 'src/pages/ItemType/ItemTypeFilter.vue';
import ItemTypeDescriptor from 'src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue';
import ItemTypeFilter from 'src/pages/Item/ItemType/ItemTypeFilter.vue';
import ItemTypeSearchbar from '../ItemTypeSearchbar.vue';
</script>
<template>

View File

@ -12,6 +12,39 @@ const props = defineProps({
});
const emit = defineEmits(['search']);
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
or: [
{
name: {
like: `%${value}%`,
},
},
{
code: {
like: `%${value}%`,
},
},
],
};
}
}
}
};
</script>
<template>
@ -19,6 +52,8 @@ const emit = defineEmits(['search']);
:data-key="props.dataKey"
:search-button="true"
@search="emit('search')"
search-url="table"
:expr-builder="exprBuilder"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">

View File

@ -10,6 +10,7 @@ const { t } = useI18n();
url="ItemTypes"
:label="t('Search item type')"
:info="t('Search itemType by id, name or code')"
search-url="table"
/>
</template>
<i18n>

View File

@ -1,98 +0,0 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const { t } = useI18n();
const router = useRouter();
const newItemTypeForm = reactive({});
const categoriesOptions = ref([]);
const temperaturesOptions = ref([]);
const redirectToItemTypeBasicData = (_, { id }) => {
router.push({ name: 'ItemTypeBasicData', params: { id } });
};
</script>
<template>
<FetchData
url="ItemCategories"
@on-fetch="(data) => (categoriesOptions = data)"
:filter="{ order: 'name ASC', fields: ['id', 'name'] }"
auto-load
/>
<FetchData
url="Temperatures"
@on-fetch="(data) => (temperaturesOptions = data)"
:filter="{ order: 'name ASC', fields: ['code', 'name'] }"
auto-load
/>
<QPage>
<VnSubToolbar />
<FormModel
url-create="ItemTypes"
model="itemTypeCreate"
:form-initial-data="newItemTypeForm"
observe-form-changes
@on-data-saved="redirectToItemTypeBasicData"
>
<template #form="{ data }">
<VnRow>
<VnInput v-model="data.code" :label="t('itemType.shared.code')" />
<VnInput v-model="data.name" :label="t('itemType.shared.name')" />
</VnRow>
<VnRow>
<VnSelect
url="Workers/search"
v-model="data.workerFk"
:label="t('shared.worker')"
sort-by="nickname ASC"
:fields="['id', 'nickname']"
:params="{ departmentCodes: ['shopping'] }"
option-label="nickname"
option-value="id"
hide-selected
><template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt?.nickname }},
{{ scope.opt?.code }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
v-model="data.categoryFk"
:label="t('itemType.shared.category')"
:options="categoriesOptions"
option-value="id"
option-label="name"
hide-selected
/>
</VnRow>
<VnRow>
<VnSelect
v-model="data.temperatureFk"
:label="t('itemType.shared.temperature')"
:options="temperaturesOptions"
option-value="code"
option-label="name"
hide-selected
/>
</VnRow>
</template>
</FormModel>
</QPage>
</template>

View File

@ -1,13 +1,16 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { ref, computed } from 'vue';
import ItemTypeSearchbar from '../ItemType/ItemTypeSearchbar.vue';
import ItemTypeSearchbar from 'src/pages/Item/ItemType/ItemTypeSearchbar.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'components/FetchData.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import ItemTypeFilter from './ItemType/ItemTypeFilter.vue';
const { t } = useI18n();
const tableRef = ref();
const ItemCategoriesOptions = ref([]);
const itemCategoriesOptions = ref([]);
const temperatureOptions = ref([]);
const columns = computed(() => [
{
@ -34,7 +37,6 @@ const columns = computed(() => [
},
{
align: 'left',
name: 'workerFk',
label: t('worker'),
create: true,
component: 'select',
@ -45,6 +47,19 @@ const columns = computed(() => [
},
cardVisible: true,
visible: true,
columnField: {
component: 'userLink',
attrs: ({ row }) => {
return {
workerId: row?.worker?.id,
name: row.worker?.user?.name,
defaultName: true,
};
},
},
columnFilter: {
name: 'workerFk',
},
},
{
align: 'left',
@ -53,9 +68,7 @@ const columns = computed(() => [
create: true,
component: 'select',
attrs: {
options: ItemCategoriesOptions.value,
fields: ['id', 'name'],
order: 'name ASC',
options: itemCategoriesOptions.value,
},
cardVisible: false,
visible: false,
@ -67,8 +80,7 @@ const columns = computed(() => [
create: true,
component: 'select',
attrs: {
url: 'Temperatures',
fields: ['id', 'name'],
options: temperatureOptions.value,
},
cardVisible: false,
visible: false,
@ -80,26 +92,52 @@ const columns = computed(() => [
<FetchData
url="ItemCategories"
:filter="{ fields: ['id', 'name'], order: ['name ASC'] }"
@on-fetch="(data) => (ItemCategoriesOptions = data)"
@on-fetch="(data) => (itemCategoriesOptions = data)"
auto-load
/>
<FetchData
url="Temperatures"
:filter="{ fields: ['id', 'name'], order: ['name ASC'] }"
@on-fetch="(data) => (temperatureOptions = data)"
auto-load
/>
<RightMenu>
<template #right-panel>
<ItemTypeFilter data-key="ItemTypeList" />
</template>
</RightMenu>
<ItemTypeSearchbar />
<VnTable
ref="tableRef"
data-key="ItemTypeList"
:url="`ItemTypes`"
url="ItemTypes"
:create="{
urlCreate: 'ItemTypes',
title: t('Create ItemTypes'),
onDataSaved: () => tableRef.reload(),
formInitialData: {},
}"
:user-filter="{
include: {
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['id', 'name'],
},
},
},
},
}"
order="name ASC"
:columns="columns"
auto-load
:right-search="false"
:is-editable="false"
:use-model="true"
redirect="item/item-type"
/>
</template>

View File

@ -2,17 +2,11 @@
import VnCard from 'components/common/VnCard.vue';
import ParkingDescriptor from 'pages/Parking/Card/ParkingDescriptor.vue';
import ParkingFilter from 'pages/Parking/ParkingFilter.vue';
const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'],
include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }],
};
</script>
<template>
<VnCard
data-key="Parking"
base-url="Parkings"
:filter="filter"
:descriptor="ParkingDescriptor"
:filter-panel="ParkingFilter"
search-data-key="ParkingList"

View File

@ -29,6 +29,7 @@ const filter = {
:url="`Parkings/${entityId}`"
title="code"
:filter="filter"
:to-module="{ name: 'ParkingList' }"
>
<template #body="{ entity }">
<VnLv :label="t('globals.code')" :value="entity.code" />

View File

@ -22,7 +22,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder'],
include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }],
};
function exprBuilder(param, value) {
@ -55,10 +54,9 @@ function exprBuilder(param, value) {
<VnPaginate
data-key="ParkingList"
url="Parkings"
:filter="filter"
:user-filter="filter"
:expr-builder="exprBuilder"
:limit="20"
auto-load
order="code"
>
<template #body="{ rows }">

View File

@ -348,7 +348,6 @@ async function hasDocuware() {
}
async function uploadDocuware(force) {
console.log('force: ', force);
if (!force)
return quasar
.dialog({

View File

@ -224,6 +224,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<template #st-actions>
<QBtnGroup push class="q-gutter-x-sm" flat>
<VnBtnSelect
data-cy="change-state"
:disable="!hasSelectedRows"
color="primary"
:label="t('globals.changeState')"

View File

@ -2,6 +2,7 @@
import { onMounted, ref, computed, onUnmounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter, useRoute } from 'vue-router';
import { useQuasar } from 'quasar';
import FetchData from 'components/FetchData.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
@ -23,6 +24,7 @@ import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnUsesMana from 'src/components/ui/VnUsesMana.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
const route = useRoute();
const router = useRouter();
@ -32,7 +34,7 @@ const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm();
const editPriceProxyRef = ref(null);
const stateBtnDropdownRef = ref(null);
const quasar = useQuasar();
const arrayData = useArrayData('ticketData');
const { store } = arrayData;
const selectedRows = ref([]);
@ -51,6 +53,7 @@ const transfer = ref({
sales: [],
});
const tableRef = ref([]);
const canProceed = ref();
watch(
() => route.params.id,
@ -214,7 +217,9 @@ const addSale = async (sale) => {
}
};
const changeQuantity = (sale) => {
const changeQuantity = async (sale) => {
canProceed.value = await isSalePrepared(sale);
if (!canProceed.value) return;
if (
!sale.itemFk ||
sale.quantity == null ||
@ -226,6 +231,8 @@ const changeQuantity = (sale) => {
};
const updateConcept = async (sale) => {
canProceed.value = await isSalePrepared(sale);
if (!canProceed.value) return;
try {
const data = { newConcept: sale.concept };
await axios.post(`Sales/${sale.id}/updateConcept`, data);
@ -286,6 +293,8 @@ const onOpenEditDiscountPopover = async (sale) => {
};
const updatePrice = async (sale) => {
canProceed.value = await isSalePrepared(sale);
if (!canProceed.value) return;
try {
const newPrice = edit.value.price;
if (newPrice != null && newPrice != sale.price) {
@ -300,12 +309,18 @@ const updatePrice = async (sale) => {
}
};
const changeDiscount = (sale) => {
const changeDiscount = async (sale) => {
canProceed.value = await isSalePrepared(sale);
if (!canProceed.value) return;
const newDiscount = edit.value.discount;
if (newDiscount != null && newDiscount != sale.discount) updateDiscount([sale]);
};
const updateDiscount = async (sales, newDiscount = null) => {
for (const sale of sales) {
const canProceed = await isSalePrepared(sale);
if (!canProceed) return;
}
const saleIds = sales.map((sale) => sale.id);
const _newDiscount = newDiscount || edit.value.discount;
const params = {
@ -433,7 +448,9 @@ onUnmounted(() => (stateStore.rightDrawer = false));
const items = ref([]);
const newRow = ref({});
const updateItem = (row) => {
const updateItem = async (row) => {
canProceed.value = await isSalePrepared(row);
if (!canProceed.value) return;
const selectedItem = items.value.find((item) => item.id === row.itemFk);
if (selectedItem) {
row.item = selectedItem;
@ -476,6 +493,55 @@ const endNewRow = (row) => {
}
};
async function isSalePrepared(item) {
const filter = {
params: {
where: { ticketFk: route.params.id },
order: ['concept ASC', 'quantity DESC'],
},
};
const { data } = await axios.get(`SaleTrackings/${route.params.id}/filter`, {
params: {
filter: JSON.stringify(filter),
},
});
const matchingSale = data.find((sale) => sale.itemFk === item.itemFk);
if (!matchingSale) {
return true;
}
if (
matchingSale.hasSaleGroupDetail ||
matchingSale.isControled ||
matchingSale.isPrepared ||
matchingSale.isPrevious ||
matchingSale.isPreviousSelected
) {
try {
await new Promise((resolve, reject) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Item prepared'),
message: t(
'This item is already prepared. Do you want to continue?'
),
data: item,
},
})
.onOk(() => resolve(true))
.onCancel(() => reject(new Error('cancelled')));
});
} catch (error) {
tableRef.value.reload();
return false;
}
}
return true;
}
watch(
() => newRow.value.itemFk,
(newItemFk) => {
@ -821,4 +887,6 @@ es:
You are going to delete lines of the ticket: Vas a eliminar lineas del ticket
Add item: Añadir artículo
Transfer lines: Transferir líneas
Item prepared: Artículo preparado
This item is already prepared. Do you want to continue?: Este artículo ya esta preparado. Desea continuar?
</i18n>

View File

@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnProgress from 'src/components/common/VnProgressModal.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import TicketAdvanceFilter from './TicketAdvanceFilter.vue';

View File

@ -72,6 +72,16 @@ const agenciesOptions = ref([]);
</VnRow>
<VnRow>
<VnInput v-model="data.m3" label="m3" />
<VnInput
:label="t('travel.basicData.daysInForward')"
v-model="data.daysInForward"
>
<template #append>
<QIcon name="info" class="cursor-info">
<QTooltip>{{ t('raidDays') }}</QTooltip>
</QIcon>
</template>
</VnInput>
</VnRow>
<VnRow>
<QCheckbox
@ -86,3 +96,10 @@ const agenciesOptions = ref([]);
</template>
</FormModel>
</template>
<i18n>
es:
raidDays: Al rellenarlo, generamos una redada. Indica los días que un travel se moverá automáticamente en el tiempo
en:
raidDays: When filling, a raid is generated. Enter the number of days the travel will automatically forward in time
</i18n>

View File

@ -32,6 +32,7 @@ const filter = {
'warehouseOutFk',
'cargoSupplierFk',
'agencyModeFk',
'daysInForward',
],
include: [
{
@ -77,6 +78,22 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
<VnLv :label="t('globals.landed')" :value="toDate(entity.landed)" />
<VnLv :label="t('globals.totalEntries')" :value="entity.totalEntries" />
</template>
<template #icons="{ entity }">
<QCardActions class="q-gutter-x-md">
<QIcon
v-if="entity.daysInForward"
name="vn:net"
color="primary"
size="xs"
>
<QTooltip>
{{
t('globals.raid', { daysInForward: entity.daysInForward })
}}</QTooltip
>
</QIcon>
</QCardActions>
</template>
<template #actions="{ entity }">
<QCardActions>
<QBtn

View File

@ -303,6 +303,10 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`;
<VnLv :label="t('globals.reference')" :value="travel.ref" />
<VnLv label="m³" :value="travel.m3" />
<VnLv :label="t('globals.totalEntries')" :value="travel.totalEntries" />
<VnLv
:label="t('travel.basicData.daysInForward')"
:value="travel?.daysInForward"
/>
</QCard>
<QCard class="full-width">
<VnTitle :text="t('travel.summary.entries')" />

View File

@ -45,6 +45,10 @@ const redirectCreateEntryView = (travelData) => {
};
const columns = computed(() => [
{
name: 'status',
columnFilter: false,
},
{
align: 'left',
name: 'id',
@ -221,6 +225,17 @@ const columns = computed(() => [
:is-editable="false"
:use-model="true"
>
<template #column-status="{ row }">
<div class="row">
<QIcon v-if="!!row.daysInForward" name="vn:net" color="primary">
<QTooltip>
{{
t('globals.raid', { daysInForward: row.daysInForward })
}}</QTooltip
>
</QIcon>
</div>
</template>
<template #column-shipped="{ row }">
<QBadge
text-color="black"

View File

@ -1,5 +1,5 @@
<script setup>
import { ref } from 'vue';
import { ref, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnInputDate from 'src/components/common/VnInputDate.vue';
@ -8,32 +8,22 @@ import FormModel from 'src/components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useAdvancedSummary } from 'src/composables/useAdvancedSummary';
const route = useRoute();
const { t } = useI18n();
const educationLevels = ref([]);
const countries = ref([]);
const maritalStatus = [
{ code: 'M', name: t('Married') },
{ code: 'S', name: t('Single') },
];
const advancedSummary = ref({});
const workerFilter = {
include: [
{
relation: 'user',
scope: {
fields: ['name', 'emailVerified'],
include: { relation: 'emailUser', scope: { fields: ['email'] } },
},
},
{ relation: 'sip', scope: { fields: ['extension', 'secret'] } },
{ relation: 'department', scope: { include: { relation: 'department' } } },
],
};
onBeforeMount(async () => {
advancedSummary.value =
(await useAdvancedSummary('Workers', +useRoute().params.id)) ?? {};
});
</script>
<template>
<FetchData
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
@ -48,10 +38,16 @@ const workerFilter = {
auto-load
/>
<FormModel
:filter="workerFilter"
:url="`Workers/${route.params.id}`"
:filter="{ where: { id: +$route.params.id } }"
url="Workers/summary"
:url-update="`Workers/${$route.params.id}`"
auto-load
model="Worker"
@on-fetch="
async (data) => {
Object.assign(data, advancedSummary);
}
"
>
<template #form="{ data }">
<VnRow>
@ -134,7 +130,7 @@ const workerFilter = {
<VnInput v-model="data.fi" :label="t('fi')" />
<VnInputDate :label="t('birth')" v-model="data.birth" />
</VnRow>
<VnRow>
<VnRow wrap>
<QCheckbox
size="sm"
:label="t('isFreelance')"

View File

@ -18,7 +18,7 @@ const { store } = useArrayData('Worker');
const entityId = computed(() => useRoute().params.id);
const filter = computed(() => ({
where: {
gender: store.data?.sex,
gender: store.data?.[0]?.sex,
or: [{ workerFk: null }, { workerFk: entityId.value }],
},
}));
@ -51,6 +51,7 @@ const init = async (data) => {
>
<template #form="{ data }">
<VnSelect
data-cy="locker"
:label="t('Locker')"
v-model="data.id"
:options="lockers"

View File

@ -2,7 +2,6 @@
import { ref, onBeforeMount, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { dashIfEmpty, toDate } from 'src/filters';
import VnLv from 'src/components/ui/VnLv.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
@ -11,7 +10,7 @@ import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
import { useRole } from 'src/composables/useRole';
import { useAdvancedSummary } from 'src/composables/useAdvancedSummary';
const route = useRoute();
const { t } = useI18n();
@ -25,18 +24,11 @@ const $props = defineProps({
const entityId = computed(() => $props.id || route.params.id);
const basicDataUrl = ref(null);
const isHr = computed(() => useRole().hasAny(['hr']));
const advancedSummary = ref();
onBeforeMount(async () => {
if (isHr.value) {
advancedSummary.value = (
await axios.get('Workers/advancedSummary', {
params: { filter: { where: { id: entityId.value } } },
})
).data[0];
basicDataUrl.value = `#/worker/${entityId.value}/basic-data`;
}
advancedSummary.value = await useAdvancedSummary('Workers', entityId.value);
basicDataUrl.value = `#/worker/${entityId.value}/basic-data`;
});
</script>
@ -101,21 +93,27 @@ onBeforeMount(async () => {
:label="t('worker.summary.seniority')"
:value="toDate(worker.seniority)"
/>
<VnLv :label="t('worker.summary.fi')" :value="worker.fi" />
<VnLv :label="t('worker.summary.birth')" :value="toDate(worker.birth)" />
<VnLv :label="t('worker.summary.fi')" :value="advancedSummary.fi" />
<VnLv
:label="t('worker.summary.birth')"
:value="toDate(advancedSummary.birth)"
/>
<VnLv
:label="t('worker.summary.isFreelance')"
:value="worker.isFreelance"
:value="advancedSummary.isFreelance"
/>
<VnLv
:label="t('worker.summary.isSsDiscounted')"
:value="worker.isSsDiscounted"
:value="advancedSummary.isSsDiscounted"
/>
<VnLv
:label="t('worker.summary.hasMachineryAuthorized')"
:value="worker.hasMachineryAuthorized"
:value="advancedSummary.hasMachineryAuthorized"
/>
<VnLv
:label="t('worker.summary.isDisable')"
:value="advancedSummary.isDisable"
/>
<VnLv :label="t('worker.summary.isDisable')" :value="worker.isDisable" />
</QCard>
<QCard class="vn-one">
<VnTitle :text="t('worker.summary.userData')" />

View File

@ -97,14 +97,6 @@ export default {
},
component: () => import('src/pages/Item/ItemTypeList.vue'),
},
{
path: 'item-type-list/create',
name: 'ItemTypeCreate',
meta: {
title: 'itemTypeCreate',
},
component: () => import('src/pages/Item/ItemTypeCreate.vue'),
},
],
},
{

View File

@ -18,7 +18,7 @@ export default {
{
name: 'ItemTypeCard',
path: ':id',
component: () => import('src/pages/ItemType/Card/ItemTypeCard.vue'),
component: () => import('src/pages/Item/ItemType/Card/ItemTypeCard.vue'),
redirect: { name: 'ItemTypeSummary' },
children: [
{
@ -28,7 +28,7 @@ export default {
title: 'summary',
},
component: () =>
import('src/pages/ItemType/Card/ItemTypeSummary.vue'),
import('src/pages/Item/ItemType/Card/ItemTypeSummary.vue'),
},
{
name: 'ItemTypeBasicData',
@ -38,7 +38,7 @@ export default {
icon: 'vn:settings',
},
component: () =>
import('src/pages/ItemType/Card/ItemTypeBasicData.vue'),
import('src/pages/Item/ItemType/Card/ItemTypeBasicData.vue'),
},
{
path: 'log',
@ -47,7 +47,8 @@ export default {
title: 'log',
icon: 'vn:History',
},
component: () => import('src/pages/ItemType/Card/ItemTypeLog.vue'),
component: () =>
import('src/pages/Item/ItemType/Card/ItemTypeLog.vue'),
},
],
},

View File

@ -87,6 +87,13 @@ export default {
meta: {
title: 'basicData',
icon: 'vn:settings',
acls: [
{
model: 'Worker',
props: 'updateAttributes',
accessType: 'WRITE',
},
],
},
component: () => import('src/pages/Worker/Card/WorkerBasicData.vue'),
},

View File

@ -1,5 +1,6 @@
/// <reference types="cypress" />
describe('ParkingList', () => {
const searchbar = '#searchbar input';
const firstCard = '.q-card:nth-child(1)';
const firstChipId =
':nth-child(1) > :nth-child(1) > .justify-between > .flex > .q-chip > .q-chip__content';
@ -14,6 +15,7 @@ describe('ParkingList', () => {
});
it('should redirect on clicking a parking', () => {
cy.get(searchbar).type('{enter}');
cy.get(firstChipId)
.invoke('text')
.then((content) => {
@ -24,6 +26,7 @@ describe('ParkingList', () => {
});
it('should open the details', () => {
cy.get(searchbar).type('{enter}');
cy.get(firstDetailBtn).click();
cy.get(summaryHeader).contains('Basic data');
});

View File

@ -11,15 +11,13 @@ describe('Ticket expedtion', () => {
it('should change the state', () => {
cy.visit('#/ticket/1/expedition');
cy.intercept('GET', /\/api\/Expeditions\/filter/).as('expeditions');
cy.intercept('POST', /\/api\/Expeditions\/crud/).as('crud');
cy.wait('@expeditions');
cy.intercept('GET', /\/api\/Expeditions\/filter/).as('show');
cy.intercept('POST', /\/api\/ExpeditionStates\/addExpeditionState/).as('add');
cy.wait('@show');
cy.selectRows([1, 2]);
cy.get('#subToolbar [aria-controls]:nth-child(1)').click();
cy.get('.q-menu .q-item').contains('Perdida').click();
cy.wait('@crud');
cy.selectOption('[data-cy="change-state"]', 'Perdida');
cy.wait('@add');
cy.get(`${tableContent} tr:nth-child(-n+2) ${stateTd}`).each(($el) => {
cy.wrap($el).contains('Perdida');

View File

@ -11,7 +11,7 @@ describe('WorkerList', () => {
it('should open the worker summary', () => {
cy.get(inputName).type('jessica{enter}');
cy.get(searchBtn).click();
cy.intercept('GET', /\/api\/Workers\/\d+/).as('worker');
cy.intercept('GET', /\/api\/Workers\/summary+/).as('worker');
cy.wait('@worker').then(() =>
cy.get(descriptorTitle).should('include.text', 'Jessica')
);

View File

@ -1,8 +1,7 @@
describe('WorkerLocker', () => {
const productionId = 49;
const lockerCode = '2F';
const input = '.q-card input';
const thirdOpt = '[role="listbox"] .q-item:nth-child(1)';
const lockerCode = '4F';
const lockerSelect = '[data-cy="locker"]';
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('productionBoss');
@ -10,10 +9,8 @@ describe('WorkerLocker', () => {
});
it('should allocates a locker', () => {
cy.get(input).click();
cy.waitForElement('[role="listbox"]');
cy.get(thirdOpt).click();
cy.selectOption(lockerSelect, lockerCode);
cy.saveCard();
cy.get(input).invoke('val').should('eq', lockerCode);
cy.get(lockerSelect).invoke('val').should('eq', lockerCode);
});
});

View File

@ -82,7 +82,7 @@ Cypress.Commands.add('getValue', (selector) => {
// Fill Inputs
Cypress.Commands.add('selectOption', (selector, option) => {
cy.waitForElement(selector);
cy.get(selector).find('.q-select__dropdown-icon').click();
cy.get(selector).click();
cy.get('.q-menu .q-item').contains(option).click();
});

View File

@ -0,0 +1,120 @@
import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import TicketAdvance from 'pages/Ticket/TicketAdvance.vue';
import { Notify } from 'quasar';
import { nextTick } from 'vue';
describe('TicketAdvance', () => {
let wrapper;
let vm;
beforeAll(() => {
vi.spyOn(axios, 'get').mockImplementation(() => ({ data: [] }));
wrapper = createWrapper(TicketAdvance);
vm = wrapper.vm;
vi.spyOn(vm.vnTableRef, 'reload').mockImplementation(() => vi.fn());
vm.vnTableRef.value = { params: {} };
});
beforeEach(() => {
Notify.create = vi.fn();
});
afterEach(() => {
vi.clearAllMocks();
});
describe('requestComponentUpdate()', () => {
const mockTicket = {
futureId: 1,
futureClientFk: 1,
nickname: 'test',
futureAddressFk: 1,
futureAgencyModeFk: 1,
futureWarehouseFk: 1,
futureCompanyFk: 1,
landed: '2023-01-02',
zoneFk: 1,
};
const mockParams = {
clientFk: 1,
nickname: 'test',
agencyModeFk: 1,
addressFk: 1,
zoneFk: 1,
warehouseFk: 1,
companyFk: 1,
landed: '2023-01-02',
shipped: '2023-01-01',
isDeleted: false,
isWithoutNegatives: false,
newTicket: undefined,
keepPrice: true,
};
const queryResult = 'tickets/1/componentUpdate';
it('should return query and params when ticket has no landed', async () => {
vm.vnTableRef.params.dateToAdvance = '2023-01-01';
await nextTick();
const mockLanded = { landed: '2023-01-02', zoneFk: 1 };
vi.spyOn(vm, 'getLanded').mockResolvedValue(mockLanded);
const { query, params } = await vm.requestComponentUpdate(mockTicket, false);
expect(query).toBe(queryResult);
expect(params).toEqual(mockParams);
});
it('should return query and params when ticket has landed', async () => {
const { query, params } = await vm.requestComponentUpdate(mockTicket, false);
expect(query).toBe(queryResult);
expect(params).toEqual(mockParams);
});
});
describe('moveTicketsAdvance()', () => {
it('should move tickets and notify success', async () => {
const tickets = [
{
id: 1,
futureId: 2,
futureShipped: '2023-01-01',
shipped: '2023-01-02',
workerFk: 1,
},
{
id: 2,
futureId: 3,
futureShipped: '2023-01-01',
shipped: '2023-01-02',
workerFk: 1,
},
];
vm.selectedTickets = tickets;
vi.spyOn(axios, 'post').mockResolvedValue({});
await vm.moveTicketsAdvance();
expect(axios.post).toHaveBeenCalledOnce('Tickets/merge', {
tickets: [
{
originId: 2,
destinationId: 1,
originShipped: '2023-01-01',
destinationShipped: '2023-01-02',
workerFk: 1,
},
{
originId: 3,
destinationId: 2,
originShipped: '2023-01-01',
destinationShipped: '2023-01-02',
workerFk: 1,
},
],
});
expect(vm.vnTableRef.reload).toHaveBeenCalled();
expect(Notify.create).toHaveBeenCalled();
expect(vm.selectedTickets).toEqual([]);
});
});
});

View File

@ -70,8 +70,10 @@ class FormDataMock {
vi.fn();
}
}
global.FormData = FormDataMock;
global.URL = class URL {};
global.Date.vnNew = () => new Date(Date.UTC(2001, 0, 1, 11));
export function createWrapper(component, options) {
const defaultOptions = {