Merge branch 'dev' of https: refs #6919//gitea.verdnatura.es/verdnatura/salix-front into 6919-syncData

This commit is contained in:
Jorge Penadés 2025-02-06 10:32:00 +01:00
commit 848d00a8b3
120 changed files with 1683 additions and 748 deletions

View File

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

View File

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

View File

@ -282,7 +282,7 @@ const setCategoryList = (data) => {
<QItem class="q-mt-lg"> <QItem class="q-mt-lg">
<QBtn <QBtn
icon="add_circle" icon="add_circle"
shortcut="+" v-shortcut="'+'"
flat flat
class="fill-icon-on-hover q-px-xs" class="fill-icon-on-hover q-px-xs"
color="primary" color="primary"

View File

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

View File

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

View File

@ -500,7 +500,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
<QCard <QCard
bordered bordered
flat flat
class="row no-wrap justify-between cursor-pointer" class="row no-wrap justify-between cursor-pointer q-pa-sm"
@click=" @click="
(_, row) => { (_, row) => {
$props.rowClick && $props.rowClick(row); $props.rowClick && $props.rowClick(row);
@ -581,7 +581,6 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
<!-- Actions --> <!-- Actions -->
<QCardSection <QCardSection
v-if="colsMap.tableActions" v-if="colsMap.tableActions"
class="column flex-center w-10 no-margin q-pa-xs q-gutter-y-xs"
@click="stopEventPropagation($event)" @click="stopEventPropagation($event)"
> >
<QBtn <QBtn
@ -630,7 +629,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
size="md" size="md"
round round
flat flat
shortcut="+" v-shortcut="'+'"
:disabled="!disabledAttr" :disabled="!disabledAttr"
/> />
<QTooltip> <QTooltip>
@ -648,7 +647,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
color="primary" color="primary"
fab fab
icon="add" icon="add"
shortcut="+" v-shortcut="'+'"
data-cy="vnTableCreateBtn" data-cy="vnTableCreateBtn"
/> />
<QTooltip self="top right"> <QTooltip self="top right">
@ -807,12 +806,15 @@ es:
.grid-two { .grid-two {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); grid-template-columns: 2fr 2fr;
max-width: 100%; .vn-label-value {
margin: 0 auto; flex-direction: column;
overflow: scroll; white-space: nowrap;
white-space: wrap; .fields {
width: 100%; display: flex;
}
}
white-space: nowrap;
} }
.w-80 { .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 route = useRoute();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const rows = ref(); const rows = ref([]);
const dmsRef = ref(); const dmsRef = ref();
const formDialog = ref({}); const formDialog = ref({});
const token = useSession().getTokenMultimedia(); const token = useSession().getTokenMultimedia();
@ -389,6 +389,14 @@ defineExpose({
</div> </div>
</template> </template>
</QTable> </QTable>
<div
v-else
class="info-row q-pa-md text-center"
>
<h5>
{{ t('No data to display') }}
</h5>
</div>
</template> </template>
</VnPaginate> </VnPaginate>
<QDialog v-model="formDialog.show"> <QDialog v-model="formDialog.show">
@ -405,7 +413,7 @@ defineExpose({
fab fab
color="primary" color="primary"
icon="add" icon="add"
shortcut="+" v-shortcut
@click="showFormDialog()" @click="showFormDialog()"
class="fill-icon" class="fill-icon"
> >

View File

@ -268,7 +268,7 @@ async function applyFilter() {
filter.where.and.push(selectedFilters.value); filter.where.and.push(selectedFilters.value);
} }
paginate.value.fetch(filter); paginate.value.fetch({ filter });
} }
function setDate(type) { function setDate(type) {
@ -404,7 +404,7 @@ watch(
ref="paginate" ref="paginate"
:data-key="`${model}Log`" :data-key="`${model}Log`"
:url="`${model}Logs`" :url="`${model}Logs`"
:filter="filter" :user-filter="filter"
:skeleton="false" :skeleton="false"
auto-load auto-load
@on-fetch="setLogTree" @on-fetch="setLogTree"

View File

@ -53,6 +53,7 @@ const url = computed(() => {
:fields="['id', 'name', 'nickname', 'code']" :fields="['id', 'name', 'nickname', 'code']"
:filter-options="['id', 'name', 'nickname', 'code']" :filter-options="['id', 'name', 'nickname', 'code']"
sort-by="nickname ASC" sort-by="nickname ASC"
data-cy="vnWorkerSelect"
> >
<template #prepend v-if="$props.hasAvatar"> <template #prepend v-if="$props.hasAvatar">
<VnAvatar :worker-id="value" color="primary" v-bind="$attrs" /> <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 { createWrapper, axios } from 'app/test/vitest/helper';
import VnNotes from 'src/components/ui/VnNotes.vue'; import VnNotes from 'src/components/ui/VnNotes.vue';
import vnDate from 'src/boot/vnDate';
describe('VnNotes', () => { describe('VnNotes', () => {
let vm; let vm;
let wrapper; let wrapper;
let spyFetch; let spyFetch;
let postMock; let postMock;
let expectedBody; let patchMock;
const mockData= {name: 'Tony', lastName: 'Stark', text: 'Test Note', observationTypeFk: 1}; let expectedInsertBody;
let expectedUpdateBody;
function generateExpectedBody() { const defaultOptions = {
expectedBody = {...vm.$props.body, ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }}; url: '/test',
} body: { name: 'Tony', lastName: 'Stark' },
selectType: false,
async function setTestParams(text, observationType, type){ saveUrl: null,
vm.newNote.text = text; justInput: false,
vm.newNote.observationTypeFk = observationType; };
wrapper.setProps({ selectType: type }); function generateWrapper(
} options = defaultOptions,
text = null,
beforeAll(async () => { observationType = null,
vi.spyOn(axios, 'get').mockReturnValue({ data: [] }); ) {
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
wrapper = createWrapper(VnNotes, { wrapper = createWrapper(VnNotes, {
propsData: { propsData: options,
url: '/test',
body: { name: 'Tony', lastName: 'Stark' },
}
}); });
wrapper = wrapper.wrapper; wrapper = wrapper.wrapper;
vm = wrapper.vm; 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(() => { beforeEach(() => {
postMock = vi.spyOn(axios, 'post').mockResolvedValue(mockData); postMock = vi.spyOn(axios, 'post');
spyFetch = vi.spyOn(vm.vnPaginateRef, 'fetch').mockImplementation(() => vi.fn()); patchMock = vi.spyOn(axios, 'patch');
}); });
afterEach(() => { afterEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
expectedBody = {}; expectedInsertBody = {};
expectedUpdateBody = {};
});
afterAll(() => {
vi.restoreAllMocks();
}); });
describe('insert', () => { describe('insert', () => {
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is null', async () => { it('should not call axios.post and vnPaginateRef.fetch when newNote.text is null', async () => {
await setTestParams( null, null, true ); generateWrapper({ selectType: true });
createSpyFetch();
await vm.insert(); await vm.insert();
@ -53,8 +80,9 @@ describe('VnNotes', () => {
expect(spyFetch).not.toHaveBeenCalled(); expect(spyFetch).not.toHaveBeenCalled();
}); });
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is empty', async () => { it('should not call axios.post and vnPaginateRef.fetch when newNote.text is empty', async () => {
await setTestParams( "", null, false ); generateWrapper(null, '');
createSpyFetch();
await vm.insert(); await vm.insert();
@ -62,8 +90,9 @@ describe('VnNotes', () => {
expect(spyFetch).not.toHaveBeenCalled(); expect(spyFetch).not.toHaveBeenCalled();
}); });
it('should not call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is true', async () => { it('should not call axios.post and vnPaginateRef.fetch when observationTypeFk is null and selectType is true', async () => {
await setTestParams( "Test Note", null, true ); generateWrapper({ selectType: true }, 'Test Note');
createSpyFetch();
await vm.insert(); await vm.insert();
@ -71,37 +100,57 @@ describe('VnNotes', () => {
expect(spyFetch).not.toHaveBeenCalled(); expect(spyFetch).not.toHaveBeenCalled();
}); });
it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is false', async () => { it('should call axios.post and vnPaginateRef.fetch when observationTypeFk is missing and selectType is false', async () => {
await setTestParams( "Test Note", null, false ); generateWrapper(null, 'Test Note');
createSpyFetch();
generateExpectedBody(); generateExpectedBody();
await vm.insert(); 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 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(spyFetch).toHaveBeenCalled(); expect(spyFetch).toHaveBeenCalled();
}); });
it('should call axios.post and vnPaginateRef.fetch when newNote is valid', async () => { 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(); generateExpectedBody();
await vm.insert(); await vm.insert();
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody); expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody);
expect(spyFetch).toHaveBeenCalled(); 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

@ -74,7 +74,7 @@ onBeforeMount(async () => {
() => [$props.url, $props.filter], () => [$props.url, $props.filter],
async () => { async () => {
if (!isSameDataKey.value) await getData(); if (!isSameDataKey.value) await getData();
} },
); );
}); });
@ -109,7 +109,7 @@ const iconModule = computed(() => route.matched[1].meta.icon);
const toModule = computed(() => const toModule = computed(() =>
route.matched[1].path.split('/').length > 2 route.matched[1].path.split('/').length > 2
? route.matched[1].redirect ? route.matched[1].redirect
: route.matched[1].children[0].redirect : route.matched[1].children[0].redirect,
); );
</script> </script>

View File

@ -15,6 +15,10 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
userFilter: {
type: Object,
default: null,
},
entityId: { entityId: {
type: [Number, String], type: [Number, String],
default: null, default: null,
@ -34,6 +38,7 @@ const isSummary = ref();
const arrayData = useArrayData(props.dataKey, { const arrayData = useArrayData(props.dataKey, {
url: props.url, url: props.url,
filter: props.filter, filter: props.filter,
userFilter: props.userFilter,
skip: 0, skip: 0,
oneRecord: true, oneRecord: true,
}); });
@ -204,4 +209,13 @@ async function fetch() {
.summaryHeader { .summaryHeader {
color: $white; color: $white;
} }
.cardSummary :deep(.q-card__section[content]) {
display: flex;
flex-wrap: wrap;
padding: 0;
> * {
flex: 1;
}
}
</style> </style>

View File

@ -114,7 +114,7 @@ async function clearFilters() {
arrayData.resetPagination(); arrayData.resetPagination();
// Filtrar los params no removibles // Filtrar los params no removibles
const removableFilters = Object.keys(userParams.value).filter((param) => const removableFilters = Object.keys(userParams.value).filter((param) =>
$props.unremovableParams.includes(param) $props.unremovableParams.includes(param),
); );
const newParams = {}; const newParams = {};
// Conservar solo los params que no son removibles // Conservar solo los params que no son removibles
@ -162,13 +162,13 @@ const formatTags = (tags) => {
const tags = computed(() => { const tags = computed(() => {
const filteredTags = tagsList.value.filter( const filteredTags = tagsList.value.filter(
(tag) => !($props.customTags || []).includes(tag.label) (tag) => !($props.customTags || []).includes(tag.label),
); );
return formatTags(filteredTags); return formatTags(filteredTags);
}); });
const customTags = computed(() => const customTags = computed(() =>
tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label)) tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label)),
); );
async function remove(key) { async function remove(key) {
@ -188,10 +188,13 @@ function formatValue(value) {
const getLocale = (label) => { const getLocale = (label) => {
const param = label.split('.').at(-1); const param = label.split('.').at(-1);
const globalLocale = `globals.params.${param}`; const globalLocale = `globals.params.${param}`;
const moduleName = route.meta.moduleName;
const moduleLocale = `${moduleName.toLowerCase()}.${param}`;
if (te(globalLocale)) return t(globalLocale); if (te(globalLocale)) return t(globalLocale);
else if (te(t(`params.${param}`))); else if (te(moduleLocale)) return t(moduleLocale);
else { else {
const camelCaseModuleName = route.meta.moduleName.charAt(0).toLowerCase() + route.meta.moduleName.slice(1); const camelCaseModuleName =
moduleName.charAt(0).toLowerCase() + moduleName.slice(1);
return t(`${camelCaseModuleName}.params.${param}`); return t(`${camelCaseModuleName}.params.${param}`);
} }
}; };

View File

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

View File

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

View File

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

View File

@ -94,6 +94,9 @@ export function useArrayData(key, userOptions) {
if (params.filter.where || exprFilter) if (params.filter.where || exprFilter)
params.filter.where = { ...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); params.filter = JSON.stringify(params.filter);
store.isLoading = true; store.isLoading = true;

View File

@ -212,6 +212,10 @@ select:-webkit-autofill {
justify-content: center; justify-content: center;
} }
.q-card__section[dense] {
padding: 0;
}
input[type='number'] { input[type='number'] {
-moz-appearance: textfield; -moz-appearance: textfield;
} }

View File

@ -332,10 +332,13 @@ globals:
wasteRecalc: Waste recaclulate wasteRecalc: Waste recaclulate
operator: Operator operator: Operator
parking: Parking parking: Parking
vehicleList: Vehicles
vehicle: Vehicle
unsavedPopup: unsavedPopup:
title: Unsaved changes will be lost title: Unsaved changes will be lost
subtitle: Are you sure exit without saving? subtitle: Are you sure exit without saving?
params: params:
description: Description
clientFk: Client id clientFk: Client id
salesPersonFk: Sales person salesPersonFk: Sales person
warehouseFk: Warehouse warehouseFk: Warehouse
@ -358,7 +361,13 @@ globals:
correctingFk: Rectificative correctingFk: Rectificative
daysOnward: Days onward daysOnward: Days onward
countryFk: Country countryFk: Country
countryCodeFk: Country
companyFk: Company companyFk: Company
model: Model
fuel: Fuel
active: Active
inactive: Inactive
deliveryPoint: Delivery point
errors: errors:
statusUnauthorized: Access denied statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred statusInternalServerError: An internal server error has ocurred
@ -378,7 +387,7 @@ login:
loginError: Invalid username or password loginError: Invalid username or password
fieldRequired: This field is required fieldRequired: This field is required
twoFactorRequired: Two-factor verification required twoFactorRequired: Two-factor verification required
twoFactorRequired: twoFactor:
validate: Validate validate: Validate
insert: Enter the verification code insert: Enter the verification code
explanation: >- explanation: >-
@ -457,48 +466,6 @@ ticket:
consigneeStreet: Street consigneeStreet: Street
create: create:
address: Address 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: department:
chat: Chat chat: Chat
bossDepartment: Boss Department bossDepartment: Boss Department

View File

@ -332,10 +332,13 @@ globals:
wasteRecalc: Recalcular mermas wasteRecalc: Recalcular mermas
operator: Operario operator: Operario
parking: Parking parking: Parking
vehicleList: Vehículos
vehicle: Vehículo
unsavedPopup: unsavedPopup:
title: Los cambios que no haya guardado se perderán title: Los cambios que no haya guardado se perderán
subtitle: ¿Seguro que quiere salir sin guardar? subtitle: ¿Seguro que quiere salir sin guardar?
params: params:
description: Descripción
clientFk: Id cliente clientFk: Id cliente
salesPersonFk: Comercial salesPersonFk: Comercial
warehouseFk: Almacén warehouseFk: Almacén
@ -356,6 +359,7 @@ globals:
daysOnward: Días adelante daysOnward: Días adelante
packing: ITP packing: ITP
countryFk: País countryFk: País
countryCodeFk: País
companyFk: Empresa companyFk: Empresa
errors: errors:
statusUnauthorized: Acceso denegado statusUnauthorized: Acceso denegado

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -158,7 +158,7 @@ const columns = computed(() => [
openConfirmationModal( openConfirmationModal(
t('Send compensation'), t('Send compensation'),
t('Do you want to report compensation to the client by mail?'), 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" color="primary"
fab fab
icon="add" icon="add"
shortcut="+" v-shortcut="'+'"
/> />
<QTooltip> <QTooltip>
{{ t('New payment') }} {{ t('New payment') }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -56,7 +56,7 @@ const { openConfirmationModal } = useVnConfirm();
openConfirmationModal( openConfirmationModal(
t('Are you sure you want to delete it?'), t('Are you sure you want to delete it?'),
t('Delete department'), t('Delete department'),
removeDepartment removeDepartment,
) )
" "
> >

View File

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

View File

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

View File

@ -215,7 +215,7 @@ function deleteFile(dmsFk) {
v-else v-else
icon="add_circle" icon="add_circle"
round round
shortcut="+" v-shortcut="'+'"
padding="xs" padding="xs"
@click=" @click="
() => { () => {

View File

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

View File

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

View File

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

View File

@ -177,7 +177,7 @@ const cols = computed(() => [
:required="true" :required="true"
/> />
<VnInput <VnInput
:label="t('invoicein.list.supplierRef')" :label="t('invoiceIn.list.supplierRef')"
v-model="data.supplierRef" v-model="data.supplierRef"
/> />
<VnSelect <VnSelect
@ -190,7 +190,7 @@ const cols = computed(() => [
:required="true" :required="true"
/> />
<VnInputDate <VnInputDate
:label="t('invoicein.summary.issued')" :label="t('invoiceIn.summary.issued')"
v-model="data.issued" v-model="data.issued"
/> />
</template> </template>

View File

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

View File

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

View File

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

View File

@ -5,6 +5,9 @@ import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.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 { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
@ -60,20 +63,17 @@ const columns = computed(() => [
label: t('code'), label: t('code'),
isTitle: true, isTitle: true,
cardVisible: true, cardVisible: true,
create: true,
}, },
{ {
align: 'left', align: 'left',
name: 'name', name: 'name',
label: t('globals.name'), label: t('globals.name'),
cardVisible: true, cardVisible: true,
create: true,
}, },
{ {
align: 'left', align: 'left',
label: t('worker'), label: t('worker'),
name: 'workerFk', name: 'workerFk',
create: true,
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Workers/search', url: 'Workers/search',
@ -100,7 +100,6 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'categoryFk', name: 'categoryFk',
label: t('ItemCategory'), label: t('ItemCategory'),
create: true,
component: 'select', component: 'select',
attrs: { attrs: {
options: itemCategoriesOptions.value, options: itemCategoriesOptions.value,
@ -112,7 +111,6 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'Temperature', name: 'Temperature',
label: t('Temperature'), label: t('Temperature'),
create: true,
component: 'select', component: 'select',
attrs: { attrs: {
options: temperatureOptions.value, options: temperatureOptions.value,
@ -180,6 +178,29 @@ const columns = computed(() => [
<WorkerDescriptorProxy :id="row.workerFk" /> <WorkerDescriptorProxy :id="row.workerFk" />
</span> </span>
</template> </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> </VnTable>
</template> </template>
</VnSection> </VnSection>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -88,7 +88,7 @@ async function deleteWorCenter(id) {
</VnPaginate> </VnPaginate>
</div> </div>
<QPageSticky :offset="[18, 18]"> <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"> <QDialog ref="dialog">
<FormModelPopup <FormModelPopup
:title="t('Add work center')" :title="t('Add work center')"

View File

@ -3,14 +3,17 @@ import axios from 'axios';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies'; import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
vi.mock('axios'); vi.mock('axios');
const response = { data: [{ agencyModeFk: 'Agency1' }, { agencyModeFk: 'Agency2' }] };
axios.get.mockResolvedValue(response);
describe('getAgencies', () => { describe('getAgencies', () => {
afterEach(() => { afterEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
const generateParams = (formData) => ({ const generateParams = (formData, filter = {}) => ({
params: { params: {
filter: JSON.stringify(filter),
warehouseFk: formData.warehouseId, warehouseFk: formData.warehouseId,
addressFk: formData.addressId, addressFk: formData.addressId,
landed: formData.landed, landed: formData.landed,
@ -23,10 +26,15 @@ describe('getAgencies', () => {
addressId: '456', addressId: '456',
landed: 'true', 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 () => { it('should not call API when formData is missing required landed field', async () => {
@ -52,4 +60,23 @@ describe('getAgencies', () => {
expect(axios.get).not.toHaveBeenCalled(); 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 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; if (!formData.warehouseId || !formData.addressId || !formData.landed) return;
const filter = {
..._filter
};
let defaultAgency = null;
let params = { let params = {
filter: JSON.stringify(filter),
warehouseFk: formData.warehouseId, warehouseFk: formData.warehouseId,
addressFk: formData.addressId, addressFk: formData.addressId,
landed: formData.landed, 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

@ -100,7 +100,7 @@ const emit = defineEmits(['search']);
<VnSelect <VnSelect
:label="t('Vehicle')" :label="t('Vehicle')"
v-model="params.vehicleFk" v-model="params.vehicleFk"
url="Vehicles" url="Vehicles/active"
sort-by="numberPlate ASC" sort-by="numberPlate ASC"
option-value="id" option-value="id"
option-label="numberPlate" option-label="numberPlate"

View File

@ -59,7 +59,7 @@ const onSave = (data, response) => {
<VnSelect <VnSelect
:label="t('Vehicle')" :label="t('Vehicle')"
v-model="data.vehicleFk" v-model="data.vehicleFk"
url="Vehicles" url="Vehicles/active"
sort-by="numberPlate ASC" sort-by="numberPlate ASC"
option-value="id" option-value="id"
option-label="numberPlate" option-label="numberPlate"

View File

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

View File

@ -96,8 +96,7 @@ const columns = computed(() => [
create: true, create: true,
component: 'select', component: 'select',
attrs: { attrs: {
url: 'vehicles', url: 'vehicles/active',
fields: ['id', 'numberPlate'],
optionLabel: 'numberPlate', optionLabel: 'numberPlate',
optionFilterValue: 'numberPlate', optionFilterValue: 'numberPlate',
find: { find: {

View File

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

View File

@ -0,0 +1,162 @@
<script setup>
import { ref } from 'vue';
import FormModel from 'components/FormModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const warehouses = ref([]);
const companies = ref([]);
const countries = ref([]);
const fuelTypes = ref([]);
const bankPolicies = ref([]);
const deliveryPoints = ref([]);
</script>
<template>
<FetchData
url="Warehouses"
:filter="{ fields: ['id', 'name'] }"
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<FetchData
url="Companies"
:filter="{ fields: ['id', 'code'] }"
@on-fetch="(data) => (companies = data)"
auto-load
/>
<FetchData
url="Countries"
:filter="{ fields: ['code'] }"
@on-fetch="(data) => (countries = data)"
auto-load
/>
<FetchData
url="FuelTypes"
:filter="{ fields: ['id', 'name'] }"
@on-fetch="(data) => (fuelTypes = data)"
auto-load
/>
<FetchData
url="DeliveryPoints"
:filter="{ fields: ['id', 'name'] }"
@on-fetch="(data) => (deliveryPoints = data)"
auto-load
/>
<FormModel model="Vehicle" :url-update="`Vehicles/${$route.params.id}`">
<template #form="{ data }">
<VnRow>
<VnInput v-model="data.description" :label="$t('globals.description')" />
<VnInput v-model="data.numberPlate" :label="$t('vehicle.numberPlate')" />
</VnRow>
<VnRow>
<VnInput
v-model="data.model"
:label="$t('globals.model')"
:required="true"
/>
<VnSelect
url="VehicleTypes"
v-model="data.vehicleTypeFk"
:label="$t('globals.type')"
/>
</VnRow>
<VnRow>
<VnInput
v-model="data.tradeMark"
:label="$t('vehicle.tradeMark')"
:required="true"
/>
<VnInput v-model="data.chassis" :label="$t('vehicle.chassis')" />
</VnRow>
<VnRow>
<VnSelect
v-model="data.fuelTypeFk"
:label="$t('globals.fuel')"
:options="fuelTypes"
/>
<VnSelect
v-model="data.deliveryPointFk"
:label="$t('globals.deliveryPoint')"
:options="deliveryPoints"
/>
</VnRow>
<VnRow>
<VnSelect
v-model="data.companyFk"
:label="$t('globals.company')"
:options="companies"
option-label="code"
/>
<VnSelect
v-model="data.warehouseFk"
:label="$t('globals.warehouse')"
:options="warehouses"
/>
</VnRow>
<VnRow>
<VnSelect
url="Suppliers"
:filter="{ fields: ['id', 'name'] }"
v-model="data.supplierFk"
:label="$t('globals.supplier')"
/>
<VnSelect
url="Suppliers"
:filter="{ fields: ['id', 'name'] }"
v-model="data.supplierCoolerFk"
:label="$t('vehicle.supplierCooler')"
/>
</VnRow>
<VnRow>
<VnSelect
url="BankPolicies"
:filter="{ fields: ['id', 'ref'] }"
v-model="data.bankPolicyFk"
:label="$t('vehicle.leasing')"
:options="bankPolicies"
option-label="ref"
option-value="id"
/>
<VnInput v-model="data.leasing" :label="$t('vehicle.nLeasing')" />
</VnRow>
<VnRow>
<VnInputNumber v-model="data.import" :label="$t('globals.amount')" />
<VnInputNumber
v-model="data.importCooler"
:label="$t('vehicle.amountCooler')"
/>
</VnRow>
<VnRow>
<VnSelect
url="Ppes"
option-label="id"
v-model="data.ppeFk"
:label="$t('vehicle.ppe')"
/>
<VnSelect
v-model="data.countryCodeFk"
:label="$t('globals.country')"
:options="countries"
option-label="code"
option-value="code"
/>
</VnRow>
<VnRow>
<VnInput v-model="data.vin" :label="$t('vehicle.vin')" />
<span :style="{ 'align-self': $q.screen.gt.xs ? 'end' : 'unset' }">
<QCheckbox
v-model="data.isActive"
:label="$t('vehicle.isActive')"
:false-value="0"
:true-value="1"
dense
class="q-mt-sm"
/>
</span>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,13 @@
<script setup>
import VnCardBeta from 'components/common/VnCardBeta.vue';
import VehicleDescriptor from './VehicleDescriptor.vue';
import VehicleFilter from '../VehicleFilter.js';
</script>
<template>
<VnCardBeta
data-key="Vehicle"
base-url="Vehicles"
:filter="VehicleFilter"
:descriptor="VehicleDescriptor"
/>
</template>

View File

@ -0,0 +1,50 @@
<script setup>
import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const { notify } = useNotify();
</script>
<template>
<CardDescriptor
:url="`Vehicles/${$route.params.id}`"
module="Vehicle"
data-key="Vehicle"
title="numberPlate"
:to-module="{ name: 'VehicleList' }"
>
<template #menu="{ entity }">
<QItem
data-cy="delete"
v-ripple
clickable
@click="
async () => {
try {
await axios.delete(`Vehicles/${entity.id}`);
notify('vehicle.remove', 'positive');
$router.push({ name: 'VehicleList' });
} catch (e) {
throw e;
}
}
"
>
<QItemSection>
{{ $t('vehicle.delete') }}
</QItemSection>
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="$t('vehicle.numberPlate')" :value="entity.numberPlate" />
<VnLv :label="$t('vehicle.tradeMark')" :value="entity.tradeMark" />
<VnLv :label="$t('globals.model')" :value="entity.model" />
<VnLv :label="$t('globals.country')" :value="entity.countryCodeFk" />
</template>
</CardDescriptor>
</template>
<i18n>
es:
Vehicle removed: Vehículo eliminado
</i18n>

View File

@ -0,0 +1,127 @@
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VehicleFilter from '../VehicleFilter.js';
import { downloadFile } from 'src/composables/downloadFile';
import { dashIfEmpty } from 'src/filters';
const props = defineProps({ id: { type: [Number, String], default: null } });
const route = useRoute();
const entityId = computed(() => props.id || +route.params.id);
const links = {
'basic-data': `#/vehicle/${entityId.value}/basic-data`,
notes: `#/vehicle/${entityId.value}/notes`,
dms: `#/vehicle/${entityId.value}/dms`,
'invoice-in': `#/vehicle/${entityId.value}/invoice-in`,
events: `#/vehicle/${entityId.value}/events`,
};
</script>
<template>
<CardSummary data-key="Vehicle" :url="`Vehicles/${entityId}`" :filter="VehicleFilter">
<template #header="{ entity }">
<div>{{ entity.id }} - {{ entity.numberPlate }}</div>
</template>
<template #body="{ entity }">
<QCard class="vn-one">
<QCardSection dense>
<VnTitle
:url="links['basic-data']"
:text="$t('globals.pageTitles.basicData')"
/>
</QCardSection>
<QCardSection content>
<QList dense>
<VnLv
:label="$t('globals.description')"
:value="entity.description"
/>
<VnLv
:label="$t('vehicle.tradeMark')"
:value="entity.tradeMark"
/>
<VnLv :label="$t('globals.model')" :value="entity.model" />
<VnLv :label="$t('globals.supplier')">
<template #value>
<span class="link">
{{ entity.supplier?.name }}
<SupplierDescriptorProxy :id="entity.supplierFk" />
</span>
</template>
</VnLv>
<VnLv :label="$t('vehicle.supplierCooler')">
<template #value>
<span class="link">
{{ entity.supplierCooler?.name }}
<SupplierDescriptorProxy
:id="entity.supplierCoolerFk"
/>
</span>
</template>
</VnLv>
<VnLv :label="$t('vehicle.vin')" :value="entity.vin" />
</QList>
<QList dense>
<VnLv :label="$t('vehicle.chassis')" :value="entity.chassis" />
<VnLv
:label="$t('globals.fuel')"
:value="entity.fuelType?.name"
/>
<VnLv :label="$t('vehicle.ppe')" :value="entity.ppeFk" />
<VnLv :label="$t('vehicle.nLeasing')" :value="entity.leasing" />
<VnLv
:label="$t('vehicle.leasing')"
:value="entity.bankPolicy?.ref"
>
<template #value>
<span v-text="dashIfEmpty(entity.bankPolicy?.name)" />
<QBtn
v-if="entity.bankPolicy?.dmsFk"
class="q-ml-xs"
color="primary"
flat
dense
icon="cloud_download"
@click="downloadFile(entity.bankPolicy?.dmsFk)"
>
<QTooltip>{{ $t('globals.download') }}</QTooltip>
</QBtn>
</template>
</VnLv>
<VnLv :label="$t('globals.amount')" :value="entity.import" />
</QList>
<QList dense>
<VnLv
:label="$t('globals.warehouse')"
:value="entity.warehouse?.name"
/>
<VnLv
:label="$t('globals.company')"
:value="entity.company?.code"
/>
<VnLv
:label="$t('globals.deliveryPoint')"
:value="entity.deliveryPoint?.name"
/>
<VnLv
:label="$t('globals.country')"
:value="entity.countryCodeFk"
/>
<VnLv
:label="$t('vehicle.isKmTruckRate')"
:value="!!entity.isKmTruckRate"
/>
<VnLv
:label="$t('vehicle.isActive')"
:value="!!entity.isActive"
/>
</QList>
</QCardSection>
</QCard>
</template>
</CardSummary>
</template>

View File

@ -0,0 +1,76 @@
export default {
fields: [
'id',
'description',
'isActive',
'isKmTruckRate',
'warehouseFk',
'companyFk',
'numberPlate',
'chassis',
'supplierFk',
'supplierCoolerFk',
'tradeMark',
'fuelTypeFk',
'import',
'importCooler',
'vin',
'model',
'ppeFk',
'countryCodeFk',
'leasing',
'bankPolicyFk',
'vehicleTypeFk',
'deliveryPointFk',
],
include: [
{
relation: 'warehouse',
scope: {
fields: ['id', 'name'],
},
},
{
relation: 'company',
scope: {
fields: ['id', 'code'],
},
},
{
relation: 'supplier',
scope: {
fields: ['id', 'name'],
},
},
{
relation: 'supplierCooler',
scope: {
fields: ['id', 'name'],
},
},
{
relation: 'fuelType',
scope: {
fields: ['id', 'name'],
},
},
{
relation: 'bankPolicy',
scope: {
fields: ['id', 'ref', 'dmsFk'],
},
},
{
relation: 'ppe',
scope: {
fields: ['id'],
},
},
{
relation: 'deliveryPoint',
scope: {
fields: ['id', 'name'],
},
},
],
};

View File

@ -0,0 +1,224 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'src/components/FetchData.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import VehicleSummary from 'src/pages/Route/Vehicle/Card/VehicleSummary.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const warehouses = ref([]);
const companies = ref([]);
const countries = ref([]);
const vehicleStates = ref([]);
const vehicleTypes = ref([]);
const columns = computed(() => [
{
name: 'isActive',
columnFilter: false,
align: 'center',
},
{
name: 'id',
label: t('globals.id'),
isId: true,
chip: {
condition: () => true,
},
},
{
name: 'description',
label: t('globals.description'),
},
{
name: 'tradeMark',
label: t('vehicle.tradeMark'),
cardVisible: true,
},
{
name: 'numberPlate',
label: t('vehicle.numberPlate'),
isTitle: true,
},
{
name: 'vehicleTypeFk',
label: t('globals.type'),
format: (row) => row.type,
columnFilter: {
component: 'select',
name: 'vehicleTypeFk',
options: vehicleTypes.value,
},
cardVisible: true,
},
{
name: 'vehicleStateFk',
label: t('globals.state'),
columnFilter: {
component: 'select',
name: 'vehicleStateFk',
optionLabel: 'state',
options: vehicleStates.value,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.state),
},
{
name: 'chassis',
label: t('vehicle.chassis'),
},
{
name: 'leasing',
label: t('vehicle.leasing'),
},
{
name: 'warehouseFk',
label: t('globals.warehouse'),
format: (row, dashIfEmpty) => dashIfEmpty(row.warehouse),
columnFilter: {
component: 'select',
name: 'warehouseFk',
options: warehouses.value,
},
cardVisible: true,
},
{
name: 'companyFk',
label: t('globals.company'),
format: (row, dashIfEmpty) => dashIfEmpty(row.company),
columnFilter: {
component: 'select',
name: 'companyFk',
optionLabel: 'code',
options: companies.value,
},
},
{
name: 'countryCodeFk',
label: t('globals.country'),
columnFilter: {
component: 'select',
name: 'countryCodeFk',
optionValue: 'code',
optionLabel: 'code',
options: countries.value,
},
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('components.smartCard.openSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, VehicleSummary),
},
],
},
]);
</script>
<template>
<FetchData
url="Warehouses"
:filter="{ fields: ['id', 'name'] }"
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<FetchData
url="Companies"
:filter="{ fields: ['id', 'code'] }"
@on-fetch="(data) => (companies = data)"
auto-load
/>
<FetchData
url="Countries"
:filter="{ fields: ['code'] }"
@on-fetch="(data) => (countries = data)"
auto-load
/>
<FetchData
url="VehicleStates"
:filter="{ fields: ['id', 'state'] }"
@on-fetch="(data) => (vehicleStates = data)"
auto-load
/>
<FetchData
url="VehicleTypes"
:filter="{ fields: ['id', 'name'] }"
@on-fetch="(data) => (vehicleTypes = data)"
auto-load
/>
<VnSection
data-key="VehicleList"
:columns="columns"
prefix="vehicle"
:array-data-props="{
url: 'Vehicles/filter',
}"
>
<template #body>
<VnTable
ref="tableRef"
data-key="VehicleList"
:columns="columns"
redirect="route/vehicle"
:create="{
urlCreate: 'Vehicles',
title: t('vehicle.create'),
onDataSaved: ({ id }) => $refs.tableRef.redirect(id),
formInitialData: { isActive: true, isKmTruckRate: false },
}"
:use-model="true"
:right-search="false"
>
<template #column-isActive="{ row }">
<span>
<QIcon
v-if="!row.isActive"
name="vn:inactive-car"
color="primary"
size="xs"
>
<QTooltip>{{ $t('globals.inactive') }}</QTooltip>
</QIcon>
</span>
</template>
<template #more-create-dialog="{ data }">
<VnInput
v-model="data.numberPlate"
:label="$t('vehicle.numberPlate')"
:uppercase="true"
/>
<VnInput v-model="data.tradeMark" :label="$t('vehicle.tradeMark')" />
<VnInput v-model="data.model" :label="$t('globals.model')" />
<VnSelect
v-model="data.vehicleTypeFk"
:label="$t('globals.type')"
:options="vehicleTypes"
/>
<VnSelect
v-model="data.warehouseFk"
:label="$t('globals.warehouse')"
:options="warehouses"
/>
<VnSelect
v-model="data.countryCodeFk"
:label="$t('globals.country')"
option-value="code"
option-label="code"
:options="countries"
/>
<VnInput
v-model="data.description"
:label="$t('globals.description')"
/>
<QCheckbox to v-model="data.isActive" :label="$t('globals.active')" />
</template>
</VnTable>
</template>
</VnSection>
</template>

View File

@ -0,0 +1,20 @@
vehicle:
tradeMark: Trade Mark
numberPlate: Nº Plate
chassis: Chassis
leasing: Leasing
isKmTruckRate: Trailer
delete: Delete Vehicle
supplierCooler: Supplier Cooler
vin: VIN
ppe: Ppe
isActive: Active
nLeasing: Nº Leasing
create: Create Vehicle
amountCooler: Amount cooler
remove: Vehicle removed
search: Search Vehicle
searchInfo: Search by id or number plate
params:
vehicleTypeFk: Type
vehicleStateFk: State

View File

@ -0,0 +1,20 @@
vehicle:
tradeMark: Marca
numberPlate: Matrícula
chassis: Nº de bastidor
leasing: Leasing
isKmTruckRate: Trailer
delete: Eliminar vehículo
supplierCooler: Proveedor Frío
vin: VIN
ppe: Nº Inmovilizado
create: Crear vehículo
amountCooler: Importe frío
isActive: Activo
nLeasing: Nº leasing
remove: Vehículo eliminado
search: Buscar Vehículo
searchInfo: Buscar por id o matrícula
params:
vehicleTypeFk: Tipo
vehicleStateFk: Estado

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,9 @@ import VnLv from 'src/components/ui/VnLv.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
import filter from './TicketFilter.js'; import filter from './TicketFilter.js';
import FetchData from 'src/components/FetchData.vue';
import TicketProblems from 'src/components/TicketProblems.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: Number,
@ -27,6 +30,7 @@ const { t } = useI18n();
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
}); });
const problems = ref({});
function ticketFilter(ticket) { function ticketFilter(ticket) {
return JSON.stringify({ clientFk: ticket.clientFk }); return JSON.stringify({ clientFk: ticket.clientFk });
@ -34,6 +38,11 @@ function ticketFilter(ticket) {
</script> </script>
<template> <template>
<FetchData
:url="`Tickets/${entityId}/getTicketProblems`"
auto-load
@on-fetch="(data) => ([problems] = data)"
/>
<CardDescriptor <CardDescriptor
module="Ticket" module="Ticket"
:url="`Tickets/${entityId}`" :url="`Tickets/${entityId}`"
@ -85,48 +94,9 @@ function ticketFilter(ticket) {
<VnLv :label="t('globals.warehouse')" :value="entity.warehouse?.name" /> <VnLv :label="t('globals.warehouse')" :value="entity.warehouse?.name" />
<VnLv :label="t('globals.alias')" :value="entity.nickname" /> <VnLv :label="t('globals.alias')" :value="entity.nickname" />
</template> </template>
<template #icons="{ entity }"> <template #icons>
<QCardActions class="q-gutter-x-md"> <QCardActions class="q-gutter-x-xs">
<QIcon <TicketProblems :row="problems" />
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>
</QCardActions> </QCardActions>
</template> </template>
<template #actions="{ entity }"> <template #actions="{ entity }">

View File

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

View File

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

View File

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

View File

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

View File

@ -40,7 +40,7 @@ watch(
async () => { async () => {
store.filter = crudModelFilter.value; store.filter = crudModelFilter.value;
await ticketServiceCrudRef.value.reload(); await ticketServiceCrudRef.value.reload();
} },
); );
onMounted(async () => await getDefaultTaxClass()); onMounted(async () => await getDefaultTaxClass());
@ -59,7 +59,7 @@ const createRefund = async () => {
t('service.createRefundSuccess', { t('service.createRefundSuccess', {
ticketId: refundTicket.id, ticketId: refundTicket.id,
}), }),
'positive' 'positive',
); );
router.push({ name: 'TicketSale', params: { id: refundTicket.id } }); router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
}; };
@ -225,7 +225,7 @@ async function handleSave() {
color="primary" color="primary"
icon="add" icon="add"
@click="ticketServiceCrudRef.insert()" @click="ticketServiceCrudRef.insert()"
shortcut="+" v-shortcut="'+'"
/> />
</QPageSticky> </QPageSticky>
</template> </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 VnSelect from 'src/components/common/VnSelect.vue';
import VnToSummary from 'src/components/ui/VnToSummary.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue';
import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
import TicketProblems from 'src/components/TicketProblems.vue';
const route = useRoute(); const route = useRoute();
const { notify } = useNotify(); const { notify } = useNotify();
@ -243,7 +244,7 @@ onMounted(async () => {
<QCard class="vn-one" v-if="entity.notes.length"> <QCard class="vn-one" v-if="entity.notes.length">
<VnTitle <VnTitle
:url="toTicketUrl('observation')" :url="toTicketUrl('observation')"
:text="t('ticket.pageTitles.notes')" :text="t('globals.pageTitles.notes')"
/> />
<QVirtualScroll <QVirtualScroll
:items="entity.notes" :items="entity.notes"
@ -311,83 +312,7 @@ onMounted(async () => {
<template #body="props"> <template #body="props">
<QTr :props="props"> <QTr :props="props">
<QTd class="q-gutter-x-xs"> <QTd class="q-gutter-x-xs">
<QBtn <TicketProblems :row="props.row" />
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>
</QTd> </QTd>
<QTd> <QTd>
<QBtn class="link" flat> <QBtn class="link" flat>

View File

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

View File

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

View File

@ -38,35 +38,43 @@ onBeforeMount(async () => {
await onClientSelected(initialFormState); await onClientSelected(initialFormState);
}); });
function resetAgenciesSelector(formData) {
agenciesOptions.value = [];
if(formData) formData.agencyModeId = null;
}
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const { data } = await getClient(formData.clientId); const response = await getClient(formData.clientId);
const [client] = data; if (!response) return;
const [client] = response.data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
const { data } = await getAddresses(formData.clientId); const response = await getAddresses(formData.clientId);
addressesOptions.value = data; if (!response) return;
addressesOptions.value = response.data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;
formData.addressId = defaultAddress.id; formData.addressId = defaultAddress.id;
}; };
const onClientSelected = async (formData) => { const onClientSelected = async (formData) => {
resetAgenciesSelector(formData);
await fetchClient(formData); await fetchClient(formData);
await fetchAddresses(formData); await fetchAddresses(formData);
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
const { data } = await getAgencies(formData); resetAgenciesSelector(formData);
agenciesOptions.value = data; const response= await getAgencies(formData, selectedClient.value);
if (!response) return;
const defaultAgency = agenciesOptions.value.find(
(agency) => const { options, agency } = response
agency.agencyModeFk === selectedClient.value.defaultAddress.agencyModeFk if(options)
); agenciesOptions.value = options;
if(agency)
if (defaultAgency) formData.agencyModeId = defaultAgency.agencyModeFk; formData.agencyModeId = agency;
}; };
const redirectToTicketList = (_, { id }) => { 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) { function redirectToLines(id) {
const url = `#/ticket/${id}/sale`; const url = `#/ticket/${id}/sale`;
window.open(url, '_blank'); window.open(url, '_blank');
} }
const onClientSelected = async (formData) => { const onClientSelected = async (formData) => {
resetAgenciesSelector(formData);
await fetchClient(formData); await fetchClient(formData);
await fetchAddresses(formData); await fetchAddresses(formData);
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
const { data } = await getAgencies(formData); resetAgenciesSelector(formData);
agenciesOptions.value = data; const response= await getAgencies(formData, selectedClient.value);
if (!response) return;
const defaultAgency = agenciesOptions.value.find(
(agency) => const { options, agency } = response
agency.agencyModeFk === selectedClient.value.defaultAddress.agencyModeFk if(options)
); agenciesOptions.value = options;
if(agency)
if (defaultAgency) formData.agencyModeId = defaultAgency.agencyModeFk; formData.agencyModeId = agency;
}; };
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const { data } = await getClient(formData.clientId); const response = await getClient(formData.clientId);
const [client] = data; if (!response) return;
const [client] = response.data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
const { data } = await getAddresses(formData.clientId); const response = await getAddresses(formData.clientId);
addressesOptions.value = data; if (!response) return;
addressesOptions.value = response.data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;
formData.addressId = defaultAddress.id; formData.addressId = defaultAddress.id;

View File

@ -208,3 +208,9 @@ ticketList:
toLines: Go to lines toLines: Go to lines
addressNickname: Address nickname addressNickname: Address nickname
ref: Reference 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 toLines: Ir a lineas
addressNickname: Alias consignatario addressNickname: Alias consignatario
ref: Referencia 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" icon="add"
color="primary" color="primary"
@click="redirectToThermographForm('create')" @click="redirectToThermographForm('create')"
shortcut="+" v-shortcut="'+'"
/> />
<QTooltip class="text-no-wrap"> <QTooltip class="text-no-wrap">
{{ t('Add thermograph') }} {{ t('Add thermograph') }}

View File

@ -113,7 +113,7 @@ warehouses();
<template #append> <template #append>
<QBtn <QBtn
icon="add" icon="add"
shortcut="+" v-shortcut="'+'"
flat flat
dense dense
size="12px" size="12px"

View File

@ -96,7 +96,13 @@ async function remove(row) {
> >
</VnTable> </VnTable>
<QPageSticky :offset="[18, 18]"> <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"> <QDialog ref="dialog">
<FormModelPopup <FormModelPopup
:title="t('Create new Wagon type')" :title="t('Create new Wagon type')"

View File

@ -1,7 +1,8 @@
<script setup> <script setup>
import { nextTick, ref, watch } from 'vue'; import { nextTick, ref, watch, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useAcl } from 'src/composables/useAcl';
import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue'; import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue';
import FetchData from 'components/FetchData.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 RightMenu from 'src/components/common/RightMenu.vue';
import axios from 'axios'; 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 router = useRouter();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const acl = useAcl();
const canSeeNotes = computed(() =>
acl.hasAny([{ model: 'Worker', props: '__get__business', accessType: 'READ' }]),
);
const workerIsFreelance = ref(); const workerIsFreelance = ref();
const WorkerFreelanceRef = ref(); const WorkerFreelanceRef = ref();
const workerCalendarFilterRef = ref(null); const workerCalendarFilterRef = ref(null);
@ -26,6 +34,10 @@ const contractHolidays = ref(null);
const yearHolidays = ref(null); const yearHolidays = ref(null);
const eventsMap = ref({}); const eventsMap = ref({});
const festiveEventsMap = ref({}); const festiveEventsMap = ref({});
const saveUrl = ref();
const body = {
workerFk: route.params.id,
};
const onFetchActiveContract = (data) => { const onFetchActiveContract = (data) => {
if (!data) return; if (!data) return;
@ -67,7 +79,7 @@ const onFetchAbsences = (data) => {
name: holidayName, name: holidayName,
isFestive: true, isFestive: true,
}, },
true true,
); );
}); });
} }
@ -146,7 +158,7 @@ watch(
async () => { async () => {
await nextTick(); await nextTick();
await activeContractRef.value.fetch(); await activeContractRef.value.fetch();
} },
); );
watch([year, businessFk], () => refreshData()); watch([year, businessFk], () => refreshData());
</script> </script>
@ -181,6 +193,20 @@ watch([year, businessFk], () => refreshData());
/> />
</template> </template>
</RightMenu> </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"> <QPage class="column items-center">
<QCard v-if="workerIsFreelance"> <QCard v-if="workerIsFreelance">
<QCardSection class="text-center"> <QCardSection class="text-center">

View File

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

View File

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

View File

@ -101,7 +101,7 @@ function reloadData() {
openConfirmationModal( openConfirmationModal(
t(`Remove PDA`), t(`Remove PDA`),
t('Do you want to remove this PDA?'), t('Do you want to remove this PDA?'),
() => deallocatePDA(row.deviceProductionFk) () => deallocatePDA(row.deviceProductionFk),
) )
" "
> >
@ -114,7 +114,13 @@ function reloadData() {
</template> </template>
</VnPaginate> </VnPaginate>
<QPageSticky :offset="[18, 18]"> <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"> <QDialog ref="dialog">
<FormModelPopup <FormModelPopup
:title="t('Add new device')" :title="t('Add new device')"

View File

@ -221,7 +221,7 @@ const deleteRelative = async (id) => {
color="primary" color="primary"
flat flat
icon="add" icon="add"
shortcut="+" v-shortcut="'+'"
style="flex: 0" style="flex: 0"
data-cy="addRelative" data-cy="addRelative"
/> />

View File

@ -12,7 +12,6 @@ import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
import { useAdvancedSummary } from 'src/composables/useAdvancedSummary'; import { useAdvancedSummary } from 'src/composables/useAdvancedSummary';
import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue';
import axios from 'axios';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -28,76 +27,6 @@ const entityId = computed(() => $props.id || route.params.id);
const basicDataUrl = ref(null); const basicDataUrl = ref(null);
const advancedSummary = ref(); const advancedSummary = ref();
const filter = {
include: [
{
relation: 'user',
scope: {
fields: ['name', 'nickname', 'roleFk'],
include: [
{
relation: 'role',
scope: {
fields: ['name'],
},
},
{
relation: 'emailUser',
scope: {
fields: ['email'],
},
},
],
},
},
{
relation: 'department',
scope: {
include: {
relation: 'department',
scope: {
fields: ['name'],
},
},
},
},
{
relation: 'boss',
},
{
relation: 'client',
},
{
relation: 'sip',
},
{
relation: 'business',
scope: {
include: [
{
relation: 'department',
scope: {
fields: ['id', 'name'],
},
},
{
relation: 'reasonEnd',
scope: {
fields: ['id', 'reason'],
},
},
{
relation: 'workerBusinessProfessionalCategory',
scope: {
fields: ['id', 'description'],
},
},
],
},
},
],
};
onBeforeMount(async () => { onBeforeMount(async () => {
advancedSummary.value = await useAdvancedSummary('Workers', entityId.value); advancedSummary.value = await useAdvancedSummary('Workers', entityId.value);
basicDataUrl.value = `#/worker/${entityId.value}/basic-data`; basicDataUrl.value = `#/worker/${entityId.value}/basic-data`;
@ -107,8 +36,8 @@ onBeforeMount(async () => {
<template> <template>
<CardSummary <CardSummary
ref="summary" ref="summary"
:url="`Workers/summary`" url="Workers/summary"
:user-filter="{ where: { id: entityId }, filter }" :user-filter="{ where: { id: entityId } }"
data-key="Worker" data-key="Worker"
> >
<template #header="{ entity }"> <template #header="{ entity }">

View File

@ -69,12 +69,12 @@ const acl = useAcl();
const selectedDateYear = computed(() => moment(selectedDate.value).isoWeekYear()); const selectedDateYear = computed(() => moment(selectedDate.value).isoWeekYear());
const worker = computed(() => arrayData.store?.data); const worker = computed(() => arrayData.store?.data);
const canSend = computed(() => const canSend = computed(() =>
acl.hasAny([{ model: 'WorkerTimeControl', props: 'sendMail', accessType: 'WRITE' }]) acl.hasAny([{ model: 'WorkerTimeControl', props: 'sendMail', accessType: 'WRITE' }]),
); );
const canUpdate = computed(() => const canUpdate = computed(() =>
acl.hasAny([ acl.hasAny([
{ model: 'WorkerTimeControl', props: 'updateMailState', accessType: 'WRITE' }, { model: 'WorkerTimeControl', props: 'updateMailState', accessType: 'WRITE' },
]) ]),
); );
const isHimself = computed(() => user.value.id === Number(route.params.id)); const isHimself = computed(() => user.value.id === Number(route.params.id));
@ -100,7 +100,7 @@ const getHeaderFormattedDate = (date) => {
}; };
const formattedWeekTotalHours = computed(() => const formattedWeekTotalHours = computed(() =>
secondsToHoursMinutes(weekTotalHours.value) secondsToHoursMinutes(weekTotalHours.value),
); );
const onInputChange = async (date) => { const onInputChange = async (date) => {
@ -320,7 +320,7 @@ const getFinishTime = () => {
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
let todayInWeek = weekDays.value.find( let todayInWeek = weekDays.value.find(
(day) => day.dated.getTime() === today.getTime() (day) => day.dated.getTime() === today.getTime(),
); );
if (todayInWeek && todayInWeek.hours && todayInWeek.hours.length) { if (todayInWeek && todayInWeek.hours && todayInWeek.hours.length) {
@ -472,7 +472,7 @@ onMounted(async () => {
openConfirmationModal( openConfirmationModal(
t('Send time control email'), t('Send time control email'),
t('Are you sure you want to send it?'), t('Are you sure you want to send it?'),
resendEmail resendEmail,
) )
" "
> >
@ -561,7 +561,7 @@ onMounted(async () => {
@show-worker-time-form=" @show-worker-time-form="
showWorkerTimeForm( showWorkerTimeForm(
{ id: hour.id, entryCode: hour.direction }, { id: hour.id, entryCode: hour.direction },
'edit' 'edit',
) )
" "
class="hour-chip" class="hour-chip"
@ -577,7 +577,7 @@ onMounted(async () => {
</span> </span>
<QBtn <QBtn
icon="add_circle" icon="add_circle"
shortcut="+" v-shortcut="'+'"
flat flat
color="primary" color="primary"
class="fill-icon cursor-pointer" class="fill-icon cursor-pointer"

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