#6919 syncData #941

Merged
jorgep merged 95 commits from 6919-syncData into dev 2025-02-06 09:39:49 +00:00
44 changed files with 496 additions and 551 deletions
Showing only changes of commit 0b7f2895a7 - Show all commits

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

@ -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
@ -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

@ -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

@ -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,
}); });

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

@ -378,7 +378,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 +457,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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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>

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();
@ -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

@ -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

@ -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

@ -44,6 +44,7 @@ const shelvingCard = {
path: ':id', path: ':id',
component: () => import('pages/Shelving/Card/ShelvingCard.vue'), component: () => import('pages/Shelving/Card/ShelvingCard.vue'),
redirect: { name: 'ShelvingSummary' }, redirect: { name: 'ShelvingSummary' },
meta: { menu: ['ShelvingBasicData', 'ShelvingLog'] },
children: [ children: [
{ {
name: 'ShelvingSummary', name: 'ShelvingSummary',
@ -81,13 +82,10 @@ export default {
title: 'shelving', title: 'shelving',
icon: 'vn:inventory', icon: 'vn:inventory',
moduleName: 'Shelving', moduleName: 'Shelving',
menu: ['ShelvingList', 'ParkingMain'],
}, },
component: RouterView, component: RouterView,
redirect: { name: 'ShelvingMain' }, redirect: { name: 'ShelvingMain' },
menus: {
main: ['ShelvingList', 'ParkingMain'],
card: ['ShelvingBasicData', 'ShelvingLog'],
},
children: [ children: [
{ {
path: '', path: '',

View File

@ -62,10 +62,9 @@ describe('Client list', () => {
it('Client founded create order', () => { it('Client founded create order', () => {
const search = 'Jessica Jones'; const search = 'Jessica Jones';
cy.searchByLabel('Name', search); cy.searchByLabel('Name', search);
cy.openActionDescriptor('New order'); cy.clickButtonWith('icon', 'icon-basketadd');
cy.waitForElement('#formModel'); cy.waitForElement('#formModel');
cy.waitForElement('.q-form'); cy.waitForElement('.q-form');
cy.checkValueForm(1, search); cy.checkValueForm(1, search);
cy.checkValueForm(2, search);
}); });
}); });

View File

@ -7,6 +7,5 @@ describe('Client notes', () => {
}); });
it('Should load layout', () => { it('Should load layout', () => {
cy.get('.q-page').should('be.visible'); cy.get('.q-page').should('be.visible');
cy.get('.q-page > :nth-child(2) > :nth-child(1)').should('be.visible');
}); });
}); });

View File

