#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() {
if (!route) return;
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);

View File

@ -1055,92 +1055,6 @@ travel:
warehouse: Warehouse
travelFileDescription: 'Travel id { travelId }'
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:
topbar: {}
itemsFilterPanel:

View File

@ -1053,92 +1053,6 @@ travel:
warehouse: Almacén
travelFileDescription: 'Id envío { travelId }'
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:
topbar: {}
itemsFilterPanel:

View File

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

View File

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

View File

@ -63,7 +63,7 @@ const onIntrastatCreated = (response, formData) => {
<template #form="{ data }">
<VnRow>
<VnSelect
:label="t('itemBasicData.type')"
:label="t('item.basicData.type')"
v-model="data.typeFk"
:options="itemTypesOptions"
option-value="id"
@ -82,17 +82,26 @@ const onIntrastatCreated = (response, formData) => {
</QItem>
</template>
</VnSelect>
<VnInput :label="t('itemBasicData.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 :label="t('item.basicData.reference')" v-model="data.comment" />
<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"
/>
<VnSelectDialog
:label="t('itemBasicData.generic')"
:label="t('item.basicData.generic')"
v-model="data.genericFk"
url="Items/withName"
:fields="['id', 'name']"
@ -121,7 +130,7 @@ const onIntrastatCreated = (response, formData) => {
</VnRow>
<VnRow>
<VnSelectDialog
:label="t('itemBasicData.intrastat')"
:label="t('item.basicData.intrastat')"
v-model="data.intrastatFk"
:options="intrastatsOptions"
option-value="id"
@ -148,7 +157,7 @@ const onIntrastatCreated = (response, formData) => {
</VnSelectDialog>
<div class="col">
<VnSelect
:label="t('itemBasicData.expense')"
:label="t('item.basicData.expense')"
v-model="data.expenseFk"
:options="expensesOptions"
option-value="id"
@ -160,64 +169,67 @@ const onIntrastatCreated = (response, formData) => {
</VnRow>
<VnRow>
<VnInput
:label="t('itemBasicData.weightByPiece')"
:label="t('item.basicData.weightByPiece')"
v-model.number="data.weightByPiece"
:min="0"
type="number"
/>
<VnInput
:label="t('itemBasicData.boxUnits')"
:label="t('item.basicData.boxUnits')"
v-model.number="data.packingOut"
:min="0"
type="number"
/>
<VnInput
:label="t('itemBasicData.recycledPlastic')"
:label="t('item.basicData.recycledPlastic')"
v-model.number="data.recycledPlastic"
:min="0"
type="number"
/>
<VnInput
:label="t('itemBasicData.nonRecycledPlastic')"
:label="t('item.basicData.nonRecycledPlastic')"
v-model.number="data.nonRecycledPlastic"
:min="0"
type="number"
/>
</VnRow>
<VnRow>
<QCheckbox v-model="data.isActive" :label="t('itemBasicData.isActive')" />
<VnRow class="row q-gutter-md q-mb-md">
<QCheckbox
v-model="data.isActive"
:label="t('item.basicData.isActive')"
/>
<QCheckbox
v-model="data.hasKgPrice"
:label="t('itemBasicData.hasKgPrice')"
:label="t('item.basicData.hasKgPrice')"
/>
<div>
<QCheckbox
v-model="data.isFragile"
:label="t('itemBasicData.isFragile')"
:label="t('item.basicData.isFragile')"
class="q-mr-sm"
/>
<QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip max-width="300px">
{{ t('itemBasicData.isFragileTooltip') }}
{{ t('item.basicData.isFragileTooltip') }}
</QTooltip>
</QIcon>
</div>
<div>
<QCheckbox
v-model="data.isPhotoRequested"
:label="t('itemBasicData.isPhotoRequested')"
:label="t('item.basicData.isPhotoRequested')"
class="q-mr-sm"
/>
<QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip>
{{ t('itemBasicData.isPhotoRequestedTooltip') }}
{{ t('item.basicData.isPhotoRequestedTooltip') }}
</QTooltip>
</QIcon>
</div>
</VnRow>
<VnRow>
<VnInput
:label="t('itemBasicData.description')"
:label="t('item.basicData.description')"
type="textarea"
v-model="data.description"
fill-input

View File

@ -20,12 +20,6 @@ let itemBotanicalsForm = reactive({ itemFk: null });
const entityId = computed(() => {
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>
<template>
<FetchData
@ -37,11 +31,14 @@ onMounted(async () => {
@on-fetch="(data) => (itemBotanicals = data)"
/>
<FormModel
url="ItemBotanicals"
url-update="ItemBotanicals"
model="entry"
model="item"
auto-load
:form-initial-data="itemBotanicalsForm"
:clear-store-on-unmount="false"
:filter="{
where: { itemFk: entityId },
}"
@on-fetch="(data) => (itemBotanicalsForm = data)"
>
<template #form="{ data }">
<VnRow>

View File

@ -1,18 +1,18 @@
<script setup>
import { computed, ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import RegularizeStockForm from 'components/RegularizeStockForm.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import useCardDescription from 'src/composables/useCardDescription';
import axios from 'axios';
import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData';
import { cloneItem } from 'src/pages/Item/composables/cloneItem';
const $props = defineProps({
id: {
@ -38,9 +38,8 @@ const $props = defineProps({
},
});
const quasar = useQuasar();
const { openCloneDialog } = cloneItem();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const warehouseConfig = ref(null);
const entityId = computed(() => {
@ -48,84 +47,52 @@ const entityId = computed(() => {
});
const regularizeStockFormDialog = ref(null);
const available = ref(null);
const visible = ref(null);
const mounted = ref();
const arrayDataStock = useArrayData('descriptorStock', {
url: `Items/${entityId.value}/getVisibleAvailable`,
});
onMounted(async () => {
await getItemConfigs();
await updateStock();
mounted.value = true;
});
const data = ref(useCardDescription());
const setData = async (entity) => {
try {
if (!entity) return;
data.value = useCardDescription(entity.name, entity.id);
await updateStock();
} catch (err) {
console.error('Error item');
}
if (!entity) return;
data.value = useCardDescription(entity.name, entity.id);
await updateStock();
};
const getItemConfigs = async () => {
try {
const { data } = await axios.get('ItemConfigs/findOne');
if (!data) return;
return (warehouseConfig.value = data.warehouseFk);
} catch (err) {
console.error('Error item');
}
const { data } = await axios.get('ItemConfigs/findOne');
if (!data) return;
return (warehouseConfig.value = data.warehouseFk);
};
const updateStock = async () => {
try {
available.value = null;
visible.value = null;
if (!mounted.value) return;
await getItemConfigs();
const params = {
warehouseFk: $props.warehouseFk,
dated: $props.dated,
};
const params = {
warehouseFk: $props.warehouseFk ?? warehouseConfig.value,
dated: $props.dated,
};
await getItemConfigs();
if (!params.warehouseFk) {
params.warehouseFk = warehouseConfig.value;
}
const { data } = await axios.get(`Items/${entityId.value}/getVisibleAvailable`, {
params,
});
available.value = data.available;
visible.value = data.visible;
} catch (err) {
console.error('Error updating stock');
}
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
const stock = useArrayData('descriptorStock', {
url: `Items/${entityId.value}/getVisibleAvailable`,
userParams: params,
});
const storeData = stock.store.data;
if (storeData?.itemFk == entityId.value) return;
await stock.fetch({});
};
const openRegularizeStockForm = () => {
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>
<template>
@ -151,7 +118,7 @@ const openCloneDialog = async () => {
</QDialog>
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="openCloneDialog()">
<QItem v-ripple clickable @click="openCloneDialog(entityId)">
<QItemSection>
{{ t('globals.clone') }}
</QItemSection>
@ -160,8 +127,8 @@ const openCloneDialog = async () => {
<template #before>
<ItemDescriptorImage
:entity-id="entityId"
:visible="visible"
:available="available"
:visible="arrayDataStock.store.data?.visible"
:available="arrayDataStock.store.data?.available"
/>
</template>
<template #body="{ entity }">
@ -194,6 +161,18 @@ const openCloneDialog = async () => {
:value="entity.value7"
/>
</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="{}">
<QCardActions class="row justify-center">
<QBtn
@ -216,8 +195,7 @@ const openCloneDialog = async () => {
<i18n>
es:
Regularize stock: Regularizar stock
All its properties will be copied: Todas sus propiedades serán copiadas
Do you want to clone this item?: ¿Desea clonar este artículo?
Inactive article: Artículo inactivo
</i18n>
<style lang="scss" scoped>

View File

@ -32,6 +32,10 @@ const editPhotoFormDialog = ref(null);
const showEditPhotoForm = ref(false);
const warehouseName = ref(null);
onMounted(async () => {
getItemConfigs();
});
const toggleEditPictureForm = () => {
showEditPhotoForm.value = !showEditPhotoForm.value;
};
@ -56,10 +60,6 @@ const getWarehouseName = async (warehouseFk) => {
warehouseName.value = data.name;
};
onMounted(async () => {
getItemConfigs();
});
const handlePhotoUpdated = (evt = false) => {
image.value.reload(evt);
};
@ -134,10 +134,10 @@ es:
Regularize stock: Regularizar stock
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?
warehouseText: Calculated on the warehouse of { warehouseName }
warehouseText: Calculado sobre el almacén de { warehouseName }
en:
warehouseText: Calculado sobre el almacén de { warehouseName }
warehouseText: Calculated on the warehouse of { warehouseName }
</i18n>
<style lang="scss" scoped>

View File

@ -17,6 +17,7 @@ import { toDateFormat } from 'src/filters/date.js';
import { dashIfEmpty } from 'src/filters';
import { date } from 'quasar';
import { useState } from 'src/composables/useState';
import { useArrayData } from 'src/composables/useArrayData';
import axios from 'axios';
const { t } = useI18n();
@ -37,6 +38,33 @@ const warehouseFk = ref(null);
const _showWhatsBeforeInventory = ref(false);
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(() => [
{
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 getBadgeAttrs = (_date) => {
@ -131,53 +181,15 @@ const formatDateForAttribute = (dateValue) => {
return dateValue;
};
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,
},
};
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();
}
);
async function updateWarehouse(warehouseFk) {
const stock = useArrayData('descriptorStock', {
userParams: {
warehouseFk,
},
});
await stock.fetch({});
stock.store.data.itemFk = route.params.id;
}
</script>
<template>
@ -193,36 +205,39 @@ watch(
auto-load
@on-fetch="(data) => (warehousesOptions = data)"
/>
<QToolbar class="justify-end">
<div id="st-data" class="row">
<VnSelect
:label="t('itemDiary.warehouse')"
:options="warehousesOptions"
hide-selected
option-label="name"
option-value="id"
dense
v-model="itemsBalanceFilter.where.warehouseFk"
@update:model-value="fetchItemBalances"
class="q-mr-lg"
/>
<QCheckbox
:label="t('itemDiary.showBefore')"
v-model="showWhatsBeforeInventory"
@update:model-value="fetchItemBalances"
class="q-mr-lg"
/>
<VnInputDate
v-if="showWhatsBeforeInventory"
:label="t('itemDiary.since')"
dense
v-model="itemsBalanceFilter.where.date"
@update:model-value="fetchItemBalances"
/>
</div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#st-data">
<div class="row">
<VnSelect
:label="t('itemDiary.warehouse')"
:options="warehousesOptions"
hide-selected
option-label="name"
option-value="id"
dense
v-model="itemsBalanceFilter.where.warehouseFk"
@update:model-value="
(value) => fetchItemBalances() && updateWarehouse(value)
"
class="q-mr-lg"
/>
<QCheckbox
:label="t('itemDiary.showBefore')"
v-model="showWhatsBeforeInventory"
@update:model-value="fetchItemBalances"
class="q-mr-lg"
/>
<VnInputDate
v-if="showWhatsBeforeInventory"
:label="t('itemDiary.since')"
dense
v-model="itemsBalanceFilter.where.date"
@update:model-value="fetchItemBalances"
/>
</div>
</Teleport>
<Teleport to="#st-actions"> </Teleport>
</template>
<QPage class="column items-center q-pa-md">
<QTable
:rows="itemBalances"

View File

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

View File

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

View File

@ -1,584 +1,360 @@
<script setup>
import { onMounted, ref, computed, reactive, onUnmounted } from 'vue';
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } 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 { useRoute } from 'vue-router';
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 stateStore = useStateStore();
const { t } = useI18n();
const entityId = computed(() => route.params.id);
const { openCloneDialog } = cloneItem();
const { viewSummary } = useSummaryDialog();
const { openConfirmationModal } = useVnConfirm();
const { t } = useI18n();
const tableRef = ref();
const route = useRoute();
const paginateRef = ref(null);
const itemTypesOptions = ref([]);
const originsOptions = ref([]);
const buyersOptions = ref([]);
const intrastatOptions = ref([]);
const itemCategoriesOptions = ref([]);
const visibleColumns = ref([]);
const allColumnNames = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'category':
return { 'ic.name': value };
case 'buyerFk':
return { 'it.workerFk': value };
case 'grouping':
return { 'b.grouping': value };
case 'packing':
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 itemFilter = {
include: [
{
relation: 'itemType',
scope: {
fields: ['id', 'name'],
},
},
{
relation: 'intrastat',
scope: {
fields: ['id', 'description'],
},
},
{
relation: 'origin',
scope: {
fields: ['id', 'name'],
},
},
],
};
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(() => [
{
label: '',
name: 'picture',
name: 'image',
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'),
name: 'id',
field: 'id',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
isId: true,
chip: {
condition: () => true,
},
cardVisible: true,
},
{
label: t('item.list.grouping'),
field: 'grouping',
name: 'grouping',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
component: 'number',
inWhere: true,
},
format: (val) => dashIfEmpty(val),
},
{
label: t('item.list.packing'),
field: 'packing',
name: 'packing',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
component: 'number',
inWhere: true,
},
format: (val) => dashIfEmpty(val),
},
{
label: t('globals.description'),
field: 'name',
name: 'description',
align: 'left',
sortable: true,
create: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
name: 'search',
},
cardVisible: true,
},
{
label: t('item.list.stems'),
field: 'stems',
name: 'stems',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
component: 'number',
inWhere: true,
},
cardVisible: true,
},
{
label: t('item.list.size'),
field: 'size',
name: 'size',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
component: 'number',
inWhere: true,
},
cardVisible: true,
},
{
label: t('item.list.typeName'),
field: 'typeName',
name: 'typeName',
name: 'typeFk',
align: 'left',
sortable: true,
component: 'select',
attrs: {
url: 'ItemTypes',
fields: ['id', 'name'],
},
columnFilter: {
component: VnSelect,
filterParamKey: 'typeFk',
type: 'select',
filterValue: null,
event: getInputEvents,
name: 'typeFk',
attrs: {
options: itemTypesOptions.value,
'option-value': 'id',
'option-label': 'name',
dense: true,
url: 'ItemTypes',
fields: ['id', 'name'],
},
},
columnField: {
component: null,
},
create: true,
},
{
label: t('item.list.category'),
field: 'category',
name: 'category',
align: 'left',
sortable: true,
component: 'select',
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
name: 'categoryFk',
attrs: {
options: itemCategoriesOptions.value,
'option-value': 'name',
'option-label': 'name',
dense: true,
url: 'ItemCategories',
fields: ['id', 'name'],
},
},
columnField: {
component: null,
},
},
{
label: t('item.list.intrastat'),
field: 'intrastat',
name: 'intrastat',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: intrastatOptions.value,
'option-value': 'description',
'option-label': 'description',
dense: true,
},
component: 'select',
attrs: {
url: 'Intrastats',
optionValue: 'description',
optionLabel: 'description',
},
columnFilter: {
name: 'description',
attrs: {
url: 'Intrastats',
optionValue: 'description',
optionLabel: 'description',
},
alias: 'intr',
},
columnField: {
component: null,
},
create: true,
cardVisible: true,
},
{
label: t('item.list.origin'),
field: 'origin',
name: 'origin',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: originsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
component: 'select',
attrs: {
url: 'Origins',
optionValue: 'id',
optionLabel: 'code',
},
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'),
field: 'userName',
name: 'userName',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
filterParamKey: 'buyerFk',
type: 'select',
filterValue: null,
event: getInputEvents,
name: 'workerFk',
attrs: {
options: buyersOptions.value,
'option-value': 'id',
'option-label': 'nickname',
dense: true,
url: 'Users',
optionValue: 'id',
optionLabel: 'userName',
},
},
},
{
label: t('item.list.weightByPiece'),
field: 'weightByPiece',
name: 'weightByPiece',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
component: 'input',
columnField: {
component: null,
},
columnFilter: {
inWhere: true,
},
format: (val) => dashIfEmpty(val),
},
{
label: t('item.list.stemMultiplier'),
field: 'stemMultiplier',
name: 'stemMultiplier',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
component: 'input',
columnField: {
component: null,
},
columnFilter: {
inWhere: true,
},
format: (val) => dashIfEmpty(val),
},
{
label: t('item.list.isActive'),
field: 'isActive',
name: 'isActive',
align: 'left',
sortable: true,
columnFilter: null,
align: 'center',
component: 'checkbox',
},
{
label: t('item.list.producer'),
field: 'producer',
name: 'producer',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
component: 'select',
attrs: {
url: 'Producers',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (val) => dashIfEmpty(val),
},
{
label: t('item.list.landed'),
field: 'landed',
name: 'landed',
align: 'left',
sortable: true,
format: (val) => dashIfEmpty(toDateFormat(val)),
columnFilter: null,
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)),
},
{
align: 'right',
label: '',
name: 'actions',
align: 'left',
columnFilter: null,
name: 'tableActions',
actions: [
{
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>
<template>
<FetchData
url="ItemTypes"
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
auto-load
@on-fetch="(data) => (itemTypesOptions = data)"
<VnSearchbar
data-key="ItemList"
:label="t('item.searchbar.label')"
:info="t('You can search by id')"
/>
<FetchData
url="ItemCategories"
:filter="{ fields: ['name'], order: 'name ASC' }"
<VnTable
ref="tableRef"
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
@on-fetch="(data) => (itemCategoriesOptions = data)"
/>
<FetchData
url="Intrastats"
:filter="{ fields: ['description'], order: 'description ASC' }"
auto-load
@on-fetch="(data) => (intrastatOptions = data)"
/>
<FetchData
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']"
/>
redirect="Item"
:is-editable="false"
:filer="itemFilter"
>
<template #column-id="{ row }">
<span class="link" @click.stop>
{{ row.id }}
<ItemDescriptorProxy :id="row.id" />
</span>
</template>
</VnSubToolbar>
<RightMenu>
<template #right-panel>
<ItemListFilter data-key="ItemList" />
<template #column-userName="{ row }">
<span class="link" @click.stop>
{{ row.userName }}
<WorkerDescriptorProxy :id="row.buyerFk" />
</span>
</template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<VnPaginate
ref="paginateRef"
data-key="ItemList"
url="Items/filter"
:order="['isActive DESC', 'name', 'id']"
:limit="12"
:expr-builder="exprBuilder"
:user-params="params"
: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 #column-description="{ row }">
<div class="row column full-width justify-between items-start">
{{ row?.name }}
<div v-if="row?.subName" class="subName">
{{ row?.subName.toUpperCase() }}
</div>
</div>
<FetchedTags :item="row" :max-length="6" />
</template>
</VnTable>
</template>
<style lang="scss" scoped>
.subName {
text-transform: uppercase;
color: var(--vn-label-color);
}
</style>
<i18n>
es:
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
Regularize stock: Regularizar stock
</i18n>

View File

@ -1,23 +1,17 @@
<script setup>
import { ref, computed, onMounted, onBeforeMount, watch } from 'vue';
import { ref, computed, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.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 { useArrayData } from 'composables/useArrayData';
import { toDateFormat } from 'src/filters/date';
import { toCurrency } from 'filters/index';
import { dashIfEmpty, toCurrency } from 'filters/index';
import useNotify from 'src/composables/useNotify.js';
import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js';
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 { notify } = useNotify();
const stateStore = useStateStore();
@ -33,6 +27,12 @@ const arrayData = useArrayData('ItemRequests', {
});
const store = arrayData.store;
const userParams = {
state: 'pending',
daysOnward: 7,
};
const tableRef = ref();
watch(
() => store.data,
(value) => (itemRequestsOptions.value = value)
@ -41,89 +41,113 @@ watch(
const columns = computed(() => [
{
label: t('item.buyRequest.ticketId'),
name: 'id',
field: 'id',
name: 'ticketFk',
align: 'left',
sortable: true,
isId: true,
chip: {
condition: () => true,
},
cardVisible: true,
},
{
label: t('item.buyRequest.shipped'),
field: 'shipped',
name: 'shipped',
align: 'left',
sortable: true,
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)),
columnClass: 'shrink',
},
{
label: t('globals.description'),
field: 'description',
name: 'description',
align: 'left',
sortable: true,
columnClass: 'expand',
},
{
label: t('item.buyRequest.requester'),
name: 'requester',
align: 'left',
sortable: true,
name: 'requesterName',
columnClass: 'shrink',
},
{
label: t('item.buyRequest.requested'),
field: 'quantity',
name: 'requested',
align: 'left',
sortable: true,
name: 'quantity',
columnClass: 'shrink',
},
{
label: t('item.buyRequest.price'),
field: 'price',
name: 'price',
align: 'left',
sortable: true,
format: (val) => toCurrency(val),
format: (row) => toCurrency(row.price),
columnClass: 'shrink',
},
{
label: t('item.buyRequest.attender'),
field: 'attender',
name: 'attender',
name: 'attenderName',
align: 'left',
sortable: true,
columnClass: 'shrink',
},
{
label: t('item.buyRequest.item'),
field: 'item',
name: 'item',
align: 'left',
sortable: true,
component: 'input',
columnClass: 'expand',
},
{
label: t('item.buyRequest.achieved'),
field: 'achieved',
name: 'achieved',
align: 'left',
sortable: true,
component: 'input',
columnClass: 'shrink',
},
{
label: t('item.buyRequest.concept'),
field: 'concept',
name: 'concept',
align: 'left',
sortable: true,
component: 'input',
columnClass: 'expand',
},
{
label: t('item.buyRequest.state'),
field: 'state',
name: 'state',
format: (row) => getState(row.isOk),
align: 'left',
sortable: true,
},
{
label: '',
name: 'action',
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) => {
try {
if (request.saleFk) {
@ -153,7 +177,6 @@ const confirmRequest = async (request) => {
`TicketRequests/${request.id}/confirm`,
params
);
request.itemDescription = data.concept;
request.isOk = true;
notify(t('globals.dataSaved'), 'positive');
@ -182,172 +205,123 @@ const onDenyAccept = (_, responseData) => {
itemRequestsOptions.value[denyRequestIndex.value].response = responseData.response;
denyRequestId.value = null;
denyRequestIndex.value = null;
tableRef.value.reload();
};
onMounted(async () => {
await arrayData.fetch({ append: false });
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>
<template>
<VnSearchbar
data-key="ItemRequests"
url="TicketRequests/filter"
:label="t('globals.search')"
:info="t('You can search by Id or alias')"
:redirect="false"
/>
<RightMenu>
<template #right-panel>
<ItemRequestFilter data-key="ItemRequests" />
<VnTable
ref="tableRef"
data-key="itemRequest"
url="ticketRequests/filter"
order="shipped ASC, isOk ASC"
:columns="columns"
:user-params="userParams"
:is-editable="true"
auto-load
: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>
</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 }">
<QTd>
<QBadge
v-if="getDateQBadgeColor(row.shipped)"
:color="getDateQBadgeColor(row.shipped)"
text-color="black"
class="q-ma-none"
dense
style="font-size: 14px"
>
{{ toDateFormat(row.shipped) }}
</QBadge>
<span v-else>{{ toDateFormat(row.shipped) }}</span>
</QTd>
</template>
<template #body-cell-requester="{ row }">
<QTd>
<QBtn flat dense class="link"> {{ row.requesterName }}</QBtn>
<WorkerDescriptorProxy :id="row.requesterFk" />
</QTd>
</template>
<template #body-cell-attender="{ row }">
<QTd>
<VnSelect
url="Workers/search"
v-model="row.attenderFk"
:params="{ departmentCodes: ['shopping'] }"
:fields="['id', 'nickname']"
sort-by="nickname ASC"
hide-selected
option-label="nickname"
option-value="id"
dense
>
<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>
</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"
<template #column-requesterName="{ row }">
<span class="link" @click.stop>
{{ row.requesterName }}
<WorkerDescriptorProxy :id="row.requesterFk" />
</span>
</template>
<template #column-item="{ row }">
<span>
<VnInput v-model.number="row.itemFk" dense />
</span>
</template>
<template #column-achieved="{ row }">
<span>
<VnInput
type="number"
v-model.number="row.saleQuantity"
:disable="!row.itemFk || row.isOk != null"
@blur="changeQuantity(row)"
@keyup.enter="changeQuantity(row)"
dense
/>
</span>
</template>
<template #column-concept="{ row }">
<span @click.stop disabled="row.isOk != null">
{{ row.itemDescription }}
</span>
</template>
<template #moreFilterPanel="{ params }">
<VnInputNumber
:label="t('params.scopeDays')"
v-model.number="params.scopeDays"
@keyup.enter="(evt) => handleScopeDays(evt.target.value)"
@remove="handleScopeDays()"
class="q-px-xs q-pr-lg"
filled
dense
/>
</QDialog>
</QPage>
</template>
<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>
<i18n>

View File

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

View File

@ -1,113 +1,128 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
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 { ref, computed } from 'vue';
import ItemTypeSearchbar from '../ItemType/ItemTypeSearchbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'components/FetchData.vue';
const router = useRouter();
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const tableRef = ref();
const workerOptions = ref([]);
const ItemCategoriesOptions = ref([]);
const redirectToItemTypeSummary = (id) => {
router.push({ name: 'ItemTypeSummary', params: { id } });
};
const redirectToCreateView = () => {
router.push({ name: 'ItemTypeCreate' });
};
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}%`,
},
},
],
};
}
}
}
};
const columns = computed(() => [
{
align: 'left',
name: 'id',
label: t('id'),
isId: true,
cardVisible: true,
},
{
align: 'left',
name: 'code',
label: t('code'),
isTitle: true,
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'name',
label: t('name'),
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'workerFk',
label: t('worker'),
create: true,
component: 'select',
attrs: {
options: workerOptions.value,
optionLabel: 'firstName',
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>
<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 />
<RightMenu>
<template #right-panel>
<ItemTypeFilter data-key="ItemTypeList" />
</template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="ItemTypeList"
url="ItemTypes"
:order="['name']"
auto-load
:expr-builder="exprBuilder"
>
<template #body="{ rows }">
<CardList
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>
<VnTable
ref="tableRef"
data-key="ItemTypeList"
:url="`ItemTypes`"
:create="{
urlCreate: 'ItemTypes',
title: t('Create ItemTypes'),
onDataSaved: () => tableRef.reload(),
formInitialData: {},
}"
order="name ASC"
:columns="columns"
auto-load
:right-search="false"
:is-editable="false"
:use-model="true"
/>
</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
category: Category
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
category: Reino
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',
actions: [
{
title: t('Preview'),
title: t('components.smartCard.viewSummary'),
icon: 'preview',
isPrimary: true,
action: (row) => viewSummary(row?.routeFk, RouteSummary),

View File

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

View File

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

View File

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

View File

@ -48,6 +48,28 @@ export default {
},
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',
name: 'ItemFixedPrice',
@ -65,19 +87,7 @@ export default {
},
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',
name: 'ItemTypeList',
@ -95,15 +105,6 @@ export default {
},
component: () => import('src/pages/Item/ItemTypeCreate.vue'),
},
{
path: 'request',
name: 'ItemRequest',
meta: {
title: 'buyRequest',
icon: 'vn:buyrequest',
},
component: () => import('src/pages/Item/ItemRequest.vue'),
},
],
},
{