Compare commits

..

29 Commits

Author SHA1 Message Date
Jose Antonio Tubau 9721dc957f refactor: refs #7527 update test descriptions for consistency and add code field in travel thermographs tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-09 16:01:19 +02:00
Jose Antonio Tubau ffafab33d1 Merge branch 'dev' into 7527-notCreatePaths
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-09 15:00:01 +02:00
Jose Antonio Tubau d5960617f4 fix: refs #7527 create and edit functionality for thermographs, update tests
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-09 14:29:59 +02:00
Jose Antonio Tubau 37773f2ce0 test: refs #7527 add tests to components now use popup to create
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-08 17:31:17 +02:00
Alex Moreno f3a1816508 Merge branch 'dev' of https: refs #7527//gitea.verdnatura.es/verdnatura/salix-front into 7527-notCreatePaths
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-07 10:03:03 +02:00
Alex Moreno 5de59dfd62 Merge branch 'dev' into 7527-notCreatePaths
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-14 11:11:23 +00:00
Alex Moreno 0bf295930d refactor(Travel): refs #7527 use popup to create
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-04-14 13:10:05 +02:00
Alex Moreno a672fd96e7 refactor(TravelThermograph): refs #7527 use popups 2025-04-14 09:05:36 +02:00
Alex Moreno 16a3a76530 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7527-notCreatePaths 2025-04-14 07:35:06 +02:00
Alex Moreno 25fc7ba038 refactor(Ticket): refs #7527 remove /create 2025-04-11 10:39:40 +02:00
Alex Moreno 59461d9f3c refactor(Supplier): refs #7527 remove /create 2025-04-11 10:38:30 +02:00
Alex Moreno 226762bf69 refactor(Route): refs #7527 remove /create 2025-04-11 10:36:40 +02:00
Alex Moreno 637b7585b9 refactor(Order): refs #7527 remove /create 2025-04-11 10:35:21 +02:00
Alex Moreno 1128fc93b8 refactor(InvoiceIn): refs #7527 remove /create 2025-04-11 10:34:53 +02:00
Alex Moreno 57bc50300a refactor(Order): refs #7527 remove /create 2025-04-11 10:34:02 +02:00
Alex Moreno 7610f8ffbd refactor(InvoiceIn): refs #7527 remove /create 2025-04-11 10:32:32 +02:00
Alex Moreno fd51cabccb refactor(i18n): refs #7527 remove unused entryCreate translation from Spanish locale
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-11 10:25:41 +02:00
Alex Moreno dc52061cf2 refactor(EntryCreate): refs #7527 remove unused EntryCreate component
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-04-11 10:25:23 +02:00
Alex Moreno 9b692bd966 refactor(EntryList): refs #7527 redirect alls creates to popup
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-11 10:24:00 +02:00
Alex Moreno 9d92c4c178 refactor(CustomeDms): refs #7527 use VnDmsList
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-11 09:59:52 +02:00
Alex Moreno 80f987085f refactor(i18n): refs #7527 remove fileManagement entry from English and Spanish locales
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-04-11 09:57:06 +02:00
Alex Moreno 33c6a94df4 refactor(CustomeDms): refs #7527 use VnDmsList
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-11 09:56:11 +02:00
Alex Moreno cb9eb61b16 refactor: refs #7527 update filter logic in CustomerSamples.vue and remove unused route in customer.js
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-11 08:50:57 +02:00
Alex Moreno 37265740de refactor: refs #7527 remove console logs from openAddressForm function
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-11 08:45:42 +02:00
Alex Moreno 3f399e5bd4 refactor: refs #7527 customerCreditContracts use popup and add all funcionality
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-11 08:43:18 +02:00
Alex Moreno 0a330bfe8f Merge branch '7527-notCreatePaths' of https://gitea.verdnatura.es/verdnatura/salix-front into 7527-notCreatePaths
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-10 10:32:53 +02:00
Alex Moreno 2486ddef36 feat: refs #7527 improve star icon in edit 2025-04-10 10:32:52 +02:00
Alex Moreno 3e520499ef Merge branch 'dev' into 7527-notCreatePaths
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-10 08:24:37 +00:00
Alex Moreno 9d5ef13bd6 refactor(customerAddress): refs #7527 is popup remove /create /edit
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-10 08:36:14 +02:00
74 changed files with 1432 additions and 2771 deletions

View File

