refs #5835 migrateInvoiceIn #110
|
@ -8,7 +8,7 @@ module.exports = defineConfig({
|
||||||
supportFile: 'test/cypress/support/index.js',
|
supportFile: 'test/cypress/support/index.js',
|
||||||
videosFolder: 'test/cypress/videos',
|
videosFolder: 'test/cypress/videos',
|
||||||
video: false,
|
video: false,
|
||||||
specPattern: 'test/cypress/integration/*.spec.js',
|
specPattern: 'test/cypress/integration/**/*.spec.js',
|
||||||
experimentalRunAllSpecs: true,
|
experimentalRunAllSpecs: true,
|
||||||
component: {
|
component: {
|
||||||
componentFolder: 'src',
|
componentFolder: 'src',
|
||||||
|
|
|
@ -136,6 +136,10 @@ async function saveChanges(data) {
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
emit('saveChanges', data);
|
emit('saveChanges', data);
|
||||||
|
quasar.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('globals.dataSaved'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insert() {
|
async function insert() {
|
||||||
|
|
|
@ -24,12 +24,13 @@ const address = ref(props.data.address);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
|
||||||
async function confirm() {
|
async function confirm() {
|
||||||
const response = { address };
|
const response = { address: address.value };
|
||||||
|
|
||||||
if (props.promise) {
|
if (props.promise) {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
const { address: _address, ...restData } = props.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Object.assign(response, props.data);
|
Object.assign(response, restData);
|
||||||
await props.promise(response);
|
await props.promise(response);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|
|
@ -15,6 +15,10 @@ const $props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
filterOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
isClearable: {
|
isClearable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -32,18 +36,22 @@ function setOptions(data) {
|
||||||
setOptions(options.value);
|
setOptions(options.value);
|
||||||
|
|
||||||
const filter = (val, options) => {
|
const filter = (val, options) => {
|
||||||
const search = val.toLowerCase();
|
const search = val.toString().toLowerCase();
|
||||||
|
|
||||||
if (val === '') return options;
|
if (!search) return options;
|
||||||
|
|
||||||
return options.filter((row) => {
|
return options.filter((row) => {
|
||||||
|
if ($props.filterOptions.length) {
|
||||||
|
return $props.filterOptions.some((prop) => {
|
||||||
|
const propValue = String(row[prop]).toLowerCase();
|
||||||
|
return propValue.includes(search);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const id = row.id;
|
const id = row.id;
|
||||||
const name = row[$props.optionLabel].toLowerCase();
|
const optionLabel = String(row[$props.optionLabel]).toLowerCase();
|
||||||
|
|
||||||
const idMatches = id == search;
|
return id == search || optionLabel.includes(search);
|
||||||
const nameMatches = name.indexOf(search) > -1;
|
|
||||||
|
|
||||||
return idMatches || nameMatches;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,6 +93,7 @@ const value = computed({
|
||||||
map-options
|
map-options
|
||||||
use-input
|
use-input
|
||||||
@filter="filterHandler"
|
@filter="filterHandler"
|
||||||
|
hide-selected
|
||||||
fill-input
|
fill-input
|
||||||
ref="vnSelectRef"
|
ref="vnSelectRef"
|
||||||
>
|
>
|
||||||
|
@ -92,7 +101,7 @@ const value = computed({
|
||||||
<QIcon name="close" @click.stop="value = null" class="cursor-pointer" />
|
<QIcon name="close" @click.stop="value = null" class="cursor-pointer" />
|
||||||
</template>
|
</template>
|
||||||
<template v-for="(_, slotName) in $slots" #[slotName]="slotData">
|
<template v-for="(_, slotName) in $slots" #[slotName]="slotData">
|
||||||
<slot :name="slotName" v-bind="slotData" />
|
<slot :name="slotName" v-bind="slotData ?? {}" />
|
||||||
</template>
|
</template>
|
||||||
</QSelect>
|
</QSelect>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -40,6 +40,10 @@ const quasar = useQuasar();
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const entity = computed(() => useArrayData($props.dataKey).store.data);
|
const entity = computed(() => useArrayData($props.dataKey).store.data);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getData,
|
||||||
|
});
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getData();
|
await getData();
|
||||||
watch(
|
watch(
|
||||||
|
|
|
@ -82,11 +82,13 @@ watch(props, async () => {
|
||||||
.summaryBody {
|
.summaryBody {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
> .q-card.vn-one {
|
> .q-card.vn-one {
|
||||||
|
width: 350px;
|
||||||
jorgep marked this conversation as resolved
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
> .q-card.vn-two {
|
> .q-card.vn-two {
|
||||||
|
@ -123,7 +125,6 @@ watch(props, async () => {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
|
|
|
@ -4,9 +4,10 @@ import { dashIfEmpty } from 'src/filters';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
label: { type: String, default: null },
|
label: { type: String, default: null },
|
||||||
value: { type: [Number, String, Boolean], default: null },
|
value: {
|
||||||
titleLabel: { type: String, default: null },
|
type: [String, Boolean],
|
||||||
titleValue: { type: [Number, String, Boolean], default: null },
|
default: null,
|
||||||
|
},
|
||||||
info: { type: String, default: null },
|
info: { type: String, default: null },
|
||||||
dash: { type: Boolean, default: true },
|
dash: { type: Boolean, default: true },
|
||||||
});
|
});
|
||||||
|
@ -16,7 +17,7 @@ const isBooleanValue = computed(() => typeof $props.value === 'boolean');
|
||||||
<div class="vn-label-value">
|
<div class="vn-label-value">
|
||||||
<div v-if="$props.label || $slots.label" class="label">
|
<div v-if="$props.label || $slots.label" class="label">
|
||||||
<slot name="label">
|
<slot name="label">
|
||||||
<span :title="$props.titleLabel ?? $props.label">{{ $props.label }}</span>
|
<span>{{ $props.label }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
|
|
|
@ -76,9 +76,9 @@ async function search() {
|
||||||
const module = route.matched[1];
|
const module = route.matched[1];
|
||||||
if (rows.length === 1) {
|
if (rows.length === 1) {
|
||||||
const [firstRow] = rows;
|
const [firstRow] = rows;
|
||||||
await router.push({ path: `/${module.name}/${firstRow.id}` });
|
await router.push({ path: `${module.path}/${firstRow.id}` });
|
||||||
} else if (route.matched.length > 3) {
|
} else if (route.matched.length > 3) {
|
||||||
await router.push({ path: `/${module.name}` });
|
await router.push({ path: `/${module.path}` });
|
||||||
arrayData.updateStateParams();
|
arrayData.updateStateParams();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { useSession } from 'src/composables/useSession';
|
||||||
|
import { getUrl } from './getUrl';
|
||||||
|
|
||||||
|
const session = useSession();
|
||||||
|
const token = session.getToken();
|
||||||
|
|
||||||
|
export async function downloadFile(dmsId) {
|
||||||
|
let appUrl = await getUrl('', 'lilium');
|
||||||
jorgep marked this conversation as resolved
jgallego
commented
confirma con @alexm que lilum en el código a piñón es correcto confirma con @alexm que lilum en el código a piñón es correcto
alexm
commented
Lo veo bien. Si se llega a repetir mucho, se podria llegar a hacer getUrlLilium o alho Lo veo bien. Si se llega a repetir mucho, se podria llegar a hacer getUrlLilium o alho
|
|||||||
|
appUrl = appUrl.replace('/#/', '');
|
||||||
|
window.open(`${appUrl}/api/dms/${dmsId}/downloadFile?access_token=${token}`);
|
||||||
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export async function getUrl(route, appName = 'salix') {
|
export async function getUrl(route, app = 'salix') {
|
||||||
const filter = {
|
let url;
|
||||||
where: { and: [{ appName: appName }, { environment: process.env.NODE_ENV }] },
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await axios.get('Urls/findOne', { params: { filter } });
|
await axios.get('Urls/getUrl', { params: { app } }).then((res) => {
|
||||||
const url = data.url;
|
url = res.data + route;
|
||||||
return route ? url + route : url;
|
});
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ body.body--light {
|
||||||
--vn-gray: #f5f5f5;
|
--vn-gray: #f5f5f5;
|
||||||
--vn-label: #5f5f5f;
|
--vn-label: #5f5f5f;
|
||||||
--vn-dark: white;
|
--vn-dark: white;
|
||||||
|
--vn-light-gray: #e7e3e3;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.body--dark {
|
body.body--dark {
|
||||||
|
@ -40,6 +41,7 @@ body.body--dark {
|
||||||
--vn-gray: #313131;
|
--vn-gray: #313131;
|
||||||
--vn-label: #a8a8a8;
|
--vn-label: #a8a8a8;
|
||||||
--vn-dark: #292929;
|
--vn-dark: #292929;
|
||||||
|
--vn-light-gray: #424242;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-vn-dark {
|
.bg-vn-dark {
|
||||||
|
|
|
@ -9,13 +9,10 @@ export default function (value, symbol = 'EUR', fractionSize = 2) {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: symbol,
|
currency: symbol,
|
||||||
minimumFractionDigits: fractionSize,
|
minimumFractionDigits: fractionSize,
|
||||||
maximumFractionDigits: fractionSize
|
maximumFractionDigits: fractionSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
const lang = locale.value == 'es' ? 'de' : locale.value;
|
const lang = locale.value == 'es' ? 'de' : locale.value;
|
||||||
|
|
||||||
return new Intl.NumberFormat(lang, options)
|
return new Intl.NumberFormat(lang, options).format(value);
|
||||||
.format(value);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -357,7 +357,7 @@ export default {
|
||||||
},
|
},
|
||||||
invoiceOut: {
|
invoiceOut: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
invoiceOuts: 'Invoices Out',
|
invoiceOuts: 'Create invoice',
|
||||||
list: 'List',
|
list: 'List',
|
||||||
createInvoiceOut: 'Create invoice out',
|
createInvoiceOut: 'Create invoice out',
|
||||||
summary: 'Summary',
|
summary: 'Summary',
|
||||||
|
@ -401,6 +401,71 @@ export default {
|
||||||
totalWithVat: 'Amount',
|
totalWithVat: 'Amount',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
invoiceIn: {
|
||||||
|
pageTitles: {
|
||||||
|
invoiceIns: 'Invoices In',
|
||||||
|
list: 'List',
|
||||||
|
createInvoiceIn: 'Create invoice in',
|
||||||
|
summary: 'Summary',
|
||||||
|
basicData: 'Basic data',
|
||||||
|
vat: 'VAT',
|
||||||
|
dueDay: 'Due day',
|
||||||
|
intrastat: 'Intrastat',
|
||||||
|
log: 'Logs',
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
ref: 'Reference',
|
||||||
|
supplier: 'Supplier',
|
||||||
|
supplierRef: 'Supplier ref.',
|
||||||
|
serialNumber: 'Serial number',
|
||||||
|
serial: 'Serial',
|
||||||
|
file: 'File',
|
||||||
|
issued: 'Issued',
|
||||||
|
isBooked: 'Is booked',
|
||||||
|
awb: 'AWB',
|
||||||
|
amount: 'Amount',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
issued: 'Issued',
|
||||||
|
amount: 'Amount',
|
||||||
|
client: 'Client',
|
||||||
|
company: 'Company',
|
||||||
|
customerCard: 'Customer card',
|
||||||
|
ticketList: 'Ticket List',
|
||||||
|
vat: 'Vat',
|
||||||
|
dueDay: 'Due day',
|
||||||
|
intrastat: 'Intrastat',
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
supplier: 'Supplier',
|
||||||
|
supplierRef: 'Supplier ref.',
|
||||||
|
currency: 'Currency',
|
||||||
|
docNumber: 'Doc number',
|
||||||
|
issued: 'Expedition date',
|
||||||
|
operated: 'Operation date',
|
||||||
|
bookEntried: 'Entry date',
|
||||||
|
bookedDate: 'Booked date',
|
||||||
|
sage: 'Sage withholding',
|
||||||
|
vat: 'Undeductible VAT',
|
||||||
|
company: 'Company',
|
||||||
|
booked: 'Booked',
|
||||||
|
expense: 'Expense',
|
||||||
|
taxableBase: 'Taxable base',
|
||||||
|
rate: 'Rate',
|
||||||
|
sageVat: 'Sage vat',
|
||||||
|
sageTransaction: 'Sage transaction',
|
||||||
|
dueDay: 'Date',
|
||||||
|
bank: 'Bank',
|
||||||
|
amount: 'Amount',
|
||||||
|
foreignValue: 'Foreign value',
|
||||||
|
dueTotal: 'Due day',
|
||||||
|
noMatch: 'Do not match',
|
||||||
|
code: 'Code',
|
||||||
|
net: 'Net',
|
||||||
|
stems: 'Stems',
|
||||||
|
country: 'Country',
|
||||||
|
},
|
||||||
|
},
|
||||||
worker: {
|
worker: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
workers: 'Workers',
|
workers: 'Workers',
|
||||||
|
@ -526,6 +591,7 @@ export default {
|
||||||
openCard: 'View card',
|
openCard: 'View card',
|
||||||
openSummary: 'Open summary',
|
openSummary: 'Open summary',
|
||||||
viewDescription: 'View description',
|
viewDescription: 'View description',
|
||||||
|
downloadFile: 'Download file',
|
||||||
},
|
},
|
||||||
cardDescriptor: {
|
cardDescriptor: {
|
||||||
mainList: 'Main list',
|
mainList: 'Main list',
|
||||||
|
|
|
@ -357,7 +357,7 @@ export default {
|
||||||
},
|
},
|
||||||
invoiceOut: {
|
invoiceOut: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
invoiceOuts: 'Fact. emitidas',
|
invoiceOuts: 'Crear factura',
|
||||||
list: 'Listado',
|
list: 'Listado',
|
||||||
createInvoiceOut: 'Crear fact. emitida',
|
createInvoiceOut: 'Crear fact. emitida',
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
|
@ -401,6 +401,69 @@ export default {
|
||||||
totalWithVat: 'Importe',
|
totalWithVat: 'Importe',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
invoiceIn: {
|
||||||
jorgep marked this conversation as resolved
jgallego
commented
aquí creo que en salix es historico, puede ser? aquí creo que en salix es historico, puede ser?
jorgep
commented
En salix se llama histórico pero en Lilium le han puesto ese nombre en las otras secciones En salix se llama histórico pero en Lilium le han puesto ese nombre en las otras secciones
|
|||||||
|
pageTitles: {
|
||||||
|
invoiceIns: 'Fact. recibidas',
|
||||||
|
list: 'Listado',
|
||||||
|
createInvoiceIn: 'Crear fact. recibida',
|
||||||
jorgep marked this conversation as resolved
Outdated
jgallego
commented
invoice out es emitida, mira si esta mal la clave o el valor invoice out es emitida, mira si esta mal la clave o el valor
|
|||||||
|
summary: 'Resumen',
|
||||||
|
basicData: 'Datos básicos',
|
||||||
|
vat: 'IVA',
|
||||||
|
dueDay: 'Vencimiento',
|
||||||
|
intrastat: 'Intrastat',
|
||||||
|
log: 'Registros de auditoría',
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
ref: 'Referencia',
|
||||||
|
supplier: 'Proveedor',
|
||||||
|
supplierRef: 'Ref. proveedor',
|
||||||
|
serialNumber: 'Num. serie',
|
||||||
|
shortIssued: 'F. emisión',
|
||||||
|
serial: 'Serie',
|
||||||
|
file: 'Fichero',
|
||||||
|
issued: 'Fecha emisión',
|
||||||
|
isBooked: 'Conciliada',
|
||||||
|
awb: 'AWB',
|
||||||
|
amount: 'Importe',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
issued: 'Fecha emisión',
|
||||||
|
amount: 'Importe',
|
||||||
|
client: 'Cliente',
|
||||||
|
company: 'Empresa',
|
||||||
|
customerCard: 'Ficha del cliente',
|
||||||
|
ticketList: 'Listado de tickets',
|
||||||
|
vat: 'Iva',
|
||||||
|
dueDay: 'Fecha de vencimiento',
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
supplier: 'Proveedor',
|
||||||
|
supplierRef: 'Ref. proveedor',
|
||||||
|
currency: 'Divisa',
|
||||||
|
docNumber: 'Número documento',
|
||||||
|
issued: 'Fecha de expedición',
|
||||||
|
operated: 'Fecha operación',
|
||||||
|
bookEntried: 'Fecha asiento',
|
||||||
|
bookedDate: 'Fecha contable',
|
||||||
|
sage: 'Retención sage',
|
||||||
|
vat: 'Iva no deducible',
|
||||||
|
company: 'Empresa',
|
||||||
|
booked: 'Contabilizada',
|
||||||
|
expense: 'Gasto',
|
||||||
|
taxableBase: 'Base imp.',
|
||||||
|
rate: 'Tasa',
|
||||||
|
sageTransaction: 'Sage transación',
|
||||||
|
dueDay: 'Fecha',
|
||||||
|
bank: 'Caja',
|
||||||
|
amount: 'Importe',
|
||||||
|
foreignValue: 'Divisa',
|
||||||
|
dueTotal: 'Vencimiento',
|
||||||
|
code: 'Código',
|
||||||
|
net: 'Neto',
|
||||||
|
stems: 'Tallos',
|
||||||
|
country: 'País',
|
||||||
|
},
|
||||||
|
},
|
||||||
worker: {
|
worker: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
workers: 'Trabajadores',
|
workers: 'Trabajadores',
|
||||||
|
@ -526,6 +589,7 @@ export default {
|
||||||
openCard: 'Ver ficha',
|
openCard: 'Ver ficha',
|
||||||
openSummary: 'Abrir detalles',
|
openSummary: 'Abrir detalles',
|
||||||
viewDescription: 'Ver descripción',
|
viewDescription: 'Ver descripción',
|
||||||
|
downloadFile: 'Descargar archivo',
|
||||||
},
|
},
|
||||||
cardDescriptor: {
|
cardDescriptor: {
|
||||||
mainList: 'Listado principal',
|
mainList: 'Listado principal',
|
||||||
|
|
|
@ -37,7 +37,7 @@ function confirmPickupOrder() {
|
||||||
data: {
|
data: {
|
||||||
address: customer.email,
|
address: customer.email,
|
||||||
},
|
},
|
||||||
send: sendPickupOrder,
|
promise: sendPickupOrder,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,710 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
|
||||||
|
import { downloadFile } from 'src/composables/downloadFile';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const dms = ref({});
|
||||||
|
const editDownloadDisabled = ref(false);
|
||||||
|
const arrayData = useArrayData('InvoiceIn');
|
||||||
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
|
const userConfig = ref(null);
|
||||||
|
|
||||||
|
const suppliers = ref([]);
|
||||||
|
const suppliersRef = ref();
|
||||||
|
const currencies = ref([]);
|
||||||
|
const currenciesRef = ref();
|
||||||
|
const companies = ref([]);
|
||||||
|
const companiesRef = ref();
|
||||||
|
const dmsTypes = ref([]);
|
||||||
|
const dmsTypesRef = ref();
|
||||||
|
const warehouses = ref([]);
|
||||||
|
const warehousesRef = ref();
|
||||||
|
const allowTypesRef = ref();
|
||||||
|
const allowedContentTypes = ref([]);
|
||||||
|
const inputFileRef = ref();
|
||||||
|
const editDmsRef = ref();
|
||||||
|
const createDmsRef = ref();
|
||||||
|
|
||||||
|
const requiredFieldRule = (val) => val || t('Required field');
|
||||||
|
const dateMask = '####-##-##';
|
||||||
|
const fillMask = '_';
|
||||||
|
|
||||||
|
async function checkFileExists(dmsId) {
|
||||||
|
if (!dmsId) return;
|
||||||
|
try {
|
||||||
|
await axios.get(`Dms/${dmsId}`, { fields: ['id'] });
|
||||||
|
editDownloadDisabled.value = false;
|
||||||
|
} catch (e) {
|
||||||
|
editDownloadDisabled.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
lo miramos lo miramos
|
|||||||
|
|
||||||
|
async function setEditDms(dmsId) {
|
||||||
|
const { data } = await axios.get(`Dms/${dmsId}`);
|
||||||
|
dms.value = {
|
||||||
|
warehouseId: data.warehouseFk,
|
||||||
|
companyId: data.companyFk,
|
||||||
|
dmsTypeId: data.dmsTypeFk,
|
||||||
|
...data,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!allowedContentTypes.value.length) await allowTypesRef.value.fetch();
|
||||||
|
|
||||||
|
editDmsRef.value.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setCreateDms() {
|
||||||
|
const { data } = await axios.get('DmsTypes/findOne', {
|
||||||
|
where: { code: 'invoiceIn' },
|
||||||
|
});
|
||||||
|
dms.value = {
|
||||||
|
reference: invoiceIn.value.supplierRef,
|
||||||
|
warehouseId: userConfig.value.warehouseFk,
|
||||||
|
companyId: userConfig.value.companyFk,
|
||||||
|
dmsTypeId: data.id,
|
||||||
|
description: invoiceIn.value.supplier.name,
|
||||||
|
hasFile: true,
|
||||||
|
hasFileAttached: true,
|
||||||
|
files: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
createDmsRef.value.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upsert() {
|
||||||
|
try {
|
||||||
|
const isEdit = !!dms.value.id;
|
||||||
jorgep marked this conversation as resolved
Outdated
jsegarra
commented
Incluso te diría que en vez de tantos ifs, haría un objeto de validaciones, donde la key es el campo y el value es el mensaje a mostrar.
Para la que aplica en edit, usuaria la bandera que hemos comentado Incluso te diría que en vez de tantos ifs, haría un objeto de validaciones, donde la key es el campo y el value es el mensaje a mostrar.
```
const validations = {
companyId:'The company can\'t be empty'
}
Object.entries(validations).forEach(([key, value])=>{
if(!dms.value[key])
throw Error(t(value))
});
```
Para la que aplica en edit, usuaria la bandera que hemos comentado
|
|||||||
|
const errors = {
|
||||||
|
companyId: `The company can't be empty`,
|
||||||
|
warehouseId: `The warehouse can't be empty`,
|
||||||
|
dmsTypeId: `The DMS Type can't be empty`,
|
||||||
|
description: `The description can't be empty`,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(errors).forEach((key) => {
|
||||||
|
if (!dms.value[key]) throw Error(t(errors[key]));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEdit && !dms.value.files) throw Error(t(`The files can't be empty`));
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
if (dms.value.files) {
|
||||||
|
for (let i = 0; i < dms.value.files.length; i++)
|
||||||
|
formData.append(dms.value.files[i].name, dms.value.files[i]);
|
||||||
|
dms.value.hasFileAttached = true;
|
||||||
|
}
|
||||||
|
const url = isEdit ? `dms/${dms.value.id}/updateFile` : 'Dms/uploadFile';
|
||||||
|
const { data } = await axios.post(url, formData, {
|
||||||
|
params: dms.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.length) invoiceIn.value.dmsFk = data[0].id;
|
||||||
|
|
||||||
|
if (!isEdit) {
|
||||||
|
createDmsRef.value.hide();
|
||||||
|
} else {
|
||||||
|
editDmsRef.value.hide();
|
||||||
|
}
|
||||||
jorgep marked this conversation as resolved
Outdated
jsegarra
commented
Yo movería la lógica de create y save a funciones constantes porque estás duplicando código que hacen lo mismo Yo movería la lógica de create y save a funciones constantes porque estás duplicando código que hacen lo mismo
|
|||||||
|
|
||||||
|
quasar.notify({
|
||||||
|
message: t('globals.dataSaved'),
|
||||||
|
type: 'positive',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
quasar.notify({
|
||||||
|
message: t(`${error.message}`),
|
||||||
|
type: 'negative',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
ref="suppliersRef"
|
||||||
|
url="Suppliers"
|
||||||
|
:filter="{ fields: ['id', 'nickname'] }"
|
||||||
|
limit="30"
|
||||||
|
@on-fetch="(data) => (suppliers = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
ref="currenciesRef"
|
||||||
|
url="Currencies"
|
||||||
|
:filter="{ fields: ['id', 'code'] }"
|
||||||
|
order="code"
|
||||||
|
@on-fetch="(data) => (currencies = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
ref="companiesRef"
|
||||||
|
url="Companies"
|
||||||
|
:filter="{ fields: ['id', 'code'] }"
|
||||||
|
order="code"
|
||||||
|
@on-fetch="(data) => (companies = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
ref="dmsTypesRef"
|
||||||
|
url="DmsTypes"
|
||||||
|
:filter="{ fields: ['id', 'name'] }"
|
||||||
|
order="name"
|
||||||
|
@on-fetch="(data) => (dmsTypes = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
ref="warehousesRef"
|
||||||
|
url="Warehouses"
|
||||||
|
:filter="{ fields: ['id', 'name'] }"
|
||||||
|
order="name"
|
||||||
|
@on-fetch="(data) => (warehouses = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
ref="allowTypesRef"
|
||||||
|
url="DmsContainers/allowedContentTypes"
|
||||||
|
@on-fetch="(data) => (allowedContentTypes = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
url="UserConfigs/getUserConfig"
|
||||||
|
@on-fetch="(data) => (userConfig = data)"
|
||||||
|
auto-load
|
||||||
|
/>
|
||||||
|
<FormModel v-if="invoiceIn" :url="`InvoiceIns/${route.params.id}`" model="invoiceIn">
|
||||||
|
<template #form="{ data }">
|
||||||
|
<div class="row q-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<VnSelectFilter
|
||||||
|
v-if="suppliersRef"
|
||||||
|
:label="t('supplierFk')"
|
||||||
|
v-model="data.supplierFk"
|
||||||
|
:options="suppliers"
|
||||||
|
option-value="id"
|
||||||
|
option-label="nickname"
|
||||||
|
:input-debounce="100"
|
||||||
|
@input-value="suppliersRef.fetch()"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{
|
||||||
|
`${scope.opt.id} - ${scope.opt.nickname}`
|
||||||
|
}}</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<QInput
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
:label="t('Supplier ref')"
|
||||||
|
v-model="data.supplierRef"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<QInput
|
||||||
|
:label="t('Expedition date')"
|
||||||
|
v-model="data.issued"
|
||||||
|
:mask="dateMask"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon
|
||||||
|
name="event"
|
||||||
|
class="cursor-pointer"
|
||||||
|
:fill-mask="fillMask"
|
||||||
|
>
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="data.issued">
|
||||||
|
<div class="row items-center justify-end">
|
||||||
|
<QBtn
|
||||||
|
v-close-popup
|
||||||
|
label="Close"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<QInput
|
||||||
|
:label="t('Operation date')"
|
||||||
|
v-model="data.operated"
|
||||||
|
:mask="dateMask"
|
||||||
|
:fill-mask="fillMask"
|
||||||
|
autofocus
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="data.operated" :mask="dateMask">
|
||||||
|
<div class="row items-center justify-end">
|
||||||
|
<QBtn
|
||||||
|
v-close-popup
|
||||||
|
label="Close"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<QInput
|
||||||
|
:label="t('Undeductible VAT')"
|
||||||
|
v-model="data.deductibleExpenseFk"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<QInput
|
||||||
|
:label="t('Document')"
|
||||||
|
v-model="data.dmsFk"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
@update:model-value="checkFileExists(data.dmsFk)"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<QBtn
|
||||||
|
v-if="data.dmsFk"
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': editDownloadDisabled,
|
||||||
|
}"
|
||||||
|
:disable="editDownloadDisabled"
|
||||||
|
icon="cloud_download"
|
||||||
|
:title="t('Download file')"
|
||||||
|
padding="xs"
|
||||||
|
round
|
||||||
|
@click="downloadFile(data.dmsFk)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<QBtn
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': editDownloadDisabled,
|
||||||
|
}"
|
||||||
|
:disable="editDownloadDisabled"
|
||||||
|
v-if="data.dmsFk"
|
||||||
|
icon="edit"
|
||||||
|
round
|
||||||
|
padding="xs"
|
||||||
|
@click="setEditDms(data.dmsFk)"
|
||||||
|
>
|
||||||
|
<QTooltip>{{ t('Edit document') }}</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
<QBtn
|
||||||
|
v-else
|
||||||
|
icon="add_circle"
|
||||||
|
round
|
||||||
|
padding="xs"
|
||||||
|
@click="setCreateDms()"
|
||||||
|
>
|
||||||
|
<QTooltip>{{ t('Create document') }}</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<QInput
|
||||||
|
:label="t('Entry date')"
|
||||||
|
v-model="data.bookEntried"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
:mask="dateMask"
|
||||||
|
:fill-mask="fillMask"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="data.bookEntried" :mask="dateMask">
|
||||||
|
<div class="row items-center justify-end">
|
||||||
|
<QBtn
|
||||||
|
v-close-popup
|
||||||
|
label="Close"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
Los valores de las propiedades de mask, podrían ser globales en el componente? Los valores de las propiedades de mask, podrían ser globales en el componente?
|
|||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<QInput
|
||||||
|
:label="t('Accounted date')"
|
||||||
|
v-model="data.booked"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
:mask="dateMask"
|
||||||
|
:fill-mask="fillMask"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="data.booked" :mask="maskDate">
|
||||||
|
<div class="row items-center justify-end">
|
||||||
|
<QBtn
|
||||||
|
v-close-popup
|
||||||
|
label="Close"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<VnSelectFilter
|
||||||
|
v-if="currenciesRef"
|
||||||
|
:label="t('Currency')"
|
||||||
|
v-model="data.currencyFk"
|
||||||
|
:options="currencies"
|
||||||
|
option-value="id"
|
||||||
|
option-label="code"
|
||||||
|
@input-value="currenciesRef.fetch()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<VnSelectFilter
|
||||||
|
v-if="companiesRef"
|
||||||
|
:label="t('Company')"
|
||||||
|
v-model="data.companyFk"
|
||||||
|
:options="companies"
|
||||||
|
option-value="id"
|
||||||
|
option-label="code"
|
||||||
|
@input-value="companiesRef.fetch()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('invoiceIn.summary.booked')"
|
||||||
|
v-model="data.isBooked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModel>
|
||||||
|
<QDialog ref="editDmsRef">
|
||||||
|
<QCard>
|
||||||
|
<QCardSection class="q-pb-none">
|
||||||
|
<QItem class="q-px-none">
|
||||||
|
<span class="text-primary text-h6 full-width">
|
||||||
|
<QIcon name="edit" class="q-mr-xs" />
|
||||||
|
{{ t('Edit document') }}
|
||||||
|
</span>
|
||||||
|
<QBtn icon="close" flat round dense v-close-popup />
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardSection class="q-py-none">
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="t('Reference')"
|
||||||
|
v-model="dms.reference"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
/>
|
||||||
|
<VnSelectFilter
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
Esta rule esta muchas veces en el código y es la misma en todos los casos. Esta rule esta muchas veces en el código y es la misma en todos los casos.
Se podría sacar del HTML y definir en el script setup?
|
|||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="`${t('Company')}*`"
|
||||||
|
v-model="dms.companyId"
|
||||||
|
:options="companies"
|
||||||
|
option-value="id"
|
||||||
|
option-label="code"
|
||||||
|
@input-value="companiesRef.fetch()"
|
||||||
|
:rules="[requiredFieldRule]"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<VnSelectFilter
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="`${t('Warehouse')}*`"
|
||||||
|
v-model="dms.warehouseId"
|
||||||
|
:options="warehouses"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
@input-value="warehousesRef.fetch()"
|
||||||
|
:rules="[requiredFieldRule]"
|
||||||
|
/>
|
||||||
|
<VnSelectFilter
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="`${t('Type')}*`"
|
||||||
|
v-model="dms.dmsTypeId"
|
||||||
|
:options="dmsTypes"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
@input-value="dmsTypesRef.fetch()"
|
||||||
|
:rules="[requiredFieldRule]"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
type="textarea"
|
||||||
|
size="lg"
|
||||||
|
autogrow
|
||||||
|
:label="`${t('Description')}*`"
|
||||||
|
v-model="dms.description"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
:rules="[(val) => val.length || t('Required field')]"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QFile
|
||||||
|
ref="inputFileRef"
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="t('File')"
|
||||||
|
v-model="dms.files"
|
||||||
|
multiple
|
||||||
|
:accept="allowedContentTypes.join(',')"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QBtn
|
||||||
|
icon="attach_file_add"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
padding="xs"
|
||||||
|
@click="inputFileRef.pickFiles()"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('Select a file') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
<QBtn icon="info" flat round padding="xs">
|
||||||
|
<QTooltip max-width="30rem">
|
||||||
|
{{
|
||||||
|
`${t(
|
||||||
|
'Allowed content types'
|
||||||
|
)}: ${allowedContentTypes.join(', ')}`
|
||||||
|
}}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
</QFile>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('Generate identifier for original file')"
|
||||||
|
v-model="dms.hasFile"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardActions class="justify-end">
|
||||||
|
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup />
|
||||||
|
<QBtn :label="t('globals.save')" color="primary" @click="upsert" />
|
||||||
|
</QCardActions>
|
||||||
|
</QCard>
|
||||||
|
</QDialog>
|
||||||
|
<QDialog ref="createDmsRef">
|
||||||
|
<QCard>
|
||||||
|
<QCardSection class="q-pb-none">
|
||||||
|
<QItem>
|
||||||
|
<span class="text-primary text-h6 full-width">
|
||||||
|
<QIcon name="edit" class="q-mr-xs" />
|
||||||
|
{{ t('Create document') }}
|
||||||
|
</span>
|
||||||
|
<QBtn icon="close" flat round dense v-close-popup align="right" />
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardSection class="q-pb-none">
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="t('Reference')"
|
||||||
|
v-model="dms.reference"
|
||||||
|
/>
|
||||||
|
<VnSelectFilter
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="`${t('Company')}*`"
|
||||||
|
v-model="dms.companyId"
|
||||||
|
:options="companies"
|
||||||
|
option-value="id"
|
||||||
|
option-label="code"
|
||||||
|
@input-value="companiesRef.fetch()"
|
||||||
|
:rules="[requiredFieldRule]"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<VnSelectFilter
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="`${t('Warehouse')}*`"
|
||||||
|
v-model="dms.warehouseId"
|
||||||
|
:options="warehouses"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
@input-value="warehousesRef.fetch()"
|
||||||
|
:rules="[requiredFieldRule]"
|
||||||
|
/>
|
||||||
|
<VnSelectFilter
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="`${t('Type')}*`"
|
||||||
|
v-model="dms.dmsTypeId"
|
||||||
|
:options="dmsTypes"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
@input-value="dmsTypesRef.fetch()"
|
||||||
|
:rules="[requiredFieldRule]"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
type="textarea"
|
||||||
|
size="lg"
|
||||||
|
autogrow
|
||||||
|
:label="`${t('Description')}*`"
|
||||||
|
v-model="dms.description"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
:rules="[(val) => val.length || t('Required field')]"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QFile
|
||||||
|
ref="inputFileRef"
|
||||||
|
class="full-width q-pa-xs"
|
||||||
|
:label="t('File')"
|
||||||
|
v-model="dms.files"
|
||||||
|
multiple
|
||||||
|
:accept="allowedContentTypes.join(',')"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QBtn
|
||||||
|
icon="attach_file_add"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
padding="xs"
|
||||||
|
@click="inputFileRef.pickFiles()"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('Select a file') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
<QBtn icon="info" flat round padding="xs">
|
||||||
|
<QTooltip max-width="30rem">
|
||||||
|
{{
|
||||||
|
`${t(
|
||||||
|
'Allowed content types'
|
||||||
|
)}: ${allowedContentTypes.join(', ')}`
|
||||||
|
}}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
</QFile>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('Generate identifier for original file')"
|
||||||
|
v-model="dms.hasFile"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardActions align="right">
|
||||||
|
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup />
|
||||||
|
<QBtn :label="t('globals.save')" color="primary" @click="upsert" />
|
||||||
|
</QCardActions>
|
||||||
|
</QCard>
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@media (max-width: $breakpoint-xs) {
|
||||||
|
.column {
|
||||||
|
.row:not(:last-child) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.q-dialog {
|
||||||
|
.q-card {
|
||||||
|
&__section:not(:first-child) {
|
||||||
|
.q-item {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
supplierFk: Supplier
|
||||||
|
es:
|
||||||
|
supplierFk: Proveedor
|
||||||
|
Supplier ref: Ref. proveedor
|
||||||
|
Expedition date: Fecha expedición
|
||||||
|
Operation date: Fecha operación
|
||||||
|
Undeductible VAT: Iva no deducible
|
||||||
|
Document: Documento
|
||||||
|
Download file: Descargar archivo
|
||||||
|
Entry date: Fecha asiento
|
||||||
|
Accounted date: Fecha contable
|
||||||
|
Currency: Moneda
|
||||||
|
Company: Empresa
|
||||||
|
Edit document: Editar documento
|
||||||
|
Reference: Referencia
|
||||||
|
Type: Tipo
|
||||||
|
Description: Descripción
|
||||||
|
Generate identifier for original file: Generar identificador para archivo original
|
||||||
|
Required field: Campo obligatorio
|
||||||
|
File: Fichero
|
||||||
|
Create document: Crear documento
|
||||||
|
Select a file: Seleccione un fichero
|
||||||
|
Allowed content types: Tipos de archivo permitidos
|
||||||
|
The company can't be empty: La empresa no puede estar vacía
|
||||||
|
The warehouse can't be empty: El almacén no puede estar vacío
|
||||||
|
The DMS Type can't be empty: El dms no puede estar vacío
|
||||||
|
The description can't be empty: La descripción no puede estar vacía
|
||||||
|
The files can't be empty: Los archivos no pueden estar vacíos
|
||||||
|
</i18n>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import InvoiceInDescriptor from './InvoiceInDescriptor.vue';
|
||||||
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import { onMounted, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'supplier',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'contacts',
|
||||||
|
scope: {
|
||||||
|
where: {
|
||||||
|
email: { neq: null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'invoiceInDueDay',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'company',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'currency',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const arrayData = useArrayData('InvoiceIn', {
|
||||||
|
url: `InvoiceIns/${route.params.id}`,
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await arrayData.fetch({ append: false });
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
async (newId, oldId) => {
|
||||||
|
if (newId) {
|
||||||
|
arrayData.store.url = `InvoiceIns/${newId}`;
|
||||||
|
await arrayData.fetch({ append: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||||
|
<VnSearchbar
|
||||||
|
data-key="InvoiceInList"
|
||||||
|
url="InvoiceIns/filter"
|
||||||
|
:label="t('Search invoice')"
|
||||||
|
:info="t('You can search by invoice reference')"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
|
||||||
|
<QScrollArea class="fit">
|
||||||
|
<InvoiceInDescriptor />
|
||||||
|
<QSeparator />
|
||||||
|
<LeftMenu source="card" />
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
<QPageContainer>
|
||||||
|
<QPage>
|
||||||
|
<QToolbar class="bg-vn-dark justify-end">
|
||||||
|
<div id="st-data"></div>
|
||||||
|
<QSpace />
|
||||||
|
<div id="st-actions"></div>
|
||||||
|
</QToolbar>
|
||||||
|
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||||
|
</QPage>
|
||||||
|
</QPageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Search invoice: Buscar factura emitida
|
||||||
|
You can search by invoice reference: Puedes buscar por referencia de la factura
|
||||||
|
</i18n>
|
|
@ -0,0 +1,327 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { toCurrency, toDate } from 'src/filters';
|
||||||
|
import { useRole } from 'src/composables/useRole';
|
||||||
|
import useCardDescription from 'src/composables/useCardDescription';
|
||||||
|
import { downloadFile } from 'src/composables/downloadFile';
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import { usePrintService } from 'composables/usePrintService';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
|
||||||
|
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const { hasAny } = useRole();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { openReport, sendEmail } = usePrintService();
|
||||||
|
const arrayData = useArrayData('InvoiceIn');
|
||||||
|
|
||||||
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
|
const cardDescriptorRef = ref();
|
||||||
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
|
const totalAmount = ref();
|
||||||
|
const currentAction = ref();
|
||||||
|
const config = ref();
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
book: {
|
||||||
|
title: 'Are you sure you want to book this invoice?',
|
||||||
|
cb: checkToBook,
|
||||||
|
action: toBook,
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
title: 'Are you sure you want to delete this invoice?',
|
||||||
|
action: deleteInvoice,
|
||||||
|
},
|
||||||
|
clone: {
|
||||||
|
title: 'Are you sure you want to clone this invoice?',
|
||||||
|
action: cloneInvoice,
|
||||||
|
},
|
||||||
|
showPdf: {
|
||||||
|
cb: showPdfInvoice,
|
||||||
|
},
|
||||||
|
sendPdf: {
|
||||||
|
cb: sendPdfInvoiceConfirmation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const filter = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'supplier',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'contacts',
|
||||||
|
scope: {
|
||||||
|
where: {
|
||||||
|
email: { neq: null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'invoiceInDueDay',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'company',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'currency',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = ref(useCardDescription());
|
||||||
|
|
||||||
|
async function setData(entity) {
|
||||||
|
data.value = useCardDescription(entity.supplierRef, entity.id);
|
||||||
|
const { totalDueDay } = await getTotals();
|
||||||
|
totalAmount.value = totalDueDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTotals() {
|
||||||
|
const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDialog() {
|
||||||
|
quasar.dialog({
|
||||||
|
component: VnConfirm,
|
||||||
|
componentProps: {
|
||||||
|
title: currentAction.value.title,
|
||||||
|
promise: currentAction.value.action,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkToBook() {
|
||||||
|
let directBooking = true;
|
||||||
|
|
||||||
|
const totals = await getTotals();
|
||||||
|
const taxableBaseNotEqualDueDay = totals.totalDueDay != totals.totalTaxableBase;
|
||||||
|
const vatNotEqualDueDay = totals.totalDueDay != totals.totalVat;
|
||||||
|
|
||||||
|
if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) directBooking = false;
|
||||||
|
|
||||||
|
const { data: dueDaysCount } = await axios.get('InvoiceInDueDays/count', {
|
||||||
|
where: {
|
||||||
|
invoiceInFk: entityId.value,
|
||||||
|
dueDated: { gte: Date.vnNew() },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dueDaysCount) directBooking = false;
|
||||||
|
|
||||||
|
if (!directBooking) openDialog();
|
||||||
|
else toBook();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toBook() {
|
||||||
|
await axios.post(`InvoiceIns/${entityId.value}/toBook`);
|
||||||
|
|
||||||
|
// Pendiente de sincronizar todo con arrayData
|
||||||
|
quasar.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('globals.dataSaved'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await cardDescriptorRef.value.getData();
|
||||||
|
setTimeout(() => location.reload(), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteInvoice() {
|
||||||
|
await axios.delete(`InvoiceIns/${entityId.value}`);
|
||||||
|
quasar.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('Invoice deleted'),
|
||||||
|
});
|
||||||
|
router.push({ path: '/invoice-in' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cloneInvoice() {
|
||||||
|
const { data } = await axios.post(`InvoiceIns/${entityId.value}/clone`);
|
||||||
|
quasar.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('Invoice cloned'),
|
||||||
|
});
|
||||||
|
router.push({ path: `/invoice-in/${data.id}/summary` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdministrative = () => hasAny(['administrative']);
|
||||||
|
|
||||||
|
const isAgricultural = () =>
|
||||||
|
invoiceIn.value.supplier.sageWithholdingFk == config.value[0].sageWithholdingFk;
|
||||||
|
|
||||||
|
function showPdfInvoice() {
|
||||||
|
if (isAgricultural()) openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPdfInvoiceConfirmation() {
|
||||||
|
quasar.dialog({
|
||||||
|
component: SendEmailDialog,
|
||||||
|
componentProps: {
|
||||||
|
data: {
|
||||||
|
address: invoiceIn.value.supplier.contacts[0].email,
|
||||||
|
},
|
||||||
|
promise: sendPdfInvoice,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPdfInvoice({ address }) {
|
||||||
|
if (!address)
|
||||||
|
quasar.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: t(`The email can't be empty`),
|
||||||
|
});
|
||||||
|
else
|
||||||
|
return sendEmail(`InvoiceIns/${entityId.value}/invoice-in-email`, {
|
||||||
|
recipientId: invoiceIn.value.supplier.id,
|
||||||
|
recipient: address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerMenu(type) {
|
||||||
|
currentAction.value = actions[type];
|
||||||
|
if (currentAction.value.cb) currentAction.value.cb();
|
||||||
|
else openDialog(type);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="InvoiceInConfigs"
|
||||||
|
:where="{ fields: ['sageWithholdingFk'] }"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (config = data)"
|
||||||
|
/>
|
||||||
|
<!--Refactor para añadir en el arrayData-->
|
||||||
|
<CardDescriptor
|
||||||
|
ref="cardDescriptorRef"
|
||||||
|
module="InvoiceIn"
|
||||||
|
:url="`InvoiceIns/${entityId}`"
|
||||||
|
:filter="filter"
|
||||||
|
:title="data.title"
|
||||||
|
:subtitle="data.subtitle"
|
||||||
|
@on-fetch="setData"
|
||||||
|
data-key="invoiceInData"
|
||||||
|
>
|
||||||
|
<template #menu="{ entity }">
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
Igual que has hecho isAgricultural, podrías hacer para isAdministrative? Igual que has hecho isAgricultural, podrías hacer para isAdministrative?
|
|||||||
|
<QItem
|
||||||
|
v-if="!entity.isBooked && isAdministrative()"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="triggerMenu('book')"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('To book') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="isAdministrative()"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="triggerMenu('delete')"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('Delete invoice') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="isAdministrative()"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="triggerMenu('clone')"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('Clone invoice') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="isAgricultural()"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="triggerMenu('showPdf')"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('Show agricultural receipt as PDF') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="isAgricultural()"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="triggerMenu('sendPdf')"
|
||||||
|
>
|
||||||
|
<QItemSection
|
||||||
|
>{{ t('Send agricultural receipt as PDF') }}...</QItemSection
|
||||||
|
>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="entity.dmsFk"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="downloadFile(entity.dmsFk)"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
<template #body="{ entity }">
|
||||||
|
<VnLv :label="t('invoiceIn.card.issued')" :value="toDate(entity.issued)" />
|
||||||
|
<VnLv :label="t('invoiceIn.summary.booked')" :value="toDate(entity.booked)" />
|
||||||
|
<VnLv :label="t('invoiceIn.card.amount')" :value="toCurrency(totalAmount)" />
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.supplier')"
|
||||||
|
:value="entity.supplier?.nickname"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ entity }">
|
||||||
|
<QCardActions>
|
||||||
|
<!--Sección proveedores no disponible-->
|
||||||
|
<!--Sección entradas no disponible-->
|
||||||
|
<QBtn
|
||||||
|
size="md"
|
||||||
|
icon="vn:ticket"
|
||||||
|
color="primary"
|
||||||
|
:to="{
|
||||||
|
name: 'InvoiceInList',
|
||||||
|
query: {
|
||||||
|
params: JSON.stringify({ supplierFk: entity.supplierFk }),
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</QCardActions>
|
||||||
|
</template>
|
||||||
|
</CardDescriptor>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.q-dialog {
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
tiene sentido tener un width y un max-width de igual tamaño? tiene sentido tener un width y un max-width de igual tamaño?
Se puede dar el caso que el valor de la qcard sea mas pequeño¿?
|
|||||||
|
.q-card {
|
||||||
|
width: 35em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
To book: Contabilizar
|
||||||
|
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
|
||||||
|
Delete invoice: Eliminar factura
|
||||||
|
Are you sure you want to delete this invoice?: Estas seguro de querer eliminar esta factura?
|
||||||
|
Invoice deleted: Factura eliminada
|
||||||
|
Clone invoice: Clonar factura
|
||||||
|
Invoice cloned: Factura clonada
|
||||||
|
Show agricultural receipt as PDF: Ver recibo agrícola como PDF
|
||||||
|
Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
|
||||||
|
Are you sure you want to send it?: Estás seguro que quieres enviarlo?
|
||||||
|
Send PDF invoice: Enviar factura a PDF
|
||||||
|
</i18n>
|
|
@ -0,0 +1,294 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { toDate } from 'src/filters';
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import CrudModel from 'src/components/CrudModel.vue';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const arrayData = useArrayData('InvoiceIn');
|
||||||
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
|
|
||||||
|
const rowsSelected = ref([]);
|
||||||
|
const banks = ref([]);
|
||||||
|
const invoiceInFormRef = ref();
|
||||||
|
const invoiceId = route.params.id;
|
||||||
|
|
||||||
|
const placeholder = 'yyyy/mm/dd';
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
invoiceInFk: invoiceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
name: 'duedate',
|
||||||
|
label: t('Date'),
|
||||||
|
field: (row) => toDate(row.dueDated),
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 1,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bank',
|
||||||
|
label: t('Bank'),
|
||||||
|
field: (row) => row.bankFk,
|
||||||
|
options: banks.value,
|
||||||
|
model: 'bankFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'bank',
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 2,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
label: t('Amount'),
|
||||||
|
field: (row) => row.amount,
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 3,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'foreignvalue',
|
||||||
|
label: t('Foreign value'),
|
||||||
|
field: (row) => row.foreignValue,
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 4,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isNotEuro = (code) => code != 'EUR';
|
||||||
|
|
||||||
jorgep marked this conversation as resolved
Outdated
jsegarra
commented
TIP: es igual poner Number(invoiceId) que +invoiceId TIP: es igual poner Number(invoiceId) que +invoiceId
|
|||||||
|
async function insert() {
|
||||||
|
await axios.post('/InvoiceInDueDays/new ', { id: +invoiceId });
|
||||||
|
await invoiceInFormRef.value.reload();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<FetchData url="Banks" auto-load limit="30" @on-fetch="(data) => (banks = data)" />
|
||||||
|
<CrudModel
|
||||||
|
v-if="invoiceIn"
|
||||||
|
ref="invoiceInFormRef"
|
||||||
|
data-key="InvoiceInDueDays"
|
||||||
|
url="InvoiceInDueDays"
|
||||||
|
:filter="filter"
|
||||||
|
auto-load
|
||||||
|
:data-required="{ invoiceInFk: invoiceId }"
|
||||||
|
v-model:selected="rowsSelected"
|
||||||
|
@on-fetch="(data) => (areRows = !!data.length)"
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<QTable
|
||||||
|
v-model:selected="rowsSelected"
|
||||||
|
selection="multiple"
|
||||||
|
:columns="columns"
|
||||||
|
:rows="rows"
|
||||||
|
row-key="$index"
|
||||||
|
hide-pagination
|
||||||
|
:grid="$q.screen.lt.sm"
|
||||||
|
>
|
||||||
|
<template #body-cell-duedate="{ row }">
|
||||||
|
<QTd>
|
||||||
|
<QInput
|
||||||
|
v-model="row.dueDated"
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
Quizás debería ser una valor constante Quizás debería ser una valor constante
|
|||||||
|
mask="date"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="row.dueDated" landscape>
|
||||||
|
<div
|
||||||
|
class="row items-center justify-end q-gutter-sm"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.cancel')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.confirm')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-bank="{ row, col }">
|
||||||
|
<QTd>
|
||||||
|
<VnSelectFilter
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:options="col.options"
|
||||||
|
:option-value="col.optionValue"
|
||||||
|
:option-label="col.optionLabel"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{
|
||||||
|
`${scope.opt.id}: ${scope.opt.bank}`
|
||||||
|
}}</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-amount="{ row }">
|
||||||
|
<QTd>
|
||||||
|
<QInput v-model="row.amount" clearable clear-icon="close" />
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-foreignvalue="{ row }">
|
||||||
|
<QTd>
|
||||||
|
<QInput
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': !isNotEuro(invoiceIn.currency.code),
|
||||||
|
}"
|
||||||
|
:disable="!isNotEuro(invoiceIn.currency.code)"
|
||||||
|
v-model="row.foreignValue"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</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>
|
||||||
|
<QInput
|
||||||
|
class="full-width"
|
||||||
|
:label="t('Date')"
|
||||||
|
v-model="props.row.dueDated"
|
||||||
|
mask="date"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate
|
||||||
|
v-model="props.row.dueDated"
|
||||||
|
landscape
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="row items-center justify-end q-gutter-sm"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
:label="
|
||||||
|
t('globals.cancel')
|
||||||
|
"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
:label="
|
||||||
|
t('globals.confirm')
|
||||||
|
"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('Bank')"
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row['bankFk']"
|
||||||
|
:options="banks"
|
||||||
|
option-value="id"
|
||||||
|
option-label="bank"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{
|
||||||
|
`${scope.opt.id}: ${scope.opt.bank}`
|
||||||
|
}}</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
:label="t('Amount')"
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row.amount"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
:label="t('Foreign value')"
|
||||||
|
class="full-width"
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': !isNotEuro(
|
||||||
|
invoiceIn.currency.code
|
||||||
|
),
|
||||||
|
}"
|
||||||
|
:disable="!isNotEuro(invoiceIn.currency.code)"
|
||||||
|
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 color="primary" icon="add" size="lg" round @click="insert" />
|
||||||
|
</QPageSticky>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Date: Fecha
|
||||||
|
Bank: Caja
|
||||||
|
Amount: Importe
|
||||||
|
Foreign value: Divisa
|
||||||
|
</i18n>
|
|
@ -0,0 +1,280 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { toCurrency } from 'src/filters';
|
||||||
|
import CrudModel from 'src/components/CrudModel.vue';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const invoceInIntrastat = ref([]);
|
||||||
|
const amountTotal = computed(() => getTotal('amount'));
|
||||||
|
const netTotal = computed(() => getTotal('net'));
|
||||||
|
const stemsTotal = computed(() => getTotal('stems'));
|
||||||
|
const rowsSelected = ref([]);
|
||||||
|
const countries = ref([]);
|
||||||
|
const intrastats = ref([]);
|
||||||
|
const invoiceInFormRef = ref();
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
invoiceInFk: route.params.id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
label: t('Code'),
|
||||||
|
field: (row) => row.intrastatFk,
|
||||||
|
options: intrastats.value,
|
||||||
|
model: 'intrastatFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'description',
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 1,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
label: t('amount'),
|
||||||
|
field: (row) => row.amount,
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 2,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'net',
|
||||||
|
label: t('net'),
|
||||||
|
field: (row) => row.net,
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 3,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stems',
|
||||||
|
label: t('stems'),
|
||||||
|
field: (row) => row.stems,
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 4,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'country',
|
||||||
|
label: t('country'),
|
||||||
|
field: (row) => row.countryFk,
|
||||||
|
options: countries.value,
|
||||||
|
model: 'countryFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'code',
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 5,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function getTotal(type) {
|
||||||
|
if (!invoceInIntrastat.value.length) return 0.0;
|
||||||
|
return invoceInIntrastat.value.reduce(
|
||||||
|
(total, intrastat) => total + intrastat[type],
|
||||||
|
0.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="Countries"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (countries = data)"
|
||||||
|
sort-by="country"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
url="Intrastats"
|
||||||
|
sort-by="id"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (intrastats = data)"
|
||||||
|
/>
|
||||||
|
<div class="invoiceIn-intrastat">
|
||||||
|
<QCard v-if="invoceInIntrastat.length" class="full-width q-mb-md q-pa-sm">
|
||||||
|
<QItem class="justify-end">
|
||||||
|
<div>
|
||||||
|
<QItemLabel>
|
||||||
|
<VnLv
|
||||||
|
:label="t('Total amount')"
|
||||||
|
:value="toCurrency(amountTotal)"
|
||||||
|
/>
|
||||||
|
</QItemLabel>
|
||||||
|
<QItemLabel>
|
||||||
|
<VnLv :label="t('Total net')" :value="netTotal" />
|
||||||
|
</QItemLabel>
|
||||||
|
<QItemLabel>
|
||||||
|
<VnLv :label="t('Total stems')" :value="stemsTotal" />
|
||||||
|
</QItemLabel>
|
||||||
|
</div>
|
||||||
|
</QItem>
|
||||||
|
</QCard>
|
||||||
|
<CrudModel
|
||||||
|
ref="invoiceInFormRef"
|
||||||
|
data-key="InvoiceInIntrastats"
|
||||||
|
url="InvoiceInIntrastats"
|
||||||
|
auto-load
|
||||||
|
:data-required="{ invoiceInFk: route.params.id }"
|
||||||
|
:filter="filter"
|
||||||
|
v-model:selected="rowsSelected"
|
||||||
|
@on-fetch="(data) => (invoceInIntrastat = data)"
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<QTable
|
||||||
|
v-model:selected="rowsSelected"
|
||||||
|
selection="multiple"
|
||||||
|
:columns="columns"
|
||||||
|
:rows="rows"
|
||||||
|
row-key="$index"
|
||||||
|
hide-pagination
|
||||||
|
:grid="$q.screen.lt.sm"
|
||||||
|
>
|
||||||
|
<template #body-cell="{ row, col }">
|
||||||
|
<QTd>
|
||||||
|
<QInput
|
||||||
|
v-model="row[col.name]"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-code="{ row, col }">
|
||||||
|
<QTd>
|
||||||
|
<VnSelectFilter
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:options="col.options"
|
||||||
|
option-value="id"
|
||||||
|
option-label="description"
|
||||||
|
:filter-options="['id', 'description']"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
{{ `${scope.opt.id}: ${scope.opt.description}` }}
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-country="{ row, col }">
|
||||||
|
<QTd>
|
||||||
|
<VnSelectFilter
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:options="col.options"
|
||||||
|
option-value="id"
|
||||||
|
option-label="code"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</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>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('code')"
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row['intrastatFk']"
|
||||||
|
:options="intrastats"
|
||||||
|
option-value="id"
|
||||||
|
option-label="description"
|
||||||
|
:filter-options="['id', 'description']"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
{{
|
||||||
|
`${scope.opt.id}: ${scope.opt.description}`
|
||||||
|
}}
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-for="(value, index) of [
|
||||||
|
'amount',
|
||||||
|
'net',
|
||||||
|
'stems',
|
||||||
|
]"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<QInput
|
||||||
|
:label="t(value)"
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row[value]"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('country')"
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row['countryFk']"
|
||||||
|
:options="countries"
|
||||||
|
option-value="id"
|
||||||
|
option-label="code"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
</QList>
|
||||||
|
</QCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</template>
|
||||||
|
</CrudModel>
|
||||||
|
</div>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
|
<QBtn
|
||||||
|
color="primary"
|
||||||
|
icon="add"
|
||||||
|
size="lg"
|
||||||
|
round
|
||||||
|
@click="invoiceInFormRef.insert()"
|
||||||
|
/>
|
||||||
|
</QPageSticky>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
.invoiceIn-intrastat {
|
||||||
|
> .q-card {
|
||||||
|
.vn-label-value {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
flex: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
amount: Amount
|
||||||
|
net: Net
|
||||||
|
stems: Stems
|
||||||
|
country: Country
|
||||||
|
es:
|
||||||
|
Code: Código
|
||||||
|
amount: Cantidad
|
||||||
|
net: Neto
|
||||||
|
stems: Tallos
|
||||||
|
country: País
|
||||||
|
Total amount: Total importe
|
||||||
|
Total net: Total neto
|
||||||
|
Total stems: Total tallos
|
||||||
|
</i18n>
|
|
@ -0,0 +1,428 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { toCurrency, toDate } from 'src/filters';
|
||||||
|
import { getUrl } from 'src/composables/getUrl';
|
||||||
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
salixUrl.value = await getUrl('');
|
||||||
|
invoiceInUrl.value = salixUrl.value + `invoiceIn/${entityId.value}/`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
|
|
||||||
|
const salixUrl = ref();
|
||||||
|
const invoiceInUrl = ref();
|
||||||
|
const amountsNotMatch = ref(null);
|
||||||
|
const intrastatTotals = ref({});
|
||||||
|
|
||||||
|
const vatColumns = ref([
|
||||||
|
{
|
||||||
|
name: 'expense',
|
||||||
|
label: 'invoiceIn.summary.expense',
|
||||||
|
field: (row) => row.expenseFk,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landed',
|
||||||
|
label: 'invoiceIn.summary.taxableBase',
|
||||||
|
field: (row) => row.taxableBase,
|
||||||
|
format: (value) => toCurrency(value),
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vat',
|
||||||
|
label: 'invoiceIn.summary.sageVat',
|
||||||
|
field: (row) => row.taxTypeSage?.vat,
|
||||||
|
format: (value) => value,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'transaction',
|
||||||
|
label: 'invoiceIn.summary.sageTransaction',
|
||||||
|
field: (row) => row.transactionTypeSage?.transaction,
|
||||||
|
format: (value) => value,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rate',
|
||||||
|
label: 'invoiceIn.summary.rate',
|
||||||
|
field: (row) => taxRate(row.taxableBase, row.taxTypeSage?.rate),
|
||||||
|
format: (value) => toCurrency(value),
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'currency',
|
||||||
|
label: 'invoiceIn.summary.currency',
|
||||||
|
field: (row) => row.foreignValue,
|
||||||
|
format: (value) => value,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dueDayColumns = ref([
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
label: 'invoiceIn.summary.dueDay',
|
||||||
|
field: (row) => toDate(row.dueDated),
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bank',
|
||||||
|
label: 'invoiceIn.summary.bank',
|
||||||
|
field: (row) => row.bank.bank,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
label: 'invoiceIn.summary.amount',
|
||||||
|
field: (row) => row.amount,
|
||||||
|
format: (value) => toCurrency(value),
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landed',
|
||||||
|
label: 'invoiceIn.summary.foreignValue',
|
||||||
|
field: (row) => row.foreignValue,
|
||||||
|
format: (value) => value,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const intrastatColumns = ref([
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
label: 'invoiceIn.summary.code',
|
||||||
|
field: (row) => {
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
({intrastat}) en vez de row ({intrastat}) en vez de row
|
|||||||
|
return `${row.intrastat.id}: ${row.intrastat?.description}`;
|
||||||
|
},
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
label: 'invoiceIn.summary.amount',
|
||||||
|
field: (row) => toCurrency(row.amount),
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'net',
|
||||||
|
label: 'invoiceIn.summary.net',
|
||||||
|
field: (row) => row.net,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stems',
|
||||||
|
label: 'invoiceIn.summary.stems',
|
||||||
|
field: (row) => row.stems,
|
||||||
|
format: (value) => value,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landed',
|
||||||
|
label: 'invoiceIn.summary.country',
|
||||||
|
field: (row) => row.country?.code,
|
||||||
|
format: (value) => value,
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function getAmountNotMatch(totals) {
|
||||||
|
return (
|
||||||
|
totals.totalDueDay != totals.totalTaxableBase &&
|
||||||
|
totals.totalDueDay != totals.totalVat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIntrastatTotals(intrastat) {
|
||||||
|
const totals = {
|
||||||
|
amount: intrastat.reduce((acc, cur) => acc + cur.amount, 0),
|
||||||
|
net: intrastat.reduce((acc, cur) => acc + cur.net, 0),
|
||||||
|
stems: intrastat.reduce((acc, cur) => acc + cur.stems, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
return totals;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTaxTotal(tax) {
|
||||||
|
return tax.reduce(
|
||||||
|
(acc, cur) => acc + taxRate(cur.taxableBase, cur.taxTypeSage?.rate),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setData(entity) {
|
||||||
|
if (!entity) return false;
|
||||||
|
|
||||||
|
amountsNotMatch.value = getAmountNotMatch(entity.totals);
|
||||||
|
|
||||||
|
if (entity.invoiceInIntrastat.length)
|
||||||
|
intrastatTotals.value = { ...getIntrastatTotals(entity.invoiceInIntrastat) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function taxRate(taxableBase = 0, rate = 0) {
|
||||||
jorgep marked this conversation as resolved
Outdated
jsegarra
commented
Revisamos si añadiendo valores por defecto podemos quitar líneas Revisamos si añadiendo valores por defecto podemos quitar líneas
|
|||||||
|
return (rate / 100) * taxableBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLink(param) {
|
||||||
|
return `#/invoice-in/${entityId.value}/${param}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CardSummary
|
||||||
|
ref="summary"
|
||||||
|
:url="`InvoiceIns/${entityId}/summary`"
|
||||||
|
@on-fetch="(data) => setData(data)"
|
||||||
|
>
|
||||||
|
<template #header="{ entity: invoiceIn }">
|
||||||
|
<div>{{ invoiceIn.id }} - {{ invoiceIn.supplier.name }}</div>
|
||||||
|
</template>
|
||||||
|
<template #body="{ entity: invoiceIn }">
|
||||||
|
<!--Basic Data-->
|
||||||
|
<QCard class="vn-one">
|
||||||
|
<QCardSection class="q-pa-none">
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
4 llamadas al mismo formato de URL, podríamos tenerlo en un método con un argumento? 4 llamadas al mismo formato de URL, podríamos tenerlo en un método con un argumento?
|
|||||||
|
<a class="header" :href="getLink('basic-data')">
|
||||||
|
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||||
|
<QIcon name="open_in_new" color="primary" />
|
||||||
|
</a>
|
||||||
|
</QCardSection>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.supplier')"
|
||||||
|
:value="invoiceIn.supplier.name"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.supplierRef')"
|
||||||
|
:value="invoiceIn.supplierRef"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.currency')"
|
||||||
|
:value="invoiceIn.currency.code"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.docNumber')"
|
||||||
|
:value="`${invoiceIn.serial}/${invoiceIn.serialNumber}`"
|
||||||
|
/>
|
||||||
|
</QCard>
|
||||||
|
<QCard class="vn-one">
|
||||||
|
<QCardSection class="q-pa-none">
|
||||||
|
<a class="header" :href="getLink('basic-data')">
|
||||||
|
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||||
|
<QIcon name="open_in_new" color="primary" />
|
||||||
|
</a>
|
||||||
|
</QCardSection>
|
||||||
|
<VnLv
|
||||||
|
:ellipsis-value="false"
|
||||||
|
:label="t('invoiceIn.summary.issued')"
|
||||||
|
:value="toDate(invoiceIn.issued)"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.operated')"
|
||||||
|
:value="toDate(invoiceIn.operated)"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.bookEntried')"
|
||||||
|
:value="toDate(invoiceIn.bookEntried)"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.bookedDate')"
|
||||||
|
:value="toDate(invoiceIn.booked)"
|
||||||
|
/>
|
||||||
|
</QCard>
|
||||||
|
<QCard class="vn-one">
|
||||||
|
<QCardSection class="q-pa-none">
|
||||||
|
<a class="header" :href="getLink('basic-data')">
|
||||||
|
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||||
|
<QIcon name="open_in_new" color="primary" />
|
||||||
|
</a>
|
||||||
|
</QCardSection>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.sage')"
|
||||||
|
:value="invoiceIn.sageWithholding.withholding"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.vat')"
|
||||||
|
:value="invoiceIn.expenseDeductible?.name"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.company')"
|
||||||
|
:value="invoiceIn.company.code"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.booked')"
|
||||||
|
:value="invoiceIn.isBooked"
|
||||||
|
/>
|
||||||
|
</QCard>
|
||||||
|
<QCard class="vn-one">
|
||||||
|
<QCardSection class="q-pa-none">
|
||||||
|
<a class="header" :href="getLink('basic-data')">
|
||||||
|
{{ t('invoiceIn.pageTitles.basicData') }}
|
||||||
|
<QIcon name="open_in_new" color="primary" />
|
||||||
|
</a>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardSection class="q-pa-none">
|
||||||
|
<div class="bordered q-px-sm q-mx-auto">
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.summary.taxableBase')"
|
||||||
|
:value="toCurrency(invoiceIn.totals.totalTaxableBase)"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
label="Total"
|
||||||
|
:value="toCurrency(invoiceIn.totals.totalVat)"
|
||||||
|
/>
|
||||||
|
<VnLv :label="t('invoiceIn.summary.dueTotal')">
|
||||||
|
<template #value>
|
||||||
|
<QChip
|
||||||
|
dense
|
||||||
|
class="q-pa-xs"
|
||||||
|
:color="amountsNotMatch ? 'negative' : 'transparent'"
|
||||||
|
:title="
|
||||||
|
amountsNotMatch
|
||||||
|
? t('invoiceIn.summary.noMatch')
|
||||||
|
: t('invoiceIn.summary.dueTotal')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ toCurrency(invoiceIn.totals.totalDueDay) }}
|
||||||
|
</QChip>
|
||||||
|
</template>
|
||||||
|
</VnLv>
|
||||||
|
</div>
|
||||||
|
</QCardSection>
|
||||||
|
</QCard>
|
||||||
|
<!--Vat-->
|
||||||
|
<QCard v-if="invoiceIn.invoiceInTax.length">
|
||||||
|
<a class="header" :href="getLink('vat')">
|
||||||
|
{{ t('invoiceIn.card.vat') }}
|
||||||
|
<QIcon name="open_in_new" color="primary" />
|
||||||
|
</a>
|
||||||
|
<QTable
|
||||||
|
:columns="vatColumns"
|
||||||
|
:rows="invoiceIn.invoiceInTax"
|
||||||
|
flat
|
||||||
|
hide-pagination
|
||||||
|
>
|
||||||
|
<template #header="props">
|
||||||
|
<QTr :props="props" class="bg">
|
||||||
|
<QTh v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ t(col.label) }}
|
||||||
|
</QTh>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
<template #bottom-row>
|
||||||
|
<QTr class="bg">
|
||||||
|
<QTd></QTd>
|
||||||
|
<QTd>{{ toCurrency(invoiceIn.totals.totalTaxableBase) }}</QTd>
|
||||||
|
<QTd></QTd>
|
||||||
|
<QTd></QTd>
|
||||||
|
<QTd>{{
|
||||||
|
toCurrency(getTaxTotal(invoiceIn.invoiceInTax))
|
||||||
|
}}</QTd>
|
||||||
|
<QTd></QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</QCard>
|
||||||
|
<!--Due Day-->
|
||||||
|
<QCard v-if="invoiceIn.invoiceInDueDay.length">
|
||||||
|
<a class="header" :href="getLink('due-day')">
|
||||||
|
{{ t('invoiceIn.card.dueDay') }}
|
||||||
|
<QIcon name="open_in_new" color="primary" />
|
||||||
|
</a>
|
||||||
|
<QTable
|
||||||
|
class="full-width"
|
||||||
|
:columns="dueDayColumns"
|
||||||
|
:rows="invoiceIn.invoiceInDueDay"
|
||||||
|
flat
|
||||||
|
hide-pagination
|
||||||
|
>
|
||||||
|
<template #header="props">
|
||||||
|
<QTr :props="props" class="bg">
|
||||||
|
<QTh v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ t(col.label) }}
|
||||||
|
</QTh>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
<template #bottom-row>
|
||||||
|
<QTr class="bg">
|
||||||
|
<QTd></QTd>
|
||||||
|
<QTd></QTd>
|
||||||
|
<QTd>{{ toCurrency(invoiceIn.totals.totalDueDay) }}</QTd>
|
||||||
|
<QTd></QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</QCard>
|
||||||
|
<!--Intrastat-->
|
||||||
|
<QCard v-if="invoiceIn.invoiceInIntrastat.length">
|
||||||
|
<a class="header" :href="getUrl('intrastat')">
|
||||||
|
{{ t('invoiceIn.card.intrastat') }}
|
||||||
|
<QIcon name="open_in_new" color="primary" />
|
||||||
|
</a>
|
||||||
|
<QTable
|
||||||
|
:columns="intrastatColumns"
|
||||||
|
:rows="invoiceIn.invoiceInIntrastat"
|
||||||
|
flat
|
||||||
|
hide-pagination
|
||||||
|
>
|
||||||
|
<template #header="props">
|
||||||
|
<QTr :props="props" class="bg">
|
||||||
|
<QTh v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ t(col.label) }}
|
||||||
|
</QTh>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
<template #bottom-row>
|
||||||
|
<QTr class="bg">
|
||||||
|
<QTd></QTd>
|
||||||
|
<QTd>{{ toCurrency(intrastatTotals.amount) }}</QTd>
|
||||||
|
<QTd>{{ intrastatTotals.net }}</QTd>
|
||||||
|
<QTd>{{ intrastatTotals.stems }}</QTd>
|
||||||
|
<QTd></QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</QCard>
|
||||||
|
</template>
|
||||||
|
</CardSummary>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.bg {
|
||||||
|
background-color: var(--vn-light-gray);
|
||||||
|
}
|
||||||
|
.bordered {
|
||||||
|
border: 1px solid var(--vn-text);
|
||||||
|
max-width: 18em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Search invoice: Buscar factura emitida
|
||||||
|
You can search by invoice reference: Puedes buscar por referencia de la factura
|
||||||
|
</i18n>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup>
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
import InvoiceInSummary from './InvoiceInSummary.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits([...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
|
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QDialog ref="dialogRef" @hide="onDialogHide">
|
||||||
|
<InvoiceInSummary v-if="$props.id" :id="$props.id" />
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.q-dialog .summary .header {
|
||||||
|
position: sticky;
|
||||||
|
z-index: $z-max;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,500 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import { toCurrency } from 'src/filters';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||||
|
import CrudModel from 'src/components/CrudModel.vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
|
||||||
|
const arrayData = useArrayData('InvoiceIn');
|
||||||
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
|
|
||||||
|
const expenses = ref([]);
|
||||||
|
const sageTaxTypes = ref([]);
|
||||||
|
const sageTransactionTypes = ref([]);
|
||||||
|
const rowsSelected = ref([]);
|
||||||
|
const newExpense = ref({
|
||||||
|
code: undefined,
|
||||||
|
isWithheld: false,
|
||||||
|
description: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const invoiceInFormRef = ref();
|
||||||
|
const expensesRef = ref();
|
||||||
|
const newExpenseRef = ref();
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
name: 'expense',
|
||||||
|
label: t('Expense'),
|
||||||
|
field: (row) => row.expenseFk,
|
||||||
|
options: expenses.value,
|
||||||
|
model: 'expenseFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'id',
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 1,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'taxablebase',
|
||||||
|
label: t('Taxable base'),
|
||||||
|
field: (row) => toCurrency(row.taxableBase),
|
||||||
|
model: 'taxableBase',
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 2,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sageiva',
|
||||||
|
label: t('Sage iva'),
|
||||||
|
field: (row) => row.taxTypeSageFk,
|
||||||
|
options: sageTaxTypes.value,
|
||||||
|
model: 'taxTypeSageFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'vat',
|
||||||
|
sortable: true,
|
||||||
|
tabindex: 3,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sagetransaction',
|
||||||
|
label: t('Sage transaction'),
|
||||||
|
field: (row) => row.transactionTypeSageFk,
|
||||||
|
options: sageTransactionTypes.value,
|
||||||
|
model: 'transactionTypeSageFk',
|
||||||
|
optionValue: 'id',
|
||||||
|
optionLabel: 'transaction',
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 4,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rate',
|
||||||
|
label: t('Rate'),
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 5,
|
||||||
|
field: (row) => toCurrency(taxRate(row, row.taxTypeSageFk)),
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'foreignvalue',
|
||||||
|
label: t('Foreign value'),
|
||||||
|
sortable: true,
|
||||||
|
tabIndex: 6,
|
||||||
|
field: (row) => row.foreignValue,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'invoiceInFk',
|
||||||
|
'taxableBase',
|
||||||
|
'expenseFk',
|
||||||
|
'foreignValue',
|
||||||
|
'taxTypeSageFk',
|
||||||
|
'transactionTypeSageFk',
|
||||||
|
],
|
||||||
|
where: {
|
||||||
|
invoiceInFk: route.params.id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNotEuro = (code) => code != 'EUR';
|
||||||
|
|
||||||
|
function taxRate(invoiceInTax) {
|
||||||
jorgep marked this conversation as resolved
Outdated
jsegarra
commented
Lo revisamos Lo revisamos
|
|||||||
|
const sageTaxTypeId = invoiceInTax.taxTypeSageFk;
|
||||||
|
const taxRateSelection = sageTaxTypes.value.find(
|
||||||
|
(transaction) => transaction.id == sageTaxTypeId
|
||||||
|
);
|
||||||
|
const taxTypeSage = taxRateSelection?.rate ?? 0;
|
||||||
|
const taxableBase = invoiceInTax?.taxableBase ?? 0;
|
||||||
|
|
||||||
|
return (taxTypeSage / 100) * taxableBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addExpense() {
|
||||||
|
try {
|
||||||
|
if (!newExpense.value.code) throw new Error(t(`The code can't be empty`));
|
||||||
|
if (isNaN(newExpense.value.code))
|
||||||
|
throw new Error(t(`The code have to be a number`));
|
||||||
|
if (!newExpense.value.description)
|
||||||
|
throw new Error(t(`The description can't be empty`));
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
id: newExpense.value.code,
|
||||||
|
isWithheld: newExpense.value.isWithheld,
|
||||||
|
name: newExpense.value.description,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await axios.post(`Expenses`, data);
|
||||||
|
await expensesRef.value.fetch();
|
||||||
|
quasar.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('globals.dataSaved'),
|
||||||
|
});
|
||||||
|
newExpenseRef.value.hide();
|
||||||
|
} catch (error) {
|
||||||
|
quasar.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: t(`${error.message}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<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="sageTransactionTypes"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (sageTransactionTypes = data)"
|
||||||
|
/>
|
||||||
|
<CrudModel
|
||||||
|
ref="invoiceInFormRef"
|
||||||
|
v-if="invoiceIn"
|
||||||
|
data-key="InvoiceInTaxes"
|
||||||
|
url="InvoiceInTaxes"
|
||||||
|
:filter="filter"
|
||||||
|
:data-required="{ invoiceInFk: route.params.id }"
|
||||||
|
auto-load
|
||||||
|
v-model:selected="rowsSelected"
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<QTable
|
||||||
|
v-model:selected="rowsSelected"
|
||||||
|
selection="multiple"
|
||||||
|
:columns="columns"
|
||||||
|
:rows="rows"
|
||||||
|
row-key="$index"
|
||||||
|
hide-pagination
|
||||||
|
:grid="$q.screen.lt.sm"
|
||||||
|
:pagination="{ rowsPerPage: 0 }"
|
||||||
|
>
|
||||||
|
<template #body-cell-expense="{ row, col }">
|
||||||
|
<QTd auto-width>
|
||||||
|
<VnSelectFilter
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:options="col.options"
|
||||||
|
:option-value="col.optionValue"
|
||||||
|
:option-label="col.optionLabel"
|
||||||
|
:filter-options="['id', 'name']"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
{{ `${scope.opt.id}: ${scope.opt.name}` }}
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<QIcon
|
||||||
|
name="close"
|
||||||
|
@click.stop="value = null"
|
||||||
|
class="cursor-pointer"
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
padding="xs"
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
icon="add_circle"
|
||||||
|
@click.stop="newExpenseRef.show()"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('Create expense') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-taxablebase="{ row }">
|
||||||
|
<QTd>
|
||||||
|
<QInput
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': isNotEuro(invoiceIn.currency.code),
|
||||||
|
}"
|
||||||
|
:disable="isNotEuro(invoiceIn.currency.code)"
|
||||||
|
label=""
|
||||||
|
clear-icon="close"
|
||||||
|
v-model="row.taxableBase"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="euro" size="xs" flat />
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-sageiva="{ row, col }">
|
||||||
|
<QTd>
|
||||||
|
<VnSelectFilter
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:options="col.options"
|
||||||
|
:option-value="col.optionValue"
|
||||||
|
:option-label="col.optionLabel"
|
||||||
|
:filter-options="['id', 'vat']"
|
||||||
|
:autofocus="col.tabIndex == 1"
|
||||||
|
input-debounce="0"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{ scope.opt.vat }}</QItemLabel>
|
||||||
|
<QItemLabel>
|
||||||
|
{{ `#${scope.opt.id}` }}
|
||||||
|
</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-sagetransaction="{ row, col }">
|
||||||
|
<QTd>
|
||||||
|
<VnSelectFilter
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:options="col.options"
|
||||||
|
:option-value="col.optionValue"
|
||||||
|
:option-label="col.optionLabel"
|
||||||
|
:filter-options="['id', 'transaction']"
|
||||||
|
:autofocus="col.tabIndex == 1"
|
||||||
|
input-debounce="0"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{
|
||||||
|
scope.opt.transaction
|
||||||
|
}}</QItemLabel>
|
||||||
|
<QItemLabel>
|
||||||
|
{{ `#${scope.opt.id}` }}
|
||||||
|
</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-foreignvalue="{ row }">
|
||||||
|
<QTd>
|
||||||
|
<QInput
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': !isNotEuro(invoiceIn.currency.code),
|
||||||
|
}"
|
||||||
|
:disable="!isNotEuro(invoiceIn.currency.code)"
|
||||||
|
v-model="row.foreignValue"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<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>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('Expense')"
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row['expenseFk']"
|
||||||
|
:options="expenses"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
:filter-options="['id', 'name']"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
{{ `${scope.opt.id}: ${scope.opt.name}` }}
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
:label="t('Taxable base')"
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': isNotEuro(
|
||||||
|
invoiceIn.currency.code
|
||||||
jorgep marked this conversation as resolved
jsegarra
commented
En InvoiceInDueDay, la condición estaba al revés, correcto? En InvoiceInDueDay, la condición estaba al revés, correcto?
|
|||||||
|
),
|
||||||
|
}"
|
||||||
|
class="full-width"
|
||||||
|
:disable="isNotEuro(invoiceIn.currency.code)"
|
||||||
|
clear-icon="close"
|
||||||
|
v-model="props.row.taxableBase"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="euro" size="xs" flat />
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('Sage iva')"
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row['taxTypeSageFk']"
|
||||||
|
:options="sageTaxTypes"
|
||||||
|
option-value="id"
|
||||||
|
option-label="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>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<VnSelectFilter
|
||||||
|
class="full-width"
|
||||||
|
v-model="props.row['transactionTypeSageFk']"
|
||||||
|
:options="sageTransactionTypes"
|
||||||
|
option-value="id"
|
||||||
|
option-label="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>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
{{ toCurrency(taxRate(props.row)) }}
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
:label="t('Foreign value')"
|
||||||
|
class="full-width"
|
||||||
|
:class="{
|
||||||
|
'no-pointer-events': !isNotEuro(
|
||||||
|
invoiceIn.currency.code
|
||||||
|
),
|
||||||
|
}"
|
||||||
|
:disable="!isNotEuro(invoiceIn.currency.code)"
|
||||||
|
v-model="props.row.foreignValue"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
</QList>
|
||||||
|
</QCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</template>
|
||||||
|
</CrudModel>
|
||||||
|
<QDialog ref="newExpenseRef">
|
||||||
|
<QCard>
|
||||||
|
<QCardSection class="q-pb-none">
|
||||||
|
<QItem class="q-pa-none">
|
||||||
|
<span class="text-primary text-h6 full-width">
|
||||||
|
<QIcon name="edit" class="q-mr-xs" />
|
||||||
|
{{ t('New expense') }}
|
||||||
|
</span>
|
||||||
|
<QBtn icon="close" flat round dense v-close-popup />
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardSection class="q-pt-none">
|
||||||
|
<QItem>
|
||||||
|
<QInput :label="`${t('Code')}*`" v-model="newExpense.code" />
|
||||||
|
<QCheckbox
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
:label="`${t('It\'s a withholding')}`"
|
||||||
|
v-model="newExpense.isWithheld"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QInput
|
||||||
|
:label="`${t('Descripction')}*`"
|
||||||
|
v-model="newExpense.description"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardActions class="justify-end">
|
||||||
|
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup />
|
||||||
|
<QBtn :label="t('globals.save')" color="primary" @click="addExpense" />
|
||||||
|
</QCardActions>
|
||||||
|
</QCard>
|
||||||
|
</QDialog>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
|
<QBtn
|
||||||
|
color="primary"
|
||||||
|
icon="add"
|
||||||
|
size="lg"
|
||||||
|
round
|
||||||
|
@click="invoiceInFormRef.insert()"
|
||||||
|
/>
|
||||||
|
</QPageSticky>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Expense: Gasto
|
||||||
|
Create expense: Crear gasto
|
||||||
|
Add tax: Crear gasto
|
||||||
|
Taxable base: Base imp.
|
||||||
|
Sage tax: Sage iva
|
||||||
|
Sage transaction: Sage transacción
|
||||||
|
Rate: Tasa
|
||||||
|
Foreign value: Divisa
|
||||||
|
New expense: Nuevo gasto
|
||||||
|
Code: Código
|
||||||
|
It's a withholding: Es una retención
|
||||||
|
Descripction: Descripción
|
||||||
|
The code can't be empty: El código no puede estar vacío
|
||||||
|
The description can't be empty: La descripción no puede estar vacía
|
||||||
|
The code have to be a number: El código debe ser un número.
|
||||||
|
</i18n>
|
|
@ -0,0 +1,307 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||||
|
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps({
|
||||||
|
dataKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const suppliers = ref([]);
|
||||||
|
const suppliersRef = ref();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
ref="suppliersRef"
|
||||||
|
url="Suppliers"
|
||||||
|
:filter="{ fields: ['id', 'nickname'] }"
|
||||||
|
order="nickname"
|
||||||
|
limit="30"
|
||||||
|
@on-fetch="(data) => (suppliers = data)"
|
||||||
|
/>
|
||||||
|
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
||||||
|
<template #tags="{ tag, formatFn }">
|
||||||
|
<div class="q-gutter-x-xs">
|
||||||
|
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||||
|
<span>{{ formatFn(tag.value) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body="{ params, searchFn }">
|
||||||
|
<QList dense>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput :label="t('Id or Supplier')" v-model="params.search">
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="badge" size="sm"></QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput
|
||||||
|
:label="t('params.supplierRef')"
|
||||||
|
v-model="params.supplierRef"
|
||||||
|
@input.
|
||||||
|
lazy-rules
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="vn:client" size="sm"></QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('params.supplierFk')"
|
||||||
|
v-model="params.supplierFk"
|
||||||
|
:options="suppliers"
|
||||||
|
option-value="id"
|
||||||
|
option-label="nickname"
|
||||||
|
@input-value="suppliersRef.fetch()"
|
||||||
|
>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput :label="t('params.fi')" v-model="params.fi" lazy-rules>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="badge" size="sm"></QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput
|
||||||
|
:label="t('params.serialNumber')"
|
||||||
|
v-model="params.serialNumber"
|
||||||
|
lazy-rules
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="badge" size="sm"></QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput
|
||||||
|
:label="t('params.serial')"
|
||||||
|
v-model="params.serial"
|
||||||
|
lazy-rules
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="badge" size="sm"></QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput :label="t('Amount')" v-model="params.amount" lazy-rules>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="euro" size="sm"></QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-mb-md">
|
||||||
|
<QItemSection>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('params.isBooked')"
|
||||||
|
v-model="params.isBooked"
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
toggle-indeterminate
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QExpansionItem :label="t('More options')" expand-separator>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput
|
||||||
|
:label="t('params.awb')"
|
||||||
|
v-model="params.awbCode"
|
||||||
|
lazy-rules
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="badge" size="sm"></QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput
|
||||||
|
:label="t('params.account')"
|
||||||
|
v-model="params.account"
|
||||||
|
lazy-rules
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<QIcon name="person" size="sm" />
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput :label="t('From')" v-model="params.from" mask="date">
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="params.from" landscape>
|
||||||
|
<div
|
||||||
|
class="row items-center justify-end q-gutter-sm"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.cancel')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.confirm')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
@click="save"
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput :label="t('To')" v-model="params.to" mask="date">
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="params.to" landscape>
|
||||||
|
<div
|
||||||
|
class="row items-center justify-end q-gutter-sm"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.cancel')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.confirm')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
@click="save"
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QInput
|
||||||
|
:label="t('Issued')"
|
||||||
|
v-model="params.issued"
|
||||||
|
mask="date"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
<QPopupProxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<QDate v-model="params.issued" landscape>
|
||||||
|
<div
|
||||||
|
class="row items-center justify-end q-gutter-sm"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.cancel')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.confirm')"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
@click="save"
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QDate>
|
||||||
|
</QPopupProxy>
|
||||||
|
</QIcon>
|
||||||
|
</template>
|
||||||
|
</QInput>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</QExpansionItem>
|
||||||
|
</QList>
|
||||||
|
</template>
|
||||||
|
</VnFilterPanel>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
params:
|
||||||
|
search: ID
|
||||||
|
supplierRef: Supplier ref.
|
||||||
|
supplierFk: Supplier
|
||||||
|
fi: Supplier fiscal id
|
||||||
|
clientFk: Customer
|
||||||
|
amount: Amount
|
||||||
|
created: Created
|
||||||
|
awb: AWB
|
||||||
|
dued: Dued
|
||||||
|
serialNumber: Serial Number
|
||||||
|
serial: Serial
|
||||||
|
account: Account
|
||||||
|
isBooked: is booked
|
||||||
|
es:
|
||||||
|
params:
|
||||||
|
search: Contiene
|
||||||
|
supplierRef: Ref. proveedor
|
||||||
|
supplierFk: Proveedor
|
||||||
|
clientFk: Cliente
|
||||||
|
fi: CIF proveedor
|
||||||
|
serialNumber: Num. serie
|
||||||
|
serial: Serie
|
||||||
|
awb: AWB
|
||||||
|
amount: Importe
|
||||||
|
issued: Emitida
|
||||||
|
isBooked: Conciliada
|
||||||
|
account: Cuenta
|
||||||
|
created: Creada
|
||||||
|
dued: Vencida
|
||||||
|
From: Desde
|
||||||
|
To: Hasta
|
||||||
|
Amount: Importe
|
||||||
|
Issued: Fecha factura
|
||||||
|
Id or supplier: Id o proveedor
|
||||||
|
More options: Más opciones
|
||||||
|
</i18n>
|
|
@ -0,0 +1,179 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import { downloadFile } from 'src/composables/downloadFile';
|
||||||
|
import { toDate, toCurrency } from 'src/filters/index';
|
||||||
|
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||||
|
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
import CardList from 'src/components/ui/CardList.vue';
|
||||||
|
import InvoiceInFilter from './InvoiceInFilter.vue';
|
||||||
|
import InvoiceInSummaryDialog from './Card/InvoiceInSummaryDialog.vue';
|
||||||
|
import { getUrl } from 'src/composables/getUrl';
|
||||||
|
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
let url = ref();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
stateStore.rightDrawer = true;
|
||||||
|
url.value = await getUrl('');
|
||||||
|
});
|
||||||
|
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
|
|
||||||
|
function navigate(id) {
|
||||||
|
router.push({ path: `/invoice-in/${id}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewSummary(id) {
|
||||||
|
quasar.dialog({
|
||||||
|
component: InvoiceInSummaryDialog,
|
||||||
|
componentProps: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-if="stateStore.isHeaderMounted()">
|
||||||
|
<Teleport to="#searchbar">
|
||||||
|
<VnSearchbar
|
||||||
|
data-key="InvoiceInList"
|
||||||
|
:label="t('Search invoice')"
|
||||||
|
:info="t('You can search by invoice reference')"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<Teleport to="#actions-append">
|
||||||
|
<div class="row q-gutter-x-sm">
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
@click="stateStore.toggleRightDrawer()"
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
icon="menu"
|
||||||
|
>
|
||||||
|
<QTooltip bottom anchor="bottom right">
|
||||||
|
{{ t('globals.collapseMenu') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||||
|
<QScrollArea class="fit text-grey-8">
|
||||||
|
<InvoiceInFilter data-key="InvoiceInList" />
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
<QPage class="column items-center q-pa-md">
|
||||||
|
<div class="card-list">
|
||||||
|
<VnPaginate
|
||||||
|
data-key="InvoiceInList"
|
||||||
|
url="InvoiceIns/filter"
|
||||||
|
order="issued DESC, id DESC"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<CardList
|
||||||
|
v-for="row of rows"
|
||||||
|
:key="row.id"
|
||||||
|
:title="row.supplierRef"
|
||||||
|
@click="navigate(row.id)"
|
||||||
|
>
|
||||||
|
<template #list-items>
|
||||||
|
<VnLv label="ID" :value="row.id" />
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.list.supplierRef')"
|
||||||
|
:value="row.supplierRef"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.list.supplier')"
|
||||||
|
:value="row.supplierName"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.list.serialNumber')"
|
||||||
|
:value="row.serialNumber"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.list.serial')"
|
||||||
|
:value="row.serial"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.list.issued')"
|
||||||
|
:value="toDate(row.issued)"
|
||||||
|
/>
|
||||||
|
<VnLv :label="t('invoiceIn.list.awb')" :value="row.awbCode" />
|
||||||
|
<VnLv
|
||||||
|
:label="t('invoiceIn.list.amount')"
|
||||||
|
:value="toCurrency(row.amount)"
|
||||||
|
/>
|
||||||
|
<VnLv :label="t('invoiceIn.list.isBooked')">
|
||||||
|
<template #value>
|
||||||
|
<QCheckbox
|
||||||
|
class="no-pointer-events"
|
||||||
|
v-model="row.isBooked"
|
||||||
|
size="xs"
|
||||||
|
:true-value="1"
|
||||||
|
:false-value="0"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VnLv>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
icon="arrow_circle_right"
|
||||||
|
@click.stop="navigate(row.id)"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('components.smartCard.openCard') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
<QBtn flat icon="preview" @click.stop="viewSummary(row.id)">
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('components.smartCard.openSummary') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
icon="cloud_download"
|
||||||
|
@click.stop="downloadFile(row.dmsFk)"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('components.smartCard.downloadFile') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
</CardList>
|
||||||
|
</template>
|
||||||
|
</VnPaginate>
|
||||||
|
</div>
|
||||||
|
</QPage>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
|
<QBtn
|
||||||
|
color="primary"
|
||||||
|
icon="add"
|
||||||
|
size="lg"
|
||||||
|
round
|
||||||
|
:href="`${url}invoice-in/create`"
|
||||||
|
/>
|
||||||
|
</QPageSticky>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card-list {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 60em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Search invoice: Buscar factura emitida
|
||||||
|
You can search by invoice reference: Puedes buscar por referencia de la factura
|
||||||
|
</i18n>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script setup>
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import LeftMenu from 'src/components/LeftMenu.vue';
|
||||||
|
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
|
||||||
|
<QScrollArea class="fit text-grey-8">
|
||||||
|
<LeftMenu />
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
<QPageContainer>
|
||||||
|
<RouterView></RouterView>
|
||||||
|
</QPageContainer>
|
||||||
|
</template>
|
|
@ -2,16 +2,9 @@ import Customer from './customer';
|
||||||
import Ticket from './ticket';
|
import Ticket from './ticket';
|
||||||
import Claim from './claim';
|
import Claim from './claim';
|
||||||
import InvoiceOut from './invoiceOut';
|
import InvoiceOut from './invoiceOut';
|
||||||
|
import invoiceIn from './invoiceIn';
|
||||||
import Worker from './worker';
|
import Worker from './worker';
|
||||||
import Wagon from './wagon';
|
import Wagon from './wagon';
|
||||||
import Route from './route';
|
import Route from './route';
|
||||||
|
|
||||||
export default [
|
export default [Customer, Ticket, Claim, InvoiceOut, invoiceIn, Worker, Wagon, Route];
|
||||||
Customer,
|
|
||||||
Ticket,
|
|
||||||
Claim,
|
|
||||||
InvoiceOut,
|
|
||||||
Worker,
|
|
||||||
Wagon,
|
|
||||||
Route
|
|
||||||
]
|
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
path: '/invoice-in',
|
||||||
|
name: 'InvoiceIn',
|
||||||
|
meta: {
|
||||||
|
title: 'invoiceIns',
|
||||||
|
icon: 'vn:invoice-in',
|
||||||
|
},
|
||||||
|
component: RouterView,
|
||||||
|
redirect: { name: 'InvoiceInMain' },
|
||||||
|
menus: {
|
||||||
|
main: ['InvoiceInList'],
|
||||||
|
card: [
|
||||||
|
'InvoiceInBasicData',
|
||||||
|
'InvoiceInVat',
|
||||||
|
'InvoiceInDueDay',
|
||||||
|
'InvoiceInIntrastat',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'InvoiceInMain',
|
||||||
|
component: () => import('src/pages/InvoiceIn/InvoiceInMain.vue'),
|
||||||
|
redirect: { name: 'InvoiceInList' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'list',
|
||||||
|
name: 'InvoiceInList',
|
||||||
|
meta: {
|
||||||
|
title: 'list',
|
||||||
|
icon: 'view_list',
|
||||||
|
},
|
||||||
|
component: () => import('src/pages/InvoiceIn/InvoiceInList.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'InvoiceInCard',
|
||||||
|
path: ':id',
|
||||||
|
component: () => import('src/pages/InvoiceIn/Card/InvoiceInCard.vue'),
|
||||||
|
redirect: { name: 'InvoiceInSummary' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'InvoiceInSummary',
|
||||||
|
path: 'summary',
|
||||||
|
meta: {
|
||||||
|
title: 'summary',
|
||||||
|
icon: 'view_list',
|
||||||
|
},
|
||||||
|
component: () =>
|
||||||
|
import('src/pages/InvoiceIn/Card/InvoiceInSummary.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'InvoiceInBasicData',
|
||||||
|
path: 'basic-data',
|
||||||
|
meta: {
|
||||||
|
title: 'basicData',
|
||||||
|
icon: 'vn:settings',
|
||||||
|
roles: ['salesPerson'],
|
||||||
|
},
|
||||||
|
component: () =>
|
||||||
|
import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'InvoiceInVat',
|
||||||
|
path: 'vat',
|
||||||
|
meta: {
|
||||||
|
title: 'vat',
|
||||||
|
icon: 'vn:tax',
|
||||||
|
},
|
||||||
|
component: () => import('src/pages/InvoiceIn/Card/InvoiceInVat.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'InvoiceInDueDay',
|
||||||
|
path: 'due-day',
|
||||||
|
meta: {
|
||||||
|
title: 'dueDay',
|
||||||
|
icon: 'vn:calendar',
|
||||||
|
},
|
||||||
|
component: () =>
|
||||||
|
import('src/pages/InvoiceIn/Card/InvoiceInDueDay.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'InvoiceInIntrastat',
|
||||||
|
path: 'intrastat',
|
||||||
|
meta: {
|
||||||
|
title: 'intrastat',
|
||||||
|
icon: 'vn:lines',
|
||||||
|
},
|
||||||
|
component: () =>
|
||||||
|
import('src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -3,6 +3,7 @@ import ticket from './modules/ticket';
|
||||||
import claim from './modules/claim';
|
import claim from './modules/claim';
|
||||||
import worker from './modules/worker';
|
import worker from './modules/worker';
|
||||||
import invoiceOut from './modules/invoiceOut';
|
import invoiceOut from './modules/invoiceOut';
|
||||||
|
import invoiceIn from './modules/invoiceIn';
|
||||||
import wagon from './modules/wagon';
|
import wagon from './modules/wagon';
|
||||||
import route from './modules/route';
|
import route from './modules/route';
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ const routes = [
|
||||||
claim,
|
claim,
|
||||||
worker,
|
worker,
|
||||||
invoiceOut,
|
invoiceOut,
|
||||||
|
invoiceIn,
|
||||||
{
|
{
|
||||||
path: '/:catchAll(.*)*',
|
path: '/:catchAll(.*)*',
|
||||||
name: 'NotFound',
|
name: 'NotFound',
|
||||||
|
|
|
@ -6,7 +6,16 @@ import { useRole } from 'src/composables/useRole';
|
||||||
import routes from 'src/router/modules';
|
import routes from 'src/router/modules';
|
||||||
|
|
||||||
export const useNavigationStore = defineStore('navigationStore', () => {
|
export const useNavigationStore = defineStore('navigationStore', () => {
|
||||||
const modules = ['customer', 'claim', 'ticket', 'invoiceOut', 'worker', 'wagon', 'route'];
|
const modules = [
|
||||||
|
'customer',
|
||||||
|
'claim',
|
||||||
|
'ticket',
|
||||||
|
'invoiceOut',
|
||||||
|
'invoiceIn',
|
||||||
|
'worker',
|
||||||
|
'wagon',
|
||||||
|
'route',
|
||||||
|
];
|
||||||
const pinnedModules = ref([]);
|
const pinnedModules = ref([]);
|
||||||
const role = useRole();
|
const role = useRole();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
describe('InvoiceInBasicData', () => {
|
||||||
|
const selects = '.q-form .q-select';
|
||||||
|
const appendBtns = 'label button';
|
||||||
|
const dialogAppendBtns = '.q-dialog label button';
|
||||||
|
const dialogInputs = '.q-dialog input';
|
||||||
|
const dialogActionBtns = '.q-card__actions button';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/invoice-in/1/basic-data`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edit the provideer and supplier ref', () => {
|
||||||
|
cy.get(selects).eq(0).click();
|
||||||
|
cy.get(selects).eq(0).type('Bros');
|
||||||
|
cy.get(selects).eq(0).type('{enter}');
|
||||||
|
|
||||||
|
cy.get(appendBtns).eq(0).click();
|
||||||
|
cy.get('input').eq(2).type(4739);
|
||||||
|
cy.saveCard();
|
||||||
|
|
||||||
|
cy.get(`${selects} input`).eq(0).invoke('val').should('eq', 'Bros nick');
|
||||||
|
cy.get('input').eq(2).invoke('val').should('eq', '4739');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edit the dms data', () => {
|
||||||
|
const firtsInput = 'Ticket:65';
|
||||||
|
const secondInput = "I don't know what posting here!";
|
||||||
|
|
||||||
|
cy.get(appendBtns).eq(3).click();
|
||||||
|
cy.get(dialogAppendBtns).eq(0).click();
|
||||||
|
cy.get(dialogInputs).eq(0).type(firtsInput);
|
||||||
|
cy.get(dialogAppendBtns).eq(1).click();
|
||||||
|
cy.get('textarea').type(secondInput);
|
||||||
|
cy.get(dialogActionBtns).eq(1).click();
|
||||||
|
|
||||||
|
cy.get(appendBtns).eq(3).click();
|
||||||
|
|
||||||
|
cy.get(dialogInputs).eq(0).invoke('val').should('eq', firtsInput);
|
||||||
|
cy.get('textarea').invoke('val').should('eq', secondInput);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error creating a new dms if a file is not attached', () => {
|
||||||
|
cy.get(appendBtns).eq(2).click();
|
||||||
|
cy.get(appendBtns).eq(1).click();
|
||||||
|
cy.get(dialogActionBtns).eq(1).click();
|
||||||
|
cy.get('.q-notification__message').should(
|
||||||
|
'have.text',
|
||||||
|
"The files can't be empty"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
describe('InvoiceInDueDay', () => {
|
||||||
|
const inputs = 'label input';
|
||||||
|
const inputBtns = 'label button';
|
||||||
|
const addBtn = '.q-page-sticky > div > .q-btn > .q-btn__content';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/invoice-in/6/due-day`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the amount', () => {
|
||||||
|
cy.get(inputBtns).eq(1).click();
|
||||||
|
cy.get(inputs).eq(3).type(23);
|
||||||
|
cy.saveCard();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the first line', () => {
|
||||||
|
cy.removeRow(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new row ', () => {
|
||||||
|
cy.waitForElement('thead');
|
||||||
|
cy.get(addBtn).click();
|
||||||
|
|
||||||
|
cy.saveCard();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
describe('InvoiceInIntrastat', () => {
|
||||||
|
const inputBtns = 'label button';
|
||||||
|
const thirdRow = 'tbody > :nth-child(3)';
|
||||||
|
const firstLineCode = 'tbody > :nth-child(1) > :nth-child(2)';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/invoice-in/1/intrastat`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edit the first line', () => {
|
||||||
|
cy.selectOption(firstLineCode, 'Plantas vivas: Esqueje/injerto, Vid');
|
||||||
|
|
||||||
|
cy.get(inputBtns).eq(1).click();
|
||||||
|
|
||||||
|
cy.saveCard();
|
||||||
|
cy.visit(`/#/invoice-in/1/intrastat`);
|
||||||
|
|
||||||
|
cy.getValue(firstLineCode).should(
|
||||||
|
'have.value',
|
||||||
|
'Plantas vivas: Esqueje/injerto, Vid'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new row', () => {
|
||||||
|
const rowData = [false, 'Plantas vivas: Esqueje/injerto, Vid', 30, 10, 5, 'FR'];
|
||||||
|
|
||||||
|
cy.addRow();
|
||||||
|
cy.fillRow(thirdRow, rowData);
|
||||||
|
cy.saveCard();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the first line', () => {
|
||||||
|
cy.removeRow(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
describe('InvoiceInList', () => {
|
||||||
|
const firstCard = '.q-card:nth-child(1)';
|
||||||
|
const firstId =
|
||||||
|
'.q-card:nth-child(1) .list-items > .vn-label-value:first-child > .value > span';
|
||||||
|
const firstDetailBtn = '.q-card:nth-child(1) .q-btn:nth-child(2)';
|
||||||
|
const summaryHeaders = '.summaryBody .header';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/invoice-in`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect on clicking a invoice', () => {
|
||||||
|
cy.get(firstId)
|
||||||
|
.invoke('text')
|
||||||
|
.then((id) => {
|
||||||
|
cy.get(firstCard).click();
|
||||||
|
cy.url().should('include', `/invoice-in/${id}/summary`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open the details', () => {
|
||||||
|
cy.get(firstDetailBtn).click();
|
||||||
|
cy.get(summaryHeaders).eq(1).contains('Basic data');
|
||||||
|
cy.get(summaryHeaders).eq(4).contains('Vat');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,55 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
describe('InvoiceInVat', () => {
|
||||||
|
const inputs = 'label input';
|
||||||
|
const inputBtns = 'label button';
|
||||||
|
const thirdRow = 'tbody > :nth-child(3)';
|
||||||
|
const firstLineVat = 'tbody > :nth-child(1) > :nth-child(4)';
|
||||||
|
const dialogInputs = '.q-dialog label input';
|
||||||
|
const dialogBtns = '.q-dialog button';
|
||||||
|
const randomInt = Math.floor(Math.random() * 100);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/invoice-in/1/vat`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edit the first line', () => {
|
||||||
|
cy.get(inputBtns).eq(1).click();
|
||||||
|
cy.get(inputs).eq(2).type(23);
|
||||||
|
cy.selectOption(firstLineVat, 'H.P. IVA 21% CEE');
|
||||||
|
|
||||||
|
cy.saveCard();
|
||||||
|
cy.visit(`/#/invoice-in/1/vat`);
|
||||||
|
|
||||||
|
cy.getValue(firstLineVat).should('have.value', 'H.P. IVA 21% CEE');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new row', () => {
|
||||||
|
cy.addRow();
|
||||||
|
cy.fillRow(thirdRow, [true, 2000000001, 30, 'H.P. IVA 10']);
|
||||||
|
cy.saveCard();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the first line', () => {
|
||||||
|
cy.removeRow(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if there are fields undefined', () => {
|
||||||
|
cy.get(inputBtns).eq(0).click();
|
||||||
|
cy.get(dialogBtns).eq(2).click();
|
||||||
|
cy.get('.q-notification__message').should('have.text', "The code can't be empty");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly handle expense addition', () => {
|
||||||
|
cy.get(inputBtns).eq(0).click();
|
||||||
|
|
||||||
|
cy.get(dialogInputs).eq(0).click();
|
||||||
|
cy.get(dialogInputs).eq(0).type(randomInt);
|
||||||
|
cy.get(dialogInputs).eq(1).click();
|
||||||
|
cy.get(dialogInputs).eq(1).type('This is a dummy expense');
|
||||||
|
|
||||||
|
cy.get(dialogBtns).eq(2).click();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||||
|
});
|
||||||
|
});
|
|
@ -108,14 +108,19 @@ Cypress.Commands.add('fillRow', (rowSelector, data) => {
|
||||||
.then((td) => {
|
.then((td) => {
|
||||||
if (td.find('.q-select__dropdown-icon').length) {
|
if (td.find('.q-select__dropdown-icon').length) {
|
||||||
cy.selectOption(td, value);
|
cy.selectOption(td, value);
|
||||||
}
|
} else if (td.find('.q-checkbox__inner').length && value) {
|
||||||
if (td.find('.q-checkbox__inner').length && value) {
|
|
||||||
cy.checkOption(td);
|
cy.checkOption(td);
|
||||||
}
|
} else if (td.find('input[type="text"]') && value)
|
||||||
|
cy.get(td).find('input').type(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('addRow', () => {
|
||||||
|
cy.waitForElement('tbody');
|
||||||
|
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content').click();
|
||||||
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
|
Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
|
||||||
cy.waitForElement('tbody');
|
cy.waitForElement('tbody');
|
||||||
cy.get(rowSelector).within(() => {
|
cy.get(rowSelector).within(() => {
|
||||||
|
@ -131,6 +136,27 @@ Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('removeRow', (rowIndex) => {
|
||||||
|
let rowsBefore;
|
||||||
|
let rowsAfter;
|
||||||
|
|
||||||
|
cy.get('tr')
|
||||||
|
.its('length')
|
||||||
|
.then((length) => {
|
||||||
|
rowsBefore = length;
|
||||||
|
cy.get('.q-checkbox').eq(rowIndex).click();
|
||||||
|
cy.removeCard();
|
||||||
|
cy.get('.q-dialog button').eq(2).click();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
cy.get('tr')
|
||||||
|
.its('length')
|
||||||
|
.then((length) => {
|
||||||
|
rowsAfter = length;
|
||||||
|
expect(rowsBefore).to.eq(rowsAfter + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Cypress.Commands.add('openListSummary', (row) => {
|
Cypress.Commands.add('openListSummary', (row) => {
|
||||||
cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click();
|
cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { vi, describe, expect, it } from 'vitest';
|
||||||
|
import { axios } from 'app/test/vitest/helper';
|
||||||
|
import { downloadFile } from 'src/composables/downloadFile';
|
||||||
|
import { useSession } from 'src/composables/useSession';
|
||||||
|
|
||||||
|
const session = useSession();
|
||||||
|
const token = session.getToken();
|
||||||
|
|
||||||
|
describe('downloadFile', () => {
|
||||||
|
it('should open a new window to download the file', async () => {
|
||||||
|
const url = 'http://localhost:9000';
|
||||||
|
|
||||||
|
vi.spyOn(axios, 'get').mockResolvedValueOnce({ data: url });
|
||||||
|
|
||||||
|
const mockWindowOpen = vi.spyOn(window, 'open');
|
||||||
|
|
||||||
|
await downloadFile(1);
|
||||||
|
|
||||||
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||||
|
`${url}/api/dms/1/downloadFile?access_token=${token}`
|
||||||
|
);
|
||||||
|
|
||||||
|
mockWindowOpen.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { vi, describe, expect, it, beforeAll } from 'vitest';
|
||||||
|
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||||
|
import InvoiceInBasicData from 'src/pages/InvoiceIn/Card/InvoiceInBasicData.vue';
|
||||||
|
|
||||||
|
describe('InvoiceInBasicData', () => {
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
vm = createWrapper(InvoiceInBasicData, {
|
||||||
|
global: {
|
||||||
|
stubs: [],
|
||||||
|
mocks: {
|
||||||
|
fetch: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).vm;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('upsert()', () => {
|
||||||
|
it('should throw an error when data is empty', async () => {
|
||||||
|
vi.spyOn(axios, 'post').mockResolvedValue({ data: [] });
|
||||||
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
|
||||||
|
await vm.upsert();
|
||||||
|
|
||||||
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
message: `The company can't be empty`,
|
||||||
|
type: 'negative',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { vi, describe, expect, it, beforeAll } from 'vitest';
|
||||||
|
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||||
|
import InvoiceInIntrastat from 'src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue';
|
||||||
|
|
||||||
|
describe('InvoiceInIntrastat', () => {
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
vm = createWrapper(InvoiceInIntrastat, {
|
||||||
|
global: {
|
||||||
|
stubs: ['vnPaginate'],
|
||||||
|
mocks: {
|
||||||
|
fetch: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).vm;
|
||||||
|
vi.spyOn(axios, 'get').mockResolvedValue({ data: [{}] });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTotal()', () => {
|
||||||
|
it('should correctly handle the sum', () => {
|
||||||
|
vm.invoceInIntrastat = [
|
||||||
|
{ amount: 10, stems: 162 },
|
||||||
|
{ amount: 20, stems: 21 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const totalAmount = vm.getTotal('amount');
|
||||||
|
const totalStems = vm.getTotal('stems');
|
||||||
|
|
||||||
|
expect(totalAmount).toBe(10 + 20);
|
||||||
|
expect(totalStems).toBe(162 + 21);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { vi, describe, expect, it, beforeAll } from 'vitest';
|
||||||
|
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||||
|
import InvoiceInVat from 'src/pages/InvoiceIn/Card/InvoiceInVat.vue';
|
||||||
|
|
||||||
|
describe('InvoiceInVat', () => {
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
vm = createWrapper(InvoiceInVat, {
|
||||||
|
global: {
|
||||||
|
stubs: [],
|
||||||
|
mocks: {
|
||||||
|
fetch: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).vm;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addExpense()', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
vi.spyOn(axios, 'post').mockResolvedValue({ data: [] });
|
||||||
|
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
|
||||||
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when the code property is undefined', async () => {
|
||||||
|
await vm.addExpense();
|
||||||
|
|
||||||
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
message: `The code can't be empty`,
|
||||||
|
type: 'negative',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly handle expense addition', async () => {
|
||||||
|
vm.newExpense = {
|
||||||
|
code: 123,
|
||||||
|
isWithheld: false,
|
||||||
|
description: 'Descripción del gasto',
|
||||||
|
};
|
||||||
|
|
||||||
|
await vm.addExpense();
|
||||||
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Data saved',
|
||||||
|
type: 'positive',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('taxRate()', () => {
|
||||||
|
it('should correctly compute the tax rate', () => {
|
||||||
|
const invoiceInTax = { taxableBase: 100, taxTypeSageFk: 1 };
|
||||||
|
vm.sageTaxTypes = [
|
||||||
|
{ id: 1, rate: 10 },
|
||||||
|
{ id: 2, rate: 20 },
|
||||||
|
];
|
||||||
|
const result = vm.taxRate(invoiceInTax);
|
||||||
|
expect(result).toBe((10 / 100) * 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 0 if there is not tax rate', () => {
|
||||||
|
const invoiceInTax = { taxableBase: 100, taxTypeSageFk: 1 };
|
||||||
|
vm.sageTaxTypes = [];
|
||||||
|
|
||||||
|
const result = vm.taxRate(invoiceInTax);
|
||||||
|
expect(result).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
tamaño fijo? Es correcto?
Según hablé con Juan sí, lo ideal es que tengan el mismo tamaño.