4774-traducciones #853

Open
carlossa wants to merge 39 commits from 4774-traducciones into dev
268 changed files with 3628 additions and 4377 deletions
Showing only changes of commit aa0b0d8317 - Show all commits

View File

@ -1,4 +1,7 @@
const { defineConfig } = require('cypress');
// https://docs.cypress.io/app/tooling/reporters
// https://docs.cypress.io/app/references/configuration
// https://www.npmjs.com/package/cypress-mochawesome-reporter
module.exports = defineConfig({
e2e: {
@ -16,6 +19,7 @@ module.exports = defineConfig({
reporterOptions: {
charts: true,
reportPageTitle: 'Cypress Inline Reporter',
reportFilename: '[status]_[datetime]-report',
embeddedScreenshots: true,
reportDir: 'test/cypress/reports',
inlineAssets: true,

View File

@ -1,6 +1,6 @@
{
"name": "salix-front",
"version": "24.44.0",
"version": "24.50.0",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
@ -64,4 +64,4 @@
"vite": "^5.1.4",
"vitest": "^0.31.1"
}
}
}

View File

@ -9,8 +9,6 @@ import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from './FormModelPopup.vue';
import { useState } from 'src/composables/useState';
defineProps({ showEntityField: { type: Boolean, default: true } });
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const bicInputRef = ref(null);
@ -18,17 +16,16 @@ const state = useState();
const customer = computed(() => state.get('customer'));
const countriesFilter = {
fields: ['id', 'name', 'code'],
};
const bankEntityFormData = reactive({
name: null,
bic: null,
countryFk: customer.value?.countryFk,
id: null,
});
const countriesFilter = {
fields: ['id', 'name', 'code'],
};
const countriesOptions = ref([]);
const onDataSaved = (...args) => {
@ -44,7 +41,6 @@ onMounted(async () => {
<template>
<FetchData
url="Countries"
:filter="countriesFilter"
auto-load
@on-fetch="(data) => (countriesOptions = data)"
/>
@ -54,6 +50,7 @@ onMounted(async () => {
:title="t('title')"
:subtitle="t('subtitle')"
:form-initial-data="bankEntityFormData"
:filter="countriesFilter"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data, validate }">
@ -85,7 +82,13 @@ onMounted(async () => {
:rules="validate('bankEntity.countryFk')"
/>
</div>
<div v-if="showEntityField" class="col">
<div
v-if="
countriesOptions.find((c) => c.id === data.countryFk)?.code ==
'ES'
"
class="col"
>
<VnInput
:label="t('id')"
v-model="data.id"

View File

@ -1,155 +0,0 @@
<script setup>
import { reactive, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
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';
import FormModelPopup from './FormModelPopup.vue';
import VnInputDate from './common/VnInputDate.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const router = useRouter();
const manualInvoiceFormData = reactive({
maxShipped: Date.vnNew(),
});
const formModelPopupRef = ref();
const invoiceOutSerialsOptions = ref([]);
const taxAreasOptions = ref([]);
const ticketsOptions = ref([]);
const clientsOptions = ref([]);
const isLoading = computed(() => formModelPopupRef.value?.isLoading);
const onDataSaved = async (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
if (requestResponse && requestResponse.id)
router.push({ name: 'InvoiceOutSummary', params: { id: requestResponse.id } });
};
</script>
<template>
<FetchData
url="InvoiceOutSerials"
:filter="{ where: { code: { neq: 'R' } }, order: ['code'] }"
@on-fetch="(data) => (invoiceOutSerialsOptions = data)"
auto-load
/>
<FetchData
url="TaxAreas"
:filter="{ order: ['code'] }"
@on-fetch="(data) => (taxAreasOptions = data)"
auto-load
/>
<FormModelPopup
ref="formModelPopupRef"
:title="t('Create manual invoice')"
url-create="InvoiceOuts/createManualInvoice"
model="invoiceOut"
:form-initial-data="manualInvoiceFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<span v-if="isLoading" class="text-primary invoicing-text">
<QIcon name="warning" class="fill-icon q-mr-sm" size="md" />
{{ t('Invoicing in progress...') }}
</span>
<VnRow>
<VnSelect
:label="t('Ticket')"
:options="ticketsOptions"
hide-selected
option-label="id"
option-value="id"
v-model="data.ticketFk"
@update:model-value="data.clientFk = null"
url="Tickets"
:where="{ refFk: null }"
:fields="['id', 'nickname']"
:filter-options="{ order: 'shipped DESC' }"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
<QItemLabel caption>{{ scope.opt?.nickname }}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<span class="row items-center" style="max-width: max-content">{{
t('Or')
}}</span>
<VnSelect
:label="t('Client')"
:options="clientsOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.clientFk"
@update:model-value="data.ticketFk = null"
url="Clients"
:fields="['id', 'name']"
:filter-options="{ order: 'name ASC' }"
/>
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
</VnRow>
<VnRow>
<VnSelect
:label="t('Serial')"
:options="invoiceOutSerialsOptions"
hide-selected
option-label="description"
option-value="code"
v-model="data.serial"
/>
<VnSelect
:label="t('Area')"
:options="taxAreasOptions"
hide-selected
option-label="code"
option-value="code"
v-model="data.taxArea"
/>
</VnRow>
<VnRow>
<VnInput
:label="t('Reference')"
type="textarea"
v-model="data.reference"
fill-input
autogrow
/>
</VnRow>
</template>
</FormModelPopup>
</template>
<style lang="scss" scoped>
.invoicing-text {
display: flex;
justify-content: center;
align-items: center;
color: $primary;
font-size: 24px;
margin-bottom: 8px;
}
</style>
<i18n>
es:
Create manual invoice: Crear factura manual
Ticket: Ticket
Client: Cliente
Max date: Fecha límite
Serial: Serie
Area: Area
Reference: Referencia
Or: O
Invoicing in progress...: Facturación en progreso...
</i18n>

View File

@ -17,10 +17,6 @@ const $props = defineProps({
type: Number,
default: null,
},
provinces: {
type: Array,
default: () => [],
},
});
const { t } = useI18n();
@ -48,7 +44,7 @@ const onDataSaved = (...args) => {
<template #form-inputs="{ data, validate }">
<VnRow>
<VnInput
:label="t('Names')"
:label="t('Name')"
v-model="data.name"
:rules="validate('city.name')"
/>
@ -56,7 +52,6 @@ const onDataSaved = (...args) => {
:province-selected="$props.provinceSelected"
:country-fk="$props.countryFk"
v-model="data.provinceFk"
:provinces="$props.provinces"
/>
</VnRow>
</template>

View File

@ -1,5 +1,5 @@
<script setup>
import { reactive, ref, watch } from 'vue';
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
@ -22,12 +22,15 @@ const postcodeFormData = reactive({
townFk: null,
});
const townsFetchDataRef = ref(null);
const provincesFetchDataRef = ref(null);
const townsFetchDataRef = ref(false);
const countriesFetchDataRef = ref(false);
const provincesFetchDataRef = ref(false);
const countriesOptions = ref([]);
const provincesOptions = ref([]);
const townsOptions = ref([]);
const town = ref({});
const townFilter = ref({});
const countryFilter = ref({});
function onDataSaved(formData) {
const newPostcode = {
@ -56,10 +59,60 @@ async function onCityCreated(newTown, formData) {
}
function setTown(newTown, data) {
if (!newTown) return;
town.value = newTown;
data.provinceFk = newTown.provinceFk;
data.countryFk = newTown.province.countryFk;
data.provinceFk = newTown?.provinceFk ?? newTown;
data.countryFk = newTown?.province?.countryFk ?? newTown;
}
async function setCountry(countryFk, data) {
data.townFk = null;
data.provinceFk = null;
data.countryFk = countryFk;
}
async function filterTowns(name) {
if (name !== '') {
townFilter.value.where = {
name: {
like: `%${name}%`,
},
};
await townsFetchDataRef.value?.fetch();
}
}
async function filterCountries(name) {
if (name !== '') {
countryFilter.value.where = {
name: {
like: `%${name}%`,
},
};
await countriesFetchDataRef.value?.fetch();
}
}
async function fetchTowns(countryFk) {
if (!countryFk) return;
townFilter.value.where = {
provinceFk: {
inq: provincesOptions.value.map(({ id }) => id),
},
};
await townsFetchDataRef.value?.fetch();
}
async function handleProvinces(data) {
provincesOptions.value = data;
if (postcodeFormData.countryFk) {
await fetchTowns(postcodeFormData.countryFk);
}
}
async function handleTowns(data) {
townsOptions.value = data;
}
async function handleCountries(data) {
countriesOptions.value = data;
}
async function setProvince(id, data) {
@ -73,60 +126,7 @@ async function onProvinceCreated(data) {
await provincesFetchDataRef.value.fetch({
where: { countryFk: postcodeFormData.countryFk },
});
postcodeFormData.provinceFk.value = data.id;
}
watch(
() => [postcodeFormData.countryFk],
async (newCountryFk, oldValueFk) => {
if (Array.isArray(newCountryFk)) {
newCountryFk = newCountryFk[0];
}
if (Array.isArray(oldValueFk)) {
oldValueFk = oldValueFk[0];
}
if (!!oldValueFk && newCountryFk !== oldValueFk) {
postcodeFormData.provinceFk = null;
postcodeFormData.townFk = null;
}
if (oldValueFk !== newCountryFk) {
await provincesFetchDataRef.value.fetch({
where: {
countryFk: newCountryFk,
},
});
await townsFetchDataRef.value.fetch({
where: {
provinceFk: {
inq: provincesOptions.value.map(({ id }) => id),
},
},
});
}
}
);
watch(
() => postcodeFormData.provinceFk,
async (newProvinceFk, oldValueFk) => {
if (Array.isArray(newProvinceFk)) {
newProvinceFk = newProvinceFk[0];
}
if (newProvinceFk !== oldValueFk) {
await townsFetchDataRef.value.fetch({
where: { provinceFk: newProvinceFk },
});
}
}
);
async function handleProvinces(data) {
provincesOptions.value = data;
}
async function handleTowns(data) {
townsOptions.value = data;
}
async function handleCountries(data) {
countriesOptions.value = data;
postcodeFormData.provinceFk = data.id;
}
</script>
@ -143,10 +143,20 @@ async function handleCountries(data) {
ref="townsFetchDataRef"
:sort-by="['name ASC']"
:limit="30"
:filter="townFilter"
@on-fetch="handleTowns"
auto-load
url="Towns/location"
/>
<FetchData
ref="countriesFetchDataRef"
:limit="30"
:filter="countryFilter"
:sort-by="['name ASC']"
@on-fetch="handleCountries"
auto-load
url="Countries"
/>
<FormModelPopup
url-create="postcodes"
@ -168,6 +178,7 @@ async function handleCountries(data) {
<VnSelectDialog
:label="t('City')"
@update:model-value="(value) => setTown(value, data)"
@filter="filterTowns"
:tooltip="t('Create city')"
v-model="data.townFk"
:options="townsOptions"
@ -193,7 +204,6 @@ async function handleCountries(data) {
<CreateNewCityForm
:country-fk="data.countryFk"
:province-selected="data.provinceFk"
:provinces="provincesOptions"
@on-data-saved="
(_, requestResponse) =>
onCityCreated(requestResponse, data)
@ -208,20 +218,20 @@ async function handleCountries(data) {
:province-selected="data.provinceFk"
@update:model-value="(value) => setProvince(value, data)"
v-model="data.provinceFk"
:clearable="true"
:provinces="provincesOptions"
@on-province-fetched="handleProvinces"
@on-province-created="onProvinceCreated"
/>
<VnSelect
url="Countries"
:sort-by="['name ASC']"
:label="t('Country')"
@update:options="handleCountries"
:options="countriesOptions"
hide-selected
@filter="filterCountries"
option-label="name"
option-value="id"
v-model="data.countryFk"
:rules="validate('postcode.countryFk')"
@update:model-value="(value) => setCountry(value, data)"
/>
</VnRow>
</template>

View File

@ -1,5 +1,5 @@
<script setup>
import { reactive, ref } from 'vue';
import { computed, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
@ -34,6 +34,12 @@ const onDataSaved = (dataSaved, requestResponse) => {
);
emit('onDataSaved', dataSaved, requestResponse);
};
const where = computed(() => {
if (!$props.countryFk) {
return {};
}
return { countryFk: $props.countryFk };
});
</script>
<template>
@ -41,9 +47,7 @@ const onDataSaved = (dataSaved, requestResponse) => {
@on-fetch="(data) => (autonomiesOptions = data)"
auto-load
:filter="{
where: {
countryFk: $props.countryFk,
},
where,
}"
url="Autonomies/location"
:sort-by="['name ASC']"

View File

@ -77,7 +77,7 @@ const isLoading = ref(false);
const hasChanges = ref(false);
const originalData = ref();
const vnPaginateRef = ref();
const formData = ref();
const formData = ref([]);
const saveButtonRef = ref(null);
const watchChanges = ref();
const formUrl = computed(() => $props.url);
@ -395,6 +395,7 @@ watch(formUrl, async () => {
@click="onSubmit"
:disable="!hasChanges"
:title="t('globals.save')"
data-cy="crudModelDefaultSaveBtn"
/>
<slot name="moreAfterActions" />
</QBtnGroup>

View File

@ -156,26 +156,22 @@ const rotateRight = () => {
};
const onSubmit = () => {
try {
if (!newPhoto.files && !newPhoto.url) {
notify(t('Select an image'), 'negative');
return;
}
const options = {
type: 'blob',
};
editor.value
.result(options)
.then((result) => {
const file = new File([result], newPhoto.files?.name || '');
newPhoto.blob = file;
})
.then(() => makeRequest());
} catch (err) {
console.error('Error uploading image');
if (!newPhoto.files && !newPhoto.url) {
notify(t('Select an image'), 'negative');
return;
}
const options = {
type: 'blob',
};
editor.value
.result(options)
.then((result) => {
const file = new File([result], newPhoto.files?.name || '');
newPhoto.blob = file;
})
.then(() => makeRequest());
};
const makeRequest = async () => {

View File

@ -51,21 +51,17 @@ const onDataSaved = () => {
};
const onSubmit = async () => {
try {
isLoading.value = true;
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
const payload = {
field: selectedField.value.field,
newValue: newValue.value,
lines: rowsToEdit,
};
isLoading.value = true;
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
const payload = {
field: selectedField.value.field,
newValue: newValue.value,
lines: rowsToEdit,
};
await axios.post($props.editUrl, payload);
onDataSaved();
isLoading.value = false;
} catch (err) {
console.error('Error submitting table cell edit');
}
await axios.post($props.editUrl, payload);
onDataSaved();
isLoading.value = false;
};
const closeForm = () => {

View File

@ -50,25 +50,25 @@ const loading = ref(false);
const tableColumns = computed(() => [
{
label: t('entry.buys.id'),
label: t('globals.id'),
name: 'id',
field: 'id',
align: 'left',
},
{
label: t('entry.buys.name'),
label: t('globals.name'),
name: 'name',
field: 'name',
align: 'left',
},
{
label: t('entry.buys.size'),
label: t('globals.size'),
name: 'size',
field: 'size',
align: 'left',
},
{
label: t('entry.buys.producer'),
label: t('globals.producer'),
name: 'producerName',
field: 'producer',
align: 'left',
@ -84,34 +84,30 @@ const tableColumns = computed(() => [
]);
const onSubmit = async () => {
try {
let filter = itemFilter;
const params = itemFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
let filter = itemFilter;
const params = itemFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'name':
where[key] = { like: `%${value}%` };
break;
case 'producerFk':
case 'typeFk':
case 'size':
case 'inkFk':
where[key] = value;
}
switch (key) {
case 'name':
where[key] = { like: `%${value}%` };
break;
case 'producerFk':
case 'typeFk':
case 'size':
case 'inkFk':
where[key] = value;
}
filter.where = where;
const { data } = await axios.get(props.url, {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
} catch (err) {
console.error('Error fetching entries items');
}
filter.where = where;
const { data } = await axios.get(props.url, {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
};
const closeForm = () => {
@ -152,10 +148,10 @@ const selectItem = ({ id }) => {
</span>
<h1 class="title">{{ t('Filter item') }}</h1>
<VnRow>
<VnInput :label="t('entry.buys.name')" v-model="itemFilterParams.name" />
<VnInput :label="t('globals.name')" v-model="itemFilterParams.name" />
<VnInput :label="t('entry.buys.size')" v-model="itemFilterParams.size" />
<VnSelect
:label="t('entry.buys.producer')"
:label="t('globals.producer')"
:options="producersOptions"
hide-selected
option-label="name"
@ -163,7 +159,7 @@ const selectItem = ({ id }) => {
v-model="itemFilterParams.producerFk"
/>
<VnSelect
:label="t('entry.buys.type')"
:label="t('globals.type')"
:options="ItemTypesOptions"
hide-selected
option-label="name"

View File

@ -48,13 +48,13 @@ const loading = ref(false);
const tableColumns = computed(() => [
{
label: t('entry.basicData.id'),
label: t('globals.id'),
name: 'id',
field: 'id',
align: 'left',
},
{
label: t('entry.basicData.warehouseOut'),
label: t('globals.warehouseOut'),
name: 'warehouseOutFk',
field: 'warehouseOutFk',
align: 'left',
@ -62,7 +62,7 @@ const tableColumns = computed(() => [
warehousesOptions.value.find((warehouse) => warehouse.id === val).name,
},
{
label: t('entry.basicData.warehouseIn'),
label: t('globals.warehouseIn'),
name: 'warehouseInFk',
field: 'warehouseInFk',
align: 'left',
@ -70,14 +70,14 @@ const tableColumns = computed(() => [
warehousesOptions.value.find((warehouse) => warehouse.id === val).name,
},
{
label: t('entry.basicData.shipped'),
label: t('globals.shipped'),
name: 'shipped',
field: 'shipped',
align: 'left',
format: (val) => toDate(val),
},
{
label: t('entry.basicData.landed'),
label: t('globals.landed'),
name: 'landed',
field: 'landed',
align: 'left',
@ -86,32 +86,28 @@ const tableColumns = computed(() => [
]);
const onSubmit = async () => {
try {
let filter = travelFilter;
const params = travelFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
let filter = travelFilter;
const params = travelFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'agencyModeFk':
case 'warehouseInFk':
case 'warehouseOutFk':
case 'shipped':
case 'landed':
where[key] = value;
}
switch (key) {
case 'agencyModeFk':
case 'warehouseInFk':
case 'warehouseOutFk':
case 'shipped':
case 'landed':
where[key] = value;
}
filter.where = where;
const { data } = await axios.get('Travels', {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
} catch (err) {
console.error('Error fetching travels');
}
filter.where = where;
const { data } = await axios.get('Travels', {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
};
const closeForm = () => {
@ -146,7 +142,7 @@ const selectTravel = ({ id }) => {
<h1 class="title">{{ t('Filter travels') }}</h1>
<VnRow>
<VnSelect
:label="t('entry.basicData.agency')"
:label="t('globals.agency')"
:options="agenciesOptions"
hide-selected
option-label="name"
@ -154,7 +150,7 @@ const selectTravel = ({ id }) => {
v-model="travelFilterParams.agencyModeFk"
/>
<VnSelect
:label="t('entry.basicData.warehouseOut')"
:label="t('globals.warehouseOut')"
:options="warehousesOptions"
hide-selected
option-label="name"
@ -162,7 +158,7 @@ const selectTravel = ({ id }) => {
v-model="travelFilterParams.warehouseOutFk"
/>
<VnSelect
:label="t('entry.basicData.warehouseIn')"
:label="t('globals.warehouseIn')"
:options="warehousesOptions"
hide-selected
option-label="name"
@ -170,11 +166,11 @@ const selectTravel = ({ id }) => {
v-model="travelFilterParams.warehouseInFk"
/>
<VnInputDate
:label="t('entry.basicData.shipped')"
:label="t('globals.shipped')"
v-model="travelFilterParams.shipped"
/>
<VnInputDate
:label="t('entry.basicData.landed')"
:label="t('globals.landed')"
v-model="travelFilterParams.landed"
/>
</VnRow>

View File

@ -91,6 +91,10 @@ const $props = defineProps({
type: Boolean,
default: true,
},
maxWidth: {
type: [String, Boolean],
default: '800px',
},
});
const emit = defineEmits(['onFetch', 'onDataSaved']);
const modelValue = computed(
@ -283,6 +287,7 @@ defineExpose({
@submit="save"
@reset="reset"
class="q-pa-md"
:style="maxWidth ? 'max-width: ' + maxWidth : ''"
id="formModel"
>
<QCard>
@ -371,7 +376,6 @@ defineExpose({
color: black;
}
#formModel {
max-width: 800px;
width: 100%;
}

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
const emit = defineEmits(['onSubmit']);
defineProps({
const $props = defineProps({
title: {
type: String,
default: '',
@ -25,16 +25,21 @@ defineProps({
type: String,
default: '',
},
submitOnEnter: {
type: Boolean,
default: true,
},
});
const { t } = useI18n();
const closeButton = ref(null);
const isLoading = ref(false);
const onSubmit = () => {
emit('onSubmit');
closeForm();
if ($props.submitOnEnter) {
emit('onSubmit');
closeForm();
}
};
const closeForm = () => {

View File

@ -88,20 +88,16 @@ const applyTags = (params, search) => {
};
const fetchItemTypes = async (id) => {
try {
const filter = {
fields: ['id', 'name', 'categoryFk'],
where: { categoryFk: id },
include: 'category',
order: 'name ASC',
};
const { data } = await axios.get('ItemTypes', {
params: { filter: JSON.stringify(filter) },
});
itemTypesOptions.value = data;
} catch (err) {
console.error('Error fetching item types', err);
}
const filter = {
fields: ['id', 'name', 'categoryFk'],
where: { categoryFk: id },
include: 'category',
order: 'name ASC',
};
const { data } = await axios.get('ItemTypes', {
params: { filter: JSON.stringify(filter) },
});
itemTypesOptions.value = data;
};
const getCategoryClass = (category, params) => {
@ -111,23 +107,19 @@ const getCategoryClass = (category, params) => {
};
const getSelectedTagValues = async (tag) => {
try {
if (!tag?.selectedTag?.id) return;
tag.value = null;
const filter = {
fields: ['value'],
order: 'value ASC',
limit: 30,
};
if (!tag?.selectedTag?.id) return;
tag.value = null;
const filter = {
fields: ['value'],
order: 'value ASC',
limit: 30,
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
params,
});
tag.valueOptions = data;
} catch (err) {
console.error('Error getting selected tag values');
}
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
params,
});
tag.valueOptions = data;
};
const removeTag = (index, params, search) => {
@ -248,7 +240,7 @@ const removeTag = (index, params, search) => {
>
<QItemSection class="col">
<VnSelect
:label="t('components.itemsFilterPanel.tag')"
:label="t('globals.tag')"
v-model="value.selectedTag"
:options="tagOptions"
option-label="name"

View File

@ -22,7 +22,7 @@ const props = defineProps({
default: 'main',
},
});
const initialized = ref(false);
const items = ref([]);
const expansionItemElements = reactive({});
const pinnedModules = computed(() => {
@ -34,18 +34,26 @@ const search = ref(null);
const filteredItems = computed(() => {
if (!search.value) return items.value;
const normalizedSearch = normalize(search.value);
return items.value.filter((item) => {
const locale = t(item.title).toLowerCase();
return locale.includes(search.value.toLowerCase());
const locale = normalize(t(item.title));
return locale.includes(normalizedSearch);
});
});
const filteredPinnedModules = computed(() => {
if (!search.value) return pinnedModules.value;
const normalizedSearch = search.value
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
const map = new Map();
for (const [key, pinnedModule] of pinnedModules.value) {
const locale = t(pinnedModule.title).toLowerCase();
if (locale.includes(search.value.toLowerCase())) map.set(key, pinnedModule);
const locale = t(pinnedModule.title)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
if (locale.includes(normalizedSearch)) map.set(key, pinnedModule);
}
return map;
});
@ -53,11 +61,13 @@ const filteredPinnedModules = computed(() => {
onMounted(async () => {
await navigation.fetchPinned();
getRoutes();
initialized.value = true;
});
watch(
() => route.matched,
() => {
if (!initialized.value) return;
items.value = [];
getRoutes();
},
@ -147,6 +157,13 @@ async function togglePinned(item, event) {
const handleItemExpansion = (itemName) => {
expansionItemElements[itemName].scrollToLastElement();
};
function normalize(text) {
return text
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
}
</script>
<template>

View File

@ -39,14 +39,10 @@ const refund = async () => {
invoiceCorrectionTypeFk: invoiceParams.invoiceCorrectionTypeFk,
};
try {
const { data } = await axios.post('InvoiceOuts/refundAndInvoice', params);
notify(t('Refunded invoice'), 'positive');
const [id] = data?.refundId || [];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
} catch (err) {
console.error('Error refunding invoice', err);
}
const { data } = await axios.post('InvoiceOuts/refundAndInvoice', params);
notify(t('Refunded invoice'), 'positive');
const [id] = data?.refundId || [];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
};
</script>

View File

@ -0,0 +1,40 @@
<script setup>
defineProps({ row: { type: Object, required: true } });
</script>
<template>
<span>
<QIcon
v-if="row.isTaxDataChecked === 0"
name="vn:no036"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip>
</QIcon>
<QIcon v-if="row.hasTicketRequest" name="vn:buyrequest" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
</QIcon>
<QIcon v-if="row.itemShortage" name="vn:unavailable" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip>
</QIcon>
<QIcon v-if="row.isFreezed" name="vn:frozen" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip>
</QIcon>
<QIcon
v-if="row.risk"
name="vn:risk"
:color="row.hasHighRisk ? 'negative' : 'primary'"
size="xs"
>
<QTooltip>
{{ $t('salesTicketsTable.risk') }}: {{ row.risk - row.credit }}
</QTooltip>
</QIcon>
<QIcon v-if="row.hasComponentLack" name="vn:components" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.componentLack') }}</QTooltip>
</QIcon>
<QIcon v-if="row.isTooLittle" name="vn:isTooLittle" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip>
</QIcon>
</span>
</template>

View File

@ -49,36 +49,32 @@ const makeInvoice = async () => {
makeInvoice: checked.value,
};
try {
if (checked.value && hasToInvoiceByAddress) {
const response = await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Bill destination client'),
message: t('transferInvoiceInfo'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
if (!response) {
return;
}
if (checked.value && hasToInvoiceByAddress) {
const response = await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Bill destination client'),
message: t('transferInvoiceInfo'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
if (!response) {
return;
}
const { data } = await axios.post('InvoiceOuts/transfer', params);
notify(t('Transferred invoice'), 'positive');
const id = data?.[0];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
} catch (err) {
console.error('Error transfering invoice', err);
}
const { data } = await axios.post('InvoiceOuts/transfer', params);
notify(t('Transferred invoice'), 'positive');
const id = data?.[0];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
};
</script>

View File

@ -1,5 +1,5 @@
<script setup>
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { useValidator } from 'src/composables/useValidator';
import { useI18n } from 'vue-i18n';
@ -7,7 +7,7 @@ import VnSelectDialog from 'components/common/VnSelectDialog.vue';
import FetchData from 'components/FetchData.vue';
import CreateNewProvinceForm from './CreateNewProvinceForm.vue';
const emit = defineEmits(['onProvinceCreated']);
const emit = defineEmits(['onProvinceCreated', 'onProvinceFetched']);
const $props = defineProps({
countryFk: {
type: Number,
@ -17,20 +17,23 @@ const $props = defineProps({
type: Number,
default: null,
},
provinces: {
type: Array,
default: () => [],
},
});
const provinceFk = defineModel({ type: Number, default: null });
const { validate } = useValidator();
const { t } = useI18n();
const filter = ref({
include: { relation: 'country' },
where: {
countryFk: $props.countryFk,
},
});
const provincesOptions = ref($props.provinces);
provinceFk.value = $props.provinceSelected;
const provincesFetchDataRef = ref();
provinceFk.value = $props.provinceSelected;
if (!$props.countryFk) {
filter.value.where = {};
}
async function onProvinceCreated(_, data) {
await provincesFetchDataRef.value.fetch({ where: { countryFk: $props.countryFk } });
provinceFk.value = data.id;
@ -39,25 +42,33 @@ async function onProvinceCreated(_, data) {
async function handleProvinces(data) {
provincesOptions.value = data;
}
watch(
() => $props.countryFk,
async () => {
if ($props.countryFk) {
filter.value.where.countryFk = $props.countryFk;
} else filter.value.where = {};
await provincesFetchDataRef.value.fetch({});
emit('onProvinceFetched', provincesOptions.value);
}
);
</script>
<template>
<FetchData
ref="provincesFetchDataRef"
:filter="{
include: { relation: 'country' },
where: {
countryFk: $props.countryFk,
},
}"
:filter="filter"
@on-fetch="handleProvinces"
url="Provinces"
auto-load
/>
<VnSelectDialog
:label="t('Province')"
:options="$props.provinces"
:options="provincesOptions"
:tooltip="t('Create province')"
hide-selected
:clearable="true"
v-model="provinceFk"
:rules="validate && validate('postcode.provinceFk')"
:acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]"

View File

@ -25,7 +25,7 @@ const $props = defineProps({
},
searchUrl: {
type: String,
default: 'params',
default: 'table',
},
});
@ -143,6 +143,10 @@ function alignRow() {
const showFilter = computed(
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
);
const onTabPressed = async () => {
if (model.value) enterEvent['keyup.enter']();
};
</script>
<template>
<div
@ -157,6 +161,7 @@ const showFilter = computed(
v-model="model"
:components="components"
component-prop="columnFilter"
@keydown.tab="onTabPressed"
/>
</div>
</template>

View File

@ -17,7 +17,7 @@ const $props = defineProps({
},
searchUrl: {
type: String,
default: 'params',
default: 'table',
},
vertical: {
type: Boolean,

View File

@ -162,9 +162,7 @@ onMounted(() => {
: $props.defaultMode;
stateStore.rightDrawer = quasar.screen.gt.xs;
columnsVisibilitySkipped.value = [
...splittedColumns.value.columns
.filter((c) => c.visible == false)
.map((c) => c.name),
...splittedColumns.value.columns.filter((c) => !c.visible).map((c) => c.name),
...['tableActions'],
];
createForm.value = $props.create;
@ -243,7 +241,7 @@ function splitColumns(columns) {
if (col.create) splittedColumns.value.create.push(col);
if (col.cardVisible) splittedColumns.value.cardVisible.push(col);
if ($props.isEditable && col.disable == null) col.disable = false;
if ($props.useModel && col.columnFilter != false)
if ($props.useModel && col.columnFilter !== false)
col.columnFilter = { inWhere: true, ...col.columnFilter };
splittedColumns.value.columns.push(col);
}
@ -332,6 +330,8 @@ function handleOnDataSaved(_) {
}
function handleScroll() {
if ($props.crudModel.disableInfiniteScroll) return;
const tMiddle = tableRef.value.$el.querySelector('.q-table__middle');
const { scrollHeight, scrollTop, clientHeight } = tMiddle;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40;
@ -400,7 +400,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
:name="col.orderBy ?? col.name"
:data-key="$attrs['data-key']"
:search-url="searchUrl"
:vertical="true"
:vertical="false"
/>
</div>
<slot
@ -745,6 +745,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
fab
icon="add"
shortcut="+"
data-cy="vnTableCreateBtn"
/>
<QTooltip self="top right">
{{ createForm?.title }}

View File

@ -58,79 +58,71 @@ const getConfig = async (url, filter) => {
};
const fetchViewConfigData = async () => {
try {
const userConfigFilter = {
where: { tableCode: $props.tableCode, userFk: user.value.id },
};
const userConfig = await getConfig('UserConfigViews', userConfigFilter);
const userConfigFilter = {
where: { tableCode: $props.tableCode, userFk: user.value.id },
};
const userConfig = await getConfig('UserConfigViews', userConfigFilter);
if (userConfig) {
initialUserConfigViewData.value = userConfig;
setUserConfigViewData(userConfig.configuration);
return;
}
if (userConfig) {
initialUserConfigViewData.value = userConfig;
setUserConfigViewData(userConfig.configuration);
return;
}
const defaultConfigFilter = { where: { tableCode: $props.tableCode } };
const defaultConfig = await getConfig('DefaultViewConfigs', defaultConfigFilter);
const defaultConfigFilter = { where: { tableCode: $props.tableCode } };
const defaultConfig = await getConfig('DefaultViewConfigs', defaultConfigFilter);
if (defaultConfig) {
// Si el backend devuelve una configuración por defecto la usamos
setUserConfigViewData(defaultConfig.columns);
return;
} else {
// Si no hay configuración por defecto mostramos todas las columnas
const defaultColumns = {};
$props.allColumns.forEach((col) => (defaultColumns[col] = true));
setUserConfigViewData(defaultColumns);
}
} catch (err) {
console.error('Error fetching config view data', err);
if (defaultConfig) {
// Si el backend devuelve una configuración por defecto la usamos
setUserConfigViewData(defaultConfig.columns);
return;
} else {
// Si no hay configuración por defecto mostramos todas las columnas
const defaultColumns = {};
$props.allColumns.forEach((col) => (defaultColumns[col] = true));
setUserConfigViewData(defaultColumns);
}
};
const saveConfig = async () => {
try {
const params = {};
const configuration = {};
const params = {};
const configuration = {};
formattedCols.value.forEach((col) => {
const { name, active } = col;
configuration[name] = active;
});
formattedCols.value.forEach((col) => {
const { name, active } = col;
configuration[name] = active;
});
// Si existe una view config del usuario hacemos un update si no la creamos
if (initialUserConfigViewData.value) {
params.updates = [
{
data: {
configuration: configuration,
},
where: {
id: initialUserConfigViewData.value.id,
},
},
];
} else {
params.creates = [
{
userFk: user.value.id,
tableCode: $props.tableCode,
tableConfig: $props.tableCode,
// Si existe una view config del usuario hacemos un update si no la creamos
if (initialUserConfigViewData.value) {
params.updates = [
{
data: {
configuration: configuration,
},
];
}
const response = await axios.post('UserConfigViews/crud', params);
if (response.data && response.data[0]) {
initialUserConfigViewData.value = response.data[0];
}
emitSavedConfig();
notify('globals.dataSaved', 'positive');
popupProxyRef.value.hide();
} catch (err) {
console.error('Error saving user view config', err);
where: {
id: initialUserConfigViewData.value.id,
},
},
];
} else {
params.creates = [
{
userFk: user.value.id,
tableCode: $props.tableCode,
tableConfig: $props.tableCode,
configuration: configuration,
},
];
}
const response = await axios.post('UserConfigViews/crud', params);
if (response.data && response.data[0]) {
initialUserConfigViewData.value = response.data[0];
}
emitSavedConfig();
notify('globals.dataSaved', 'positive');
popupProxyRef.value.hide();
};
const emitSavedConfig = () => {

View File

@ -1,20 +1,24 @@
<script setup>
import { ref, watch } from 'vue';
import { nextTick, ref, watch } from 'vue';
import { QInput } from 'quasar';
const props = defineProps({
const $props = defineProps({
modelValue: {
type: String,
default: '',
},
insertable: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['update:modelValue', 'accountShortToStandard']);
let internalValue = ref(props.modelValue);
let internalValue = ref($props.modelValue);
watch(
() => props.modelValue,
() => $props.modelValue,
(newVal) => {
internalValue.value = newVal;
}
@ -28,8 +32,46 @@ watch(
}
);
const handleKeydown = (e) => {
if (e.key === 'Backspace') return;
if (e.key === '.') {
accountShortToStandard();
// TODO: Fix this setTimeout, with nextTick doesn't work
setTimeout(() => {
setCursorPosition(0, e.target);
}, 1);
return;
}
if ($props.insertable && e.key.match(/[0-9]/)) {
handleInsertMode(e);
}
};
function setCursorPosition(pos, el = vnInputRef.value) {
el.focus();
el.setSelectionRange(pos, pos);
}
const vnInputRef = ref(false);
const handleInsertMode = (e) => {
e.preventDefault();
const input = e.target;
const cursorPos = input.selectionStart;
const { maxlength } = vnInputRef.value;
let currentValue = internalValue.value;
if (!currentValue) currentValue = e.key;
const newValue = e.key;
if (newValue && !isNaN(newValue) && cursorPos < maxlength) {
internalValue.value =
currentValue.substring(0, cursorPos) +
newValue +
currentValue.substring(cursorPos + 1);
}
nextTick(() => {
input.setSelectionRange(cursorPos + 1, cursorPos + 1);
});
};
function accountShortToStandard() {
internalValue.value = internalValue.value.replace(
internalValue.value = internalValue.value?.replace(
'.',
'0'.repeat(11 - internalValue.value.length)
);
@ -37,5 +79,5 @@ function accountShortToStandard() {
</script>
<template>
<q-input v-model="internalValue" />
<QInput @keydown="handleKeydown" ref="vnInputRef" v-model="internalValue" />
</template>

View File

@ -163,7 +163,7 @@ function addDefaultData(data) {
/>
<QFile
ref="inputFileRef"
:label="t('entry.buys.file')"
:label="t('globals.file')"
v-model="dms.files"
:multiple="false"
:accept="allowedContentTypes"

View File

@ -1,5 +1,5 @@
<script setup>
import { computed, ref, useAttrs } from 'vue';
import { computed, ref, useAttrs, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRequired } from 'src/composables/useRequired';
@ -34,6 +34,14 @@ const $props = defineProps({
type: Boolean,
default: true,
},
insertable: {
type: Boolean,
default: false,
},
maxlength: {
type: Number,
default: null,
},
});
const vnInputRef = ref(null);
@ -69,6 +77,9 @@ const mixinRules = [
requiredFieldRule,
...($attrs.rules ?? []),
(val) => {
const { maxlength } = vnInputRef.value;
if (maxlength && +val.length > maxlength)
return t(`maxLength`, { value: maxlength });
const { min, max } = vnInputRef.value.$attrs;
if (!min) return null;
if (min >= 0) if (Math.floor(val) < min) return t('inputMin', { value: min });
@ -78,6 +89,33 @@ const mixinRules = [
}
},
];
const handleKeydown = (e) => {
if (e.key === 'Backspace') return;
if ($props.insertable && e.key.match(/[0-9]/)) {
handleInsertMode(e);
}
};
const handleInsertMode = (e) => {
e.preventDefault();
const input = e.target;
const cursorPos = input.selectionStart;
const { maxlength } = vnInputRef.value;
let currentValue = value.value;
if (!currentValue) currentValue = e.key;
const newValue = e.key;
if (newValue && !isNaN(newValue) && cursorPos < maxlength) {
value.value =
currentValue.substring(0, cursorPos) +
newValue +
currentValue.substring(cursorPos + 1);
}
nextTick(() => {
input.setSelectionRange(cursorPos + 1, cursorPos + 1);
});
};
</script>
<template>
@ -89,10 +127,12 @@ const mixinRules = [
:type="$attrs.type"
:class="{ required: isRequired }"
@keyup.enter="emit('keyup.enter')"
@keydown="handleKeydown"
:clearable="false"
:rules="mixinRules"
:lazy-rules="true"
hide-bottom-space
:data-cy="$attrs.dataCy ?? $attrs.label + '_input'"
>
<template v-if="$slots.prepend" #prepend>
<slot name="prepend" />
@ -101,7 +141,13 @@ const mixinRules = [
<QIcon
name="close"
size="xs"
v-if="hover && value && !$attrs.disabled && $props.clearable"
v-if="
hover &&
value &&
!$attrs.disabled &&
!$attrs.readonly &&
$props.clearable
"
@click="
() => {
value = null;
@ -123,9 +169,11 @@ const mixinRules = [
<i18n>
en:
inputMin: Must be more than {value}
maxLength: The value exceeds {value} characters
inputMax: Must be less than {value}
es:
inputMin: Debe ser mayor a {value}
maxLength: El valor excede los {value} carácteres
inputMax: Debe ser menor a {value}
</i18n>
<style lang="scss">

View File

@ -1,8 +1,7 @@
<script setup>
import { onMounted, watch, computed, ref } from 'vue';
import { onMounted, watch, computed, ref, useAttrs } from 'vue';
import { date } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useAttrs } from 'vue';
import VnDate from './VnDate.vue';
import { useRequired } from 'src/composables/useRequired';

View File

@ -2,5 +2,12 @@
const model = defineModel({ type: Boolean, required: true });
</script>
<template>
<QRadio v-model="model" v-bind="$attrs" dense :dark="true" class="q-mr-sm" />
<QRadio
v-model="model"
v-bind="$attrs"
dense
:dark="true"
class="q-mr-sm"
size="xs"
/>
</template>

View File

@ -1,7 +1,7 @@
<script setup>
import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
import { useArrayData } from 'src/composables/useArrayData';
import { useRequired } from 'src/composables/useRequired';
import dataByOrder from 'src/utils/dataByOrder';
@ -90,6 +90,10 @@ const $props = defineProps({
type: Boolean,
default: false,
},
dataKey: {
type: String,
default: null,
},
});
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
@ -98,14 +102,14 @@ const { optionLabel, optionValue, optionFilter, optionFilterValue, options, mode
const myOptions = ref([]);
const myOptionsOriginal = ref([]);
const vnSelectRef = ref();
const dataRef = ref();
const lastVal = ref();
const noOneText = t('globals.noOne');
const noOneOpt = ref({
[optionValue.value]: false,
[optionLabel.value]: noOneText,
});
const isLoading = ref(false);
const useURL = computed(() => $props.url);
const value = computed({
get() {
return $props.modelValue;
@ -129,11 +133,16 @@ watch(modelValue, async (newValue) => {
onMounted(() => {
setOptions(options.value);
if ($props.url && $props.modelValue && !findKeyInOptions())
if (useURL.value && $props.modelValue && !findKeyInOptions())
fetchFilter($props.modelValue);
if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
});
const arrayDataKey =
$props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label);
const arrayData = useArrayData(arrayDataKey, { url: $props.url, searchUrl: false });
function findKeyInOptions() {
if (!$props.options) return;
return filter($props.modelValue, $props.options)?.length;
@ -168,7 +177,7 @@ function filter(val, options) {
}
async function fetchFilter(val) {
if (!$props.url || !dataRef.value) return;
if (!$props.url) return;
const { fields, include, sortBy, limit } = $props;
const key =
@ -190,8 +199,11 @@ async function fetchFilter(val) {
const fetchOptions = { where, include, limit };
if (fields) fetchOptions.fields = fields;
if (sortBy) fetchOptions.order = sortBy;
arrayData.reset(['skip', 'filter.skip', 'page']);
return dataRef.value.fetch(fetchOptions);
const { data } = await arrayData.applyFilter({ filter: fetchOptions });
setOptions(data);
return data;
}
async function filterHandler(val, update) {
@ -231,20 +243,47 @@ function nullishToTrue(value) {
const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
async function onScroll({ to, direction, from, index }) {
const lastIndex = myOptions.value.length - 1;
if (from === 0 && index === 0) return;
if (!useURL.value && !$props.fetchRef) return;
if (direction === 'decrease') return;
if (to === lastIndex && arrayData.store.hasMoreData && !isLoading.value) {
isLoading.value = true;
await arrayData.loadMore();
setOptions(arrayData.store.data);
vnSelectRef.value.scrollTo(lastIndex);
isLoading.value = false;
}
}
defineExpose({ opts: myOptions });
function handleKeyDown(event) {
if (event.key === 'Tab') {
event.preventDefault();
const inputValue = vnSelectRef.value?.inputValue;
if (inputValue) {
const matchingOption = myOptions.value.find(
(option) =>
option[optionLabel.value].toLowerCase() === inputValue.toLowerCase()
);
if (matchingOption) {
emit('update:modelValue', matchingOption[optionValue.value]);
} else {
emit('update:modelValue', inputValue);
}
vnSelectRef.value?.hidePopup();
}
}
}
</script>
<template>
<FetchData
ref="dataRef"
:url="$props.url"
@on-fetch="(data) => setOptions(data)"
:where="where || { [optionValue]: value }"
:limit="limit"
:sort-by="sortBy"
:fields="fields"
:params="params"
/>
<QSelect
v-model="value"
:options="myOptions"
@ -252,6 +291,7 @@ defineExpose({ opts: myOptions });
:option-value="optionValue"
v-bind="$attrs"
@filter="filterHandler"
@keydown="handleKeyDown"
:emit-value="nullishToTrue($attrs['emit-value'])"
:map-options="nullishToTrue($attrs['map-options'])"
:use-input="nullishToTrue($attrs['use-input'])"
@ -263,6 +303,10 @@ defineExpose({ opts: myOptions });
:rules="mixinRules"
virtual-scroll-slice-size="options.length"
hide-bottom-space
:input-debounce="useURL ? '300' : '0'"
:loading="isLoading"
@virtual-scroll="onScroll"
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
>
<template v-if="isClearable" #append>
<QIcon

View File

@ -86,7 +86,7 @@ async function send() {
</script>
<template>
<QDialog ref="dialogRef">
<QDialog ref="dialogRef" data-cy="vnSmsDialog">
<QCard class="q-pa-sm">
<QCardSection class="row items-center q-pb-none">
<span class="text-h6 text-grey">
@ -161,6 +161,7 @@ async function send() {
:loading="isLoading"
color="primary"
unelevated
data-cy="sendSmsBtn"
/>
</QCardActions>
</QCard>

View File

@ -83,7 +83,7 @@ async function fetch() {
<slot name="header" :entity="entity" dense>
<VnLv :label="`${entity.id} -`" :value="entity.name" />
</slot>
<slot name="header-right">
<slot name="header-right" :entity="entity">
<span></span>
</slot>
</div>

View File

@ -37,7 +37,7 @@ const $props = defineProps({
},
hiddenTags: {
type: Array,
default: () => ['filter', 'search', 'or', 'and'],
default: () => ['filter', 'or', 'and'],
},
customTags: {
type: Array,
@ -49,7 +49,7 @@ const $props = defineProps({
},
searchUrl: {
type: String,
default: 'params',
default: 'table',
},
redirect: {
type: Boolean,
@ -61,7 +61,6 @@ const emit = defineEmits([
'update:modelValue',
'refresh',
'clear',
'search',
'init',
'remove',
'setUserParams',
@ -75,6 +74,9 @@ const arrayData = useArrayData($props.dataKey, {
const route = useRoute();
const store = arrayData.store;
const userParams = ref({});
defineExpose({ search, sanitizer, params: userParams });
onMounted(() => {
userParams.value = $props.modelValue ?? {};
emit('init', { params: userParams.value });
@ -198,7 +200,7 @@ const customTags = computed(() =>
async function remove(key) {
userParams.value[key] = undefined;
search();
await search();
emit('remove', key);
emit('update:modelValue', userParams.value);
}
@ -224,8 +226,6 @@ function sanitizer(params) {
}
return params;
}
defineExpose({ search, sanitizer, userParams });
</script>
<template>

View File

@ -9,7 +9,7 @@ const props = defineProps({
const config = reactive({
sip: { icon: 'phone', href: `sip:${props.phoneNumber}` },
'say-simple': {
icon: 'help',
icon: 'vn:saysimple',
href: null,
channel: props.channel,
},
@ -17,19 +17,12 @@ const config = reactive({
const type = Object.keys(config).find((key) => key in useAttrs()) || 'sip';
onBeforeMount(async () => {
let url;
let { channel } = config[type];
if (type === 'say-simple') {
url = (await axios.get('SaySimpleConfigs/findOne')).data.url;
if (!channel)
channel = (
await axios.get('SaySimpleCountries/findOne', {
params: {
filter: { fields: ['channel'], where: { countryFk: 0 } },
},
})
).data?.channel;
const { url, defaultChannel } = (await axios.get('SaySimpleConfigs/findOne'))
.data;
if (!channel) channel = defaultChannel;
config[
type

View File

@ -65,13 +65,9 @@ onBeforeRouteLeave((to, from, next) => {
auto-load
@on-fetch="(data) => (observationTypes = data)"
/>
<QCard class="q-pa-xs q-mb-xl full-width" v-if="$props.addNote">
<QCard class="q-pa-xs q-mb-lg full-width" v-if="$props.addNote">
<QCardSection horizontal>
<VnAvatar :worker-id="currentUser.id" size="md" />
<div class="full-width row justify-between q-pa-xs">
<VnUserLink :name="t('New note')" :worker-id="currentUser.id" />
{{ t('globals.now') }}
</div>
{{ t('New note') }}
</QCardSection>
<QCardSection class="q-px-xs q-my-none q-py-none">
<VnRow class="full-width">
@ -144,7 +140,7 @@ onBeforeRouteLeave((to, from, next) => {
<div class="full-width row justify-between q-pa-xs">
<div>
<VnUserLink
:name="`${note.worker.user.nickname}`"
:name="`${note.worker.user.name}`"
:worker-id="note.worker.id"
/>
<QBadge

View File

@ -44,7 +44,7 @@ const props = defineProps({
},
limit: {
type: Number,
default: 10,
default: 20,
},
userParams: {
type: Object,
@ -100,7 +100,7 @@ const arrayData = useArrayData(props.dataKey, {
const store = arrayData.store;
onMounted(async () => {
if (props.autoLoad) await fetch();
if (props.autoLoad && !store.data?.length) await fetch();
mounted.value = true;
});
@ -115,7 +115,11 @@ watch(
watch(
() => store.data,
(data) => emit('onChange', data)
(data) => {
if (!mounted.value) return;
emit('onChange', data);
},
{ immediate: true }
);
watch(
@ -130,8 +134,22 @@ async function fetch(params) {
useArrayData(props.dataKey, params);
arrayData.reset(['filter.skip', 'skip', 'page']);
await arrayData.fetch({ append: false });
if (!store.hasMoreData) isLoading.value = false;
return emitStoreData();
}
async function update(params) {
useArrayData(props.dataKey, params);
const { limit, skip } = store;
store.limit = limit + skip;
store.skip = 0;
await arrayData.fetch({ append: false });
store.limit = limit;
store.skip = skip;
return emitStoreData();
}
function emitStoreData() {
if (!store.hasMoreData) isLoading.value = false;
emit('onFetch', store.data);
return store.data;
}
@ -177,7 +195,7 @@ async function onLoad(index, done) {
done(isDone);
}
defineExpose({ fetch, addFilter, paginate });
defineExpose({ fetch, update, addFilter, paginate });
</script>
<template>

View File

@ -1,5 +1,5 @@
<template>
<div class="vn-row q-gutter-md q-mb-md">
<div class="vn-row q-gutter-md">
<slot />
</div>
</template>
@ -18,6 +18,9 @@
&:not(.wrap) {
flex-direction: column;
}
&[fixed] {
flex-direction: row;
}
}
}
</style>

View File

@ -45,7 +45,7 @@ const props = defineProps({
},
limit: {
type: Number,
default: 10,
default: 20,
},
userParams: {
type: Object,
@ -130,6 +130,7 @@ async function search() {
dense
standout
autofocus
data-cy="vnSearchBar"
>
<template #prepend>
<QIcon

View File

@ -1,6 +1,5 @@
<script setup>
import { useRoute } from 'vue-router';
import { defineProps } from 'vue';
const props = defineProps({
routeName: {

View File

@ -1,11 +1,24 @@
import { useSession } from 'src/composables/useSession';
import { getUrl } from './getUrl';
import axios from 'axios';
import { exportFile } from 'quasar';
const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia();
export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) {
let appUrl = await getUrl('', 'lilium');
appUrl = appUrl.replace('/#/', '');
window.open(url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`);
const appUrl = (await getUrl('', 'lilium')).replace('/#/', '');
const response = await axios.get(
url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`,
{ responseType: 'blob' }
);
const contentDisposition = response.headers['content-disposition'];
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
const filename =
matches != null && matches[1]
? matches[1].replace(/['"]/g, '')
: 'downloaded-file';
exportFile(filename, response.data);
}

View File

@ -75,18 +75,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
limit: store.limit,
};
let exprFilter;
let userParams = { ...store.userParams };
if (store?.exprBuilder) {
const where = buildFilter(userParams, (param, value) => {
const res = store.exprBuilder(param, value);
if (res) delete userParams[param];
return res;
});
exprFilter = where ? { where } : null;
}
Object.assign(filter, store.userFilter, exprFilter);
Object.assign(filter, store.userFilter);
let where;
if (filter?.where || store.filter?.where)
where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
@ -95,12 +87,29 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const params = { filter };
Object.assign(params, userParams);
params.filter.skip = store.skip;
if (store.order && store.order.length) params.filter.order = store.order;
if (params.filter) params.filter.skip = store.skip;
if (store?.order && typeof store?.order == 'string') store.order = [store.order];
if (store.order?.length) params.filter.order = [...store.order];
else delete params.filter.order;
store.currentFilter = JSON.parse(JSON.stringify(params));
delete store.currentFilter.filter.include;
store.currentFilter.filter = JSON.stringify(store.currentFilter.filter);
let exprFilter;
if (store?.exprBuilder) {
exprFilter = buildFilter(params, (param, value) => {
if (param == 'filter') return;
const res = store.exprBuilder(param, value);
if (res) delete params[param];
return res;
});
}
if (params.filter.where || exprFilter)
params.filter.where = { ...params.filter.where, ...exprFilter };
params.filter = JSON.stringify(params.filter);
store.currentFilter = params;
store.isLoading = true;
const response = await axios.get(store.url, {
signal: canceller.signal,
@ -247,6 +256,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
}
function updateStateParams() {
if (!route?.path) return;
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
if (store?.searchUrl)
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);
@ -270,7 +280,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const pushUrl = { path: to };
if (to.endsWith('/list') || to.endsWith('/'))
pushUrl.query = newUrl.query;
destroy();
else destroy();
return router.push(pushUrl);
}
}

View File

@ -0,0 +1,8 @@
import { openURL } from 'quasar';
const defaultWindowFeatures = {
noopener: true,
noreferrer: true,
};
export default function (url, windowFeatures = defaultWindowFeatures, fn = undefined) {
openURL(url, fn, windowFeatures);
}

View File

@ -241,7 +241,7 @@ input::-webkit-inner-spin-button {
th,
td {
padding: 1px 10px 1px 10px;
max-width: 100px;
max-width: 130px;
div span {
overflow: hidden;
white-space: nowrap;

View File

@ -9,7 +9,7 @@ function parseJSON(str, fallback) {
}
export default function (route, param) {
// catch route query params
const params = parseJSON(route?.query?.params, {});
const params = parseJSON(route?.query?.table, {});
// extract and parse filter from params
const { filter: filterStr = '{}' } = params;

View File

@ -59,7 +59,7 @@ globals:
downloadCSVSuccess: CSV downloaded successfully
reference: Reference
agency: Agency
wareHouseOut: Warehouse Out
warehouseOut: Warehouse Out
wareHouseIn: Warehouse In
landed: Landed
shipped: Shipped
@ -106,6 +106,26 @@ globals:
weight: Weight
error: Ups! Something went wrong
recalc: Recalculate
alias: Alias
vat: VAT
intrastat: Intrastat
tags: Tags
size: Size
producer: Producer
origin: Origin
state: State
subtotal: Subtotal
visible: Visible
price: Price
client: Client
country: Country
phone: Phone
mobile: Mobile
postcode: Postcode
street: Street
tag: Tag
ticketId: Ticket ID
confirmed: Confirmed
small: Small
medium: Medium
big: Big
@ -278,6 +298,7 @@ globals:
clientsActionsMonitor: Clients and actions
serial: Serial
medical: Mutual
pit: IRPF
RouteExtendedList: Router
wasteRecalc: Waste recaclulate
translations: Translations
@ -301,13 +322,10 @@ globals:
maxTemperature: Max
minTemperature: Min
params:
id: ID
clientFk: Client id
salesPersonFk: Sales person
warehouseFk: Warehouse
provinceFk: Province
from: From
To: To
stateFk: State
email: Email
SSN: SSN
@ -337,16 +355,12 @@ login:
loginError: Invalid username or password
fieldRequired: This field is required
twoFactorRequired: Two-factor verification required
twoFactor:
code: Code
twoFactorRequired:
validate: Validate
insert: Enter the verification code
explanation: >-
Please, enter the verification code that we have sent to your email in the
next 5 minutes
verifyEmail:
pageTitles:
verifyEmail: Email verification
recoverPassword:
userOrEmail: User or recovery email
explanation: >-
@ -358,15 +372,7 @@ resetPassword:
entry:
list:
newEntry: New entry
landed: Landed
invoiceNumber: Invoice number
supplier: Supplier
booked: Booked
confirmed: Confirmed
ordered: Ordered
tableVisibleColumns:
id: Id
reference: Reference
created: Creation
supplierFk: Supplier
isBooked: Booked
@ -379,236 +385,117 @@ entry:
summary:
commission: Commission
currency: Currency
company: Company
reference: Reference
invoiceNumber: Invoice number
ordered: Ordered
confirmed: Confirmed
booked: Booked
excludedFromAvailable: Inventory
travelReference: Reference
travelAgency: Agency
travelShipped: Shipped
travelWarehouseOut: Warehouse Out
travelDelivered: Delivered
travelLanded: Landed
travelWarehouseIn: Warehouse In
travelReceived: Received
buys: Buys
quantity: Quantity
stickers: Stickers
package: Package
weight: Weight
packing: Packing
grouping: Grouping
buyingValue: Buying value
import: Import
pvp: PVP
item: Item
basicData:
supplier: Supplier
travel: Travel
reference: Reference
invoiceNumber: Invoice number
company: Company
currency: Currency
commission: Commission
observation: Observation
ordered: Ordered
confirmed: Confirmed
booked: Booked
excludedFromAvailable: Inventory
agency: Agency
warehouseOut: Warehouse Out
warehouseIn: Warehouse In
shipped: Shipped
landed: Landed
id: ID
buys:
groupingPrice: Grouping price
packingPrice: Packing price
reference: Reference
observations: Observations
item: Item
size: Size
packing: Packing
grouping: Grouping
buyingValue: Buying value
packagingFk: Box
file: File
name: Name
producer: Producer
type: Type
color: Color
id: ID
printedStickers: Printed stickers
notes:
observationType: Observation type
descriptor:
agency: Agency
landed: Landed
warehouseOut: Warehouse Out
latestBuys:
tableVisibleColumns:
image: Picture
itemFk: Item ID
packing: Packing
grouping: Grouping
quantity: Quantity
size: Size
tags: Tags
type: Type
intrastat: Intrastat
origin: Origin
weightByPiece: Weight/Piece
isActive: Active
family: Family
entryFk: Entry
buyingValue: Buying value
freightValue: Freight value
comissionValue: Commission value
description: Description
packageValue: Package value
isIgnored: Is ignored
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
weight: Weight
packagingFk: Package
packingOut: Package out
landing: Landing
isExcludedFromAvailable: Es inventory
ticket:
pageTitles:
tickets: Tickets
list: List
ticketCreate: New ticket
summary: Summary
basicData: Basic Data
boxing: Boxing
sms: Sms
notes: Notes
sale: Sale
dms: File management
volume: Volume
observation: Notes
ticketAdvance: Advance tickets
futureTickets: Future tickets
purchaseRequest: Purchase request
weeklyTickets: Weekly tickets
list:
nickname: Nickname
state: State
shipped: Shipped
landed: Landed
salesPerson: Sales person
total: Total
card:
ticketId: Ticket ID
state: State
customerId: Customer ID
salesPerson: Sales person
agency: Agency
shipped: Shipped
warehouse: Warehouse
customerCard: Customer card
alias: Alias
ticketList: Ticket List
newOrder: New Order
boxing:
expedition: Expedition
item: Item
created: Created
worker: Worker
selectTime: 'Select time:'
selectVideo: 'Select video:'
notFound: No videos available
summary:
state: State
salesPerson: Sales person
agency: Agency
zone: Zone
warehouse: Warehouse
collection: Collection
route: Route
invoice: Invoice
shipped: Shipped
landed: Landed
consigneePhone: Consignee phone
consigneeMobile: Consignee mobile
consigneeAddress: Consignee address
clientPhone: Client phone
clientMobile: Client mobile
consignee: Consignee
subtotal: Subtotal
vat: VAT
total: Total
saleLines: Line items
item: Item
visible: Visible
available: Available
quantity: Quantity
price: Price
discount: Discount
packing: Packing
hasComponentLack: Component lack
itemShortage: Not visible
claim: Claim
reserved: Reserved
created: Created
package: Package
taxClass: Tax class
services: Services
requester: Requester
atender: Atender
request: Request
weight: Weight
goTo: Go to
summaryAmount: Summary
purchaseRequest: Purchase request
service: Service
description: Description
attender: Attender
ok: Ok
create:
client: Client
address: Address
landed: Landed
warehouse: Warehouse
agency: Agency
invoiceOut:
list:
ref: Reference
issued: Issued
shortIssued: Issued
client: Client
created: Created
shortCreated: Created
company: Company
dued: Due date
shortDued: Due date
amount: Amount
card:
issued: Issued
client: Client
company: Company
customerCard: Customer card
summary:
issued: Issued
created: Created
dued: Due
booked: Booked
company: Company
taxBreakdown: Tax breakdown
type: Type
taxableBase: Taxable base
rate: Rate
fee: Fee
tickets: Tickets
ticketId: Ticket id
nickname: Alias
shipped: Shipped
totalWithVat: Amount
globalInvoices:
errors:
@ -621,23 +508,16 @@ invoiceOut:
invoiceWithFutureDate: Exists an invoice with a future date
noTicketsToInvoice: There are not tickets to invoice
criticalInvoiceError: 'Critical invoicing error, process stopped'
invalidSerialTypeForAll: The serial type must be global when invoicing all clients
table:
client: Client
addressId: Address id
streetAddress: Street
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} of {getNAddresses}'
pdfsNumberText: '{nPdfs} of {totalPdfs} PDFs'
negativeBases:
from: From
to: To
company: Company
country: Country
clientId: Client Id
client: Client
amount: Amount
base: Base
ticketId: Ticket Id
active: Active
hasToInvoice: Has to Invoice
verifiedData: Verified Data
@ -650,15 +530,6 @@ shelving:
priority: Priority
newShelving: New Shelving
summary:
code: Code
parking: Parking
priority: Priority
worker: Worker
recyclable: Recyclable
basicData:
code: Code
parking: Parking
priority: Priority
recyclable: Recyclable
parking:
pickingOrder: Picking order
@ -671,56 +542,27 @@ parking:
order:
field:
salesPersonFk: Sales Person
clientFk: Client
isConfirmed: Confirmed
created: Created
landed: Landed
hour: Hour
agency: Agency
total: Total
form:
clientFk: Client
addressFk: Address
landed: Landed
agencyModeFk: Agency
list:
newOrder: New Order
summary:
basket: Basket
nickname: Nickname
company: Company
confirmed: Confirmed
notConfirmed: Not confirmed
created: Created
landed: Landed
phone: Phone
createdFrom: Created From
address: Address
notes: Notes
subtotal: Subtotal
total: Total
vat: VAT
state: State
alias: Alias
items: Items
orderTicketList: Order Ticket List
details: Details
item: Item
quantity: Quantity
price: Price
amount: Amount
confirm: Confirm
confirmLines: Confirm lines
department:
pageTitles:
basicData: Basic data
department: Department
summary: Summary
name: Name
code: Code
chat: Chat
bossDepartment: Boss Department
email: Email
selfConsumptionCustomer: Self-consumption customer
telework: Telework
notifyOnErrors: Notify on errors
@ -729,47 +571,11 @@ department:
hasToSendMail: Send check-ins by email
departmentRemoved: Department removed
worker:
pageTitles:
workers: Workers
list: List
basicData: Basic data
summary: Summary
notifications: Notifications
workerCreate: New worker
department: Department
pda: PDA
notes: Notas
dms: My documentation
pbx: Private Branch Exchange
log: Log
calendar: Calendar
timeControl: Time control
locker: Locker
balance: Balance
medical: Medical
operator: Operator
list:
name: Name
email: Email
phone: Phone
mobile: Mobile
active: Active
department: Department
schedule: Schedule
newWorker: New worker
card:
workerId: Worker ID
user: User
name: Name
email: Email
phone: Phone
mobile: Mobile
active: Active
warehouse: Warehouse
agency: Agency
salesPerson: Sales person
summary:
basicData: Basic data
boss: Boss
phoneExtension: Phone extension
entPhone: Enterprise phone
@ -780,15 +586,15 @@ worker:
role: Role
sipExtension: Extension
locker: Locker
fiDueDate: Fecha de caducidad del DNI
sex: Sexo
seniority: Antigüedad
fiDueDate: FI due date
sex: Sex
seniority: Seniority
fi: DNI/NIE/NIF
birth: Fecha de nacimiento
isFreelance: Autónomo
birth: Birth
isFreelance: Freelance
isSsDiscounted: Bonificación SS
hasMachineryAuthorized: Autorizado para llevar maquinaria
isDisable: Trabajador desactivado
hasMachineryAuthorized: Machinery authorized
isDisable: Disable
notificationsManager:
activeNotifications: Active notifications
availableNotifications: Available notifications
@ -801,19 +607,12 @@ worker:
serialNumber: Serial number
removePDA: Deallocate PDA
create:
name: Name
lastName: Last name
birth: Birth
fi: Fi
code: Worker code
phone: Phone
postcode: Postcode
province: Province
city: City
street: Street
webUser: Web user
personalEmail: Personal email
company: Company
boss: Boss
payMethods: Pay method
iban: IBAN
@ -825,16 +624,13 @@ worker:
endDate: End date
center: Training center
invoice: Invoice
amount: Amount
remark: Remark
hasDiploma: Has diploma
medical:
tableVisibleColumns:
date: Date
time: Hour
center: Formation Center
invoice: Invoice
amount: Amount
isFit: Fit
remark: Observations
imageNotFound: Image not found
@ -858,18 +654,7 @@ worker:
isOnReservationMode: Reservation mode
machine: Machine
wagon:
pageTitles:
wagons: Wagons
wagonsList: Wagons List
wagonCreate: Create wagon
wagonEdit: Edit wagon
typesList: Types List
typeCreate: Create type
typeEdit: Edit type
wagonCounter: Trolley counter
wagonTray: Tray List
type:
name: Name
submit: Submit
reset: Reset
trayColor: Tray color
@ -877,13 +662,10 @@ wagon:
list:
plate: Plate
volume: Volume
type: Type
remove: Remove
removeItem: Wagon removed successfully
create:
plate: Plate
volume: Volume
type: Type
label: Label
warnings:
noData: No data available
@ -900,26 +682,17 @@ wagon:
supplier:
list:
payMethod: Pay method
payDeadline: Pay deadline
payDay: Pay day
account: Account
newSupplier: New supplier
tableVisibleColumns:
id: Id
name: Name
nif: NIF/CIF
nickname: Alias
account: Account
payMethod: Pay Method
payDay: Pay Day
country: Country
summary:
responsible: Responsible
notes: Notes
verified: Verified
isActive: Is active
billingData: Billing data
payMethod: Pay method
payDeadline: Pay deadline
payDay: Pay day
account: Account
@ -932,17 +705,12 @@ supplier:
fiscalAddress: Fiscal address
socialName: Social name
taxNumber: Tax number
street: Street
city: City
postCode: Postcode
province: Province
country: Country
create:
supplierName: Supplier name
basicData:
alias: Alias
workerFk: Responsible
isSerious: Verified
isReal: Verified
isActive: Active
isPayMethodChecked: PayMethod checked
note: Notes
@ -955,36 +723,18 @@ supplier:
sageWithholdingFk: Sage withholding
sageTransactionTypeFk: Sage transaction type
supplierActivityFk: Supplier activity
healthRegister: Health register
street: Street
postcode: Postcode
city: City *
provinceFk: Province
country: Country
isTrucker: Trucker
isVies: Vies
billingData:
payMethodFk: Billing data
payDemFk: Payment deadline
payDay: Pay day
accounts:
iban: Iban
bankEntity: Bank entity
beneficiary: Beneficiary
contacts:
name: Name
phone: Phone
mobile: Mobile
email: Email
observation: Notes
addresses:
street: Street
postcode: Postcode
phone: Phone
name: Name
city: City
province: Province
mobile: Mobile
agencyTerms:
agencyFk: Agency
minimumM3: Minimum M3
@ -996,25 +746,16 @@ supplier:
addRow: Add row
consumption:
entry: Entry
date: Date
reference: Reference
travel:
travelList:
tableVisibleColumns:
id: Id
ref: Reference
agency: Agency
shipped: Shipped
landed: Landed
shipHour: Shipment Hour
landHour: Landing Hour
warehouseIn: Warehouse in
warehouseOut: Warehouse out
totalEntries: Total entries
totalEntriesTooltip: Total entries
daysOnward: Landed days onwards
summary:
confirmed: Confirmed
entryId: Entry Id
freight: Freight
package: Package
@ -1027,64 +768,90 @@ travel:
AddEntry: Add entry
thermographs: Thermographs
hb: HB
variables:
search: Id/Reference
agencyModeFk: Agency
warehouseInFk: ' Warehouse In'
warehouseOutFk: Warehouse Out
landedFrom: Landed from
landedTo: Landed to
continent: Continent out
totalEntries: Total entries
basicData:
reference: Reference
agency: Agency
shipped: Shipped
landed: Landed
warehouseOut: Warehouse Out
warehouseIn: Warehouse In
delivered: Delivered
received: Received
daysInForward: Days in forward
isRaid: Raid
thermographs:
code: Code
temperature: Temperature
state: State
destination: Destination
created: Created
thermograph: Thermograph
reference: Reference
type: Type
company: Company
warehouse: Warehouse
travelFileDescription: 'Travel id { travelId }'
file: File
item:
descriptor:
buyer: Buyer
color: Color
category: Category
available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary
list:
id: Identifier
stems: Stems
category: Category
typeName: Type
isActive: Active
userName: Buyer
weightByPiece: Weight/Piece
stemMultiplier: Multiplier
fixedPrice:
itemFk: Item ID
groupingPrice: Grouping price
packingPrice: Packing price
hasMinPrice: Has min price
minPrice: Min price
started: Started
ended: Ended
create:
priority: Priority
buyRequest:
requester: Requester
requested: Requested
attender: Atender
achieved: Achieved
concept: Concept
summary:
otherData: Other data
tax: Tax
botanical: Botanical
barcode: Barcode
completeName: Complete name
family: Familiy
stems: Stems
multiplier: Multiplier
buyer: Buyer
doPhoto: Do photo
intrastatCode: Intrastat code
ref: Reference
relevance: Relevance
weight: Weight (gram)/stem
units: Units/box
expense: Expense
generic: Generic
recycledPlastic: Recycled plastic
nonRecycledPlastic: Non recycled plastic
minSalesQuantity: Min sales quantity
genus: Genus
specie: Specie
components:
topbar: {}
itemsFilterPanel:
typeFk: Type
tag: Tag
value: Value
# ItemFixedPriceFilter
buyerFk: Buyer
warehouseFk: Warehouse
started: From
ended: To
mine: For me
hasMinPrice: Minimum price
# LatestBuysFilter
salesPersonFk: Buyer
supplierFk: Supplier
from: From
to: To
active: Is active
visible: Is visible
floramondo: Is floramondo
showBadDates: Show future items
userPanel:
copyToken: Token copied to clipboard
settings: Settings
logOut: Log Out
localWarehouse: Local warehouse
localBank: Local bank
localCompany: Local company
@ -1092,9 +859,9 @@ components:
userCompany: User company
smartCard:
downloadFile: Download file
clone: Clone
openCard: View
openSummary: Summary
viewSummary: Summary
cardDescriptor:
mainList: Main list
summary: Summary

View File

@ -58,8 +58,8 @@ globals:
downloadCSVSuccess: Descarga de CSV exitosa
reference: Referencia
agency: Agencia
wareHouseOut: Alm. salida
wareHouseIn: Alm. entrada
warehouseOut: Alm. salida
warehouseIn: Alm. entrada
landed: F. entrega
shipped: F. envío
totalEntries: Ent. totales
@ -108,6 +108,26 @@ globals:
weight: Peso
error: ¡Ups! Algo salió mal
recalc: Recalcular
alias: Alias
vat: IVA
intrastat: Intrastat
tags: Etiquetas
size: Medida
producer: Productor
origin: Origen
state: Estado
subtotal: Subtotal
visible: Visible
price: Precio
client: Cliente
country: País
phone: Teléfono
mobile: Móvil
postcode: Código postal
street: Dirección
tag: Etiqueta
ticketId: ID ticket
confirmed: Confirmado
small: Pequeño/a
medium: Mediano/a
big: Grande
@ -283,6 +303,7 @@ globals:
clientsActionsMonitor: Clientes y acciones
serial: Facturas por serie
medical: Mutua
pit: IRPF
wasteRecalc: Recalcular mermas
translations: Traducciones
operator: Operario
@ -305,13 +326,10 @@ globals:
maxTemperature: Máx
minTemperature: Mín
params:
id: Id
clientFk: Id cliente
salesPersonFk: Comercial
warehouseFk: Almacén
provinceFk: Provincia
from: Desde
To: Hasta
stateFk: Estado
departmentFk: Departamento
email: Correo
@ -342,13 +360,9 @@ login:
fieldRequired: Este campo es obligatorio
twoFactorRequired: Verificación de doble factor requerida
twoFactor:
code: Código
validate: Validar
insert: Introduce el código de verificación
explanation: Por favor introduce el código de verificación que te hemos enviado a tu email en los próximos 5 minutos
verifyEmail:
pageTitles:
verifyEmail: Verificación de correo
recoverPassword:
userOrEmail: Usuario o correo de recuperación
explanation: >-
@ -360,15 +374,7 @@ resetPassword:
entry:
list:
newEntry: Nueva entrada
landed: F. entrega
invoiceNumber: Núm. factura
supplier: Proveedor
booked: Asentado
confirmed: Confirmado
ordered: Pedida
tableVisibleColumns:
id: Id
reference: Referencia
created: Creación
supplierFk: Proveedor
isBooked: Asentado
@ -381,11 +387,8 @@ entry:
summary:
commission: Comisión
currency: Moneda
company: Empresa
reference: Referencia
invoiceNumber: Núm. factura
ordered: Pedida
confirmed: Confirmada
booked: Contabilizada
excludedFromAvailable: Inventario
travelReference: Referencia
@ -394,230 +397,108 @@ entry:
travelWarehouseOut: Alm. salida
travelDelivered: Enviada
travelLanded: F. entrega
travelWarehouseIn: Alm. entrada
travelReceived: Recibida
buys: Compras
quantity: Cantidad
stickers: Etiquetas
package: Embalaje
weight: Peso
packing: Packing
grouping: Grouping
buyingValue: Coste
import: Importe
pvp: PVP
item: Artículo
basicData:
supplier: Proveedor
travel: Envío
reference: Referencia
invoiceNumber: Núm. factura
company: Empresa
currency: Moneda
observation: Observación
commission: Comisión
ordered: Pedida
confirmed: Confirmado
booked: Asentado
excludedFromAvailable: Inventario
agency: Agencia
warehouseOut: Alm. salida
warehouseIn: Alm. entrada
shipped: F. envío
landed: F. entrega
id: ID
buys:
groupingPrice: Precio grouping
packingPrice: Precio packing
reference: Referencia
observations: Observaciónes
item: Artículo
size: Medida
packing: Packing
grouping: Grouping
buyingValue: Coste
packagingFk: Embalaje
file: Fichero
name: Nombre
producer: Productor
type: Tipo
color: Color
id: ID
printedStickers: Etiquetas impresas
notes:
observationType: Tipo de observación
descriptor:
agency: Agencia
landed: F. entrega
warehouseOut: Alm. salida
latestBuys:
tableVisibleColumns:
image: Foto
itemFk: Id Artículo
packing: packing
grouping: Grouping
quantity: Cantidad
size: Medida
tags: Etiquetas
type: Tipo
intrastat: Intrastat
origin: Origen
weightByPiece: Peso (gramos)/tallo
isActive: Activo
family: Familia
entryFk: Entrada
buyingValue: Coste
freightValue: Porte
comissionValue: Comisión
description: Descripción
packageValue: Embalaje
isIgnored: Ignorado
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
weight: Peso
packagingFk: Embalaje
packingOut: Embalaje envíos
landing: Llegada
isExcludedFromAvailable: Es inventario
ticket:
pageTitles:
tickets: Tickets
list: Listado
ticketCreate: Nuevo ticket
summary: Resumen
basicData: Datos básicos
boxing: Encajado
sms: Sms
notes: Notas
sale: Lineas del pedido
dms: Gestión documental
volume: Volumen
observation: Notas
ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro
expedition: Expedición
purchaseRequest: Petición de compra
weeklyTickets: Tickets programados
saleTracking: Líneas preparadas
services: Servicios
tracking: Estados
components: Componentes
pictures: Fotos
packages: Embalajes
list:
nickname: Alias
state: Estado
shipped: Enviado
landed: Entregado
salesPerson: Comercial
total: Total
card:
ticketId: ID ticket
state: Estado
customerId: ID cliente
salesPerson: Comercial
agency: Agencia
shipped: Enviado
warehouse: Almacén
customerCard: Ficha del cliente
alias: Alias
ticketList: Listado de tickets
newOrder: Nuevo pedido
boxing:
expedition: Expedición
item: Artículo
created: Creado
worker: Trabajador
selectTime: 'Seleccionar hora:'
selectVideo: 'Seleccionar vídeo:'
notFound: No hay vídeos disponibles
summary:
state: Estado
salesPerson: Comercial
agency: Agencia
zone: Zona
warehouse: Almacén
collection: Colección
route: Ruta
invoice: Factura
shipped: Enviado
landed: Entregado
consigneePhone: Tel. consignatario
consigneeMobile: Móv. consignatario
consigneeAddress: Dir. consignatario
clientPhone: Tel. cliente
clientMobile: Móv. cliente
consignee: Consignatario
subtotal: Subtotal
vat: IVA
total: Total
saleLines: Líneas del pedido
item: Artículo
visible: Visible
available: Disponible
quantity: Cantidad
price: Precio
discount: Descuento
packing: Encajado
hasComponentLack: Faltan componentes
itemShortage: No visible
claim: Reclamación
reserved: Reservado
created: Fecha creación
package: Embalaje
taxClass: Tipo IVA
services: Servicios
requester: Solicitante
atender: Comprador
request: Petición de compra
weight: Peso
goTo: Ir a
summaryAmount: Resumen
purchaseRequest: Petición de compra
service: Servicio
description: Descripción
attender: Consignatario
create:
client: Cliente
address: Dirección
landed: F. entrega
warehouse: Almacén
agency: Agencia
invoiceOut:
list:
ref: Referencia
issued: Fecha emisión
shortIssued: F. emisión
client: Cliente
created: Fecha creación
shortCreated: F. creación
company: Empresa
dued: Fecha vencimineto
shortDued: F. vencimiento
amount: Importe
card:
issued: Fecha emisión
client: Cliente
company: Empresa
customerCard: Ficha del cliente
ticketList: Listado de tickets
summary:
issued: Fecha
created: Fecha creación
dued: Vencimiento
booked: Contabilizada
company: Empresa
taxBreakdown: Desglose impositivo
type: Tipo
taxableBase: Base imp.
rate: Tarifa
fee: Cuota
tickets: Tickets
ticketId: Id ticket
nickname: Alias
shipped: F. envío
totalWithVat: Importe
globalInvoices:
errors:
@ -630,21 +511,16 @@ invoiceOut:
invoiceWithFutureDate: Existe una factura con una fecha futura
noTicketsToInvoice: No existen tickets para facturar
criticalInvoiceError: Error crítico en la facturación proceso detenido
invalidSerialTypeForAll: El tipo de serie debe ser global cuando se facturan todos los clientes
table:
client: Cliente
addressId: Id dirección
streetAddress: Dirección fiscal
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}'
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs'
negativeBases:
company: Empresa
country: País
clientId: Id cliente
client: Cliente
amount: Importe
base: Base
ticketId: Id ticket
active: Activo
hasToInvoice: Facturar
verifiedData: Datos comprobados
@ -654,43 +530,24 @@ invoiceOut:
order:
field:
salesPersonFk: Comercial
clientFk: Cliente
isConfirmed: Confirmada
created: Creado
landed: F. entrega
hour: Hora
agency: Agencia
total: Total
form:
clientFk: Cliente
addressFk: Dirección
landed: F. entrega
agencyModeFk: Agencia
list:
newOrder: Nuevo Pedido
summary:
basket: Cesta
nickname: Alias
company: Empresa
confirmed: Confirmada
notConfirmed: No confirmada
created: Creado
landed: F. entrega
phone: Teléfono
createdFrom: Creado desde
address: Dirección
notes: Notas
subtotal: Subtotal
total: Total
vat: IVA
state: Estado
alias: Alias
items: Artículos
orderTicketList: Tickets del pedido
details: Detalles
item: Item
quantity: Cantidad
price: Precio
amount: Monto
confirm: Confirmar
confirmLines: Confirmar lineas
@ -700,15 +557,6 @@ shelving:
priority: Prioridad
newShelving: Nuevo Carro
summary:
code: Código
parking: Parking
priority: Prioridad
worker: Trabajador
recyclable: Reciclable
basicData:
code: Código
parking: Parking
priority: Prioridad
recyclable: Reciclable
parking:
pickingOrder: Orden de recogida
@ -718,15 +566,8 @@ parking:
info: Puedes buscar por código de parking
label: Buscar parking...
department:
pageTitles:
basicData: Basic data
department: Departamentos
summary: Resumen
name: Nombre
code: Código
chat: Chat
bossDepartment: Jefe de departamento
email: Email
selfConsumptionCustomer: Cliente autoconsumo
telework: Teletrabaja
notifyOnErrors: Notificar errores
@ -735,52 +576,15 @@ department:
hasToSendMail: Enviar fichadas por mail
departmentRemoved: Departamento eliminado
worker:
pageTitles:
workers: Trabajadores
list: Listado
basicData: Datos básicos
summary: Resumen
notifications: Notificaciones
workerCreate: Nuevo trabajador
department: Departamentos
pda: PDA
notes: Notas
dms: Mi documentación
pbx: Centralita
log: Historial
calendar: Calendario
timeControl: Control de horario
locker: Taquilla
balance: Balance
formation: Formación
medical: Mutua
operator: Operario
list:
name: Nombre
email: Email
phone: Teléfono
mobile: Móvil
active: Activo
department: Departamento
schedule: Horario
newWorker: Nuevo trabajador
card:
workerId: ID Trabajador
user: Usuario
name: Nombre
email: Correo personal
phone: Teléfono
mobile: Móvil
active: Activo
warehouse: Almacén
agency: Empresa
salesPerson: Comercial
summary:
basicData: Datos básicos
boss: Jefe
phoneExtension: Extensión de teléfono
entPhone: Teléfono de empresa
personalPhone: Teléfono personal
phoneExtension: Ext. de teléfono
entPhone: Tel. de empresa
personalPhone: Tel. personal
noBoss: Sin jefe
userData: Datos de usuario
userId: ID del usuario
@ -799,19 +603,12 @@ worker:
serialNumber: Número de serie
removePDA: Desasignar PDA
create:
name: Nombre
lastName: Apellido
birth: Fecha de nacimiento
fi: DNI/NIF/NIE
code: Código de trabajador
phone: Teléfono
postcode: Código postal
province: Provincia
city: Población
street: Dirección
webUser: Usuario Web
personalEmail: Correo personal
company: Empresa
boss: Jefe
payMethods: Método de pago
iban: IBAN
@ -823,16 +620,13 @@ worker:
endDate: Fecha Fin
center: Centro Formación
invoice: Factura
amount: Importe
remark: Bonficado
hasDiploma: Diploma
medical:
tableVisibleColumns:
date: Fecha
time: Hora
center: Centro de Formación
invoice: Factura
amount: Importe
isFit: Apto
remark: Observaciones
imageNotFound: No se ha encontrado la imagen
@ -857,18 +651,7 @@ worker:
machine: Máquina
wagon:
pageTitles:
wagons: Vagones
wagonsList: Listado vagones
wagonCreate: Crear tipo
wagonEdit: Editar tipo
typesList: Listado tipos
typeCreate: Crear tipo
typeEdit: Editar tipo
wagonCounter: Contador de carros
wagonTray: Listado bandejas
type:
name: Nombre
submit: Guardar
reset: Deshacer cambios
trayColor: Color de la bandeja
@ -876,13 +659,9 @@ wagon:
list:
plate: Matrícula
volume: Volumen
type: Tipo
remove: Borrar
removeItem: Vagón borrado correctamente
create:
plate: Matrícula
volume: Volumen
type: Tipo
label: Etiqueta
warnings:
noData: Sin datos disponibles
@ -898,26 +677,16 @@ wagon:
supplier:
list:
payMethod: Método de pago
payDeadline: Plazo de pago
payDay: Día de pago
account: Cuenta
newSupplier: Nuevo proveedor
tableVisibleColumns:
id: Id
name: Nombre
nif: NIF/CIF
nickname: Alias
account: Cuenta
payMethod: Método de pago
payDay: Dia de pago
country: País
summary:
responsible: Responsable
notes: Notas
verified: Verificado
isActive: Está activo
billingData: Forma de pago
payMethod: Método de pago
payDeadline: Plazo de pago
payDay: Día de pago
account: Cuenta
@ -930,17 +699,13 @@ supplier:
fiscalAddress: Dirección fiscal
socialName: Razón social
taxNumber: NIF/CIF
street: Dirección
city: Población
postCode: Código postal
province: Provincia
country: País
create:
supplierName: Nombre del proveedor
basicData:
alias: Alias
workerFk: Responsable
isSerious: Verificado
isReal: Verificado
isActive: Activo
isPayMethodChecked: Método de pago validado
note: Notas
@ -953,36 +718,17 @@ supplier:
sageWithholdingFk: Retención sage
sageTransactionTypeFk: Tipo de transacción sage
supplierActivityFk: Actividad proveedor
healthRegister: Pasaporte sanitario
street: Calle
postcode: Código postal
city: Población *
provinceFk: Provincia
country: País
isTrucker: Transportista
isVies: Vies
billingData:
payMethodFk: Forma de pago
payDemFk: Plazo de pago
payDay: Día de pago
accounts:
iban: Iban
bankEntity: Entidad bancaria
beneficiary: Beneficiario
contacts:
name: Nombre
phone: Teléfono
mobile: Móvil
email: Email
observation: Notas
addresses:
street: Dirección
postcode: Código postal
phone: Teléfono
name: Nombre
city: Población
province: Provincia
mobile: Móvil
agencyTerms:
agencyFk: Agencia
minimumM3: M3 mínimos
@ -994,25 +740,16 @@ supplier:
addRow: Añadir fila
consumption:
entry: Entrada
date: Fecha
reference: Referencia
travel:
travelList:
tableVisibleColumns:
id: Id
ref: Referencia
agency: Agencia
shipped: F.envío
shipHour: Hora de envío
landHour: Hora de llegada
landed: F.entrega
warehouseIn: Alm.salida
warehouseOut: Alm.entrada
totalEntries:
totalEntriesTooltip: Entradas totales
daysOnward: Días de llegada en adelante
summary:
confirmed: Confirmado
entryId: Id entrada
freight: Porte
package: Embalaje
@ -1025,62 +762,89 @@ travel:
AddEntry: Añadir entrada
thermographs: Termógrafos
hb: HB
variables:
search: Id/Referencia
agencyModeFk: Agencia
warehouseInFk: Alm. entrada
warehouseOutFk: ' Alm. salida'
landedFrom: Llegada desde
landedTo: Llegada hasta
continent: Cont. Salida
totalEntries: Ent. totales
basicData:
reference: Referencia
agency: Agencia
shipped: F. Envío
landed: F. entrega
warehouseOut: Alm. salida
warehouseIn: Alm. entrada
delivered: Enviada
received: Recibida
daysInForward: Días redada
isRaid: Redada
thermographs:
code: Código
temperature: Temperatura
state: Estado
destination: Destino
created: Fecha creación
thermograph: Termógrafo
reference: Referencia
type: Tipo
company: Empresa
warehouse: Almacén
travelFileDescription: 'Id envío { travelId }'
file: Fichero
item:
descriptor:
buyer: Comprador
color: Color
category: Categoría
available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta
list:
id: Identificador
stems: Tallos
category: Reino
typeName: Tipo
isActive: Activo
weightByPiece: Peso (gramos)/tallo
userName: Comprador
stemMultiplier: Multiplicador
fixedPrice:
itemFk: ID Artículo
groupingPrice: Precio grouping
packingPrice: Precio packing
hasMinPrice: Tiene precio mínimo
minPrice: Precio min
started: Inicio
ended: Fin
create:
priority: Prioridad
summary:
otherData: Otros datos
tax: IVA
botanical: Botánico
barcode: Código de barras
completeName: Nombre completo
family: Familia
stems: Tallos
multiplier: Multiplicador
buyer: Comprador
doPhoto: Hacer foto
intrastatCode: Código intrastat
ref: Referencia
relevance: Relevancia
weight: Peso (gramos)/tallo
units: Unidades/caja
expense: Gasto
generic: Genérico
recycledPlastic: Plástico reciclado
nonRecycledPlastic: Plástico no reciclado
minSalesQuantity: Cantidad mínima de venta
genus: Genus
specie: Specie
buyRequest:
requester: Solicitante
requested: Solicitado
attender: Comprador
achieved: Conseguido
concept: Concepto
components:
topbar: {}
itemsFilterPanel:
typeFk: Tipo
tag: Etiqueta
value: Valor
# ItemFixedPriceFilter
buyerFk: Comprador
warehouseFk: Almacén
started: Desde
ended: Hasta
mine: Para mi
hasMinPrice: Precio mínimo
# LatestBuysFilter
salesPersonFk: Comprador
supplierFk: Proveedor
active: Activo
visible: Visible
floramondo: Floramondo
showBadDates: Ver items a futuro
userPanel:
copyToken: Token copiado al portapapeles
settings: Configuración
logOut: Cerrar sesión
localWarehouse: Almacén local
localBank: Banco local
localCompany: Empresa local
@ -1088,7 +852,6 @@ components:
userCompany: Empresa del usuario
smartCard:
downloadFile: Descargar archivo
clone: Clonar
openCard: Ficha
openSummary: Detalles
viewSummary: Vista previa

View File

@ -11,21 +11,13 @@ const { t } = useI18n();
const { notify } = useNotify();
const onSynchronizeAll = async () => {
try {
notify(t('Synchronizing in the background'), 'positive');
await axios.patch(`Accounts/syncAll`);
} catch (error) {
console.error('Error synchronizing all accounts', error);
}
notify(t('Synchronizing in the background'), 'positive');
await axios.patch(`Accounts/syncAll`);
};
const onSynchronizeRoles = async () => {
try {
await axios.patch(`RoleInherits/sync`);
notify(t('Roles synchronized!'), 'positive');
} catch (error) {
console.error('Error synchronizing roles', error);
}
await axios.patch(`RoleInherits/sync`);
notify(t('Roles synchronized!'), 'positive');
};
</script>

View File

@ -111,29 +111,25 @@ const columns = computed(() => [
},
]);
const deleteAcl = async ({ id }) => {
try {
await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Remove ACL'),
message: t('Do you want to remove this ACL?'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
await axios.delete(`ACLs/${id}`);
tableRef.value.reload();
notify('ACL removed', 'positive');
} catch (error) {
console.error('Error deleting Acl: ', error);
}
await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Remove ACL'),
message: t('Do you want to remove this ACL?'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
await axios.delete(`ACLs/${id}`);
tableRef.value.reload();
notify('ACL removed', 'positive');
};
</script>

View File

@ -34,13 +34,9 @@ const refresh = () => paginateRef.value.fetch();
const navigate = (id) => router.push({ name: 'AccountSummary', params: { id } });
const killSession = async ({ userId, created }) => {
try {
await axios.post(`${urlPath}/killSession`, { userId, created });
paginateRef.value.fetch();
notify(t('Session killed'), 'positive');
} catch (error) {
console.error('Error killing session', error);
}
await axios.post(`${urlPath}/killSession`, { userId, created });
paginateRef.value.fetch();
notify(t('Session killed'), 'positive');
};
</script>

View File

@ -37,7 +37,7 @@ const redirectToAccountBasicData = (_, { id }) => {
<div class="column q-gutter-sm">
<VnInput
v-model="data.name"
:label="t('account.create.name')"
:label="t('globals.name')"
:rules="validate('VnUser.name')"
/>
<VnInput
@ -47,12 +47,12 @@ const redirectToAccountBasicData = (_, { id }) => {
/>
<VnInput
v-model="data.email"
:label="t('account.create.email')"
:label="t('globals.params.email')"
type="email"
:rules="validate('VnUser.email')"
/>
<VnSelect
:label="t('account.create.role')"
:label="t('account.card.role')"
v-model="data.roleFk"
:options="rolesOptions"
option-value="id"
@ -63,7 +63,7 @@ const redirectToAccountBasicData = (_, { id }) => {
/>
<VnInput
v-model="data.password"
:label="t('account.create.password')"
:label="t('ldap.password')"
type="password"
:rules="validate('VnUser.password')"
/>

View File

@ -45,7 +45,7 @@ const rolesOptions = ref([]);
<QItem class="q-my-sm">
<QItemSection>
<VnInput
:label="t('account.card.name')"
:label="t('globals.name')"
v-model="params.name"
lazy-rules
is-outlined

View File

@ -40,12 +40,8 @@ const formUrlCreate = ref(null);
const formUrlUpdate = ref(null);
const formCustomFn = ref(null);
const onTestConection = async () => {
try {
await axios.get(`LdapConfigs/test`);
notify(t('LDAP connection established!'), 'positive');
} catch (error) {
console.error('Error testing connection', error);
}
await axios.get(`LdapConfigs/test`);
notify(t('LDAP connection established!'), 'positive');
};
const getInitialLdapConfig = async () => {
try {
@ -72,14 +68,10 @@ const getInitialLdapConfig = async () => {
}
};
const deleteMailForward = async () => {
try {
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
} catch (err) {
console.error('Error deleting mail forward', err);
}
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
};
onMounted(async () => await getInitialLdapConfig());
@ -102,11 +94,11 @@ onMounted(async () => await getInitialLdapConfig());
<QBtn
class="q-ml-none"
color="primary"
:label="t('ldap.testConnection')"
:label="t('account.card.testConnection')"
@click="onTestConection()"
>
<QTooltip>
{{ t('ldap.testConnection') }}
{{ t('account.card.testConnection') }}
</QTooltip>
</QBtn>
</template>
@ -114,7 +106,7 @@ onMounted(async () => await getInitialLdapConfig());
<VnRow class="row q-gutter-md">
<div class="col">
<QCheckbox
:label="t('ldap.enableSync')"
:label="t('account.card.enableSync')"
v-model="data.hasData"
@update:model-value="($event) => (hasData = $event)"
:toggle-indeterminate="false"
@ -146,7 +138,7 @@ onMounted(async () => await getInitialLdapConfig());
/>
<VnInput :label="t('ldap.userDN')" clearable v-model="data.userDn" />
<VnInput
:label="t('ldap.groupDN')"
:label="t('account.card.groupDN')"
clearable
v-model="data.groupDn"
/>

View File

@ -104,7 +104,7 @@ const exprBuilder = (param, value) => {
<template>
<VnSearchbar
data-key="AccountUsers"
data-key="AccountList"
:expr-builder="exprBuilder"
:label="t('account.search')"
:info="t('account.searchInfo')"
@ -112,12 +112,12 @@ const exprBuilder = (param, value) => {
/>
<RightMenu>
<template #right-panel>
<AccountFilter data-key="AccountUsers" />
<AccountFilter data-key="AccountList" />
</template>
</RightMenu>
<VnTable
ref="tableRef"
data-key="AccountUsers"
data-key="AccountList"
url="VnUsers/preview"
:filter="filter"
order="id DESC"

View File

@ -46,12 +46,8 @@ const formUrlUpdate = ref(null);
const formCustomFn = ref(null);
const onTestConection = async () => {
try {
await axios.get(`SambaConfigs/test`);
notify(t('Samba connection established!'), 'positive');
} catch (error) {
console.error('Error testing connection', error);
}
await axios.get(`SambaConfigs/test`);
notify(t('Samba connection established!'), 'positive');
};
const getInitialSambaConfig = async () => {
@ -79,14 +75,10 @@ const getInitialSambaConfig = async () => {
};
const deleteMailForward = async () => {
try {
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
} catch (err) {
console.error('Error deleting mail forward', err);
}
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
};
onMounted(async () => await getInitialSambaConfig());
@ -110,12 +102,12 @@ onMounted(async () => await getInitialSambaConfig());
<QBtn
class="q-ml-none"
color="primary"
:label="t('samba.testConnection')"
:label="t('account.card.testConnection')"
:disable="formModel.hasChanges"
@click="onTestConection()"
>
<QTooltip>
{{ t('samba.testConnection') }}
{{ t('account.card.testConnection') }}
</QTooltip>
</QBtn>
</template>
@ -123,7 +115,7 @@ onMounted(async () => await getInitialSambaConfig());
<VnRow class="row q-gutter-md">
<div class="col">
<QCheckbox
:label="t('samba.enableSync')"
:label="t('account.card.enableSync')"
v-model="data.hasData"
@update:model-value="($event) => (hasData = $event)"
:toggle-indeterminate="false"

View File

@ -36,15 +36,12 @@ const onDataSaved = ({ id }) => {
<template #form-inputs="{ data }">
<VnRow>
<div class="col">
<VnInput v-model="data.alias" :label="t('mailAlias.name')" />
<VnInput v-model="data.alias" :label="t('globals.name')" />
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
v-model="data.description"
:label="t('mailAlias.description')"
/>
<VnInput v-model="data.description" :label="t('role.description')" />
</div>
</VnRow>
</template>

View File

@ -11,8 +11,8 @@ const { t } = useI18n();
<FormModel model="Alias">
<template #form="{ data }">
<div class="column q-gutter-y-md">
<VnInput v-model="data.alias" :label="t('mailAlias.name')" />
<VnInput v-model="data.description" :label="t('mailAlias.description')" />
<VnInput v-model="data.alias" :label="t('globals.name')" />
<VnInput v-model="data.description" :label="t('role.description')" />
<QCheckbox :label="t('mailAlias.isPublic')" v-model="data.isPublic" />
</div>
</template>

View File

@ -44,13 +44,9 @@ const removeAlias = () => {
cancel: true,
})
.onOk(async () => {
try {
await axios.delete(`MailAliases/${entityId.value}`);
notify(t('Alias removed'), 'positive');
router.push({ name: 'AccountAlias' });
} catch (err) {
console.error('Error removing alias');
}
await axios.delete(`MailAliases/${entityId.value}`);
notify(t('Alias removed'), 'positive');
router.push({ name: 'AccountAlias' });
});
};
</script>
@ -71,7 +67,7 @@ const removeAlias = () => {
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('mailAlias.description')" :value="entity.description" />
<VnLv :label="t('role.description')" :value="entity.description" />
</template>
</CardDescriptor>
</template>

View File

@ -42,8 +42,8 @@ const entityId = computed(() => $props.id || route.params.id);
<QIcon name="open_in_new" />
</router-link>
</QCardSection>
<VnLv :label="t('mailAlias.id')" :value="alias.id" />
<VnLv :label="t('mailAlias.description')" :value="alias.description" />
<VnLv :label="t('role.id')" :value="alias.id" />
<VnLv :label="t('role.description')" :value="alias.description" />
</QCard>
</template>
</CardSummary>

View File

@ -36,7 +36,7 @@ watch(
<div class="q-gutter-y-sm">
<VnInput v-model="data.name" :label="t('account.card.nickname')" />
<VnInput v-model="data.nickname" :label="t('account.card.alias')" />
<VnInput v-model="data.email" :label="t('account.card.email')" />
<VnInput v-model="data.email" :label="t('globals.params.email')" />
<VnSelect
url="Languages"
v-model="data.lang"

View File

@ -54,7 +54,7 @@ const hasAccount = ref(false);
</template>
<template #before>
<!-- falla id :id="entityId.value" collection="user" size="160x160" -->
<VnImg :id="entityId" collection="user" resolution="160x160" class="photo">
<VnImg :id="entityId" collection="user" resolution="520x520" class="photo">
<template #error>
<div
class="absolute-full picture text-center q-pa-md flex flex-center"

View File

@ -41,35 +41,22 @@ const fetchAccountExistence = async () => {
};
const fetchMailForwards = async () => {
try {
const response = await axios.get(`MailForwards/${route.params.id}`);
return response.data;
} catch (err) {
console.error('Error fetching mail forwards', err);
return null;
}
const response = await axios.get(`MailForwards/${route.params.id}`);
return response.data;
};
const deleteMailForward = async () => {
try {
await axios.delete(`MailForwards/${route.params.id}`);
formData.value.forwardTo = null;
initialData.value.forwardTo = null;
initialData.value.hasData = hasData.value;
notify(t('globals.dataSaved'), 'positive');
} catch (err) {
console.error('Error deleting mail forward', err);
}
await axios.delete(`MailForwards/${route.params.id}`);
formData.value.forwardTo = null;
initialData.value.forwardTo = null;
initialData.value.hasData = hasData.value;
notify(t('globals.dataSaved'), 'positive');
};
const updateMailForward = async () => {
try {
await axios.patch('MailForwards', formData.value);
initialData.value = { ...formData.value };
initialData.value.hasData = hasData.value;
} catch (err) {
console.error('Error creating mail forward', err);
}
await axios.patch('MailForwards', formData.value);
initialData.value = { ...formData.value };
initialData.value.hasData = hasData.value;
};
const onSubmit = async () => {

View File

@ -82,14 +82,14 @@ const exprBuilder = (param, value) => {
<template>
<VnSearchbar
data-key="Roles"
data-key="AccountRolesList"
:expr-builder="exprBuilder"
:label="t('role.searchRoles')"
:info="t('role.searchInfo')"
/>
<VnTable
ref="tableRef"
data-key="Roles"
data-key="AccountRolesList"
:url="`VnRoles`"
:create="{
urlCreate: 'VnRoles',

View File

@ -29,7 +29,7 @@ const props = defineProps({
<QItem class="q-my-sm">
<QItemSection>
<VnInput
:label="t('role.name')"
:label="t('globals.name')"
v-model="params.name"
lazy-rules
is-outlined

View File

@ -12,15 +12,12 @@ const { t } = useI18n();
<template #form="{ data }">
<VnRow>
<div class="col">
<VnInput v-model="data.name" :label="t('role.card.name')" />
<VnInput v-model="data.name" :label="t('globals.name')" />
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
v-model="data.description"
:label="t('role.card.description')"
/>
<VnInput v-model="data.description" :label="t('role.description')" />
</div>
</VnRow>
</template>

View File

@ -9,7 +9,7 @@ const { t } = useI18n();
<VnCard
data-key="Role"
:descriptor="RoleDescriptor"
search-data-key="AccountRoles"
search-data-key="AccountRolesList"
:searchbar-props="{
url: 'VnRoles',
label: t('role.searchRoles'),

View File

@ -32,12 +32,8 @@ const filter = {
where: { id: entityId },
};
const removeRole = async () => {
try {
await axios.delete(`VnRoles/${entityId.value}`);
notify(t('Role removed'), 'positive');
} catch (error) {
console.error('Error deleting role', error);
}
await axios.delete(`VnRoles/${entityId.value}`);
notify(t('Role removed'), 'positive');
};
</script>
@ -58,7 +54,7 @@ const removeRole = async () => {
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('role.card.description')" :value="entity.description" />
<VnLv :label="t('role.description')" :value="entity.description" />
</template>
</CardDescriptor>
</template>

View File

@ -22,15 +22,12 @@ const { t } = useI18n();
<template #form-inputs="{ data }">
<VnRow>
<div class="col">
<VnInput v-model="data.name" :label="t('role.card.name')" />
<VnInput v-model="data.name" :label="t('globals.name')" />
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
v-model="data.description"
:label="t('role.card.description')"
/>
<VnInput v-model="data.description" :label="t('role.description')" />
</div>
</VnRow>
</template>

View File

@ -44,9 +44,9 @@ const filter = {
<QIcon name="open_in_new" />
</a>
</QCardSection>
<VnLv :label="t('role.card.id')" :value="role.id" />
<VnLv :label="t('role.card.name')" :value="role.name" />
<VnLv :label="t('role.card.description')" :value="role.description" />
<VnLv :label="t('role.id')" :value="role.id" />
<VnLv :label="t('globals.name')" :value="role.name" />
<VnLv :label="t('role.description')" :value="role.description" />
</QCard>
</template>
</CardSummary>

View File

@ -1,32 +1,15 @@
account:
pageTitles:
users: Users
list: Users
roles: Roles
alias: Mail aliasses
accounts: Accounts
ldap: LDAP
samba: Samba
acls: ACLs
connections: Connections
inheritedRoles: Inherited Roles
subRoles: Sub Roles
newRole: New role
privileges: Privileges
mailAlias: Mail Alias
mailForwarding: Mail Forwarding
accountCreate: New user
aliasUsers: Users
card:
name: Name
nickname: User
role: Role
email: Email
alias: Alias
lang: Language
roleFk: Role
newUser: New user
ticketTracking: Ticket tracking
enableSync: Habilitar sincronización
groupDN: DN grupos
testConnection: Probar conexión
privileges:
delegate: Can delegate privileges
enabled: Account enabled!
@ -74,11 +57,7 @@ account:
search: Search user
searchInfo: You can search by id, name or nickname
create:
name: Name
nickname: Nickname
email: Email
role: Role
password: Password
active: Active
mailForwarding:
forwardingMail: Forward email
@ -86,50 +65,30 @@ account:
enableMailForwarding: Enable mail forwarding
mailInputInfo: All emails will be forwarded to the specified address.
role:
pageTitles:
inheritedRoles: Inherited Roles
subRoles: Sub Roles
card:
description: Description
id: Id
name: Name
newRole: New role
searchRoles: Search role
searchInfo: Search role by id or name
name: Name
description: Description
id: Id
mailAlias:
pageTitles:
aliasUsers: Users
search: Search mail alias
searchInfo: Search alias by id or name
alias: Alias
description: Description
id: Id
newAlias: New alias
name: Name
isPublic: Public
ldap:
enableSync: Enable synchronization
server: Server
rdn: RDN
userDN: User DN
filter: Filter
groupDN: Group DN
testConnection: Test connection
success: LDAP connection established!
password: Password
samba:
enableSync: Enable synchronization
domainController: Domain controller
domainAD: AD domain
userAD: AD user
groupDN: Group DN
passwordAD: AD password
domainPart: User DN (without domain part)
verifyCertificate: Verify certificate
testConnection: Test connection
success: Samba connection established!
accounts:
homedir: Homedir base
@ -147,8 +106,6 @@ connections:
created: Created
killSession: Kill session
acls:
role: Role
accessType: Access type
permissions: Permission
search: Search acls
searchInfo: Search acls by model name

View File

@ -1,27 +1,7 @@
account:
pageTitles:
users: Usuarios
list: Usuarios
roles: Roles
alias: Alias de correo
accounts: Cuentas
ldap: LDAP
samba: Samba
acls: ACLs
connections: Conexiones
inheritedRoles: Roles heredados
newRole: Nuevo rol
subRoles: Subroles
privileges: Privilegios
mailAlias: Alias de correo
mailForwarding: Reenvío de correo
accountCreate: Nuevo usuario
aliasUsers: Usuarios
card:
nickname: Usuario
name: Nombre
role: Rol
email: Mail
alias: Alias
lang: Idioma
roleFk: Rol
@ -33,6 +13,9 @@ account:
deactivated: ¡Usuario desactivado!
newUser: Nuevo usuario
twoFactor: Doble factor
enableSync: Habilitar sincronización
groupDN: DN grupos
testConnection: Probar conexión
privileges:
delegate: Puede delegar privilegios
actions:
@ -73,11 +56,7 @@ account:
search: Buscar usuario
searchInfo: Puedes buscar por id, nombre o usuario
create:
name: Nombre
nickname: Nombre mostrado
email: Email
role: Rol
password: Contraseña
active: Activo
mailForwarding:
forwardingMail: Dirección de reenvío
@ -85,51 +64,30 @@ account:
enableMailForwarding: Habilitar redirección de correo
mailInputInfo: Todos los correos serán reenviados a la dirección especificada, no se mantendrá copia de los mismos en el buzón del usuario.
role:
pageTitles:
inheritedRoles: Roles heredados
subRoles: Subroles
newRole: Nuevo rol
card:
description: Descripción
id: Id
name: Nombre
newRole: Nuevo rol
searchRoles: Buscar roles
searchInfo: Buscar rol por id o nombre
name: Nombre
description: Descripción
id: Id
mailAlias:
pageTitles:
aliasUsers: Usuarios
search: Buscar alias de correo
searchInfo: Buscar alias por id o nombre
alias: Alias
description: Descripción
id: Id
newAlias: Nuevo alias
name: Nombre
isPublic: Público
ldap:
password: Contraseña
enableSync: Habilitar sincronización
server: Servidor
rdn: RDN
userDN: DN usuarios
filter: Filtro
groupDN: DN grupos
testConnection: Probar conexión
success: ¡Conexión con LDAP establecida!
samba:
enableSync: Habilitar sincronización
domainController: Controlador de dominio
domainAD: Dominio AD
groupDN: DN grupos
userAD: Usuario AD
passwordAD: Contraseña AD
domainPart: DN usuarios (sin la parte del dominio)
verifyCertificate: Verificar certificado
testConnection: Probar conexión
success: ¡Conexión con Samba establecida!
accounts:
homedir: Directorio base para carpetas de usuario
@ -147,8 +105,6 @@ connections:
created: Creado
killSession: Matar sesión
acls:
role: Rol
accessType: Tipo de acceso
permissions: Permiso
search: Buscar acls
searchInfo: Buscar acls por nombre

View File

@ -100,7 +100,7 @@ async function remove() {
</QMenu>
</QItem>
<QSeparator />
<QItem @click="confirmRemove()" v-ripple clickable>
<QItem @click="confirmRemove()" v-ripple clickable data-cy="deleteClaim">
<QItemSection avatar>
<QIcon name="delete" />
</QItemSection>

View File

@ -130,7 +130,7 @@ function cancel() {
<template #body-cell-description="{ row, value }">
<QTd auto-width align="right" class="link">
{{ value }}
<ItemDescriptorProxy :id="row.itemFk"></ItemDescriptorProxy>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd>
</template>
</QTable>

View File

@ -25,7 +25,7 @@ const claimFilter = computed(() => {
include: {
relation: 'user',
scope: {
fields: ['id', 'nickname'],
fields: ['id', 'nickname', 'name'],
},
},
},

View File

@ -23,7 +23,7 @@ defineExpose({ states });
<template>
<FetchData url="ClaimStates" @on-fetch="(data) => (states = data)" auto-load />
<VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table">
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>

View File

@ -95,6 +95,7 @@ const columns = computed(() => [
optionLabel: 'description',
},
},
orderBy: 'priority',
},
{
align: 'right',

View File

@ -167,7 +167,7 @@ const toCustomerAddressEdit = (addressId) => {
<div>{{ item.street }}</div>
<div>
{{ item.postalCode }} - {{ item.city }},
{{ item.province.name }}
{{ item.province?.name }}
</div>
<div>
{{ item.phone }}

View File

@ -256,10 +256,10 @@ const showBalancePdf = ({ id }) => {
{{ toCurrency(balances[rowIndex]?.balance) }}
</template>
<template #column-description="{ row }">
<div class="link" v-if="row.isInvoice">
<span class="link" v-if="row.isInvoice" @click.stop>
{{ t('bill', { ref: row.description }) }}
<InvoiceOutDescriptorProxy :id="row.description" />
</div>
<InvoiceOutDescriptorProxy :id="row.id" />
</span>
<span v-else class="q-pa-xs dotted rounded-borders" :title="row.description">
{{ row.description }}
</span>

View File

@ -55,7 +55,7 @@ const exprBuilder = (param, value) => {
/>
<VnSelect
:input-debounce="0"
:label="t('customer.basicData.businessType')"
:label="t('customer.summary.businessType')"
:options="businessTypes"
:rules="validate('client.businessTypeFk')"
emit-value
@ -67,13 +67,13 @@ const exprBuilder = (param, value) => {
</VnRow>
<VnRow>
<VnInput
:label="t('customer.basicData.contact')"
:label="t('customer.summary.contact')"
:rules="validate('client.contact')"
clearable
v-model="data.contact"
/>
<VnInput
:label="t('customer.basicData.email')"
:label="t('globals.params.email')"
:rules="validate('client.email')"
clearable
type="email"
@ -90,14 +90,14 @@ const exprBuilder = (param, value) => {
</VnRow>
<VnRow>
<VnInput
:label="t('customer.basicData.phone')"
:label="t('customer.extendedList.tableVisibleColumns.phone')"
:rules="validate('client.phone')"
clearable
type="number"
v-model="data.phone"
/>
<VnInput
:label="t('customer.basicData.mobile')"
:label="t('customer.summary.mobile')"
:rules="validate('client.mobile')"
clearable
type="number"
@ -108,7 +108,7 @@ const exprBuilder = (param, value) => {
<VnSelect
url="Workers/search"
v-model="data.salesPersonFk"
:label="t('customer.basicData.salesPerson')"
:label="t('customer.summary.salesPerson')"
:params="{
departmentCodes: ['VT', 'shopping'],
}"
@ -146,7 +146,7 @@ const exprBuilder = (param, value) => {
option-value="id"
option-label="name"
emit-value
:label="t('customer.basicData.contactChannel')"
:label="t('customer.summary.contactChannel')"
map-options
:rules="validate('client.contactChannelFk')"
:input-debounce="0"

View File

@ -37,6 +37,9 @@ const entityId = computed(() => {
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity?.name, entity?.id));
const debtWarning = computed(() => {
return customer.value?.debt > customer.value?.credit ? 'negative' : 'primary';
});
</script>
<template>
@ -53,11 +56,17 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
<CustomerDescriptorMenu :customer="entity" />
</template>
<template #body="{ entity }">
<VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
<VnLv
:label="t('customer.card.securedCredit')"
:label="t('customer.summary.payMethod')"
:value="entity.payMethod.name"
/>
<VnLv
:label="t('customer.summary.credit')"
:value="toCurrency(entity.credit)"
/>
<VnLv
:label="t('customer.summary.securedCredit')"
:value="toCurrency(entity.creditInsurance)"
/>
@ -66,7 +75,7 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
:value="toCurrency(entity.debt)"
:info="t('customer.summary.riskInfo')"
/>
<VnLv :label="t('customer.card.salesPerson')">
<VnLv :label="t('customer.summary.salesPerson')">
<template #value>
<VnUserLink
v-if="entity.salesPersonUser"
@ -77,7 +86,7 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
</template>
</VnLv>
<VnLv
:label="t('customer.card.businessTypeFk')"
:label="t('customer.extendedList.tableVisibleColumns.businessTypeFk')"
:value="entity.businessType.description"
/>
</template>
@ -111,7 +120,7 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
v-if="customer.debt > customer.credit"
name="vn:risk"
size="xs"
color="primary"
:color="debtWarning"
>
<QTooltip>{{ t('customer.card.hasDebt') }}</QTooltip>
</QIcon>
@ -168,23 +177,6 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
>
<QTooltip>{{ t('Customer ticket list') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'TicketList',
query: {
table: JSON.stringify({
clientFk: entity.id,
}),
createForm: JSON.stringify({ clientId: entity.id }),
},
}"
size="md"
color="primary"
target="_blank"
icon="vn:ticketAdd"
>
<QTooltip>{{ t('New ticket') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'InvoiceOutList',
@ -196,23 +188,6 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
>
<QTooltip>{{ t('Customer invoice out list') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'OrderList',
query: {
table: JSON.stringify({
clientFk: entity.id,
}),
createForm: JSON.stringify({ clientFk: entity.id }),
},
}"
size="md"
target="_blank"
icon="vn:basketadd"
color="primary"
>
<QTooltip>{{ t('New order') }}</QTooltip>
</QBtn>
<QBtn
:to="{
name: 'AccountSummary',

View File

@ -6,8 +6,8 @@ import axios from 'axios';
import { useQuasar } from 'quasar';
import useNotify from 'src/composables/useNotify';
import VnSmsDialog from 'src/components/common/VnSmsDialog.vue';
import useOpenURL from 'src/composables/useOpenURL';
const $props = defineProps({
customer: {
@ -15,7 +15,6 @@ const $props = defineProps({
required: true,
},
});
const { notify } = useNotify();
const { t } = useI18n();
const quasar = useQuasar();
@ -40,9 +39,42 @@ const sendSms = async (payload) => {
notify(error.message, 'positive');
}
};
const openCreateForm = (type) => {
const query = {
table: {
clientFk: $props.customer.id,
},
createForm: {
addressId: $props.customer.defaultAddressFk,
},
};
const clientFk = {
ticket: 'clientId',
order: 'clientFk',
};
const key = clientFk[type];
if (!key) return;
query.createForm[key] = $props.customer.id;
const params = Object.entries(query)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join('&');
useOpenURL(`/#/${type}/list?${params}`);
};
</script>
<template>
<QItem v-ripple clickable @click="openCreateForm('ticket')">
<QItemSection>
{{ t('globals.pageTitles.createTicket') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="openCreateForm('order')">
<QItemSection>
{{ t('globals.pageTitles.createOrder') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection>
</QItem>

View File

@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { toCurrency, toPercentage, toDate } from 'src/filters';
import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue';
import { getUrl } from 'src/composables/getUrl';
import VnLv from 'src/components/ui/VnLv.vue';
@ -27,21 +27,16 @@ const $props = defineProps({
const entityId = computed(() => $props.id || route.params.id);
const customer = computed(() => summary.value.entity);
const summary = ref();
const clientUrl = ref();
onMounted(async () => {
clientUrl.value = (await getUrl('client/')) + entityId.value + '/';
});
const defaulterAmount = computed(() => customer.value.defaulters[0]?.amount);
const balanceDue = computed(() => {
return (
customer.value &&
customer.value.defaulters.length &&
customer.value.defaulters[0].amount
);
const amount = defaulterAmount.value;
if (!amount || amount < 0) {
return null;
}
return amount;
});
const balanceDueWarning = computed(() => (balanceDue.value ? 'negative' : ''));
const balanceDueWarning = computed(() => (defaulterAmount.value ? 'negative' : ''));
const claimRate = computed(() => {
return customer.value.claimsRatio?.claimingRate ?? 0;
@ -87,7 +82,7 @@ const sumRisk = ({ clientRisks }) => {
<VnLv :label="t('customer.summary.contact')" :value="entity.contact" />
<VnLv :value="entity.phone">
<template #label>
{{ t('customer.summary.phone') }}
{{ t('customer.extendedList.tableVisibleColumns.phone') }}
<VnLinkPhone :phone-number="entity.phone" />
</template>
</VnLv>
@ -99,12 +94,13 @@ const sumRisk = ({ clientRisks }) => {
say-simple
:phone-number="entity.mobile"
:channel="entity.country?.saySimpleCountry?.channel"
class="q-ml-xs"
/>
</template>
</VnLv>
<VnLv :value="entity.email" copy
><template #label>
{{ t('customer.summary.email') }}
{{ t('globals.params.email') }}
<VnLinkMail email="entity.email"></VnLinkMail> </template
></VnLv>
<VnLv
@ -218,7 +214,7 @@ const sumRisk = ({ clientRisks }) => {
:value="entity.defaultAddress.city"
/>
<VnLv
:label="t('customer.summary.addressStreet')"
:label="t('customer.summary.street')"
:value="entity.defaultAddress.street"
/>
</QCard>
@ -304,7 +300,7 @@ const sumRisk = ({ clientRisks }) => {
<VnLv
v-if="entity.defaulters"
:label="t('customer.summary.balanceDue')"
:value="toCurrency(balanceDue)"
:value="dashOrCurrency(balanceDue)()"
:class="balanceDueWarning"
:info="t('customer.summary.balanceDueInfo')"
/>
@ -324,7 +320,7 @@ const sumRisk = ({ clientRisks }) => {
:value="entity.recommendedCredit"
/>
</QCard>
<QCard class="vn-one">
<QCard class="vn-max">
<VnTitle :text="t('Latest tickets')" />
<CustomerSummaryTable />
</QCard>

View File

@ -2,10 +2,9 @@
import { computed, onBeforeMount, ref, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify';
import { useStateStore } from 'stores/useStateStore';

View File

@ -11,10 +11,24 @@ defineProps({
required: true,
},
});
const handleSalesModelValue = (val) => ({
or: [
{ id: val },
{ name: val },
{ nickname: { like: '%' + val + '%' } },
{ code: { like: `${val}%` } },
],
});
const exprBuilder = (param, value) => {
return {
and: [{ active: { neq: false } }, handleSalesModelValue(value)],
};
};
</script>
<template>
<VnFilterPanel :data-key="dataKey" :search-button="true" search-url="table">
<VnFilterPanel :data-key="dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
@ -34,7 +48,7 @@ defineProps({
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('customerFilter.filter.name')"
:label="t('globals.name')"
v-model="params.name"
is-outlined
/>
@ -43,7 +57,7 @@ defineProps({
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('customerFilter.filter.socialName')"
:label="t('customer.summary.socialName')"
v-model="params.socialName"
is-outlined
/>
@ -52,14 +66,18 @@ defineProps({
<QItem class="q-mb-sm">
<QItemSection>
<VnSelect
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
url="Workers/search"
:params="{
departmentCodes: ['VT'],
}"
auto-load
:label="t('Salesperson')"
:expr-builder="exprBuilder"
v-model="params.salesPersonFk"
@update:model-value="searchFn()"
option-value="id"
option-label="name"
sort-by="nickname ASC"
emit-value
map-options
use-input
@ -68,7 +86,18 @@ defineProps({
outlined
rounded
:input-debounce="0"
/>
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }},{{ opt.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template></VnSelect
>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">

View File

@ -77,7 +77,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.credit'),
label: t('customer.summary.credit'),
name: 'credit',
columnFilter: {
component: 'number',
@ -115,7 +115,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.mobile'),
label: t('customer.summary.mobile'),
name: 'mobile',
cardVisible: true,
columnFilter: {
@ -162,17 +162,17 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.city'),
label: t('customer.summary.city'),
name: 'city',
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.postcode'),
label: t('customer.summary.postcode'),
name: 'postcode',
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.email'),
label: t('globals.params.email'),
name: 'email',
cardVisible: true,
},
@ -207,7 +207,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.payMethodFk'),
label: t('customer.summary.payMethodFk'),
name: 'payMethodFk',
columnFilter: {
component: 'select',
@ -250,7 +250,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.isActive'),
label: t('customer.summary.isActive'),
name: 'isActive',
chip: {
color: null,
@ -279,7 +279,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
label: t('customer.summary.isEqualizated'),
name: 'isEqualizated',
create: true,
columnFilter: {
@ -325,7 +325,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.hasLcr'),
label: t('customer.summary.hasLcr'),
name: 'hasLcr',
columnFilter: {
inWhere: true,
@ -333,7 +333,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.hasCoreVnl'),
label: t('customer.summary.hasCoreVnl'),
name: 'hasCoreVnl',
columnFilter: {
inWhere: true,
@ -394,16 +394,16 @@ function handleLocation(data, location) {
<VnSearchbar
:info="t('You can search by customer id or name')"
:label="t('Search customer')"
data-key="Customer"
data-key="CustomerList"
/>
<RightMenu>
<template #right-panel>
<CustomerFilter data-key="Customer" />
<CustomerFilter data-key="CustomerList" />
</template>
</RightMenu>
<VnTable
ref="tableRef"
data-key="Customer"
data-key="CustomerList"
url="Clients/filter"
:create="{
urlCreate: 'Clients/createWithUser',
@ -424,7 +424,7 @@ function handleLocation(data, location) {
<VnSelect
url="Workers/search"
v-model="data.salesPersonFk"
:label="t('customer.basicData.salesPerson')"
:label="t('customer.summary.salesPerson')"
:params="{
departmentCodes: ['VT', 'shopping'],
}"

View File

@ -23,6 +23,7 @@ const incoterms = ref([]);
const customsAgents = ref([]);
const observationTypes = ref([]);
const notes = ref([]);
let originalNotes = [];
const deletes = ref([]);
onBeforeMount(() => {
@ -42,7 +43,8 @@ const getData = async (observations) => {
});
if (data.length) {
notes.value = data
originalNotes = data;
notes.value = originalNotes
.map((observation) => {
const type = observationTypes.value.find(
(type) => type.id === observation.observationTypeFk
@ -81,14 +83,24 @@ const deleteNote = (id, index) => {
};
const onDataSaved = async () => {
let payload = {};
const creates = notes.value.filter((note) => note.$isNew);
if (creates.length) {
payload.creates = creates;
}
if (deletes.value.length) {
payload.deletes = deletes.value;
}
let payload = {
creates: notes.value.filter((note) => note.$isNew),
deletes: deletes.value,
updates: notes.value
.filter((note) =>
originalNotes.some(
(oNote) =>
oNote.id === note.id &&
(note.description !== oNote.description ||
note.observationTypeFk !== oNote.observationTypeFk)
)
)
.map((note) => ({
data: note,
where: { id: note.id },
})),
};
await axios.post('AddressObservations/crud', payload);
notes.value = [];
deletes.value = [];

View File

@ -106,28 +106,24 @@ const setParams = (params) => {
};
const getPreview = async () => {
try {
const params = {
recipientId: entityId,
};
const validationMessage = validateMessage();
if (validationMessage) return notify(t(validationMessage), 'negative');
const params = {
recipientId: entityId,
};
const validationMessage = validateMessage();
if (validationMessage) return notify(t(validationMessage), 'negative');
setParams(params);
setParams(params);
const path = `${sampleType.value.model}/${entityId.value}/${sampleType.value.code}-html`;
const { data } = await axios.get(path, { params });
const path = `${sampleType.value.model}/${entityId.value}/${sampleType.value.code}-html`;
const { data } = await axios.get(path, { params });
if (!data) return;
quasar.dialog({
component: CustomerSamplesPreview,
componentProps: {
htmlContent: data,
},
});
} catch (err) {
notify('Errors getting preview', 'negative');
}
if (!data) return;
quasar.dialog({
component: CustomerSamplesPreview,
componentProps: {
htmlContent: data,
},
});
};
const onSubmit = async () => {

View File

@ -194,14 +194,14 @@ const getItemPackagingType = (ticketSales) => {
redirect="ticket"
>
<template #column-nickname="{ row }">
<span class="link">
<span class="link" @click.stop>
{{ row.nickname }}
<CustomerDescriptorProxy :id="row.clientFk" />
</span>
</template>
<template #column-routeFk="{ row }">
<span class="link">
<span class="link" @click.stop>
{{ row.routeFk }}
<RouteDescriptorProxy :id="row.routeFk" />
</span>
@ -218,7 +218,7 @@ const getItemPackagingType = (ticketSales) => {
<span v-else> {{ toCurrency(row.totalWithVat) }}</span>
</template>
<template #column-state="{ row }">
<span v-if="row.invoiceOut">
<span v-if="row.invoiceOut" @click.stop>
<span :class="{ link: row.invoiceOut.ref }">
{{ row.invoiceOut.ref }}
<InvoiceOutDescriptorProxy :id="row.invoiceOut.id" />

View File

@ -1,21 +1,5 @@
customerFilter:
filter:
name: Name
socialName: Social name
customer:
list:
phone: Phone
email: Email
customerOrders: Display customer orders
moreOptions: More options
card:
customerList: Customer list
customerId: Claim ID
salesPerson: Sales person
credit: Credit
risk: Risk
securedCredit: Secured credit
payMethod: Pay method
debt: Debt
isFrozen: Customer frozen
hasDebt: Customer has debt
@ -23,9 +7,7 @@ customer:
notChecked: Customer no checked
webAccountInactive: Web account inactive
noWebAccess: Web access is disabled
businessType: Business type
passwordRequirements: 'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n'
businessTypeFk: Business type
summary:
basicData: Basic data
fiscalAddress: Fiscal address
@ -37,9 +19,7 @@ customer:
customerId: Customer ID
name: Name
contact: Contact
phone: Phone
mobile: Mobile
email: Email
salesPerson: Sales person
contactChannel: Contact channel
socialName: Social name
@ -63,7 +43,6 @@ customer:
hasB2BVnl: Has B2B VNL
addressName: Address name
addressCity: City
addressStreet: Street
username: Username
webAccess: Web access
totalGreuge: Total greuge
@ -92,45 +71,27 @@ customer:
goToLines: Go to lines
basicData:
socialName: Fiscal name
businessType: Business type
contact: Contact
youCanSaveMultipleEmails: You can save multiple emails
email: Email
phone: Phone
mobile: Mobile
salesPerson: Sales person
contactChannel: Contact channel
previousClient: Previous client
extendedList:
tableVisibleColumns:
id: Identifier
name: Name
socialName: Social name
fi: Tax number
salesPersonFk: Salesperson
credit: Credit
creditInsurance: Credit insurance
phone: Phone
mobile: Mobile
street: Street
countryFk: Country
provinceFk: Province
city: City
postcode: Postcode
email: Email
created: Created
businessTypeFk: Business type
payMethodFk: Billing data
sageTaxTypeFk: Sage tax type
sageTransactionTypeFk: Sage tr. type
isActive: Active
isVies: Vies
isTaxDataChecked: Verified data
isEqualizated: Is equalizated
isFreezed: Freezed
hasToInvoice: Invoice
hasToInvoiceByAddress: Invoice by address
isToBeMailed: Mailing
hasLcr: Received LCR
hasCoreVnl: VNL core received
hasSepaVnl: VNL B2B received

View File

@ -1,22 +1,7 @@
Search customer: Buscar cliente
You can search by customer id or name: Puedes buscar por id o nombre del cliente
customerFilter:
filter:
name: Nombre
socialName: Razón Social
customer:
list:
phone: Teléfono
email: Email
customerOrders: Mostrar órdenes del cliente
moreOptions: Más opciones
card:
customerId: ID cliente
salesPerson: Comercial
credit: Crédito
risk: Riesgo
securedCredit: Crédito asegurado
payMethod: Método de pago
debt: Riesgo
isFrozen: Cliente congelado
hasDebt: Cliente con riesgo
@ -24,9 +9,7 @@ customer:
notChecked: Cliente no comprobado
webAccountInactive: Sin acceso web
noWebAccess: El acceso web está desactivado
businessType: Tipo de negocio
passwordRequirements: 'La contraseña debe tener al menos { length } caracteres de longitud, {nAlpha} caracteres alfabéticos, {nUpper} letras mayúsculas, {nDigits} dígitos y {nPunct} símbolos (Ej: $%&.)'
businessTypeFk: Tipo de negocio
summary:
basicData: Datos básicos
fiscalAddress: Dirección fiscal
@ -38,9 +21,7 @@ customer:
customerId: ID cliente
name: Nombre
contact: Contacto
phone: Teléfono
mobile: Móvil
email: Email
salesPerson: Comercial
contactChannel: Canal de contacto
socialName: Razón social
@ -64,7 +45,6 @@ customer:
hasB2BVnl: Recibido B2B VNL
addressName: Nombre de la dirección
addressCity: Ciudad
addressStreet: Calle
username: Usuario
webAccess: Acceso web
totalGreuge: Greuge total
@ -93,45 +73,27 @@ customer:
goToLines: Ir a líneas
basicData:
socialName: Nombre fiscal
businessType: Tipo de negocio
contact: Contacto
youCanSaveMultipleEmails: Puede guardar varios correos electrónicos encadenándolos mediante comas sin espacios{','} ejemplo{':'} user{'@'}dominio{'.'}com, user2{'@'}dominio{'.'}com siendo el primer correo electrónico el principal
email: Email
phone: Teléfono
mobile: Móvil
salesPerson: Comercial
contactChannel: Canal de contacto
previousClient: Cliente anterior
extendedList:
tableVisibleColumns:
id: Identificador
name: Nombre
socialName: Razón social
fi: NIF / CIF
salesPersonFk: Comercial
credit: Crédito
creditInsurance: Crédito asegurado
phone: Teléfono
mobile: Móvil
street: Dirección fiscal
countryFk: País
provinceFk: Provincia
city: Población
postcode: Código postal
email: Email
created: Fecha creación
businessTypeFk: Tipo de negocio
payMethodFk: Forma de pago
sageTaxTypeFk: Tipo de impuesto Sage
sageTransactionTypeFk: Tipo tr. sage
isActive: Activo
isVies: Vies
isTaxDataChecked: Datos comprobados
isEqualizated: Recargo de equivalencias
isFreezed: Congelado
hasToInvoice: Factura
hasToInvoiceByAddress: Factura por consigna
isToBeMailed: Env. emails
hasLcr: Recibido LCR
hasCoreVnl: Recibido core VNL
hasSepaVnl: Recibido B2B VNL

View File

@ -20,16 +20,16 @@ const { t } = useI18n();
<template #form="{ data, validate }">
<VnRow>
<VnInput
:label="t('department.name')"
:label="t('globals.name')"
v-model="data.name"
:rules="validate('department.name')"
:rules="validate('globals.name')"
clearable
autofocus
/>
<VnInput
v-model="data.code"
:label="t('department.code')"
:rules="validate('department.code')"
:label="t('globals.code')"
:rules="validate('globals.code')"
clearable
/>
</VnRow>
@ -42,8 +42,8 @@ const { t } = useI18n();
/>
<VnInput
v-model="data.notificationEmail"
:label="t('department.email')"
:rules="validate('department.email')"
:label="t('globals.params.email')"
:rules="validate('globals.params.email')"
clearable
/>
</VnRow>

View File

@ -42,13 +42,9 @@ const setData = (entity) => {
};
const removeDepartment = async () => {
try {
await axios.post(`/Departments/${entityId.value}/removeChild`, entityId.value);
router.push({ name: 'WorkerDepartment' });
notify('department.departmentRemoved', 'positive');
} catch (err) {
console.error('Error removing department');
}
await axios.post(`/Departments/${entityId.value}/removeChild`, entityId.value);
router.push({ name: 'WorkerDepartment' });
notify('department.departmentRemoved', 'positive');
};
const { openConfirmationModal } = useVnConfirm();

View File

@ -45,16 +45,8 @@ onMounted(async () => {
/>
<div class="full-width row wrap justify-between content-between">
<div class="column" style="min-width: 50%">
<VnLv
:label="t('department.name')"
:value="department.name"
dash
/>
<VnLv
:label="t('department.code')"
:value="department.code"
dash
/>
<VnLv :label="t('globals.name')" :value="department.name" dash />
<VnLv :label="t('globals.code')" :value="department.code" dash />
<VnLv
:label="t('department.chat')"
:value="department.chatName"

View File

@ -52,7 +52,7 @@ const onFilterTravelSelected = (formData, id) => {
<template #form="{ data }">
<VnRow>
<VnSelect
:label="t('entry.basicData.supplier')"
:label="t('globals.supplier')"
v-model="data.supplierFk"
url="Suppliers"
option-value="id"
@ -107,18 +107,15 @@ const onFilterTravelSelected = (formData, id) => {
</VnSelectDialog>
</VnRow>
<VnRow>
<VnInput
v-model="data.reference"
:label="t('entry.basicData.reference')"
/>
<VnInput v-model="data.reference" :label="t('globals.reference')" />
</VnRow>
<VnRow>
<VnInput
v-model="data.invoiceNumber"
:label="t('entry.basicData.invoiceNumber')"
:label="t('entry.summary.invoiceNumber')"
/>
<VnSelect
:label="t('entry.basicData.company')"
:label="t('globals.company')"
v-model="data.companyFk"
:options="companiesOptions"
option-value="id"
@ -130,14 +127,14 @@ const onFilterTravelSelected = (formData, id) => {
</VnRow>
<VnRow>
<VnSelect
:label="t('entry.basicData.currency')"
:label="t('entry.summary.currency')"
v-model="data.currencyFk"
:options="currenciesOptions"
option-value="id"
option-label="code"
/>
<QInput
:label="t('entry.basicData.commission')"
:label="t('entry.summary.commission')"
v-model="data.commission"
type="number"
autofocus
@ -155,17 +152,11 @@ const onFilterTravelSelected = (formData, id) => {
/>
</VnRow>
<VnRow>
<QCheckbox
v-model="data.isOrdered"
:label="t('entry.basicData.ordered')"
/>
<QCheckbox
v-model="data.isConfirmed"
:label="t('entry.basicData.confirmed')"
/>
<QCheckbox v-model="data.isOrdered" :label="t('entry.summary.ordered')" />
<QCheckbox v-model="data.isConfirmed" :label="t('globals.confirmed')" />
<QCheckbox
v-model="data.isExcludedFromAvailable"
:label="t('entry.basicData.excludedFromAvailable')"
:label="t('entry.summary.excludedFromAvailable')"
/>
<QCheckbox
v-if="isAdministrative()"

View File

@ -5,7 +5,6 @@ import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
@ -157,13 +156,13 @@ const tableColumnComponents = computed(() => ({
const entriesTableColumns = computed(() => {
return [
{
label: t('entry.summary.item'),
label: t('globals.item'),
field: 'itemFk',
name: 'item',
align: 'left',
},
{
label: t('entry.summary.quantity'),
label: t('globals.quantity'),
field: 'quantity',
name: 'quantity',
align: 'left',
@ -187,7 +186,7 @@ const entriesTableColumns = computed(() => {
align: 'left',
},
{
label: t('entry.summary.weight'),
label: t('globals.weight'),
field: 'weight',
name: 'weight',
align: 'left',
@ -212,13 +211,13 @@ const entriesTableColumns = computed(() => {
format: (value) => toCurrency(value),
},
{
label: t('entry.buys.groupingPrice'),
label: t('item.fixedPrice.groupingPrice'),
field: 'price2',
name: 'price2',
align: 'left',
},
{
label: t('entry.buys.packingPrice'),
label: t('item.fixedPrice.packingPrice'),
field: 'price3',
name: 'price3',
align: 'left',
@ -237,13 +236,9 @@ const copyOriginalRowsData = (rows) => {
};
const saveChange = async (field, { rowIndex, row }) => {
try {
if (originalRowDataCopy.value[rowIndex][field] == row[field]) return;
await axios.patch(`Buys/${row.id}`, row);
originalRowDataCopy.value[rowIndex][field] = row[field];
} catch (err) {
console.error('Error saving changes', err);
}
if (originalRowDataCopy.value[rowIndex][field] == row[field]) return;
await axios.patch(`Buys/${row.id}`, row);
originalRowDataCopy.value[rowIndex][field] = row[field];
};
const openRemoveDialog = async () => {
@ -261,15 +256,11 @@ const openRemoveDialog = async () => {
},
})
.onOk(async () => {
try {
await deleteBuys();
const notifyMessage = t(
`Buy${rowsSelected.value.length > 1 ? 's' : ''} deleted`
);
notify(notifyMessage, 'positive');
} catch (err) {
console.error('Error deleting buys');
}
await deleteBuys();
const notifyMessage = t(
`Buy${rowsSelected.value.length > 1 ? 's' : ''} deleted`
);
notify(notifyMessage, 'positive');
});
};
@ -283,17 +274,13 @@ const importBuys = () => {
};
const toggleGroupingMode = async (buy, mode) => {
try {
const groupingMode = mode === 'grouping' ? mode : 'packing';
const newGroupingMode = buy.groupingMode === groupingMode ? null : groupingMode;
const params = {
groupingMode: newGroupingMode,
};
await axios.patch(`Buys/${buy.id}`, params);
buy.groupingMode = newGroupingMode;
} catch (err) {
console.error('Error toggling grouping mode');
}
const groupingMode = mode === 'grouping' ? mode : 'packing';
const newGroupingMode = buy.groupingMode === groupingMode ? null : groupingMode;
const params = {
groupingMode: newGroupingMode,
};
await axios.patch(`Buys/${buy.id}`, params);
buy.groupingMode = newGroupingMode;
};
const lockIconType = (groupingMode, mode) => {

View File

@ -35,7 +35,7 @@ const packagingsOptions = ref([]);
const columns = computed(() => [
{
label: t('entry.buys.item'),
label: t('globals.item'),
name: 'item',
field: 'itemFk',
options: lastItemBuysOptions.value,
@ -56,19 +56,19 @@ const columns = computed(() => [
align: 'left',
},
{
label: t('entry.buys.packing'),
label: t('entry.summary.packing'),
name: 'packing',
field: 'packing',
align: 'left',
},
{
label: t('entry.buys.grouping'),
label: t('entry.summary.grouping'),
name: 'grouping',
field: 'grouping',
align: 'left',
},
{
label: t('entry.buys.buyingValue'),
label: t('entry.summary.buyingValue'),
name: 'buyingValue',
field: 'buyingValue',
align: 'left',
@ -123,36 +123,28 @@ const fillData = async (rawData) => {
};
const fetchBuys = async (buys) => {
try {
const params = { buys };
const { data } = await axios.post(
`Entries/${route.params.id}/importBuysPreview`,
params
);
importData.value.buys = data;
} catch (err) {
console.error('Error fetching buys');
}
const params = { buys };
const { data } = await axios.post(
`Entries/${route.params.id}/importBuysPreview`,
params
);
importData.value.buys = data;
};
const onSubmit = async () => {
try {
const params = importData.value;
const hasAnyEmptyRow = params.buys.some((buy) => {
return buy.itemFk === null;
});
const params = importData.value;
const hasAnyEmptyRow = params.buys.some((buy) => {
return buy.itemFk === null;
});
if (hasAnyEmptyRow) {
notify(t('Some of the imported buys does not have an item'), 'negative');
return;
}
await axios.post(`Entries/${route.params.id}/importBuys`, params);
notify('globals.dataSaved', 'positive');
redirectToBuysView();
} catch (err) {
console.error('Error importing buys', err);
if (hasAnyEmptyRow) {
notify(t('Some of the imported buys does not have an item'), 'negative');
return;
}
await axios.post(`Entries/${route.params.id}/importBuys`, params);
notify('globals.dataSaved', 'positive');
redirectToBuysView();
};
const redirectToBuysView = () => {
@ -200,7 +192,7 @@ const redirectToBuysView = () => {
<VnRow>
<QFile
ref="inputFileRef"
:label="t('entry.buys.file')"
:label="t('globals.file')"
v-model="importData.file"
:multiple="false"
accept=".json"
@ -220,10 +212,7 @@ const redirectToBuysView = () => {
</VnRow>
<div v-if="importData.file">
<VnRow>
<VnInput
:label="t('entry.buys.reference')"
v-model="importData.ref"
/>
<VnInput :label="t('globals.reference')" v-model="importData.ref" />
</VnRow>
<VnRow>
<VnInput

View File

@ -73,14 +73,11 @@ const showEntryReport = () => {
</QItem>
</template>
<template #body="{ entity }">
<VnLv
:label="t('entry.descriptor.agency')"
:value="entity.travel?.agency?.name"
/>
<VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" />
<VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" />
<VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" />
<VnLv
:label="t('entry.descriptor.warehouseOut')"
:label="t('globals.warehouseOut')"
:value="entity.travel?.warehouseOut?.name"
/>
</template>

View File

@ -84,7 +84,7 @@ const tableColumnComponents = {
const entriesTableColumns = computed(() => {
return [
{
label: t('entry.summary.quantity'),
label: t('globals.quantity'),
field: 'quantity',
name: 'quantity',
align: 'left',
@ -102,7 +102,7 @@ const entriesTableColumns = computed(() => {
align: 'left',
},
{
label: t('entry.summary.weight'),
label: t('globals.weight'),
field: 'weight',
name: 'weight',
align: 'left',
@ -147,12 +147,9 @@ async function setEntryData(data) {
}
const fetchEntryBuys = async () => {
try {
const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`);
if (data) entryBuys.value = data;
} catch (err) {
console.error('Error fetching entry buys');
}
};
</script>
@ -188,8 +185,8 @@ const fetchEntryBuys = async () => {
:label="t('entry.summary.currency')"
:value="entry.currency?.name"
/>
<VnLv :label="t('entry.summary.company')" :value="entry.company.code" />
<VnLv :label="t('entry.summary.reference')" :value="entry.reference" />
<VnLv :label="t('globals.company')" :value="entry.company.code" />
<VnLv :label="t('globals.reference')" :value="entry.reference" />
<VnLv
:label="t('entry.summary.invoiceNumber')"
:value="entry.invoiceNumber"
@ -217,7 +214,7 @@ const fetchEntryBuys = async () => {
/>
<VnLv :label="t('shipped')" :value="toDate(entry.travel.shipped)" />
<VnLv
:label="t('entry.summary.travelWarehouseOut')"
:label="t('globals.warehouseOut')"
:value="entry.travel.warehouseOut?.name"
/>
<QCheckbox
@ -227,7 +224,7 @@ const fetchEntryBuys = async () => {
/>
<VnLv :label="t('landed')" :value="toDate(entry.travel.landed)" />
<VnLv
:label="t('entry.summary.travelWarehouseIn')"
:label="t('globals.warehouseIn')"
:value="entry.travel.warehouseIn?.name"
/>
<QCheckbox
@ -250,7 +247,7 @@ const fetchEntryBuys = async () => {
:disable="true"
/>
<QCheckbox
:label="t('entry.summary.confirmed')"
:label="t('globals.confirmed')"
v-model="entry.isConfirmed"
:disable="true"
/>

View File

@ -35,7 +35,7 @@ const entriesTableColumns = computed(() => [
{
align: 'left',
name: 'item',
label: t('entry.summary.item'),
label: t('globals.item'),
field: (row) => row.item.name,
},
{

View File

@ -12,6 +12,7 @@ import VnImg from 'src/components/ui/VnImg.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const tableRef = ref();
const columns = [
{
align: 'center',
@ -40,7 +41,7 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.packing'),
label: t('entry.summary.packing'),
name: 'packing',
columnFilter: {
component: 'number',
@ -49,7 +50,7 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.grouping'),
label: t('entry.summary.grouping'),
name: 'grouping',
columnFilter: {
component: 'number',
@ -58,7 +59,7 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.quantity'),
label: t('globals.quantity'),
name: 'quantity',
columnFilter: {
component: 'number',
@ -67,12 +68,12 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.description'),
label: t('globals.description'),
name: 'description',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.size'),
label: t('globals.size'),
name: 'size',
columnFilter: {
component: 'number',
@ -81,27 +82,27 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.tags'),
label: t('globals.tags'),
name: 'tags',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.type'),
label: t('globals.type'),
name: 'type',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.intrastat'),
label: t('globals.intrastat'),
name: 'intrastat',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.origin'),
label: t('globals.origin'),
name: 'origin',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.weightByPiece'),
label: t('globals.weightByPiece'),
name: 'weightByPiece',
columnFilter: {
component: 'number',
@ -129,7 +130,7 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.buyingValue'),
label: t('entry.summary.buyingValue'),
name: 'buyingValue',
columnFilter: {
component: 'number',
@ -156,7 +157,7 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.packageValue'),
label: t('entry.buys.packageValue'),
name: 'packageValue',
columnFilter: {
component: 'number',
@ -202,7 +203,7 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.weight'),
label: t('globals.weight'),
name: 'weight',
columnFilter: {
component: 'number',
@ -211,7 +212,7 @@ const columns = [
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.packagingFk'),
label: t('entry.buys.packagingFk'),
name: 'packagingFk',
columnFilter: {
component: 'number',
@ -234,7 +235,6 @@ const columns = [
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)),
},
];
const tableRef = ref();
onMounted(async () => {
stateStore.rightDrawer = true;

View File

@ -47,14 +47,14 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.id'),
label: t('globals.id'),
name: 'id',
isTitle: true,
cardVisible: true,
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.reference'),
label: t('globals.reference'),
name: 'reference',
isTitle: true,
component: 'input',
@ -221,7 +221,7 @@ onMounted(async () => {
t('entry.list.tableVisibleColumns.isExcludedFromAvailable')
}}</QTooltip>
</QIcon>
<QIcon v-if="!!row.daysInForward" name="vn:net" color="primary">
<QIcon v-if="!!row.isRaid" name="vn:net" color="primary">
<QTooltip>
{{
t('globals.raid', { daysInForward: row.daysInForward })

Some files were not shown because too many files have changed in this diff Show More