Merge branch 'dev' into 7385-fixColumnNameReference
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Jose Antonio Tubau 2025-05-19 10:20:00 +00:00
commit acd9c97324
20 changed files with 547 additions and 918 deletions

View File

@ -131,11 +131,10 @@ async function fetch(data) {
const rows = keyData ? data[keyData] : data; const rows = keyData ? data[keyData] : data;
resetData(rows); resetData(rows);
emit('onFetch', rows); emit('onFetch', rows);
$props.insertOnLoad && await insert(); $props.insertOnLoad && (await insert());
return rows; return rows;
} }
function resetData(data) { function resetData(data) {
if (!data) return; if (!data) return;
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
@ -146,15 +145,22 @@ function resetData(data) {
formData.value = JSON.parse(JSON.stringify(data)); formData.value = JSON.parse(JSON.stringify(data));
if (watchChanges.value) watchChanges.value(); //destroy watcher if (watchChanges.value) watchChanges.value(); //destroy watcher
watchChanges.value = watch(formData, (nVal) => { watchChanges.value = watch(
formData,
(nVal) => {
hasChanges.value = false; hasChanges.value = false;
const filteredNewData = nVal.filter(row => !isRowEmpty(row) || row[$props.primaryKey]); const filteredNewData = nVal.filter(
const filteredOriginal = originalData.value.filter(row => row[$props.primaryKey]); (row) => !isRowEmpty(row) || row[$props.primaryKey],
);
const filteredOriginal = originalData.value.filter(
(row) => row[$props.primaryKey],
);
const changes = getDifferences(filteredOriginal, filteredNewData); const changes = getDifferences(filteredOriginal, filteredNewData);
hasChanges.value = !isEmpty(changes); hasChanges.value = !isEmpty(changes);
},
}, { deep: true }); { deep: true },
);
} }
async function reset() { async function reset() {
await fetch(originalData.value); await fetch(originalData.value);
@ -185,7 +191,6 @@ async function onSubmit() {
isLoading.value = true; isLoading.value = true;
await saveChanges($props.saveFn ? formData.value : null); await saveChanges($props.saveFn ? formData.value : null);
} }
async function onSubmitAndGo() { async function onSubmitAndGo() {
@ -194,8 +199,8 @@ async function onSubmitAndGo() {
} }
async function saveChanges(data) { async function saveChanges(data) {
formData.value = formData.value.filter(row => formData.value = formData.value.filter(
row[$props.primaryKey] || !isRowEmpty(row) (row) => row[$props.primaryKey] || !isRowEmpty(row),
); );
if ($props.saveFn) { if ($props.saveFn) {
@ -228,7 +233,7 @@ async function saveChanges(data) {
} }
async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) { async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) {
formData.value = formData.value.filter(row => !isRowEmpty(row)); formData.value = formData.value.filter((row) => !isRowEmpty(row));
const lastRow = formData.value.at(-1); const lastRow = formData.value.at(-1);
const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false; const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false;
@ -239,20 +244,18 @@ async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault
const nRow = Object.assign({ $index }, pushData); const nRow = Object.assign({ $index }, pushData);
formData.value.push(nRow); formData.value.push(nRow);
const hasChange = Object.keys(nRow).some(key => !isChange(nRow, key)); const hasChange = Object.keys(nRow).some((key) => !isChange(nRow, key));
if (hasChange) hasChanges.value = true; if (hasChange) hasChanges.value = true;
} }
function isRowEmpty(row) { function isRowEmpty(row) {
return Object.keys(row).every(key => isChange(row, key)); return Object.keys(row).every((key) => isChange(row, key));
} }
function isChange(row, key) {
function isChange(row,key){
return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key); return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key);
} }
async function remove(data) { async function remove(data) {
if (!data.length) if (!data.length)
return quasar.notify({ return quasar.notify({
@ -270,7 +273,9 @@ async function remove(data) {
(form) => !preRemove.some((index) => index == form.$index), (form) => !preRemove.some((index) => index == form.$index),
); );
formData.value = newData; formData.value = newData;
hasChanges.value = JSON.stringify(removeIndexField(formData.value)) !== JSON.stringify(removeIndexField(originalData.value)); hasChanges.value =
JSON.stringify(removeIndexField(formData.value)) !==
JSON.stringify(removeIndexField(originalData.value));
} }
if (ids.length) { if (ids.length) {
quasar quasar
@ -286,7 +291,7 @@ async function remove(data) {
}) })
.onOk(async () => { .onOk(async () => {
newData = newData.filter((form) => !ids.some((id) => id == form[pk])); newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
fetch(newData); await reload();
}); });
} }

View File

@ -254,17 +254,13 @@ async function save() {
old: originalData.value, old: originalData.value,
}); });
if ($props.reload) await arrayData.fetch({}); if ($props.reload) await arrayData.fetch({});
if ($props.goTo) push({ path: $props.goTo });
hasChanges.value = false; hasChanges.value = false;
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
} }
async function saveAndGo() {
await save();
push({ path: $props.goTo });
}
function reset() { function reset() {
formData.value = JSON.parse(JSON.stringify(originalData.value)); formData.value = JSON.parse(JSON.stringify(originalData.value));
updateAndEmit('onFetch', { val: originalData.value }); updateAndEmit('onFetch', { val: originalData.value });
@ -385,7 +381,7 @@ defineExpose({
<QBtnDropdown <QBtnDropdown
data-cy="saveAndContinueDefaultBtn" data-cy="saveAndContinueDefaultBtn"
v-if="$props.goTo" v-if="$props.goTo"
@click="saveAndGo" @click="submitForm"
:label=" :label="
tMobile('globals.saveAndContinue') + tMobile('globals.saveAndContinue') +
' ' + ' ' +
@ -405,7 +401,7 @@ defineExpose({
<QItem <QItem
clickable clickable
v-close-popup v-close-popup
@click="save" @click="submitForm"
:title="t('globals.save')" :title="t('globals.save')"
> >
<QItemSection> <QItemSection>

View File

@ -185,6 +185,7 @@ const col = computed(() => {
newColumn.attrs = { ...newColumn.component?.attrs, autofocus: $props.autofocus }; newColumn.attrs = { ...newColumn.component?.attrs, autofocus: $props.autofocus };
newColumn.event = { ...newColumn.component?.event, ...$props?.eventHandlers }; newColumn.event = { ...newColumn.component?.event, ...$props?.eventHandlers };
} }
return newColumn; return newColumn;
}); });

View File

@ -151,6 +151,10 @@ const $props = defineProps({
type: String, type: String,
default: 'vnTable', default: 'vnTable',
}, },
selectionFn: {
type: Function,
default: null,
},
}); });
const { t } = useI18n(); const { t } = useI18n();
@ -338,10 +342,10 @@ function stopEventPropagation(event) {
event.stopPropagation(); event.stopPropagation();
} }
function reload(params) { async function reload(params) {
selected.value = []; selected.value = [];
selectAll.value = false; selectAll.value = false;
CrudModelRef.value.reload(params); await CrudModelRef.value.reload(params);
} }
function columnName(col) { function columnName(col) {
@ -395,12 +399,14 @@ function hasEditableFormat(column) {
} }
const clickHandler = async (event) => { const clickHandler = async (event) => {
const clickedElement = event.target.closest('td'); const el = event.target;
const isDateElement = event.target.closest('.q-date'); const clickedElement = el.closest('td');
const isTimeElement = event.target.closest('.q-time'); const isDateElement = el.closest('.q-date');
const isQSelectDropDown = event.target.closest('.q-select__dropdown-icon'); const isTimeElement = el.closest('.q-time');
const isQSelectDropDown = el.closest('.q-select__dropdown-icon');
const isDialog = el.closest('.q-dialog');
if (isDateElement || isTimeElement || isQSelectDropDown) return; if (isDateElement || isTimeElement || isQSelectDropDown || isDialog) return;
if (clickedElement === null) { if (clickedElement === null) {
await destroyInput(editingRow.value, editingField.value); await destroyInput(editingRow.value, editingField.value);
@ -447,6 +453,7 @@ async function renderInput(rowId, field, clickedElement) {
const row = CrudModelRef.value.formData[rowId]; const row = CrudModelRef.value.formData[rowId];
const oldValue = CrudModelRef.value.formData[rowId][column?.name]; const oldValue = CrudModelRef.value.formData[rowId][column?.name];
if (column.disable) return;
if (!clickedElement) if (!clickedElement)
clickedElement = document.querySelector( clickedElement = document.querySelector(
`[data-row-index="${rowId}"][data-col-field="${field}"]`, `[data-row-index="${rowId}"][data-col-field="${field}"]`,
@ -480,6 +487,7 @@ async function renderInput(rowId, field, clickedElement) {
await destroyInput(rowId, field, clickedElement); await destroyInput(rowId, field, clickedElement);
}, },
keydown: async (event) => { keydown: async (event) => {
await column?.cellEvent?.['keydown']?.(event, row);
switch (event.key) { switch (event.key) {
case 'Tab': case 'Tab':
await handleTabKey(event, rowId, field); await handleTabKey(event, rowId, field);
@ -655,7 +663,9 @@ const rowCtrlClickFunction = computed(() => {
}); });
const handleHeaderSelection = (evt, data) => { const handleHeaderSelection = (evt, data) => {
if (evt === 'updateSelected' && selectAll.value) { if (evt === 'updateSelected' && selectAll.value) {
selected.value = tableRef.value.rows; const fn = $props.selectionFn;
const rows = tableRef.value.rows;
selected.value = fn ? fn(rows) : rows;
} else if (evt === 'selectAll') { } else if (evt === 'selectAll') {
selected.value = data; selected.value = data;
} else { } else {
@ -701,7 +711,6 @@ const handleHeaderSelection = (evt, data) => {
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll="isTableMode" :disable-infinite-scroll="isTableMode"
:before-save-fn="removeTextValue" :before-save-fn="removeTextValue"
@save-changes="reload"
:has-sub-toolbar="$props.hasSubToolbar ?? isEditable" :has-sub-toolbar="$props.hasSubToolbar ?? isEditable"
:auto-load="hasParams || $attrs['auto-load']" :auto-load="hasParams || $attrs['auto-load']"
> >
@ -729,7 +738,15 @@ const handleHeaderSelection = (evt, data) => {
:virtual-scroll="isTableMode" :virtual-scroll="isTableMode"
@virtual-scroll="onVirtualScroll" @virtual-scroll="onVirtualScroll"
@row-click="(event, row) => handleRowClick(event, row)" @row-click="(event, row) => handleRowClick(event, row)"
@update:selected="emit('update:selected', $event)" @update:selected="
(evt) => {
if ($props.selectionFn) selected = $props.selectionFn(evt);
emit(
'update:selected',
selectionFn ? selectionFn(selected) : selected,
);
}
"
@selection="(details) => handleSelection(details, rows)" @selection="(details) => handleSelection(details, rows)"
:hide-selected-banner="true" :hide-selected-banner="true"
:data-cy :data-cy

View File

@ -36,8 +36,6 @@ const validate = async () => {
isLoading.value = true; isLoading.value = true;
await props.submitFn(newPassword, oldPassword); await props.submitFn(newPassword, oldPassword);
emit('onSubmit'); emit('onSubmit');
} catch (e) {
notify('errors.writeRequest', 'negative');
} finally { } finally {
changePassDialog.value.hide(); changePassDialog.value.hide();
isLoading.value = false; isLoading.value = false;

View File

@ -445,7 +445,12 @@ function getOptionLabel(property) {
</template> </template>
<template #option="{ opt, itemProps }"> <template #option="{ opt, itemProps }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">
<QItemSection v-if="typeof opt !== 'object'"> {{ opt }}</QItemSection> <QItemSection v-if="typeof optionLabel === 'function'">{{
optionLabel(opt)
}}</QItemSection>
<QItemSection v-else-if="typeof opt !== 'object'">
{{ opt }}</QItemSection
>
<QItemSection v-else-if="opt[optionValue] == opt[optionLabel]"> <QItemSection v-else-if="opt[optionValue] == opt[optionLabel]">
<QItemLabel>{{ opt[optionLabel] }}</QItemLabel> <QItemLabel>{{ opt[optionLabel] }}</QItemLabel>
</QItemSection> </QItemSection>

View File

@ -1,22 +1,20 @@
<script setup> <script setup>
import { ref, useTemplateRef } from 'vue'; import { computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
import VnSelectDialog from './VnSelectDialog.vue'; import VnSelectDialog from './VnSelectDialog.vue';
import CreateNewExpenseForm from '../CreateNewExpenseForm.vue'; import CreateNewExpenseForm from '../CreateNewExpenseForm.vue';
import FetchData from '../FetchData.vue'; import { useArrayData } from 'src/composables/useArrayData';
const model = defineModel({ type: [String, Number, Object] }); const model = defineModel({ type: [String, Number, Object] });
const { t } = useI18n(); const { t } = useI18n();
const expenses = ref([]); const arrayData = useArrayData('expenses', { url: 'Expenses' });
const selectDialogRef = useTemplateRef('selectDialogRef'); const expenses = computed(() => arrayData.store.data);
async function autocompleteExpense(evt) { onMounted(async () => await arrayData.fetch({}));
const val = evt.target.value;
if (!val || isNaN(val)) return; async function updateModel(evt) {
const lookup = expenses.value.find(({ id }) => id == useAccountShortToStandard(val)); await arrayData.fetch({});
if (selectDialogRef.value) model.value = evt.id;
selectDialogRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
} }
</script> </script>
<template> <template>
@ -30,18 +28,11 @@ async function autocompleteExpense(evt) {
:filter-options="['id', 'name']" :filter-options="['id', 'name']"
:tooltip="t('Create a new expense')" :tooltip="t('Create a new expense')"
:acls="[{ model: 'Expense', props: '*', accessType: 'WRITE' }]" :acls="[{ model: 'Expense', props: '*', accessType: 'WRITE' }]"
@keydown.tab.prevent="autocompleteExpense"
> >
<template #form> <template #form>
<CreateNewExpenseForm @on-data-saved="$refs.expensesRef.fetch()" /> <CreateNewExpenseForm @on-data-saved="updateModel" />
</template> </template>
</VnSelectDialog> </VnSelectDialog>
<FetchData
ref="expensesRef"
url="Expenses"
auto-load
@on-fetch="(data) => (expenses = data)"
/>
</template> </template>
<i18n> <i18n>
es: es:

View File

@ -1,5 +1,13 @@
<script setup> <script setup>
import { ref, computed, markRaw, useTemplateRef, onBeforeMount, watch } from 'vue'; import {
ref,
computed,
markRaw,
useTemplateRef,
onBeforeMount,
watch,
onUnmounted,
} from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
@ -19,7 +27,10 @@ import { useQuasar } from 'quasar';
import InvoiceInDescriptorProxy from '../InvoiceIn/Card/InvoiceInDescriptorProxy.vue'; import InvoiceInDescriptorProxy from '../InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import { useStateStore } from 'src/stores/useStateStore'; import { useStateStore } from 'src/stores/useStateStore';
import { downloadFile } from 'src/composables/downloadFile'; import { downloadFile } from 'src/composables/downloadFile';
import { useRoute } from 'vue-router';
import { useAcl } from 'src/composables/useAcl';
const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const { notify } = useNotify(); const { notify } = useNotify();
@ -27,8 +38,6 @@ const user = useState().getUser();
const stateStore = useStateStore(); const stateStore = useStateStore();
const updateDialog = ref(); const updateDialog = ref();
const uploadDialog = ref(); const uploadDialog = ref();
let maxDays;
let defaultDays;
const dataKey = 'entryPreaccountingFilter'; const dataKey = 'entryPreaccountingFilter';
const url = 'Entries/preAccountingFilter'; const url = 'Entries/preAccountingFilter';
const arrayData = useArrayData(dataKey); const arrayData = useArrayData(dataKey);
@ -45,12 +54,16 @@ const defaultDmsDescription = ref();
const dmsTypeId = ref(); const dmsTypeId = ref();
const selectedRows = ref([]); const selectedRows = ref([]);
const totalAmount = ref(); const totalAmount = ref();
const hasDiferentDms = ref(false);
let maxDays;
let defaultDays;
let supplierRef;
let dmsFk;
const totalSelectedAmount = computed(() => { const totalSelectedAmount = computed(() => {
if (!selectedRows.value.length) return 0; if (!selectedRows.value.length) return 0;
return selectedRows.value.reduce((acc, entry) => acc + entry.amount, 0); return selectedRows.value.reduce((acc, entry) => acc + entry.amount, 0);
}); });
let supplierRef;
let dmsFk;
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'id', name: 'id',
@ -63,6 +76,7 @@ const columns = computed(() => [
{ {
name: 'invoiceNumber', name: 'invoiceNumber',
label: t('entry.preAccount.invoiceNumber'), label: t('entry.preAccount.invoiceNumber'),
width: 'max-content',
}, },
{ {
name: 'company', name: 'company',
@ -73,7 +87,7 @@ const columns = computed(() => [
optionLabel: 'code', optionLabel: 'code',
options: companies.value, options: companies.value,
}, },
orderBy: false, class: 'shrink',
}, },
{ {
name: 'warehouse', name: 'warehouse',
@ -102,6 +116,7 @@ const columns = computed(() => [
{ {
name: 'reference', name: 'reference',
label: t('entry.preAccount.reference'), label: t('entry.preAccount.reference'),
width: 'max-content',
}, },
{ {
name: 'shipped', name: 'shipped',
@ -136,6 +151,7 @@ const columns = computed(() => [
class: 'fit', class: 'fit',
event: 'update', event: 'update',
}, },
width: 'max-content',
}, },
{ {
name: 'country', name: 'country',
@ -145,6 +161,7 @@ const columns = computed(() => [
name: 'countryFk', name: 'countryFk',
options: countries.value, options: countries.value,
}, },
class: 'shrink',
}, },
{ {
name: 'description', name: 'description',
@ -165,11 +182,12 @@ const columns = computed(() => [
component: 'number', component: 'number',
name: 'payDem', name: 'payDem',
}, },
class: 'shrink',
}, },
{ {
name: 'fiscalCode', name: 'fiscalCode',
label: t('entry.preAccount.fiscalCode'), label: t('entry.preAccount.fiscalCode'),
format: ({ fiscalCode }) => t(fiscalCode), format: ({ fiscalCode }, dashIfEmpty) => t(dashIfEmpty(fiscalCode)),
columnFilter: { columnFilter: {
component: 'select', component: 'select',
name: 'fiscalCode', name: 'fiscalCode',
@ -208,20 +226,21 @@ const columns = computed(() => [
]); ]);
onBeforeMount(async () => { onBeforeMount(async () => {
const filter = JSON.parse(route.query.entryPreaccountingFilter ?? '{}');
const { data } = await axios.get('EntryConfigs/findOne', { const { data } = await axios.get('EntryConfigs/findOne', {
params: { filter: JSON.stringify({ fields: ['maxDays', 'defaultDays'] }) }, params: { filter: JSON.stringify({ fields: ['maxDays', 'defaultDays'] }) },
}); });
maxDays = data.maxDays; maxDays = data.maxDays;
defaultDays = data.defaultDays; defaultDays = data.defaultDays;
daysAgo.value = arrayData.store.userParams.daysAgo || defaultDays; daysAgo.value = filter.daysAgo || defaultDays;
isBooked.value = arrayData.store.userParams.isBooked || false; isBooked.value = filter.isBooked || false;
stateStore.leftDrawer = false; stateStore.leftDrawer = false;
}); });
watch(selectedRows, (nVal, oVal) => { onUnmounted(() => (stateStore.leftDrawer = true));
const lastRow = nVal.at(-1);
if (lastRow?.isBooked) selectedRows.value.pop(); watch(selectedRows, async (nVal, oVal) => {
if (nVal.length > oVal.length && lastRow.invoiceInFk) if (nVal.length > oVal.length && nVal.at(-1).invoiceInFk)
quasar.dialog({ quasar.dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { title: t('entry.preAccount.hasInvoice') }, componentProps: { title: t('entry.preAccount.hasInvoice') },
@ -232,15 +251,22 @@ function filterByDaysAgo(val) {
if (!val) val = defaultDays; if (!val) val = defaultDays;
else if (val > maxDays) val = maxDays; else if (val > maxDays) val = maxDays;
daysAgo.value = val; daysAgo.value = val;
arrayData.store.userParams.daysAgo = daysAgo.value; table.value.reload({
table.value.reload(); userParams: { ...arrayData.store.userParams, daysAgo: daysAgo.value },
});
} }
async function preAccount() { async function preAccount() {
const entries = selectedRows.value; const entries = selectedRows.value;
const { companyFk, isAgricultural, landed } = entries.at(0); const { companyFk, isAgricultural, landed } = entries.at(0);
supplierRef = null;
dmsFk = undefined;
hasDiferentDms.value = false;
try { try {
dmsFk = entries.find(({ gestDocFk }) => gestDocFk)?.gestDocFk; entries.forEach(({ gestDocFk }) => {
if (!dmsFk && gestDocFk) dmsFk = gestDocFk;
if (dmsFk && gestDocFk && dmsFk != gestDocFk) hasDiferentDms.value = true;
});
if (isAgricultural) { if (isAgricultural) {
const year = new Date(landed).getFullYear(); const year = new Date(landed).getFullYear();
supplierRef = ( supplierRef = (
@ -297,8 +323,6 @@ async function createInvoice() {
} catch (e) { } catch (e) {
throw e; throw e;
} finally { } finally {
supplierRef = null;
dmsFk = undefined;
selectedRows.value.length = 0; selectedRows.value.length = 0;
table.value.reload(); table.value.reload();
} }
@ -359,6 +383,7 @@ async function createInvoice() {
:search-remove-params="false" :search-remove-params="false"
/> />
<VnTable <VnTable
v-if="isBooked !== undefined && daysAgo !== undefined"
v-model:selected="selectedRows" v-model:selected="selectedRows"
:data-key :data-key
:columns :columns
@ -377,10 +402,12 @@ async function createInvoice() {
@on-fetch=" @on-fetch="
(data) => (totalAmount = data?.reduce((acc, entry) => acc + entry.amount, 0)) (data) => (totalAmount = data?.reduce((acc, entry) => acc + entry.amount, 0))
" "
:selection-fn="(rows) => rows.filter((row) => !row.isBooked)"
auto-load auto-load
> >
<template #top-left> <template #top-left>
<QBtn <QBtn
v-if="useAcl().hasAcl('Entry', 'addInvoiceIn', 'WRITE')"
data-cy="preAccount_btn" data-cy="preAccount_btn"
icon="account_balance" icon="account_balance"
color="primary" color="primary"
@ -437,6 +464,11 @@ async function createInvoice() {
:title="t('entry.preAccount.dialog.title')" :title="t('entry.preAccount.dialog.title')"
:message="t('entry.preAccount.dialog.message')" :message="t('entry.preAccount.dialog.message')"
> >
<template #customHTML v-if="hasDiferentDms">
<p class="text-negative">
{{ t('differentGestdoc') }}
</p>
</template>
<template #actions> <template #actions>
<QBtn <QBtn
data-cy="updateFileYes" data-cy="updateFileYes"
@ -471,9 +503,11 @@ en:
IntraCommunity: Intra-community IntraCommunity: Intra-community
NonCommunity: Non-community NonCommunity: Non-community
CanaryIslands: Canary Islands CanaryIslands: Canary Islands
differentGestdoc: The entries have different gestdoc
es: es:
IntraCommunity: Intracomunitaria IntraCommunity: Intracomunitaria
NonCommunity: Extracomunitaria NonCommunity: Extracomunitaria
CanaryIslands: Islas Canarias CanaryIslands: Islas Canarias
National: Nacional National: Nacional
differentGestdoc: Las entradas tienen diferentes gestdoc
</i18n> </i18n>

View File

@ -130,7 +130,7 @@ entry:
supplierFk: Supplier supplierFk: Supplier
country: Country country: Country
description: Entry type description: Entry type
payDem: Payment term payDem: P. term
isBooked: B isBooked: B
isReceived: R isReceived: R
entryType: Entry type entryType: Entry type
@ -138,7 +138,7 @@ entry:
fiscalCode: Account type fiscalCode: Account type
daysAgo: Max 365 days daysAgo: Max 365 days
search: Search search: Search
searchInfo: You can search by supplier name or nickname searchInfo: You can search by supplier name, nickname or tax number
btn: Pre-account btn: Pre-account
hasInvoice: This entry has already an invoice in hasInvoice: This entry has already an invoice in
success: It has been successfully pre-accounted success: It has been successfully pre-accounted

View File

@ -81,7 +81,7 @@ entry:
supplierFk: Proveedor supplierFk: Proveedor
country: País country: País
description: Tipo de Entrada description: Tipo de Entrada
payDem: Plazo de pago payDem: P. pago
isBooked: C isBooked: C
isReceived: R isReceived: R
entryType: Tipo de entrada entryType: Tipo de entrada
@ -89,7 +89,7 @@ entry:
fiscalCode: Tipo de cuenta fiscalCode: Tipo de cuenta
daysAgo: Máximo 365 días daysAgo: Máximo 365 días
search: Buscar search: Buscar
searchInfo: Puedes buscar por nombre o alias de proveedor searchInfo: Puedes buscar por nombre, alias o cif de proveedor
btn: Precontabilizar btn: Precontabilizar
hasInvoice: Esta entrada ya tiene una f. recibida hasInvoice: Esta entrada ya tiene una f. recibida
success: Se ha precontabilizado correctamente success: Se ha precontabilizado correctamente

View File

@ -3,15 +3,11 @@ import { ref, computed, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { toDate } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { getTotal } from 'src/composables/getTotal'; import { getTotal } from 'src/composables/getTotal';
import CrudModel from 'src/components/CrudModel.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
import VnTable from 'src/components/VnTable/VnTable.vue';
const route = useRoute(); const route = useRoute();
const { notify } = useNotify(); const { notify } = useNotify();
@ -19,256 +15,187 @@ const { t } = useI18n();
const arrayData = useArrayData(); const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const currency = computed(() => invoiceIn.value?.currency?.code); const currency = computed(() => invoiceIn.value?.currency?.code);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const invoiceInFormRef = ref(); const invoiceInDueDayTableRef = ref();
const invoiceId = +route.params.id; const invoiceId = +route.params.id;
const filter = { where: { invoiceInFk: invoiceId } }; const filter = { where: { invoiceInFk: invoiceId } };
const areRows = ref(false); const areRows = ref(false);
const totalTaxableBase = ref(); const totalTaxableBase = ref();
const noMatch = computed(() => totalAmount.value != totalTaxableBase.value); const totalVat = ref();
const tableRows = computed(
() => invoiceInDueDayTableRef.value?.CrudModelRef?.formData || [],
);
const totalAmount = computed(() => getTotal(tableRows.value, 'amount'));
const noMatch = computed(
() =>
totalAmount.value != totalTaxableBase.value &&
totalAmount.value != totalVat.value,
);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'duedate', name: 'dueDated',
label: t('Date'), label: t('Date'),
field: (row) => toDate(row.dueDated),
sortable: true, sortable: true,
tabIndex: 1, tabIndex: 1,
align: 'left', align: 'left',
component: 'date',
isEditable: true,
create: true,
}, },
{ {
name: 'bank', name: 'bankFk',
label: t('Bank'), label: t('Bank'),
field: (row) => row.bankFk,
model: 'bankFk',
optionLabel: 'bank',
url: 'Accountings',
sortable: true, sortable: true,
tabIndex: 2, tabIndex: 2,
align: 'left', align: 'left',
component: 'select',
attrs: {
url: 'Accountings',
optionLabel: 'bank',
optionValue: 'id',
fields: ['id', 'bank'],
'emit-value': true,
},
format: ({ bank }) => bank?.bank,
isEditable: true,
create: true,
}, },
{ {
name: 'amount', name: 'amount',
label: t('Amount'), label: t('Amount'),
field: (row) => row.amount,
sortable: true, sortable: true,
tabIndex: 3, tabIndex: 3,
align: 'left', align: 'left',
component: 'number',
attrs: {
clearable: true,
'clear-icon': 'close',
},
isEditable: true,
create: true,
}, },
{ {
name: 'foreignvalue', name: 'foreignValue',
label: t('Foreign value'), label: t('Foreign value'),
field: (row) => row.foreignValue,
sortable: true, sortable: true,
tabIndex: 4, tabIndex: 4,
align: 'left', align: 'left',
component: 'number',
isEditable: isNotEuro(currency.value),
create: true,
}, },
]); ]);
const totalAmount = computed(() => getTotal(invoiceInFormRef.value.formData, 'amount'));
const isNotEuro = (code) => code != 'EUR'; const isNotEuro = (code) => code != 'EUR';
async function insert() { async function insert() {
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId }); await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
await invoiceInFormRef.value.reload(); await invoiceInDueDayTableRef.value.reload();
notify(t('globals.dataSaved'), 'positive'); notify(t('globals.dataSaved'), 'positive');
} }
async function setTaxableBase() { async function setTaxableBase() {
const ref = invoiceInDueDayTableRef.value;
if (ref) ref.create = null;
const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`); const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`);
totalTaxableBase.value = data.totalTaxableBase; totalTaxableBase.value = data.totalTaxableBase;
totalVat.value = data.totalVat;
} }
onBeforeMount(async () => await setTaxableBase()); onBeforeMount(async () => await setTaxableBase());
</script> </script>
<template> <template>
<CrudModel <div class="invoice-in-due-day">
<VnTable
v-if="invoiceIn" v-if="invoiceIn"
ref="invoiceInFormRef" ref="invoiceInDueDayTableRef"
data-key="InvoiceInDueDays" data-key="InvoiceInDueDays"
url="InvoiceInDueDays" url="InvoiceInDueDays"
save-url="InvoiceInDueDays/crud"
:filter="filter" :filter="filter"
:user-filter="{
include: { relation: 'bank', scope: { fields: ['id', 'bank'] } },
}"
auto-load auto-load
:data-required="{ invoiceInFk: invoiceId }" :data-required="{ invoiceInFk: invoiceId }"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
@on-fetch="(data) => (areRows = !!data.length)" @on-fetch="(data) => (areRows = !!data.length)"
@save-changes="setTaxableBase" @save-changes="setTaxableBase"
:columns="columns"
:is-editable="true"
:table="{ selection: 'multiple', 'row-key': '$index' }"
footer
:right-search="false"
:column-search="false"
:disable-option="{ card: true }"
class="q-pa-none"
> >
<template #body="{ rows }"> <template #column-footer-amount>
<QTable
v-model:selected="rowsSelected"
selection="multiple"
:columns
:rows
row-key="$index"
:grid="$q.screen.lt.sm"
>
<template #body-cell-duedate="{ row }">
<QTd>
<VnInputDate v-model="row.dueDated" />
</QTd>
</template>
<template #body-cell-bank="{ row, col }">
<QTd>
<VnSelect
v-model="row[col.model]"
:url="col.url"
:option-label="col.optionLabel"
:option-value="col.optionValue"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
`${scope.opt.id}: ${scope.opt.bank}`
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-amount="{ row }">
<QTd>
<VnInputNumber
v-model="row.amount"
:is-outlined="false"
clearable
clear-icon="close"
/>
</QTd>
</template>
<template #body-cell-foreignvalue="{ row }">
<QTd>
<VnInputNumber
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="row.foreignValue"
/>
</QTd>
</template>
<template #bottom-row>
<QTr class="bg">
<QTd />
<QTd />
<QTd />
<QTd>
<QChip <QChip
dense dense
:color="noMatch ? 'negative' : 'transparent'" :color="noMatch ? 'negative' : 'transparent'"
class="q-pa-xs" class="q-pa-xs"
:title=" :title="noMatch ? t('invoiceIn.noMatch', { totalTaxableBase }) : ''"
noMatch
? t('invoiceIn.noMatch', { totalTaxableBase })
: ''
"
> >
{{ toCurrency(totalAmount) }} {{ toCurrency(totalAmount) }}
</QChip> </QChip>
</QTd> </template>
<QTd> <template #column-footer-foreignValue>
<template v-if="isNotEuro(invoiceIn.currency.code)"> <template v-if="isNotEuro(currency)">
{{ {{
getTotal(rows, 'foreignValue', { getTotal(tableRows, 'foreignValue', {
currency: invoiceIn.currency.code, currency: currency,
}) })
}} }}
</template> </template>
</QTd>
</QTr>
</template> </template>
<template #item="props"> </VnTable>
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition"> <QPageSticky :offset="[20, 20]" style="z-index: 2">
<QCard>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList>
<QItem>
<VnInputDate
class="full-width"
:label="t('Date')"
v-model="props.row.dueDated"
/>
</QItem>
<QItem>
<VnSelect
:label="t('Bank')"
class="full-width"
v-model="props.row['bankFk']"
url="Accountings"
option-label="bank"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
`${scope.opt.id}: ${scope.opt.bank}`
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItem>
<QItem>
<VnInputNumber
:label="t('Amount')"
class="full-width"
v-model="props.row.amount"
/>
</QItem>
<QItem>
<VnInputNumber
:label="t('Foreign value')"
class="full-width"
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="props.row.foreignValue"
clearable
clear-icon="close"
/>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn <QBtn
color="primary"
icon="add"
v-shortcut="'+'"
size="lg"
round
@click=" @click="
() => { () => {
if (!areRows) insert(); if (!areRows) return insert();
else
invoiceInFormRef.insert({ invoiceInDueDayTableRef.create = {
amount: (totalTaxableBase - totalAmount).toFixed(2), urlCreate: 'InvoiceInDueDays',
onDataSaved: () => invoiceInDueDayTableRef.reload(),
title: t('Create due day'),
formInitialData: {
invoiceInFk: invoiceId, invoiceInFk: invoiceId,
}); dueDated: Date.vnNew(),
amount: (totalTaxableBase - totalAmount).toFixed(2),
},
};
invoiceInDueDayTableRef.showForm = true;
} }
" "
color="primary"
fab
icon="add"
v-shortcut="'+'"
data-cy="invoiceInDueDayAdd"
/> />
<QTooltip class="text-no-wrap">
{{ t('Create due day') }}
</QTooltip>
</QPageSticky> </QPageSticky>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.bg {
background-color: var(--vn-light-gray);
}
.q-chip { .q-chip {
color: var(--vn-text-color); color: var(--vn-text-color);
} }
.invoice-in-due-day {
display: flex;
flex-direction: column;
align-items: center;
}
:deep(.full-width) {
max-width: 900px;
}
</style> </style>
<i18n> <i18n>
es: es:
@ -276,4 +203,6 @@ onBeforeMount(async () => await setTaxableBase());
Bank: Caja Bank: Caja
Amount: Importe Amount: Importe
Foreign value: Divisa Foreign value: Divisa
invoiceIn.noMatch: El total {totalTaxableBase} no coincide con el importe
Create due day: Crear Vencimiento
</i18n> </i18n>

View File

@ -3,71 +3,83 @@ import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { getTotal } from 'src/composables/getTotal'; import { getTotal } from 'src/composables/getTotal';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const invoceInIntrastat = ref([]);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const countries = ref([]); const countries = ref([]);
const intrastats = ref([]); const intrastats = ref([]);
const invoiceInFormRef = ref(); const invoiceInIntrastatRef = ref();
const invoiceInId = computed(() => +route.params.id); const invoiceInId = computed(() => +route.params.id);
const filter = { where: { invoiceInFk: invoiceInId.value } }; const filter = computed(() => ({ where: { invoiceInFk: invoiceInId.value } }));
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'code', name: 'intrastatFk',
label: t('Code'), label: t('Code'),
field: (row) => row.intrastatFk, component: 'select',
columnFilter: false,
attrs: {
options: intrastats.value, options: intrastats.value,
model: 'intrastatFk',
optionValue: 'id', optionValue: 'id',
optionLabel: (row) => `${row.id}: ${row.description}`, optionLabel: (row) => `${row.id}: ${row.description}`,
sortable: true, 'data-cy': 'intrastat-code',
tabIndex: 1, sortBy: 'id',
align: 'left', fields: ['id', 'description'],
filterOptions: ['id', 'description']
},
format: (row, dashIfEmpty) => dashIfEmpty(getCode(row)),
create: true,
isEditable: true,
width: 'max-content',
}, },
{ {
name: 'amount', name: 'amount',
label: t('amount'), label: t('amount'),
field: (row) => row.amount, component: 'number',
sortable: true, create: true,
tabIndex: 2, isEditable: true,
align: 'left', columnFilter: false,
}, },
{ {
name: 'net', name: 'net',
label: t('net'), label: t('net'),
field: (row) => row.net, component: 'number',
sortable: true, create: true,
tabIndex: 3, isEditable: true,
align: 'left', columnFilter: false,
}, },
{ {
name: 'stems', name: 'stems',
label: t('stems'), label: t('stems'),
field: (row) => row.stems, component: 'number',
sortable: true, create: true,
tabIndex: 4, isEditable: true,
align: 'left', columnFilter: false,
}, },
{ {
name: 'country', name: 'countryFk',
label: t('country'), label: t('country'),
field: (row) => row.countryFk, component: 'select',
attrs: {
options: countries.value, options: countries.value,
model: 'countryFk',
optionValue: 'id',
optionLabel: 'code', optionLabel: 'code',
sortable: true, },
tabIndex: 5, create: true,
align: 'left', isEditable: true,
columnFilter: false,
}, },
]); ]);
const tableRows = computed(
() => invoiceInIntrastatRef.value?.CrudModelRef?.formData || [],
);
function getCode(row) {
const code = intrastats.value.find(({ id }) => id === row.intrastatFk);
return code ? `${code.id}: ${code.description}` : null;
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -82,165 +94,51 @@ const columns = computed(() => [
auto-load auto-load
@on-fetch="(data) => (intrastats = data)" @on-fetch="(data) => (intrastats = data)"
/> />
<div class="invoiceIn-intrastat"> <div class="invoice-in-intrastat">
<CrudModel <VnTable
ref="invoiceInFormRef" ref="invoiceInIntrastatRef"
data-key="InvoiceInIntrastats" data-key="InvoiceInIntrastats"
url="InvoiceInIntrastats" url="InvoiceInIntrastats"
save-url="InvoiceInIntrastats/crud"
search-url="InvoiceInIntrastats"
auto-load auto-load
:data-required="{ invoiceInFk: invoiceInId }" :filter
:filter="filter" :user-filter
:insert-on-load="true"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
@on-fetch="(data) => (invoceInIntrastat = data)" :columns
:is-editable="true"
:table="{ selection: 'multiple', 'row-key': '$index' }"
:create="{
urlCreate: 'InvoiceInIntrastats',
title: t('Create Intrastat'),
formInitialData: { invoiceInFk: invoiceInId },
onDataSaved: () => invoiceInIntrastatRef.reload(),
}"
footer
data-cy="invoice-in-intrastat-table"
:right-search="false"
:disable-option="{ card: true }"
> >
<template #body="{ rows }"> <template #column-footer-amount>
<QTable {{ getTotal(tableRows, 'amount', { currency: 'default' }) }}
v-model:selected="rowsSelected"
selection="multiple"
:columns="columns"
:rows="rows"
row-key="$index"
:grid="$q.screen.lt.sm"
>
<template #body-cell="{ row, col }">
<QTd>
<VnInputNumber v-model="row[col.name]" />
</QTd>
</template> </template>
<template #body-cell-code="{ row, col }"> <template #column-footer-net>
<QTd> {{ getTotal(tableRows, 'net') }}
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'description']"
data-cy="intrastat-code"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.description}` }}
</QItem>
</template> </template>
</VnSelect> <template #column-footer-stems>
</QTd> {{ getTotal(tableRows, 'stems', { decimalPlaces: 0 }) }}
</template> </template>
<template #body-cell-country="{ row, col }"> </VnTable>
<QTd>
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
/>
</QTd>
</template>
<template #bottom-row>
<QTr class="bg">
<QTd />
<QTd />
<QTd>
{{ getTotal(rows, 'amount', { currency: 'default' }) }}
</QTd>
<QTd>
{{ getTotal(rows, 'net') }}
</QTd>
<QTd>
{{ getTotal(rows, 'stems', { decimalPlaces: 0 }) }}
</QTd>
<QTd />
</QTr>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList>
<QItem>
<VnSelect
:label="t('Code')"
class="full-width"
v-model="props.row['intrastatFk']"
:options="intrastats"
option-value="id"
:option-label="
(row) => `${row.id}:${row.description}`
"
:filter-options="['id', 'description']"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{
`${scope.opt.id}: ${scope.opt.description}`
}}
</QItem>
</template>
</VnSelect>
</QItem>
<QItem
v-for="(value, index) of [
'amount',
'net',
'stems',
]"
:key="index"
>
<VnInputNumber
:label="t(value)"
class="full-width"
v-model="props.row[value]"
clearable
clear-icon="close"
/>
</QItem>
<QItem>
<VnSelect
:label="t('country')"
class="full-width"
v-model="props.row['countryFk']"
:options="countries"
option-value="id"
option-label="code"
/>
</QItem>
</QList>
</QCard>
</div> </div>
</template>
</QTable>
</template>
</CrudModel>
</div>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
color="primary"
icon="add"
v-shortcut="'+'"
size="lg"
round
@click="invoiceInFormRef.insert()"
/>
</QPageSticky>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.invoiceIn-intrastat { .invoice-in-intrastat {
> .q-card {
.vn-label-value {
display: flex; display: flex;
gap: 1em; flex-direction: column;
align-items: center;
.label { }
flex: 1; :deep(.full-width) {
} max-width: 900px;
.value {
flex: 0.5;
}
}
}
} }
</style> </style>
<i18n> <i18n>
@ -258,4 +156,5 @@ const columns = computed(() => [
Total amount: Total importe Total amount: Total importe
Total net: Total neto Total net: Total neto
Total stems: Total tallos Total stems: Total tallos
Create Intrastat: Crear Intrastat
</i18n> </i18n>

View File

@ -363,6 +363,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd> <QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd>
<QTd></QTd> <QTd></QTd>
<QTd></QTd> <QTd></QTd>
<QTd></QTd>
<QTd>{{ toCurrency(getTotalTax(entity.invoiceInTax)) }}</QTd> <QTd>{{ toCurrency(getTotalTax(entity.invoiceInTax)) }}</QTd>
<QTd>{{ <QTd>{{
entity.totals.totalTaxableBaseForeignValue && entity.totals.totalTaxableBaseForeignValue &&

View File

@ -1,123 +1,178 @@
<script setup> <script setup>
import { ref, computed, nextTick } from 'vue'; import { ref, computed, markRaw, useTemplateRef, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { getTotal } from 'src/composables/getTotal'; import { getTotal } from 'src/composables/getTotal';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import CrudModel from 'src/components/CrudModel.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue';
import { getExchange } from 'src/composables/getExchange'; import { getExchange } from 'src/composables/getExchange';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnSelectExpense from 'src/components/common/VnSelectExpense.vue';
import axios from 'axios';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
const { t } = useI18n(); const { t } = useI18n();
const arrayData = useArrayData(); const arrayData = useArrayData();
const expensesArrayData = useArrayData('expenses', { url: 'Expenses' });
const route = useRoute(); const route = useRoute();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const currency = computed(() => invoiceIn.value?.currency?.code); const currency = computed(() => invoiceIn.value?.currency?.code);
const expenses = ref([]); const expenses = computed(() => expensesArrayData.store.data);
const sageTaxTypes = ref([]); const sageTaxTypes = ref([]);
const sageTransactionTypes = ref([]); const sageTransactionTypes = ref([]);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const invoiceInFormRef = ref(); const invoiceInVatTableRef = useTemplateRef('invoiceInVatTableRef');
defineProps({ actionIcon: { type: String, default: 'add' } });
defineProps({ function taxRate(invoiceInTax) {
actionIcon: { const sageTaxTypeId = invoiceInTax.taxTypeSageFk;
type: String, const taxRateSelection = sageTaxTypes.value.find(
default: 'add', (transaction) => transaction.id == sageTaxTypeId,
}, );
}); const taxTypeSage = taxRateSelection?.rate ?? 0;
const taxableBase = invoiceInTax?.taxableBase ?? 0;
return (taxTypeSage / 100) * taxableBase;
}
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'expense', name: 'expenseFk',
label: t('Expense'), label: t('Expense'),
field: (row) => row.expenseFk, component: markRaw(VnSelectExpense),
options: expenses.value, format: (row, dashIfEmpty) => {
model: 'expenseFk', const expense = expenses.value?.find((e) => e.id === row.expenseFk);
optionValue: 'id', return expense ? `${expense.id}: ${expense.name}` : dashIfEmpty(null);
optionLabel: (row) => `${row.id}: ${row.name}`, },
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
width: 'max-content',
cellEvent: {
keydown: async (evt, row) => {
if (evt.key !== 'Tab') return;
const val = evt.target.value;
if (!val || isNaN(val)) return;
row.expenseFk = expenses.value.find(
(e) => e.id === useAccountShortToStandard(val),
)?.id;
},
},
}, },
{ {
name: 'taxablebase', name: 'taxableBase',
label: t('Taxable base'), label: t('Taxable base'),
field: (row) => row.taxableBase, component: 'number',
model: 'taxableBase', attrs: {
clearable: true,
'clear-icon': 'close',
},
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
}, },
{ {
name: 'isDeductible', name: 'isDeductible',
label: t('invoiceIn.isDeductible'), label: t('invoiceIn.isDeductible'),
field: (row) => row.isDeductible, component: 'checkbox',
model: 'isDeductible',
align: 'center', align: 'center',
isEditable: true,
create: true,
createAttrs: {
defaultValue: true,
},
}, },
{ {
name: 'sageiva', name: 'taxTypeSageFk',
label: t('Sage iva'), label: t('Sage iva'),
field: (row) => row.taxTypeSageFk, component: 'select',
attrs: {
options: sageTaxTypes.value, options: sageTaxTypes.value,
model: 'taxTypeSageFk',
optionValue: 'id', optionValue: 'id',
optionLabel: (row) => `${row.id}: ${row.vat}`, optionLabel: (row) => `${row.id}: ${row.vat}`,
filterOptions: ['id', 'vat'],
'data-cy': 'vat-sageiva',
},
format: ({ taxTypeSageFk }, dashIfEmpty) => {
const taxType = sageTaxTypes.value.find((t) => t.id === taxTypeSageFk);
return taxType ? `${taxType.id}: ${taxType.vat}` : dashIfEmpty(taxTypeSageFk);
},
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
width: 'max-content',
}, },
{ {
name: 'sagetransaction', name: 'transactionTypeSageFk',
label: t('Sage transaction'), label: t('Sage transaction'),
field: (row) => row.transactionTypeSageFk, component: 'select',
attrs: {
options: sageTransactionTypes.value, options: sageTransactionTypes.value,
model: 'transactionTypeSageFk',
optionValue: 'id', optionValue: 'id',
optionLabel: (row) => `${row.id}: ${row.transaction}`, optionLabel: (row) => `${row.id}: ${row.transaction}`,
filterOptions: ['id', 'transaction'],
},
format: ({ transactionTypeSageFk }, dashIfEmpty) => {
const transType = sageTransactionTypes.value.find(
(t) => t.id === transactionTypeSageFk,
);
return transType
? `${transType.id}: ${transType.transaction}`
: dashIfEmpty(transactionTypeSageFk);
},
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
width: 'max-content',
}, },
{ {
name: 'rate', name: 'rate',
label: t('Rate'), label: t('Rate'),
sortable: true, sortable: false,
field: (row) => taxRate(row, row.taxTypeSageFk), format: (row) => taxRate(row).toFixed(2),
align: 'left', align: 'left',
}, },
{ {
name: 'foreignvalue', name: 'foreignValue',
label: t('Foreign value'), label: t('Foreign value'),
component: 'number',
sortable: true, sortable: true,
field: (row) => row.foreignValue,
align: 'left', align: 'left',
create: true,
isEditable: isNotEuro(currency.value),
format: (row, dashIfEmpty) => dashIfEmpty(row.foreignValue),
cellEvent: {
'update:modelValue': async (value, oldValue, row) =>
await handleForeignValueUpdate(value, row),
},
}, },
{ {
name: 'total', name: 'total',
label: 'Total', label: t('Total'),
align: 'left', align: 'left',
format: (row) => (Number(row.taxableBase || 0) + Number(taxRate(row))).toFixed(2),
}, },
]); ]);
const tableRows = computed(
() => invoiceInVatTableRef.value?.CrudModelRef?.formData || [],
);
const taxableBaseTotal = computed(() => { const taxableBaseTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, 'taxableBase'); return getTotal(tableRows.value, 'taxableBase');
}); });
const taxRateTotal = computed(() => { const taxRateTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, null, { return tableRows.value.reduce((sum, row) => sum + Number(taxRate(row)), 0);
cb: taxRate,
});
}); });
const combinedTotal = computed(() => { const combinedTotal = computed(() => {
return +taxableBaseTotal.value + +taxRateTotal.value; return +taxableBaseTotal.value + +taxRateTotal.value;
}); });
const filter = { const filter = computed(() => ({
fields: [ fields: [
'id', 'id',
'invoiceInFk', 'invoiceInFk',
@ -131,389 +186,75 @@ const filter = {
where: { where: {
invoiceInFk: route.params.id, invoiceInFk: route.params.id,
}, },
}; }));
onBeforeMount(async () => await expensesArrayData.fetch({}));
const isNotEuro = (code) => code != 'EUR'; const isNotEuro = (code) => code != 'EUR';
function taxRate(invoiceInTax) { async function handleForeignValueUpdate(val, row) {
const sageTaxTypeId = invoiceInTax.taxTypeSageFk; if (!isNotEuro(currency.value)) return;
const taxRateSelection = sageTaxTypes.value.find( row.taxableBase = await getExchange(
(transaction) => transaction.id == sageTaxTypeId, val,
invoiceIn.value?.currencyFk,
invoiceIn.value?.issued,
); );
const taxTypeSage = taxRateSelection?.rate ?? 0;
const taxableBase = invoiceInTax?.taxableBase ?? 0;
return ((taxTypeSage / 100) * taxableBase).toFixed(2);
}
function autocompleteExpense(evt, row, col, ref) {
const val = evt.target.value;
if (!val) return;
const param = isNaN(val) ? row[col.model] : val;
const lookup = expenses.value.find(
({ id }) => id == useAccountShortToStandard(param),
);
ref.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
}
function setCursor(ref) {
nextTick(() => {
const select = ref.vnSelectDialogRef
? ref.vnSelectDialogRef.vnSelectRef
: ref.vnSelectRef;
select.$el.querySelector('input').setSelectionRange(0, 0);
});
} }
</script> </script>
<template> <template>
<FetchData
ref="expensesRef"
url="Expenses"
auto-load
@on-fetch="(data) => (expenses = data)"
/>
<FetchData url="SageTaxTypes" auto-load @on-fetch="(data) => (sageTaxTypes = data)" /> <FetchData url="SageTaxTypes" auto-load @on-fetch="(data) => (sageTaxTypes = data)" />
<FetchData <FetchData
url="sageTransactionTypes" url="sageTransactionTypes"
auto-load auto-load
@on-fetch="(data) => (sageTransactionTypes = data)" @on-fetch="(data) => (sageTransactionTypes = data)"
/> />
<CrudModel <VnTable
ref="invoiceInFormRef"
v-if="invoiceIn" v-if="invoiceIn"
ref="invoiceInVatTableRef"
data-key="InvoiceInTaxes" data-key="InvoiceInTaxes"
url="InvoiceInTaxes" url="InvoiceInTaxes"
save-url="InvoiceInTaxes/crud"
:filter="filter" :filter="filter"
:data-required="{ invoiceInFk: $route.params.id }" :data-required="{ invoiceInFk: $route.params.id }"
:insert-on-load="true"
auto-load auto-load
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:columns="columns"
:is-editable="true"
:table="{ selection: 'multiple', 'row-key': '$index' }"
footer
:right-search="false"
:column-search="false"
:disable-option="{ card: true }"
class="q-pa-none"
:create="{
urlCreate: 'InvoiceInTaxes',
title: t('Add tax'),
formInitialData: { invoiceInFk: $route.params.id, isDeductible: true },
onDataSaved: () => invoiceInVatTableRef.reload(),
}"
:go-to="`/invoice-in/${$route.params.id}/due-day`" :go-to="`/invoice-in/${$route.params.id}/due-day`"
> >
<template #body="{ rows }"> <template #column-footer-taxableBase>
<QTable
v-model:selected="rowsSelected"
selection="multiple"
:columns="columns"
:rows="rows"
row-key="$index"
:grid="$q.screen.lt.sm"
>
<template #body-cell-expense="{ row, col }">
<QTd>
<VnSelectDialog
:ref="`expenseRef-${row.$index}`"
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
:acls="[
{ model: 'Expense', props: '*', accessType: 'WRITE' },
]"
@keydown.tab.prevent="
autocompleteExpense(
$event,
row,
col,
$refs[`expenseRef-${row.$index}`],
)
"
@update:model-value="
setCursor($refs[`expenseRef-${row.$index}`])
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem>
</template>
<template #form>
<CreateNewExpenseForm
@on-data-saved="$refs.expensesRef.fetch()"
/>
</template>
</VnSelectDialog>
</QTd>
</template>
<template #body-cell-isDeductible="{ row }">
<QTd align="center">
<QCheckbox
v-model="row.isDeductible"
data-cy="isDeductible_checkbox"
/>
</QTd>
</template>
<template #body-cell-taxablebase="{ row }">
<QTd shrink>
<VnInputNumber
clear-icon="close"
v-model="row.taxableBase"
clearable
/>
</QTd>
</template>
<template #body-cell-sageiva="{ row, col }">
<QTd>
<VnSelect
:ref="`sageivaRef-${row.$index}`"
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'vat']"
data-cy="vat-sageiva"
@update:model-value="
setCursor($refs[`sageivaRef-${row.$index}`])
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt.vat }}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-sagetransaction="{ row, col }">
<QTd>
<VnSelect
:ref="`sagetransactionRef-${row.$index}`"
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'transaction']"
@update:model-value="
setCursor($refs[`sagetransactionRef-${row.$index}`])
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
scope.opt.transaction
}}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-foreignvalue="{ row }">
<QTd shrink>
<VnInputNumber
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="row.foreignValue"
@update:model-value="
async (val) => {
if (!isNotEuro(currency)) return;
row.taxableBase = await getExchange(
val,
row.currencyFk,
invoiceIn.issued,
);
}
"
/>
</QTd>
</template>
<template #bottom-row>
<QTr class="bg">
<QTd />
<QTd />
<QTd>
{{ toCurrency(taxableBaseTotal) }} {{ toCurrency(taxableBaseTotal) }}
</QTd> </template>
<QTd /> <template #column-footer-rate>
<QTd />
<QTd />
<QTd>
{{ toCurrency(taxRateTotal) }} {{ toCurrency(taxRateTotal) }}
</QTd> </template>
<QTd /> <template #column-footer-total>
<QTd>
{{ toCurrency(combinedTotal) }} {{ toCurrency(combinedTotal) }}
</QTd>
</QTr>
</template> </template>
</VnTable>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard bordered flat class="q-my-xs">
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList>
<QItem>
<VnSelectDialog
:label="t('Expense')"
class="full-width"
v-model="props.row['expenseFk']"
:options="expenses"
option-value="id"
:option-label="(row) => `${row.id}:${row.name}`"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem>
</template>
<template #form>
<CreateNewExpenseForm />
</template>
</VnSelectDialog>
</QItem>
<QItem>
<VnInputNumber
:label="t('Taxable base')"
:class="{
'no-pointer-events': isNotEuro(currency),
}"
class="full-width"
:disable="isNotEuro(currency)"
clear-icon="close"
v-model="props.row.taxableBase"
clearable
/>
</QItem>
<QItem>
<VnSelect
:label="t('Sage iva')"
class="full-width"
v-model="props.row['taxTypeSageFk']"
:options="sageTaxTypes"
option-value="id"
:option-label="(row) => `${row.id}:${row.vat}`"
:filter-options="['id', 'vat']"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
scope.opt.vat
}}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItem>
<QItem>
<VnSelect
class="full-width"
v-model="props.row['transactionTypeSageFk']"
:options="sageTransactionTypes"
option-value="id"
:option-label="
(row) => `${row.id}:${row.transaction}`
"
:filter-options="['id', 'transaction']"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
scope.opt.transaction
}}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItem>
<QItem>
{{ toCurrency(taxRate(props.row), currency) }}
</QItem>
<QItem>
<VnInputNumber
:label="t('Foreign value')"
class="full-width"
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="props.row.foreignValue"
/>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
color="primary"
icon="add"
size="lg"
v-shortcut="'+'"
round
@click="invoiceInFormRef.insert()"
>
<QTooltip>{{ t('Add tax') }}</QTooltip>
</QBtn>
</QPageSticky>
</template> </template>
<style lang="scss" scoped>
.bg {
background-color: var(--vn-light-gray);
}
@media (max-width: $breakpoint-xs) {
.q-dialog {
.q-card {
&__section:not(:first-child) {
.q-item {
flex-direction: column;
.q-checkbox {
margin-top: 2rem;
}
}
}
}
}
}
.q-item {
min-height: 0;
}
.default-icon {
cursor: pointer;
border-radius: 50px;
background-color: $primary;
}
</style>
<i18n> <i18n>
es: es:
Expense: Gasto Expense: Gasto
Create a new expense: Crear nuevo gasto Create a new expense: Crear nuevo gasto
Add tax: Crear gasto Add tax: Añadir Gasto/IVA # Changed label slightly
Taxable base: Base imp. Taxable base: Base imp.
Sage tax: Sage iva Sage iva: Sage iva # Kept original label
Sage transaction: Sage transacción Sage transaction: Sage transacción
Rate: Tasa Rate: Cuota # Changed label
Foreign value: Divisa Foreign value: Divisa
Total: Total
invoiceIn.isDeductible: Deducible
</i18n> </i18n>

View File

@ -41,7 +41,7 @@ async function checkToBook(id) {
params: { params: {
where: JSON.stringify({ where: JSON.stringify({
invoiceInFk: id, invoiceInFk: id,
dueDated: { gte: Date.vnNew() }, dueDated: { lte: Date.vnNew() },
}), }),
}, },
}) })

View File

@ -69,4 +69,5 @@ invoiceIn:
isBooked: Is booked isBooked: Is booked
account: Ledger account account: Ledger account
correctingFk: Rectificative correctingFk: Rectificative
issued: Issued
noMatch: No match with the vat({totalTaxableBase}) noMatch: No match with the vat({totalTaxableBase})

View File

@ -67,4 +67,5 @@ invoiceIn:
isBooked: Contabilizada isBooked: Contabilizada
account: Cuenta contable account: Cuenta contable
correctingFk: Rectificativa correctingFk: Rectificativa
issued: Fecha de emisión
noMatch: No cuadra con el iva({totalTaxableBase}) noMatch: No cuadra con el iva({totalTaxableBase})

View File

@ -13,6 +13,7 @@ export default {
'daysInForward', 'daysInForward',
'availabled', 'availabled',
'awbFk', 'awbFk',
'isOrdered',
'isDelivered', 'isDelivered',
'isReceived', 'isReceived',
], ],

View File

@ -1,33 +1,38 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
describe('InvoiceInIntrastat', () => { describe('InvoiceInIntrastat', () => {
const firstRow = 'tbody > :nth-child(1)'; const firstInstrastat = 'td[data-col-field="intrastatFk"][data-row-index="0"]';
const thirdRow = 'tbody > :nth-child(3)'; const firstAmount = 'td[data-col-field="amount"][data-row-index="0"]';
const codes = `[data-cy="intrastat-code"]`; const intrastat = 'Plantas vivas: Esqueje/injerto, Vid';
const firstRowAmount = `${firstRow} > :nth-child(3)`;
beforeEach(() => { before(() => {
cy.login('administrative'); cy.login('administrative');
cy.visit(`/#/invoice-in/1/intrastat`); cy.visit(`/#/invoice-in/1/intrastat`);
}); });
it('should edit the first line', () => { it('should edit the first line', () => {
cy.selectOption(`${firstRow} ${codes}`, 'Plantas vivas: Esqueje/injerto, Vid'); cy.get(firstInstrastat).click().type(`{selectall}{backspace}${intrastat}{enter}`);
cy.get(firstAmount).click().type('10{selectall}{backspace}20{enter}');
cy.saveCard(); cy.saveCard();
cy.get(codes).eq(0).contains('6021010: Plantas vivas: Esqueje/injerto, Vid'); cy.get(firstInstrastat).should('have.text', `6021010: ${intrastat}`);
}); });
it('should add a new row', () => { it('should add a new row', () => {
cy.addRow(); cy.dataCy('vnTableCreateBtn').click();
cy.fillRow(thirdRow, [ cy.fillInForm(
false, {
'Plantas vivas: Esqueje/injerto, Vid', 'intrastat-code': {
30, val: 'Plantas vivas: Esqueje/injerto, Vid',
10, type: 'select',
5, },
'FR', Amount_input: '30',
]); Net_input: '10',
cy.saveCard(); Stems_input: '5',
cy.checkNotification('Data saved'); Country_select: { val: 'FR', type: 'select' },
},
{ attr: 'data-cy' },
);
cy.dataCy('FormModelPopup_save').click();
cy.get('.q-notification__message').should('have.text', 'Data created');
}); });
it('should remove the first line', () => { it('should remove the first line', () => {

View File

@ -1,37 +1,41 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
describe('InvoiceInVat', () => { describe('InvoiceInVat', () => {
const thirdRow = 'tbody > :nth-child(3)';
const firstLineVat = 'tbody > :nth-child(1) ';
const vats = '[data-cy="vat-sageiva"]';
const dialogInputs = '.q-dialog label input'; const dialogInputs = '.q-dialog label input';
const addBtn = 'tbody tr:nth-child(1) td:nth-child(2) .--add-icon';
const randomInt = Math.floor(Math.random() * 100); const randomInt = Math.floor(Math.random() * 100);
const firstTaxType = 'td[data-col-field="taxTypeSageFk"][data-row-index="0"]';
const firstExpense = 'td[data-col-field="expenseFk"][data-row-index="0"]';
const firstDeductible = 'td[data-col-field="isDeductible"][data-row-index="0"]';
const taxType = 'H.P. IVA 21% CEE';
beforeEach(() => { before(() => {
cy.login('administrative'); cy.login('administrative');
cy.visit(`/#/invoice-in/1/vat`); cy.visit(`/#/invoice-in/1/vat`);
}); });
it('should edit the sage iva', () => { it('should edit the sage iva', () => {
cy.selectOption(`${firstLineVat} ${vats}`, 'H.P. IVA 21% CEE'); cy.get(firstTaxType).click().type(`{selectall}{backspace}${taxType}{enter}`);
cy.saveCard(); cy.saveCard();
cy.get(vats).eq(0).contains('8: H.P. IVA 21% CEE'); cy.get(firstTaxType).should('have.text', `8: ${taxType}`);
}); });
it('should mark the line as deductible VAT', () => { it('should mark the line as deductible VAT', () => {
cy.get(`${firstLineVat} [data-cy="isDeductible_checkbox"]`).click(); cy.get(firstDeductible).click().click();
cy.saveCard();
cy.get(`${firstLineVat} [data-cy="isDeductible_checkbox"]`).click();
cy.saveCard(); cy.saveCard();
cy.get('.q-notification__message').should('have.text', 'Data saved');
}); });
it('should add a new row', () => { it('should add a new row', () => {
cy.addRow(); cy.dataCy('vnTableCreateBtn').click();
cy.fillRow(thirdRow, [true, 2000000001, 30, 'H.P. IVA 10']); cy.fillInForm(
cy.saveCard(); {
cy.get('.q-notification__message').should('have.text', 'Data saved'); Expense_select: { val: '2000000001', type: 'select' },
'Taxable base_input': '30',
'vat-sageiva': { val: 'H.P. IVA 10', type: 'select' },
},
{ attr: 'data-cy' },
);
cy.dataCy('FormModelPopup_save').click();
cy.get('.q-notification__message').should('have.text', 'Data created');
}); });
it('should remove the first line', () => { it('should remove the first line', () => {
@ -39,10 +43,10 @@ describe('InvoiceInVat', () => {
}); });
it('should correctly handle expense addition', () => { it('should correctly handle expense addition', () => {
cy.get(addBtn).click(); cy.get(firstExpense).click();
cy.get('.--add-icon').click();
cy.get(dialogInputs).eq(0).type(randomInt); cy.get(dialogInputs).eq(0).type(randomInt);
cy.get(dialogInputs).eq(1).type('This is a dummy expense'); cy.get(dialogInputs).eq(1).type('This is a dummy expense');
cy.get('[data-cy="FormModelPopup_save"]').click(); cy.get('[data-cy="FormModelPopup_save"]').click();
cy.get('.q-notification__message').should('have.text', 'Data created'); cy.get('.q-notification__message').should('have.text', 'Data created');
}); });