Entry buys import
This commit is contained in:
parent
596904c4f8
commit
64a4a800f7
|
@ -325,6 +325,15 @@ export default {
|
||||||
buys: {
|
buys: {
|
||||||
groupingPrice: 'Grouping price',
|
groupingPrice: 'Grouping price',
|
||||||
packingPrice: 'Packing price',
|
packingPrice: 'Packing price',
|
||||||
|
reference: 'Reference',
|
||||||
|
observations: 'Observations',
|
||||||
|
item: 'Item',
|
||||||
|
description: 'Description',
|
||||||
|
size: 'Size',
|
||||||
|
packing: 'Packing',
|
||||||
|
grouping: 'Grouping',
|
||||||
|
buyingValue: 'Buying value',
|
||||||
|
packagingFk: 'Box',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ticket: {
|
ticket: {
|
||||||
|
|
|
@ -324,6 +324,15 @@ export default {
|
||||||
buys: {
|
buys: {
|
||||||
groupingPrice: 'Precio grouping',
|
groupingPrice: 'Precio grouping',
|
||||||
packingPrice: 'Precio packing',
|
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: {
|
ticket: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||||
|
@ -18,6 +18,7 @@ import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
@ -251,7 +252,7 @@ const deleteBuys = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const importBuys = () => {
|
const importBuys = () => {
|
||||||
// redirect to buys import view
|
router.push({ name: 'EntryBuysImport' });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -72,6 +72,11 @@ export default {
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Entry/Card/EntryBuys.vue'),
|
component: () => import('src/pages/Entry/Card/EntryBuys.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'buys/import',
|
||||||
|
name: 'EntryBuysImport',
|
||||||
|
component: () => import('src/pages/Entry/Card/EntryBuysImport.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'notes',
|
path: 'notes',
|
||||||
name: 'EntryNotes',
|
name: 'EntryNotes',
|
||||||
|
|
Loading…
Reference in New Issue