@ -8,12 +8,12 @@ describe('EntryMy when is supplier', () => {
}, },
}); });
}); });
// https://redmine.verdnatura.es/issues/8418
it.skip('should open buyLabel when is supplier', () => { it('should open buyLabel when is supplier', () => {
cy.get( cy.get(
'[to="/null/3"] > .q-card > .column > .q-btn > .q-btn__content > .q-icon' '[to="/null/3"] > .q-card > :nth-child(2) > .q-btn > .q-btn__content > .q-icon'
).click(); ).click();
cy.get('.q-card__actions > .q-btn').click(); cy.dataCy('printLabelsBtn').click();
cy.window().its('open').should('be.called'); cy.window().its('open').should('be.called');
}); });
}); });

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
describe('Item shelving', () => { describe('ItemBarcodes', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1920, 1080); cy.viewport(1920, 1080);
cy.login('developer'); cy.login('developer');

View File

@ -12,28 +12,22 @@ describe('Item tag', () => {
cy.get('.q-page-sticky > div').click(); cy.get('.q-page-sticky > div').click();
cy.dataCy('Tag_select').eq(7).type('Tallos'); cy.dataCy('Tag_select').eq(7).type('Tallos');
cy.get('.q-menu .q-item').contains('Tallos').click(); cy.get('.q-menu .q-item').contains('Tallos').click();
cy.get( cy.get(':nth-child(8) > [label="Value"]').type('1');
':nth-child(8) > [label="Value"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Value_input"]'
).type('1');
+cy.dataCy('crudModelDefaultSaveBtn').click(); +cy.dataCy('crudModelDefaultSaveBtn').click();
cy.checkNotification("The tag or priority can't be repeated for an item"); cy.checkNotification("The tag or priority can't be repeated for an item");
}); });
// https://redmine.verdnatura.es/issues/8422
it.skip('should add a new tag', () => { it('should add a new tag', () => {
cy.get('.q-page').should('be.visible'); cy.get('.q-page').should('be.visible');
cy.get('.q-page-sticky > div').click(); cy.get('.q-page-sticky > div').click();
cy.get('.q-page-sticky > div').click(); cy.get('.q-page-sticky > div').click();
cy.dataCy('Tag_select').eq(7).click(); cy.dataCy('Tag_select').eq(7).click();
cy.get('.q-menu .q-item').contains('Ancho de la base').click(); cy.get('.q-menu .q-item').contains('Ancho de la base').type('{enter}');
cy.get( cy.get(':nth-child(8) > [label="Value"]').type('50');
':nth-child(8) > [label="Value"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Value_input"]'
).type('50');
cy.dataCy('crudModelDefaultSaveBtn').click(); cy.dataCy('crudModelDefaultSaveBtn').click();
cy.checkNotification('Data saved'); cy.checkNotification('Data saved');
cy.get( cy.dataCy('itemTags').children(':nth-child(8)').find('.justify-center > .q-icon').click();
'[data-cy="itemTags"] > :nth-child(7) > .justify-center > .q-icon'
).click();
cy.dataCy('VnConfirm_confirm').click(); cy.dataCy('VnConfirm_confirm').click();
cy.checkNotification('Data saved'); cy.checkNotification('Data saved');
}); });
}); });

View File

