488 lines
16 KiB
Vue
488 lines
16 KiB
Vue
<script setup>
|
|
import { ref, computed } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { QBtn } from 'quasar';
|
|
|
|
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
|
import FetchData from 'src/components/FetchData.vue';
|
|
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
|
import VnInput from 'src/components/common/VnInput.vue';
|
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
|
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
|
|
|
import { useQuasar } from 'quasar';
|
|
import { useStateStore } from 'stores/useStateStore';
|
|
import { toCurrency } from 'src/filters';
|
|
import axios from 'axios';
|
|
import useNotify from 'src/composables/useNotify.js';
|
|
|
|
const quasar = useQuasar();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const { t } = useI18n();
|
|
const stateStore = useStateStore();
|
|
const { notify } = useNotify();
|
|
|
|
const rowsSelected = ref([]);
|
|
const entryBuysPaginateRef = ref(null);
|
|
const packagingsOptions = ref(null);
|
|
const originalRowDataCopy = ref(null);
|
|
|
|
const getInputEvents = (colField, props) => {
|
|
return colField === 'packagingFk'
|
|
? { 'update:modelValue': () => saveChange(colField, props) }
|
|
: {
|
|
'keyup.enter': () => saveChange(colField, props),
|
|
blur: () => saveChange(colField, props),
|
|
};
|
|
};
|
|
|
|
const tableColumnComponents = computed(() => ({
|
|
item: {
|
|
component: QBtn,
|
|
props: {
|
|
color: 'primary',
|
|
flat: true,
|
|
},
|
|
event: () => ({}),
|
|
},
|
|
quantity: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
class: 'input-number',
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
packagingFk: {
|
|
component: VnSelectFilter,
|
|
props: {
|
|
'option-value': 'id',
|
|
'option-label': 'id',
|
|
'emit-value': true,
|
|
'map-options': true,
|
|
'use-input': true,
|
|
'hide-selected': true,
|
|
options: packagingsOptions.value,
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
stickers: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
class: 'input-number',
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
weight: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
packing: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
grouping: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
buyingValue: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
price2: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
price3: {
|
|
component: VnInput,
|
|
props: {
|
|
type: 'number',
|
|
min: 0,
|
|
dense: true,
|
|
},
|
|
event: getInputEvents,
|
|
},
|
|
import: {
|
|
component: 'span',
|
|
props: {},
|
|
event: () => ({}),
|
|
},
|
|
}));
|
|
|
|
const entriesTableColumns = computed(() => {
|
|
return [
|
|
{
|
|
label: t('entry.summary.item'),
|
|
field: 'id',
|
|
name: 'item',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.quantity'),
|
|
field: 'quantity',
|
|
name: 'quantity',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.package'),
|
|
field: 'packagingFk',
|
|
name: 'packagingFk',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.stickers'),
|
|
field: 'stickers',
|
|
name: 'stickers',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.weight'),
|
|
field: 'weight',
|
|
name: 'weight',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.packing'),
|
|
field: 'packing',
|
|
name: 'packing',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.grouping'),
|
|
field: 'grouping',
|
|
name: 'grouping',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.buyingValue'),
|
|
field: 'buyingValue',
|
|
name: 'buyingValue',
|
|
align: 'left',
|
|
format: (value) => toCurrency(value),
|
|
},
|
|
{
|
|
label: t('entry.buys.groupingPrice'),
|
|
field: 'price2',
|
|
name: 'price2',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.buys.packingPrice'),
|
|
field: 'price3',
|
|
name: 'price3',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('entry.summary.import'),
|
|
name: 'import',
|
|
align: 'left',
|
|
format: (_, row) => toCurrency(row.buyingValue * row.quantity),
|
|
},
|
|
];
|
|
});
|
|
|
|
const copyOriginalRowsData = (rows) => {
|
|
// el objetivo de esto es guardar los valores iniciales de todas las rows para evitar guardar cambios si la data no cambió al disparar los eventos
|
|
originalRowDataCopy.value = JSON.parse(JSON.stringify(rows));
|
|
};
|
|
|
|
const saveChange = async (field, { rowIndex, row }) => {
|
|
try {
|
|
if (originalRowDataCopy.value[rowIndex][field] == row[field]) return;
|
|
await axios.patch(`Buys/${row.id}`, row);
|
|
originalRowDataCopy.value[rowIndex][field] = row[field];
|
|
} catch (err) {
|
|
console.error('Error saving changes', err);
|
|
}
|
|
};
|
|
|
|
const openRemoveDialog = async () => {
|
|
quasar
|
|
.dialog({
|
|
component: VnConfirm,
|
|
componentProps: {
|
|
title: t('Confirm deletion'),
|
|
message: t(
|
|
`Are you sure you want to delete this buy${
|
|
rowsSelected.value.length > 1 ? 's' : ''
|
|
}?`
|
|
),
|
|
data: rowsSelected.value,
|
|
},
|
|
})
|
|
.onOk(async () => {
|
|
try {
|
|
await deleteBuys();
|
|
const notifyMessage = t(
|
|
`Buy${rowsSelected.value.length > 1 ? 's' : ''} deleted`
|
|
);
|
|
notify(notifyMessage, 'positive');
|
|
} catch (err) {
|
|
console.error('Error deleting buys');
|
|
}
|
|
});
|
|
};
|
|
|
|
const deleteBuys = async () => {
|
|
await axios.post('Buys/deleteBuys', { buys: rowsSelected.value });
|
|
entryBuysPaginateRef.value.fetch();
|
|
};
|
|
|
|
const importBuys = () => {
|
|
router.push({ name: 'EntryBuysImport' });
|
|
};
|
|
|
|
const toggleGroupingMode = async (buy, mode) => {
|
|
try {
|
|
const grouping = 1;
|
|
const packing = 2;
|
|
const groupingMode = mode === 'grouping' ? grouping : packing;
|
|
|
|
const newGroupingMode = buy.groupingMode === groupingMode ? 0 : groupingMode;
|
|
|
|
const params = {
|
|
groupingMode: newGroupingMode,
|
|
};
|
|
|
|
await axios.patch(`Buys/${buy.id}`, params);
|
|
buy.groupingMode = newGroupingMode;
|
|
} catch (err) {
|
|
console.error('Error toggling grouping mode');
|
|
}
|
|
};
|
|
|
|
const lockIconType = (groupingMode, mode) => {
|
|
if (mode === 'packing') {
|
|
return groupingMode === 2 ? 'lock' : 'lock_open';
|
|
} else {
|
|
return groupingMode === 1 ? 'lock' : 'lock_open';
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<FetchData
|
|
ref="expensesRef"
|
|
url="Packagings"
|
|
:filter="{ fields: ['id'], where: { freightItemFk: true }, order: 'id ASC' }"
|
|
auto-load
|
|
@on-fetch="(data) => (packagingsOptions = data)"
|
|
/>
|
|
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
|
<QBtnGroup push style="column-gap: 10px">
|
|
<slot name="moreBeforeActions" />
|
|
<QBtn
|
|
:label="t('globals.remove')"
|
|
color="primary"
|
|
icon="delete"
|
|
flat
|
|
@click="openRemoveDialog()"
|
|
:disable="!rowsSelected?.length"
|
|
:title="t('globals.remove')"
|
|
/>
|
|
</QBtnGroup>
|
|
</Teleport>
|
|
<VnPaginate
|
|
ref="entryBuysPaginateRef"
|
|
data-key="EntryBuys"
|
|
:url="`Entries/${route.params.id}/getBuys`"
|
|
@on-fetch="copyOriginalRowsData($event)"
|
|
auto-load
|
|
>
|
|
<template #body="{ rows }">
|
|
<QTable
|
|
:rows="rows"
|
|
:columns="entriesTableColumns"
|
|
selection="multiple"
|
|
row-key="id"
|
|
class="full-width q-mt-md"
|
|
:grid="$q.screen.lt.md"
|
|
v-model:selected="rowsSelected"
|
|
:no-data-label="t('globals.noResults')"
|
|
>
|
|
<template #body="props">
|
|
<QTr>
|
|
<QTd>
|
|
<QCheckbox v-model="props.selected" />
|
|
</QTd>
|
|
<QTd
|
|
v-for="col in props.cols"
|
|
:key="col.name"
|
|
style="max-width: 100px"
|
|
>
|
|
<component
|
|
:is="tableColumnComponents[col.name].component"
|
|
v-bind="tableColumnComponents[col.name].props"
|
|
v-model="props.row[col.field]"
|
|
v-on="
|
|
tableColumnComponents[col.name].event(
|
|
col.field,
|
|
props
|
|
)
|
|
"
|
|
>
|
|
<template
|
|
v-if="
|
|
col.name === 'grouping' || col.name === 'packing'
|
|
"
|
|
#append
|
|
>
|
|
<QBtn
|
|
:icon="
|
|
lockIconType(props.row.groupingMode, col.name)
|
|
"
|
|
@click="toggleGroupingMode(props.row, col.name)"
|
|
class="cursor-pointer"
|
|
size="sm"
|
|
flat
|
|
dense
|
|
unelevated
|
|
push
|
|
:style="{
|
|
'font-variation-settings': `'FILL' ${
|
|
lockIconType(
|
|
props.row.groupingMode,
|
|
col.name
|
|
) === 'lock'
|
|
? 1
|
|
: 0
|
|
}`,
|
|
}"
|
|
/>
|
|
</template>
|
|
<template
|
|
v-if="col.name === 'item' || col.name === 'import'"
|
|
>
|
|
{{ col.value }}
|
|
</template>
|
|
<ItemDescriptorProxy
|
|
v-if="col.name === 'item'"
|
|
:id="props.row.id"
|
|
/>
|
|
</component>
|
|
</QTd>
|
|
</QTr>
|
|
<QTr no-hover>
|
|
<QTd />
|
|
<QTd>
|
|
<span>{{ props.row.item.itemType.code }}</span>
|
|
</QTd>
|
|
<QTd>
|
|
<span>{{ props.row.item.id }}</span>
|
|
</QTd>
|
|
<QTd>
|
|
<span>{{ props.row.item.size }}</span>
|
|
</QTd>
|
|
<QTd>
|
|
<span>{{ toCurrency(props.row.item.minPrice) }}</span>
|
|
</QTd>
|
|
<QTd colspan="7">
|
|
<span>{{ props.row.item.concept }}</span>
|
|
<span v-if="props.row.item.subName" class="subName">
|
|
{{ props.row.item.subName }}
|
|
</span>
|
|
<fetched-tags :item="props.row.item" :max-length="5" />
|
|
</QTd>
|
|
</QTr>
|
|
<!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys -->
|
|
<QTr v-if="props.rowIndex !== rows.length - 1" class="separation-row">
|
|
<QTd colspan="12" class="vn-table-separation-row" />
|
|
</QTr>
|
|
</template>
|
|
<template #item="props">
|
|
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
|
<QCard bordered flat>
|
|
<QCardSection>
|
|
<QCheckbox v-model="props.selected" dense />
|
|
</QCardSection>
|
|
<QSeparator />
|
|
<QList dense>
|
|
<QItem v-for="col in props.cols" :key="col.name">
|
|
<component
|
|
:is="tableColumnComponents[col.name].component"
|
|
v-bind="tableColumnComponents[col.name].props"
|
|
v-model="props.row[col.field]"
|
|
v-on="
|
|
tableColumnComponents[col.name].event(
|
|
col.field,
|
|
props
|
|
)
|
|
"
|
|
class="full-width"
|
|
>
|
|
<template
|
|
v-if="
|
|
col.name === 'item' ||
|
|
col.name === 'import'
|
|
"
|
|
>
|
|
{{ col.label + ': ' + col.value }}
|
|
</template>
|
|
</component>
|
|
</QItem>
|
|
</QList>
|
|
</QCard>
|
|
</div>
|
|
</template>
|
|
</QTable>
|
|
</template>
|
|
</VnPaginate>
|
|
|
|
<QPageSticky :offset="[20, 20]">
|
|
<QBtn fab icon="upload" color="primary" @click="importBuys()" />
|
|
<QTooltip class="text-no-wrap">
|
|
{{ t('Import buys') }}
|
|
</QTooltip>
|
|
</QPageSticky>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.grid-style-transition {
|
|
transition: transform 0.28s, background-color 0.28s;
|
|
}
|
|
</style>
|
|
|
|
<i18n>
|
|
es:
|
|
Import buys: Importar compras
|
|
Buy deleted: Compra eliminada
|
|
Buys deleted: Compras eliminadas
|
|
Confirm deletion: Confirmar eliminación
|
|
Are you sure you want to delete this buy?: Seguro que quieres eliminar esta compra?
|
|
Are you sure you want to delete this buys?: Seguro que quieres eliminar estas compras?
|
|
</i18n>
|