Merge pull request 'feature/EntrySubmodules' (#71) from feature/EntrySubmodules into dev
Reviewed-on: hyervoni/salix-front-mindshore#71
This commit is contained in:
commit
a41fd4c487
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'update:options']);
|
||||
const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']);
|
||||
|
||||
const $props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -32,6 +32,10 @@ const styleAttrs = computed(() => {
|
|||
}
|
||||
: {};
|
||||
});
|
||||
|
||||
const onEnterPress = () => {
|
||||
emit('keyup.enter');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -41,6 +45,7 @@ const styleAttrs = computed(() => {
|
|||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
type="text"
|
||||
:class="{ required: $attrs.required }"
|
||||
@keyup.enter="onEnterPress()"
|
||||
>
|
||||
<template v-if="$slots.prepend" #prepend>
|
||||
<slot name="prepend" />
|
||||
|
|
|
@ -261,9 +261,11 @@ export default {
|
|||
pageTitles: {
|
||||
entries: 'Entries',
|
||||
list: 'List',
|
||||
createEntry: 'New entry',
|
||||
summary: 'Summary',
|
||||
create: 'Create',
|
||||
basicData: 'Basic data',
|
||||
buys: 'Buys',
|
||||
notes: 'Notes',
|
||||
log: 'Log',
|
||||
},
|
||||
list: {
|
||||
newEntry: 'New entry',
|
||||
|
@ -303,6 +305,35 @@ export default {
|
|||
buyingValue: 'Buying value',
|
||||
import: 'Import',
|
||||
pvp: 'PVP',
|
||||
item: 'Item',
|
||||
},
|
||||
basicData: {
|
||||
supplier: 'Supplier',
|
||||
travel: 'Travel',
|
||||
reference: 'Reference',
|
||||
invoiceNumber: 'Invoice number',
|
||||
company: 'Company',
|
||||
currency: 'Currency',
|
||||
commission: 'Commission',
|
||||
observation: 'Observation',
|
||||
ordered: 'Ordered',
|
||||
confirmed: 'Confirmed',
|
||||
booked: 'Booked',
|
||||
raid: 'Raid',
|
||||
excludedFromAvailable: 'Inventory',
|
||||
},
|
||||
buys: {
|
||||
groupingPrice: 'Grouping price',
|
||||
packingPrice: 'Packing price',
|
||||
reference: 'Reference',
|
||||
observations: 'Observations',
|
||||
item: 'Item',
|
||||
description: 'Description',
|
||||
size: 'Size',
|
||||
packing: 'Packing',
|
||||
grouping: 'Grouping',
|
||||
buyingValue: 'Buying value',
|
||||
packagingFk: 'Box',
|
||||
},
|
||||
},
|
||||
ticket: {
|
||||
|
|
|
@ -261,7 +261,10 @@ export default {
|
|||
entries: 'Entradas',
|
||||
list: 'Listado',
|
||||
summary: 'Resumen',
|
||||
create: 'Crear',
|
||||
basicData: 'Datos básicos',
|
||||
buys: 'Compras',
|
||||
notes: 'Notas',
|
||||
log: 'Historial',
|
||||
},
|
||||
list: {
|
||||
newEntry: 'Nueva entrada',
|
||||
|
@ -301,6 +304,35 @@ export default {
|
|||
buyingValue: 'Coste',
|
||||
import: 'Importe',
|
||||
pvp: 'PVP',
|
||||
item: 'Artículo',
|
||||
},
|
||||
basicData: {
|
||||
supplier: 'Proveedor',
|
||||
travel: 'Envío',
|
||||
reference: 'Referencia',
|
||||
invoiceNumber: 'Núm. factura',
|
||||
company: 'Empresa',
|
||||
currency: 'Moneda',
|
||||
observation: 'Observación',
|
||||
commission: 'Comisión',
|
||||
ordered: 'Pedida',
|
||||
confirmed: 'Confirmado',
|
||||
booked: 'Asentado',
|
||||
raid: 'Redada',
|
||||
excludedFromAvailable: 'Inventario',
|
||||
},
|
||||
buys: {
|
||||
groupingPrice: 'Precio grouping',
|
||||
packingPrice: 'Precio packing',
|
||||
reference: 'Referencia',
|
||||
observations: 'Observaciónes',
|
||||
item: 'Artículo',
|
||||
description: 'Descripción',
|
||||
size: 'Medida',
|
||||
packing: 'Packing',
|
||||
grouping: 'Grouping',
|
||||
buyingValue: 'Coste',
|
||||
packagingFk: 'Embalaje',
|
||||
},
|
||||
},
|
||||
ticket: {
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
|
||||
import { toDate } from 'src/filters';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const suppliersOptions = ref([]);
|
||||
const travelsOptions = ref([]);
|
||||
const companiesOptions = ref([]);
|
||||
const currenciesOptions = ref([]);
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
:filter="{ fields: ['id', 'nickname'] }"
|
||||
order="nickname"
|
||||
@on-fetch="(data) => (suppliersOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Travels/filter"
|
||||
:filter="{ fields: ['id', 'warehouseInName'] }"
|
||||
order="id"
|
||||
@on-fetch="(data) => (travelsOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
ref="companiesRef"
|
||||
url="Companies"
|
||||
:filter="{ fields: ['id', 'code'] }"
|
||||
order="code"
|
||||
@on-fetch="(data) => (companiesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
ref="currenciesRef"
|
||||
url="Currencies"
|
||||
:filter="{ fields: ['id', 'code'] }"
|
||||
order="code"
|
||||
@on-fetch="(data) => (currenciesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FormModel
|
||||
:url="`Entries/${route.params.id}`"
|
||||
:url-update="`Entries/${route.params.id}`"
|
||||
model="entry"
|
||||
auto-load
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.basicData.supplier')"
|
||||
v-model="data.supplierFk"
|
||||
:options="suppliersOptions"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
hide-selected
|
||||
:required="true"
|
||||
map-options
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ scope.opt?.nickname }}, {{ scope.opt?.id }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.basicData.travel')"
|
||||
v-model="data.travelFk"
|
||||
:options="travelsOptions"
|
||||
option-value="id"
|
||||
option-label="warehouseInName"
|
||||
map-options
|
||||
hide-selected
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel
|
||||
>{{ scope.opt?.agencyModeName }} -
|
||||
{{ scope.opt?.warehouseInName }} ({{
|
||||
toDate(scope.opt?.shipped)
|
||||
}}) → {{ scope.opt?.warehouseOutName }} ({{
|
||||
toDate(scope.opt?.landed)
|
||||
}})</QItemLabel
|
||||
>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.reference"
|
||||
:label="t('entry.basicData.reference')"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.invoiceNumber"
|
||||
:label="t('entry.basicData.invoiceNumber')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.basicData.company')"
|
||||
v-model="data.companyFk"
|
||||
:options="companiesOptions"
|
||||
option-value="id"
|
||||
option-label="code"
|
||||
map-options
|
||||
hide-selected
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.basicData.currency')"
|
||||
v-model="data.currencyFk"
|
||||
:options="currenciesOptions"
|
||||
option-value="id"
|
||||
option-label="code"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<QInput
|
||||
:label="t('entry.basicData.commission')"
|
||||
v-model="data.commission"
|
||||
type="number"
|
||||
autofocus
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
:label="t('entry.basicData.observation')"
|
||||
type="textarea"
|
||||
v-model="data.observation"
|
||||
fill-input
|
||||
autogrow
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QCheckbox
|
||||
v-model="data.isOrdered"
|
||||
:label="t('entry.basicData.ordered')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<QCheckbox
|
||||
v-model="data.isConfirmed"
|
||||
:label="t('entry.basicData.confirmed')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<QCheckbox
|
||||
v-model="data.isExcludedFromAvailable"
|
||||
:label="t('entry.basicData.excludedFromAvailable')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<QCheckbox v-model="data.isRaid" :label="t('entry.basicData.raid')" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QCheckbox
|
||||
v-model="data.isBooked"
|
||||
:label="t('entry.basicData.booked')"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
|
@ -0,0 +1,407 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
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 { 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 tableColumnComponents = computed(() => ({
|
||||
item: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => ({}),
|
||||
},
|
||||
quantity: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
class: 'input-number',
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
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,
|
||||
}),
|
||||
event: (props) => ({
|
||||
'update:modelValue': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
stickers: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
class: 'input-number',
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
weight: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
packing: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
grouping: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
buyingValue: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
price2: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
price3: {
|
||||
component: () => VnInput,
|
||||
props: (col) => ({
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: col.label,
|
||||
}),
|
||||
event: (props) => ({
|
||||
'keyup.enter': () => saveChange(props.row),
|
||||
}),
|
||||
},
|
||||
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 saveChange = async (rowData) => {
|
||||
await axios.patch(`Buys/${rowData.id}`, rowData);
|
||||
};
|
||||
|
||||
const openRemoveDialog = async () => {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Confirm deletion'),
|
||||
message:
|
||||
rowsSelected.value.length > 1
|
||||
? t('Are you sure you want to delete this buys?')
|
||||
: t('Are you sure you want to delete this buy?'),
|
||||
data: rowsSelected.value,
|
||||
},
|
||||
})
|
||||
.onOk(async () => {
|
||||
try {
|
||||
await deleteBuys();
|
||||
const notifyMessage =
|
||||
rowsSelected.value.length > 1 ? t('Buys deleted') : t('Buy 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' });
|
||||
};
|
||||
</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`"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
:rows="rows"
|
||||
:columns="entriesTableColumns"
|
||||
selection="multiple"
|
||||
row-key="id"
|
||||
hide-bottom
|
||||
class="full-width q-mt-md"
|
||||
:grid="$q.screen.lt.md"
|
||||
v-model:selected="rowsSelected"
|
||||
>
|
||||
<template #body="props">
|
||||
<QTr>
|
||||
<QTd>
|
||||
<QCheckbox v-model="props.selected" />
|
||||
</QTd>
|
||||
<QTd v-for="col in props.cols" :key="col.name">
|
||||
<component
|
||||
:is="tableColumnComponents[col.name].component()"
|
||||
v-bind="tableColumnComponents[col.name].props(col)"
|
||||
v-model="props.row[col.field]"
|
||||
v-on="tableColumnComponents[col.name].event(props)"
|
||||
>
|
||||
<template
|
||||
v-if="col.name === 'item' || col.name === 'import'"
|
||||
>
|
||||
{{ col.value }}
|
||||
</template>
|
||||
</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" style="height: 24px" />
|
||||
</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(col)
|
||||
"
|
||||
v-model="props.row[col.field]"
|
||||
v-on="
|
||||
tableColumnComponents[col.name].event(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>
|
||||
{{ t('Import buys') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.separation-row {
|
||||
background-color: var(--vn-gray) !important;
|
||||
}
|
||||
.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>
|
|
@ -0,0 +1,281 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { toCurrency } from 'filters/index';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const importData = ref({
|
||||
file: null,
|
||||
invoice: null,
|
||||
buys: [],
|
||||
observation: null,
|
||||
ref: null,
|
||||
});
|
||||
|
||||
const lastItemBuysOptions = ref([]);
|
||||
const packagingsOptions = ref([]);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
label: t('entry.buys.item'),
|
||||
name: 'item',
|
||||
field: 'itemFk',
|
||||
options: lastItemBuysOptions.value,
|
||||
optionValue: 'id',
|
||||
optionLabel: 'name',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('entry.buys.description'),
|
||||
name: 'description',
|
||||
field: 'description',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('entry.buys.size'),
|
||||
name: 'size',
|
||||
field: 'size',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('entry.buys.packing'),
|
||||
name: 'packing',
|
||||
field: 'packing',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('entry.buys.grouping'),
|
||||
name: 'grouping',
|
||||
field: 'grouping',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('entry.buys.buyingValue'),
|
||||
name: 'buyingValue',
|
||||
field: 'buyingValue',
|
||||
align: 'left',
|
||||
format: (val) => toCurrency(val),
|
||||
},
|
||||
{
|
||||
label: t('entry.buys.packagingFk'),
|
||||
name: 'packagingFk',
|
||||
field: 'packagingFk',
|
||||
options: packagingsOptions.value,
|
||||
optionValue: 'id',
|
||||
optionLabel: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
const onFileChange = (e) => {
|
||||
importData.value.file = e;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => fillData(e.target.result);
|
||||
reader.readAsText(importData.value.file);
|
||||
};
|
||||
|
||||
const fillData = async (rawData) => {
|
||||
const data = JSON.parse(rawData);
|
||||
const [invoice] = data.invoices;
|
||||
|
||||
importData.value.observation = invoice.tx_awb;
|
||||
const companyName = invoice.tx_company;
|
||||
const boxes = invoice.boxes;
|
||||
const buys = [];
|
||||
|
||||
for (let box of boxes) {
|
||||
const boxVolume = box.nu_length * box.nu_width * box.nu_height;
|
||||
for (let product of box.products) {
|
||||
const packing = product.nu_stems_bunch * product.nu_bunches;
|
||||
buys.push({
|
||||
description: product.nm_product,
|
||||
companyName: companyName,
|
||||
size: product.nu_length,
|
||||
packing: packing,
|
||||
grouping: product.nu_stems_bunch,
|
||||
buyingValue: parseFloat(product.mny_rate_stem),
|
||||
volume: boxVolume,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const boxesId = boxes.map((box) => box.id_box);
|
||||
importData.value.ref = boxesId.join(', ');
|
||||
await fetchBuys(buys);
|
||||
};
|
||||
|
||||
const fetchBuys = async (buys) => {
|
||||
try {
|
||||
const params = { buys };
|
||||
const { data } = await axios.post(
|
||||
`Entries/${route.params.id}/importBuysPreview`,
|
||||
params
|
||||
);
|
||||
importData.value.buys = data;
|
||||
} catch (err) {
|
||||
console.error('Error fetching buys');
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
const params = importData.value;
|
||||
const hasAnyEmptyRow = params.buys.some((buy) => {
|
||||
return buy.itemFk === null;
|
||||
});
|
||||
|
||||
if (hasAnyEmptyRow) {
|
||||
notify(t('Some of the imported buys does not have an item'), 'negative');
|
||||
return;
|
||||
}
|
||||
|
||||
await axios.post(`Entries/${route.params.id}/importBuys`, params);
|
||||
notify('globals.dataSaved', 'positive');
|
||||
redirectToBuysView();
|
||||
} catch (err) {
|
||||
console.error('Error importing buys', err);
|
||||
}
|
||||
};
|
||||
|
||||
const redirectToBuysView = () => {
|
||||
router.push({ name: 'EntryBuys' });
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
:url="`Entries/${route.params.id}/lastItemBuys`"
|
||||
:filter="{ fields: ['id', 'name'] }"
|
||||
order="id DESC"
|
||||
@on-fetch="(data) => (lastItemBuysOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Packagings"
|
||||
:filter="{ fields: ['id'], where: { isBox: true } }"
|
||||
order="id ASC"
|
||||
@on-fetch="(data) => (packagingsOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<QForm>
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<div>
|
||||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
color="primary"
|
||||
icon="restart_alt"
|
||||
flat
|
||||
@click="redirectToBuysView()"
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
color="primary"
|
||||
icon="save"
|
||||
type="submit"
|
||||
:disable="!importData.file"
|
||||
@click="onSubmit()"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</div>
|
||||
</Teleport>
|
||||
<QCard class="q-pa-lg">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QFile
|
||||
label="Standard"
|
||||
:multiple="false"
|
||||
v-model="importData.file"
|
||||
@update:model-value="onFileChange($event)"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="vn:attach" class="cursor-pointer">
|
||||
<QTooltip>{{ t('Select a file') }}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QFile>
|
||||
</div>
|
||||
</VnRow>
|
||||
<div v-if="importData.file">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('entry.buys.reference')"
|
||||
v-model="importData.ref"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('entry.buys.observations')"
|
||||
v-model="importData.observation"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:rows="importData.buys"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
hide-pagination
|
||||
>
|
||||
<template #body-cell-item="{ row, col }">
|
||||
<QTd auto-width>
|
||||
<VnSelectFilter
|
||||
v-model="row[col.field]"
|
||||
:options="col.options"
|
||||
:option-value="col.optionValue"
|
||||
:option-label="col.optionLabel"
|
||||
hide-selected
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt?.id }} -
|
||||
{{ scope.opt?.name }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-packagingFk="{ row, col }">
|
||||
<QTd auto-width>
|
||||
<VnSelectFilter
|
||||
v-model="row[col.field]"
|
||||
:options="col.options"
|
||||
:option-value="col.optionValue"
|
||||
:option-label="col.optionLabel"
|
||||
hide-selected
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</VnRow>
|
||||
</div>
|
||||
</QCard>
|
||||
</QForm>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Select a file: Selecciona un fichero
|
||||
Some of the imported buys does not have an item: Algunas de las compras importadas no tienen un artículo
|
||||
</i18n>
|
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
</script>
|
||||
<template>
|
||||
<VnLog model="Entry" url="/EntryLogs"></VnLog>
|
||||
</template>
|
|
@ -0,0 +1 @@
|
|||
<template>Entry Notes</template>
|
|
@ -39,47 +39,30 @@ onMounted(async () => {
|
|||
const tableColumnComponents = {
|
||||
quantity: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
},
|
||||
stickers: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
packagingFk: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
weight: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
packing: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
grouping: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
buyingValue: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
amount: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
pvp: {
|
||||
component: () => 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -314,10 +297,7 @@ const fetchEntryBuys = async () => {
|
|||
<QTr no-hover>
|
||||
<QTd v-for="col in cols" :key="col.name">
|
||||
<component
|
||||
:is="tableColumnComponents[col.name].component(props)"
|
||||
v-bind="tableColumnComponents[col.name].props(props)"
|
||||
@click="tableColumnComponents[col.name].event(props)"
|
||||
class="col-content"
|
||||
:is="tableColumnComponents[col.name].component()"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
|
|
|
@ -11,7 +11,7 @@ export default {
|
|||
redirect: { name: 'EntryMain' },
|
||||
menus: {
|
||||
main: ['EntryList'],
|
||||
card: [],
|
||||
card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryLog'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
@ -54,6 +54,47 @@ export default {
|
|||
},
|
||||
component: () => import('src/pages/Entry/Card/EntrySummary.vue'),
|
||||
},
|
||||
{
|
||||
path: 'basic-data',
|
||||
name: 'EntryBasicData',
|
||||
meta: {
|
||||
title: 'basicData',
|
||||
icon: 'vn:settings',
|
||||
},
|
||||
component: () => import('src/pages/Entry/Card/EntryBasicData.vue'),
|
||||
},
|
||||
{
|
||||
path: 'buys',
|
||||
name: 'EntryBuys',
|
||||
meta: {
|
||||
title: 'buys',
|
||||
icon: 'vn:lines',
|
||||
},
|
||||
component: () => import('src/pages/Entry/Card/EntryBuys.vue'),
|
||||
},
|
||||
{
|
||||
path: 'buys/import',
|
||||
name: 'EntryBuysImport',
|
||||
component: () => import('src/pages/Entry/Card/EntryBuysImport.vue'),
|
||||
},
|
||||
{
|
||||
path: 'notes',
|
||||
name: 'EntryNotes',
|
||||
meta: {
|
||||
title: 'notes',
|
||||
icon: 'vn:notes',
|
||||
},
|
||||
component: () => import('src/pages/Entry/Card/EntryNotes.vue'),
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
name: 'EntryLog',
|
||||
meta: {
|
||||
title: 'log',
|
||||
icon: 'vn:History',
|
||||
},
|
||||
component: () => import('src/pages/Entry/Card/EntryLog.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue