#7283 #7831 itemMigration #553

Merged
carlossa merged 77 commits from 7283-itemMigration into dev 2024-10-25 07:09:13 +00:00
24 changed files with 1020 additions and 1134 deletions

View File

@ -247,6 +247,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
} }
function updateStateParams() { function updateStateParams() {
if (!route) return;
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } }; const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter); newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);

View File

@ -1055,92 +1055,6 @@ travel:
warehouse: Warehouse warehouse: Warehouse
travelFileDescription: 'Travel id { travelId }' travelFileDescription: 'Travel id { travelId }'
file: File file: File
item:
descriptor:
item: Item
buyer: Buyer
color: Color
category: Category
stems: Stems
visible: Visible
available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary
producer: Producer
list:
id: Identifier
grouping: Grouping
packing: Packing
description: Description
stems: Stems
category: Category
typeName: Type
intrastat: Intrastat
isActive: Active
size: Size
origin: Origin
userName: Buyer
weightByPiece: Weight/Piece
stemMultiplier: Multiplier
producer: Producer
landed: Landed
fixedPrice:
itemFk: Item ID
groupingPrice: Grouping price
packingPrice: Packing price
hasMinPrice: Has min price
minPrice: Min price
started: Started
ended: Ended
warehouse: Warehouse
create:
name: Name
tag: Tag
priority: Priority
type: Type
intrastat: Intrastat
origin: Origin
buyRequest:
ticketId: 'Ticket ID'
shipped: 'Shipped'
requester: 'Requester'
requested: 'Requested'
price: 'Price'
attender: 'Atender'
item: 'Item'
achieved: 'Achieved'
concept: 'Concept'
state: 'State'
summary:
basicData: 'Basic data'
otherData: 'Other data'
description: 'Description'
tax: 'Tax'
tags: 'Tags'
botanical: 'Botanical'
barcode: 'Barcode'
name: 'Nombre'
completeName: 'Nombre completo'
family: 'Familia'
size: 'Medida'
origin: 'Origen'
stems: 'Tallos'
multiplier: 'Multiplicador'
buyer: 'Comprador'
doPhoto: 'Do photo'
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: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -1053,92 +1053,6 @@ travel:
warehouse: Almacén warehouse: Almacén
travelFileDescription: 'Id envío { travelId }' travelFileDescription: 'Id envío { travelId }'
file: Fichero file: Fichero
item:
descriptor:
item: Artículo
buyer: Comprador
color: Color
category: Categoría
stems: Tallos
visible: Visible
available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta
producer: Productor
list:
id: Identificador
grouping: Grouping
packing: Packing
description: Descripción
stems: Tallos
category: Reino
typeName: Tipo
intrastat: Intrastat
isActive: Activo
size: Medida
origin: Origen
weightByPiece: Peso (gramos)/tallo
userName: Comprador
stemMultiplier: Multiplicador
producer: Productor
landed: F. entrega
fixedPrice:
itemFk: ID Artículo
groupingPrice: Precio grouping
packingPrice: Precio packing
hasMinPrice: Tiene precio mínimo
minPrice: Precio min
started: Inicio
ended: Fin
warehouse: Almacén
create:
name: Nombre
tag: Etiqueta
priority: Prioridad
type: Tipo
intrastat: Intrastat
origin: Origen
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'
buyRequest:
ticketId: 'ID Ticket'
shipped: 'F. envío'
requester: 'Solicitante'
requested: 'Solicitado'
price: 'Precio'
attender: 'Comprador'
item: 'Artículo'
achieved: 'Conseguido'
concept: 'Concepto'
state: 'Estado'
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -74,7 +74,7 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('View Summary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
action: (row) => viewSummary(row.id, AccountSummary), action: (row) => viewSummary(row.id, AccountSummary),
isPrimary: true, isPrimary: true,

View File

@ -54,7 +54,7 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('View Summary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
action: (row) => viewSummary(row.id, RoleSummary), action: (row) => viewSummary(row.id, RoleSummary),
isPrimary: true, isPrimary: true,

View File

@ -63,7 +63,7 @@ const onIntrastatCreated = (response, formData) => {
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>
<VnSelect <VnSelect
:label="t('itemBasicData.type')" :label="t('item.basicData.type')"
v-model="data.typeFk" v-model="data.typeFk"
:options="itemTypesOptions" :options="itemTypesOptions"
option-value="id" option-value="id"
@ -82,17 +82,26 @@ const onIntrastatCreated = (response, formData) => {
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelect>
<VnInput :label="t('itemBasicData.reference')" v-model="data.comment" /> <VnInput :label="t('item.basicData.reference')" v-model="data.comment" />
<VnInput :label="t('itemBasicData.relevancy')" v-model="data.relevancy" />
</VnRow>
<VnRow>
<VnInput :label="t('itemBasicData.stems')" v-model="data.stems" />
<VnInput <VnInput
:label="t('itemBasicData.multiplier')" :label="t('item.basicData.relevancy')"
carlossa marked this conversation as resolved
Review

Debería aceptar solo números

Debería aceptar solo números
type="number"
v-model="data.relevancy"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
:label="t('item.basicData.stems')"
type="number"
v-model="data.stems"
/>
<VnInput
:label="t('item.basicData.multiplier')"
type="number"
v-model="data.stemMultiplier" v-model="data.stemMultiplier"
/> />
<VnSelectDialog <VnSelectDialog
:label="t('itemBasicData.generic')" :label="t('item.basicData.generic')"
v-model="data.genericFk" v-model="data.genericFk"
url="Items/withName" url="Items/withName"
:fields="['id', 'name']" :fields="['id', 'name']"
@ -121,7 +130,7 @@ const onIntrastatCreated = (response, formData) => {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelectDialog <VnSelectDialog
:label="t('itemBasicData.intrastat')" :label="t('item.basicData.intrastat')"
v-model="data.intrastatFk" v-model="data.intrastatFk"
:options="intrastatsOptions" :options="intrastatsOptions"
option-value="id" option-value="id"
@ -148,7 +157,7 @@ const onIntrastatCreated = (response, formData) => {
</VnSelectDialog> </VnSelectDialog>
<div class="col"> <div class="col">
<VnSelect <VnSelect
:label="t('itemBasicData.expense')" :label="t('item.basicData.expense')"
v-model="data.expenseFk" v-model="data.expenseFk"
:options="expensesOptions" :options="expensesOptions"
option-value="id" option-value="id"
@ -160,64 +169,67 @@ const onIntrastatCreated = (response, formData) => {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput <VnInput
:label="t('itemBasicData.weightByPiece')" :label="t('item.basicData.weightByPiece')"
v-model.number="data.weightByPiece" v-model.number="data.weightByPiece"
:min="0" :min="0"
type="number" type="number"
/> />
<VnInput <VnInput
:label="t('itemBasicData.boxUnits')" :label="t('item.basicData.boxUnits')"
v-model.number="data.packingOut" v-model.number="data.packingOut"
:min="0" :min="0"
type="number" type="number"
/> />
<VnInput <VnInput
:label="t('itemBasicData.recycledPlastic')" :label="t('item.basicData.recycledPlastic')"
v-model.number="data.recycledPlastic" v-model.number="data.recycledPlastic"
:min="0" :min="0"
type="number" type="number"
/> />
<VnInput <VnInput
:label="t('itemBasicData.nonRecycledPlastic')" :label="t('item.basicData.nonRecycledPlastic')"
v-model.number="data.nonRecycledPlastic" v-model.number="data.nonRecycledPlastic"
:min="0" :min="0"
type="number" type="number"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="row q-gutter-md q-mb-md">
<QCheckbox v-model="data.isActive" :label="t('itemBasicData.isActive')" /> <QCheckbox
v-model="data.isActive"
:label="t('item.basicData.isActive')"
/>
<QCheckbox <QCheckbox
v-model="data.hasKgPrice" v-model="data.hasKgPrice"
:label="t('itemBasicData.hasKgPrice')" :label="t('item.basicData.hasKgPrice')"
/> />
<div> <div>
<QCheckbox <QCheckbox
v-model="data.isFragile" v-model="data.isFragile"
:label="t('itemBasicData.isFragile')" :label="t('item.basicData.isFragile')"
class="q-mr-sm" class="q-mr-sm"
/> />
<QIcon name="info" class="cursor-pointer" size="xs"> <QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip max-width="300px"> <QTooltip max-width="300px">
{{ t('itemBasicData.isFragileTooltip') }} {{ t('item.basicData.isFragileTooltip') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</div> </div>
<div> <div>
<QCheckbox <QCheckbox
v-model="data.isPhotoRequested" v-model="data.isPhotoRequested"
:label="t('itemBasicData.isPhotoRequested')" :label="t('item.basicData.isPhotoRequested')"
class="q-mr-sm" class="q-mr-sm"
/> />
<QIcon name="info" class="cursor-pointer" size="xs"> <QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip> <QTooltip>
{{ t('itemBasicData.isPhotoRequestedTooltip') }} {{ t('item.basicData.isPhotoRequestedTooltip') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</div> </div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput <VnInput
:label="t('itemBasicData.description')" :label="t('item.basicData.description')"
type="textarea" type="textarea"
v-model="data.description" v-model="data.description"
fill-input fill-input

View File

@ -20,12 +20,6 @@ let itemBotanicalsForm = reactive({ itemFk: null });
const entityId = computed(() => { const entityId = computed(() => {
return route.params.id; return route.params.id;
}); });
onMounted(async () => {
itemBotanicalsForm.itemFk = entityId.value;
itemBotanicals.value = await itemBotanicalsRef.value.fetch();
if (itemBotanicals.value.length > 0)
Object.assign(itemBotanicalsForm, itemBotanicals.value[0]);
});
</script> </script>
<template> <template>
<FetchData <FetchData
@ -37,11 +31,14 @@ onMounted(async () => {
@on-fetch="(data) => (itemBotanicals = data)" @on-fetch="(data) => (itemBotanicals = data)"
/> />
<FormModel <FormModel
url="ItemBotanicals"
url-update="ItemBotanicals" url-update="ItemBotanicals"
model="entry" model="item"
auto-load auto-load
:form-initial-data="itemBotanicalsForm" :filter="{
:clear-store-on-unmount="false" where: { itemFk: entityId },
}"
@on-fetch="(data) => (itemBotanicalsForm = data)"
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>

View File

@ -1,18 +1,18 @@
<script setup> <script setup>
import { computed, ref, onMounted } from 'vue'; import { computed, ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; 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 RegularizeStockForm from 'components/RegularizeStockForm.vue'; import RegularizeStockForm from 'components/RegularizeStockForm.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import axios from 'axios'; import axios from 'axios';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData';
import { cloneItem } from 'src/pages/Item/composables/cloneItem';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -38,9 +38,8 @@ const $props = defineProps({
}, },
}); });
const quasar = useQuasar(); const { openCloneDialog } = cloneItem();
const route = useRoute(); const route = useRoute();
const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const warehouseConfig = ref(null); const warehouseConfig = ref(null);
const entityId = computed(() => { const entityId = computed(() => {
@ -48,84 +47,52 @@ const entityId = computed(() => {
}); });
const regularizeStockFormDialog = ref(null); const regularizeStockFormDialog = ref(null);
const available = ref(null); const mounted = ref();
const visible = ref(null);
const arrayDataStock = useArrayData('descriptorStock', {
url: `Items/${entityId.value}/getVisibleAvailable`,
});
onMounted(async () => { onMounted(async () => {
await getItemConfigs(); await getItemConfigs();
await updateStock(); mounted.value = true;
}); });
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = async (entity) => { const setData = async (entity) => {
try { if (!entity) return;
if (!entity) return; data.value = useCardDescription(entity.name, entity.id);
data.value = useCardDescription(entity.name, entity.id); await updateStock();
await updateStock();
} catch (err) {
console.error('Error item');
}
}; };
const getItemConfigs = async () => { const getItemConfigs = async () => {
try { const { data } = await axios.get('ItemConfigs/findOne');
const { data } = await axios.get('ItemConfigs/findOne'); if (!data) return;
if (!data) return; return (warehouseConfig.value = data.warehouseFk);
return (warehouseConfig.value = data.warehouseFk);
} catch (err) {
console.error('Error item');
}
}; };
const updateStock = async () => { const updateStock = async () => {
try { if (!mounted.value) return;
available.value = null; await getItemConfigs();
visible.value = null;
const params = { const params = {
warehouseFk: $props.warehouseFk, warehouseFk: $props.warehouseFk ?? warehouseConfig.value,
dated: $props.dated, dated: $props.dated,
}; };
await getItemConfigs(); if (!params.warehouseFk) return;
carlossa marked this conversation as resolved
Review

Deberia aparecer el almacen seleccionado al abrir el modal segun el valor del usaurio

Deberia aparecer el almacen seleccionado al abrir el modal segun el valor del usaurio
if (!params.warehouseFk) {
params.warehouseFk = warehouseConfig.value; const stock = useArrayData('descriptorStock', {
} url: `Items/${entityId.value}/getVisibleAvailable`,
const { data } = await axios.get(`Items/${entityId.value}/getVisibleAvailable`, { userParams: params,
params, });
}); const storeData = stock.store.data;
available.value = data.available; if (storeData?.itemFk == entityId.value) return;
visible.value = data.visible; await stock.fetch({});
} catch (err) {
console.error('Error updating stock');
}
}; };
const openRegularizeStockForm = () => { const openRegularizeStockForm = () => {
regularizeStockFormDialog.value.show(); regularizeStockFormDialog.value.show();
}; };
const cloneItem = async () => {
try {
const { data } = await axios.post(`Items/${entityId.value}/clone`);
router.push({ name: 'ItemTags', params: { id: data.id } });
} catch (err) {
console.error('Error cloning item');
}
};
const openCloneDialog = async () => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('All its properties will be copied'),
message: t('Do you want to clone this item?'),
},
})
.onOk(async () => {
await cloneItem();
});
};
</script> </script>
<template> <template>
@ -151,7 +118,7 @@ const openCloneDialog = async () => {
</QDialog> </QDialog>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable @click="openCloneDialog()"> <QItem v-ripple clickable @click="openCloneDialog(entityId)">
<QItemSection> <QItemSection>
{{ t('globals.clone') }} {{ t('globals.clone') }}
</QItemSection> </QItemSection>
@ -160,8 +127,8 @@ const openCloneDialog = async () => {
<template #before> <template #before>
<ItemDescriptorImage <ItemDescriptorImage
:entity-id="entityId" :entity-id="entityId"
:visible="visible" :visible="arrayDataStock.store.data?.visible"
:available="available" :available="arrayDataStock.store.data?.available"
/> />
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
@ -194,6 +161,18 @@ const openCloneDialog = async () => {
:value="entity.value7" :value="entity.value7"
/> />
</template> </template>
<template #icons="{ entity }">
<QCardActions v-if="entity" class="q-gutter-x-md">
<QIcon
v-if="!entity.isActive"
name="vn:unavailable"
color="primary"
size="xs"
>
<QTooltip>{{ t('Inactive article') }}</QTooltip>
</QIcon>
</QCardActions>
</template>
<template #actions="{}"> <template #actions="{}">
<QCardActions class="row justify-center"> <QCardActions class="row justify-center">
<QBtn <QBtn
@ -216,8 +195,7 @@ const openCloneDialog = async () => {
<i18n> <i18n>
es: es:
Regularize stock: Regularizar stock Regularize stock: Regularizar stock
All its properties will be copied: Todas sus propiedades serán copiadas Inactive article: Artículo inactivo
Do you want to clone this item?: ¿Desea clonar este artículo?
</i18n> </i18n>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -32,6 +32,10 @@ const editPhotoFormDialog = ref(null);
const showEditPhotoForm = ref(false); const showEditPhotoForm = ref(false);
const warehouseName = ref(null); const warehouseName = ref(null);
onMounted(async () => {
getItemConfigs();
});
const toggleEditPictureForm = () => { const toggleEditPictureForm = () => {
showEditPhotoForm.value = !showEditPhotoForm.value; showEditPhotoForm.value = !showEditPhotoForm.value;
}; };
@ -56,10 +60,6 @@ const getWarehouseName = async (warehouseFk) => {
warehouseName.value = data.name; warehouseName.value = data.name;
}; };
onMounted(async () => {
getItemConfigs();
});
const handlePhotoUpdated = (evt = false) => { const handlePhotoUpdated = (evt = false) => {
image.value.reload(evt); image.value.reload(evt);
}; };
@ -134,10 +134,10 @@ es:
Regularize stock: Regularizar stock Regularize stock: Regularizar stock
All it's properties will be copied: Todas sus propiedades serán copiadas 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? Do you want to clone this item?: ¿Desea clonar este artículo?
warehouseText: Calculated on the warehouse of { warehouseName } warehouseText: Calculado sobre el almacén de { warehouseName }
en: en:
warehouseText: Calculado sobre el almacén de { warehouseName } warehouseText: Calculated on the warehouse of { warehouseName }
</i18n> </i18n>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -17,6 +17,7 @@ import { toDateFormat } from 'src/filters/date.js';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { date } from 'quasar'; import { date } from 'quasar';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useArrayData } from 'src/composables/useArrayData';
import axios from 'axios'; import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
@ -37,6 +38,33 @@ const warehouseFk = ref(null);
const _showWhatsBeforeInventory = ref(false); const _showWhatsBeforeInventory = ref(false);
const inventoriedDate = ref(null); const inventoriedDate = ref(null);
const originTypeMap = {
entry: {
descriptor: EntryDescriptorProxy,
icon: 'vn:entry',
color: 'green',
},
ticket: {
descriptor: TicketDescriptorProxy,
icon: 'vn:ticket',
color: 'red',
},
order: {
descriptor: OrderDescriptorProxy,
icon: 'vn:basket',
color: 'yellow',
},
};
const entityTypeMap = {
client: {
descriptor: CustomerDescriptorProxy,
},
supplier: {
descriptor: SupplierDescriptorProxy,
},
};
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'claim', name: 'claim',
@ -105,6 +133,28 @@ const showWhatsBeforeInventory = computed({
}, },
}); });
onMounted(async () => {
today.value.setHours(0, 0, 0, 0);
if (route.query.warehouseFk) warehouseFk.value = route.query.warehouseFk;
else if (user.value) warehouseFk.value = user.value.warehouseFk;
itemsBalanceFilter.where.warehouseFk = warehouseFk.value;
const { data } = await axios.get('Configs/findOne');
inventoriedDate.value = data.inventoried;
await fetchItemBalances();
await scrollToToday();
await updateWarehouse(warehouseFk.value);
});
onUnmounted(() => (stateStore.rightDrawer = false));
watch(
() => router.currentRoute.value.params.id,
(newId) => {
itemsBalanceFilter.where.itemFk = newId;
itemBalancesRef.value.fetch();
}
);
const fetchItemBalances = async () => await itemBalancesRef.value.fetch(); const fetchItemBalances = async () => await itemBalancesRef.value.fetch();
const getBadgeAttrs = (_date) => { const getBadgeAttrs = (_date) => {
@ -131,53 +181,15 @@ const formatDateForAttribute = (dateValue) => {
return dateValue; return dateValue;
}; };
const originTypeMap = { async function updateWarehouse(warehouseFk) {
entry: { const stock = useArrayData('descriptorStock', {
descriptor: EntryDescriptorProxy, userParams: {
icon: 'vn:entry', warehouseFk,
color: 'green', },
}, });
ticket: { await stock.fetch({});
descriptor: TicketDescriptorProxy, stock.store.data.itemFk = route.params.id;
icon: 'vn:ticket', }
color: 'red',
},
order: {
descriptor: OrderDescriptorProxy,
icon: 'vn:basket',
color: 'yellow',
},
};
const entityTypeMap = {
client: {
descriptor: CustomerDescriptorProxy,
},
supplier: {
descriptor: SupplierDescriptorProxy,
},
};
onMounted(async () => {
today.value.setHours(0, 0, 0, 0);
if (route.query.warehouseFk) warehouseFk.value = route.query.warehouseFk;
else if (user.value) warehouseFk.value = user.value.warehouseFk;
itemsBalanceFilter.where.warehouseFk = warehouseFk.value;
const { data } = await axios.get('Configs/findOne');
inventoriedDate.value = data.inventoried;
await fetchItemBalances();
await scrollToToday();
});
onUnmounted(() => (stateStore.rightDrawer = false));
watch(
() => router.currentRoute.value.params.id,
(newId) => {
itemsBalanceFilter.where.itemFk = newId;
itemBalancesRef.value.fetch();
}
);
</script> </script>
<template> <template>
@ -193,36 +205,39 @@ watch(
auto-load auto-load
@on-fetch="(data) => (warehousesOptions = data)" @on-fetch="(data) => (warehousesOptions = data)"
/> />
<QToolbar class="justify-end"> <template v-if="stateStore.isHeaderMounted()">
<div id="st-data" class="row"> <Teleport to="#st-data">
<VnSelect <div class="row">
:label="t('itemDiary.warehouse')" <VnSelect
:options="warehousesOptions" :label="t('itemDiary.warehouse')"
hide-selected :options="warehousesOptions"
option-label="name" hide-selected
option-value="id" option-label="name"
dense option-value="id"
v-model="itemsBalanceFilter.where.warehouseFk" dense
@update:model-value="fetchItemBalances" v-model="itemsBalanceFilter.where.warehouseFk"
class="q-mr-lg" @update:model-value="
/> (value) => fetchItemBalances() && updateWarehouse(value)
<QCheckbox "
:label="t('itemDiary.showBefore')" class="q-mr-lg"
v-model="showWhatsBeforeInventory" />
@update:model-value="fetchItemBalances" <QCheckbox
class="q-mr-lg" :label="t('itemDiary.showBefore')"
/> v-model="showWhatsBeforeInventory"
<VnInputDate @update:model-value="fetchItemBalances"
v-if="showWhatsBeforeInventory" class="q-mr-lg"
:label="t('itemDiary.since')" />
dense <VnInputDate
v-model="itemsBalanceFilter.where.date" v-if="showWhatsBeforeInventory"
@update:model-value="fetchItemBalances" :label="t('itemDiary.since')"
/> dense
</div> v-model="itemsBalanceFilter.where.date"
<QSpace /> @update:model-value="fetchItemBalances"
<div id="st-actions"></div> />
</QToolbar> </div>
</Teleport>
<Teleport to="#st-actions"> </Teleport>
</template>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <QTable
:rows="itemBalances" :rows="itemBalances"

View File

@ -119,7 +119,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
<VnLv <VnLv
v-for="(tag, index) in tags" v-for="(tag, index) in tags"
:key="index" :key="index"
:label="`${tag.priority} ${tag.tag.name}`" :label="`${tag.priority} ${tag.tag.name}:`"
:value="tag.value" :value="tag.value"
/> />
</QCard> </QCard>
@ -135,7 +135,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
<VnLv <VnLv
v-for="(tax, index) in item.taxes" v-for="(tax, index) in item.taxes"
:key="index" :key="index"
:label="tax.country.country" :label="tax.country.name"
:value="tax.taxClass.description" :value="tax.taxClass.description"
/> />
</QCard> </QCard>
@ -155,7 +155,8 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
:url="getUrl(entityId, 'barcode')" :url="getUrl(entityId, 'barcode')"
:text="t('item.summary.barcode')" :text="t('item.summary.barcode')"
/> />
<p <div
class="text-bold"
v-for="(barcode, index) in item.itemBarcode" v-for="(barcode, index) in item.itemBarcode"
:key="index" :key="index"
v-text="barcode.code" v-text="barcode.code"

View File

@ -22,7 +22,7 @@ const taxesFilter = {
{ {
relation: 'country', relation: 'country',
scope: { scope: {
fields: ['country'], fields: ['name'],
}, },
}, },
], ],
@ -73,7 +73,7 @@ const submitTaxes = async (data) => {
> >
<VnInput <VnInput
:label="t('tax.country')" :label="t('tax.country')"
v-model="row.country.country" v-model="row.country.name"
disable disable
/> />
<VnSelect <VnSelect

View File

@ -1,584 +1,360 @@
<script setup> <script setup>
import { onMounted, ref, computed, reactive, onUnmounted } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemSummary from '../Item/Card/ItemSummary.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import ItemListFilter from './ItemListFilter.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDateFormat } from 'src/filters/date.js';
import { dashIfEmpty } from 'src/filters';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useVnConfirm } from 'composables/useVnConfirm';
import axios from 'axios';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import { toDate } from 'src/filters';
import FetchedTags from 'src/components/ui/FetchedTags.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import ItemSummary from '../Item/Card/ItemSummary.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
import { cloneItem } from 'src/pages/Item/composables/cloneItem';
const router = useRouter(); const entityId = computed(() => route.params.id);
const stateStore = useStateStore(); const { openCloneDialog } = cloneItem();
const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const { openConfirmationModal } = useVnConfirm(); const { t } = useI18n();
const tableRef = ref();
const route = useRoute();
const paginateRef = ref(null); const itemFilter = {
const itemTypesOptions = ref([]); include: [
const originsOptions = ref([]); {
const buyersOptions = ref([]); relation: 'itemType',
const intrastatOptions = ref([]); scope: {
const itemCategoriesOptions = ref([]); fields: ['id', 'name'],
const visibleColumns = ref([]); },
const allColumnNames = ref([]); },
{
const exprBuilder = (param, value) => { relation: 'intrastat',
switch (param) { scope: {
case 'category': fields: ['id', 'description'],
return { 'ic.name': value }; },
case 'buyerFk': },
return { 'it.workerFk': value }; {
case 'grouping': relation: 'origin',
return { 'b.grouping': value }; scope: {
case 'packing': fields: ['id', 'name'],
return { 'b.packing': value }; },
case 'origin': },
return { 'ori.code': value }; ],
case 'typeFk':
return { 'i.typeFk': value };
case 'intrastat':
return { 'intr.description': value };
case 'name':
return { 'i.name': { like: `%${value}%` } };
case 'producer':
return { 'pr.name': { like: `%${value}%` } };
case 'id':
case 'size':
case 'subname':
case 'isActive':
case 'weightByPiece':
case 'stemMultiplier':
case 'stems':
return { [`i.${param}`]: value };
}
}; };
const params = reactive({ isFloramondo: false, isActive: true });
const applyColumnFilter = async (col) => {
try {
const paramKey = col.columnFilter?.filterParamKey || col.field;
params[paramKey] = col.columnFilter.filterValue;
await paginateRef.value.addFilter(null, params);
} catch (err) {
console.error('Error applying column filter', err);
}
};
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const columns = computed(() => [ const columns = computed(() => [
{ {
label: '', label: '',
name: 'picture', name: 'image',
align: 'left', align: 'left',
columnFilter: null, columnField: {
component: VnImg,
attrs: ({ row }) => {
return {
id: row?.id,
zoomResolution: '1600x900',
zoom: true,
class: 'rounded',
};
},
},
columnFilter: false,
cardVisible: true,
}, },
{ {
label: t('item.list.id'), label: t('item.list.id'),
name: 'id', name: 'id',
field: 'id',
align: 'left', align: 'left',
sortable: true, isId: true,
columnFilter: { chip: {
component: VnInput, condition: () => true,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
cardVisible: true,
}, },
{ {
label: t('item.list.grouping'), label: t('item.list.grouping'),
field: 'grouping',
name: 'grouping', name: 'grouping',
align: 'left', align: 'left',
sortable: true,
columnFilter: { columnFilter: {
component: VnInput, component: 'number',
type: 'text', inWhere: true,
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('item.list.packing'), label: t('item.list.packing'),
field: 'packing',
name: 'packing', name: 'packing',
align: 'left', align: 'left',
sortable: true,
columnFilter: { columnFilter: {
component: VnInput, component: 'number',
type: 'text', inWhere: true,
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('globals.description'), label: t('globals.description'),
field: 'name',
name: 'description', name: 'description',
align: 'left', align: 'left',
sortable: true, create: true,
columnFilter: { columnFilter: {
component: VnInput, name: 'search',
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
cardVisible: true,
}, },
{ {
label: t('item.list.stems'), label: t('item.list.stems'),
field: 'stems',
name: 'stems', name: 'stems',
align: 'left', align: 'left',
sortable: true,
columnFilter: { columnFilter: {
component: VnInput, component: 'number',
type: 'text', inWhere: true,
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
cardVisible: true,
}, },
{ {
label: t('item.list.size'), label: t('item.list.size'),
field: 'size',
name: 'size', name: 'size',
align: 'left', align: 'left',
sortable: true,
columnFilter: { columnFilter: {
component: VnInput, component: 'number',
type: 'text', inWhere: true,
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
cardVisible: true,
}, },
{ {
label: t('item.list.typeName'), label: t('item.list.typeName'),
field: 'typeName', name: 'typeFk',
name: 'typeName',
align: 'left', align: 'left',
sortable: true, component: 'select',
attrs: {
url: 'ItemTypes',
fields: ['id', 'name'],
},
columnFilter: { columnFilter: {
component: VnSelect, name: 'typeFk',
filterParamKey: 'typeFk',
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: { attrs: {
options: itemTypesOptions.value, url: 'ItemTypes',
'option-value': 'id', fields: ['id', 'name'],
'option-label': 'name',
dense: true,
}, },
}, },
columnField: {
component: null,
},
create: true,
}, },
{ {
label: t('item.list.category'), label: t('item.list.category'),
field: 'category',
name: 'category', name: 'category',
align: 'left', align: 'left',
sortable: true, component: 'select',
columnFilter: { columnFilter: {
component: VnSelect, name: 'categoryFk',
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: { attrs: {
options: itemCategoriesOptions.value, url: 'ItemCategories',
'option-value': 'name', fields: ['id', 'name'],
'option-label': 'name',
dense: true,
}, },
}, },
columnField: {
component: null,
},
}, },
{ {
label: t('item.list.intrastat'), label: t('item.list.intrastat'),
field: 'intrastat',
name: 'intrastat', name: 'intrastat',
align: 'left', align: 'left',
sortable: true, component: 'select',
columnFilter: { attrs: {
component: VnSelect, url: 'Intrastats',
type: 'select', optionValue: 'description',
filterValue: null, optionLabel: 'description',
event: getInputEvents,
attrs: {
options: intrastatOptions.value,
'option-value': 'description',
'option-label': 'description',
dense: true,
},
}, },
columnFilter: {
name: 'description',
attrs: {
url: 'Intrastats',
optionValue: 'description',
optionLabel: 'description',
},
alias: 'intr',
},
columnField: {
component: null,
},
create: true,
cardVisible: true,
}, },
{ {
label: t('item.list.origin'), label: t('item.list.origin'),
field: 'origin',
name: 'origin', name: 'origin',
align: 'left', align: 'left',
sortable: true, component: 'select',
columnFilter: { attrs: {
component: VnSelect, url: 'Origins',
type: 'select', optionValue: 'id',
filterValue: null, optionLabel: 'code',
event: getInputEvents,
attrs: {
options: originsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
}, },
columnFilter: {
name: 'id',
attrs: {
url: 'Origins',
optionValue: 'id',
optionLabel: 'code',
},
inWhere: true,
alias: 'ori',
},
columnField: {
component: null,
},
create: true,
cardVisible: true,
}, },
{ {
label: t('item.list.userName'), label: t('item.list.userName'),
field: 'userName',
name: 'userName', name: 'userName',
align: 'left', align: 'left',
sortable: true,
columnFilter: { columnFilter: {
component: VnSelect, name: 'workerFk',
filterParamKey: 'buyerFk',
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: { attrs: {
options: buyersOptions.value, url: 'Users',
'option-value': 'id', optionValue: 'id',
'option-label': 'nickname', optionLabel: 'userName',
dense: true,
}, },
}, },
}, },
{ {
label: t('item.list.weightByPiece'), label: t('item.list.weightByPiece'),
field: 'weightByPiece',
name: 'weightByPiece', name: 'weightByPiece',
align: 'left', align: 'left',
sortable: true, component: 'input',
columnFilter: { columnField: {
component: VnInput, component: null,
type: 'text', },
filterValue: null, columnFilter: {
event: getInputEvents, inWhere: true,
attrs: {
dense: true,
},
}, },
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('item.list.stemMultiplier'), label: t('item.list.stemMultiplier'),
field: 'stemMultiplier',
name: 'stemMultiplier', name: 'stemMultiplier',
align: 'left', align: 'left',
sortable: true, component: 'input',
columnFilter: { columnField: {
component: VnInput, component: null,
type: 'text', },
filterValue: null, columnFilter: {
event: getInputEvents, inWhere: true,
attrs: {
dense: true,
},
}, },
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('item.list.isActive'), label: t('item.list.isActive'),
field: 'isActive',
name: 'isActive', name: 'isActive',
align: 'left', align: 'center',
sortable: true, component: 'checkbox',
columnFilter: null,
}, },
{ {
label: t('item.list.producer'), label: t('item.list.producer'),
field: 'producer',
name: 'producer', name: 'producer',
align: 'left', align: 'left',
sortable: true, component: 'select',
columnFilter: { attrs: {
component: VnInput, url: 'Producers',
type: 'text', fields: ['id', 'name'],
filterValue: null, },
event: getInputEvents, columnField: {
attrs: { component: null,
dense: true,
},
}, },
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('item.list.landed'), label: t('item.list.landed'),
field: 'landed',
name: 'landed', name: 'landed',
align: 'left', align: 'left',
sortable: true, component: 'date',
format: (val) => dashIfEmpty(toDateFormat(val)), columnField: {
columnFilter: null, component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)),
}, },
{ {
align: 'right',
label: '', label: '',
name: 'actions', name: 'tableActions',
align: 'left', actions: [
columnFilter: null, {
title: t('globals.clone'),
icon: 'vn:clone',
action: openCloneDialog,
isPrimary: true,
},
{
title: t('components.smartCard.viewSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, ItemSummary),
isPrimary: true,
},
],
}, },
]); ]);
const redirectToItemCreate = () => {
router.push({ name: 'ItemCreate' });
};
const redirectToItemSummary = (id) => {
router.push({ name: 'ItemSummary', params: { id } });
};
const cloneItem = async (itemFk) => {
try {
const { data } = await axios.post(`Items/${itemFk}/clone`);
if (!data) return;
router.push({ name: 'ItemTags', params: { id: data.id } });
} catch (err) {
console.error('Error cloning item', err);
}
};
onMounted(async () => {
stateStore.rightDrawer = true;
const filteredColumns = columns.value.filter(
(col) => col.name !== 'picture' && col.name !== 'actions'
);
allColumnNames.value = filteredColumns.map((col) => col.name);
});
onUnmounted(() => (stateStore.rightDrawer = false));
</script> </script>
<template> <template>
<FetchData <VnSearchbar
url="ItemTypes" data-key="ItemList"
:filter="{ fields: ['id', 'name'], order: 'name ASC' }" :label="t('item.searchbar.label')"
auto-load :info="t('You can search by id')"
@on-fetch="(data) => (itemTypesOptions = data)"
/> />
<FetchData <VnTable
url="ItemCategories" ref="tableRef"
:filter="{ fields: ['name'], order: 'name ASC' }" data-key="ItemList"
url="Items/filter"
url-create="Items"
:create="{
urlCreate: 'Items',
title: t('Create Item'),
onDataSaved: () => tableRef.redirect(),
formInitialData: {
editorFk: entityId,
},
}"
:order="['isActive DESC', 'name', 'id']"
:columns="columns"
auto-load auto-load
@on-fetch="(data) => (itemCategoriesOptions = data)" redirect="Item"
/> :is-editable="false"
<FetchData :filer="itemFilter"
url="Intrastats" >
:filter="{ fields: ['description'], order: 'description ASC' }" <template #column-id="{ row }">
auto-load <span class="link" @click.stop>
@on-fetch="(data) => (intrastatOptions = data)" {{ row.id }}
/> <ItemDescriptorProxy :id="row.id" />
<FetchData </span>
url="Origins"
:filter="{ fields: ['code'], order: 'code ASC' }"
auto-load
@on-fetch="(data) => (originsOptions = data)"
/>
<FetchData
url="TicketRequests/getItemTypeWorker"
:filter="{ fields: ['id', 'nickname'], order: 'nickname ASC' }"
auto-load
@on-fetch="(data) => (buyersOptions = data)"
/>
<VnSubToolbar>
<template #st-data>
<TableVisibleColumns
:all-columns="allColumnNames"
table-code="itemsIndex"
labels-traductions-path="item.list"
@on-config-saved="visibleColumns = ['picture', ...$event, 'actions']"
/>
</template> </template>
</VnSubToolbar> <template #column-userName="{ row }">
<RightMenu> <span class="link" @click.stop>
<template #right-panel> {{ row.userName }}
<ItemListFilter data-key="ItemList" /> <WorkerDescriptorProxy :id="row.buyerFk" />
</span>
</template> </template>
</RightMenu> <template #column-description="{ row }">
<QPage class="column items-center q-pa-md"> <div class="row column full-width justify-between items-start">
<VnPaginate {{ row?.name }}
ref="paginateRef" <div v-if="row?.subName" class="subName">
data-key="ItemList" {{ row?.subName.toUpperCase() }}
url="Items/filter" </div>
:order="['isActive DESC', 'name', 'id']" </div>
:limit="12" <FetchedTags :item="row" :max-length="6" />
:expr-builder="exprBuilder" </template>
:user-params="params" </VnTable>
:keep-opts="['userParams']"
:offset="50"
auto-load
>
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:visible-columns="visibleColumns"
:no-data-label="t('globals.noResults')"
@row-click="(_, row) => redirectToItemSummary(row.id)"
>
<template #top-row="{ cols }">
<QTr>
<QTd
v-for="(col, index) in cols"
:key="index"
style="max-width: 100px"
>
<component
:is="col.columnFilter.component"
v-if="col.columnFilter"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #body-cell-picture="{ row }">
<QTd>
<VnImg
size="50x50"
zoom-resolution="1600x900"
:id="row.id"
class="image"
/>
</QTd>
</template>
<template #body-cell-id="{ row }">
<QTd @click.stop>
<QBtn flat color="primary">
{{ row.id }}
</QBtn>
<ItemDescriptorProxy :id="row.id" />
</QTd>
</template>
<template #body-cell-userName="{ row }">
<QTd @click.stop>
<QBtn flat color="primary" dense>
{{ row.userName }}
</QBtn>
<WorkerDescriptorProxy :id="row.buyerFk" />
</QTd>
</template>
<template #body-cell-description="{ row }">
<QTd class="col">
<span>{{ row.name }} {{ row.subName }}</span>
<FetchedTags :item="row" />
</QTd>
</template>
<template #body-cell-isActive="{ row }">
<QTd>
<QCheckbox :model-value="!!row.isActive" disable />
</QTd>
</template>
<template #body-cell-actions="{ row }">
<QTd>
<QIcon
@click.stop="
openConfirmationModal(
t(`All it's properties will be copied`),
t('Do you want to clone this item?'),
() => cloneItem(row.id)
)
"
class="q-ml-sm"
color="primary"
name="vn:clone"
size="sm"
>
<QTooltip>
{{ t('globals.clone') }}
</QTooltip>
</QIcon>
<QIcon
@click.stop="viewSummary(row.id, ItemSummary)"
class="q-ml-md"
color="primary"
name="preview"
size="sm"
>
<QTooltip class="text-no-wrap">
{{ t('Preview') }}
</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
</template>
</VnPaginate>
<QPageSticky :offset="[20, 20]">
<QBtn
@click="redirectToItemCreate()"
color="primary"
fab
icon="add"
shortcut="+"
/>
<QTooltip class="text-no-wrap">
{{ t('New item') }}
</QTooltip>
</QPageSticky>
</QPage>
</template> </template>
<style lang="scss" scoped>
.subName {
text-transform: uppercase;
color: var(--vn-label-color);
}
</style>
<i18n> <i18n>
es: es:
New item: Nuevo artículo New item: Nuevo artículo
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?
Preview: Vista previa Preview: Vista previa
Regularize stock: Regularizar stock
</i18n> </i18n>

View File

@ -1,23 +1,17 @@
<script setup> <script setup>
import { ref, computed, onMounted, onBeforeMount, watch } from 'vue'; import { ref, computed, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import VnInput from 'src/components/common/VnInput.vue';
import ItemRequestDenyForm from './ItemRequestDenyForm.vue';
import ItemRequestFilter from './ItemRequestFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSelect from 'components/common/VnSelect.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { toDateFormat } from 'src/filters/date'; import { dashIfEmpty, toCurrency } from 'filters/index';
import { toCurrency } from 'filters/index';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js';
import axios from 'axios'; import axios from 'axios';
import RightMenu from 'src/components/common/RightMenu.vue'; import ItemRequestDenyForm from './ItemRequestDenyForm.vue';
import { toDate } from 'src/filters';
import VnTable from 'components/VnTable/VnTable.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -33,6 +27,12 @@ const arrayData = useArrayData('ItemRequests', {
}); });
const store = arrayData.store; const store = arrayData.store;
const userParams = {
state: 'pending',
daysOnward: 7,
};
const tableRef = ref();
watch( watch(
() => store.data, () => store.data,
(value) => (itemRequestsOptions.value = value) (value) => (itemRequestsOptions.value = value)
@ -41,89 +41,113 @@ watch(
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('item.buyRequest.ticketId'), label: t('item.buyRequest.ticketId'),
name: 'id', name: 'ticketFk',
field: 'id',
align: 'left', align: 'left',
sortable: true, isId: true,
chip: {
condition: () => true,
},
cardVisible: true,
}, },
{ {
label: t('item.buyRequest.shipped'), label: t('item.buyRequest.shipped'),
field: 'shipped',
name: 'shipped', name: 'shipped',
align: 'left', align: 'left',
sortable: true, component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)),
columnClass: 'shrink',
}, },
{ {
label: t('globals.description'), label: t('globals.description'),
field: 'description', field: 'description',
name: 'description', name: 'description',
align: 'left', align: 'left',
sortable: true, columnClass: 'expand',
}, },
{ {
label: t('item.buyRequest.requester'), label: t('item.buyRequest.requester'),
name: 'requester', name: 'requesterName',
align: 'left', columnClass: 'shrink',
sortable: true,
}, },
{ {
label: t('item.buyRequest.requested'), label: t('item.buyRequest.requested'),
field: 'quantity', name: 'quantity',
name: 'requested', columnClass: 'shrink',
align: 'left',
sortable: true,
}, },
{ {
label: t('item.buyRequest.price'), label: t('item.buyRequest.price'),
field: 'price',
name: 'price', name: 'price',
align: 'left', align: 'left',
sortable: true, format: (row) => toCurrency(row.price),
format: (val) => toCurrency(val), columnClass: 'shrink',
}, },
{ {
label: t('item.buyRequest.attender'), label: t('item.buyRequest.attender'),
field: 'attender', name: 'attenderName',
name: 'attender',
align: 'left', align: 'left',
sortable: true, columnClass: 'shrink',
}, },
{ {
label: t('item.buyRequest.item'), label: t('item.buyRequest.item'),
field: 'item',
name: 'item', name: 'item',
align: 'left', align: 'left',
sortable: true, component: 'input',
columnClass: 'expand',
}, },
{ {
label: t('item.buyRequest.achieved'), label: t('item.buyRequest.achieved'),
field: 'achieved',
name: 'achieved', name: 'achieved',
align: 'left', align: 'left',
sortable: true, component: 'input',
columnClass: 'shrink',
}, },
{ {
label: t('item.buyRequest.concept'), label: t('item.buyRequest.concept'),
field: 'concept',
name: 'concept', name: 'concept',
align: 'left', align: 'left',
sortable: true, sortable: true,
component: 'input',
columnClass: 'expand',
}, },
{ {
label: t('item.buyRequest.state'), label: t('item.buyRequest.state'),
field: 'state',
name: 'state', name: 'state',
format: (row) => getState(row.isOk),
align: 'left', align: 'left',
sortable: true,
}, },
{ {
label: '',
name: 'action',
align: 'left', align: 'left',
columnFilter: null, name: 'daysOnward',
label: t('travel.travelList.tableVisibleColumns.daysOnward'),
visible: false,
columnFilter: {
inWhere: false,
},
},
{
align: 'right',
label: '',
name: 'denyOptions',
}, },
]); ]);
const getBadgeColor = (date) => {
const today = Date.vnNew();
today.setHours(0, 0, 0, 0);
const orderLanded = new Date(date);
orderLanded.setHours(0, 0, 0, 0);
const difference = today - orderLanded;
if (difference == 0) return 'warning';
if (difference < 0) return 'success';
if (difference > 0) return 'alert';
};
const changeQuantity = async (request) => { const changeQuantity = async (request) => {
try { try {
if (request.saleFk) { if (request.saleFk) {
@ -153,7 +177,6 @@ const confirmRequest = async (request) => {
`TicketRequests/${request.id}/confirm`, `TicketRequests/${request.id}/confirm`,
params params
); );
request.itemDescription = data.concept; request.itemDescription = data.concept;
request.isOk = true; request.isOk = true;
notify(t('globals.dataSaved'), 'positive'); notify(t('globals.dataSaved'), 'positive');
@ -182,172 +205,123 @@ const onDenyAccept = (_, responseData) => {
itemRequestsOptions.value[denyRequestIndex.value].response = responseData.response; itemRequestsOptions.value[denyRequestIndex.value].response = responseData.response;
denyRequestId.value = null; denyRequestId.value = null;
denyRequestIndex.value = null; denyRequestIndex.value = null;
tableRef.value.reload();
}; };
onMounted(async () => { onMounted(async () => {
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
}); });
onBeforeMount(() => {
const today = Date.vnNew();
today.setHours(0, 0, 0, 0);
const nextWeek = Date.vnNew();
nextWeek.setHours(23, 59, 59, 59);
nextWeek.setDate(nextWeek.getDate() + 7);
filterParams.value = {
from: today,
to: nextWeek,
state: 'pending',
};
});
</script> </script>
<template> <template>
<VnSearchbar <VnTable
data-key="ItemRequests" ref="tableRef"
url="TicketRequests/filter" data-key="itemRequest"
:label="t('globals.search')" url="ticketRequests/filter"
:info="t('You can search by Id or alias')" order="shipped ASC, isOk ASC"
:redirect="false" :columns="columns"
/> :user-params="userParams"
<RightMenu> :is-editable="true"
<template #right-panel> auto-load
<ItemRequestFilter data-key="ItemRequests" /> :disable-option="{ card: true }"
chip-locale="item.params"
>
<template #column-ticketFk="{ row }">
<span class="link">
{{ row.ticketFk }}
<TicketDescriptorProxy :id="row.ticketFk" />
</span>
</template>
<template #column-shipped="{ row }">
<QTd>
<QBadge
:color="getBadgeColor(row.shipped)"
text-color="black"
class="q-pa-sm"
style="font-size: 14px"
>
{{ toDate(row.shipped) }}
</QBadge>
</QTd>
</template>
<template #column-attenderName="{ row }">
<span class="link" @click.stop>
{{ row.attenderName }}
<WorkerDescriptorProxy :id="row.attenderFk" />
</span>
</template> </template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<QTable
:rows="itemRequestsOptions"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:no-data-label="t('globals.noResults')"
>
<template #body-cell-id="{ row }">
<QTd>
<QBtn flat class="link"> {{ row.ticketFk }}</QBtn>
<TicketDescriptorProxy :id="row.ticketFk" />
</QTd>
</template>
<template #body-cell-shipped="{ row }"> <template #column-requesterName="{ row }">
<QTd> <span class="link" @click.stop>
<QBadge {{ row.requesterName }}
v-if="getDateQBadgeColor(row.shipped)" <WorkerDescriptorProxy :id="row.requesterFk" />
:color="getDateQBadgeColor(row.shipped)" </span>
text-color="black" </template>
class="q-ma-none"
dense <template #column-item="{ row }">
style="font-size: 14px" <span>
> <VnInput v-model.number="row.itemFk" dense />
{{ toDateFormat(row.shipped) }} </span>
</QBadge> </template>
<span v-else>{{ toDateFormat(row.shipped) }}</span> <template #column-achieved="{ row }">
</QTd> <span>
</template> <VnInput
<template #body-cell-requester="{ row }"> type="number"
<QTd> v-model.number="row.saleQuantity"
<QBtn flat dense class="link"> {{ row.requesterName }}</QBtn> :disable="!row.itemFk || row.isOk != null"
<WorkerDescriptorProxy :id="row.requesterFk" /> @blur="changeQuantity(row)"
</QTd> @keyup.enter="changeQuantity(row)"
</template> dense
<template #body-cell-attender="{ row }"> />
<QTd> </span>
<VnSelect </template>
url="Workers/search" <template #column-concept="{ row }">
v-model="row.attenderFk" <span @click.stop disabled="row.isOk != null">
:params="{ departmentCodes: ['shopping'] }" {{ row.itemDescription }}
:fields="['id', 'nickname']" </span>
sort-by="nickname ASC" </template>
hide-selected <template #moreFilterPanel="{ params }">
option-label="nickname" <VnInputNumber
option-value="id" :label="t('params.scopeDays')"
dense v-model.number="params.scopeDays"
> @keyup.enter="(evt) => handleScopeDays(evt.target.value)"
<template #option="scope"> @remove="handleScopeDays()"
<QItem v-bind="scope.itemProps"> class="q-px-xs q-pr-lg"
<QItemSection> filled
<QItemLabel>{{ scope.opt?.name }}</QItemLabel> dense
<QItemLabel caption
>{{ scope.opt?.nickname }},
{{ scope.opt?.code }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-item="{ row }">
<QTd>
<VnInput
v-model.number="row.itemFk"
type="number"
:disable="row.isOk != null"
dense
/>
</QTd>
</template>
<template #body-cell-achieved="{ row }">
<QTd>
<VnInput
v-model.number="row.saleQuantity"
@blur="changeQuantity(row)"
type="number"
:disable="!row.itemFk || row.isOk != null"
dense
/>
</QTd>
</template>
<template #body-cell-concept="{ row }">
<QTd>
<QBtn flat dense class="link"> {{ row.itemDescription }}</QBtn>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd>
</template>
<template #body-cell-state="{ row }">
<QTd>
<span>{{ getState(row.isOk) }}</span>
</QTd>
</template>
<template #body-cell-action="{ row, rowIndex }">
<QTd>
<QIcon
v-if="row.response?.length"
name="insert_drive_file"
color="primary"
size="sm"
>
<QTooltip>
{{ row.response }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.isOk == null"
name="thumb_down"
color="primary"
size="sm"
class="fill-icon"
@click="showDenyRequestForm(row.id, rowIndex)"
>
<QTooltip>
{{ t('Discard') }}
</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
<ItemRequestDenyForm
:request-id="denyRequestId"
@on-data-saved="onDenyAccept"
/> />
</QDialog> </template>
</QPage> <template #column-denyOptions="{ row, rowIndex }">
<QTd class="sticky no-padding">
<QIcon
v-if="row.response?.length"
name="insert_drive_file"
color="primary"
size="sm"
>
<QTooltip>
{{ row.response }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.isOk == null"
name="thumb_down"
color="primary"
size="sm"
class="fill-icon"
@click="showDenyRequestForm(row.id, rowIndex)"
>
<QTooltip>
{{ t('Discard') }}
</QTooltip>
</QIcon>
</QTd>
</template>
</VnTable>
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
<ItemRequestDenyForm :request-id="denyRequestId" @on-data-saved="onDenyAccept" />
</QDialog>
</template> </template>
<i18n> <i18n>

View File

@ -10,6 +10,7 @@ defineProps({
requestId: { requestId: {
type: Number, type: Number,
default: null, default: null,
required: true,
}, },
}); });
@ -43,6 +44,7 @@ onMounted(async () => {
type="textarea" type="textarea"
v-model="data.observation" v-model="data.observation"
fill-input fill-input
:required="true"
autogrow autogrow
/> />
</div> </div>

View File

@ -1,113 +1,128 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { ref, computed } from 'vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue';
import ItemTypeSummary from 'src/pages/ItemType/Card/ItemTypeSummary.vue';
import ItemTypeFilter from 'src/pages/ItemType/ItemTypeFilter.vue';
import ItemTypeSearchbar from '../ItemType/ItemTypeSearchbar.vue'; import ItemTypeSearchbar from '../ItemType/ItemTypeSearchbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnTable from 'components/VnTable/VnTable.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import FetchData from 'components/FetchData.vue';
const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const tableRef = ref();
const workerOptions = ref([]);
const ItemCategoriesOptions = ref([]);
const redirectToItemTypeSummary = (id) => { const columns = computed(() => [
router.push({ name: 'ItemTypeSummary', params: { id } }); {
}; align: 'left',
name: 'id',
const redirectToCreateView = () => { label: t('id'),
router.push({ name: 'ItemTypeCreate' }); isId: true,
}; cardVisible: true,
},
const exprBuilder = (param, value) => { {
switch (param) { align: 'left',
case 'name': name: 'code',
return { label: t('code'),
name: { like: `%${value}%` }, isTitle: true,
}; cardVisible: true,
case 'code': create: true,
return { },
code: { like: `%${value}%` }, {
}; align: 'left',
case 'search': name: 'name',
if (value) { label: t('name'),
if (!isNaN(value)) { cardVisible: true,
return { id: value }; create: true,
} else { },
return { {
or: [ align: 'left',
{ name: 'workerFk',
name: { label: t('worker'),
like: `%${value}%`, create: true,
}, component: 'select',
}, attrs: {
{ options: workerOptions.value,
code: { optionLabel: 'firstName',
like: `%${value}%`, optionValue: 'id',
}, },
}, cardVisible: false,
], visible: false,
}; },
} {
} align: 'left',
} name: 'categoryFk',
}; label: t('ItemCategory'),
create: true,
component: 'select',
attrs: {
options: ItemCategoriesOptions.value,
fields: ['id', 'name'],
order: 'name ASC',
},
cardVisible: false,
visible: false,
},
{
align: 'left',
name: 'Temperature',
label: t('Temperature'),
create: true,
component: 'select',
attrs: {
url: 'Temperatures',
fields: ['id', 'name'],
},
cardVisible: false,
visible: false,
},
]);
</script> </script>
<template> <template>
<FetchData
url="Workers"
:filter="{ fields: ['id', 'firstName'], order: ['firstName ASC'] }"
@on-fetch="(data) => (workerOptions = data)"
auto-load
/>
<FetchData
url="ItemCategories"
:filter="{ fields: ['id', 'name'], order: ['name ASC'] }"
@on-fetch="(data) => (ItemCategoriesOptions = data)"
auto-load
/>
<ItemTypeSearchbar /> <ItemTypeSearchbar />
<RightMenu> <VnTable
<template #right-panel> ref="tableRef"
<ItemTypeFilter data-key="ItemTypeList" /> data-key="ItemTypeList"
</template> :url="`ItemTypes`"
</RightMenu> :create="{
<QPage class="column items-center q-pa-md"> urlCreate: 'ItemTypes',
<div class="vn-card-list"> title: t('Create ItemTypes'),
<VnPaginate onDataSaved: () => tableRef.reload(),
data-key="ItemTypeList" formInitialData: {},
url="ItemTypes" }"
:order="['name']" order="name ASC"
auto-load :columns="columns"
:expr-builder="exprBuilder" auto-load
> :right-search="false"
<template #body="{ rows }"> :is-editable="false"
<CardList :use-model="true"
v-for="row of rows" />
:key="row.id"
:title="row.code"
@click="redirectToItemTypeSummary(row.id)"
:id="row.id"
>
<template #list-items>
<VnLv :label="t('Name')" :value="row.name" />
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, ItemTypeSummary)"
color="primary"
type="submit"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
<QPageSticky :offset="[20, 20]">
<QBtn
fab
icon="add"
color="primary"
@click="redirectToCreateView()"
shortcut="+"
/>
<QTooltip>
{{ t('New item type') }}
</QTooltip>
</QPageSticky>
</template> </template>
<i18n>
es:
id: Id
code: Código
name: Nombre
worker: Trabajador
ItemCategory: Reino
Temperature: Temperatura
Create ItemTypes: Crear familia
en:
code: Code
name: Name
worker: Worker
ItemCategory: ItemCategory
Temperature: Temperature
</i18n>

View File

@ -0,0 +1,36 @@
import axios from 'axios';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import VnConfirm from 'components/ui/VnConfirm.vue';
export function cloneItem() {
const { t } = useI18n();
const quasar = useQuasar();
const router = useRouter();
const cloneItem = async (entityId) => {
const { id } = entityId;
try {
const { data } = await axios.post(`Items/${id ?? entityId}/clone`);
router.push({ name: 'ItemTags', params: { id: data.id } });
} catch (err) {
console.error('Error cloning item');
}
};
const openCloneDialog = async (entityId) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('item.descriptor.clone.title'),
message: t('item.descriptor.clone.subTitle'),
},
})
.onOk(async () => {
await cloneItem(entityId);
});
};
return { openCloneDialog };
}

View File

@ -88,3 +88,127 @@ itemType:
worker: Worker worker: Worker
category: Category category: Category
temperature: Temperature temperature: Temperature
item:
params:
daysOnward: Days onward
search: General search
ticketFk: Ticket id
attenderFk: Atender
clientFk: Client id
warehouseFk: Warehouse
requesterFk: Salesperson
from: From
to: To
mine: For me
state: State
myTeam: My team
searchbar:
label: Search item
descriptor:
item: Item
buyer: Buyer
color: Color
category: Category
stems: Stems
visible: Visible
available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary
producer: Producer
clone:
title: All its properties will be copied
subTitle: Do you want to clone this item?
list:
id: Identifier
grouping: Grouping
packing: Packing
description: Description
stems: Stems
category: Category
typeName: Type
intrastat: Intrastat
isActive: Active
size: Size
origin: Origin
userName: Buyer
weightByPiece: Weight/Piece
stemMultiplier: Multiplier
producer: Producer
landed: Landed
basicData:
type: Type
reference: Reference
relevancy: Relevancy
stems: Stems
multiplier: Multiplier
generic: Generic
intrastat: Intrastat
expense: Expense
weightByPiece: Weight/Piece
boxUnits: Units/Box
recycledPlastic: Recycled Plastic
nonRecycledPlastic: Non recycled plastic
isActive: Active
hasKgPrice: Price in kg
isFragile: Fragile
isFragileTooltip: Is shown at website, app that this item cannot travel (wreath, palms, ...)
isPhotoRequested: Do photo
isPhotoRequestedTooltip: This item does need a photo
description: Description
fixedPrice:
itemFk: Item ID
groupingPrice: Grouping price
packingPrice: Packing price
hasMinPrice: Has min price
minPrice: Min price
started: Started
ended: Ended
warehouse: Warehouse
create:
name: Name
tag: Tag
priority: Priority
type: Type
intrastat: Intrastat
origin: Origin
buyRequest:
ticketId: 'Ticket ID'
shipped: 'Shipped'
requester: 'Requester'
requested: 'Requested'
price: 'Price'
attender: 'Attender'
item: 'Item'
achieved: 'Achieved'
concept: 'Concept'
state: 'State'
summary:
basicData: 'Basic data'
otherData: 'Other data'
description: 'Description'
tax: 'Tax'
tags: 'Tags'
botanical: 'Botanical'
barcode: 'Barcode'
name: 'Nombre'
completeName: 'Nombre completo'
family: 'Familia'
size: 'Medida'
origin: 'Origen'
stems: 'Tallos'
multiplier: 'Multiplicador'
buyer: 'Comprador'
doPhoto: 'Do photo'
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'

View File

@ -88,3 +88,129 @@ itemType:
worker: Trabajador worker: Trabajador
category: Reino category: Reino
temperature: Temperatura temperature: Temperatura
params:
state: asfsdf
item:
params:
daysOnward: Días adelante
search: Búsqueda general
ticketFk: Id ticket
attenderFk: Comprador
clientFk: Id cliente
warehouseFk: Almacén
requesterFk: Comercial
from: Desde
to: Hasta
mine: Para mi
state: Estado
myTeam: Mi equipo
searchbar:
label: Buscar artículo
descriptor:
item: Artículo
buyer: Comprador
color: Color
category: Categoría
stems: Tallos
visible: Visible
available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta
producer: Productor
clone:
title: Todas sus propiedades serán copiadas
subTitle: ¿Desea clonar este artículo?
list:
id: Identificador
grouping: Grouping
packing: Packing
description: Descripción
stems: Tallos
category: Reino
typeName: Tipo
intrastat: Intrastat
isActive: Activo
size: Medida
origin: Origen
weightByPiece: Peso (gramos)/tallo
userName: Comprador
stemMultiplier: Multiplicador
producer: Productor
landed: F. entrega
basicData:
type: Tipo
reference: Referencia
relevancy: Relevancia
stems: Tallos
multiplier: Multiplicador
generic: Genérico
intrastat: Intrastat
expense: Gasto
weightByPiece: Peso (gramos)/tallo
boxUnits: Unidades/caja
recycledPlastic: Plastico reciclado
nonRecycledPlastic: Plático no reciclado
isActive: Activo
hasKgPrice: Precio en kg
isFragile: Frágil
isFragileTooltip: Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...)
isPhotoRequested: Hacer foto
isPhotoRequestedTooltip: Este artículo necesita una foto
description: Descripción
fixedPrice:
itemFk: ID Artículo
groupingPrice: Precio grouping
packingPrice: Precio packing
hasMinPrice: Tiene precio mínimo
minPrice: Precio min
started: Inicio
ended: Fin
warehouse: Almacén
create:
name: Nombre
tag: Etiqueta
priority: Prioridad
type: Tipo
intrastat: Intrastat
origin: Origen
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'
buyRequest:
ticketId: 'ID Ticket'
shipped: 'F. envío'
requester: 'Solicitante'
requested: 'Solicitado'
price: 'Precio'
attender: 'Comprador'
item: 'Artículo'
achieved: 'Conseguido'
concept: 'Concepto'
state: 'Estado'

View File

@ -126,7 +126,7 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('Preview'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
isPrimary: true, isPrimary: true,
action: (row) => viewSummary(row?.routeFk, RouteSummary), action: (row) => viewSummary(row?.routeFk, RouteSummary),

View File

@ -204,7 +204,7 @@ const columns = computed(() => [
isPrimary: true, isPrimary: true,
}, },
{ {
title: t('route.components.smartCard.viewSummary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
action: (row) => viewSummary(row?.id, RouteSummary), action: (row) => viewSummary(row?.id, RouteSummary),
isPrimary: true, isPrimary: true,

View File

@ -204,7 +204,7 @@ const columns = computed(() => [
action: (row) => redirectToLines(row.id), action: (row) => redirectToLines(row.id),
}, },
{ {
title: t('ticketList.summary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
isPrimary: true, isPrimary: true,
action: (row) => viewSummary(row.id, TicketSummary), action: (row) => viewSummary(row.id, TicketSummary),

View File

@ -103,7 +103,7 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('list.zoneSummary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
action: (row) => viewSummary(row.id, ZoneSummary), action: (row) => viewSummary(row.id, ZoneSummary),
isPrimary: true, isPrimary: true,

View File

@ -48,6 +48,28 @@ export default {
}, },
component: () => import('src/pages/Item/ItemList.vue'), component: () => import('src/pages/Item/ItemList.vue'),
}, },
{
path: 'request',
name: 'ItemRequest',
meta: {
title: 'buyRequest',
icon: 'vn:buyrequest',
},
component: () => import('src/pages/Item/ItemRequest.vue'),
},
{
path: 'waste-breakdown',
name: 'WasteBreakdown',
meta: {
title: 'wasteBreakdown',
icon: 'vn:claims',
},
beforeEnter: (to, from, next) => {
next({ name: 'ItemList' });
window.location.href =
'https://grafana.verdnatura.es/d/TTNXQAxVk';
},
},
{ {
path: 'fixed-price', path: 'fixed-price',
name: 'ItemFixedPrice', name: 'ItemFixedPrice',
@ -65,19 +87,7 @@ export default {
}, },
component: () => import('src/pages/Item/ItemCreate.vue'), component: () => import('src/pages/Item/ItemCreate.vue'),
}, },
{
path: 'waste-breakdown',
name: 'WasteBreakdown',
meta: {
title: 'wasteBreakdown',
icon: 'vn:claims',
},
beforeEnter: (to, from, next) => {
next({ name: 'ItemList' });
window.location.href =
'https://grafana.verdnatura.es/d/TTNXQAxVk';
},
},
{ {
path: 'item-type-list', path: 'item-type-list',
name: 'ItemTypeList', name: 'ItemTypeList',
@ -95,15 +105,6 @@ export default {
}, },
component: () => import('src/pages/Item/ItemTypeCreate.vue'), component: () => import('src/pages/Item/ItemTypeCreate.vue'),
}, },
{
path: 'request',
name: 'ItemRequest',
meta: {
title: 'buyRequest',
icon: 'vn:buyrequest',
},
component: () => import('src/pages/Item/ItemRequest.vue'),
},
], ],
}, },
{ {