Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 6426-refactorConstants

This commit is contained in:
Carlos Satorres 2025-02-06 09:53:09 +01:00
commit ded1dae481
127 changed files with 1359 additions and 985 deletions

View File

@ -1,74 +1,74 @@
{
"name": "salix-front",
"version": "25.06.0",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
"private": true,
"packageManager": "pnpm@8.15.1",
"type": "module",
"scripts": {
"resetDatabase": "cd ../salix && gulp docker",
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open",
"test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest",
"test:unit:ci": "vitest run",
"commitlint": "commitlint --edit",
"prepare": "npx husky install",
"addReferenceTag": "node .husky/addReferenceTag.js",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
},
"dependencies": {
"@quasar/cli": "^2.4.1",
"@quasar/extras": "^1.16.16",
"axios": "^1.4.0",
"chromium": "^3.0.3",
"croppie": "^2.6.5",
"moment": "^2.30.1",
"pinia": "^2.1.3",
"quasar": "^2.17.7",
"validator": "^13.9.0",
"vue": "^3.5.13",
"vue-i18n": "^9.3.0",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@commitlint/cli": "^19.2.1",
"@commitlint/config-conventional": "^19.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.2",
"@pinia/testing": "^0.1.2",
"@quasar/app-vite": "^2.0.8",
"@quasar/quasar-app-extension-qcalendar": "^4.0.2",
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
"@vue/test-utils": "^2.4.4",
"autoprefixer": "^10.4.14",
"cypress": "^13.6.6",
"cypress-mochawesome-reporter": "^3.8.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-cypress": "^4.1.0",
"eslint-plugin-vue": "^9.32.0",
"husky": "^8.0.0",
"postcss": "^8.4.23",
"prettier": "^3.4.2",
"sass": "^1.83.4",
"vitepress": "^1.6.3",
"vitest": "^0.34.0"
},
"engines": {
"node": "^20 || ^18 || ^16",
"npm": ">= 8.1.2",
"yarn": ">= 1.21.1",
"bun": ">= 1.0.25"
},
"overrides": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.0.11",
"vitest": "^0.31.1"
}
"name": "salix-front",
"version": "25.08.0",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
"private": true,
"packageManager": "pnpm@8.15.1",
"type": "module",
"scripts": {
"resetDatabase": "cd ../salix && gulp docker",
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open",
"test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest",
"test:unit:ci": "vitest run",
"commitlint": "commitlint --edit",
"prepare": "npx husky install",
"addReferenceTag": "node .husky/addReferenceTag.js",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
},
"dependencies": {
"@quasar/cli": "^2.4.1",
"@quasar/extras": "^1.16.16",
"axios": "^1.4.0",
"chromium": "^3.0.3",
"croppie": "^2.6.5",
"moment": "^2.30.1",
"pinia": "^2.1.3",
"quasar": "^2.17.7",
"validator": "^13.9.0",
"vue": "^3.5.13",
"vue-i18n": "^9.3.0",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@commitlint/cli": "^19.2.1",
"@commitlint/config-conventional": "^19.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.2",
"@pinia/testing": "^0.1.2",
"@quasar/app-vite": "^2.0.8",
"@quasar/quasar-app-extension-qcalendar": "^4.0.2",
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
"@vue/test-utils": "^2.4.4",
"autoprefixer": "^10.4.14",
"cypress": "^13.6.6",
"cypress-mochawesome-reporter": "^3.8.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-cypress": "^4.1.0",
"eslint-plugin-vue": "^9.32.0",
"husky": "^8.0.0",
"postcss": "^8.4.23",
"prettier": "^3.4.2",
"sass": "^1.83.4",
"vitepress": "^1.6.3",
"vitest": "^0.34.0"
},
"engines": {
"node": "^20 || ^18 || ^16",
"npm": ">= 8.1.2",
"yarn": ">= 1.21.1",
"bun": ">= 1.0.25"
},
"overrides": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.0.11",
"vitest": "^0.31.1"
}
}

View File

