Items summary #281

Merged
alexm merged 8 commits from :feature/ItemsSummary into dev 2024-04-25 12:01:08 +00:00
11 changed files with 529 additions and 116 deletions
Showing only changes of commit 4a0ae8aa45 - Show all commits

View File

@ -1213,9 +1213,13 @@ export default {
pageTitles: { pageTitles: {
jsegarra marked this conversation as resolved Outdated
Outdated
Review

Pasar traducciones al ingles

Pasar traducciones al ingles

Traducido

Traducido
items: 'Items', items: 'Items',
list: 'List', list: 'List',
diary: 'Diary', diary: 'Histórico',
tags: 'Tags', tags: 'Etiquetas',
create: 'Create', create: 'Crear',
basicData: 'Datos básicos',
tax: 'IVA',
botanical: 'Botánico',
barcode: 'Código de barras',
}, },
descriptor: { descriptor: {
item: 'Item', item: 'Item',
@ -1246,6 +1250,37 @@ export default {
producer: 'Producer', producer: 'Producer',
landed: 'Landed', landed: 'Landed',
}, },
summary: {
basicData: 'Datos básicos',
otherData: 'Otros datos',
description: 'Descripción',
tax: 'IVA',
tags: 'Etiquetas',
botanical: 'Botánico',
barcode: 'Código de barras',
name: 'Nombre',
completeName: 'Nombre completo',
family: 'Familia',
size: 'Medida',
origin: 'Origen',
stems: 'Tallos',
multiplier: 'Multiplicador',
buyer: 'Comprador',
doPhoto: 'Hacer foto',
intrastatCode: 'Código intrastat',
intrastat: 'Intrastat',
ref: 'Referencia',
relevance: 'Relevancia',
weight: 'Peso (gramos)/tallo',
units: 'Unidades/caja',
expense: 'Gasto',
generic: 'Genérico',
recycledPlastic: 'Plástico reciclado',
nonRecycledPlastic: 'Plástico no reciclado',
minSalesQuantity: 'Cantidad mínima de venta',
genus: 'Genus',
specie: 'Specie',
},
}, },
components: { components: {
topbar: {}, topbar: {},

View File

@ -1215,6 +1215,10 @@ export default {
diary: 'Histórico', diary: 'Histórico',
tags: 'Etiquetas', tags: 'Etiquetas',
create: 'Crear', create: 'Crear',
basicData: 'Datos básicos',
tax: 'IVA',
botanical: 'Botánico',
barcode: 'Código de barras',
}, },
descriptor: { descriptor: {
item: 'Artículo', item: 'Artículo',
@ -1245,6 +1249,37 @@ export default {
producer: 'Productor', producer: 'Productor',
landed: 'F. entrega', landed: 'F. entrega',
}, },
summary: {
basicData: 'Datos básicos',
otherData: 'Otros datos',
description: 'Descripción',
tax: 'IVA',
tags: 'Etiquetas',
botanical: 'Botánico',
barcode: 'Código de barras',
name: 'Nombre',
completeName: 'Nombre completo',
family: 'Familia',
size: 'Medida',
origin: 'Origen',
stems: 'Tallos',
multiplier: 'Multiplicador',
buyer: 'Comprador',
doPhoto: 'Hacer foto',
intrastatCode: 'Código intrastat',
intrastat: 'Intrastat',
ref: 'Referencia',
relevance: 'Relevancia',
weight: 'Peso (gramos)/tallo',
units: 'Unidades/caja',
expense: 'Gasto',
generic: 'Genérico',
recycledPlastic: 'Plástico reciclado',
nonRecycledPlastic: 'Plástico no reciclado',
minSalesQuantity: 'Cantidad mínima de venta',
genus: 'Genus',
specie: 'Specie',
},
}, },
components: { components: {
topbar: {}, topbar: {},

View File

@ -0,0 +1 @@
<template>Item barcode</template>

View File

@ -0,0 +1 @@
<template>Item basic data</template>

View File

@ -0,0 +1 @@
<template>Item Botanical</template>

View File

@ -9,7 +9,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import RegularizeStockForm from 'components/RegularizeStockForm.vue'; import RegularizeStockForm from 'components/RegularizeStockForm.vue';
import EditPictureForm from 'components/EditPictureForm.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
@ -50,12 +50,10 @@ const entityId = computed(() => {
}); });
const image = ref(null); const image = ref(null);
const regularizeStockFormDialog = ref(null); const regularizeStockFormDialog = ref(null);
const editPhotoFormDialog = ref(null);
const item = ref(null); const item = ref(null);
const available = ref(null); const available = ref(null);
const visible = ref(null); const visible = ref(null);
const _warehouseFk = ref(null); const _warehouseFk = ref(null);
const warehouseText = ref(null);
const salixUrl = ref(); const salixUrl = ref();
const warehouseFk = computed({ const warehouseFk = computed({
get() { get() {
@ -63,14 +61,9 @@ const warehouseFk = computed({
}, },
set(val) { set(val) {
_warehouseFk.value = val; _warehouseFk.value = val;
if (val) { if (val) updateStock();
updateStock();
getWarehouseName(val);
}
}, },
}); });
const showWarehouseIconTooltip = ref(true);
const showEditPhotoForm = ref(false);
onMounted(async () => { onMounted(async () => {
await getItemAvatar(); await getItemAvatar();
@ -90,26 +83,6 @@ const setData = (entity) => {
data.value = useCardDescription(entity.name, entity.id); data.value = useCardDescription(entity.name, entity.id);
}; };
const getWarehouseName = async (warehouseFk) => {
try {
showWarehouseIconTooltip.value = false;
const filter = {
where: { id: warehouseFk },
};
const { data } = await axios.get('Warehouses/findOne', { filter });
warehouseText.value = t('item.descriptor.warehouseText', {
warehouseName: data.name,
});
showWarehouseIconTooltip.value = true;
} catch (err) {
console.error('Error finding warehouse');
}
};
const updateStock = async () => { const updateStock = async () => {
try { try {
available.value = null; available.value = null;
@ -135,10 +108,6 @@ const openRegularizeStockForm = () => {
regularizeStockFormDialog.value.show(); regularizeStockFormDialog.value.show();
}; };
const toggleEditPictureForm = () => {
showEditPhotoForm.value = !showEditPhotoForm.value;
};
const cloneItem = async () => { const cloneItem = async () => {
try { try {
const { data } = await axios.post(`Items/${entityId.value}/clone`); const { data } = await axios.post(`Items/${entityId.value}/clone`);
@ -198,74 +167,11 @@ const openCloneDialog = async () => {
</QItem> </QItem>
</template> </template>
<template #before> <template #before>
<div class="relative-position"> <ItemDescriptorImage
<QImg :src="image" spinner-color="primary" class="photo"> :entity-id="entityId"
<template #error> :visible="visible"
<div :available="available"
class="absolute-full picture text-center q-pa-md flex flex-center"
>
<div>
<div
class="text-grey-5"
style="opacity: 0.4; font-size: 5vh"
>
<QIcon name="vn:item" />
</div>
<div class="text-grey-5" style="opacity: 0.4">
{{ t('item.descriptor.item') }}
</div>
</div>
</div>
</template>
</QImg>
<QBtn
color="primary"
size="lg"
round
class="edit-photo-btn"
@click="toggleEditPictureForm()"
>
<QIcon name="edit" size="sm" />
<QDialog ref="editPhotoFormDialog" v-model="showEditPhotoForm">
<EditPictureForm
collection="catalog"
:id="entityId"
@close-form="toggleEditPictureForm()"
@on-photo-uploaded="getItemAvatar()"
/> />
</QDialog>
</QBtn>
</div>
<div
class="row justify-between items-center full-width bg-primary"
style="height: 54px"
>
<div class="col column items-center">
<span class="text-uppercase color-vn-white" style="font-size: 11px">
{{ t('item.descriptor.visible') }}
</span>
<span class="text-weight-bold text-h5 color-vn-white">{{
visible
}}</span>
</div>
<div
class="col column items-center separation-borders"
style="font-size: 11px"
>
<span class="text-uppercase color-vn-white">
{{ t('item.descriptor.available') }}
</span>
<span class="text-weight-bold text-h5 color-vn-white">{{
available
}}</span>
</div>
<div class="col column items-center justify-center">
<QIcon name="info" class="cursor-pointer color-vn-white" size="md">
<QTooltip>{{ warehouseText }}</QTooltip>
</QIcon>
</div>
</div>
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('item.descriptor.buyer')"> <VnLv :label="t('item.descriptor.buyer')">

View File

@ -0,0 +1,159 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import EditPictureForm from 'components/EditPictureForm.vue';
import { useSession } from 'src/composables/useSession';
import axios from 'axios';
const $props = defineProps({
visible: {
type: Number,
default: null,
},
available: {
type: Number,
default: null,
},
entityId: {
type: String,
default: null,
},
showEditButton: {
type: Boolean,
default: true,
},
});
const { t } = useI18n();
const { getTokenMultimedia } = useSession();
const image = ref(null);
const editPhotoFormDialog = ref(null);
const showEditPhotoForm = ref(false);
const warehouseName = ref(null);
const getItemAvatar = async () => {
const token = getTokenMultimedia();
const timeStamp = `timestamp=${Date.now()}`;
image.value = `/api/Images/catalog/200x200/${$props.entityId}/download?access_token=${token}&${timeStamp}`;
};
const toggleEditPictureForm = () => {
showEditPhotoForm.value = !showEditPhotoForm.value;
};
const getItemConfigs = async () => {
const { data } = await axios.get('ItemConfigs/findOne');
if (!data) return;
await getWarehouseName(data.warehouseFk);
return data;
};
const getWarehouseName = async (warehouseFk) => {
const filter = {
where: { id: warehouseFk },
};
const { data } = await axios.get('Warehouses/findOne', { filter });
if (!data) return;
warehouseName.value = data.name;
};
onMounted(async () => {
getItemAvatar();
getItemConfigs();
});
</script>
<template>
<div class="relative-position">
<QImg :src="image" spinner-color="primary" style="min-height: 256px">
<template #error>
<div class="absolute-full picture text-center q-pa-md flex flex-center">
<div>
<div class="text-grey-5" style="opacity: 0.4; font-size: 5vh">
<QIcon name="vn:item" />
</div>
<div class="text-grey-5" style="opacity: 0.4">
{{ t('item.descriptor.item') }}
</div>
</div>
</div>
</template>
</QImg>
<QBtn
v-if="showEditButton"
color="primary"
size="lg"
round
class="edit-photo-btn"
@click="toggleEditPictureForm()"
>
<QIcon name="edit" size="sm" />
<QDialog ref="editPhotoFormDialog" v-model="showEditPhotoForm">
<EditPictureForm
collection="catalog"
:id="entityId"
@close-form="toggleEditPictureForm()"
@on-photo-uploaded="getItemAvatar()"
/>
</QDialog>
</QBtn>
</div>
<div
class="row justify-between items-center full-width bg-primary"
style="height: 54px"
>
<div class="col column items-center">
<span class="text-uppercase color-vn-white" style="font-size: 11px">
{{ t('item.descriptor.visible') }}
</span>
<span class="text-weight-bold text-h5 color-vn-white">{{ visible }}</span>
</div>
<div class="col column items-center separation-borders" style="font-size: 11px">
<span class="text-uppercase color-vn-white">
{{ t('item.descriptor.available') }}
</span>
<span class="text-weight-bold text-h5 color-vn-white">{{ available }}</span>
</div>
<div class="col column items-center justify-center">
<QIcon name="info" class="cursor-pointer color-vn-white" size="md">
<QTooltip>{{
t('warehouseText', {
warehouseName: warehouseName,
})
}}</QTooltip>
</QIcon>
</div>
</div>
</template>
<i18n>
es:
Regularize stock: Regularizar stock
Clone: Clonar
jsegarra marked this conversation as resolved
Review

Usar globals

Usar globals
All it's properties will be copied: Todas sus propiedades serán copiadas
Do you want to clone this item?: ¿Desea clonar este artículo?
Review

Ver pq no funciona traducción

Ver pq no funciona traducción
warehouseText: Calculated on the warehouse of { warehouseName }
en:
warehouseText: Calculado sobre el almacén de { warehouseName }
</i18n>
<style lang="scss" scoped>
.edit-photo-btn {
position: absolute;
right: 12px;
bottom: 12px;
z-index: 1;
cursor: pointer;
}
.separation-borders {
border-left: 1px solid $white;
border-right: 1px solid $white;
}
</style>

View File

@ -1 +1,231 @@
<template>Item summary</template> <script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { useRole } from 'src/composables/useRole';
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const route = useRoute();
const { t } = useI18n();
const roleState = useRole();
const entityId = computed(() => $props.id || route.params.id);
const isBuyer = computed(() => {
return roleState.hasAny(['buyer']);
});
const isReplenisher = computed(() => {
return roleState.hasAny(['replenisher']);
});
const isAdministrative = computed(() => {
return roleState.hasAny(['administrative']);
});
</script>
<template>
<CardSummary
ref="summary"
:url="`Items/${entityId}/getSummary`"
:entity-id="entityId"
data-key="ItemSummary"
>
<template #header-left>
<router-link
v-if="route.name !== 'ItemSummary'"
:to="{ name: 'ItemSummary', params: { id: entityId } }"
class="header link"
>
<QIcon name="open_in_new" color="white" size="sm" />
</router-link>
</template>
<template #header="{ entity: { item } }">
{{ item.id }} - {{ item.name }}
</template>
<template #body="{ entity: { item, tags, visible, available } }">
<QCard class="vn-one photo">
<ItemDescriptorImage
:entity-id="entityId"
:visible="visible"
:available="available"
:show-edit-button="false"
/>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBasicData', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.basicData') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnLv :label="t('item.summary.name')" :value="item.name" />
<VnLv :label="t('item.summary.completeName')" :value="item.longName" />
<VnLv :label="t('item.summary.family')" :value="item.itemType.name" />
<VnLv :label="t('item.summary.size')" :value="item.size" />
<VnLv :label="t('item.summary.origin')" :value="item.origin.name" />
<VnLv :label="t('item.summary.stems')" :value="item.stems" />
<VnLv
:label="t('item.summary.multiplier')"
:value="item.stemMultiplier"
/>
<VnLv :label="t('item.summary.buyer')">
<template #value>
<VnUserLink
:name="item.itemType.worker.user.name"
:worker-id="item.itemType.worker.id"
/>
</template>
</VnLv>
<VnLv :info="t('Este artículo necesita una foto')">
<template #value>
<QCheckbox
:label="t('item.summary.doPhoto')"
v-model="item.isPhotoRequested"
:disable="true"
/>
</template>
</VnLv>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBasicData', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.otherData') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnLv
:label="t('item.summary.intrastatCode')"
:value="item.intrastat.id"
/>
<VnLv
:label="t('item.summary.intrastat')"
:value="item.intrastat.description"
/>
<VnLv :label="t('item.summary.ref')" :value="item.comment" />
<VnLv :label="t('item.summary.relevance')" :value="item.relevancy" />
<VnLv :label="t('item.summary.weight')" :value="item.weightByPiece" />
<VnLv :label="t('item.summary.units')" :value="item.packingOut" />
<VnLv :label="t('item.summary.expense')" :value="item.expense.name" />
<VnLv :label="t('item.summary.generic')" :value="item.genericFk" />
<VnLv
:label="t('item.summary.recycledPlastic')"
:value="item.recycledPlastic"
/>
<VnLv
:label="t('item.summary.nonRecycledPlastic')"
:value="item.nonRecycledPlastic"
/>
<VnLv
:label="t('item.summary.minSalesQuantity')"
:value="item.minQuantity"
/>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer || isReplenisher ? 'router-link' : 'span'"
:to="{ name: 'ItemTags', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer || isReplenisher }"
>
{{ t('item.summary.tags') }}
<QIcon v-if="isBuyer || isReplenisher" name="open_in_new" />
</component>
<VnLv
v-for="(tag, index) in tags"
:key="index"
:label="`${tag.priority} ${tag.tag.name}`"
:value="tag.value"
/>
</QCard>
<QCard class="vn-one" v-if="item.description">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBasicData', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.description') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<p>
{{ item.description }}
</p>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer || isAdministrative ? 'router-link' : 'span'"
:to="{ name: 'ItemTax', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer || isAdministrative }"
>
{{ t('item.summary.tax') }}
<QIcon v-if="isBuyer || isAdministrative" name="open_in_new" />
</component>
<VnLv
v-for="(tax, index) in item.taxes"
:key="index"
:label="tax.country.country"
:value="tax.taxClass.description"
/>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBotanical', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.botanical') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnLv
:label="t('item.summary.genus')"
:value="item.botanical?.genus?.name"
jsegarra marked this conversation as resolved
Review

item.summary?.botanical?.genus?.name

item.**summary**?.botanical?.genus?.name
Review

Corregido:

Commit: 5f46ad225e

Corregido: Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/5f46ad225e82b80a6cb3bb849f18f59dd8acdcad
/>
<VnLv
:label="t('item.summary.specie')"
:value="item.botanical?.specie?.name"
jsegarra marked this conversation as resolved
Review

item.summary?.botanical?.specie?.name

Lo demás esta OK

item.**summary**?.botanical?.specie?.name Lo demás esta OK
Review

Corregido:

Commit: 5f46ad225e

Corregido: Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/5f46ad225e82b80a6cb3bb849f18f59dd8acdcad
/>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer || isReplenisher ? 'router-link' : 'span'"
:to="{ name: 'ItemBarcode', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer || isReplenisher }"
>
{{ t('item.summary.barcode') }}
<QIcon v-if="isBuyer || isReplenisher" name="open_in_new" />
</component>
<p v-for="(barcode, index) in item.itemBarcode" :key="index">
{{ barcode.code }}
</p>
</QCard>
</template>
</CardSummary>
</template>
<i18n>
en:
Este artículo necesita una foto: Este artículo necesita una foto
</i18n>

View File

@ -0,0 +1 @@
<template>Item tax</template>

View File

@ -436,7 +436,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
:all-columns="allColumnNames" :all-columns="allColumnNames"
table-code="itemsIndex" table-code="itemsIndex"
labels-traductions-path="item.list" labels-traductions-path="item.list"
@on-config-saved="visibleColumns = ['picture', ...$event]" @on-config-saved="visibleColumns = ['picture', ...$event, 'actions']"
/> />
</div> </div>
<QSpace /> <QSpace />

View File

@ -11,7 +11,14 @@ export default {
redirect: { name: 'ItemMain' }, redirect: { name: 'ItemMain' },
menus: { menus: {
main: ['ItemList'], main: ['ItemList'],
card: [], card: [
'ItemBasicData',
'ItemDiary',
'ItemTags',
'ItemTax',
'ItemBotanical',
'ItemBarcode',
],
}, },
children: [ children: [
{ {
@ -54,6 +61,52 @@ export default {
}, },
component: () => import('src/pages/Item/Card/ItemSummary.vue'), component: () => import('src/pages/Item/Card/ItemSummary.vue'),
}, },
{
path: 'basic-data',
name: 'ItemBasicData',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('src/pages/Item/Card/ItemBasicData.vue'),
},
{
path: 'tags',
name: 'ItemTags',
meta: {
title: 'tags',
icon: 'vn:tags',
},
component: () => import('src/pages/Item/Card/ItemTags.vue'),
},
{
path: 'tax',
name: 'ItemTax',
meta: {
title: 'tax',
icon: 'vn:tax',
},
component: () => import('src/pages/Item/Card/ItemTax.vue'),
},
{
path: 'botanical',
name: 'ItemBotanical',
meta: {
title: 'botanical',
icon: 'vn:botanical',
},
component: () => import('src/pages/Item/Card/ItemBotanical.vue'),
},
{
path: 'barcode',
name: 'ItemBarcode',
meta: {
title: 'barcode',
icon: 'vn:barcode',
},
component: () => import('src/pages/Item/Card/ItemBarcode.vue'),
},
{ {
path: 'diary', path: 'diary',
name: 'ItemDiary', name: 'ItemDiary',
@ -63,15 +116,6 @@ export default {
}, },
component: () => import('src/pages/Item/Card/ItemDiary.vue'), component: () => import('src/pages/Item/Card/ItemDiary.vue'),
}, },
{
path: 'tags',
name: 'ItemTags',
meta: {
title: 'Tags',
icon: 'vn:tags',
},
component: () => import('src/pages/Item/Card/ItemTags.vue'),
},
], ],
}, },
], ],