@ -7,7 +7,7 @@ import { QLayout } from 'quasar';
import mainShortcutMixin from './mainShortcutMixin';
import { useCau } from 'src/composables/useCau';
export default boot(({ app, router }) => {
export default boot(({ app }) => {
QForm.mixins = [qFormMixin];
QLayout.mixins = [mainShortcutMixin];
@ -22,14 +22,6 @@ export default boot(({ app, router }) => {
}
switch (response?.status) {
case 401:
if (!router.currentRoute.value.name.toLowerCase().includes('login')) {
message = 'errors.sessionExpired';
} else message = 'login.loginError';
break;
case 403:
if (!message || message.toLowerCase() === 'access denied')
message = 'errors.accessDenied';
case 422:
if (error.name == 'ValidationError')
message += ` "${responseError.details.context}.${Object.keys(

View File

@ -181,10 +181,6 @@ const col = computed(() => {
newColumn.component = 'checkbox';
if ($props.default && !newColumn.component) newColumn.component = $props.default;
if (typeof newColumn.component !== 'string') {
newColumn.attrs = { ...newColumn.component.attrs, autofocus: $props.autofocus };
newColumn.event = { ...newColumn.component.event, ...$props?.eventHandlers };
}
return newColumn;
});

View File

@ -250,6 +250,7 @@ watch(
defineExpose({
create: createForm,
showForm,
openForm,
reload,
redirect: redirectFn,
selected,
@ -646,6 +647,12 @@ const rowCtrlClickFunction = computed(() => {
};
return () => {};
});
function openForm(data) {
showForm.value = !showForm.value;
createForm.value = { ...$props.create, ...data };
}
const handleHeaderSelection = (evt, data) => {
if (evt === 'updateSelected' && selectAll.value) {
selected.value = tableRef.value.rows;
@ -1055,9 +1062,7 @@ const handleHeaderSelection = (evt, data) => {
<QBtn
@click="
() =>
createAsDialog
? (showForm = !showForm)
: handleOnDataSaved(create)
createAsDialog ? openForm(create) : handleOnDataSaved(create)
"
class="cursor-pointer fill-icon"
color="primary"
@ -1076,10 +1081,7 @@ const handleHeaderSelection = (evt, data) => {
</CrudModel>
<QPageSticky v-if="$props.create" :offset="[20, 20]" style="z-index: 2">
<QBtn
@click="
() =>
createAsDialog ? (showForm = !showForm) : handleOnDataSaved(create)
"
@click="() => (createAsDialog ? openForm(create) : handleOnDataSaved(create))"
color="primary"
fab
icon="add"

View File

@ -7,7 +7,6 @@ import axios from 'axios';
import { usePrintService } from 'composables/usePrintService';
import VnUserLink from '../ui/VnUserLink.vue';
import { downloadFile } from 'src/composables/downloadFile';
import VnImg from 'components/ui/VnImg.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnDms from 'src/components/common/VnDms.vue';
@ -52,6 +51,10 @@ const $props = defineProps({
type: String,
required: true,
},
description: {
type: String,
deafult: undefined,
},
});
const dmsFilter = {
@ -338,6 +341,7 @@ defineExpose({
<span
v-if="props.col.component == 'span'"
style="white-space: wrap"
:data-cy="`${props.col.name}_form`"
>{{ props.value }}</span
>
</component>

View File

@ -1,49 +0,0 @@
<script setup>
import { ref, useTemplateRef } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
import VnSelectDialog from './VnSelectDialog.vue';
import CreateNewExpenseForm from '../CreateNewExpenseForm.vue';
import FetchData from '../FetchData.vue';
const model = defineModel({ type: [String, Number, Object] });
const { t } = useI18n();
const expenses = ref([]);
const selectDialogRef = useTemplateRef('selectDialogRef');
async function autocompleteExpense(evt) {
const val = evt.target.value;
if (!val || isNaN(val)) return;
const lookup = expenses.value.find(({ id }) => id == useAccountShortToStandard(val));
if (selectDialogRef.value)
selectDialogRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
}
</script>
<template>
<VnSelectDialog
v-bind="$attrs"
ref="selectDialogRef"
v-model="model"
:options="expenses"
option-value="id"
:option-label="(x) => `${x.id}: ${x.name}`"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
:acls="[{ model: 'Expense', props: '*', accessType: 'WRITE' }]"
@keydown.tab.prevent="autocompleteExpense"
>
<template #form>
<CreateNewExpenseForm @on-data-saved="$refs.expensesRef.fetch()" />
</template>
</VnSelectDialog>
<FetchData
ref="expensesRef"
url="Expenses"
auto-load
@on-fetch="(data) => (expenses = data)"
/>
</template>
<i18n>
es:
Create a new expense: Crear nuevo gasto
</i18n>

View File

@ -220,12 +220,10 @@ globals:
mandates: Mandates
contacts: Contacts
webPayment: Web payment
fileManagement: File management
unpaid: Unpaid
entries: Entries
buys: Buys
dms: File management
entryCreate: New entry
latestBuys: Latest buys
reserves: Reserves
tickets: Tickets
@ -244,14 +242,12 @@ globals:
invoiceOutCreate: Create invoice out
order: Orders
orderList: List
orderCreate: New order
catalog: Catalog
volume: Volume
shelving: Shelving
shelvingList: Shelving List
shelvingCreate: New shelving
invoiceIns: Invoices In
invoiceInCreate: Create invoice in
vat: VAT
labeler: Labeler
dueDay: Due day
@ -278,21 +274,18 @@ globals:
routes: Routes
cmrsList: CMRs
RouteList: List
routeCreate: New route
RouteRoadmap: Roadmaps
RouteRoadmapCreate: Create roadmap
RouteExtendedList: Router
autonomous: Autonomous
suppliers: Suppliers
supplier: Supplier
supplierCreate: New supplier
accounts: Accounts
addresses: Addresses
agencyTerm: Agency agreement
travel: Travels
create: Create
extraCommunity: Extra community
travelCreate: New travel
history: Log
thermographs: Thermograph
items: Items
@ -400,8 +393,6 @@ errors:
updateUserConfig: Error updating user config
tokenConfig: Error fetching token config
writeRequest: The requested operation could not be completed
sessionExpired: Your session has expired. Please log in again
accessDenied: Access denied
claimBeginningQuantity: Cannot import a line with a claimed quantity of 0
login:
title: Login

View File

@ -223,12 +223,10 @@ globals:
mandates: Mandatos
contacts: Contactos
webPayment: Pago web
fileManagement: Gestión documental
unpaid: Impago
entries: Entradas
buys: Compras
dms: Gestión documental
entryCreate: Nueva entrada
latestBuys: Últimas compras
reserves: Reservas
tickets: Tickets
@ -247,14 +245,12 @@ globals:
invoiceOutCreate: Crear fact. emitida
order: Cesta
orderList: Listado
orderCreate: Nueva orden
catalog: Catálogo
volume: Volumen
shelving: Carros
shelvingList: Listado de carros
shelvingCreate: Nuevo carro
invoiceIns: Fact. recibidas
invoiceInCreate: Crear fact. recibida
vat: IVA
labeler: Etiquetas
dueDay: Vencimiento
@ -281,21 +277,18 @@ globals:
routes: Rutas
cmrsList: CMRs
RouteList: Listado
routeCreate: Nueva ruta
RouteRoadmap: Troncales
RouteRoadmapCreate: Crear troncal
RouteExtendedList: Enrutador
autonomous: Autónomos
suppliers: Proveedores
supplier: Proveedor
supplierCreate: Nuevo proveedor
accounts: Cuentas
addresses: Direcciones
agencyTerm: Acuerdo agencia
travel: Envíos
create: Crear
extraCommunity: Extra comunitarios
travelCreate: Nuevo envío
history: Historial
thermographs: Termógrafos
items: Artículos
@ -396,8 +389,6 @@ errors:
updateUserConfig: Error al actualizar la configuración de usuario
tokenConfig: Error al obtener configuración de token
writeRequest: No se pudo completar la operación solicitada
sessionExpired: Tu sesión ha expirado, por favor vuelve a iniciar sesión
accessDenied: Acceso denegado
claimBeginningQuantity: No se puede importar una linea sin una cantidad reclamada
login:
title: Inicio de sesión

View File

@ -1,18 +1,22 @@
<script setup>
import { onBeforeMount, ref, watch } from 'vue';
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useRoute } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import axios from 'axios';
import CustomerAddressForm from '../components/CustomerAddressForm.vue';
import CustomerAddressEdit from '../components/CustomerAddressEdit.vue';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const addresses = ref([]);
const client = ref(null);
const showFormCreate = ref();
const showFormEdit = ref();
const showFormEditModel = ref();
const vnPaginateRef = ref();
const addressFilter = {
fields: [
'id',
@ -27,6 +31,11 @@ const addressFilter = {
'isEqualizated',
'isLogifloraAllowed',
'postalCode',
'agencyModeFk',
'longitude',
'latitude',
'incotermsFk',
'customsAgentFk',
],
order: ['isDefaultAddress DESC', 'isActive DESC', 'id DESC', 'nickname ASC'],
include: [
@ -48,14 +57,15 @@ const addressFilter = {
],
},
},
{
relation: 'agencyMode',
scope: {
fields: ['id', 'name'],
},
},
],
};
onBeforeMount(() => {
const { id } = route.params;
getClientData(id);
});
watch(
() => route.params.id,
(newValue) => {
@ -65,16 +75,13 @@ watch(
);
const getClientData = async (id) => {
try {
const { data } = await axios.get(`Clients/${id}`);
client.value = data;
} catch (error) {
return error;
}
const { data } = await axios.get(`Clients/${id}`);
client.value = data;
};
const isDefaultAddress = (address) => {
return client?.value?.defaultAddressFk === address.id ? 1 : 0;
address.isDefaultAddress = client?.value?.defaultAddressFk === address.id ? 1 : 0;
return address.isDefaultAddress;
};
const setDefault = async (address) => {
@ -88,35 +95,35 @@ const setDefault = async (address) => {
});
};
const sortAddresses = (data) => {
const sortAddresses = async (data) => {
await getClientData(route.params.id);
if (!client.value || !data) return;
addresses.value = data.sort((a, b) => {
return isDefaultAddress(b) - isDefaultAddress(a);
});
openAddressForm();
};
const toCustomerAddressCreate = () => {
router.push({ name: 'CustomerAddressCreate' });
};
const toCustomerAddressEdit = (addressId) => {
router.push({
name: 'CustomerAddressEdit',
params: {
id: route.params.id,
addressId,
},
});
};
function openAddressForm() {
if (route.query.addressId) {
const address = addresses.value.find(
(address) => address.id == +route.query.addressId,
);
if (address) {
showFormEdit.value = true;
showFormEditModel.value = address;
}
}
}
</script>
<template>
<FetchData
ref="vnPaginateRef"
@on-fetch="sortAddresses"
auto-load
data-key="CustomerAddresses"
order="id DESC"
ref="vnPaginateRef"
:filter="addressFilter"
:url="`Clients/${route.params.id}/addresses`"
/>
@ -131,7 +138,7 @@ const toCustomerAddressEdit = (addressId) => {
'q-mb-md': index < addresses.length - 1,
'item-disabled': !item.isActive,
}"
@click="toCustomerAddressEdit(item.id)"
@click="(showFormEdit = !showFormEdit) && (showFormEditModel = item)"
>
<div class="q-ml-xs q-mr-md flex items-center">
<QIcon
@ -144,6 +151,7 @@ const toCustomerAddressEdit = (addressId) => {
name="star"
size="md"
@click.stop="!isDefaultAddress(item) && setDefault(item)"
data-cy="setDefaultAddress"
>
<QTooltip>
{{
@ -200,19 +208,18 @@ const toCustomerAddressEdit = (addressId) => {
v-for="(observation, obIndex) in item.observations"
>
<div class="text-weight-bold q-mr-sm">
{{ observation.observationType.description }}:
{{ observation?.observationType?.description }}:
</div>
<div>{{ observation.description }}</div>
<div>{{ observation?.description }}</div>
</div>
</div>
</div>
</QCardSection>
</QCard>
</div>
<QPageSticky :offset="[18, 18]">
<QBtn
@click.stop="toCustomerAddressCreate()"
@click.stop="showFormCreate = !showFormCreate"
color="primary"
fab
icon="add"
@ -222,6 +229,28 @@ const toCustomerAddressEdit = (addressId) => {
{{ t('New consignee') }}
</QTooltip>
</QPageSticky>
<QDialog v-model="showFormEdit" :full-width="true">
<CustomerAddressEdit
:id="showFormEditModel.id"
v-model="showFormEditModel"
@on-data-saved="() => vnPaginateRef.fetch()"
/>
</QDialog>
<QDialog v-model="showFormCreate" :full-width="true">
<CustomerAddressForm
:is-create="true"
:form-initial-data="{
isDefaultAddress: false,
isActive: true,
isEqualizated: false,
isLogifloraAllowed: false,
}"
:observe-form-changes="false"
:url-create="`Clients/${route.params.id}/createAddress`"
model="client"
@on-data-saved="() => vnPaginateRef.fetch()"
/>
</QDialog>
</template>
<style lang="scss" scoped>

View File

@ -0,0 +1,28 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnDmsList from 'src/components/common/VnDmsList.vue';
import { useArrayData } from 'src/composables/useArrayData';
const arrayData = useArrayData('Customer');
const { t } = useI18n();
</script>
<template>
<VnDmsList
v-if="arrayData?.store?.data"
model="ClientDms"
update-model="Clients"
default-dms-code="paymentsLaw"
filter="clientFk"
:description="
t('ClientFileDescription', {
clientId: $route.params.id,
clientName: arrayData.store.data.socialName,
})
"
/>
</template>
<i18n>
en:
ClientFileDescription: Payment law from client {clientName} ID {clientId}
es:
ClientFileDescription: Ley de pagos del cliente {clientName} ID {clientId}
</i18n>

View File

@ -1,269 +0,0 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { QBadge, QBtn, QCheckbox } from 'quasar';
import { downloadFile } from 'src/composables/downloadFile';
import { toDateTimeFormat } from 'src/filters/date';
import FetchData from 'components/FetchData.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerFileManagementActions from 'src/pages/Customer/components/CustomerFileManagementActions.vue';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const ClientDmsRef = ref(null);
const rows = ref([]);
const filter = {
include: {
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'reference',
'hardCopyNumber',
'workerFk',
'description',
'hasFile',
'file',
'created',
],
include: [
{ relation: 'dmsType', scope: { fields: ['name'] } },
{
relation: 'worker',
scope: {
fields: ['id'],
include: { relation: 'user', scope: { fields: ['name'] } },
},
},
],
},
},
where: { clientFk: route.params.id },
order: ['dmsFk DESC'],
limit: 20,
};
const tableColumnComponents = {
id: {
component: 'span',
props: () => {},
event: () => {},
},
type: {
component: 'span',
props: () => {},
event: () => {},
},
order: {
component: QBadge,
props: () => {},
event: () => {},
},
reference: {
component: 'span',
props: () => {},
event: () => {},
},
description: {
component: 'span',
props: () => {},
event: () => {},
},
original: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
file: {
component: QBtn,
props: () => ({ flat: true }),
event: ({ row }) => downloadFile(row.dmsFk),
},
employee: {
component: QBtn,
props: () => ({ flat: true }),
event: () => {},
},
created: {
component: 'span',
props: () => {},
event: () => {},
},
actions: {
component: CustomerFileManagementActions,
props: (prop) => ({
id: prop.row.dmsFk,
promise: setData,
}),
event: () => {},
},
};
const columns = computed(() => [
{
align: 'left',
field: ({ dms }) => dms.id,
label: t('Id'),
name: 'id',
},
{
align: 'left',
field: ({ dms }) => dms.dmsType.name,
label: t('Type'),
name: 'type',
},
{
align: 'left',
field: ({ dms }) => dms.hardCopyNumber,
label: t('Order'),
name: 'order',
},
{
align: 'left',
field: ({ dms }) => dms.reference,
label: t('Reference'),
name: 'reference',
},
{
align: 'left',
field: ({ dms }) => dms.description,
label: t('Description'),
name: 'description',
},
{
align: 'left',
field: ({ dms }) => dms.hasFile,
label: t('Original'),
name: 'original',
},
{
align: 'left',
field: ({ dms }) => dms.file,
label: t('File'),
name: 'file',
},
{
align: 'left',
field: ({ dms }) => dms.worker.user.name,
label: t('Employee'),
name: 'employee',
},
{
align: 'left',
field: (value) => value.dms.created,
label: t('Created'),
name: 'created',
format: (value) => toDateTimeFormat(value),
},
{
align: 'right',
field: 'actions',
label: '',
name: 'actions',
},
]);
const setData = () => {
ClientDmsRef.value.fetch();
};
const toCustomerFileManagementCreate = () => {
router.push({ name: 'CustomerFileManagementCreate' });
};
</script>
<template>
<FetchData
ref="ClientDmsRef"
:filter="filter"
@on-fetch="(data) => (rows = data)"
auto-load
url="ClientDms"
/>
<QPage class="column items-center q-pa-md">
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 12 }"
:rows="rows"
class="full-width q-mt-md"
row-key="id"
v-if="rows?.length"
>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props" class="cursor-pointer">
<component
:is="
props.col.name === 'order' && !props.value
? 'span'
: tableColumnComponents[props.col.name].component
"
@click="tableColumnComponents[props.col.name].event(props)"
class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)"
>
<template v-if="props.col.name !== 'original'">
<span
:class="{
link:
props.col.name === 'employee' ||
props.col.name === 'file',
}"
>
{{ props.value }}
</span>
</template>
<WorkerDescriptorProxy
:id="props.row.dms.workerFk"
v-if="props.col.name === 'employee'"
/>
</component>
</QTr>
</QTd>
</template>
</QTable>
<h5 class="flex justify-center color-vn-label" v-else>
{{ t('globals.noResults') }}
</h5>
</QPage>
<QPageSticky :offset="[18, 18]">
<QBtn
@click.stop="toCustomerFileManagementCreate()"
color="primary"
fab
v-shortcut="'+'"
icon="add"
/>
<QTooltip>
{{ t('Upload file') }}
</QTooltip>
</QPageSticky>
</template>
<i18n>
es:
Id: Id
Type: Tipo
Order: Orden
Reference: Referencia
Description: Descripción
Original: Original
File: Fichero
Employee: Empleado
Created: Fecha creación
Upload file: Subir fichero
</i18n>

View File

@ -19,9 +19,7 @@ const filter = {
{ relation: 'user', scope: { fields: ['id', 'name'] } },
{ relation: 'company', scope: { fields: ['code'] } },
],
where: { clientFk: route.params.id },
order: ['created DESC'],
limit: 20,
};
const columns = computed(() => [
@ -76,6 +74,7 @@ const tableRef = ref();
data-key="CustomerSamples"
auto-load
:user-filter="filter"
:filter="{ where: { clientFk: route.params.id } }"
url="ClientSamples"
:columns="columns"
:disable-option="{ card: true }"

View File

@ -4,16 +4,14 @@ import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import axios from 'axios';
import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import CustomerAddressForm from './CustomerAddressForm.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const route = useRoute();
@ -22,31 +20,31 @@ const quasar = useQuasar();
const urlUpdate = ref('');
const agencyModes = ref([]);
const incoterms = ref([]);
const customsAgents = ref([]);
const observationTypes = ref([]);
const notes = ref([]);
const customsAgents = ref([]);
let originalNotes = [];
const deletes = ref([]);
const model = defineModel({ type: Object });
const notes = ref(model.value.observations || []);
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
onBeforeMount(() => {
urlUpdate.value = `Clients/${route.params.id}/updateAddress/${route.params.addressId}`;
urlUpdate.value = `Clients/${route.params.id}/updateAddress/${$props.id}`;
});
const getData = async (observations) => {
observationTypes.value = observations;
if (observationTypes.value.length) {
const filter = {
fields: ['id', 'addressFk', 'observationTypeFk', 'description'],
where: { addressFk: `${route.params.addressId}` },
};
const { data } = await axios.get('AddressObservations', {
params: { filter: JSON.stringify(filter) },
});
if (data.length) {
originalNotes = data;
notes.value = originalNotes
if (notes.value.length) {
originalNotes = JSON.parse(JSON.stringify(notes.value));
notes.value = notes.value
.map((observation) => {
const type = observationTypes.value.find(
(type) => type.id === observation.observationTypeFk,
@ -56,7 +54,7 @@ const getData = async (observations) => {
$isNew: false,
$oldData: null,
$orgIndex: null,
addressFk: `${route.params.addressId}`,
addressFk: `${$props.id}`,
description: observation.description,
id: observation.id,
observationTypeFk: type.id,
@ -68,22 +66,6 @@ const getData = async (observations) => {
}
};
const addNote = () => {
notes.value.push({
$isNew: true,
$oldData: null,
$orgIndex: null,
addressFk: `${route.params.addressId}`,
description: '',
observationTypeFk: '',
});
};
const deleteNote = (id, index) => {
deletes.value.push(id);
notes.value.splice(index, 1);
};
const updateAddress = async (data) => {
await axios.patch(urlUpdate.value, data);
};
@ -116,8 +98,9 @@ function cleanPayload(payload) {
async function updateAll({ data, payload }) {
await updateObservations(payload);
await updateAddress(data);
toCustomerAddress();
emit('onDataSaved');
}
function getPayload() {
return {
creates: notes.value.filter((note) => note.$isNew),
@ -162,23 +145,21 @@ async function handleDialog(data) {
}
}
const toCustomerAddress = () => {
notes.value = [];
deletes.value = [];
router.push({
name: 'CustomerAddress',
params: {
id: route.params.id,
},
const addNote = () => {
notes.value.push({
$isNew: true,
$oldData: null,
$orgIndex: null,
addressFk: `${$props.id}`,
description: '',
observationTypeFk: '',
});
};
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postalCode = code;
data.city = town;
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
const deleteNote = (id, index) => {
deletes.value.push(id);
notes.value.splice(index, 1);
};
</script>
<template>
@ -194,207 +175,77 @@ function handleLocation(data, location) {
url="CustomsAgents"
/>
<FetchData @on-fetch="getData" auto-load url="ObservationTypes" />
<FormModel
:observe-form-changes="false"
<CustomerAddressForm
:url-update="urlUpdate"
:url="`Addresses/${route.params.addressId}`"
:form-initial-data="model"
:save-fn="handleDialog"
auto-load
>
<template #moreActions>
<QBtn
:label="t('globals.cancel')"
@click="toCustomerAddress"
color="primary"
flat
icon="close"
/>
</template>
<template #form="{ data, validate }">
<VnRow>
<div class="col">
<QCheckbox :label="t('Enabled')" v-model="data.isActive" />
</div>
<div class="col">
<QCheckbox
:label="t('Is equalizated')"
v-model="data.isEqualizated"
/>
</div>
<div class="col">
<QCheckbox
:label="t('Is Loginflora allowed')"
v-model="data.isLogifloraAllowed"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput :label="t('Consignee')" clearable v-model="data.nickname" />
</div>
<div class="col">
<VnInput :label="t('Street')" clearable v-model="data.street" />
</div>
</VnRow>
<VnRow>
<div class="col">
<VnLocation
:rules="validate('Worker.postcode')"
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
:location="{
postcode: data.postalCode,
city: data.city,
province: data.province,
country: data.province?.country,
}"
@update:model-value="(location) => handleLocation(data, location)"
></VnLocation>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnSelect
:label="t('Agency')"
:options="agencyModes"
:rules="validate('route.agencyFk')"
hide-selected
option-label="name"
option-value="id"
v-model="data.agencyModeFk"
/>
</div>
<div class="col">
<VnInput :label="t('Phone')" clearable v-model="data.phone" />
</div>
<div class="col">
<VnInput :label="t('Mobile')" clearable v-model="data.mobile" />
</div>
</VnRow>
<VnRow>
<VnSelect
:label="t('Incoterms')"
:options="incoterms"
hide-selected
option-label="name"
option-value="code"
v-model="data.incotermsFk"
/>
<VnSelectDialog
:label="t('Customs agent')"
:options="customsAgents"
hide-selected
option-label="fiscalName"
option-value="id"
v-model="data.customsAgentFk"
:tooltip="t('New customs agent')"
<template #more-form="{ validate }">
<div class="column">
<h4 class="q-mb-xs">{{ t('Notes') }}</h4>
<VnRow
v-if="!isPopup"
:key="index"
class="row q-gutter-md q-mb-md"
v-for="(note, index) in notes"
>
<template #form>
<CustomerNewCustomsAgent />
</template>
</VnSelectDialog>
</VnRow>
<VnRow>
<VnInputNumber
:label="t('Longitude')"
clearable
v-model="data.longitude"
:decimal-places="7"
:positive="false"
/>
<VnInputNumber
:label="t('Latitude')"
clearable
v-model="data.latitude"
:decimal-places="7"
:positive="false"
/>
</VnRow>
<h4 class="q-mb-xs">{{ t('Notes') }}</h4>
<VnRow
:key="index"
class="row q-gutter-md q-mb-md"
v-for="(note, index) in notes"
>
<VnSelect
:label="t('Observation type')"
:options="observationTypes"
hide-selected
option-label="description"
option-value="id"
v-model="note.observationTypeFk"
/>
<VnInput
:label="t('Description')"
:rules="validate('route.description')"
clearable
v-model="note.description"
/>
<QIcon
:style="{ flex: 0, 'align-self': $q.screen.gt.xs ? 'end' : 'center' }"
@click.stop="deleteNote(note.id, index)"
class="cursor-pointer"
<VnSelect
:label="t('Observation type')"
:options="observationTypes"
hide-selected
option-label="description"
option-value="id"
v-model="note.observationTypeFk"
/>
<VnInput
:label="t('Description')"
:rules="validate('route.description')"
clearable
v-model="note.description"
/>
<QIcon
:style="{
flex: 0,
'align-self': $q.screen.gt.xs ? 'end' : 'center',
}"
@click.stop="deleteNote(note.id, index)"
class="cursor-pointer"
color="primary"
name="delete"
size="sm"
>
<QTooltip>
{{ t('Remove note') }}
</QTooltip>
</QIcon>
</VnRow>
<QBtn
@click.stop="addNote()"
icon="add"
v-shortcut="'+'"
color="primary"
name="delete"
size="sm"
style="width: 10%"
data-cy="addNoteBtn_form"
>
<QTooltip>
{{ t('Remove note') }}
{{ t('Add note') }}
</QTooltip>
</QIcon>
</VnRow>
<QBtn
@click.stop="addNote()"
class="cursor-pointer add-icon q-mt-md"
flat
icon="add"
v-shortcut="'+'"
>
<QTooltip>
{{ t('Add note') }}
</QTooltip>
</QBtn>
</QBtn>
</div>
</template>
</FormModel>
</CustomerAddressForm>
</template>
<style lang="scss" scoped>
.add-icon {
background-color: $primary;
border-radius: 50px;
}
</style>
<i18n>
es:
Enabled: Activo
Is equalizated: Recargo de equivalencia
Is Loginflora allowed: Compra directa en Holanda
Consignee: Consignatario
Street: Dirección fiscal
Postcode: Código postal
City: Población
Province: Provincia
Agency: Agencia
Phone: Teléfono
Mobile: Movíl
Incoterms: Incoterms
Customs agent: Agente de aduanas
New customs agent: Nuevo agente de aduanas
Notes: Notas
Observation type: Tipo de observación
Description: Descripción
Add note: Añadir nota
Remove note: Eliminar nota
Longitude: Longitud
Latitude: Latitud
confirmTicket: ¿Desea modificar también los estados de todos los tickets que están a punto de ser servidos?
confirmDeletionMessage: Si le das a aceptar, se modificaran todas las notas de los ticket a futuro
en:
confirmTicket: Do you also want to modify the states of all the tickets that are about to be served?
confirmDeletionMessage: If you click accept, all the notes of the future tickets will be modified
es:
Notes: Notas
Observation type: Tipo de observación
Description: Descripción
Add note: Añadir nota
Remove note: Eliminar nota
confirmTicket: ¿Desea modificar también los estados de todos los tickets que están a punto de ser servidos?
confirmDeletionMessage: Si le das a aceptar, se modificaran todas las notas de los ticket a futuro
en:
confirmTicket: Do you also want to modify the states of all the tickets that are about to be served?
confirmDeletionMessage: If you click accept, all the notes of the future tickets will be modified
</i18n>

View File

@ -1,36 +1,35 @@
<script setup>
import { reactive, ref } from 'vue';
import { onBeforeMount, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useRoute } from 'vue-router';
import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const formInitialData = reactive({ isDefaultAddress: false });
const urlUpdate = ref('');
const agencyModes = ref([]);
const incoterms = ref([]);
const customsAgents = ref([]);
const toCustomerAddress = () => {
router.push({
name: 'CustomerAddress',
params: {
id: route.params.id,
},
});
};
const $props = defineProps({
isCreate: {
type: Boolean,
default: false,
},
});
onBeforeMount(() => {
urlUpdate.value = `Clients/${route.params.id}/updateAddress/${route.params.addressId}`;
});
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postalCode = code;
@ -38,33 +37,21 @@ function handleLocation(data, location) {
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
function onAgentCreated({ id, fiscalName }, data) {
customsAgents.value.push({ id, fiscalName });
data.customsAgentFk = id;
}
</script>
<template>
<FetchData
@on-fetch="(data) => (customsAgents = data)"
auto-load
url="CustomsAgents"
/>
<FetchData
@on-fetch="(data) => (agencyModes = data)"
auto-load
url="AgencyModes/isActive"
/>
<FetchData @on-fetch="(data) => (incoterms = data)" auto-load url="Incoterms" />
<FormModel
:form-initial-data="formInitialData"
:observe-form-changes="false"
:url-create="`Clients/${route.params.id}/createAddress`"
@on-data-saved="toCustomerAddress()"
model="client"
>
<FetchData
@on-fetch="(data) => (customsAgents = data)"
auto-load
url="CustomsAgents"
/>
<FormModelPopup v-bind="$attrs" v-on="$attrs" :reload="false">
<template #moreActions>
<QBtn
:label="t('globals.cancel')"
@ -74,34 +61,53 @@ function onAgentCreated({ id, fiscalName }, data) {
icon="close"
/>
</template>
<template #form="{ data, validate }">
<QCheckbox :label="t('Default')" v-model="data.isDefaultAddress" />
<template #form-inputs="{ data, validate }">
<VnRow>
<VnInput
:label="t('Consignee')"
required
clearable
v-model="data.nickname"
<QCheckbox
:label="t('Default')"
v-model="data.isDefaultAddress"
checked-icon="star"
unchecked-icon="star"
indeterminate-icon="star"
size="lg"
color="primary"
:class="{ 'fill-icon': !!data.isDefaultAddress }"
data-cy="isDefaultAddress_form"
/>
<VnInput
:label="t('Street address')"
clearable
v-model="data.street"
required
<QCheckbox
:label="t('Enabled')"
v-model="data.isActive"
data-cy="enabled_form"
/>
<QCheckbox
:label="t('Is equalizated')"
v-model="data.isEqualizated"
data-cy="isEqualizated_form"
/>
<QCheckbox
:label="t('Is Loginflora allowed')"
v-model="data.isLogifloraAllowed"
data-cy="isLoginfloraAllowed_form"
/>
</VnRow>
<VnLocation
:rules="validate('Worker.postcode')"
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
/>
<div class="row justify-between q-gutter-md q-mb-md">
<VnRow>
<VnInput :label="t('Consignee')" clearable v-model="data.nickname" />
<VnInput :label="t('Street')" clearable v-model="data.street" />
</VnRow>
<VnRow>
<VnLocation
:rules="validate('Worker.postcode')"
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
:location="{
postcode: data.postalCode,
city: data.city,
province: data.province,
country: data.province?.country,
}"
@update:model-value="(location) => handleLocation(data, location)"
></VnLocation>
</VnRow>
<VnRow>
<VnSelect
:label="t('Agency')"
:options="agencyModes"
@ -110,17 +116,10 @@ function onAgentCreated({ id, fiscalName }, data) {
option-label="name"
option-value="id"
v-model="data.agencyModeFk"
class="col"
/>
<VnInput class="col" :label="t('Phone')" clearable v-model="data.phone" />
<VnInput
class="col"
:label="t('Mobile')"
clearable
v-model="data.mobile"
/>
</div>
<VnInput :label="t('Phone')" clearable v-model="data.phone" />
<VnInput :label="t('Mobile')" clearable v-model="data.mobile" />
</VnRow>
<VnRow>
<VnSelect
:label="t('Incoterms')"
@ -130,23 +129,17 @@ function onAgentCreated({ id, fiscalName }, data) {
option-value="code"
v-model="data.incotermsFk"
/>
<VnSelectDialog
url="CustomsAgents"
:label="t('Customs agent')"
:options="customsAgents"
hide-selected
option-label="fiscalName"
option-value="id"
v-model="data.customsAgentFk"
:tooltip="t('Create a new expense')"
:tooltip="t('New customs agent')"
>
<template #form>
<CustomerNewCustomsAgent
@on-data-saved="
(requestResponse) => onAgentCreated(requestResponse, data)
"
/>
<CustomerNewCustomsAgent />
</template>
</VnSelectDialog>
</VnRow>
@ -166,13 +159,13 @@ function onAgentCreated({ id, fiscalName }, data) {
:positive="false"
/>
</VnRow>
<slot name="more-form" :validate />
</template>
</FormModel>
</FormModelPopup>
</template>
<style lang="scss" scoped>
.add-icon {
cursor: pointer;
background-color: $primary;
border-radius: 50px;
}
@ -180,9 +173,11 @@ function onAgentCreated({ id, fiscalName }, data) {
<i18n>
es:
Default: Predeterminado
Enabled: Activo
Is equalizated: Recargo de equivalencia
Is Loginflora allowed: Compra directa en Holanda
Consignee: Consignatario
Street address: Dirección postal
Street: Dirección fiscal
Postcode: Código postal
City: Población
Province: Provincia
@ -191,6 +186,17 @@ es:
Mobile: Movíl
Incoterms: Incoterms
Customs agent: Agente de aduanas
New customs agent: Nuevo agente de aduanas
Notes: Notas
Observation type: Tipo de observación
Description: Descripción
Add note: Añadir nota
Remove note: Eliminar nota
Longitude: Longitud
Latitude: Latitud
confirmTicket: ¿Desea modificar también los estados de todos los tickets que están a punto de ser servidos?
confirmDeletionMessage: Si le das a aceptar, se modificaran todas las notas de los ticket a futuro
en:
confirmTicket: Do you also want to modify the states of all the tickets that are about to be served?
confirmDeletionMessage: If you click accept, all the notes of the future tickets will be modified
</i18n>

View File

@ -1,96 +0,0 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import { usePrintService } from 'composables/usePrintService';
import { downloadFile } from 'src/composables/downloadFile';
import CustomerFileManagementDelete from 'src/pages/Customer/components/CustomerFileManagementDelete.vue';
const { t } = useI18n();
const quasar = useQuasar();
const route = useRoute();
const router = useRouter();
const { openReport } = usePrintService();
const $props = defineProps({
id: {
type: Number,
required: true,
},
promise: {
type: Function,
required: true,
},
});
const setDownloadFile = () => openReport(`dms/${$props.id}/downloadFile`, {}, '_blank');
const toCustomerFileManagementEdit = () => {
router.push({
name: 'CustomerFileManagementEdit',
params: {
id: route.params.id,
dmsId: $props.id,
},
});
};
const showCustomerFileManagementDeleteDialog = () => {
quasar.dialog({
component: CustomerFileManagementDelete,
componentProps: {
id: $props.id,
promise: setData,
},
});
};
const setData = () => {
$props.promise();
};
</script>
<template>
<div>
<QIcon
@click.stop="setDownloadFile"
color="primary"
name="cloud_download"
size="sm"
>
<QTooltip>
{{ t('actionFile', { action: t('globals.download') }) }}
</QTooltip>
</QIcon>
<QIcon
@click.stop="toCustomerFileManagementEdit"
class="q-ml-md"
color="primary"
name="edit"
size="sm"
>
<QTooltip>
{{ t('actionFile', { action: t('globals.edit') }) }}
</QTooltip>
</QIcon>
<QIcon
@click.stop="showCustomerFileManagementDeleteDialog"
class="q-ml-md"
color="primary"
name="delete"
size="sm"
>
<QTooltip>
{{ t('actionFile', { action: t('globals.remove') }) }}
</QTooltip>
</QIcon>
</div>
</template>
<i18n>
en:
actionFile: '{action} file'
es:
actionFile: '{action} fichero'
</i18n>

View File

@ -1,260 +0,0 @@
<script setup>
import { onBeforeMount, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { useState } from 'src/composables/useState';
import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { notify } = useNotify();
const { t } = useI18n();
const { validate } = useValidator();
const route = useRoute();
const router = useRouter();
const state = useState();
const user = state.getUser();
const filterFindOne = { where: { code: 'paymentsLaw' } };
const filterCompanies = { order: ['code'] };
const filterWarehouses = { order: ['name'] };
const inputFileRef = ref();
const client = ref({});
const findOne = ref([]);
const allowedContentTypes = ref([]);
const optionsCompanies = ref([]);
const optionsWarehouses = ref([]);
const optionsDmsTypes = ref([]);
const isLoading = ref(false);
const dms = ref({
hasFile: false,
});
onBeforeMount(() => {
const { companyFk, warehouseFk } = user.value;
dms.value.reference = route.params.id;
dms.value.companyId = companyFk;
dms.value.warehouseId = warehouseFk;
});
watch([client, findOne], ([newClient, newFindOne]) => {
dms.value.description = t('clientFileDescription', {
dmsTypeName: newFindOne.name?.toUpperCase(),
clientName: newClient.name?.toUpperCase(),
clientId: newClient.id,
});
dms.value.dmsTypeId = newFindOne.id;
});
const saveData = async () => {
try {
const formData = new FormData();
const files = dms.value.files;
if (files && files.length > 0) {
for (let file of files) {
formData.append(file.name, file);
}
dms.value.hasFileAttached = true;
const url = `clients/${route.params.id}/uploadFile`;
await axios.post(url, formData, {
params: dms.value,
});
notify('globals.dataSaved', 'positive');
toCustomerFileManagement();
}
} catch (error) {
notify(error.message, 'negative');
}
};
const toCustomerFileManagement = () => {
router.push({ name: 'CustomerFileManagement' });
};
</script>
<template>
<FetchData
@on-fetch="(data) => (client = data)"
auto-load
:url="`Clients/${route.params.id}/getCard`"
/>
<FetchData
:filter="filterFindOne"
@on-fetch="(data) => (findOne = data)"
auto-load
url="DmsTypes/findOne"
/>
<FetchData
@on-fetch="(data) => (allowedContentTypes = data)"
auto-load
url="DmsContainers/allowedContentTypes"
/>
<FetchData
:filter="filterCompanies"
@on-fetch="(data) => (optionsCompanies = data)"
auto-load
url="Companies"
/>
<FetchData
:filter="filterWarehouses"
@on-fetch="(data) => (optionsWarehouses = data)"
auto-load
url="Warehouses"
/>
<FetchData
:filter="filterWarehouses"
@on-fetch="(data) => (optionsDmsTypes = data)"
auto-load
url="DmsTypes"
/>
<Teleport to="#st-actions">
<QBtnGroup push class="q-gutter-x-sm">
<QBtn
:disabled="isLoading"
:label="t('globals.cancel')"
:loading="isLoading"
@click="toCustomerFileManagement"
color="primary"
flat
icon="close"
/>
<QBtn
:disabled="isLoading"
:label="t('globals.save')"
:loading="isLoading"
@click.stop="saveData"
color="primary"
icon="save"
/>
</QBtnGroup>
</Teleport>
<QCard class="q-pa-lg">
<QCardSection>
<QForm>
<VnRow>
<div class="col">
<VnInput
:label="t('Reference')"
clearable
v-model="dms.reference"
/>
</div>
<div class="col">
<VnSelect
:label="t('Company')"
:options="optionsCompanies"
:rules="validate('entry.companyFk')"
option-label="code"
option-value="id"
v-model="dms.companyId"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnSelect
:label="t('Warehouse')"
:options="optionsWarehouses"
option-label="name"
option-value="id"
v-model="dms.warehouseId"
/>
</div>
<div class="col">
<VnSelect
:label="t('Type')"
:options="optionsDmsTypes"
option-label="name"
option-value="id"
v-model="dms.dmsTypeId"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
:label="t('Description')"
:rules="validate('route.description')"
clearable
type="textarea"
v-model="dms.description"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<QFile
ref="inputFileRef"
class="required"
:label="t('File')"
v-model="dms.files"
multiple
:accept="allowedContentTypes.join(',')"
clearable
clear-icon="close"
>
<template #append>
<QBtn
icon="vn:attach"
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>
</div>
</VnRow>
<QCheckbox
:label="t('Generate identifier for original file')"
v-model="dms.hasFile"
/>
</QForm>
</QCardSection>
</QCard>
</template>
<i18n>
en:
clientFileDescription: '{dmsTypeName} FROM CLIENT {clientName} ID {clientId}'
es:
Reference: Referencia
Company: Empresa
Warehouse: Almacén
Type: Tipo
Description: Descripción
clientFileDescription: '{dmsTypeName} DEL CLIENTE {clientName} ID {clientId}'
File: Fichero
Select a file: Selecciona un fichero
Allowed content types: Tipos de archivo permitidos
Generate identifier for original file: Generar identificador para archivo original
</i18n>

View File

@ -1,82 +0,0 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useDialogPluginComponent } from 'quasar';
import axios from 'axios';
import useNotify from 'src/composables/useNotify';
const $props = defineProps({
id: {
type: Number,
required: true,
},
promise: {
type: Function,
required: true,
},
});
const { dialogRef } = useDialogPluginComponent();
const { notify } = useNotify();
const { t } = useI18n();
const closeButton = ref(null);
const isLoading = ref(false);
const deleteDms = async () => {
isLoading.value = true;
try {
await axios.post(`ClientDms/${$props.id}/removeFile`);
if ($props.promise) await $props.promise();
notify('globals.dataDeleted', 'positive');
} catch (error) {
notify(error.message, 'negative');
} finally {
closeButton.value.click();
isLoading.value = false;
}
};
</script>
<template>
<QDialog ref="dialogRef">
<QCard class="q-pa-md q-mb-md">
<span ref="closeButton" class="row justify-end close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<QCardSection>
<div class="mt-1 text-h6">{{ t('This file will be deleted') }}</div>
<div>{{ t('Are you sure you want to continue?') }}</div>
</QCardSection>
<QCardActions class="flex justify-end">
<QBtn
:disabled="isLoading"
:label="t('globals.cancel')"
:loading="isLoading"
class="q-mr-xl"
color="primary"
flat
v-close-popup
/>
<QBtn
:disabled="isLoading"
:label="t('globals.save')"
:loading="isLoading"
@click.stop="deleteDms"
color="primary"
/>
</QCardActions>
</QCard>
</QDialog>
</template>
<i18n>
es:
This file will be deleted: Este fichero va a ser borrado
Are you sure you want to continue?: ¿Seguro que quieres continuar?
</i18n>

View File

@ -1,237 +0,0 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { notify } = useNotify();
const { t } = useI18n();
const { validate } = useValidator();
const route = useRoute();
const router = useRouter();
const filterCompanies = { order: ['code'] };
const filterWarehouses = { order: ['name'] };
const inputFileRef = ref();
const allowedContentTypes = ref([]);
const optionsCompanies = ref([]);
const optionsWarehouses = ref([]);
const optionsDmsTypes = ref([]);
const isLoading = ref(false);
const dms = ref({
hasFile: true,
});
const setCurrentDms = (data) => {
dms.value.reference = data.reference;
dms.value.companyId = data.companyFk;
dms.value.warehouseId = data.warehouseFk;
dms.value.dmsTypeId = data.dmsTypeFk;
dms.value.description = data.description;
};
const saveData = async () => {
try {
const formData = new FormData();
const files = dms.value.files;
if (files && files.length > 0) {
for (let file of files) {
formData.append(file.name, file);
}
dms.value.hasFileAttached = true;
const url = `dms/${route.params.dmsId}/updateFile`;
await axios.post(url, formData, {
params: dms.value,
});
notify('globals.dataSaved', 'positive');
toCustomerFileManagement();
}
} catch (error) {
notify(error.message, 'negative');
}
};
const toCustomerFileManagement = () => {
router.push({ name: 'CustomerFileManagement' });
};
</script>
<template>
<FetchData :url="`Dms/${route.params.dmsId}`" @on-fetch="setCurrentDms" auto-load />
<FetchData
@on-fetch="(data) => (allowedContentTypes = data)"
auto-load
url="DmsContainers/allowedContentTypes"
/>
<FetchData
:filter="filterCompanies"
@on-fetch="(data) => (optionsCompanies = data)"
auto-load
url="Companies"
/>
<FetchData
:filter="filterWarehouses"
@on-fetch="(data) => (optionsWarehouses = data)"
auto-load
url="Warehouses"
/>
<FetchData
:filter="filterWarehouses"
@on-fetch="(data) => (optionsDmsTypes = data)"
auto-load
url="DmsTypes"
/>
<Teleport to="#st-actions">
<QBtnGroup push class="q-gutter-x-sm">
<QBtn
:disabled="isLoading"
:label="t('globals.cancel')"
:loading="isLoading"
@click="toCustomerFileManagement"
color="primary"
flat
icon="close"
/>
<QBtn
:disabled="isLoading"
:label="t('globals.save')"
:loading="isLoading"
@click.stop="saveData"
color="primary"
icon="save"
/>
</QBtnGroup>
</Teleport>
<QCard class="q-pa-lg">
<QCardSection>
<QForm>
<VnRow>
<div class="col">
<VnInput
:label="t('Reference')"
clearable
v-model="dms.reference"
/>
</div>
<div class="col">
<VnSelect
:label="t('Company')"
:options="optionsCompanies"
:rules="validate('entry.companyFk')"
option-label="code"
option-value="id"
v-model="dms.companyId"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnSelect
:label="t('Warehouse')"
:options="optionsWarehouses"
option-label="name"
option-value="id"
v-model="dms.warehouseId"
/>
</div>
<div class="col">
<VnSelect
:label="t('Type')"
:options="optionsDmsTypes"
option-label="name"
option-value="id"
v-model="dms.dmsTypeId"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
:label="t('Description')"
:rules="validate('route.description')"
clearable
type="textarea"
v-model="dms.description"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<QFile
ref="inputFileRef"
class="required"
:label="t('File')"
v-model="dms.files"
multiple
:accept="allowedContentTypes.join(',')"
clearable
clear-icon="close"
>
<template #append>
<QBtn
icon="vn:attach"
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>
</div>
</VnRow>
<QCheckbox
:label="t('Generate identifier for original file')"
v-model="dms.hasFile"
disable
/>
</QForm>
</QCardSection>
</QCard>
</template>
<i18n>
en:
clientFileDescription: '{dmsTypeName} FROM CLIENT {clientName} ID {clientId}'
es:
Reference: Referencia
Company: Empresa
Warehouse: Almacén
Type: Tipo
Description: Descripción
clientFileDescription: '{dmsTypeName} DEL CLIENTE {clientName} ID {clientId}'
File: Fichero
Select a file: Selecciona un fichero
Allowed content types: Tipos de archivo permitidos
Generate identifier for original file: Generar identificador para archivo original
</i18n>

View File

@ -29,7 +29,7 @@ const saveData = async () => {
finished: timestamp,
};
await axios.patch(`CreditClassifications/${$props.id}`, payload);
$props.promise();
await $props.promise();
closeButton.value.click();
};
</script>

View File

@ -1,135 +0,0 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const state = useState();
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const stateStore = useStateStore();
const user = state.getUser();
const newEntryForm = reactive({
supplierFk: null,
travelFk: Number(route.query?.travelFk) || null,
companyFk: user.value.companyFk || null,
});
const travelsOptions = ref([]);
const companiesOptions = ref([]);
const redirectToEntryBasicData = (_, { id }) => {
router.push({ name: 'EntryBasicData', params: { id } });
};
</script>
<template>
<FetchData
url="Travels/filter"
:filter="{ fields: ['id', 'warehouseInName'] }"
order="id"
@on-fetch="(data) => (travelsOptions = data)"
auto-load
/>
<FetchData
ref="companiesRef"
url="Companies"
:filter="{ fields: ['id', 'code'] }"
order="code"
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
url="Entries/filter"
custom-route-redirect-name="EntrySummary"
data-key="Entry"
:label="t('Search entries')"
:info="t('You can search by entry reference')"
/>
</Teleport>
</template>
<QPage>
<VnSubToolbar />
<FormModel
url-create="Entries"
model="entry"
:form-initial-data="newEntryForm"
@on-data-saved="redirectToEntryBasicData"
>
<template #form="{ data, validate }">
<VnRow>
<VnSelectSupplier
class="full-width"
v-model="data.supplierFk"
hide-selected
:required="true"
:rules="validate('entry.supplierFk')"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('Travel')"
class="full-width"
v-model="data.travelFk"
:options="travelsOptions"
option-value="id"
option-label="warehouseInName"
map-options
hide-selected
:required="true"
:rules="validate('entry.travelFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
>{{ scope.opt?.agencyModeName }} -
{{ scope.opt?.warehouseInName }} ({{
toDate(scope.opt?.shipped)
}}) &#x2192; {{ scope.opt?.warehouseOutName }} ({{
toDate(scope.opt?.landed)
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
</VnRow>
<VnRow>
<VnSelect
:label="t('Company')"
class="full-width"
v-model="data.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
map-options
hide-selected
:required="true"
:rules="validate('entry.companyFk')"
/>
</VnRow>
</template>
</FormModel>
</QPage>
</template>
<i18n>
es:
Travel: Envío
Company: Empresa
</i18n>

View File

@ -73,7 +73,6 @@ const columns = computed(() => [
optionLabel: 'code',
options: companies.value,
},
orderBy: false,
},
{
name: 'warehouse',

View File

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

View File

@ -1,101 +0,0 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const state = useState();
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const stateStore = useStateStore();
const user = state.getUser();
const newInvoiceIn = reactive({
supplierFk: +route.query?.supplierFk || null,
supplierRef: null,
companyFk: user.value.companyFk || null,
issued: Date.vnNew(),
});
const companies = ref([]);
const redirectToInvoiceInBasicData = (__, { id }) => {
router.push({ name: 'InvoiceInBasicData', params: { id } });
};
</script>
<template>
<FetchData
ref="companiesRef"
url="Companies"
:filter="{ fields: ['id', 'code'] }"
order="code"
@on-fetch="(data) => (companies = data)"
auto-load
/>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
custom-route-redirect-name="InvoiceInSummary"
data-key="InvoiceInSummary"
/>
</Teleport>
</template>
<QPage>
<VnSubToolbar />
<FormModel
url-create="InvoiceIns"
model="InvoiceIn"
:form-initial-data="newInvoiceIn"
@on-data-saved="redirectToInvoiceInBasicData"
>
<template #form="{ data, validate }">
<VnRow>
<VnSelectSupplier
v-model="data.supplierFk"
hide-selected
:required="true"
:rules="validate('entry.supplierFk')"
/>
<VnInput
:label="t('invoiceIn.list.supplierRef')"
v-model="data.supplierRef"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('Company')"
v-model="data.companyFk"
:options="companies"
option-value="id"
option-label="code"
map-options
hide-selected
:required="true"
:rules="validate('invoiceIn.companyFk')"
/>
<VnInputDate
:label="t('invoiceIn.summary.issued')"
v-model="data.issued"
/>
</VnRow>
</template>
</FormModel>
</QPage>
</template>
<i18n>
es:
Travel: Envío
Company: Empresa
</i18n>

View File

@ -1,11 +1,12 @@
<script setup>
import { ref } from 'vue';
import { Notify } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnInputPassword from 'src/components/common/VnInputPassword.vue';
import { useSession } from 'src/composables/useSession';
import { useLogin } from 'src/composables/useLogin';
import useNotify from 'src/composables/useNotify';
import VnLogo from 'components/ui/VnLogo.vue';
import VnInput from 'src/components/common/VnInput.vue';
import axios from 'axios';
@ -14,14 +15,16 @@ const session = useSession();
const loginCache = useLogin();
const router = useRouter();
const { t } = useI18n();
const { notify } = useNotify();
const username = ref('');
const password = ref('');
const keepLogin = ref(true);
async function onSubmit() {
const params = { user: username.value, password: password.value };
const params = {
user: username.value,
password: password.value,
};
try {
const { data } = await axios.post('Accounts/login', params);
if (!data) return;
@ -30,7 +33,11 @@ async function onSubmit() {
await session.setLogin(data);
} catch (res) {
if (res.response?.data?.error?.code === 'REQUIRES_2FA') {
notify(t('login.twoFactorRequired'), 'warning', 'phoneLink_lock');
Notify.create({
message: t('login.twoFactorRequired'),
icon: 'phoneLink_lock',
type: 'warning',
});
params.keepLogin = keepLogin.value;
loginCache.setUser(params);
return router.push({
@ -38,7 +45,10 @@ async function onSubmit() {
query: router.currentRoute.value?.query,
});
}
throw res;
Notify.create({
message: t('login.loginError'),
type: 'negative',
});
}
}
</script>

View File

@ -1,57 +0,0 @@
<script setup>
import { reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useStateStore } from 'stores/useStateStore';
const router = useRouter();
const { t } = useI18n();
const stateStore = useStateStore();
const newSupplierForm = reactive({
name: null,
});
const redirectToSupplierFiscalData = (_, responseData) => {
router.push({ name: 'SupplierFiscalData', params: { id: responseData.id } });
};
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="SuppliersList"
:limit="20"
:label="t('Search suppliers')"
/>
</Teleport>
</template>
<QPage>
<VnSubToolbar />
<FormModel
url-create="Suppliers/newSupplier"
model="supplier"
:form-initial-data="newSupplierForm"
@on-data-saved="redirectToSupplierFiscalData"
>
<template #form="{ data }">
<VnRow>
<VnInput
v-model="data.name"
:label="t('supplier.create.supplierName')"
@keyup="newSupplierForm.name = newSupplierForm.name.toUpperCase()"
/>
</VnRow>
</template>
</FormModel>
</QPage>
</template>

View File

@ -232,8 +232,9 @@ const onFormModelInit = () => {
const redirectToCustomerAddress = () => {
router.push({
name: 'CustomerAddressEditCard',
params: { id: clientId.value, addressId: addressId.value },
name: 'CustomerAddress',
params: { id: clientId.value },
query: { addressId: addressId.value },
});
};

View File

@ -1,200 +0,0 @@
<script setup>
import { useRoute, useRouter } from 'vue-router';
import { onBeforeMount, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { getClient } from 'src/pages/Customer/composables/getClient';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
import { useState } from 'composables/useState';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const state = useState();
const user = state.getUser();
const initialFormState = reactive({
clientId: Number(route.query?.clientFk) || null,
addressId: null,
agencyModeId: null,
warehouseId: user.value.warehouseFk,
landed: null,
});
const clientOptions = ref([]);
const agenciesOptions = ref([]);
const addressesOptions = ref([]);
const warehousesOptions = ref([]);
const selectedClient = ref(null);
onBeforeMount(async () => {
await onClientSelected(initialFormState);
});
function resetAgenciesSelector(formData) {
agenciesOptions.value = [];
formData.agencyModeId = null;
}
const fetchClient = async (formData) => {
const response = await getClient(formData.clientId);
if (!response) return;
const [client] = response.data;
selectedClient.value = client;
};
const fetchAddresses = async (formData) => {
const response = await getAddresses(formData.clientId);
if (!response) return;
addressesOptions.value = response.data;
const { defaultAddress } = selectedClient.value;
formData.addressId = defaultAddress.id;
};
const onClientSelected = async (formData) => {
resetAgenciesSelector(formData);
await fetchClient(formData);
await fetchAddresses(formData);
};
const fetchAvailableAgencies = async (formData) => {
resetAgenciesSelector(formData);
const response = await getAgencies(formData, selectedClient.value);
if (!response) return;
const { options, agency } = response;
if (options) agenciesOptions.value = options;
if (agency) formData.agencyModeId = agency;
};
const redirectToTicketList = (_, { id }) => {
router.push({ name: 'TicketSummary', params: { id } });
};
</script>
<template>
<FetchData
url="Clients"
@on-fetch="(data) => (clientOptions = data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'], order: 'id' }"
auto-load
/>
<FetchData
url="Warehouses"
@on-fetch="(data) => (warehousesOptions = data)"
:where="{
isForTicket: true,
}"
order="name"
auto-load
/>
<VnSubToolbar />
<div class="q-pa-md">
<FormModel
url-create="Tickets/new"
model="ticket"
:form-initial-data="initialFormState"
@on-data-saved="redirectToTicketList"
>
<template #form="{ data }">
<VnRow>
<div class="col">
<VnSelect
:label="t('globals.client')"
v-model="data.clientId"
:options="clientOptions"
option-value="id"
option-label="name"
hide-selected
@update:model-value="(client) => onClientSelected(data)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.name }}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnSelect
:label="t('ticket.create.address')"
v-model="data.addressId"
:options="addressesOptions"
option-value="id"
option-label="nickname"
hide-selected
:disable="!data.clientId"
@update:model-value="() => fetchAvailableAgencies(data)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.nickname }}
</QItemLabel>
<QItemLabel caption>
{{ `${scope.opt.street}, ${scope.opt.city}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInputDate
placeholder="dd-mm-aaa"
:label="t('globals.landed')"
v-model="data.landed"
@update:model-value="() => fetchAvailableAgencies(data)"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnSelect
:label="t('globals.warehouse')"
v-model="data.warehouseId"
:options="warehousesOptions"
option-value="id"
option-label="name"
hide-selected
@update:model-value="() => fetchAvailableAgencies(data)"
/>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnSelect
:label="t('globals.agency')"
v-model="data.agencyModeId"
:options="agenciesOptions"
option-value="agencyModeFk"
option-label="agencyMode"
hide-selected
:disable="!data.clientId || !data.landed || !data.warehouseId"
/>
</div>
</VnRow>
</template>
</FormModel>
</div>
</template>

View File

@ -21,25 +21,21 @@ const { t } = useI18n();
const router = useRouter();
const quasar = useQuasar();
const { notify } = useNotify();
const canDelete = computed(() => useAcl().hasAcl('Travel', '*', 'WRITE'));
const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
};
function cloneTravel() {
router.push({
name: 'TravelList',
query: { createForm: JSON.stringify($props.travel) },
});
}
const cloneTravel = () => {
const stringifiedTravelData = JSON.stringify($props.travel);
redirectToCreateView(stringifiedTravelData);
};
const cloneTravelWithEntries = async () => {
async function cloneTravelWithEntries() {
const { data } = await axios.post(`Travels/${$props.travel.id}/cloneWithEntries`);
notify('globals.dataSaved', 'positive');
router.push({ name: 'TravelBasicData', params: { id: data.id } });
};
const canDelete = computed(() => useAcl().hasAcl('Travel', '*', 'WRITE'));
const openDeleteEntryDialog = (id) => {
}
function openDeleteEntryDialog(id) {
quasar
.dialog({
component: VnConfirm,
@ -51,13 +47,13 @@ const openDeleteEntryDialog = (id) => {
.onOk(async () => {
await deleteTravel(id);
});
};
}
const deleteTravel = async (id) => {
async function deleteTravel(id) {
await axios.delete(`Travels/${id}`);
router.push({ name: 'TravelList' });
notify('globals.dataDeleted', 'positive');
};
}
</script>
<template>
@ -82,7 +78,10 @@ const deleteTravel = async (id) => {
<QItem v-ripple clickable>
<QItemSection>
<RouterLink
:to="{ name: 'EntryCreate', query: { travelFk: travel.id } }"
:to="{
name: 'EntryList',
query: { createForm: JSON.stringify({ travelFk: travel.id }) },
}"
class="color-vn-text"
>
{{ t('travel.summary.AddEntry') }}

View File

@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import FetchData from 'src/components/FetchData.vue';
@ -13,6 +13,7 @@ import useNotify from 'src/composables/useNotify.js';
import { toDate, toCelsius } from 'src/filters';
import { downloadFile } from 'src/composables/downloadFile';
import { useArrayData } from 'src/composables/useArrayData';
import TravelThermographsForm from './TravelThermographsForm.vue';
const route = useRoute();
const quasar = useQuasar();
@ -22,8 +23,10 @@ const { notify } = useNotify();
const travel = computed(() => useArrayData('Travel').store.data);
const thermographPaginateRef = ref();
const saveType= ref('create');
const warehouses = ref([]);
const showForm = ref(false);
const formData = ref({});
const thermographFilter = {
include: [
{
@ -38,13 +41,21 @@ const thermographFilter = {
fields: ['id', 'name'],
},
},
{
relation: 'dms',
},
],
where: { travelFk: route.params.id },
order: ['created'],
};
const TableColumns = computed(() => {
return [
{
label: t('globals.id'),
field: 'id',
name: 'id',
visible: false,
},
{
label: t('globals.code'),
field: 'thermographFk',
@ -59,23 +70,23 @@ const TableColumns = computed(() => {
},
{
label: t('travel.thermographs.carrier'),
field: (row) => row.agencyMode?.name,
name: 'agencyModeFk',
align: 'left',
format: (row) => row.agencyMode?.name,
},
{
label: t('globals.maxTemperature'),
field: 'maxTemperature',
name: 'maxTemperature',
align: 'left',
format: (val) => toCelsius(val),
format: ({ maxTemperature }) => toCelsius(maxTemperature),
},
{
label: t('globals.minTemperature'),
field: 'minTemperature',
name: 'minTemperature',
align: 'left',
format: (val) => toCelsius(val),
format: ({ minTemperature }) => toCelsius(minTemperature),
},
{
label: t('globals.state'),
@ -88,60 +99,47 @@ const TableColumns = computed(() => {
field: 'warehouseFk',
name: 'destination',
align: 'left',
format: (val) =>
warehouses.value.find((warehouse) => warehouse.id === val)?.name,
format: ({ warehouseFk }) =>
warehouses.value.find((warehouse) => warehouse.id === warehouseFk)?.name,
},
{
label: t('globals.created'),
field: 'created',
name: 'created',
align: 'left',
format: (val) => toDate(val),
format: ({ created }) => toDate(created),
},
{
name: 'downloadFile',
align: 'left',
columnFilter: false,
},
{
name: 'editFile',
align: 'left',
columnFilter: false,
},
{
name: 'removeThermograph',
align: 'left',
columnFilter: false,
},
];
});
const openRemoveDialog = async (id) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
message: t('Are you sure you want to remove the thermograph?'),
},
})
.onOk(async () => {
await removeThermograph(id);
});
};
const redirectToThermographForm = (action, id) => {
const routeDetails = {
name: action === 'create' ? 'TravelThermographsCreate' : 'TravelThermographsEdit',
};
if (action === 'edit' && id) {
routeDetails.query = { id };
} else if (action === 'create') {
routeDetails.query = { agencyModeFk: travel.value?.agencyModeFk };
}
router.push(routeDetails);
quasar.dialog({
component: VnConfirm,
componentProps: {
message: t('Are you sure you want to remove the thermograph?'),
promise: () => removeThermograph(id),
},
});
};
const removeThermograph = async (id) => {
await axios.delete(`Travels/deleteThermograph?id=${id}`);
await thermographPaginateRef.value.fetch();
await thermographPaginateRef.value.reload();
notify(t('Thermograph removed'), 'positive');
};
</script>
@ -154,75 +152,80 @@ const removeThermograph = async (id) => {
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<VnPaginate
<VnTable
ref="thermographPaginateRef"
data-key="TravelThermographs"
url="TravelThermographs"
:user-filter="thermographFilter"
:filter="{ where: { travelFk: route.params.id } }"
:columns="TableColumns"
auto-load
>
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="TableColumns"
:no-data-label="t('No results')"
row-key="id"
class="full-width q-mt-md"
<template #column-downloadFile="{ row }">
<QIcon
name="cloud_download"
color="primary"
size="sm"
class="cursor-pointer"
@click="downloadFile(row.dmsFk)"
>
<template #body-cell-downloadFile="{ row }">
<QTd auto-width>
<QIcon
name="cloud_download"
color="primary"
size="sm"
class="cursor-pointer"
@click="downloadFile(row.dmsFk)"
>
<QTooltip>{{ t('Download file') }}</QTooltip>
</QIcon>
</QTd>
</template>
<template #body-cell-editFile="{ row }">
<QTd auto-width>
<QIcon
name="edit"
color="primary"
size="sm"
class="cursor-pointer"
@click="redirectToThermographForm('edit', row.id)"
>
<QTooltip>{{ t('Edit file') }}</QTooltip>
</QIcon>
</QTd>
</template>
<template #body-cell-removeThermograph="{ row }">
<QTd auto-width>
<QIcon
name="delete"
color="primary"
size="sm"
class="cursor-pointer"
@click="openRemoveDialog(row.id)"
>
<QTooltip>{{ t('Remove thermograph') }}</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
<QTooltip>{{ t('Download file') }}</QTooltip>
</QIcon>
</template>
</VnPaginate>
<template #column-editFile="{ row }">
<QIcon
name="edit"
color="primary"
size="sm"
class="cursor-pointer"
@click="
() => {
formData = row;
showForm = !showForm;
saveType = 'update';
}
"
>
<QTooltip>{{ t('Edit file') }}</QTooltip>
</QIcon>
</template>
<template #column-removeThermograph="{ row }">
<QIcon
name="delete"
color="primary"
size="sm"
class="cursor-pointer"
@click="openRemoveDialog(row.id)"
>
<QTooltip>{{ t('Remove thermograph') }}</QTooltip>
</QIcon>
</template>
</VnTable>
<QPageSticky :offset="[20, 20]">
<QBtn
fab
icon="add"
color="primary"
@click="redirectToThermographForm('create')"
@click="
() => {
formData = { dms: {} };
saveType = 'create';
showForm = !showForm;
}
"
v-shortcut="'+'"
/>
<QTooltip class="text-no-wrap">
{{ t('Add thermograph') }}
</QTooltip>
</QPageSticky>
<QDialog v-model="showForm">
<TravelThermographsForm
:form-initial-data="formData"
:agencyModeFk="travel?.agencyModeFk"
:save-type="saveType"
/>
</QDialog>
</template>
<i18n>

View File

@ -1,6 +1,6 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { ref, onBeforeMount } from 'vue';
import { ref, onBeforeMount, useAttrs } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
@ -11,22 +11,14 @@ import VnInput from 'src/components/common/VnInput.vue';
import CreateThermographForm from 'src/components/CreateThermographForm.vue';
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue';
const props = defineProps({
viewAction: {
type: String,
default: 'create',
},
});
const stateStore = useStateStore();
const attrs = useAttrs();
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const state = useState();
const { notify } = useNotify();
@ -37,26 +29,27 @@ const dmsTypesOptions = ref([]);
const companiesOptions = ref([]);
const warehousesOptions = ref([]);
const temperaturesOptions = ref([]);
const thermographForm = ref({});
const inputFileRef = ref(null);
const agencyModeOptions = ref([]);
onBeforeMount(async () => {
if (props.viewAction === 'create') {
setCreateDefaultParams();
} else {
await setEditDefaultParams();
}
if (route.query.thermographData) {
const thermographData = JSON.parse(route.query.thermographData);
for (let key in thermographForm.value) {
thermographForm.value[key] = thermographData[key];
}
const $props = defineProps({
agencyModeFk: {
type: Number,
default: undefined,
},
saveType: {
type: String,
default: 'create',
}
});
const fetchDmsTypes = async () => {
onBeforeMount(async () => {
if (!attrs['form-initial-data'].thermographFk) {
await setCreateDefaultParams();
}
});
async function fetchDmsTypes() {
const params = {
filter: {
where: { code: 'thermograph' },
@ -64,74 +57,79 @@ const fetchDmsTypes = async () => {
};
const { data } = await axios.get('DmsTypes/findOne', { params });
return data;
};
}
const setCreateDefaultParams = async () => {
async function setCreateDefaultParams() {
const dataResponse = await fetchDmsTypes();
thermographForm.value.companyId = user.value.companyFk;
thermographForm.value.warehouseId = user.value.warehouseFk;
thermographForm.value.reference = route.params.id;
thermographForm.value.dmsTypeId = dataResponse.id;
thermographForm.value.state = 'Ok';
thermographForm.value.agencyModeFk = +route.query.agencyModeFk;
thermographForm.value.description = t('travel.thermographs.travelFileDescription', {
travelId: route.params.id,
}).toUpperCase();
};
attrs['form-initial-data'].companyFk = user.value.companyFk;
attrs['form-initial-data'].warehouseFk = user.value.warehouseFk;
attrs['form-initial-data'].dms.reference = route.params.id;
attrs['form-initial-data'].dms.dmsTypeFk = dataResponse.id;
attrs['form-initial-data'].state = 'Ok';
attrs['form-initial-data'].agencyModeFk = $props.agencyModeFk;
attrs['form-initial-data'].dms.description = t(
'travel.thermographs.travelFileDescription',
{
travelId: route.params.id,
},
).toUpperCase();
}
const setEditDefaultParams = async () => {
const filterObj = { include: { relation: 'dms' } };
const filter = encodeURIComponent(JSON.stringify(filterObj));
const { data } = await axios.get(
`TravelThermographs/${route.query.id}?filter=${filter}`,
);
async function onSubmit(data) {
const basicData = {
state: data.result,
reference: data.dms.reference,
warehouseId: +data.warehouseFk,
companyId: +data.dms.companyFk,
dmsTypeId: +data.dms.dmsTypeFk,
description: data.dms.description,
hasFile: data.dms.hasFile,
maxTemperature: +data.maxTemperature,
minTemperature: +data.minTemperature,
temperatureFk: data.temperatureFk,
agencyModeFk: +data.agencyModeFk,
};
if (data) {
thermographForm.value.thermographFk = data.thermographFk;
thermographForm.value.state = data.result;
thermographForm.value.reference = data.dms?.reference;
thermographForm.value.warehouseId = data.warehouseFk;
thermographForm.value.companyId = data.dms?.companyFk;
thermographForm.value.dmsTypeId = data.dms?.dmsTypeFk;
thermographForm.value.description = data.dms?.description || '';
thermographForm.value.hasFile = data.dms?.hasFile;
thermographForm.value.hasFileAttached = false;
thermographForm.value.maxTemperature = data.maxTemperature;
thermographForm.value.minTemperature = data.minTemperature;
thermographForm.value.temperatureFk = data.temperatureFk;
thermographForm.value.travelThermographFk = data.id;
thermographForm.value.agencyModeFk = data.agencyModeFk;
const updateData = {
travelThermographFk: data.id,
thermographFk: data.thermographFk,
}
};
const onSubmit = async () => {
const createData = {
travelThermographFk: data.thermographFk
}
const aditionalData = $props.saveType === 'create' ? createData : updateData
const dataParsed = {...basicData, ...aditionalData };
const formData = new FormData();
if (Array.isArray(thermographForm.value.files)) {
thermographForm.value.hasFileAttached = true;
thermographForm.value.files.forEach((file) => {
if (Array.isArray(data.files)) {
dataParsed.hasFileAttached = true;
data.files.forEach((file) => {
formData.append(file.name, file);
});
}
delete thermographForm.value.files;
delete data.files;
delete dataParsed.dms;
await axios.post(`/travels/${route.params.id}/saveThermograph`, formData, {
params: thermographForm.value,
params: dataParsed,
headers: {
'Content-Type': 'multipart/form-data',
},
});
router.push({ name: 'TravelThermographsIndex' });
notify(t('Thermograph created'), 'positive');
};
}
const onThermographCreated = async (data) => {
await fetchTravelThermographsRef.value.fetch();
thermographForm.value = {
...thermographForm.value,
async function onThermographCreated(newThermograph, data) {
data = {
...data,
travelThermographFk: data.id,
warehouseId: data.warehouseFk,
...newThermograph,
travelThermographFk: newThermograph.id,
warehouseId: newThermograph.warehouseFk,
};
};
}
</script>
<template>
<FetchData
@ -167,161 +165,134 @@ const onThermographCreated = async (data) => {
auto-load
url="AgencyModeIncomings"
/>
<QPage class="column items-center full-width">
<QForm
:form-initial-data="thermographForm"
:observe-form-changes="viewAction === 'create'"
:default-actions="true"
@submit="onSubmit()"
class="full-width"
style="max-width: 800px"
>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<div>
<QBtnGroup push class="q-gutter-x-sm">
<slot name="moreActions" />
<QBtn
color="primary"
icon="restart_alt"
flat
@click="reset()"
:label="t('globals.reset')"
<FormModelPopup
v-if="attrs['form-initial-data'].dms?.dmsTypeFk"
v-bind="$attrs"
:observe-form-changes="viewAction === 'create'"
:save-fn="(data) => onSubmit(data)"
>
<template #form-inputs="{ data }">
<VnRow>
<VnSelectDialog
:label="t('travel.thermographs.thermograph')"
v-model="data.thermographFk"
url="TravelThermographs"
:fields="['id', 'thermographFk']"
:where="{
or: [{ travelFk: null }, { travelFk: $route.params.id }],
}"
sort-by="thermographFk ASC"
option-label="thermographFk"
option-filter-value="thermographFk"
:disable="!!$attrs['form-initial-data'].thermographFk"
:tooltip="t('New thermograph')"
:roles-allowed-to-create="['logistic']"
data-key="travelThermographSelect"
>
<template #form>
<CreateThermographForm
@on-data-saved="
(newThermograph) =>{
onThermographCreated(newThermograph, data);
}
"
/>
<QBtn
color="primary"
icon="save"
@click="onSubmit()"
:label="t('globals.save')"
/>
</QBtnGroup>
</div>
</Teleport>
<QCard class="q-pa-lg">
<VnRow>
<VnSelectDialog
:label="t('travel.thermographs.thermograph')"
v-model="thermographForm.travelThermographFk"
url="TravelThermographs"
:fields="['id', 'thermographFk']"
:where="{
or: [{ travelFk: null }, { travelFk: $route.params.id }],
}"
sort-by="thermographFk ASC"
option-label="thermographFk"
option-filter-value="thermographFk"
:disable="viewAction === 'edit'"
:tooltip="t('New thermograph')"
:roles-allowed-to-create="['logistic']"
data-key="travelThermographSelect"
>
<template #form>
<CreateThermographForm
@on-data-saved="onThermographCreated"
/>
</template>
</VnSelectDialog>
<VnInput
v-model="thermographForm.state"
:label="t('globals.state')"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('travel.thermographs.carrier')"
v-model="thermographForm.agencyModeFk"
:options="agencyModeOptions"
option-value="id"
option-label="name"
/>
</template>
</VnSelectDialog>
<VnInput v-model="data.result" :label="t('globals.state')" />
</VnRow>
<VnRow>
<VnSelect
:label="t('travel.thermographs.carrier')"
v-model="data.agencyModeFk"
:options="agencyModeOptions"
option-value="id"
option-label="name"
/>
<VnInput
v-model="thermographForm.reference"
:label="t('globals.reference')"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('globals.type')"
v-model="thermographForm.dmsTypeId"
:options="dmsTypesOptions"
option-value="id"
option-label="name"
/>
<VnSelect
:label="t('globals.company')"
v-model="thermographForm.companyId"
:options="companiesOptions"
option-value="id"
option-label="code"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('globals.warehouse')"
v-model="thermographForm.warehouseId"
:options="warehousesOptions"
option-value="id"
option-label="name"
/>
<VnSelect
:label="t('travel.thermographs.temperature')"
:options="temperaturesOptions"
hide-selected
option-label="name"
option-value="code"
v-model="thermographForm.temperatureFk"
:required="true"
/>
</VnRow>
<VnRow>
<VnInputNumber
v-model="thermographForm.maxTemperature"
:label="t('globals.maxTemperature')"
/>
<VnInputNumber
v-model="thermographForm.minTemperature"
:label="t('globals.minTemperature')"
/>
</VnRow>
<VnInput v-model="data.dms.reference" :label="t('globals.reference')" />
</VnRow>
<VnRow>
<VnSelect
:label="t('globals.type')"
v-model="data.dms.dmsTypeFk"
:options="dmsTypesOptions"
option-value="id"
option-label="name"
/>
<VnSelect
:label="t('globals.company')"
v-model="data.dms.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('globals.warehouse')"
v-model="data.warehouseFk"
:options="warehousesOptions"
option-value="id"
option-label="name"
/>
<VnSelect
:label="t('travel.thermographs.temperature')"
:options="temperaturesOptions"
hide-selected
option-label="name"
option-value="code"
v-model="data.temperatureFk"
:required="true"
/>
</VnRow>
<VnRow>
<VnInputNumber
v-model="data.maxTemperature"
:label="t('globals.maxTemperature')"
/>
<VnInputNumber
v-model="data.minTemperature"
:label="t('globals.minTemperature')"
/>
</VnRow>
<VnRow v-if="viewAction === 'edit'" class="row q-gutter-md q-mb-md">
<QInput
:label="t('globals.description')"
type="textarea"
v-model="thermographForm.description"
fill-input
/>
</VnRow>
<VnRow>
<QFile
ref="inputFileRef"
:label="t('globals.file')"
multiple
:accept="allowedContentTypes"
v-model="thermographForm.files"
>
<template #append>
<QIcon
name="vn:attach"
class="cursor-pointer q-mr-sm"
@click="inputFileRef.pickFiles()"
>
<QTooltip>{{ t('Select files') }}</QTooltip>
</QIcon>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t('globals.allowedFilesText', {
allowedContentTypes: allowedContentTypes,
})
}}</QTooltip>
</QIcon>
</template>
</QFile>
</VnRow>
</QCard>
</QForm>
</QPage>
<VnRow v-if="viewAction === 'edit'" class="row q-gutter-md q-mb-md">
<QInput
:label="t('globals.description')"
type="textarea"
v-model="data.description"
fill-input
/>
</VnRow>
<VnRow>
<QFile
ref="inputFileRef"
:label="t('globals.file')"
multiple
:accept="allowedContentTypes"
v-model="data.files"
>
<template #append>
<QIcon
name="vn:attach"
class="cursor-pointer q-mr-sm"
@click="inputFileRef.pickFiles()"
>
<QTooltip>{{ t('Select files') }}</QTooltip>
</QIcon>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t('globals.allowedFilesText', {
allowedContentTypes: allowedContentTypes,
})
}}</QTooltip>
</QIcon>
</template>
</QFile>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:

View File

@ -1,103 +0,0 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { ref, onBeforeMount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const agenciesOptions = ref([]);
const warehousesOptions = ref([]);
const viewAction = ref();
const newTravelForm = ref({});
onBeforeMount(() => {
viewAction.value = route.query.travelData ? 'clone' : 'create';
if (route.query.travelData) {
const travelData = JSON.parse(route.query.travelData);
newTravelForm.value = { ...newTravelForm.value, ...travelData };
delete newTravelForm.value.id;
}
});
const redirectToTravelBasicData = (_, { id }) => {
router.push({ name: 'TravelBasicData', params: { id } });
};
</script>
<template>
<FetchData
url="AgencyModes"
:filter="{ fields: ['id', 'name'], order: ['name ASC'] }"
@on-fetch="(data) => (agenciesOptions = data)"
auto-load
/>
<FetchData
url="Warehouses"
@on-fetch="(data) => (warehousesOptions = data)"
auto-load
/>
<QPage>
<VnSubToolbar />
<FormModel
url-create="Travels"
model="travelCreate"
:form-initial-data="newTravelForm"
:observe-form-changes="viewAction === 'create'"
@on-data-saved="redirectToTravelBasicData"
>
<template #form="{ data }">
<VnRow>
<VnInput v-model="data.ref" :label="t('globals.reference')" />
<VnSelect
:label="t('globals.agency')"
v-model="data.agencyModeFk"
:options="agenciesOptions"
option-value="agencyFk"
option-label="name"
hide-selected
/>
</VnRow>
<VnRow>
<VnInputDate v-model="data.shipped" :label="t('globals.shipped')" />
<VnInputDate :label="t('globals.landed')" v-model="data.landed" />
</VnRow>
<VnRow>
<VnSelect
:label="t('globals.warehouseOut')"
v-model="data.warehouseOutFk"
:options="warehousesOptions"
option-value="id"
option-label="name"
hide-selected
:where="{
isOrigin: true,
}"
/>
<VnSelect
:label="t('globals.warehouseIn')"
v-model="data.warehouseInFk"
:options="warehousesOptions"
option-value="id"
option-label="name"
hide-selected
:where="{
isDestiny: true,
}"
/>
</VnRow>
</template>
</FormModel>
</QPage>
</template>

View File

@ -28,19 +28,6 @@ const $props = defineProps({
});
const entityId = computed(() => $props.id || route.params.id);
const cloneTravel = (travelData) => {
const stringifiedTravelData = JSON.stringify(travelData);
redirectToCreateView(stringifiedTravelData);
};
const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
};
const redirectCreateEntryView = (travelData) => {
router.push({ name: 'EntryCreate', query: { travelFk: travelData.id } });
};
const columns = computed(() => [
{
name: 'status',
@ -209,6 +196,19 @@ const columns = computed(() => [
],
},
]);
function cloneTravel(travelData) {
const data = { formInitialData: travelData };
delete data.formInitialData.id;
tableRef.value.openForm({ formInitialData: travelData });
}
function redirectCreateEntryView(travelData) {
router.push({
name: 'EntryList',
query: { createForm: JSON.stringify({ travelFk: travelData.id }) },
});
}
</script>
<template>

View File

@ -66,8 +66,6 @@ const excludeType = computed({
const arrayData = useArrayData('ZoneEvents');
const exclusionGeoCreate = async () => {
if (await zoneHasTickets(route.params.id, dated.value)) return;
const params = {
zoneFk: parseInt(route.params.id),
date: dated.value,
@ -89,8 +87,6 @@ const exclusionCreate = async () => {
};
const zoneIds = props.zoneIds?.length ? props.zoneIds : [route.params.id];
for (const id of zoneIds) {
if (await zoneHasTickets(id, dated.value)) return;
const url = `Zones/${id}/exclusions`;
let today = moment(dated.value);
let lastDay = today.clone().add(nMonths, 'months').endOf('month');
@ -127,26 +123,6 @@ const exclusionCreate = async () => {
await refetchEvents();
};
const zoneHasTickets = async (zoneId, date) => {
const filter = {
where: {
zoneFk: zoneId,
shipped: date,
},
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('Tickets', { params });
if (data.length > 0) {
quasar.notify({
message: t('eventsExclusionForm.cantCloseZone'),
type: 'negative',
});
await refetchEvents();
return true;
}
return false;
};
const onSubmit = async () => {
if (excludeType.value === 'all') exclusionCreate();
else exclusionGeoCreate();

View File

@ -80,7 +80,6 @@ eventsExclusionForm:
all: All
specificLocations: Specific locations
rootTreeLabel: Locations where it is not distributed
cantCloseZone: Can not close this zone because there are tickets programmed for that day
eventsInclusionForm:
addEvent: Add event
editEvent: Edit event

View File

@ -81,7 +81,6 @@ eventsExclusionForm:
all: Todo
specificLocations: Localizaciones concretas
rootTreeLabel: Localizaciones en las que no se reparte
cantCloseZone: No se puede cerrar la zona porque hay tickets programados para ese día
eventsInclusionForm:
addEvent: Añadir evento
editEvent: Editar evento

View File

@ -75,33 +75,6 @@ const customerCard = {
component: () =>
import('src/pages/Customer/Card/CustomerAddress.vue'),
},
{
path: 'create',
name: 'CustomerAddressCreate',
meta: {
title: 'address-create',
},
component: () =>
import('src/pages/Customer/components/CustomerAddressCreate.vue'),
},
{
path: ':addressId',
name: 'CustomerAddressEditCard',
redirect: { name: 'CustomerAddressEdit' },
children: [
{
path: 'edit',
name: 'CustomerAddressEdit',
meta: {
title: 'addressEdit',
},
component: () =>
import(
'src/pages/Customer/components/CustomerAddressEdit.vue'
),
},
],
},
],
},
{
@ -212,14 +185,6 @@ const customerCard = {
'src/pages/Customer/Card/CustomerCreditContracts.vue'
),
},
{
path: 'create',
name: 'CustomerCreditContractsCreate',
component: () =>
import(
'src/pages/Customer/components/CustomerCreditContractsCreate.vue'
),
},
{
path: 'insurance/:creditId',
name: 'CustomerCreditContractsInsurance',
@ -274,9 +239,9 @@ const customerCard = {
icon: 'vn:onlinepayment',
},
{
name: 'CustomerFileManagement',
title: 'fileManagement',
icon: 'Upload',
name: 'CustomerDms',
title: 'dms',
icon: 'cloud_upload',
},
{
name: 'CustomerUnpaid',
@ -309,14 +274,6 @@ const customerCard = {
component: () =>
import('src/pages/Customer/Card/CustomerSamples.vue'),
},
{
path: 'create',
name: 'CustomerSamplesCreate',
component: () =>
import(
'src/pages/Customer/components/CustomerSamplesCreate.vue'
),
},
],
},
{
@ -356,47 +313,12 @@ const customerCard = {
import('src/pages/Customer/Card/CustomerWebPayment.vue'),
},
{
path: 'file-management',
name: 'CustomerFileManagement',
path: 'dms',
name: 'CustomerDms',
meta: {
title: 'fileManagement',
title: 'dms',
},
component: () =>
import('src/pages/Customer/Card/CustomerFileManagement.vue'),
},
{
path: 'file-management',
name: 'CustomerFileManagementCard',
redirect: { name: 'CustomerFileManagement' },
children: [
{
path: '',
name: 'CustomerFileManagement',
meta: {
title: 'fileManagement',
},
component: () =>
import(
'src/pages/Customer/Card/CustomerFileManagement.vue'
),
},
{
path: 'create',
name: 'CustomerFileManagementCreate',
component: () =>
import(
'src/pages/Customer/components/CustomerFileManagementCreate.vue'
),
},
{
path: ':dmsId/edit',
name: 'CustomerFileManagementEdit',
component: () =>
import(
'src/pages/Customer/components/CustomerFileManagementEdit.vue'
),
},
],
component: () => import('src/pages/Customer/Card/CustomerDms.vue'),
},
{
path: 'unpaid',

View File

@ -116,15 +116,6 @@ export default {
entryCard,
],
},
{
path: 'create',
name: 'EntryCreate',
meta: {
title: 'entryCreate',
icon: 'add',
},
component: () => import('src/pages/Entry/EntryCreate.vue'),
},
{
path: 'my',
name: 'EntrySupplier',

View File

@ -122,15 +122,7 @@ export default {
invoiceInCard,
],
},
{
path: 'create',
name: 'InvoiceInCreare',
meta: {
title: 'invoiceInCreate',
icon: 'create',
},
component: () => import('src/pages/InvoiceIn/InvoiceInCreate.vue'),
},
{
path: 'serial',
name: 'InvoiceInSerial',

View File

@ -93,15 +93,6 @@ export default {
orderCard,
],
},
{
path: 'create',
name: 'OrderCreate',
meta: {
title: 'orderCreate',
icon: 'add',
},
component: () => import('src/pages/Order/OrderList.vue'),
},
],
},
],

View File

@ -16,7 +16,7 @@ const routeCard = {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('pages/Route/Card/RouteForm.vue'),
component: () => import('pages/Route/Card/RouteBasicData.vue'),
},
{
name: 'RouteSummary',
@ -270,15 +270,6 @@ export default {
},
component: () => import('src/pages/Route/RouteExtendedList.vue'),
},
{
path: 'create',
name: 'RouteCreate',
meta: {
title: 'routeCreate',
icon: 'add',
},
component: () => import('src/pages/Route/Card/RouteForm.vue'),
},
{
path: 'agency-term',
name: 'RouteAutonomous',

View File

@ -180,15 +180,6 @@ export default {
supplierCard,
],
},
{
path: 'create',
name: 'SupplierCreate',
meta: {
title: 'supplierCreate',
icon: 'add',
},
component: () => import('src/pages/Supplier/SupplierCreate.vue'),
},
],
},
],

View File

@ -226,15 +226,6 @@ export default {
ticketCard,
],
},
{
path: 'create',
name: 'TicketCreate',
meta: {
title: 'createTicket',
icon: 'vn:ticketAdd',
},
component: () => import('src/pages/Ticket/TicketCreate.vue'),
},
{
path: 'negative',
redirect: { name: 'TicketNegative' },

View File

@ -42,31 +42,7 @@ const travelCard = {
title: 'thermographs',
icon: 'vn:thermometer',
},
redirect: {
name: 'TravelThermographsIndex',
},
children: [
{
name: 'TravelThermographsIndex',
path: 'index',
component: () =>
import('src/pages/Travel/Card/TravelThermographs.vue'),
},
{
name: 'TravelThermographsCreate',
path: 'create',
props: { viewAction: 'create' },
component: () =>
import('src/pages/Travel/Card/TravelThermographsForm.vue'),
},
{
name: 'TravelThermographsEdit',
path: 'edit',
props: { viewAction: 'edit' },
component: () =>
import('src/pages/Travel/Card/TravelThermographsForm.vue'),
},
],
component: () => import('src/pages/Travel/Card/TravelThermographs.vue'),
},
],
};
@ -115,15 +91,6 @@ export default {
},
component: () => import('src/pages/Travel/ExtraCommunity.vue'),
},
{
path: 'create',
name: 'TravelCreate',
meta: {
title: 'travelCreate',
icon: 'add',
},
component: () => import('src/pages/Travel/TravelCreate.vue'),
},
],
},
],

View File

@ -24,8 +24,13 @@ export CI=true
export TZ=Europe/Madrid
# IMAGES
docker-compose -f test/cypress/docker-compose.yml --project-directory . pull db
docker-compose -f test/cypress/docker-compose.yml --project-directory . pull back
docker build -t registry.verdnatura.es/salix-back:dev -f "$salix_dir/back/Dockerfile" "$salix_dir"
cd "$salix_dir" && npx myt run -t
docker exec vn-database sh -c "rm -rf /mysql-template"
docker exec vn-database sh -c "cp -a /var/lib/mysql /mysql-template"
docker commit vn-database registry.verdnatura.es/salix-db:dev
docker rm -f vn-database
cd "$current_dir"
docker build -f ./docs/Dockerfile.dev -t lilium-dev .
# END IMAGES

View File

@ -1,47 +0,0 @@
/// <reference types="cypress" />
describe('Client consignee', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('#/customer/1107/address');
});
it('Should load layout', () => {
cy.get('.q-card').should('be.visible');
});
it('check as equalizated', () => {
cy.get('.q-card__section > .address-card').then(($el) => {
const addressCards_before = $el.length;
cy.get('.q-page-sticky > div > .q-btn').click();
const addressName = 'test';
cy.dataCy('Consignee_input').type(addressName);
cy.dataCy('Location_select').click();
cy.getOption();
cy.dataCy('Street address_input').type('TEST ADDRESS');
cy.get('.q-btn-group > .q-btn--standard').click();
cy.location('href').should('contain', '#/customer/1107/address');
cy.get('.q-card__section > .address-card').should(
'have.length',
addressCards_before + 1,
);
cy.get('.q-card__section > .address-card')
.eq(addressCards_before)
.should('be.visible')
.get('.text-weight-bold')
.eq(addressCards_before - 1)
.should('contain', addressName)
.click();
});
cy.get(
'.q-card > :nth-child(1) > :nth-child(2) > .q-checkbox > .q-checkbox__inner',
)
.should('have.class', 'q-checkbox__inner--falsy')
.click();
cy.get('.q-btn-group > .q-btn--standard > .q-btn__content').click();
cy.get(
':nth-child(2) > :nth-child(2) > .flex > .q-mr-lg > .q-checkbox__inner',
).should('have.class', 'q-checkbox__inner--truthy');
});
});

View File

@ -1,5 +1,8 @@
/// <reference types="cypress" />
describe('Client credit contracts', () => {
const firstCredit = 123;
const secondCredit = 321;
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
@ -7,9 +10,10 @@ describe('Client credit contracts', () => {
timeout: 5000,
});
});
it('Should add a new contract and an additional credit', () => {
cy.dataCy('createBtn').click();
cy.dataCy('Credit_input').type(123);
cy.dataCy('Credit_input').type(firstCredit);
cy.dataCy('Grade_input').type(9);
cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data created');
@ -19,12 +23,27 @@ describe('Client credit contracts', () => {
it('Should add an additional credit', () => {
cy.dataCy('viewBtn').eq(0).click();
cy.get('.q-page-sticky > div').click();
cy.dataCy('Credit_input').type(321);
cy.dataCy('Credit_input').type(secondCredit);
cy.dataCy('Grade_input').type(89);
cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data created');
});
it('Should view correctly credit contracts', () => {
cy.get('.q-card > .q-card__section')
.first()
.find('[data-cy="vnLvCredit"]')
.first()
.invoke('text')
.should('contain', firstCredit);
cy.get('.q-card > .q-card__section')
.first()
.find('[data-cy="vnLvCredit"]')
.last()
.invoke('text')
.should('contain', secondCredit);
});
it('Should close a contract', () => {
cy.dataCy('closeBtn').eq(0).click();
cy.get('.q-btn--unelevated').click();

View File

@ -0,0 +1,85 @@
/// <reference types="cypress" />
describe('CustomerAddress (consignee)', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('#/customer/1107/address');
});
it('Should change default address', () => {
cy.dataCy('setDefaultAddress')
.first()
.should('have.attr', 'style')
.and('include', '"FILL" 1');
cy.dataCy('setDefaultAddress').last().click();
cy.dataCy('setDefaultAddress')
.first()
.should('have.attr', 'style')
.and('include', '"FILL" 0');
cy.dataCy('setDefaultAddress')
.last()
.should('have.attr', 'style')
.and('include', '"FILL" 1');
});
it('check as equalizated', () => {
cy.get('.q-card__section > .address-card').then(($el) => {
const addressCards_before = $el.length;
cy.get('.q-page-sticky > div > .q-btn').click();
const addressName = 'test';
cy.dataCy('Consignee_input').type(addressName);
cy.dataCy('Location_select').click();
cy.getOption();
cy.dataCy('Street_input').type('TEST ADDRESS');
cy.saveCard();
cy.get('.q-card__section > .address-card').should(
'have.length',
addressCards_before + 1,
);
cy.get('.q-card__section > .address-card')
.eq(addressCards_before)
.should('be.visible')
.get('.text-weight-bold')
.eq(addressCards_before - 1)
.should('contain', addressName)
.click();
});
cy.get('[data-cy="isEqualizated_form"] > .q-checkbox__inner')
.should('have.class', 'q-checkbox__inner--falsy')
.click();
cy.saveCard();
cy.get(
':nth-child(2) > :nth-child(2) > .flex > .q-mr-lg > .q-checkbox__inner',
).should('have.class', 'q-checkbox__inner--truthy');
});
it('Should edit address', () => {
const updateData = {
Consignee: { val: 'Stark tower' },
Street: { val: 'test' },
Location: { val: 'Algemesi', type: 'select' },
Agency: { val: 'inhouse pickup', type: 'select' },
Phone: { val: '3333333333' },
Mobile: { val: '4444444444' },
Incoterms: { val: 'Free Alongside Ship', type: 'select' },
'Customs agent': { val: 'Agent two', type: 'select' },
Longitude: { val: '123' },
Latitude: { val: '123' },
};
cy.get('.address-card').last().click();
cy.fillInForm(updateData);
cy.dataCy('enabled_form').click();
cy.dataCy('isEqualizated_form').click();
cy.dataCy('isLoginfloraAllowed_form').click();
cy.dataCy('addNoteBtn_form').click();
cy.selectOption('[data-cy="Observation type_select"]', '6');
cy.dataCy('Description_input').click().type('Test description');
cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data saved');
cy.get('.address-card').last().should('have.class', 'item-disabled');
});
});

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client balance', () => {
describe('CustomerBalance', () => {
beforeEach(() => {
cy.login('developer');
cy.visit('#/customer/1101/balance');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client basic data', () => {
describe('CustomerBasicData', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client billing data', () => {
describe('CustomerBillingData', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client credits', () => {
describe('CustomerCredits', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -0,0 +1,16 @@
describe('CustomerDms', () => {
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/customer/1104/others/dms`);
});
it('should display customer DMS', () => {
const expectedValue = '104';
cy.get('[data-cy="reference_form"]').each(($el) => {
const actualText = $el.text().trim();
expect(actualText).to.contain(expectedValue);
});
});
});

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client fiscal data', { testIsolation: true }, () => {
describe('CustomerFiscalData', { testIsolation: true }, () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client greuges', () => {
describe('CustomerGreuges', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client list', { testIsolation: true }, () => {
describe('CustomerList', { testIsolation: true }, () => {
beforeEach(() => {
cy.login('developer');
cy.visit('/#/customer/list', {

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client notes', () => {
describe('CustomerNotes', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client recoveries', () => {
describe('CustomerRecoveries', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client notes', () => {
describe('CustomerSms', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" />
describe('Client web-access', () => {
describe('CustomerWebAccess', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');

View File

@ -6,11 +6,14 @@ describe('EntryList', () => {
cy.visit(`/#/entry/list`);
});
it('View popup summary', () => {
it('Should create and delete entry', () => {
cy.createEntry();
cy.get('.q-notification__message').eq(0).should('have.text', 'Data created');
cy.waitForElement('[data-cy="entry-buys"]');
cy.deleteEntry();
});
it('View popup summary', () => {
cy.typeSearchbar('{enter}');
cy.get('button[title="Summary"]').eq(1).should('be.visible').click();
cy.dataCy('entry-summary').should('be.visible');

View File

@ -5,28 +5,33 @@ describe('Logout', { testIsolation: true }, () => {
cy.visit(`/#/dashboard`);
cy.waitForElement('.q-page', 6000);
});
it('should logout', () => {
cy.get('#user').click();
cy.get('#logout').click();
describe('by user', () => {
it('should logout', () => {
cy.get('#user').click();
cy.get('#logout').click();
});
});
it('should throw session expired error if token has expired or is not valid during navigation', () => {
cy.intercept('GET', '**StarredModules**', {
statusCode: 401,
body: {
error: {
statusCode: 401,
name: 'Error',
message: 'Authorization Required',
code: 'AUTHORIZATION_REQUIRED',
describe('not user', () => {
beforeEach(() => {
cy.intercept('GET', '**StarredModules**', {
statusCode: 401,
body: {
error: {
statusCode: 401,
name: 'Error',
message: 'Authorization Required',
code: 'AUTHORIZATION_REQUIRED',
},
},
},
statusMessage: 'AUTHORIZATION_REQUIRED',
}).as('badRequest');
cy.get('.q-list').should('be.visible').first().should('be.visible').click();
cy.wait('@badRequest');
statusMessage: 'AUTHORIZATION_REQUIRED',
}).as('badRequest');
});
cy.checkNotification('Your session has expired. Please log in again');
it('when token not exists', () => {
cy.get('.q-list').should('be.visible').first().should('be.visible').click();
cy.wait('@badRequest');
cy.checkNotification('Authorization Required');
});
});
});

View File

@ -1,4 +1,4 @@
describe.skip('RoadMap', () => {
describe('RoadMap', () => {
const getSelector = (colField) =>
`tr:last-child > [data-col-field="${colField}"] > .no-padding`;

View File

@ -0,0 +1,46 @@
describe('Travel List', () => {
const selectors = {
listItem: '[role="menu"] .q-list .q-item',
addEntryOpt: 'Add entry',
warhouseIn: 'vnLvWarehouse In',
entryTravelReference: 'vnLvReference',
descriptorTitle: 'vnDescriptor_title',
};
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('#/travel/3/summary');
});
it('Should add entry', () => {
const data = {
Supplier: { val: 'FARMER KING', type: 'select' },
Company: { val: 'VNL', type: 'select' },
};
cy.dataCy(selectors.warhouseIn)
.find('.value > span')
.invoke('attr', 'title')
.then((text) => text.trim())
.as('warehouseIn');
cy.dataCy(selectors.descriptorTitle)
.invoke('text')
.then((text) => text.trim())
.as('reference');
cy.openActionsDescriptor();
cy.contains(selectors.listItem, selectors.addEntryOpt).click();
cy.get('@warehouseIn').then((warehouseIn) => {
cy.dataCy('entry-travel-select').invoke('val').should('contain', warehouseIn);
});
cy.fillInForm(data);
cy.dataCy('FormModelPopup_save').click();
cy.get('@reference').then((reference) => {
cy.dataCy(selectors.entryTravelReference).should('contain', reference);
});
});
});

View File

@ -0,0 +1,108 @@
describe('Travel List', () => {
const selectors = {
cloneBtn: 'tableAction-0',
addEntryBtn: 'tableAction-1',
openSummaryBtn: 'tableAction-2',
summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]',
descriptorTitle: 'vnDescriptor_title',
warehouseInField: 'vnTableCell_warehouseInFk',
referenceField: 'vnTableCell_ref',
entryTravelReference: 'vnLvReference',
};
const travelSummaryUrlRegex = /travel\/\d+\/summary/;
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('#/travel/list');
cy.typeSearchbar('{enter}');
});
it('Should create travel', () => {
const data = {
Reference: { val: 'Travel' },
Agency: { val: 'inhouse pickup', type: 'select' },
'Warehouse In': { val: 'Warehouse One', type: 'select' },
Shipped: { val: '1.2', type: 'date' },
'Warehouse Out': { val: 'Warehouse Two', type: 'select' },
Landed: { val: '2.2', type: 'date' },
'Total entries': { val: '1' },
Availabled: { val: '2.2', type: 'date' },
'Available hour': { val: '11:10', type: 'time' },
};
cy.addBtnClick();
cy.fillInForm(data);
cy.dataCy('FormModelPopup_save').click();
cy.dataCy(selectors.descriptorTitle).should('contain', data.Reference.val);
});
it('Should clone travel', () => {
cy.dataCy(selectors.cloneBtn).first().click();
const data = {
Reference: { val: 'Travel clone' },
};
cy.fillInForm(data);
cy.dataCy('FormModelPopup_save').click();
cy.dataCy(selectors.descriptorTitle).should('contain', data.Reference.val);
});
it('Should add entry', () => {
const data = {
Supplier: { val: 'FARMER KING', type: 'select' },
Company: { val: 'VNL', type: 'select' },
};
cy.dataCy(selectors.warehouseInField)
.first()
.invoke('text')
.then((text) => text.trim())
.as('warehouseIn');
cy.dataCy(selectors.referenceField)
.first()
.invoke('text')
.then((text) => text.trim())
.as('reference');
cy.dataCy(selectors.addEntryBtn).first().click();
cy.get('@warehouseIn').then((warehouseIn) => {
cy.dataCy('entry-travel-select').invoke('val').should('contain', warehouseIn);
});
cy.fillInForm(data);
cy.dataCy('FormModelPopup_save').click();
cy.get('@reference').then((reference) => {
cy.dataCy(selectors.entryTravelReference).should('contain', reference);
});
});
it('Should redirect to vehicle summary when click summary icon on summary pop-up', () => {
cy.dataCy(selectors.referenceField)
.first()
.invoke('text')
.then((text) => text.trim())
.as('reference');
cy.dataCy(selectors.openSummaryBtn).first().click();
cy.get(selectors.summaryGoToSummaryBtn).click();
cy.location().should('match', travelSummaryUrlRegex);
cy.get('@reference').then((label) => {
cy.dataCy(selectors.descriptorTitle).should('contain', label);
});
});
it('Should redirect to travel summary when click on row', () => {
cy.dataCy(selectors.referenceField)
.first()
.invoke('text')
.then((text) => text.trim())
.as('reference');
cy.dataCy(selectors.referenceField).first().click();
cy.location().should('match', travelSummaryUrlRegex);
cy.get('@reference').then((label) => {
cy.dataCy(selectors.descriptorTitle).should('contain', label);
});
});
});

View File

@ -0,0 +1,48 @@
describe('TravelThermographs', () => {
const selectors = {
fileInput: 'input[type="file"]',
editBtn: 'vnTableCell_editFile',
removeBtn: 'vnTableCell_removeThermograph',
temperatureField: 'vnTableCell_temperatureFk',
codeField: 'vnTableCell_thermographFk',
}
beforeEach(() => {
cy.login('developer');
cy.visit(`/#/travel/10/thermographs`);
});
it('Should create thermograph', () => {
const data = {
Thermograph: { val: 138350-0, type: 'select' },
State: { val: 'Ok'},
Company: { val: 'VNL', type: 'select' },
Temperature: { val: 'Cool', type: 'select' },
Max: { val: '10' },
Min: { val: '5' },
};
cy.addBtnClick();
cy.fillInForm(data);
cy.get(selectors.fileInput).selectFile('test/cypress/fixtures/image.jpg', {
force: true,
});
cy.dataCy('FormModelPopup_save').click();
cy.dataCy(selectors.codeField).invoke('text').should('contain', data.Thermograph.val);
});
it('Should edit thermograph', () => {
cy.dataCy(selectors.editBtn).eq(0).click();
const updateData = {
Temperature: { val: 'Warm', type: 'select' },
}
cy.fillInForm(updateData);
cy.dataCy('FormModelPopup_save').click();
cy.dataCy(selectors.temperatureField).invoke('text').should('contain', 'warm');
});
it('Should delete thermograph', () => {
cy.dataCy(selectors.removeBtn).eq(0).click();
cy.clickConfirm();
cy.checkNotification('Thermograph removed');
});
});

View File

@ -1,4 +1,4 @@
describe('ZoneCalendar', { testIsolation: true }, () => {
describe('ZoneCalendar', () => {
const addEventBtn = '.q-page-sticky > div > .q-btn';
const submitBtn = '.q-mt-lg > .q-btn--standard';
const deleteBtn = 'ZoneEventsPanelDeleteBtn';
@ -47,20 +47,4 @@ describe('ZoneCalendar', { testIsolation: true }, () => {
cy.dataCy('ZoneEventExclusionDeleteBtn').click();
cy.dataCy('VnConfirm_confirm').click();
});
it(
'should not exclude an event if there are tickets for that zone and day',
{ testIsoaltion: true },
() => {
cy.visit(`/#/zone/3/events`);
cy.get('.q-mb-sm > .q-radio__inner').click();
cy.get(
'.q-current-day > .q-calendar-month__day--content > [data-cy="ZoneCalendarDay"]',
).click();
cy.get('.q-mt-lg > .q-btn--standard').click();
cy.checkNotification(
'Can not close this zone because there are tickets programmed for that day',
);
},
);
});

View File

@ -26,8 +26,27 @@ describe('ZoneDeliveryDays', () => {
});
}).as('events');
cy.selectOption('[data-cy="ZoneDeliveryDaysPostcodeSelect"]', postcode);
cy.selectOption('[data-cy="ZoneDeliveryDaysAgencySelect"]', agency);
cy.dataCy('ZoneDeliveryDaysPostcodeSelect').type(postcode);
cy.get('.q-menu .q-item').contains(postcode).click();
cy.get('.q-menu').then(($menu) => {
if ($menu.is(':visible')) {
cy.get('[data-cy="ZoneDeliveryDaysPostcodeSelect"]')
.as('focusedElement')
.focus();
cy.get('@focusedElement').blur();
}
});
cy.dataCy('ZoneDeliveryDaysAgencySelect').type(agency);
cy.get('.q-menu .q-item').contains(agency).click();
cy.get('.q-menu').then(($menu) => {
if ($menu.is(':visible')) {
cy.get('[data-cy="ZoneDeliveryDaysAgencySelect"]')
.as('focusedElement')
.focus();
cy.get('@focusedElement').blur();
}
});
cy.get(submitForm).click();
cy.wait('@events').then((interception) => {