@ -1,6 +1,6 @@
export default {
mounted: function (el, binding) {
const shortcut = binding.value ?? '+';
mounted(el, binding) {
const shortcut = binding.value || '+';
const { key, ctrl, alt, callback } =
typeof shortcut === 'string'
@ -8,25 +8,24 @@ export default {
key: shortcut,
ctrl: true,
alt: true,
callback: () =>
document
.querySelector(`button[shortcut="${shortcut}"]`)
?.click(),
callback: () => el?.click(),
}
: binding.value;
if (!el.hasAttribute('shortcut')) {
el.setAttribute('shortcut', key);
}
const handleKeydown = (event) => {
if (event.key === key && (!ctrl || event.ctrlKey) && (!alt || event.altKey)) {
callback();
}
};
// Attach the event listener to the window
window.addEventListener('keydown', handleKeydown);
el._handleKeydown = handleKeydown;
},
unmounted: function (el) {
unmounted(el) {
if (el._handleKeydown) {
window.removeEventListener('keydown', el._handleKeydown);
}

View File

@ -31,7 +31,6 @@ const props = defineProps({
const route = useRoute();
const itemTypesOptions = ref([]);
const suppliersOptions = ref([]);
const tagOptions = ref([]);
const tagValues = ref([]);
const categoryList = ref(null);
@ -40,13 +39,13 @@ const selectedTypeFk = ref(getParamWhere(route.query.table, 'typeFk', false));
const selectedCategory = computed(() => {
return (categoryList.value || []).find(
(category) => category?.id === selectedCategoryFk.value
(category) => category?.id === selectedCategoryFk.value,
);
});
const selectedType = computed(() => {
return (itemTypesOptions.value || []).find(
(type) => type?.id === selectedTypeFk.value
(type) => type?.id === selectedTypeFk.value,
);
});
@ -134,13 +133,6 @@ const setCategoryList = (data) => {
<template>
<FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
<FetchData
url="Suppliers"
limit="30"
auto-load
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
@on-fetch="(data) => (suppliersOptions = data)"
/>
<FetchData
url="Tags"
:filter="{ fields: ['id', 'name', 'isFree'] }"
@ -290,7 +282,7 @@ const setCategoryList = (data) => {
<QItem class="q-mt-lg">
<QBtn
icon="add_circle"
shortcut="+"
v-shortcut="'+'"
flat
class="fill-icon-on-hover q-px-xs"
color="primary"

View File

@ -10,12 +10,13 @@ import routes from 'src/router/modules';
import LeftMenuItem from './LeftMenuItem.vue';
import LeftMenuItemGroup from './LeftMenuItemGroup.vue';
import VnInput from './common/VnInput.vue';
import { useRouter } from 'vue-router';
const { t } = useI18n();
const route = useRoute();
const quasar = useQuasar();
const navigation = useNavigationStore();
const router = useRouter();
const props = defineProps({
source: {
type: String,
@ -174,6 +175,10 @@ function normalize(text) {
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
}
const searchModule = () => {
const [item] = filteredItems.value;
if (item) router.push({ name: item.name });
};
</script>
<template>
@ -188,10 +193,11 @@ function normalize(text) {
filled
dense
autofocus
@keyup.enter.stop="searchModule()"
/>
</QItem>
<QSeparator />
<template v-if="filteredPinnedModules.size">
<template v-if="filteredPinnedModules.size && !search">
<LeftMenuItem
v-for="[key, pinnedModule] of filteredPinnedModules"
:key="key"
@ -215,11 +221,11 @@ function normalize(text) {
</LeftMenuItem>
<QSeparator />
</template>
<template v-for="item in filteredItems" :key="item.name">
<template v-for="(item, index) in filteredItems" :key="item.name">
<template
v-if="item.children && !filteredPinnedModules.has(item.name)"
v-if="search ||item.children && !filteredPinnedModules.has(item.name)"
>
<LeftMenuItem :item="item" group="modules">
<LeftMenuItem :item="item" group="modules" :class="search && index === 0 ? 'searched' : ''">
<template #side>
<QBtn
v-if="item.isPinned === true"
@ -336,6 +342,9 @@ function normalize(text) {
.header {
color: var(--vn-label-color);
}
.searched{
background-color: var(--vn-section-hover-color);
}
</style>
<i18n>
es:

View File

@ -2,26 +2,9 @@
defineProps({ row: { type: Object, required: true } });
</script>
<template>
<span>
<span class="q-gutter-x-xs">
<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"
v-if="row?.risk"
name="vn:risk"
:color="row.hasHighRisk ? 'negative' : 'primary'"
size="xs"
@ -30,10 +13,57 @@ defineProps({ row: { type: Object, required: true } });
{{ $t('salesTicketsTable.risk') }}: {{ row.risk - row.credit }}
</QTooltip>
</QIcon>
<QIcon v-if="row.hasComponentLack" name="vn:components" color="primary" size="xs">
<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">
<QIcon v-if="row?.hasItemDelay" color="primary" size="xs" name="vn:hasItemDelay">
<QTooltip>
{{ $t('ticket.summary.hasItemDelay') }}
</QTooltip>
</QIcon>
<QIcon v-if="row?.hasItemLost" color="primary" size="xs" name="vn:hasItemLost">
<QTooltip>
{{ $t('salesTicketsTable.hasItemLost') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row?.hasItemShortage"
name="vn:unavailable"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip>
</QIcon>
<QIcon v-if="row?.hasRounding" color="primary" name="sync_problem" size="xs">
<QTooltip>
{{ $t('ticketList.rounding') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row?.hasTicketRequest"
name="vn:buyrequest"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
</QIcon>
<QIcon
v-if="!row?.isTaxDataChecked === 0"
name="vn:no036"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip>
</QIcon>
<QIcon v-if="row?.isFreezed" name="vn:frozen" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip>
</QIcon>
<QIcon v-if="row?.isTooLittle" name="vn:isTooLittle" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip>
</QIcon>
</span>

View File

@ -500,7 +500,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
<QCard
bordered
flat
class="row no-wrap justify-between cursor-pointer"
class="row no-wrap justify-between cursor-pointer q-pa-sm"
@click="
(_, row) => {
$props.rowClick && $props.rowClick(row);
@ -581,7 +581,6 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
<!-- Actions -->
<QCardSection
v-if="colsMap.tableActions"
class="column flex-center w-10 no-margin q-pa-xs q-gutter-y-xs"
@click="stopEventPropagation($event)"
>
<QBtn
@ -630,7 +629,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
size="md"
round
flat
shortcut="+"
v-shortcut="'+'"
:disabled="!disabledAttr"
/>
<QTooltip>
@ -648,7 +647,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
color="primary"
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
data-cy="vnTableCreateBtn"
/>
<QTooltip self="top right">
@ -807,12 +806,15 @@ es:
.grid-two {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
max-width: 100%;
margin: 0 auto;
overflow: scroll;
white-space: wrap;
width: 100%;
grid-template-columns: 2fr 2fr;
.vn-label-value {
flex-direction: column;
white-space: nowrap;
.fields {
display: flex;
}
}
white-space: nowrap;
}
.w-80 {

View File

@ -0,0 +1,61 @@
import { vi, describe, expect, it, beforeEach, beforeAll, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import UserPanel from 'src/components/UserPanel.vue';
import axios from 'axios';
import { useState } from 'src/composables/useState';
describe('UserPanel', () => {
let wrapper;
let vm;
let state;
beforeEach(() => {
wrapper = createWrapper(UserPanel, {});
state = useState();
state.setUser({
id: 115,
name: 'itmanagement',
nickname: 'itManagementNick',
lang: 'en',
darkMode: false,
companyFk: 442,
warehouseFk: 1,
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
});
afterEach(() => {
vi.clearAllMocks();
});
it('should fetch warehouses data on mounted', async () => {
const fetchData = wrapper.findComponent({ name: 'FetchData' });
expect(fetchData.props('url')).toBe('Warehouses');
expect(fetchData.props('autoLoad')).toBe(true);
});
it('should toggle dark mode correctly and update preferences', async () => {
await vm.saveDarkMode(true);
expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true });
expect(vm.user.darkMode).toBe(true);
vm.updatePreferences();
expect(vm.darkMode).toBe(true);
});
it('should change user language and update preferences', async () => {
const userLanguage = 'es';
await vm.saveLanguage(userLanguage);
expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage });
expect(vm.user.lang).toBe(userLanguage);
vm.updatePreferences();
expect(vm.locale).toBe(userLanguage);
});
it('should update user data', async () => {
const key = 'name';
const value = 'itboss';
await vm.saveUserData(key, value);
expect(axios.post).toHaveBeenCalledWith('UserConfigs/setUserConfig', { [key]: value });
});
});

View File

@ -17,7 +17,7 @@ import { useSession } from 'src/composables/useSession';
const route = useRoute();
const quasar = useQuasar();
const { t } = useI18n();
const rows = ref();
const rows = ref([]);
const dmsRef = ref();
const formDialog = ref({});
const token = useSession().getTokenMultimedia();
@ -389,6 +389,14 @@ defineExpose({
</div>
</template>
</QTable>
<div
v-else
class="info-row q-pa-md text-center"
>
<h5>
{{ t('No data to display') }}
</h5>
</div>
</template>
</VnPaginate>
<QDialog v-model="formDialog.show">
@ -405,7 +413,7 @@ defineExpose({
fab
color="primary"
icon="add"
shortcut="+"
v-shortcut
@click="showFormDialog()"
class="fill-icon"
>

View File

@ -0,0 +1,35 @@
<script setup>
import { computed } from 'vue';
import VnSelect from 'components/common/VnSelect.vue';
const model = defineModel({ type: [String, Number, Object] });
const url = 'Suppliers';
</script>
<template>
<VnSelect
:label="$t('globals.supplier')"
v-bind="$attrs"
v-model="model"
:url="url"
option-value="id"
option-label="nickname"
:fields="['id', 'name', 'nickname', 'nif']"
sort-by="name ASC"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name }}
</QItemLabel>
<QItemLabel caption>
{{
`#${scope.opt?.id} , ${scope.opt?.nickname} (${scope.opt?.nif})`
}}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</template>

View File

@ -53,6 +53,7 @@ const url = computed(() => {
:fields="['id', 'name', 'nickname', 'code']"
:filter-options="['id', 'name', 'nickname', 'code']"
sort-by="nickname ASC"
data-cy="vnWorkerSelect"
>
<template #prepend v-if="$props.hasAvatar">
<VnAvatar :worker-id="value" color="primary" v-bind="$attrs" />

View File

@ -1,51 +1,78 @@
import { describe, it, expect, vi, beforeAll, afterEach, beforeEach } from 'vitest';
import {
describe,
it,
expect,
vi,
beforeAll,
afterEach,
beforeEach,
afterAll,
} from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import VnNotes from 'src/components/ui/VnNotes.vue';
import vnDate from 'src/boot/vnDate';
describe('VnNotes', () => {
let vm;
let wrapper;
let spyFetch;
let postMock;
let expectedBody;
const mockData= {name: 'Tony', lastName: 'Stark', text: 'Test Note', observationTypeFk: 1};
function generateExpectedBody() {
expectedBody = {...vm.$props.body, ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }};
}
async function setTestParams(text, observationType, type){
vm.newNote.text = text;
vm.newNote.observationTypeFk = observationType;
wrapper.setProps({ selectType: type });
}
beforeAll(async () => {
vi.spyOn(axios, 'get').mockReturnValue({ data: [] });
let patchMock;
let expectedInsertBody;
let expectedUpdateBody;
const defaultOptions = {
url: '/test',
body: { name: 'Tony', lastName: 'Stark' },
selectType: false,
saveUrl: null,
justInput: false,
};
function generateWrapper(
options = defaultOptions,
text = null,
observationType = null,
) {
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
wrapper = createWrapper(VnNotes, {
propsData: {
url: '/test',
body: { name: 'Tony', lastName: 'Stark' },
}
propsData: options,
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
});
vm.newNote.text = text;
vm.newNote.observationTypeFk = observationType;
}
function createSpyFetch() {
spyFetch = vi.spyOn(vm.$refs.vnPaginateRef, 'fetch');
}
function generateExpectedBody() {
expectedInsertBody = {
...vm.$props.body,
...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk },
};
expectedUpdateBody = { ...vm.$props.body, ...{ notes: vm.newNote.text } };
}
beforeEach(() => {
postMock = vi.spyOn(axios, 'post').mockResolvedValue(mockData);
spyFetch = vi.spyOn(vm.vnPaginateRef, 'fetch').mockImplementation(() => vi.fn());
postMock = vi.spyOn(axios, 'post');
patchMock = vi.spyOn(axios, 'patch');
});
afterEach(() => {
vi.clearAllMocks();
expectedBody = {};
expectedInsertBody = {};
expectedUpdateBody = {};
});
afterAll(() => {
vi.restoreAllMocks();
});
describe('insert', () => {
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is null', async () => {
await setTestParams( null, null, true );
it('should not call axios.post and vnPaginateRef.fetch when newNote.text is null', async () => {
generateWrapper({ selectType: true });
createSpyFetch();
await vm.insert();
@ -53,8 +80,9 @@ describe('VnNotes', () => {
expect(spyFetch).not.toHaveBeenCalled();
});
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is empty', async () => {
await setTestParams( "", null, false );
it('should not call axios.post and vnPaginateRef.fetch when newNote.text is empty', async () => {
generateWrapper(null, '');
createSpyFetch();
await vm.insert();
@ -62,8 +90,9 @@ describe('VnNotes', () => {
expect(spyFetch).not.toHaveBeenCalled();
});
it('should not call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is true', async () => {
await setTestParams( "Test Note", null, true );
it('should not call axios.post and vnPaginateRef.fetch when observationTypeFk is null and selectType is true', async () => {
generateWrapper({ selectType: true }, 'Test Note');
createSpyFetch();
await vm.insert();
@ -71,37 +100,57 @@ describe('VnNotes', () => {
expect(spyFetch).not.toHaveBeenCalled();
});
it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is false', async () => {
await setTestParams( "Test Note", null, false );
it('should call axios.post and vnPaginateRef.fetch when observationTypeFk is missing and selectType is false', async () => {
generateWrapper(null, 'Test Note');
createSpyFetch();
generateExpectedBody();
await vm.insert();
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
expect(spyFetch).toHaveBeenCalled();
});
it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is setted and selectType is false', async () => {
await setTestParams( "Test Note", 1, false );
generateExpectedBody();
await vm.insert();
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody);
expect(spyFetch).toHaveBeenCalled();
});
it('should call axios.post and vnPaginateRef.fetch when newNote is valid', async () => {
await setTestParams( "Test Note", 1, true );
generateWrapper({ selectType: true }, 'Test Note', 1);
createSpyFetch();
generateExpectedBody();
await vm.insert();
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody);
expect(spyFetch).toHaveBeenCalled();
});
});
});
describe('update', () => {
it('should call axios.patch with saveUrl when saveUrl is set and justInput is true', async () => {
generateWrapper({
url: '/business',
justInput: true,
saveUrl: '/saveUrlTest',
});
generateExpectedBody();
await vm.update();
expect(patchMock).toHaveBeenCalledWith(vm.$props.saveUrl, expectedUpdateBody);
});
it('should call axios.patch with url when saveUrl is not set and justInput is true', async () => {
generateWrapper({
url: '/business',
body: { workerFk: 1110 },
justInput: true,
});
generateExpectedBody();
await vm.update();
expect(patchMock).toHaveBeenCalledWith(
`${vm.$props.url}/${vm.$props.body.workerFk}`,
expectedUpdateBody,
);
});
});
});

View File

@ -15,6 +15,10 @@ const props = defineProps({
type: Object,
default: null,
},
userFilter: {
type: Object,
default: null,
},
entityId: {
type: [Number, String],
default: null,
@ -34,6 +38,7 @@ const isSummary = ref();
const arrayData = useArrayData(props.dataKey, {
url: props.url,
filter: props.filter,
userFilter: props.userFilter,
skip: 0,
});
const { store } = arrayData;

View File

@ -1,6 +1,6 @@
<script setup>
import axios from 'axios';
import { ref, reactive } from 'vue';
import { ref, reactive, useAttrs, computed } from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
@ -16,12 +16,22 @@ import VnSelect from 'components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnInput from 'components/common/VnInput.vue';
const emit = defineEmits(['onFetch']);
const $attrs = useAttrs();
const isRequired = computed(() => {
return Object.keys($attrs).includes('required')
});
const $props = defineProps({
url: { type: String, default: null },
saveUrl: {type: String, default: null},
filter: { type: Object, default: () => {} },
body: { type: Object, default: () => {} },
addNote: { type: Boolean, default: false },
selectType: { type: Boolean, default: false },
justInput: { type: Boolean, default: false },
});
const { t } = useI18n();
@ -29,6 +39,13 @@ const quasar = useQuasar();
const newNote = reactive({ text: null, observationTypeFk: null });
const observationTypes = ref([]);
const vnPaginateRef = ref();
let originalText;
function handleClick(e) {
if (e.shiftKey && e.key === 'Enter') return;
if ($props.justInput) confirmAndUpdate();
else insert();
}
async function insert() {
if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return;
@ -41,8 +58,36 @@ async function insert() {
await axios.post($props.url, newBody);
await vnPaginateRef.value.fetch();
}
function confirmAndUpdate() {
if(!newNote.text && originalText)
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('New note is empty'),
message: t('Are you sure remove this note?'),
},
})
.onOk(update)
.onCancel(() => {
newNote.text = originalText;
});
else update();
}
async function update() {
originalText = newNote.text;
const body = $props.body;
const newBody = {
...body,
...{ notes: newNote.text },
};
await axios.patch(`${$props.saveUrl ?? `${$props.url}/${$props.body.workerFk}`}`, newBody);
}
onBeforeRouteLeave((to, from, next) => {
if (newNote.text)
if ((newNote.text && !$props.justInput) || (newNote.text !== originalText) && $props.justInput)
quasar.dialog({
component: VnConfirm,
componentProps: {
@ -53,6 +98,13 @@ onBeforeRouteLeave((to, from, next) => {
});
else next();
});
function fetchData([ data ]) {
newNote.text = data?.notes;
originalText = data?.notes;
emit('onFetch', data);
}
</script>
<template>
<FetchData
@ -62,8 +114,19 @@ onBeforeRouteLeave((to, from, next) => {
auto-load
@on-fetch="(data) => (observationTypes = data)"
/>
<QCard class="q-pa-xs q-mb-lg full-width" v-if="$props.addNote">
<QCardSection horizontal>
<FetchData
v-if="justInput"
:url="url"
:filter="filter"
@on-fetch="fetchData"
auto-load
/>
<QCard
class="q-pa-xs q-mb-lg full-width"
:class="{ 'just-input': $props.justInput }"
v-if="$props.addNote || $props.justInput"
>
<QCardSection horizontal v-if="!$props.justInput">
{{ t('New note') }}
</QCardSection>
<QCardSection class="q-px-xs q-my-none q-py-none">
@ -75,19 +138,19 @@ onBeforeRouteLeave((to, from, next) => {
v-model="newNote.observationTypeFk"
option-label="description"
style="flex: 0.15"
:required="true"
:required="isRequired"
@keyup.enter.stop="insert"
/>
<VnInput
v-model.trim="newNote.text"
type="textarea"
:label="t('Add note here...')"
:label="$props.justInput && newNote.text ? '' : t('Add note here...')"
filled
size="lg"
autogrow
@keyup.enter.stop="insert"
@keyup.enter.stop="handleClick"
:required="isRequired"
clearable
:required="true"
>
<template #append>
<QBtn
@ -95,7 +158,7 @@ onBeforeRouteLeave((to, from, next) => {
icon="save"
color="primary"
flat
@click="insert"
@click="handleClick"
class="q-mb-xs"
dense
data-cy="saveNote"
@ -106,6 +169,7 @@ onBeforeRouteLeave((to, from, next) => {
</QCardSection>
</QCard>
<VnPaginate
v-if="!$props.justInput"
:data-key="$props.url"
:url="$props.url"
order="created DESC"
@ -198,6 +262,11 @@ onBeforeRouteLeave((to, from, next) => {
}
}
}
.just-input {
padding-right: 18px;
margin-bottom: 2px;
box-shadow: none;
}
</style>
<i18n>
es:
@ -205,4 +274,6 @@ onBeforeRouteLeave((to, from, next) => {
New note: Nueva nota
Save (Enter): Guardar (Intro)
Observation type: Tipo de observación
New note is empty: La nueva nota esta vacia
Are you sure remove this note?: Estas seguro de quitar esta nota?
</i18n>

View File

@ -123,7 +123,7 @@ watch(
() => props.data,
() => {
store.data = props.data;
}
},
);
watch(
@ -132,12 +132,12 @@ watch(
if (!mounted.value) return;
emit('onChange', data);
},
{ immediate: true }
{ immediate: true },
);
watch(
() => [props.url, props.filter],
([url, filter]) => mounted.value && fetch({ url, filter })
([url, filter]) => mounted.value && fetch({ url, filter }),
);
const addFilter = async (filter, params) => {
await arrayData.addFilter({ filter, params });
@ -198,7 +198,7 @@ function endPagination() {
async function onLoad(index, done) {
if (!store.data || !mounted.value) return done();
if (store.data.length === 0 || !props.url) return done(false);
if (store.data.length === 0 || !arrayData.store.url) return done(false);
pagination.value.page = pagination.value.page + 1;

View File

@ -19,23 +19,26 @@ onMounted(() => {
const observer = new MutationObserver(
() =>
(hasContent.value =
actions.value?.childNodes?.length + data.value?.childNodes?.length)
actions.value?.childNodes?.length + data.value?.childNodes?.length),
);
if (actions.value) observer.observe(actions.value, opts);
if (data.value) observer.observe(data.value, opts);
});
onBeforeUnmount(() => stateStore.toggleSubToolbar());
const actionsChildCount = () => !!actions.value?.childNodes?.length;
onBeforeUnmount(() => stateStore.toggleSubToolbar() && hasSubToolbar);
</script>
<template>
<QToolbar
id="subToolbar"
class="justify-end sticky"
v-show="hasContent || $slots['st-actions'] || $slots['st-data']"
class="justify-end sticky"
>
<slot name="st-data">
<div id="st-data"></div>
<div id="st-data" :class="{ 'full-width': !actionsChildCount() }">
</div>
</slot>
<QSpace />
<slot name="st-actions">

View File

@ -93,6 +93,9 @@ export function useArrayData(key, userOptions) {
if (params.filter.where || exprFilter)
params.filter.where = { ...params.filter.where, ...exprFilter };
if (!params?.filter?.order?.length) delete params?.filter?.order;
params.filter = JSON.stringify(params.filter);
store.isLoading = true;

Binary file not shown.

View File

@ -127,6 +127,7 @@
<glyph unicode="&#xe975;" glyph-name="stowaway" d="M1006.933 452.267l-260.267 106.667 29.867 29.867c4.267 4.267 4.267 12.8 4.267 17.067-4.267 4.267-8.533 8.533-12.8 8.533h-157.867c0 93.867 76.8 157.867 174.933 157.867 4.267 0 8.533 4.267 12.8 8.533s4.267 8.533 0 17.067l-81.067 153.6c-4.267 0-12.8 4.267-17.067 4.267-46.933 0-93.867-17.067-132.267-42.667-21.333-17.067-42.667-38.4-55.467-59.733-12.8 21.333-29.867 42.667-55.467 59.733-34.133 12.8-81.067 34.133-128 34.133-4.267 0-12.8-4.267-12.8-8.533l-85.333-153.6c-4.267-4.267-4.267-4.267 0-12.8 4.267-4.267 8.533-8.533 12.8-8.533 98.133 0 174.933-59.733 174.933-153.6v0h-140.8c-4.267 0-12.8-4.267-12.8-8.533-8.533-4.267-4.267-17.067 0-21.333l21.333-21.333-277.333-110.933c-8.533-8.533-12.8-12.8-8.533-21.333 0-8.533 8.533-12.8 17.067-12.8v0l98.133 4.267-81.067-85.333c0-4.267-4.267-8.533 0-12.8 0-4.267 4.267-8.533 8.533-8.533l85.333-34.133v-179.2c0-8.533 4.267-12.8 8.533-12.8l358.4-145.067h8.533l358.4 145.067c4.267 4.267 8.533 8.533 8.533 12.8v179.2l85.333 34.133c4.267 0 8.533 4.267 8.533 8.533s0 8.533-4.267 12.8l-68.267 98.133 102.4-4.267c8.533 0 12.8 4.267 17.067 12.8 8.533 0 4.267 4.267-4.267 12.8zM110.933 456.533l196.267 76.8 8.533-8.533-166.4-64-38.4-4.267zM153.6 285.867v0l-68.267 34.133 68.267 98.133 328.533-132.267-68.267-98.133-260.267 98.133zM490.667-29.867l-328.533 132.267v153.6l243.2-98.133h12.8c0 0 0 0 4.267 0v0c0 0 4.267 0 4.267 4.267l64 85.333c0-4.267 0-277.333 0-277.333zM490.667 324.267l-298.667 115.2 149.333 64 153.6-157.867v-17.067h-4.267zM529.067 337.067l157.867 157.867 140.8-55.467-298.667-115.2c0 0 0 12.8 0 12.8zM849.067 102.4l-328.533-132.267v281.6l64-85.333c0 0 0-4.267 4.267-4.267v0h17.067l243.2 98.133v-157.867zM938.667 324.267l-324.267-132.267-68.267 98.133 328.533 132.267 64-98.133zM870.4 460.8l-157.867 64 12.8 8.533 187.733-76.8-42.667 4.267z" />
<glyph unicode="&#xe976;" glyph-name="supplier" d="M797.867 405.333l98.133 34.133 21.333-59.733-98.133-34.133-21.333 59.733zM1019.733 341.333c-4.267-8.533-8.533-12.8-17.067-17.067l-332.8-119.467c4.267-4.267 8.533-12.8 12.8-17.067l277.333 102.4 21.333-59.733-277.333-102.4c0-8.533 4.267-12.8 4.267-21.333 0-85.333-68.267-157.867-157.867-157.867-85.333 0-157.867 68.267-157.867 157.867 0 55.467 29.867 106.667 72.533 132.267l-217.6 610.133c-8.533 25.6-38.4 42.667-68.267 29.867l-157.867-55.467-21.333 59.733 157.867 59.733c59.733 17.067 123.733-12.8 149.333-72.533l221.867-614.4c8.533 0 12.8 0 21.333 4.267l-119.467 332.8c-4.267 17.067 4.267 34.133 17.067 38.4l136.533 51.2c0 0 0 0 0 0l115.2 42.667c0 0 0 0 0 0l136.533 51.2c8.533 4.267 17.067 4.267 25.6 0s12.8-8.533 17.067-17.067l145.067-396.8c0-4.267 0-12.8-4.267-21.333zM695.467 657.067l-59.733-21.333 8.533-21.333 59.733 21.333-8.533 21.333zM644.267 106.667c0 51.2-42.667 93.867-93.867 93.867s-93.867-42.667-93.867-93.867c0-51.2 42.667-93.867 93.867-93.867s93.867 38.4 93.867 93.867zM951.467 371.2l-119.467 332.8-76.8-29.867 17.067-51.2c4.267-8.533 4.267-17.067 0-25.6s-8.533-12.8-17.067-17.067l-115.2-42.667c-4.267 0-8.533 0-12.8 0-12.8 0-25.6 8.533-29.867 21.333l-17.067 51.2-76.8-29.867 119.467-332.8 328.533 123.733z" />
<glyph unicode="&#xe977;" glyph-name="supplierfalse" d="M198.827 882.773c22.187 0.427 41.813-14.080 48.64-34.133l8.107-22.187 105.813-105.813-54.187 149.333c-25.6 59.733-89.6 89.6-149.333 72.533l-13.653-5.12 54.613-54.613zM708.693 129.28l-173.653 173.653 15.36-43.093c-8.533-4.267-12.8-4.267-21.333-4.267l-29.867 83.2-108.373 108.373 74.24-208.64c-42.667-25.6-72.533-76.8-72.533-132.267 0-89.6 72.533-157.867 157.867-157.867 89.6 0 157.867 72.533 157.867 157.867 0 8.533-4.267 12.8-4.267 21.333l4.693 1.707zM550.4 12.373c-51.2 0-93.867 42.667-93.867 93.867s42.667 93.867 93.867 93.867 93.867-42.667 93.867-93.867c0-55.467-42.667-93.867-93.867-93.867zM960 289.707l-122.453-45.227 49.493-49.067 94.293 34.56zM504.32 577.707l-0.853 2.133 76.8 29.867 17.067-51.2c4.267-12.8 17.067-21.333 29.867-21.333 4.267 0 8.533 0 12.8 0l115.2 42.667c8.533 4.267 12.8 8.533 17.067 17.067s4.267 17.067 0 25.6l-17.067 51.2 76.8 29.867 119.467-332.8-174.507-65.707 45.653-45.653 180.053 64.427c8.533 4.267 12.8 8.533 17.067 17.067s4.267 17.067 4.267 21.333l-145.067 396.8c-4.267 8.533-8.533 12.8-17.067 17.067s-17.067 4.267-25.6 0l-136.533-51.2-115.2-42.667-134.4-50.347 54.187-54.187zM695.467 656.64l8.533-21.333-59.733-21.333-8.533 21.333 59.733 21.333zM896 439.040l-98.133-34.133 21.333-59.733 98.133 34.133zM39.253 960c-9.813 0-20.053-3.84-27.733-11.52-15.36-15.787-15.36-40.533 0-55.893l945.493-945.067c7.68-7.68 17.493-11.52 27.733-11.52 9.813 0 20.053 3.84 27.733 11.52 15.36 15.787 15.36 40.533 0 55.893l-945.493 945.067c-7.68 7.68-17.493 11.52-27.733 11.52z" />
<glyph unicode="&#xe978;" glyph-name="inactive-car" d="M1024 900.267l-59.733 59.733-964.267-964.267 59.733-59.733 964.267 964.267zM0 55.893v448.853l119.467 341.333c5.547 17.067 15.787 30.72 30.72 41.387 14.507 10.24 31.147 15.787 49.067 15.787h625.92c6.827 0 13.653-0.853 20.053-2.133l-111.36-111.36h-514.987l-59.733-170.667h403.2l-113.92-113.92h-334.507v-284.587h50.773l-164.693-164.693zM316.587 423.253c8.96-8.96 15.787-19.627 19.627-30.72l-110.080-110.080c-11.52 4.267-21.76 10.667-30.72 19.627-16.64 16.64-24.747 36.693-24.747 60.587s8.107 43.947 24.747 60.587c16.64 16.64 36.693 24.747 60.587 24.747s43.947-8.107 60.587-24.747zM768 277.333c-23.893 0-43.947 8.107-60.587 24.747s-24.747 36.693-24.747 60.587 8.107 43.947 24.747 60.587c16.64 16.64 36.693 24.747 60.587 24.747s43.947-8.107 60.587-24.747c16.64-16.64 24.747-36.693 24.747-60.587s-8.107-43.947-24.747-60.587c-16.64-16.64-36.693-24.747-60.587-24.747zM936.96 753.067l87.040-248.32v-455.253c0-16.213-5.547-29.44-16.213-40.533s-24.32-16.213-40.533-16.213h-56.747c-16.213 0-29.44 5.547-40.533 16.213s-16.213 24.32-16.213 40.533v56.747h-563.2l113.92 113.92h505.6v284.587h-221.44l113.92 113.92h61.867l-16.213 46.080 88.32 88.32z" />
<glyph unicode="&#xe979;" glyph-name="tags" d="M729.6 960c-42.667 0-89.6 0-132.267 0-21.333 0-38.4-8.533-51.2-21.333-140.8-140.8-281.6-281.6-422.4-422.4-25.6-25.6-25.6-51.2 0-76.8 93.867-93.867 187.733-187.733 281.6-281.6 25.6-25.6 51.2-25.6 76.8 0 140.8 140.8 281.6 281.6 422.4 422.4 17.067 12.8 21.333 29.867 21.333 51.2 0 93.867 0 183.467 0 277.333 0 34.133-17.067 51.2-51.2 51.2-51.2 0-98.133 0-145.067 0zM682.667 763.733c0 25.6 17.067 46.933 42.667 46.933s46.933-21.333 46.933-46.933c0-25.6-21.333-46.933-46.933-46.933-21.333 0-42.667 21.333-42.667 46.933zM878.933 482.133c4.267-12.8 0-21.333-8.533-29.867-34.133-51.2-64-98.133-98.133-149.333-76.8-115.2-153.6-234.667-230.4-349.867-12.8-17.067-21.333-21.333-38.4-8.533-115.2 76.8-226.133 149.333-337.067 226.133-17.067 8.533-17.067 21.333-8.533 38.4 12.8 21.333 29.867 46.933 42.667 68.267 8.533 12.8 8.533 12.8 17.067 0 55.467-55.467 115.2-115.2 170.667-170.667 8.533-8.533 17.067-17.067 29.867-21.333 29.867-12.8 55.467-4.267 76.8 21.333 123.733 123.733 247.467 247.467 371.2 371.2 4.267 4.267 4.267 8.533 8.533 12.8 0-8.533 0-8.533 4.267-8.533z" />
<glyph unicode="&#xe97a;" glyph-name="tax" d="M448 192c0 174.933 145.067 320 320 320 76.8 0 145.067-25.6 196.267-68.267v324.267c4.267 51.2-38.4 98.133-93.867 98.133h-204.8c-21.333 55.467-72.533 93.867-136.533 93.867s-115.2-38.4-136.533-98.133h-209.067c-55.467 0-98.133-42.667-98.133-93.867v-674.133c0-51.2 42.667-98.133 98.133-98.133h332.8c-42.667 55.467-68.267 123.733-68.267 196.267zM529.067 861.867c29.867 0 46.933-21.333 46.933-46.933 0-29.867-25.6-46.933-46.933-46.933-29.867 0-46.933 21.333-46.933 46.933-4.267 29.867 17.067 46.933 46.933 46.933zM708.267 247.467c-8.533 0-12.8 4.267-17.067 8.533s-8.533 8.533-8.533 17.067v17.067c0 8.533 0 12.8 4.267 17.067s8.533 8.533 17.067 8.533c8.533 0 12.8-4.267 17.067-8.533s4.267-12.8 4.267-17.067v-12.8c4.267-21.333-4.267-29.867-17.067-29.867zM870.4 132.267c4.267-4.267 4.267-12.8 4.267-17.067v-21.333c0-12.8-8.533-21.333-21.333-21.333-8.533 0-12.8 4.267-17.067 8.533s-8.533 12.8-8.533 17.067v17.067c0 8.533 4.267 12.8 8.533 17.067s8.533 8.533 17.067 8.533c8.533 0 12.8-4.267 17.067-8.533zM768 448c-140.8 0-256-115.2-256-256s115.2-256 256-256 256 115.2 256 256-115.2 256-256 256zM635.733 273.067v17.067c0 21.333 4.267 34.133 17.067 46.933s29.867 17.067 51.2 17.067c21.333 0 38.4-4.267 51.2-17.067s17.067-29.867 17.067-46.933v-17.067c0-21.333-4.267-34.133-17.067-46.933s-29.867-17.067-51.2-17.067-38.4 4.267-51.2 17.067c-8.533 12.8-17.067 29.867-17.067 46.933zM721.067 59.733l-34.133 17.067 153.6 243.2 34.133-17.067-153.6-243.2zM925.867 98.133c0-21.333-4.267-34.133-17.067-46.933s-29.867-17.067-51.2-17.067-38.4 4.267-51.2 17.067c-12.8 12.8-21.333 25.6-21.333 46.933v17.067c0 21.333 4.267 34.133 17.067 46.933s29.867 17.067 51.2 17.067 38.4-4.267 51.2-17.067c12.8-12.8 17.067-29.867 17.067-46.933v-17.067h4.267z" />
<glyph unicode="&#xe97b;" glyph-name="thermometer" d="M641.567 326.792v35.527h64.784v25.078h-64.784v119.118h64.784v25.078h-64.784v119.118h64.784v25.078h-64.784v121.208h64.784v25.078h-64.784v8.359c0 71.053-58.514 129.567-129.567 129.567s-129.567-58.514-129.567-129.567v-503.641c-54.335-39.706-87.771-104.49-87.771-173.453 0-119.118 96.131-217.339 217.339-217.339 119.118 0 217.339 96.131 217.339 217.339 0 66.873-33.437 131.657-87.771 173.453zM512-28.473c-100.31 0-179.722 81.502-179.722 179.722 0 64.784 33.437 123.298 87.771 154.645v524.539c0 50.155 41.796 91.951 91.951 91.951s91.951-41.796 91.951-91.951v-522.449c54.335-31.347 87.771-89.861 87.771-154.645 0-100.31-79.412-181.812-179.722-181.812zM652.016 435.461v25.078h35.527v-25.078h-35.527zM652.016 579.657v25.078h35.527v-25.078h-35.527zM652.016 723.853v25.078h35.527v-25.078h-35.527zM568.424 284.996v543.347c0 0 0 0 0 0s0 0 0 0v0 0c0 31.347-25.078 56.424-56.424 56.424s-56.424-25.078-56.424-56.424v0-543.347c-52.245-20.898-87.771-73.143-87.771-131.657 0-79.412 64.784-144.196 144.196-144.196s144.196 64.784 144.196 144.196c0 58.514-35.527 108.669-87.771 131.657zM470.204 824.163v4.18c0 22.988 18.808 41.796 41.796 41.796s41.796-18.808 41.796-41.796v-219.429h-85.682v215.249z" />

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,10 @@
@font-face {
font-family: 'icon';
src: url('fonts/icon.eot?7j3xju');
src: url('fonts/icon.eot?7j3xju#iefix') format('embedded-opentype'),
url('fonts/icon.ttf?7j3xju') format('truetype'),
url('fonts/icon.woff?7j3xju') format('woff'),
url('fonts/icon.svg?7j3xju#icon') format('svg');
src: url('fonts/icon.eot?uocffs');
src: url('fonts/icon.eot?uocffs#iefix') format('embedded-opentype'),
url('fonts/icon.ttf?uocffs') format('truetype'),
url('fonts/icon.woff?uocffs') format('woff'),
url('fonts/icon.svg?uocffs#icon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@ -25,6 +25,9 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-inactive-car:before {
content: "\e978";
}
.icon-hasItemLost:before {
content: "\e957";
}

View File

@ -326,6 +326,7 @@ globals:
ticketsMonitor: Tickets monitor
clientsActionsMonitor: Clients and actions
serial: Serial
business: Business
medical: Mutual
pit: IRPF
wasteRecalc: Waste recaclulate
@ -377,7 +378,7 @@ login:
loginError: Invalid username or password
fieldRequired: This field is required
twoFactorRequired: Two-factor verification required
twoFactorRequired:
twoFactor:
validate: Validate
insert: Enter the verification code
explanation: >-
@ -456,48 +457,6 @@ ticket:
consigneeStreet: Street
create:
address: Address
invoiceOut:
card:
issued: Issued
customerCard: Customer card
ticketList: Ticket List
summary:
issued: Issued
dued: Due
booked: Booked
taxBreakdown: Tax breakdown
taxableBase: Taxable base
rate: Rate
fee: Fee
tickets: Tickets
totalWithVat: Amount
globalInvoices:
errors:
chooseValidClient: Choose a valid client
chooseValidCompany: Choose a valid company
chooseValidPrinter: Choose a valid printer
chooseValidSerialType: Choose a serial type
fillDates: Invoice date and the max date should be filled
invoiceDateLessThanMaxDate: Invoice date can not be less than max date
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:
addressId: Address id
streetAddress: Street
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} of {getNAddresses}'
pdfsNumberText: '{nPdfs} of {totalPdfs} PDFs'
negativeBases:
clientId: Client Id
base: Base
active: Active
hasToInvoice: Has to Invoice
verifiedData: Verified Data
comercial: Comercial
errors:
downloadCsvFailed: CSV download failed
department:
chat: Chat
bossDepartment: Boss Department
@ -509,6 +468,24 @@ 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
list:
department: Department
schedule: Schedule
@ -524,15 +501,24 @@ worker:
role: Role
sipExtension: Extension
locker: Locker
fiDueDate: FI due date
fiDueDate: DNI expiration date
sex: Sex
seniority: Seniority
seniority: Antiquity
fi: DNI/NIE/NIF
birth: Birth
isFreelance: Freelance
isSsDiscounted: SS Bonification
hasMachineryAuthorized: Machinery authorized
isDisable: Disable
business: Business
started: Started
ended: Ended
reasonEnd: Reason End
department: Departament
workerBusinessCategory: Worker Business Category
notes: Notes
workCenter: Center
professionalCategory: Professional Category
notificationsManager:
activeNotifications: Active notifications
availableNotifications: Available notifications
@ -591,6 +577,23 @@ worker:
sizeLimit: Size limit
isOnReservationMode: Reservation mode
machine: Machine
business:
tableVisibleColumns:
started: Start Date
ended: End Date
company: Company
reasonEnd: Reason for Termination
department: Department
professionalCategory: Professional Category
calendarType: Work Calendar
workCenter: Work Center
payrollCategories: Contract Category
occupationCode: Contribution Code
rate: Rate
businessType: Contract Type
amount: Salary
basicSalary: Transport Workers Salary
notes: Notes
wagon:
type:
submit: Submit

View File

@ -326,6 +326,7 @@ globals:
ticketsMonitor: Monitor de tickets
clientsActionsMonitor: Clientes y acciones
serial: Facturas por serie
business: Contratos
medical: Mutua
pit: IRPF
wasteRecalc: Recalcular mermas
@ -481,6 +482,26 @@ 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
business: Contrato
formation: Formación
medical: Mutua
list:
department: Departamento
schedule: Horario
@ -505,6 +526,15 @@ worker:
isSsDiscounted: Bonificación SS
hasMachineryAuthorized: Autorizado para maquinaria
isDisable: Deshabilitado
business: Contrato
started: Antigüedad
ended: Fin
reasonEnd: Motivo finalización
department: Departamento
workerBusinessCategory: Categoria profesional
notes: Notas
workCenter: Centro
professionalCategory: Categoria profesional
notificationsManager:
activeNotifications: Notificaciones activas
availableNotifications: Notificaciones disponibles
@ -551,6 +581,23 @@ worker:
debit: Debe
credit: Haber
concept: Concepto
business:
tableVisibleColumns:
started: Fecha inicio
ended: Fecha fin
company: Empresa
reasonEnd: Motivo finalización
department: Departamento
professionalCategory: Categoria profesional
calendarType: Calendario laboral
workCenter: Centro
payrollCategories: Categoria contrato
occupationCode: Cotización
rate: Tarifa
businessType: Contrato
amount: Salario
basicSalary: Salario transportistas
notes: Notas
operator:
numberOfWagons: Número de vagones
train: tren
@ -563,7 +610,6 @@ worker:
sizeLimit: Tamaño límite
isOnReservationMode: Modo de reserva
machine: Máquina
wagon:
type:
submit: Guardar

View File

@ -2,7 +2,7 @@
import Navbar from 'src/components/NavBar.vue';
</script>
<template>
<QLayout view="hHh LpR fFf" v-shortcut>
<QLayout view="hHh LpR fFf">
<Navbar />
<RouterView></RouterView>
<QFooter v-if="$q.platform.is.mobile"></QFooter>

View File

@ -86,7 +86,7 @@ watch(
() => route.params.id,
() => {
getAccountData();
}
},
);
onMounted(async () => await getAccountData(false));
@ -130,7 +130,8 @@ onMounted(async () => await getAccountData(false));
openConfirmationModal(
t('User will be removed from alias'),
t('¿Seguro que quieres continuar?'),
() => deleteMailAlias(row, rows, rowIndex)
() =>
deleteMailAlias(row, rows, rowIndex),
)
"
>
@ -157,7 +158,7 @@ onMounted(async () => await getAccountData(false));
icon="add"
color="primary"
@click="openCreateMailAliasForm()"
shortcut="+"
v-shortcut="'+'"
>
<QTooltip>{{ t('warehouses.add') }}</QTooltip>
</QBtn>

View File

@ -63,7 +63,7 @@ watch(
store.url = urlPath.value;
store.filter = filter.value;
fetchSubRoles();
}
},
);
const fetchSubRoles = () => paginateRef.value.fetch();
@ -109,7 +109,7 @@ const redirectToRoleSummary = (id) =>
openConfirmationModal(
t('El rol va a ser eliminado'),
t('¿Seguro que quieres continuar?'),
() => deleteSubRole(row, rows, rowIndex)
() => deleteSubRole(row, rows, rowIndex),
)
"
>
@ -131,7 +131,7 @@ const redirectToRoleSummary = (id) =>
<QBtn
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
color="primary"
@click="openCreateSubRoleForm()"
>

View File

@ -57,7 +57,6 @@ function onFetch(rows, newRows) {
const price = row.quantity * sale.price;
const discount = (sale.discount * price) / 100;
amountClaimed.value = amountClaimed.value + (price - discount);
}
}
@ -208,7 +207,6 @@ async function saveWhenHasChanges() {
selection="multiple"
v-model:selected="selected"
:grid="$q.screen.lt.md"
>
<template #body-cell-claimed="{ row }">
<QTd auto-width align="right" class="text-primary shrink">
@ -319,7 +317,13 @@ async function saveWhenHasChanges() {
</div>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" shortcut="+" icon="add" @click="showImportDialog()" />
<QBtn
fab
color="primary"
v-shortcut="'+'"
icon="add"
@click="showImportDialog()"
/>
</QPageSticky>
</template>
@ -330,9 +334,10 @@ async function saveWhenHasChanges() {
width: 100%;
}
.grid-style-transition {
transition: transform 0.28s, background-color 0.28s;
transition:
transform 0.28s,
background-color 0.28s;
}
</style>
<i18n>

View File

@ -61,7 +61,7 @@ watch(
() => {
claimDmsFilter.value.where.id = router.currentRoute.value.params.id;
claimDmsRef.value.fetch();
}
},
);
function openDialog(dmsId) {
@ -249,7 +249,7 @@ function onDrag() {
<QBtn
fab
@click="inputFile.nativeEl.click()"
shortcut="+"
v-shortcut="'+'"
icon="add"
color="primary"
>

View File

@ -61,7 +61,7 @@ watch(
(newValue) => {
if (!newValue) return;
getClientData(newValue);
}
},
);
const getClientData = async (id) => {
@ -137,7 +137,7 @@ const toCustomerAddressEdit = (addressId) => {
<QIcon
:style="{
'font-variation-settings': `'FILL' ${isDefaultAddress(
item
item,
)}`,
}"
color="primary"
@ -150,7 +150,7 @@ const toCustomerAddressEdit = (addressId) => {
t(
isDefaultAddress(item)
? 'Default address'
: 'Set as default'
: 'Set as default',
)
}}
</QTooltip>
@ -216,7 +216,7 @@ const toCustomerAddressEdit = (addressId) => {
color="primary"
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip>
{{ t('New consignee') }}

View File

@ -158,7 +158,7 @@ const columns = computed(() => [
openConfirmationModal(
t('Send compensation'),
t('Do you want to report compensation to the client by mail?'),
() => sendEmail(`Receipts/${id}/balance-compensation-email`)
() => sendEmail(`Receipts/${id}/balance-compensation-email`),
),
},
],
@ -291,7 +291,7 @@ const showBalancePdf = ({ id }) => {
color="primary"
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip>
{{ t('New payment') }}

View File

@ -62,7 +62,7 @@ const customerContactsRef = ref(null);
color="primary"
flat
icon="add"
shortcut="+"
v-shortcut="'+'"
>
<QTooltip>
{{ t('Add contact') }}

View File

@ -195,7 +195,7 @@ const updateData = () => {
color="primary"
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip>
{{ t('New contract') }}

View File

@ -192,6 +192,7 @@ const debtWarning = computed(() => {
query: {
createForm: JSON.stringify({
clientFk: entity.id,
addressId: entity.defaultAddressFk,
}),
},
}"

View File

@ -236,7 +236,7 @@ const toCustomerFileManagementCreate = () => {
@click.stop="toCustomerFileManagementCreate()"
color="primary"
fab
shortcut="+"
v-shortcut="'+'"
icon="add"
/>
<QTooltip>

View File

@ -23,5 +23,6 @@ const noteFilter = computed(() => {
:body="{ clientFk: route.params.id }"
style="overflow-y: auto"
:select-type="true"
required
/>
</template>

View File

@ -104,7 +104,7 @@ const tableRef = ref();
color="primary"
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip>
{{ t('Send sample') }}

View File

@ -49,7 +49,7 @@ const getData = async (observations) => {
notes.value = originalNotes
.map((observation) => {
const type = observationTypes.value.find(
(type) => type.id === observation.observationTypeFk
(type) => type.id === observation.observationTypeFk,
);
return type
? {
@ -112,8 +112,8 @@ function getPayload() {
(oNote) =>
oNote.id === note.id &&
(note.description !== oNote.description ||
note.observationTypeFk !== oNote.observationTypeFk)
)
note.observationTypeFk !== oNote.observationTypeFk),
),
)
.map((note) => ({
data: note,
@ -130,9 +130,7 @@ async function handleDialog(data) {
.dialog({
component: VnConfirm,
componentProps: {
title: t(
'confirmTicket'
),
title: t('confirmTicket'),
message: t('confirmDeletionMessage'),
},
})
@ -341,7 +339,7 @@ function handleLocation(data, location) {
class="cursor-pointer add-icon q-mt-md"
flat
icon="add"
shortcut="+"
v-shortcut="'+'"
>
<QTooltip>
{{ t('Add note') }}

View File

@ -1,8 +1,9 @@
import axios from 'axios';
export async function getAddresses(clientId) {
export async function getAddresses(clientId, _filter = {}) {
if (!clientId) return;
const filter = {
..._filter,
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',

View File

@ -1,7 +1,8 @@
import axios from 'axios';
export async function getClient(clientId) {
export async function getClient(clientId, _filter = {}) {
const filter = {
..._filter,
include: {
relation: 'defaultAddress',
scope: {

View File

@ -12,6 +12,7 @@ import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FilterTravelForm from 'src/components/FilterTravelForm.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { toDate } from 'src/filters';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const route = useRoute();
const { t } = useI18n();
@ -51,28 +52,12 @@ const onFilterTravelSelected = (formData, id) => {
>
<template #form="{ data }">
<VnRow>
<VnSelect
:label="t('globals.supplier')"
<VnSelectSupplier
v-model="data.supplierFk"
url="Suppliers"
option-value="id"
option-label="nickname"
:fields="['id', 'nickname']"
hide-selected
:required="true"
map-options
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.nickname }}, {{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
<VnSelectDialog
:label="t('entry.basicData.travel')"
v-model="data.travelFk"

View File

@ -17,7 +17,7 @@ const selected = ref([]);
const sortEntryObservationOptions = (data) => {
entryObservationsOptions.value = [...data].sort((a, b) =>
a.description.localeCompare(b.description)
a.description.localeCompare(b.description),
);
};
@ -142,7 +142,7 @@ const columns = computed(() => [
fab
color="primary"
icon="add"
shortcut="+"
v-shortcut="'+'"
@click="entryObservationsRef.insert()"
/>
</QPageSticky>

View File

@ -134,6 +134,7 @@ function downloadCSV(rows) {
@click="
openReport(`Entries/${entityId}/labelSupplier`)
"
data-cy="printLabelsBtn"
/>
</template>
<template #body="props">

View File

@ -13,6 +13,7 @@ import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const state = useState();
const { t } = useI18n();
@ -26,7 +27,6 @@ const newEntryForm = reactive({
travelFk: Number(route.query?.travelFk) || null,
companyFk: user.value.companyFk || null,
});
const suppliersOptions = ref([]);
const travelsOptions = ref([]);
const companiesOptions = ref([]);
@ -36,13 +36,6 @@ const redirectToEntryBasicData = (_, { id }) => {
</script>
<template>
<FetchData
url="Suppliers"
:filter="{ fields: ['id', 'nickname'] }"
order="nickname"
@on-fetch="(data) => (suppliersOptions = data)"
auto-load
/>
<FetchData
url="Travels/filter"
:filter="{ fields: ['id', 'warehouseInName'] }"
@ -79,28 +72,13 @@ const redirectToEntryBasicData = (_, { id }) => {
>
<template #form="{ data, validate }">
<VnRow>
<VnSelect
:label="t('Supplier')"
<VnSelectSupplier
class="full-width"
v-model="data.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="nickname"
hide-selected
:required="true"
:rules="validate('entry.supplierFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
</VnRow>
<VnRow>
<VnSelect
@ -152,7 +130,6 @@ const redirectToEntryBasicData = (_, { id }) => {
<i18n>
es:
Supplier: Proveedor
Travel: Envío
Company: Empresa
</i18n>

View File

@ -7,6 +7,7 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
const props = defineProps({
@ -115,34 +116,14 @@ const companiesOptions = ref([]);
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('entryFilter.params.supplierFk')"
<VnSelectSupplier
v-model="params.supplierFk"
@update:model-value="searchFn()"
url="Suppliers"
option-value="id"
option-label="name"
:fields="['id', 'name', 'nickname']"
:filter-options="['id', 'name', 'nickname']"
sort-by="nickname"
hide-selected
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name}}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
</QItemSection>
</QItem>
<QItem>
@ -202,4 +183,4 @@ const companiesOptions = ref([]);
</QItem>
</template>
</VnFilterPanel>
</template>
</template>

View File

@ -7,6 +7,7 @@ import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnSelect from 'components/common/VnSelect.vue';
import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
@ -18,7 +19,6 @@ defineProps({
});
const itemTypeWorkersOptions = ref([]);
const suppliersOptions = ref([]);
const tagValues = ref([]);
</script>
@ -30,13 +30,6 @@ const tagValues = ref([]);
:filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }"
@on-fetch="(data) => (itemTypeWorkersOptions = data)"
/>
<FetchData
url="Suppliers"
limit="30"
auto-load
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
@on-fetch="(data) => (suppliersOptions = data)"
/>
<ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']">
<template #body="{ params, searchFn }">
<QItem class="q-my-md">
@ -57,31 +50,14 @@ const tagValues = ref([]);
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnSelect
:label="t('globals.params.supplierFk')"
<VnSelectSupplier
v-model="params.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="name"
@update:model-value="searchFn()"
hide-selected
dense
outlined
rounded
use-input
@update:model-value="searchFn()"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name}}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">

View File

@ -13,6 +13,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnDms from 'src/components/common/VnDms.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import axios from 'axios';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
@ -115,27 +116,12 @@ function deleteFile(dmsFk) {
>
<template #form="{ data }">
<VnRow>
<VnSelect
:required="true"
:label="t('supplierFk')"
<VnSelectSupplier
v-model="data.supplierFk"
option-value="id"
option-label="nickname"
url="Suppliers"
:fields="['id', 'nickname']"
sort-by="nickname"
hide-selected
:is-clearable="false"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
`${scope.opt.id} - ${scope.opt.nickname}`
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
:required="true"
/>
<VnInput
clearable
clear-icon="close"
@ -229,7 +215,7 @@ function deleteFile(dmsFk) {
v-else
icon="add_circle"
round
shortcut="+"
v-shortcut="'+'"
padding="xs"
@click="
() => {

View File

@ -226,6 +226,7 @@ const createInvoiceInCorrection = async () => {
v-ripple
clickable
@click="triggerMenu('correct')"
data-cy="createCorrectiveItem"
>
<QItemSection
>{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection

View File

@ -232,7 +232,7 @@ async function insert() {
<QBtn
color="primary"
icon="add"
shortcut="+"
v-shortcut="'+'"
size="lg"
round
@click="!areRows ? insert() : invoiceInFormRef.insert()"

View File

@ -218,7 +218,7 @@ const columns = computed(() => [
<QBtn
color="primary"
icon="add"
shortcut="+"
v-shortcut="'+'"
size="lg"
round
@click="invoiceInFormRef.insert()"

View File

@ -117,7 +117,7 @@ const isNotEuro = (code) => code != 'EUR';
function taxRate(invoiceInTax) {
const sageTaxTypeId = invoiceInTax.taxTypeSageFk;
const taxRateSelection = sageTaxTypes.value.find(
(transaction) => transaction.id == sageTaxTypeId
(transaction) => transaction.id == sageTaxTypeId,
);
const taxTypeSage = taxRateSelection?.rate ?? 0;
const taxableBase = invoiceInTax?.taxableBase ?? 0;
@ -131,14 +131,14 @@ function autocompleteExpense(evt, row, col) {
const param = isNaN(val) ? row[col.model] : val;
const lookup = expenses.value.find(
({ id }) => id == useAccountShortToStandard(param)
({ id }) => id == useAccountShortToStandard(param),
);
expenseRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
}
const taxableBaseTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, 'taxableBase', );
const taxableBaseTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, 'taxableBase');
});
const taxRateTotal = computed(() => {
@ -147,13 +147,9 @@ const taxRateTotal = computed(() => {
});
});
const combinedTotal = computed(() => {
return +taxableBaseTotal.value + +taxRateTotal.value;
});
</script>
<template>
<FetchData
@ -283,7 +279,7 @@ const combinedTotal = computed(() => {
row.taxableBase = await getExchange(
val,
row.currencyFk,
invoiceIn.issued
invoiceIn.issued,
);
}
"
@ -426,7 +422,7 @@ const combinedTotal = computed(() => {
color="primary"
icon="add"
size="lg"
shortcut="+"
v-shortcut="'+'"
round
@click="invoiceInFormRef.insert()"
>

View File

@ -12,6 +12,7 @@ import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const state = useState();
const { t } = useI18n();
@ -60,28 +61,12 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
>
<template #form="{ data, validate }">
<VnRow>
<VnSelect
url="Suppliers"
:fields="['id', 'nickname']"
:label="t('Supplier')"
<VnSelectSupplier
v-model="data.supplierFk"
option-value="id"
option-label="nickname"
hide-selected
:required="true"
:rules="validate('entry.supplierFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
<VnInput
:label="t('invoiceIn.list.supplierRef')"
v-model="data.supplierRef"
@ -111,7 +96,6 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
<i18n>
es:
Supplier: Proveedor
Travel: Envío
Company: Empresa
</i18n>

View File

@ -6,6 +6,7 @@ import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { dateRange } from 'src/filters';
import { date } from 'quasar';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
defineProps({ dataKey: { type: String, required: true } });
const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
@ -65,29 +66,12 @@ function handleDaysAgo(params, daysAgo) {
</QItem>
<QItem>
<QItemSection>
<VnSelect
<VnSelectSupplier
v-model="params.supplierFk"
url="Suppliers"
:fields="['id', 'nickname', 'name']"
:label="getLocale('supplierFk')"
option-label="nickname"
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name}}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
</QItemSection>
</QItem>
<QItem>

View File

@ -14,6 +14,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSection from 'src/components/common/VnSection.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const user = useState().getUser();
const { viewSummary } = useSummaryDialog();
@ -148,7 +149,9 @@ const cols = computed(() => [
<template #body>
<VnTable
ref="tableRef"
:data-key
data-key="InvoiceInList"
url="InvoiceIns/filter"
:order="['issued DESC', 'id DESC']"
:create="{
urlCreate: 'InvoiceIns',
title: t('globals.createInvoiceIn'),
@ -168,27 +171,11 @@ const cols = computed(() => [
</span>
</template>
<template #more-create-dialog="{ data }">
<VnSelect
<VnSelectSupplier
v-model="data.supplierFk"
url="Suppliers"
:fields="['id', 'name', 'nickname']"
:label="t('globals.supplier')"
option-value="id"
option-label="nickname"
:filter-options="['id', 'name', 'nickname']"
hide-selected
:required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}, {{ scope.opt?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
<VnInput
:label="t('invoiceIn.list.supplierRef')"
v-model="data.supplierRef"

View File

@ -92,7 +92,7 @@ const submit = async (rows) => {
class="cursor-pointer fill-icon-on-hover"
color="primary"
icon="add_circle"
shortcut="+"
v-shortcut="'+'"
flat
>
<QTooltip>

View File

@ -125,7 +125,7 @@ onMounted(async () => {
inventoriedDate.value =
(await axios.get('Configs/findOne')).data?.inventoried || today;
if (query.warehouseFk) ref.warehouseFk = query.warehouseFk;
if (query.warehouseFk) ref.warehouseFk = +query.warehouseFk;
else if (!ref.warehouseFk && user.value) ref.warehouseFk = user.value.warehouseFk;
if (ref.date) showWhatsBeforeInventory.value = true;
ref.itemFk = route.params.id;
@ -143,7 +143,7 @@ onMounted(async () => {
const fetchItemBalances = async () => await arrayDataItemBalances.fetch({});
const getBadgeAttrs = (_date) => {
const isSameDate = date.isSameDate(today.value, _date);
const isSameDate = date.isSameDate(today, _date);
const attrs = {
'text-color': isSameDate ? 'black' : 'white',
color: isSameDate ? 'warning' : 'transparent',
@ -153,8 +153,6 @@ const getBadgeAttrs = (_date) => {
const scrollToToday = async () => {
await nextTick();
const today = Date.vnNew();
today.setHours(0, 0, 0, 0);
const todayCell = document.querySelector(`td[data-date="${today.toISOString()}"]`);
if (todayCell) {
todayCell.scrollIntoView({ behavior: 'smooth', block: 'center' });

View File

@ -175,7 +175,7 @@ const insertTag = (rows) => {
@click="insertTag(rows)"
color="primary"
icon="add"
shortcut="+"
v-shortcut="'+'"
fab
>
<QTooltip>

View File

@ -12,6 +12,7 @@ import { QCheckbox } from 'quasar';
import { useArrayData } from 'composables/useArrayData';
import { useValidator } from 'src/composables/useValidator';
import axios from 'axios';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
@ -246,34 +247,14 @@ onMounted(async () => {
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('params.supplierFk')"
<VnSelectSupplier
v-model="params.supplierFk"
@update:model-value="searchFn()"
url="Suppliers"
option-value="id"
option-label="name"
:fields="['id', 'name', 'nickname']"
:filter-options="['id', 'name', 'nickname']"
sort-by="name ASC"
hide-selected
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name }}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id} , ${scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
</QItemSection>
</QItem>
<!-- Tags filter -->

View File

@ -5,6 +5,9 @@ import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'components/FetchData.vue';
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n();
const tableRef = ref();
@ -60,20 +63,17 @@ const columns = computed(() => [
label: t('code'),
isTitle: true,
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'name',
label: t('globals.name'),
cardVisible: true,
create: true,
},
{
align: 'left',
label: t('worker'),
name: 'workerFk',
create: true,
component: 'select',
attrs: {
url: 'Workers/search',
@ -100,7 +100,6 @@ const columns = computed(() => [
align: 'left',
name: 'categoryFk',
label: t('ItemCategory'),
create: true,
component: 'select',
attrs: {
options: itemCategoriesOptions.value,
@ -112,7 +111,6 @@ const columns = computed(() => [
align: 'left',
name: 'Temperature',
label: t('Temperature'),
create: true,
component: 'select',
attrs: {
options: temperatureOptions.value,
@ -180,6 +178,29 @@ const columns = computed(() => [
<WorkerDescriptorProxy :id="row.workerFk" />
</span>
</template>
<template #more-create-dialog="{ data }">
<VnInput v-model="data.code" :label="t('code')" data-cy="codeInput" />
<VnInput
v-model="data.name"
:label="t('globals.name')"
data-cy="nameInput"
/>
<VnSelectWorker v-model="data.workerFk" />
<VnSelect
:label="t('ItemCategory')"
v-model="data.categoryFk"
:options="itemCategoriesOptions"
hide-selected
data-cy="itemCategorySelect"
/>
<VnSelect
:label="t('Temperature')"
v-model="data.temperatureFk"
:options="temperatureOptions"
hide-selected
data-cy="temperatureSelect"
/>
</template>
</VnTable>
</template>
</VnSection>

View File

@ -293,7 +293,7 @@ const columns = computed(() => [
title: t('globals.preview'),
icon: 'preview',
color: 'primary',
action: (row) => viewSummary(row.id, TicketSummary),
action: (row) => viewSummary(row.id, TicketSummary, 'lg-width'),
isPrimary: true,
attrs: {
flat: true,

View File

@ -38,6 +38,7 @@ salesTicketsTable:
payMethod: Pay method
department: Department
packing: ITP
hasItemLost: Item lost
searchBar:
label: Search tickets
info: Search tickets by id or alias

View File

@ -39,6 +39,7 @@ salesTicketsTable:
payMethod: Método de pago
department: Departamento
packing: ITP
hasItemLost: Artículo perdido
searchBar:
label: Buscar tickets
info: Buscar tickets por identificador o alias

View File

@ -110,7 +110,7 @@ const getSelectedTagValues = async (tag) => {
</div>
<QBtn
icon="add_circle"
shortcut="+"
v-shortcut="'+'"
flat
class="filter-icon q-mb-md"
size="md"

View File

@ -22,7 +22,6 @@ const catalogParams = {
};
const arrayData = useArrayData(dataKey, {
url: 'Orders/CatalogFilter',
limit: 50,
userParams: catalogParams,
});
const store = arrayData.store;
@ -66,7 +65,7 @@ function extractValueTags(items) {
.filter((k) => /^value\d+$/.test(k))
.map((v) => x[v])
.filter((v) => v)
.sort()
.sort(),
);
tagValue.value = resultValueTags;
}
@ -76,7 +75,7 @@ watch(
(val) => {
extractTags(val);
},
{ immediate: true }
{ immediate: true },
);
</script>

View File

@ -184,7 +184,7 @@ function addOrder(value, field, params) {
{{
t(
categoryList.find((c) => c.id == customTag.value)?.name ||
''
'',
)
}}
</strong>
@ -296,7 +296,7 @@ function addOrder(value, field, params) {
<template #append>
<QBtn
icon="add_circle"
shortcut="+"
v-shortcut="'+'"
flat
color="primary"
size="md"

View File

@ -81,7 +81,7 @@ const columns = computed(() => [
label: t('module.created'),
component: 'date',
cardVisible: true,
format: (row) => toDateTimeFormat(row?.landed),
format: (row) => toDateTimeFormat(row?.created),
columnField: {
component: null,
},

View File

@ -88,7 +88,7 @@ async function deleteWorCenter(id) {
</VnPaginate>
</div>
<QPageSticky :offset="[18, 18]">
<QBtn @click.stop="dialog.show()" color="primary" fab shortcut="+" icon="add">
<QBtn @click.stop="dialog.show()" color="primary" fab v-shortcut="'+'" icon="add">
<QDialog ref="dialog">
<FormModelPopup
:title="t('Add work center')"

View File

@ -3,14 +3,17 @@ import axios from 'axios';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
vi.mock('axios');
const response = { data: [{ agencyModeFk: 'Agency1' }, { agencyModeFk: 'Agency2' }] };
axios.get.mockResolvedValue(response);
describe('getAgencies', () => {
afterEach(() => {
vi.clearAllMocks();
});
const generateParams = (formData) => ({
const generateParams = (formData, filter = {}) => ({
params: {
filter: JSON.stringify(filter),
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
@ -23,10 +26,15 @@ describe('getAgencies', () => {
addressId: '456',
landed: 'true',
};
const filter = {
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
};
await getAgencies(formData);
await getAgencies(formData, null, filter);
expect(axios.get).toHaveBeenCalledWith('Agencies/getAgenciesWithWarehouse', generateParams(formData));
expect(axios.get).toHaveBeenCalledWith('Agencies/getAgenciesWithWarehouse', generateParams(formData, filter));
});
it('should not call API when formData is missing required landed field', async () => {
@ -52,4 +60,23 @@ describe('getAgencies', () => {
expect(axios.get).not.toHaveBeenCalled();
});
it('should return options and agency when default agency is found', async () => {
const formData = { warehouseId: '123', addressId: '456', landed: 'true' };
const client = { defaultAddress: { agencyModeFk: 'Agency1' } };
const { options, agency } = await getAgencies(formData, client);
expect(options).toEqual(response.data);
expect(agency).toEqual(response.data[0]);
});
it('should return options and agency when client is not provided', async () => {
const formData = { warehouseId: '123', addressId: '456', landed: 'true' };
const { options, agency } = await getAgencies(formData);
expect(options).toEqual(response.data);
expect(agency).toBeNull();
});
});

View File

@ -1,12 +1,26 @@
import axios from 'axios';
import agency from 'src/router/modules/agency';
export async function getAgencies(formData) {
export async function getAgencies(formData, client, _filter = {}) {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return;
const filter = {
..._filter
};
let defaultAgency = null;
let params = {
filter: JSON.stringify(filter),
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
return await axios.get('Agencies/getAgenciesWithWarehouse', { params });
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
if(data && client) {
defaultAgency = data.find((agency) => agency.agencyModeFk === client.defaultAddress.agencyModeFk );
};
return {options: data, agency: defaultAgency}
}

View File

@ -6,6 +6,7 @@ import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
const props = defineProps({
@ -19,7 +20,6 @@ const emit = defineEmits(['search']);
const agencyList = ref([]);
const agencyAgreementList = ref([]);
const supplierList = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
@ -58,14 +58,6 @@ const exprBuilder = (param, value) => {
@on-fetch="(data) => (agencyAgreementList = data)"
auto-load
/>
<FetchData
url="Suppliers"
:filter="{ fields: ['name'] }"
sort-by="name ASC"
limit="30"
@on-fetch="(data) => (supplierList = data)"
auto-load
/>
<VnFilterPanel
:data-key="props.dataKey"
:expr-builder="exprBuilder"
@ -123,14 +115,12 @@ const exprBuilder = (param, value) => {
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm" v-if="supplierList">
<QItem class="q-my-sm">
<QItemSection>
<VnSelect
<VnSelectSupplier
:label="t('Autonomous')"
v-model="params.supplierFk"
:options="supplierList"
option-value="name"
option-label="name"
hide-selected
dense
outlined
rounded

View File

@ -6,29 +6,18 @@ import FormModel from 'components/FormModel.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import VnSelect from 'components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { ref } from 'vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
const router = useRouter();
const route = useRoute();
const supplierList = ref([]);
const filter = { include: [{ relation: 'supplier' }] };
const onSave = (data, response) => {
router.push({ name: 'RoadmapSummary', params: { id: response?.id } });
};
</script>
<template>
<FetchData
url="Suppliers"
auto-load
:filter="{ fields: ['id', 'nickname'] }"
sort-by="nickname"
limit="30"
@on-fetch="(data) => (supplierList = data)"
/>
<FormModel
:url="`Roadmaps/${route.params?.id}`"
observe-form-changes
@ -56,27 +45,14 @@ const onSave = (data, response) => {
/>
</VnRow>
<VnRow>
<VnSelect
<VnSelectSupplier
:label="t('Carrier')"
v-model="data.supplierFk"
:options="supplierList"
option-value="id"
option-label="nickname"
emit-value
map-options
use-input
:input-debounce="0"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel
>{{ opt.id }} - {{ opt.nickname }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
<VnInput
v-model="data.price"
:label="t('Price')"

View File

@ -3,9 +3,9 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
const props = defineProps({
@ -16,19 +16,9 @@ const props = defineProps({
});
const emit = defineEmits(['search']);
const supplierList = ref([]);
</script>
<template>
<FetchData
url="Suppliers"
:filter="{ fields: ['id', 'nickname'] }"
sort-by="nickname"
limit="30"
@on-fetch="(data) => (supplierList = data)"
auto-load
/>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
@ -71,14 +61,11 @@ const supplierList = ref([]);
/>
</QItemSection>
</QItem>
<QItem v-if="supplierList" class="q-my-sm">
<QItem class="q-my-sm">
<QItemSection>
<VnSelect
<VnSelectSupplier
:label="t('Carrier')"
v-model="params.supplierFk"
:options="supplierList"
option-value="id"
option-label="nickname"
dense
outlined
rounded
@ -86,17 +73,7 @@ const supplierList = ref([]);
map-options
use-input
:input-debounce="0"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel
>{{ opt.id }} - {{ opt.nickname }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">

View File

@ -68,7 +68,7 @@ const updateDefaultStop = (data) => {
<QBtn
flat
icon="add"
shortcut="+"
v-shortcut="'+'"
class="cursor-pointer"
color="primary"
@click="roadmapStopsCrudRef.insert()"

View File

@ -120,8 +120,8 @@ const deletePriorities = async () => {
try {
await Promise.all(
selectedRows.value.map((ticket) =>
axios.patch(`Tickets/${ticket?.id}/`, { priority: null })
)
axios.patch(`Tickets/${ticket?.id}/`, { priority: null }),
),
);
} finally {
refreshKey.value++;
@ -132,8 +132,8 @@ const setOrderedPriority = async () => {
try {
await Promise.all(
ticketList.value.map((ticket, index) =>
axios.patch(`Tickets/${ticket?.id}/`, { priority: index + 1 })
)
axios.patch(`Tickets/${ticket?.id}/`, { priority: index + 1 }),
),
);
} finally {
refreshKey.value++;
@ -162,7 +162,7 @@ const setHighestPriority = async (ticket, ticketList) => {
const goToBuscaman = async (ticket = null) => {
await openBuscaman(
routeEntity.value?.vehicleFk,
ticket ? [ticket] : selectedRows.value
ticket ? [ticket] : selectedRows.value,
);
};
@ -393,7 +393,13 @@ const openSmsDialog = async () => {
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" shortcut="+" color="primary" @click="openTicketsDialog">
<QBtn
fab
icon="add"
v-shortcut="'+'"
color="primary"
@click="openTicketsDialog"
>
<QTooltip>
{{ t('Add ticket') }}
</QTooltip>

View File

@ -84,7 +84,7 @@ function exprBuilder(param, value) {
</div>
<QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'ShelvingCreate' }">
<QBtn fab icon="add" color="primary" shortcut="+" />
<QBtn fab icon="add" color="primary" v-shortcut="'+'" />
<QTooltip>
{{ t('shelving.list.newShelving') }}
</QTooltip>

View File

@ -71,7 +71,7 @@ function bankEntityFilter(val, update) {
filteredBankEntitiesOptions.value = bankEntitiesOptions.value.filter(
(bank) =>
bank.bic.toLowerCase().startsWith(needle) ||
bank.name.toLowerCase().includes(needle)
bank.name.toLowerCase().includes(needle),
);
});
}
@ -170,7 +170,7 @@ function bankEntityFilter(val, update) {
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t(
'Name of the bank account holder if different from the provider'
'Name of the bank account holder if different from the provider',
)
}}</QTooltip>
</QIcon>
@ -194,7 +194,7 @@ function bankEntityFilter(val, update) {
<QBtn
flat
icon="add"
shortcut="+"
v-shortcut
class="cursor-pointer"
color="primary"
@click="supplierAccountRef.insert()"

View File

@ -89,7 +89,7 @@ const redirectToUpdateView = (addressData) => {
icon="add"
color="primary"
@click="redirectToCreateView()"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip>
{{ t('New address') }}

View File

@ -114,7 +114,7 @@ const redirectToCreateView = () => {
icon="add"
color="primary"
@click="redirectToCreateView()"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip>
{{ t('supplier.agencyTerms.addRow') }}

View File

@ -78,7 +78,7 @@ const insertRow = () => {
<QBtn
flat
icon="add"
shortcut="+"
v-shortcut="'+'"
class="cursor-pointer"
color="primary"
@click="insertRow()"

View File

@ -9,6 +9,8 @@ import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { toDateTimeFormat } from 'src/filters/date';
import FetchData from 'src/components/FetchData.vue';
import TicketProblems from 'src/components/TicketProblems.vue';
const $props = defineProps({
id: {
@ -28,6 +30,7 @@ const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const problems = ref({});
const filter = {
include: [
@ -113,6 +116,11 @@ const setData = (entity) => {
</script>
<template>
<FetchData
:url="`Tickets/${entityId}/getTicketProblems`"
auto-load
@on-fetch="(data) => ([problems] = data)"
/>
<CardDescriptor
module="Ticket"
:url="`Tickets/${entityId}`"
@ -167,48 +175,9 @@ const setData = (entity) => {
<VnLv :label="t('globals.warehouse')" :value="entity.warehouse?.name" />
<VnLv :label="t('globals.alias')" :value="entity.nickname" />
</template>
<template #icons="{ entity }">
<QCardActions class="q-gutter-x-md">
<QIcon
v-if="entity.client.isActive == false"
name="vn:disabled"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client inactive') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity.client.isFreezed == true"
name="vn:frozen"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client Frozen') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity?.problem?.includes('hasRisk')"
name="vn:risk"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client has debt') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity.client.isTaxDataChecked == false"
name="vn:no036"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client not checked') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity.isDeleted == true"
name="vn:deletedTicket"
size="xs"
color="primary"
>
<QTooltip>{{ t('This ticket is deleted') }}</QTooltip>
</QIcon>
<template #icons>
<QCardActions class="q-gutter-x-xs">
<TicketProblems :row="problems" />
</QCardActions>
</template>
<template #actions="{ entity }">

View File

@ -69,14 +69,16 @@ const onAddressSelected = (addressId) => {
}
const fetchClient = async () => {
const { data } = await getClient(client.value)
const [retrievedClient] = data;
const response = await getClient(client.value)
if (!response) return;
const [retrievedClient] = response.data;
selectedClient.value = retrievedClient;
};
const fetchAddresses = async () => {
const { data } = await getAddresses(client.value);
addressesOptions.value = data;
const response = await getAddresses(client.value);
if (!response) return;
addressesOptions.value = response.data;
const { defaultAddress } = selectedClient.value;
address.value = defaultAddress.id;

View File

@ -32,7 +32,7 @@ watch(
crudModelFilter.where.ticketFk = route.params.id;
store.filter = crudModelFilter;
await ticketNotesCrudRef.value.reload();
}
},
);
function handleDelete(row) {
ticketNotesCrudRef.value.remove([row]);
@ -105,7 +105,7 @@ async function handleSave() {
<VnRow v-if="observationTypes.length > rows.length">
<QBtn
icon="add_circle"
shortcut="+"
v-shortcut="'+'"
flat
class="fill-icon-on-hover q-ml-md"
color="primary"

View File

@ -41,7 +41,7 @@ watch(
crudModelFilter.where.ticketFk = route.params.id;
store.filter = crudModelFilter;
await ticketPackagingsCrudRef.value.reload();
}
},
);
</script>
@ -118,7 +118,7 @@ watch(
<VnRow>
<QBtn
icon="add_circle"
shortcut="+"
v-shortcut="'+'"
flat
class="fill-icon-on-hover q-ml-md"
color="primary"

View File

@ -24,6 +24,7 @@ import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnUsesMana from 'src/components/ui/VnUsesMana.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import TicketProblems from 'src/components/TicketProblems.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const route = useRoute();
@ -56,7 +57,7 @@ const canProceed = ref();
watch(
() => route.params.id,
() => tableRef.value.reload()
() => tableRef.value.reload(),
);
const columns = computed(() => [
@ -199,7 +200,7 @@ const changeQuantity = async (sale) => {
await updateQuantity(sale);
} catch (e) {
const { quantity } = tableRef.value.CrudModelRef.originalData.find(
(s) => s.id === sale.id
(s) => s.id === sale.id,
);
sale.quantity = quantity;
throw e;
@ -503,7 +504,7 @@ async function isSalePrepared(item) {
componentProps: {
title: t('Item prepared'),
message: t(
'This item is already prepared. Do you want to continue?'
'This item is already prepared. Do you want to continue?',
),
data: item,
},
@ -525,7 +526,7 @@ watch(
if (newItemFk) {
updateItem(newRow.value);
}
}
},
);
</script>
@ -595,7 +596,7 @@ watch(
openConfirmationModal(
t('Continue anyway?'),
t('You are going to delete lines of the ticket'),
removeSales
removeSales,
)
"
>
@ -679,53 +680,7 @@ watch(
:disabled-attr="isTicketEditable"
>
<template #column-statusIcons="{ row }">
<router-link
v-if="row.claim?.claimFk"
:to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }"
>
<QIcon color="primary" name="vn:claims" size="xs">
<QTooltip>
{{ t('ticketSale.claim') }}:
{{ row.claim?.claimFk }}
</QTooltip>
</QIcon>
</router-link>
<QIcon v-if="row.visible < 0" color="primary" name="warning" size="xs">
<QTooltip>
{{ t('ticketSale.visible') }}: {{ row.visible || 0 }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.reserved"
color="primary"
name="vn:reserva"
size="xs"
data-cy="ticketSaleReservedIcon"
>
<QTooltip>
{{ t('ticketSale.reserved') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.itemShortage"
color="primary"
name="vn:unavailable"
size="xs"
>
<QTooltip>
{{ t('ticketSale.noVisible') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.hasComponentLack"
color="primary"
name="vn:components"
size="xs"
>
<QTooltip>
{{ t('ticketSale.hasComponentLack') }}
</QTooltip>
</QIcon>
<TicketProblems :row="row" />
</template>
<template #body-cell-picture="{ row }">
<QTd>
@ -856,7 +811,7 @@ watch(
color="primary"
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
data-cy="ticketSaleAddToBasketBtn"
/>
<QTooltip class="text-no-wrap">

View File

@ -40,7 +40,7 @@ watch(
async () => {
store.filter = crudModelFilter.value;
await ticketServiceCrudRef.value.reload();
}
},
);
onMounted(async () => await getDefaultTaxClass());
@ -59,7 +59,7 @@ const createRefund = async () => {
t('service.createRefundSuccess', {
ticketId: refundTicket.id,
}),
'positive'
'positive',
);
router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
};
@ -225,7 +225,7 @@ async function handleSave() {
color="primary"
icon="add"
@click="ticketServiceCrudRef.insert()"
shortcut="+"
v-shortcut="'+'"
/>
</QPageSticky>
</template>

View File

@ -20,6 +20,7 @@ import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnToSummary from 'src/components/ui/VnToSummary.vue';
import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
import TicketProblems from 'src/components/TicketProblems.vue';
const route = useRoute();
const { notify } = useNotify();
@ -243,7 +244,7 @@ onMounted(async () => {
<QCard class="vn-one" v-if="entity.notes.length">
<VnTitle
:url="toTicketUrl('observation')"
:text="t('ticket.pageTitles.notes')"
:text="t('globals.pageTitles.notes')"
/>
<QVirtualScroll
:items="entity.notes"
@ -311,83 +312,7 @@ onMounted(async () => {
<template #body="props">
<QTr :props="props">
<QTd class="q-gutter-x-xs">
<QBtn
flat
round
icon="vn:claims"
v-if="props.row.claim"
color="primary"
:to="{
name: 'ClaimCard',
params: {
id: props.row.claim.claimFk,
},
}"
>
<QTooltip>
{{ t('ticket.summary.claim') }}:
{{ props.row.claim.claimFk }}
</QTooltip>
</QBtn>
<QBtn
flat
round
icon="vn:claims"
v-if="props.row.claimBeginning"
color="primary"
:to="{
name: 'ClaimCard',
params: {
id: props.row.claimBeginning.claimFk,
},
}"
>
<QTooltip>
{{ t('ticket.summary.claim') }}:
{{ props.row.claimBeginning.claimFk }}
</QTooltip>
</QBtn>
<QIcon
name="warning"
v-show="props.row.visible < 0"
color="primary"
size="xs"
>
<QTooltip>
{{ t('globals.visible') }}:
{{ props.row.visible }}
</QTooltip>
</QIcon>
<QIcon
name="vn:reserved"
v-show="props.row.reserved"
color="primary"
size="xs"
>
<QTooltip>
{{ t('ticket.summary.reserved') }}
</QTooltip>
</QIcon>
<QIcon
name="vn:unavailable"
v-show="props.row.itemShortage"
color="primary"
size="xs"
>
<QTooltip>
{{ t('ticket.summary.itemShortage') }}
</QTooltip>
</QIcon>
<QIcon
name="vn:components"
v-show="props.row.hasComponentLack"
color="primary"
size="xs"
>
<QTooltip>
{{ t('ticket.summary.hasComponentLack') }}
</QTooltip>
</QIcon>
<TicketProblems :row="props.row" />
</QTd>
<QTd>
<QBtn class="link" flat>

View File

@ -19,7 +19,7 @@ watch(
async (val) => {
paginateFilter.where.ticketFk = val;
paginateRef.value.fetch();
}
},
);
const paginateFilter = reactive({
@ -119,7 +119,7 @@ const openCreateModal = () => createTrackingDialogRef.value.show();
color="primary"
fab
icon="add"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip class="text-no-wrap">
{{ t('tracking.addState') }}

View File

@ -38,35 +38,43 @@ onBeforeMount(async () => {
await onClientSelected(initialFormState);
});
function resetAgenciesSelector(formData) {
agenciesOptions.value = [];
formData.agencyModeId = null;
}
const fetchClient = async (formData) => {
const { data } = await getClient(formData.clientId);
const [client] = data;
const response = await getClient(formData.clientId);
if (!response) return;
const [client] = response.data;
selectedClient.value = client;
};
const fetchAddresses = async (formData) => {
const { data } = await getAddresses(formData.clientId);
addressesOptions.value = data;
const response = await getAddresses(formData.clientId);
if (!response) return;
addressesOptions.value = response.data;
const { defaultAddress } = selectedClient.value;
formData.addressId = defaultAddress.id;
};
const onClientSelected = async (formData) => {
resetAgenciesSelector(formData);
await fetchClient(formData);
await fetchAddresses(formData);
};
const fetchAvailableAgencies = async (formData) => {
const { data } = await getAgencies(formData);
agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find(
(agency) =>
agency.agencyModeFk === selectedClient.value.defaultAddress.agencyModeFk
);
if (defaultAgency) formData.agencyModeId = defaultAgency.agencyModeFk;
resetAgenciesSelector(formData);
const response= await getAgencies(formData, selectedClient.value);
if (!response) return;
const { options, agency } = response
if(options)
agenciesOptions.value = options;
if(agency)
formData.agencyModeId = agency;
};
const redirectToTicketList = (_, { id }) => {

View File

@ -38,35 +38,43 @@ onBeforeMount(async () => {
await onClientSelected(initialFormState);
});
function resetAgenciesSelector(formData) {
agenciesOptions.value = [];
if(formData) formData.agencyModeId = null;
}
const fetchClient = async (formData) => {
const { data } = await getClient(formData.clientId);
const [client] = data;
const response = await getClient(formData.clientId);
if (!response) return;
const [client] = response.data;
selectedClient.value = client;
};
const fetchAddresses = async (formData) => {
const { data } = await getAddresses(formData.clientId);
addressesOptions.value = data;
const response = await getAddresses(formData.clientId);
if (!response) return;
addressesOptions.value = response.data;
const { defaultAddress } = selectedClient.value;
formData.addressId = defaultAddress.id;
};
const onClientSelected = async (formData) => {
resetAgenciesSelector(formData);
await fetchClient(formData);
await fetchAddresses(formData);
};
const fetchAvailableAgencies = async (formData) => {
const { data } = await getAgencies(formData);
agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find(
(agency) =>
agency.agencyModeFk === selectedClient.value.defaultAddress.agencyModeFk
);
if (defaultAgency) formData.agencyModeId = defaultAgency.agencyModeFk;
resetAgenciesSelector(formData);
const response= await getAgencies(formData, selectedClient.value);
if (!response) return;
const { options, agency } = response
if(options)
agenciesOptions.value = options;
if(agency)
formData.agencyModeId = agency;
};
const redirectToTicketList = (_, { id }) => {

View File

@ -229,37 +229,46 @@ const columns = computed(() => [
],
},
]);
function resetAgenciesSelector(formData) {
agenciesOptions.value = [];
if(formData) formData.agencyModeId = null;
}
function redirectToLines(id) {
const url = `#/ticket/${id}/sale`;
window.open(url, '_blank');
}
const onClientSelected = async (formData) => {
const onClientSelected = async (formData) => {
resetAgenciesSelector(formData);
await fetchClient(formData);
await fetchAddresses(formData);
};
const fetchAvailableAgencies = async (formData) => {
const { data } = await getAgencies(formData);
agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find(
(agency) =>
agency.agencyModeFk === selectedClient.value.defaultAddress.agencyModeFk
);
if (defaultAgency) formData.agencyModeId = defaultAgency.agencyModeFk;
resetAgenciesSelector(formData);
const response= await getAgencies(formData, selectedClient.value);
if (!response) return;
const { options, agency } = response
if(options)
agenciesOptions.value = options;
if(agency)
formData.agencyModeId = agency;
};
const fetchClient = async (formData) => {
const { data } = await getClient(formData.clientId);
const [client] = data;
const response = await getClient(formData.clientId);
if (!response) return;
const [client] = response.data;
selectedClient.value = client;
};
const fetchAddresses = async (formData) => {
const { data } = await getAddresses(formData.clientId);
addressesOptions.value = data;
const response = await getAddresses(formData.clientId);
if (!response) return;
addressesOptions.value = response.data;
const { defaultAddress } = selectedClient.value;
formData.addressId = defaultAddress.id;

View File

@ -208,3 +208,9 @@ ticketList:
toLines: Go to lines
addressNickname: Address nickname
ref: Reference
rounding: Rounding
noVerifiedData: No verified data
purchaseRequest: Purchase request
notVisible: Not visible
clientFrozen: Client frozen
componentLack: Component lack

View File

@ -213,3 +213,9 @@ ticketList:
toLines: Ir a lineas
addressNickname: Alias consignatario
ref: Referencia
rounding: Redondeo
noVerifiedData: Sin datos comprobados
purchaseRequest: Petición de compra
notVisible: No visible
clientFrozen: Cliente congelado
componentLack: Faltan componentes

View File

@ -217,7 +217,7 @@ const removeThermograph = async (id) => {
icon="add"
color="primary"
@click="redirectToThermographForm('create')"
shortcut="+"
v-shortcut="'+'"
/>
<QTooltip class="text-no-wrap">
{{ t('Add thermograph') }}

View File

@ -8,6 +8,7 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import axios from 'axios';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
const props = defineProps({
@ -48,11 +49,11 @@ const warehouses = async () => {
continentsResponse.data.forEach((continent) => {
const countriesInContinent = Object.keys(countryContinentMap).filter(
(countryId) => countryContinentMap[countryId] === continent.id.toString()
(countryId) => countryContinentMap[countryId] === continent.id.toString(),
);
warehousesByContinent.value[continent.code] = warehousesResponse.data.filter(
(warehouse) => countriesInContinent.includes(warehouse.countryFk.toString())
(warehouse) => countriesInContinent.includes(warehouse.countryFk.toString()),
);
});
};
@ -112,7 +113,7 @@ warehouses();
<template #append>
<QBtn
icon="add"
shortcut="+"
v-shortcut="'+'"
flat
dense
size="12px"
@ -211,30 +212,13 @@ warehouses();
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('globals.pageTitles.supplier')"
<VnSelectSupplier
v-model="params.cargoSupplierFk"
url="Suppliers"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name}}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
</QItemSection>
</QItem>
<QItem>

View File

@ -96,7 +96,13 @@ async function remove(row) {
>
</VnTable>
<QPageSticky :offset="[18, 18]">
<QBtn @click.stop="dialog.show()" color="primary" fab icon="add" shortcut="+">
<QBtn
@click.stop="dialog.show()"
color="primary"
fab
icon="add"
v-shortcut="'+'"
>
<QDialog ref="dialog">
<FormModelPopup
:title="t('Create new Wagon type')"

View File

@ -0,0 +1,234 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnTable from 'components/VnTable/VnTable.vue';
import { toDate } from 'src/filters';
import { useQuasar } from 'quasar';
import axios from 'axios';
const { t } = useI18n();
const route = useRoute();
const tableRef = ref();
const entityId = computed(() => route.params.id);
const quasar = useQuasar();
async function reactivateWorker() {
const hasToReactive = tableRef.value.CrudModelRef.formData.find(
(data) => !data.ended
);
if (hasToReactive) {
quasar
.dialog({
message: t('Do you want to reactivate the user?'),
ok: {
push: true,
color: 'primary',
},
cancel: true,
})
.onOk(async () => {
await axios.patch(`Workers/${entityId.value}`, {
isDisable: false,
});
});
}
}
const columns = computed(() => [
{
name: 'started',
label: t('worker.business.tableVisibleColumns.started'),
align: 'left',
format: ({ started }) => toDate(started),
component: 'date',
cardVisible: true,
create: true,
},
{
name: 'ended',
label: t('worker.business.tableVisibleColumns.ended'),
align: 'left',
format: ({ ended }) => toDate(ended),
component: 'date',
cardVisible: true,
create: true,
},
{
label: t('worker.business.tableVisibleColumns.company'),
align: 'left',
name: 'companyCodeFk',
component: 'select',
attrs: {
url: 'Companies',
fields: ['code'],
optionLabel: 'code',
optionValue: 'code',
},
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'reasonEndFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.reasonEnd'),
attrs: {
url: 'BusinessReasonEnds',
fields: ['id', 'reason'],
optionLabel: 'reason',
},
cardVisible: true,
},
{
align: 'left',
name: 'departmentFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.department'),
attrs: {
url: 'Departments',
fields: ['id', 'name'],
optionLabel: 'name',
},
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'workerBusinessProfessionalCategoryFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.professionalCategory'),
attrs: {
url: 'WorkerBusinessProfessionalCategories',
fields: ['id', 'description', 'code'],
optionLabel: 'description',
},
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'calendarTypeFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.calendarType'),
attrs: {
url: 'CalendarTypes',
fields: ['id', 'description'],
optionLabel: 'description',
},
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'workcenterFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.workCenter'),
attrs: {
url: 'WorkCenters',
fields: ['id', 'name'],
optionLabel: 'name',
},
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'workerBusinessCategoryFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.payrollCategories'),
attrs: {
url: 'payrollCategories',
fields: ['id', 'description'],
optionLabel: 'description',
},
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'occupationCodeFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.occupationCode'),
attrs: {
url: 'OccupationCodes',
fields: ['code', 'name'],
optionValue: 'code',
},
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'rate',
label: t('worker.business.tableVisibleColumns.rate'),
component: 'input',
cardVisible: true,
create: true,
},
{
align: 'left',
name: 'workerBusinessTypeFk',
component: 'select',
label: t('worker.business.tableVisibleColumns.businessType'),
attrs: {
url: 'WorkerBusinessTypes',
fields: ['id', 'name'],
},
cardVisible: true,
create: true,
},
{
align: 'left',
label: t('worker.business.tableVisibleColumns.amount'),
name: 'amount',
component: 'input',
cardVisible: true,
create: true,
},
{
align: 'left',
label: t('worker.business.tableVisibleColumns.basicSalary'),
name: 'basicSalary',
component: 'input',
cardVisible: true,
create: true,
},
{
name: 'notes',
label: t('worker.business.tableVisibleColumns.notes'),
align: 'left',
component: 'input',
cardVisible: true,
},
]);
</script>
<template>
<VnTable
ref="tableRef"
data-key="WorkerBusiness"
:url="`Workers/${entityId}/Business`"
save-url="/Businesses/crud"
:create="{
urlCreate: `Workers/${entityId}/Business`,
title: 'Create business',
onDataSaved: () => tableRef.reload(),
formInitialData: {},
}"
order="id DESC"
:columns="columns"
default-mode="card"
auto-load
:disable-option="{ table: true }"
:right-search="false"
card-class="grid-two q-gutter-x-xl q-gutter-y-md q-pr-lg q-py-lg"
:is-editable="true"
:use-model="true"
@save-changes="(data) => reactivateWorker(data)"
/>
</template>
<i18n>
es:
Do you want to reactivate the user?: desea reactivar el usuario?
</i18n>

View File

@ -1,7 +1,8 @@
<script setup>
import { nextTick, ref, watch } from 'vue';
import { nextTick, ref, watch, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useAcl } from 'src/composables/useAcl';
import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue';
import FetchData from 'components/FetchData.vue';
@ -9,10 +10,17 @@ import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import axios from 'axios';
import VnNotes from 'src/components/ui/VnNotes.vue';
import { useStateStore } from 'src/stores/useStateStore';
const stateStore = useStateStore();
const router = useRouter();
const route = useRoute();
const { t } = useI18n();
const acl = useAcl();
const canSeeNotes = computed(() =>
acl.hasAny([{ model: 'Worker', props: '__get__business', accessType: 'READ' }]),
);
const workerIsFreelance = ref();
const WorkerFreelanceRef = ref();
const workerCalendarFilterRef = ref(null);
@ -26,6 +34,10 @@ const contractHolidays = ref(null);
const yearHolidays = ref(null);
const eventsMap = ref({});
const festiveEventsMap = ref({});
const saveUrl = ref();
const body = {
workerFk: route.params.id,
};
const onFetchActiveContract = (data) => {
if (!data) return;
@ -67,7 +79,7 @@ const onFetchAbsences = (data) => {
name: holidayName,
isFestive: true,
},
true
true,
);
});
}
@ -146,7 +158,7 @@ watch(
async () => {
await nextTick();
await activeContractRef.value.fetch();
}
},
);
watch([year, businessFk], () => refreshData());
</script>
@ -181,6 +193,20 @@ watch([year, businessFk], () => refreshData());
/>
</template>
</RightMenu>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown() && canSeeNotes">
<VnNotes
:just-input="true"
:url="`Workers/${route.params.id}/business`"
:filter="{ fields: ['id', 'notes', 'workerFk'] }"
:save-url="saveUrl"
@on-fetch="
(data) => {
saveUrl = `Businesses/${data.id}`;
}
"
:body="body"
/>
</Teleport>
<QPage class="column items-center">
<QCard v-if="workerIsFreelance">
<QCardSection class="text-center">

View File

@ -180,8 +180,6 @@ const yearList = ref(generateYears());
:is-clearable="false"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('Contract')"

View File

@ -1,7 +1,7 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { ref, computed } from 'vue';
import { ref, computed, watch } from 'vue';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
@ -19,6 +19,7 @@ const trainsData = ref([]);
const machinesData = ref([]);
const route = useRoute();
const routeId = computed(() => route.params.id);
const selected = ref([]);
const initialData = computed(() => {
return {
@ -41,6 +42,21 @@ async function insert() {
await axios.post('Operators', initialData.value);
crudModelRef.value.reload();
}
watch(
() => crudModelRef.value?.formData,
(formData) => {
if (formData && formData.length) {
if (JSON.stringify(selected.value) !== JSON.stringify(formData)) {
selected.value = formData;
}
} else if (selected.value.length > 0) {
selected.value = [];
}
},
{ immediate: true, deep: true }
);
</script>
<template>
@ -67,6 +83,7 @@ async function insert() {
:data-required="{ workerFk: route.params.id }"
ref="crudModelRef"
search-url="operator"
:selected="selected"
auto-load
>
<template #body="{ rows }">

View File

@ -101,7 +101,7 @@ function reloadData() {
openConfirmationModal(
t(`Remove PDA`),
t('Do you want to remove this PDA?'),
() => deallocatePDA(row.deviceProductionFk)
() => deallocatePDA(row.deviceProductionFk),
)
"
>
@ -114,7 +114,13 @@ function reloadData() {
</template>
</VnPaginate>
<QPageSticky :offset="[18, 18]">
<QBtn @click.stop="dialog.show()" color="primary" fab icon="add" shortcut="+">
<QBtn
@click.stop="dialog.show()"
color="primary"
fab
icon="add"
v-shortcut="'+'"
>
<QDialog ref="dialog">
<FormModelPopup
:title="t('Add new device')"

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