#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">
carlossa marked this conversation as resolved Outdated

Debería aceptar solo números

Debería aceptar solo números
<VnInput
:label="t('item.basicData.stems')"
carlossa marked this conversation as resolved Outdated

Debería aceptar solo números

Debería aceptar solo números
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"
carlossa marked this conversation as resolved Outdated

Debería mostrar el icono: icon-unavailable cuando el checkbox esté desmarcado y esconderlo cuando esté marcado

Debería mostrar el icono: icon-unavailable cuando el checkbox esté desmarcado y esconderlo cuando esté marcado
: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>
jsegarra marked this conversation as resolved Outdated
Outdated
Review

Quitar?

Quitar?
<template>
<FetchData
@ -37,11 +31,14 @@ onMounted(async () => {
@on-fetch="(data) => (itemBotanicals = data)"
/>
<FormModel
jsegarra marked this conversation as resolved Outdated

Comentarios?

Comentarios?
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>
carlossa marked this conversation as resolved Outdated
Outdated
Review

(data) => itemBotanicalsForm = data

(data) => itemBotanicalsForm = data

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());
carlossa marked this conversation as resolved Outdated
Outdated
Review

?

?
const setData = async (entity) => {
try {
if (!entity) return;
data.value = useCardDescription(entity.name, entity.id);
await updateStock();
} catch (err) {
console.error('Error item');
}
};
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 updateStock = async () => {
try {
available.value = null;
visible.value = null;
if (!mounted.value) return;
await getItemConfigs();
const params = {
warehouseFk: $props.warehouseFk,
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,
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,
});
available.value = data.available;
visible.value = data.visible;
} catch (err) {
console.error('Error updating stock');
}
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"
/>
jsegarra marked this conversation as resolved Outdated

Traducir el icono de información

Traducir el icono de información
</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',
async function updateWarehouse(warehouseFk) {
const stock = useArrayData('descriptorStock', {
userParams: {
warehouseFk,
},
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();
}
);
});
await stock.fetch({});
stock.store.data.itemFk = route.params.id;
}
</script>
<template>
@ -193,8 +205,9 @@ watch(
auto-load
@on-fetch="(data) => (warehousesOptions = data)"
/>
<QToolbar class="justify-end">
<div id="st-data" class="row">
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#st-data">
<div class="row">
<VnSelect
:label="t('itemDiary.warehouse')"
:options="warehousesOptions"
@ -203,7 +216,9 @@ watch(
option-value="id"
dense
v-model="itemsBalanceFilter.where.warehouseFk"
@update:model-value="fetchItemBalances"
@update:model-value="
(value) => fetchItemBalances() && updateWarehouse(value)
"
class="q-mr-lg"
/>
<QCheckbox
@ -220,9 +235,9 @@ watch(
@update:model-value="fetchItemBalances"
/>
</div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
</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: '',
jsegarra marked this conversation as resolved Outdated

comentarios

comentarios
name: 'picture',
name: 'image',
align: 'left',
columnFilter: null,
columnField: {
component: VnImg,
attrs: ({ row }) => {
return {
jsegarra marked this conversation as resolved Outdated

rounded? O se quiere cuadrada?

rounded? O se quiere cuadrada?
id: row?.id,
zoomResolution: '1600x900',
zoom: true,
class: 'rounded',
};
},
},
columnFilter: false,
cardVisible: true,
},
{
label: t('item.list.id'),
jsegarra marked this conversation as resolved Outdated

En esta columna hecho en falta el descriptor

En esta columna hecho en falta el descriptor
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,
columnFilter: {
component: VnSelect,
filterParamKey: 'typeFk',
type: 'select',
filterValue: null,
event: getInputEvents,
component: 'select',
attrs: {
options: itemTypesOptions.value,
'option-value': 'id',
'option-label': 'name',
dense: true,
url: 'ItemTypes',
fields: ['id', 'name'],
},
columnFilter: {
name: 'typeFk',
attrs: {
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,
component: 'select',
attrs: {
options: intrastatOptions.value,
'option-value': 'description',
'option-label': 'description',
dense: true,
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,
component: 'select',
attrs: {
options: originsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
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,
component: 'input',
columnField: {
component: null,
},
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
inWhere: true,
},
},
format: (val) => dashIfEmpty(val),
},
{
label: t('item.list.stemMultiplier'),
field: 'stemMultiplier',
name: 'stemMultiplier',
align: 'left',
sortable: true,
component: 'input',
columnField: {
component: null,
},
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
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,
component: 'select',
attrs: {
dense: true,
url: 'Producers',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (val) => dashIfEmpty(val),
},
{
label: t('item.list.landed'),
jsegarra marked this conversation as resolved Outdated

traducir

traducir
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',
jsegarra marked this conversation as resolved Outdated

No tenemos VnConfirm? En Salix si que habia

No tenemos VnConfirm? En Salix si que habia

Se ha copiado y pegado la misma funcionalidad que hay en ItemDescriptor. Para que esté del todo fino hay que hacer un composable para unificar la lógica

Se ha copiado y pegado la misma funcionalidad que hay en ItemDescriptor. Para que esté del todo fino hay que hacer un composable para unificar la lógica
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>
jsegarra marked this conversation as resolved Outdated

Uff...me lo anoto porque esta clase está declarada también en una PR de Jon

Uff...me lo anoto porque esta clase está declarada también en una PR de Jon
<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' }"
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']"
/>
</template>
</VnSubToolbar>
<RightMenu>
<template #right-panel>
<ItemListFilter data-key="ItemList" />
</template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<VnPaginate
ref="paginateRef"
<VnTable
jsegarra marked this conversation as resolved Outdated

Revisar como se muestra cuando es card

Revisar como se muestra cuando es card
ref="tableRef"
data-key="ItemList"
url="Items/filter"
url-create="Items"
:create="{
urlCreate: 'Items',
title: t('Create Item'),
jsegarra marked this conversation as resolved Outdated

Añadir traducción al title

Añadir traducción al title
onDataSaved: () => tableRef.redirect(),
formInitialData: {
editorFk: entityId,
},
}"
:order="['isActive DESC', 'name', 'id']"
jsegarra marked this conversation as resolved Outdated

Revisar order-by

Revisar order-by
: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)"
auto-load
redirect="Item"
:is-editable="false"
:filer="itemFilter"
>
<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">
<template #column-id="{ row }">
<span class="link" @click.stop>
{{ row.id }}
</QBtn>
<ItemDescriptorProxy :id="row.id" />
</QTd>
</span>
</template>
<template #body-cell-userName="{ row }">
<QTd @click.stop>
<QBtn flat color="primary" dense>
<template #column-userName="{ row }">
<span class="link" @click.stop>
{{ row.userName }}
</QBtn>
<WorkerDescriptorProxy :id="row.buyerFk" />
</QTd>
</span>
</template>
<template #body-cell-description="{ row }">
<QTd class="col">
<span>{{ row.name }} {{ row.subName }}</span>
<FetchedTags :item="row" />
</QTd>
<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>
<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>
</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,140 +205,95 @@ 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" />
</template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<QTable
:rows="itemRequestsOptions"
<VnTable
ref="tableRef"
data-key="itemRequest"
url="ticketRequests/filter"
order="shipped ASC, isOk ASC"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:no-data-label="t('globals.noResults')"
:user-params="userParams"
:is-editable="true"
auto-load
:disable-option="{ card: true }"
chip-locale="item.params"
>
<template #body-cell-id="{ row }">
<QTd>
<QBtn flat class="link"> {{ row.ticketFk }}</QBtn>
<template #column-ticketFk="{ row }">
<span class="link">
{{ row.ticketFk }}
<TicketDescriptorProxy :id="row.ticketFk" />
</QTd>
</span>
</template>
<template #body-cell-shipped="{ row }">
<template #column-shipped="{ row }">
<QTd>
<QBadge
v-if="getDateQBadgeColor(row.shipped)"
:color="getDateQBadgeColor(row.shipped)"
:color="getBadgeColor(row.shipped)"
text-color="black"
class="q-ma-none"
dense
class="q-pa-sm"
style="font-size: 14px"
>
{{ toDateFormat(row.shipped) }}
{{ toDate(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>
<template #column-attenderName="{ row }">
<span class="link" @click.stop>
{{ row.attenderName }}
<WorkerDescriptorProxy :id="row.attenderFk" />
</span>
</template>
<template #column-requesterName="{ row }">
<span class="link" @click.stop>
{{ row.requesterName }}
<WorkerDescriptorProxy :id="row.requesterFk" />
</QTd>
</span>
</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 #column-item="{ row }">
<span>
<VnInput v-model.number="row.itemFk" dense />
</span>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-item="{ row }">
<QTd>
<template #column-achieved="{ row }">
<span>
<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"
@blur="changeQuantity(row)"
@keyup.enter="changeQuantity(row)"
dense
/>
</QTd>
</span>
</template>
<template #body-cell-concept="{ row }">
<QTd>
<QBtn flat dense class="link"> {{ row.itemDescription }}</QBtn>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd>
<template #column-concept="{ row }">
<span @click.stop disabled="row.isOk != null">
{{ row.itemDescription }}
</span>
</template>
<template #body-cell-state="{ row }">
<QTd>
<span>{{ getState(row.isOk) }}</span>
</QTd>
<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
/>
</template>
<template #body-cell-action="{ row, rowIndex }">
<QTd>
<template #column-denyOptions="{ row, rowIndex }">
<QTd class="sticky no-padding">
<QIcon
v-if="row.response?.length"
name="insert_drive_file"
@ -340,14 +318,10 @@ onBeforeMount(() => {
</QIcon>
</QTd>
</template>
</QTable>
</VnTable>
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
<ItemRequestDenyForm
:request-id="denyRequestId"
@on-data-saved="onDenyAccept"
/>
<ItemRequestDenyForm :request-id="denyRequestId" @on-data-saved="onDenyAccept" />
</QDialog>
</QPage>
</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: [
const columns = computed(() => [
{
name: {
like: `%${value}%`,
},
align: 'left',
name: 'id',
label: t('id'),
isId: true,
cardVisible: true,
},
{
code: {
like: `%${value}%`,
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>
<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']"
<FetchData
url="Workers"
:filter="{ fields: ['id', 'firstName'], order: ['firstName ASC'] }"
@on-fetch="(data) => (workerOptions = data)"
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="+"
<FetchData
url="ItemCategories"
:filter="{ fields: ['id', 'name'], order: ['name ASC'] }"
@on-fetch="(data) => (ItemCategoriesOptions = data)"
auto-load
/>
<ItemTypeSearchbar />
<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"
/>
<QTooltip>
{{ t('New item type') }}
</QTooltip>
</QPageSticky>
</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'),
},
],
},
{