@ -1,5 +1,10 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
describe('Item type', () => { describe('Item type', () => {
const workerError = 'employeeNick';
const worker = 'buyerNick';
const category = 'Artificial';
const type = 'Flower';
beforeEach(() => { beforeEach(() => {
cy.viewport(1920, 1080); cy.viewport(1920, 1080);
cy.login('developer'); cy.login('developer');
@ -8,32 +13,24 @@ describe('Item type', () => {
it('should throw an error if the code already exists', () => { it('should throw an error if the code already exists', () => {
cy.dataCy('vnTableCreateBtn').click(); cy.dataCy('vnTableCreateBtn').click();
cy.get( cy.dataCy('codeInput').type('ALS');
'div.fit > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Code_input"]' cy.dataCy('nameInput').type('Alstroemeria');
).type('ALS'); cy.dataCy('vnWorkerSelect').type(workerError);
cy.get( cy.get('.q-menu .q-item').contains(workerError).click();
'div.fit > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Name_input"]' cy.dataCy('itemCategorySelect').type(category);
).type('Alstroemeria'); cy.get('.q-menu .q-item').contains(category).click();
cy.dataCy('Worker_select').type('employeeNick');
cy.get('.q-menu .q-item').contains('employeeNick').click();
cy.dataCy('ItemCategory_select').type('Artificial');
cy.get('.q-menu .q-item').contains('Artificial').click();
cy.dataCy('FormModelPopup_save').click(); cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('An item type with the same code already exists'); cy.checkNotification('An item type with the same code already exists');
}); });
it('should create a new type', () => { it('should create a new type', () => {
cy.dataCy('vnTableCreateBtn').click(); cy.dataCy('vnTableCreateBtn').click();
cy.get( cy.dataCy('codeInput').type('LIL');
'div.fit > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Code_input"]' cy.dataCy('nameInput').type('Lilium');
).type('LIL'); cy.dataCy('vnWorkerSelect').type(worker);
cy.get( cy.get('.q-menu .q-item').contains(worker).click();
'div.fit > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Name_input"]' cy.dataCy('itemCategorySelect').type(type);
).type('Lilium'); cy.get('.q-menu .q-item').contains(type).click();
cy.dataCy('Worker_select').type('buyerNick');
cy.get('.q-menu .q-item').contains('buyerNick').click();
cy.dataCy('ItemCategory_select').type('Flower');
cy.get('.q-menu .q-item').contains('Flower').click();
cy.dataCy('FormModelPopup_save').click(); cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data created'); cy.checkNotification('Data created');
}); });

View File

@ -53,4 +53,29 @@ describe('TicketList', () => {
cy.checkNotification('Data created'); cy.checkNotification('Data created');
cy.url().should('match', /\/ticket\/\d+\/summary/); cy.url().should('match', /\/ticket\/\d+\/summary/);
}); });
it('should show the corerct problems', () => {
cy.intercept('GET', '**/api/Tickets/filter*', (req) => {
req.headers['cache-control'] = 'no-cache';
req.headers['pragma'] = 'no-cache';
req.headers['expires'] = '0';
req.on('response', (res) => {
delete res.headers['if-none-match'];
delete res.headers['if-modified-since'];
});
}).as('ticket');
cy.get('[data-cy="Warehouse_select"]').type('Warehouse Five');
cy.get('.q-menu .q-item').contains('Warehouse Five').click();
cy.wait('@ticket').then((interception) => {
const data = interception.response.body[1];
expect(data.hasComponentLack).to.equal(1);
expect(data.isTooLittle).to.equal(1);
expect(data.hasItemShortage).to.equal(1);
});
cy.get('.icon-components').should('exist');
cy.get('.icon-unavailable').should('exist');
cy.get('.icon-isTooLittle').should('exist');
});
}); });

View File

@ -1,7 +1,6 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
describe('VnSearchBar', () => { describe('VnSearchBar', () => {
const employeeId = ' #1'; const employeeId = ' #1';
const salesPersonId = ' #18';
const idGap = '.q-item > .q-item__label'; const idGap = '.q-item > .q-item__label';
const vnTableRow = '.q-virtual-scroll__content'; const vnTableRow = '.q-virtual-scroll__content';
beforeEach(() => { beforeEach(() => {
@ -12,11 +11,10 @@ describe('VnSearchBar', () => {
it('should redirect to account summary page', () => { it('should redirect to account summary page', () => {
searchAndCheck('1', employeeId); searchAndCheck('1', employeeId);
searchAndCheck('salesPerson', salesPersonId); searchAndCheck('employee', employeeId);
}); });
it('should stay on the list page if there are several results or none', () => { it('should stay on the list page if there are several results or none', () => {
cy.typeSearchbar('salesA{enter}');
cy.typeSearchbar('salesA{enter}'); cy.typeSearchbar('salesA{enter}');
checkTableLength(2); checkTableLength(2);
@ -29,7 +27,6 @@ describe('VnSearchBar', () => {
const searchAndCheck = (searchTerm, expectedText) => { const searchAndCheck = (searchTerm, expectedText) => {
cy.clearSearchbar(); cy.clearSearchbar();
cy.typeSearchbar(`${searchTerm}{enter}`); cy.typeSearchbar(`${searchTerm}{enter}`);
cy.typeSearchbar(`${searchTerm}{enter}`);
cy.get(idGap).should('have.text', expectedText); cy.get(idGap).should('have.text', expectedText);
}; };

View File

@ -8,16 +8,16 @@ describe('WagonCreate', () => {
it('should create and delete a new wagon', () => { it('should create and delete a new wagon', () => {
cy.dataCy('vnTableCreateBtn').click(); cy.dataCy('vnTableCreateBtn').click();
cy.get( cy.get(
'.grid-create > [label="Label"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Label_input"]' '.grid-create > [label="Label"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Label_input"]',
).type('1234'); ).type('1234');
cy.get( cy.get(
'.grid-create > [label="Plate"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Plate_input"]' '.grid-create > [label="Plate"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Plate_input"]',
).type('1234ABCD'); ).type('1234ABCD');
cy.get( cy.get(
'.grid-create > [label="Volume"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Volume_input"]' '.grid-create > [label="Volume"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Volume_input"]',
).type('100'); ).type('100');
cy.dataCy('Type_select').type('{downarrow}{enter}'); cy.dataCy('Type_select').type('{downarrow}{enter}');
// // Delete wagon type created
cy.get('[to="/null/1"] > .q-card > .column > [title="Remove"]').click(); cy.get('[title="Remove"] > .q-btn__content > .q-icon').click();
}); });
}); });

View File

@ -6,14 +6,10 @@ describe('WagonTypeCreate', () => {
cy.waitForElement('.q-page', 6000); cy.waitForElement('.q-page', 6000);
}); });
it('should create a new wagon type', () => { it('should create a new wagon type and then delete it', () => {
cy.get('.q-page-sticky > div > .q-btn').click(); cy.get('.q-page-sticky > div > .q-btn').click();
cy.get('input').first().type('Example for testing'); cy.get('input').first().type('Example for testing');
cy.get('button[type="submit"]').click(); cy.get('button[type="submit"]').click();
}); cy.get('[title="Remove"] > .q-btn__content > .q-icon').first().click();
it('delete a wagon type', () => {
cy.get(
'[to="/null/2"] > .q-card > .column > [title="Remove"] > .q-btn__content > .q-icon'
).click();
}); });
}); });

View File

@ -289,40 +289,13 @@ Cypress.Commands.add('openActionDescriptor', (opt) => {
cy.openActionsDescriptor(); cy.openActionsDescriptor();
const listItem = '[role="menu"] .q-list .q-item'; const listItem = '[role="menu"] .q-list .q-item';
cy.contains(listItem, opt).click(); cy.contains(listItem, opt).click();
1;
}); });
Cypress.Commands.add('openActionsDescriptor', () => { Cypress.Commands.add('openActionsDescriptor', () => {
cy.get('[data-cy="descriptor-more-opts"]').click(); cy.get('[data-cy="descriptor-more-opts"]').click();
}); });
Cypress.Commands.add('clickButtonsDescriptor', (id) => { Cypress.Commands.add('clickButtonDescriptor', (id) => {
cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`)
.invoke('removeAttr', 'target')
.click();
});
Cypress.Commands.add('openActionDescriptor', (opt) => {
cy.openActionsDescriptor();
const listItem = '[role="menu"] .q-list .q-item';
cy.contains(listItem, opt).click();
1;
});
Cypress.Commands.add('clickButtonsDescriptor', (id) => {
cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`)
.invoke('removeAttr', 'target')
.click();
});
Cypress.Commands.add('openActionDescriptor', (opt) => {
cy.openActionsDescriptor();
const listItem = '[role="menu"] .q-list .q-item';
cy.contains(listItem, opt).click();
1;
});
Cypress.Commands.add('clickButtonsDescriptor', (id) => {
cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`) cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`)
.invoke('removeAttr', 'target') .invoke('removeAttr', 'target')
.click(); .click();
@ -374,3 +347,21 @@ Cypress.Commands.add('addBtnClick', () => {
.and('be.visible') .and('be.visible')
.click(); .click();
}); });
Cypress.Commands.add('clickButtonWith', (type, value) => {
switch (type) {
case 'icon':
cy.clickButtonWithIcon(value);
break;
default:
cy.clickButtonWithText(value);
break;
}
});
Cypress.Commands.add('clickButtonWithIcon', (iconClass) => {
cy.get(`.q-icon.${iconClass}`).parent().click();
});
Cypress.Commands.add('clickButtonWithText', (buttonText) => {
cy.get('.q-btn').contains(buttonText).click();
});