forked from verdnatura/salix-front
Merge branch 'dev' into 6899_InvoiceOut_End
This commit is contained in:
commit
06f8121ede
|
@ -1,6 +1,6 @@
|
|||
import { defineConfig } from 'cypress';
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
export default defineConfig({
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:9000/',
|
||||
experimentalStudio: true,
|
||||
|
|
10
package.json
10
package.json
|
@ -1,12 +1,11 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.28.1",
|
||||
"version": "24.30.1",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.15.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue ./",
|
||||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
|
@ -23,7 +22,7 @@
|
|||
"chromium": "^3.0.3",
|
||||
"croppie": "^2.6.5",
|
||||
"pinia": "^2.1.3",
|
||||
"quasar": "^2.16.4",
|
||||
"quasar": "^2.14.5",
|
||||
"validator": "^13.9.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
|
@ -32,7 +31,7 @@
|
|||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@quasar/app-vite": "^1.9.3",
|
||||
"@quasar/app-vite": "^1.7.3",
|
||||
"@quasar/quasar-app-extension-qcalendar": "4.0.0-beta.15",
|
||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
|
@ -44,8 +43,7 @@
|
|||
"eslint-plugin-vue": "^9.14.1",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier": "^2.8.8",
|
||||
"vite": "^2.5.10",
|
||||
"vitest": "^0.34.0"
|
||||
"vitest": "^0.31.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20 || ^18 || ^16",
|
||||
|
|
14161
pnpm-lock.yaml
14161
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,104 +0,0 @@
|
|||
/* eslint-env node */
|
||||
|
||||
/*
|
||||
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
|
||||
* the ES6 features that are supported by your Node version. https://node.green/
|
||||
*/
|
||||
|
||||
const { configure } = require('quasar/wrappers');
|
||||
const VueI18nPlugin = require('@intlify/unplugin-vue-i18n/vite');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = configure(function (/* ctx */) {
|
||||
return {
|
||||
eslint: {
|
||||
warnings: true,
|
||||
errors: true,
|
||||
},
|
||||
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
|
||||
css: ['app.scss'],
|
||||
extras: ['roboto-font', 'material-icons-outlined', 'material-symbols-outlined'],
|
||||
build: {
|
||||
target: {
|
||||
browser: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
||||
node: 'node18',
|
||||
},
|
||||
vueRouterMode: 'hash',
|
||||
rawDefine: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
},
|
||||
extendViteConf(viteConf) {
|
||||
delete viteConf.build.polyfillModulePreload;
|
||||
viteConf.build.modulePreload = {
|
||||
polyfill: false,
|
||||
};
|
||||
},
|
||||
alias: {
|
||||
composables: path.join(__dirname, './src/composables'),
|
||||
filters: path.join(__dirname, './src/filters'),
|
||||
},
|
||||
vitePlugins: [
|
||||
[
|
||||
VueI18nPlugin({
|
||||
runtimeOnly: false,
|
||||
include: [
|
||||
path.resolve(__dirname, './src/i18n/locale/**'),
|
||||
path.resolve(__dirname, './src/pages/**/locale/**'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
server: {
|
||||
type: 'http',
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://0.0.0.0:3000',
|
||||
logLevel: 'debug',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
open: false,
|
||||
},
|
||||
framework: {
|
||||
config: {
|
||||
dark: 'auto',
|
||||
},
|
||||
lang: 'en-GB',
|
||||
plugins: ['Notify', 'Dialog'],
|
||||
all: 'auto',
|
||||
autoImportComponentCase: 'pascal',
|
||||
},
|
||||
animations: [],
|
||||
ssr: {
|
||||
pwa: false,
|
||||
prodPort: 3000,
|
||||
middlewares: ['render'],
|
||||
},
|
||||
pwa: {
|
||||
workboxMode: 'generateSW',
|
||||
injectPwaMetaTags: true,
|
||||
swFilename: 'sw.js',
|
||||
manifestFilename: 'manifest.json',
|
||||
useCredentialsForManifestTag: false,
|
||||
},
|
||||
cordova: {},
|
||||
capacitor: {
|
||||
hideSplashscreen: true,
|
||||
},
|
||||
electron: {
|
||||
inspectPort: 5858,
|
||||
bundler: 'packager',
|
||||
packager: {},
|
||||
builder: {
|
||||
appId: 'salix-frontend',
|
||||
},
|
||||
},
|
||||
bex: {
|
||||
contentScripts: ['my-content-script'],
|
||||
},
|
||||
};
|
||||
});
|
|
@ -0,0 +1,242 @@
|
|||
/* eslint-env node */
|
||||
|
||||
/*
|
||||
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
|
||||
* the ES6 features that are supported by your Node version. https://node.green/
|
||||
*/
|
||||
|
||||
// Configuration for your app
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
||||
|
||||
const { configure } = require('quasar/wrappers');
|
||||
const VueI18nPlugin = require('@intlify/unplugin-vue-i18n/vite');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = configure(function (/* ctx */) {
|
||||
return {
|
||||
eslint: {
|
||||
// fix: true,
|
||||
// include = [],
|
||||
// exclude = [],
|
||||
// rawOptions = {},
|
||||
warnings: true,
|
||||
errors: true,
|
||||
},
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli/prefetch-feature
|
||||
// preFetch: true,
|
||||
|
||||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli/boot-files
|
||||
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
css: ['app.scss'],
|
||||
|
||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||
extras: [
|
||||
// 'ionicons-v4',
|
||||
// 'mdi-v5',
|
||||
// 'fontawesome-v6',
|
||||
// 'eva-icons',
|
||||
// 'themify',
|
||||
// 'line-awesome',
|
||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||
|
||||
'roboto-font',
|
||||
'material-icons-outlined',
|
||||
'material-symbols-outlined',
|
||||
],
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||
build: {
|
||||
target: {
|
||||
browser: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
||||
node: 'node18',
|
||||
},
|
||||
|
||||
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||
// vueRouterBase,
|
||||
// vueDevtools,
|
||||
// vueOptionsAPI: false,
|
||||
|
||||
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
|
||||
|
||||
// publicPath: '/',
|
||||
// analyze: true,
|
||||
// env: {},
|
||||
rawDefine: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
},
|
||||
// ignorePublicFolder: true,
|
||||
// minify: false,
|
||||
// polyfillModulePreload: true,
|
||||
// distDir
|
||||
|
||||
extendViteConf(viteConf) {
|
||||
// FIXME: Delete deprecated property polyfillModulePreload
|
||||
// that is set by Quasar by default
|
||||
delete viteConf.build.polyfillModulePreload;
|
||||
viteConf.build.modulePreload = {
|
||||
polyfill: false,
|
||||
};
|
||||
},
|
||||
// viteVuePluginOptions: {},
|
||||
|
||||
alias: {
|
||||
composables: path.join(__dirname, './src/composables'),
|
||||
filters: path.join(__dirname, './src/filters'),
|
||||
},
|
||||
|
||||
vitePlugins: [
|
||||
[
|
||||
VueI18nPlugin({
|
||||
runtimeOnly: false,
|
||||
include: [
|
||||
path.resolve(__dirname, './src/i18n/locale/**'),
|
||||
path.resolve(__dirname, './src/pages/**/locale/**'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
],
|
||||
},
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||
devServer: {
|
||||
server: {
|
||||
type: 'http',
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://0.0.0.0:3000',
|
||||
logLevel: 'debug',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
open: false,
|
||||
},
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
||||
framework: {
|
||||
config: {
|
||||
config: {
|
||||
dark: 'auto',
|
||||
},
|
||||
},
|
||||
lang: 'en-GB',
|
||||
|
||||
// iconSet: 'material-icons', // Quasar icon set
|
||||
// lang: 'en-US', // Quasar language pack
|
||||
|
||||
// For special cases outside of where the auto-import strategy can have an impact
|
||||
// (like functional components as one of the examples),
|
||||
// you can manually specify Quasar components/directives to be available everywhere:
|
||||
//
|
||||
// components: [],
|
||||
// directives: [],
|
||||
|
||||
// Quasar plugins
|
||||
plugins: ['Notify', 'Dialog'],
|
||||
all: 'auto',
|
||||
autoImportComponentCase: 'pascal',
|
||||
},
|
||||
|
||||
// animations: 'all', // --- includes all animations
|
||||
// https://v2.quasar.dev/options/animations
|
||||
animations: [],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
|
||||
// sourceFiles: {
|
||||
// rootComponent: 'src/App.vue',
|
||||
// router: 'src/router/index',
|
||||
// store: 'src/store/index',
|
||||
// registerServiceWorker: 'src-pwa/register-service-worker',
|
||||
// serviceWorker: 'src-pwa/custom-service-worker',
|
||||
// pwaManifestFile: 'src-pwa/manifest.json',
|
||||
// electronMain: 'src-electron/electron-main',
|
||||
// electronPreload: 'src-electron/electron-preload'
|
||||
// },
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr
|
||||
ssr: {
|
||||
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
|
||||
// will mess up SSR
|
||||
|
||||
// extendSSRWebserverConf (esbuildConf) {},
|
||||
// extendPackageJson (json) {},
|
||||
|
||||
pwa: false,
|
||||
|
||||
// manualStoreHydration: true,
|
||||
// manualPostHydrationTrigger: true,
|
||||
|
||||
prodPort: 3000, // The default port that the production server should use
|
||||
// (gets superseded if process.env.PORT is specified at runtime)
|
||||
|
||||
middlewares: [
|
||||
'render', // keep this as last one
|
||||
],
|
||||
},
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
|
||||
pwa: {
|
||||
workboxMode: 'generateSW', // or 'injectManifest'
|
||||
injectPwaMetaTags: true,
|
||||
swFilename: 'sw.js',
|
||||
manifestFilename: 'manifest.json',
|
||||
useCredentialsForManifestTag: false,
|
||||
// useFilenameHashes: true,
|
||||
// extendGenerateSWOptions (cfg) {}
|
||||
// extendInjectManifestOptions (cfg) {},
|
||||
// extendManifestJson (json) {}
|
||||
// extendPWACustomSWConf (esbuildConf) {}
|
||||
},
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
|
||||
cordova: {
|
||||
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
|
||||
},
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
|
||||
capacitor: {
|
||||
hideSplashscreen: true,
|
||||
},
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
|
||||
electron: {
|
||||
// extendElectronMainConf (esbuildConf)
|
||||
// extendElectronPreloadConf (esbuildConf)
|
||||
|
||||
inspectPort: 5858,
|
||||
|
||||
bundler: 'packager', // 'packager' or 'builder'
|
||||
|
||||
packager: {
|
||||
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
|
||||
// OS X / Mac App Store
|
||||
// appBundleId: '',
|
||||
// appCategoryType: '',
|
||||
// osxSign: '',
|
||||
// protocol: 'myapp://path',
|
||||
// Windows only
|
||||
// win32metadata: { ... }
|
||||
},
|
||||
|
||||
builder: {
|
||||
// https://www.electron.build/configuration/configuration
|
||||
|
||||
appId: 'salix-frontend',
|
||||
},
|
||||
},
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
||||
bex: {
|
||||
contentScripts: ['my-content-script'],
|
||||
|
||||
// extendBexScriptsConf (esbuildConf) {}
|
||||
// extendBexManifestJson (json) {}
|
||||
},
|
||||
};
|
||||
});
|
|
@ -262,7 +262,7 @@ defineExpose({
|
|||
<template>
|
||||
<div class="column items-center full-width">
|
||||
<QForm
|
||||
v-if="formData"
|
||||
|
||||
@submit="save"
|
||||
@reset="reset"
|
||||
class="q-pa-md"
|
||||
|
@ -270,11 +270,13 @@ defineExpose({
|
|||
>
|
||||
<QCard>
|
||||
<slot
|
||||
v-if="formData"
|
||||
name="form"
|
||||
:data="formData"
|
||||
:validate="validate"
|
||||
:filter="filter"
|
||||
/>
|
||||
<SkeletonForm v-else/>
|
||||
</QCard>
|
||||
</QForm>
|
||||
</div>
|
||||
|
@ -337,7 +339,7 @@ defineExpose({
|
|||
</QBtnGroup>
|
||||
</div>
|
||||
</Teleport>
|
||||
<SkeletonForm v-if="!formData" />
|
||||
|
||||
<QInnerLoading
|
||||
:showing="isLoading"
|
||||
:label="t('globals.pleaseWait')"
|
||||
|
|
|
@ -179,14 +179,14 @@ function columnName(col) {
|
|||
}
|
||||
|
||||
function getColAlign(col) {
|
||||
return 'text-' + (col.align ?? 'left')
|
||||
return 'text-' + (col.align ?? 'left');
|
||||
}
|
||||
|
||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||
defineExpose({
|
||||
reload,
|
||||
redirect: redirectFn,
|
||||
selected
|
||||
selected,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
|
@ -234,7 +234,11 @@ defineExpose({
|
|||
@save-changes="reload"
|
||||
:has-subtoolbar="$attrs['hasSubtoolbar'] ?? isEditable"
|
||||
>
|
||||
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
|
||||
<template
|
||||
v-for="(_, slotName) in $slots"
|
||||
#[slotName]="slotData"
|
||||
:key="slotName"
|
||||
>
|
||||
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
|
||||
</template>
|
||||
<template #body="{ rows }">
|
||||
|
@ -256,7 +260,7 @@ defineExpose({
|
|||
CrudModelRef.vnPaginateRef.paginate()
|
||||
"
|
||||
@row-click="(_, row) => rowClickFunction(row)"
|
||||
@update:selected="$emit('update:selected', $event)"
|
||||
@update:selected="emit('update:selected', $event)"
|
||||
>
|
||||
<template #top-left>
|
||||
<slot name="top-left"></slot>
|
||||
|
@ -321,13 +325,15 @@ defineExpose({
|
|||
class="no-margin q-px-xs"
|
||||
:class="getColAlign(col)"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
:is-editable="false"
|
||||
v-model="row[col.name]"
|
||||
component-prop="columnField"
|
||||
/>
|
||||
<slot :name="`column-${col.name}`" :col="col" :row="row">
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
:is-editable="false"
|
||||
v-model="row[col.name]"
|
||||
component-prop="columnField"
|
||||
/>
|
||||
</slot>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-tableActions="{ col, row }">
|
||||
|
@ -423,14 +429,20 @@ defineExpose({
|
|||
stopEventPropagation($event)
|
||||
"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
<slot
|
||||
:name="`column-${col.name}`"
|
||||
:col="col"
|
||||
:row="row"
|
||||
:is-editable="false"
|
||||
v-model="row[col.name]"
|
||||
component-prop="columnField"
|
||||
:show-label="true"
|
||||
/>
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
:is-editable="false"
|
||||
v-model="row[col.name]"
|
||||
component-prop="columnField"
|
||||
:show-label="true"
|
||||
/>
|
||||
</slot>
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
|
|
|
@ -93,7 +93,12 @@ const inputRules = [
|
|||
name="close"
|
||||
size="xs"
|
||||
v-if="hover && value && !$attrs.disabled && $props.clearable"
|
||||
@click="value = null"
|
||||
@click="
|
||||
() => {
|
||||
value = null;
|
||||
emit('remove');
|
||||
}
|
||||
"
|
||||
></QIcon>
|
||||
<QIcon v-if="info" name="info">
|
||||
<QTooltip max-width="350px">
|
||||
|
|
|
@ -61,6 +61,10 @@ const $props = defineProps({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useLike: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -114,11 +118,14 @@ async function fetchFilter(val) {
|
|||
if (!$props.url || !dataRef.value) return;
|
||||
|
||||
const { fields, sortBy, limit } = $props;
|
||||
let key = optionLabel.value;
|
||||
let key = optionFilter.value ?? optionLabel.value;
|
||||
|
||||
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
|
||||
if (new RegExp(/\d/g).test(val)) key = optionValue.value;
|
||||
|
||||
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
|
||||
const defaultWhere = $props.useLike
|
||||
? { [key]: { like: `%${val}%` } }
|
||||
: { [key]: val };
|
||||
const where = { ...defaultWhere, ...$props.where };
|
||||
const fetchOptions = { where, order: sortBy, limit };
|
||||
if (fields) fetchOptions.fields = fields;
|
||||
return dataRef.value.fetch(fetchOptions);
|
||||
|
|
|
@ -187,15 +187,10 @@ function existSummary(routes) {
|
|||
color: lighten($primary, 20%);
|
||||
}
|
||||
.q-checkbox {
|
||||
display: flex;
|
||||
margin-bottom: 9px;
|
||||
& .q-checkbox__label {
|
||||
margin-left: 31px;
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
& .q-checkbox__inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--vn-label-color);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
<template>
|
||||
<div class="q-pa-md">
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md">
|
||||
<QSkeleton type="QBtn" />
|
||||
<QSkeleton type="QBtn" />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<QSkeleton type="QInput" class="col" square />
|
||||
<QSkeleton type="QInput" class="col" square />
|
||||
</div>
|
||||
</template>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<QSkeleton type="QInput" class="col" square />
|
||||
<QSkeleton type="QInput" class="col" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<QSkeleton type="QInput" class="col" square />
|
||||
<QSkeleton type="QInput" class="col" square />
|
||||
</div>
|
||||
</template>
|
|
@ -7,8 +7,11 @@ import toDate from 'filters/toDate';
|
|||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const params = defineModel({ default: {}, required: true, type: Object });
|
||||
const $props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -55,7 +58,14 @@ const $props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'refresh',
|
||||
'clear',
|
||||
'search',
|
||||
'init',
|
||||
'remove',
|
||||
]);
|
||||
|
||||
const arrayData = useArrayData($props.dataKey, {
|
||||
exprBuilder: $props.exprBuilder,
|
||||
|
@ -64,9 +74,10 @@ const arrayData = useArrayData($props.dataKey, {
|
|||
});
|
||||
const route = useRoute();
|
||||
const store = arrayData.store;
|
||||
|
||||
const userParams = ref({});
|
||||
onMounted(() => {
|
||||
emit('init', { params: params.value });
|
||||
userParams.value = $props.modelValue ?? {};
|
||||
emit('init', { params: userParams.value });
|
||||
});
|
||||
|
||||
function setUserParams(watchedParams) {
|
||||
|
@ -75,7 +86,7 @@ function setUserParams(watchedParams) {
|
|||
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||
watchedParams = { ...watchedParams, ...watchedParams.filter?.where };
|
||||
delete watchedParams.filter;
|
||||
params.value = { ...params.value, ...watchedParams };
|
||||
userParams.value = { ...userParams.value, ...watchedParams };
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@ -88,18 +99,23 @@ watch(
|
|||
(val) => setUserParams(val)
|
||||
);
|
||||
|
||||
watch(
|
||||
() => $props.modelValue,
|
||||
(val) => (userParams.value = val ?? {})
|
||||
);
|
||||
|
||||
const isLoading = ref(false);
|
||||
async function search(evt) {
|
||||
if (evt && $props.disableSubmitEvent) return;
|
||||
|
||||
store.filter.where = {};
|
||||
isLoading.value = true;
|
||||
const filter = { ...params.value };
|
||||
const filter = { ...userParams.value };
|
||||
store.userParamsChanged = true;
|
||||
store.filter.skip = 0;
|
||||
store.skip = 0;
|
||||
const { params: newParams } = await arrayData.addFilter({ params: params.value });
|
||||
params.value = newParams;
|
||||
const { params: newParams } = await arrayData.addFilter({ params: userParams.value });
|
||||
userParams.value = newParams;
|
||||
|
||||
if (!$props.showAll && !Object.values(filter).length) store.data = [];
|
||||
|
||||
|
@ -109,7 +125,7 @@ async function search(evt) {
|
|||
|
||||
async function reload() {
|
||||
isLoading.value = true;
|
||||
const params = Object.values(params.value).filter((param) => param);
|
||||
const params = Object.values(userParams.value).filter((param) => param);
|
||||
|
||||
await arrayData.fetch({ append: false });
|
||||
if (!$props.showAll && !params.length) store.data = [];
|
||||
|
@ -123,17 +139,17 @@ async function clearFilters() {
|
|||
store.filter.skip = 0;
|
||||
store.skip = 0;
|
||||
// Filtrar los params no removibles
|
||||
const removableFilters = Object.keys(params.value).filter((param) =>
|
||||
const removableFilters = Object.keys(userParams.value).filter((param) =>
|
||||
$props.unremovableParams.includes(param)
|
||||
);
|
||||
const newParams = {};
|
||||
// Conservar solo los params que no son removibles
|
||||
for (const key of removableFilters) {
|
||||
newParams[key] = params.value[key];
|
||||
newParams[key] = userParams.value[key];
|
||||
}
|
||||
params.value = {};
|
||||
params.value = { ...newParams }; // Actualizar los params con los removibles
|
||||
await arrayData.applyFilter({ params: params.value });
|
||||
userParams.value = {};
|
||||
userParams.value = { ...newParams }; // Actualizar los params con los removibles
|
||||
await arrayData.applyFilter({ params: userParams.value });
|
||||
|
||||
if (!$props.showAll) {
|
||||
store.data = [];
|
||||
|
@ -141,12 +157,13 @@ async function clearFilters() {
|
|||
|
||||
isLoading.value = false;
|
||||
emit('clear');
|
||||
emit('update:modelValue', userParams.value);
|
||||
}
|
||||
|
||||
const tagsList = computed(() => {
|
||||
const tagList = [];
|
||||
for (const key of Object.keys(params.value)) {
|
||||
const value = params.value[key];
|
||||
for (const key of Object.keys(userParams.value)) {
|
||||
const value = userParams.value[key];
|
||||
if (value == null || ($props.hiddenTags || []).includes(key)) continue;
|
||||
tagList.push({ label: key, value });
|
||||
}
|
||||
|
@ -161,9 +178,10 @@ const customTags = computed(() =>
|
|||
);
|
||||
|
||||
async function remove(key) {
|
||||
params.value[key] = undefined;
|
||||
userParams.value[key] = undefined;
|
||||
search();
|
||||
emit('remove', key);
|
||||
emit('update:modelValue', userParams.value);
|
||||
}
|
||||
|
||||
function formatValue(value) {
|
||||
|
@ -236,7 +254,7 @@ function formatValue(value) {
|
|||
<slot
|
||||
v-if="$slots.customTags"
|
||||
name="customTags"
|
||||
:params="params"
|
||||
:params="userParams"
|
||||
:tags="customTags"
|
||||
:format-fn="formatValue"
|
||||
:search-fn="search"
|
||||
|
@ -246,7 +264,7 @@ function formatValue(value) {
|
|||
<QSeparator />
|
||||
</QList>
|
||||
<QList dense class="list q-gutter-y-sm q-mt-sm">
|
||||
<slot name="body" :params="params" :search-fn="search"></slot>
|
||||
<slot name="body" :params="userParams" :search-fn="search"></slot>
|
||||
</QList>
|
||||
<template v-if="$props.searchButton">
|
||||
<QItem>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { dashIfEmpty } from 'src/filters';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClipboard } from 'src/composables/useClipboard';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const $props = defineProps({
|
||||
label: { type: String, default: null },
|
||||
|
@ -24,52 +25,67 @@ function copyValueText() {
|
|||
},
|
||||
});
|
||||
}
|
||||
const val = computed(() => $props.value);
|
||||
</script>
|
||||
<style scoped>
|
||||
.label,
|
||||
.value {
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="vn-label-value">
|
||||
<div v-if="$props.label || $slots.label" class="label">
|
||||
<slot name="label">
|
||||
<span>{{ $props.label }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="value">
|
||||
<slot name="value">
|
||||
<span :title="$props.value">
|
||||
{{ $props.dash ? dashIfEmpty($props.value) : $props.value }}
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="info" v-if="$props.info">
|
||||
<QIcon name="info" class="cursor-pointer" size="xs" color="grey">
|
||||
<QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]">
|
||||
{{ $props.info }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
<div class="copy" v-if="$props.copy && $props.value" @click="copyValueText()">
|
||||
<QIcon name="Content_Copy" color="primary">
|
||||
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
<QCheckbox
|
||||
v-if="typeof value === 'boolean'"
|
||||
v-model="val"
|
||||
:label="label"
|
||||
disable
|
||||
dense
|
||||
/>
|
||||
<template v-else>
|
||||
<div v-if="label || $slots.label" class="label">
|
||||
<slot name="label">
|
||||
<span>{{ label }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="value">
|
||||
<slot name="value">
|
||||
<span :title="value">
|
||||
{{ dash ? dashIfEmpty(value) : value }}
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="info" v-if="info">
|
||||
<QIcon name="info" class="cursor-pointer" size="xs" color="grey">
|
||||
<QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]">
|
||||
{{ info }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
<div class="copy" v-if="copy && value" @click="copyValueText()">
|
||||
<QIcon name="Content_Copy" color="primary">
|
||||
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vn-label-value:hover .copy {
|
||||
visibility: visible;
|
||||
cursor: pointer;
|
||||
.vn-label-value {
|
||||
&:hover .copy {
|
||||
visibility: visible;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.label,
|
||||
.value {
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.copy {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
.copy {
|
||||
visibility: hidden;
|
||||
}
|
||||
.info {
|
||||
margin-left: 5px;
|
||||
|
||||
:deep(.q-checkbox.disabled) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,7 +29,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
const filter = params?.filter;
|
||||
delete params.filter;
|
||||
store.userParams = { ...params, ...store.userParams };
|
||||
store.userFilter = { ...JSON.parse(filter), ...store.userFilter };
|
||||
store.userFilter = { ...JSON.parse(filter ?? '{}'), ...store.userFilter };
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -444,10 +444,14 @@ ticket:
|
|||
sms: Sms
|
||||
notes: Notes
|
||||
sale: Sale
|
||||
volume: Volume
|
||||
observation: Notes
|
||||
ticketAdvance: Advance tickets
|
||||
futureTickets: Future tickets
|
||||
purchaseRequest: Purchase request
|
||||
weeklyTickets: Weekly tickets
|
||||
services: Service
|
||||
tracking: Tracking
|
||||
list:
|
||||
nickname: Nickname
|
||||
state: State
|
||||
|
|
|
@ -443,10 +443,14 @@ ticket:
|
|||
sms: Sms
|
||||
notes: Notas
|
||||
sale: Lineas del pedido
|
||||
volume: Volumen
|
||||
observation: Notas
|
||||
ticketAdvance: Adelantar tickets
|
||||
futureTickets: Tickets a futuro
|
||||
purchaseRequest: Petición de compra
|
||||
weeklyTickets: Tickets programados
|
||||
services: Servicios
|
||||
tracking: Estados
|
||||
list:
|
||||
nickname: Alias
|
||||
state: Estado
|
||||
|
|
|
@ -15,6 +15,10 @@ const $props = defineProps({
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
summary: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
@ -60,14 +64,14 @@ const removeRole = () => {
|
|||
|
||||
<template>
|
||||
<CardDescriptor
|
||||
ref="descriptor"
|
||||
:url="`VnRoles`"
|
||||
:url="`VnRoles/${entityId}`"
|
||||
:filter="filter"
|
||||
module="Role"
|
||||
@on-fetch="setData"
|
||||
data-key="accountData"
|
||||
:title="data.title"
|
||||
:subtitle="data.subtitle"
|
||||
:summary="$props.summary"
|
||||
>
|
||||
<template #menu>
|
||||
<QItem v-ripple clickable @click="removeRole()">
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<script setup>
|
||||
import RoleDescriptor from './RoleDescriptor.vue';
|
||||
import RoleSummary from './RoleSummary.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QPopupProxy>
|
||||
<RoleDescriptor v-if="$props.id" :id="$props.id" :summary="RoleSummary" />
|
||||
</QPopupProxy>
|
||||
</template>
|
|
@ -30,6 +30,7 @@ const filter = {
|
|||
:url="`VnRoles`"
|
||||
:filter="filter"
|
||||
@on-fetch="(data) => (role = data)"
|
||||
data-key="RoleSummary"
|
||||
>
|
||||
<template #header> {{ role.id }} - {{ role.name }} </template>
|
||||
<template #body>
|
||||
|
|
|
@ -65,6 +65,8 @@ const columns = computed(() => [
|
|||
url: 'Workers/activeWithInheritedRole',
|
||||
fields: ['id', 'name'],
|
||||
where: { role: 'salesPerson' },
|
||||
optionFilter: 'firstName',
|
||||
useLike: false,
|
||||
},
|
||||
create: true,
|
||||
columnField: {
|
||||
|
|
|
@ -156,22 +156,18 @@ const onOrderFieldChange = (value, params) => {
|
|||
case 'Relevancy':
|
||||
tagObj.field = value + ' DESC, name';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
case 'ColorAndPrice':
|
||||
tagObj.field = 'showOrder, price';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
case 'Name':
|
||||
tagObj.field = 'name';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
case 'Price':
|
||||
tagObj.field = 'price';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -122,8 +122,6 @@ const orderFilter = {
|
|||
const onClientChange = async (clientId) => {
|
||||
try {
|
||||
const { data } = await axios.get(`Clients/${clientId}`);
|
||||
console.log('info cliente: ', data);
|
||||
|
||||
await fetchAddressList(data.defaultAddressFk);
|
||||
} catch (error) {
|
||||
console.error('Error al cambiar el cliente:', error);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<script setup>
|
||||
import { reactive, ref, onMounted, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['onDataSaved']);
|
||||
|
||||
const nameInputRef = ref(null);
|
||||
const serviceFormData = reactive({});
|
||||
|
||||
const onDataSaved = (formData, requestResponse) => {
|
||||
emit('onDataSaved', formData, requestResponse);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
nameInputRef.value.focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormModelPopup
|
||||
url-create="TicketServiceTypes"
|
||||
model="TicketServiceType"
|
||||
:title="t('New service type')"
|
||||
:form-initial-data="serviceFormData"
|
||||
@on-data-saved="onDataSaved"
|
||||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<VnInput
|
||||
ref="nameInputRef"
|
||||
:label="t('service.description')"
|
||||
v-model="data.name"
|
||||
:required="true"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
New service type: Nuevo tipo de servicio
|
||||
</i18n>
|
|
@ -0,0 +1,86 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
const emit = defineEmits(['onRequestCreated']);
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
const stateFetchDataRef = ref(null);
|
||||
|
||||
const statesOptions = ref([]);
|
||||
const workersOptions = ref([]);
|
||||
|
||||
const onStateFkChange = (formData) => (formData.userFk = user.value.id);
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
ref="stateFetchDataRef"
|
||||
url="States"
|
||||
auto-load
|
||||
@on-fetch="(data) => (statesOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Workers/search"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (workersOptions = data)"
|
||||
/>
|
||||
<FormModelPopup
|
||||
:title="t('Create tracking')"
|
||||
url-create="Tickets/state"
|
||||
model="CreateTicketTracking"
|
||||
:form-initial-data="{ ticketFk: route.params.id }"
|
||||
@on-data-saved="() => emit('onRequestCreated')"
|
||||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<VnSelect
|
||||
v-model="data.stateFk"
|
||||
:label="t('tracking.state')"
|
||||
:options="statesOptions"
|
||||
@update:model-value="onStateFkChange(data)"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('tracking.worker')"
|
||||
v-model="data.userFk"
|
||||
:options="workersOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
>
|
||||
<template #option="{ opt, itemProps }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ opt.name }}
|
||||
</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }}, {{ opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template></VnSelect
|
||||
>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Create tracking: Crear estado
|
||||
</i18n>
|
|
@ -71,7 +71,7 @@ const filter = {
|
|||
|
||||
const data = ref(useCardDescription());
|
||||
const setData = (entity) =>
|
||||
(data.value = useCardDescription(entity.client.name, entity.id));
|
||||
(data.value = useCardDescription(entity.client?.name, entity.id));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -92,7 +92,7 @@ const setData = (entity) =>
|
|||
<template #value>
|
||||
<span class="link">
|
||||
{{ entity.clientFk }}
|
||||
<CustomerDescriptorProxy :id="entity.client.id" />
|
||||
<CustomerDescriptorProxy :id="entity.client?.id" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
|
@ -109,8 +109,8 @@ const setData = (entity) =>
|
|||
<VnLv :label="t('ticket.summary.salesPerson')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="entity.client.salesPersonUser?.name"
|
||||
:worker-id="entity.client.salesPersonFk"
|
||||
:name="entity.client?.salesPersonUser?.name"
|
||||
:worker-id="entity.client?.salesPersonFk"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
<script setup>
|
||||
import { ref, watch, computed, reactive } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const ticketNotesCrudRef = ref(null);
|
||||
const observationTypes = ref([]);
|
||||
const arrayData = useArrayData('TicketNotes');
|
||||
const { store } = arrayData;
|
||||
|
||||
const crudModelFilter = reactive({
|
||||
where: { ticketFk: route.params.id },
|
||||
fields: ['id', 'ticketFk', 'observationTypeFk', 'description'],
|
||||
});
|
||||
|
||||
const crudModelRequiredData = computed(() => ({ ticketFk: route.params.id }));
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async () => {
|
||||
crudModelFilter.where.ticketFk = route.params.id;
|
||||
store.filter = crudModelFilter;
|
||||
await ticketNotesCrudRef.value.reload();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (observationTypes = data)"
|
||||
auto-load
|
||||
url="ObservationTypes"
|
||||
/>
|
||||
<div class="flex justify-center">
|
||||
<CrudModel
|
||||
ref="ticketNotesCrudRef"
|
||||
data-key="TicketNotes"
|
||||
url="TicketObservations"
|
||||
model="TicketNotes"
|
||||
:filter="crudModelFilter"
|
||||
:data-required="crudModelRequiredData"
|
||||
:default-remove="false"
|
||||
auto-load
|
||||
style="max-width: 800px"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard class="q-px-lg q-py-md">
|
||||
<div
|
||||
v-for="(row, index) in rows"
|
||||
:key="index"
|
||||
class="q-mb-md row items-center q-gutter-x-md"
|
||||
>
|
||||
<VnSelect
|
||||
:label="t('ticketNotes.observationType')"
|
||||
:options="observationTypes"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
v-model="row.observationTypeFk"
|
||||
:disable="!!row.id"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('ticketNotes.description')"
|
||||
v-model="row.description"
|
||||
class="col"
|
||||
/>
|
||||
<QIcon
|
||||
name="delete"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
@click="ticketNotesCrudRef.remove([row])"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('ticketNotes.removeNote') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
<VnRow v-if="observationTypes.length > rows.length">
|
||||
<QIcon
|
||||
name="add_circle"
|
||||
class="fill-icon-on-hover q-ml-md"
|
||||
size="sm"
|
||||
color="primary"
|
||||
@click="ticketNotesCrudRef.insert()"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('ticketNotes.addNote') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</VnRow>
|
||||
</QCard>
|
||||
</template>
|
||||
</CrudModel>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,190 @@
|
|||
<script setup>
|
||||
import { ref, watch, computed, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import TicketCreateServiceType from './TicketCreateServiceType.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import axios from 'axios';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const ticketServiceTypeFetchRef = ref(null);
|
||||
const ticketServiceCrudRef = ref(null);
|
||||
const ticketServiceOptions = ref([]);
|
||||
const arrayData = useArrayData('TicketNotes');
|
||||
const { store } = arrayData;
|
||||
const { notify } = useNotify();
|
||||
|
||||
const selected = ref([]);
|
||||
const defaultTaxClass = ref(null);
|
||||
|
||||
const crudModelFilter = computed(() => ({
|
||||
where: { ticketFk: route.params.id },
|
||||
}));
|
||||
|
||||
const crudModelRequiredData = computed(() => ({
|
||||
ticketFk: route.params.id,
|
||||
taxClassFk: defaultTaxClass.value?.id,
|
||||
}));
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async () => {
|
||||
store.filter = crudModelFilter.value;
|
||||
await ticketServiceCrudRef.value.reload();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => await getDefaultTaxClass());
|
||||
|
||||
const createRefund = async () => {
|
||||
try {
|
||||
if (!selected.value.length) return;
|
||||
|
||||
const params = {
|
||||
servicesIds: selected.value.map((s) => +s.ticketFk),
|
||||
withWarehouse: false,
|
||||
negative: true,
|
||||
};
|
||||
const { data } = await axios.post('Sales/clone', params);
|
||||
const [refundTicket] = data;
|
||||
notify(
|
||||
t('service.createRefundSuccess', {
|
||||
ticketId: refundTicket.id,
|
||||
}),
|
||||
'positive'
|
||||
);
|
||||
router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultTaxClass = async () => {
|
||||
try {
|
||||
let filter = {
|
||||
where: { code: 'G' },
|
||||
};
|
||||
const { data } = await axios.get('TaxClasses/findOne', {
|
||||
params: { filter: JSON.stringify(filter) },
|
||||
});
|
||||
defaultTaxClass.value = data;
|
||||
console.log('defaultTaxClass', defaultTaxClass.value);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'description',
|
||||
label: t('service.description'),
|
||||
field: (row) => row.ticketServiceTypeFk,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: t('service.quantity'),
|
||||
field: (row) => row.quantity,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: t('service.price'),
|
||||
field: (row) => row.price,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="ticketServiceTypeFetchRef"
|
||||
@on-fetch="(data) => (ticketServiceOptions = data)"
|
||||
auto-load
|
||||
url="TicketServiceTypes"
|
||||
/>
|
||||
<CrudModel
|
||||
ref="ticketServiceCrudRef"
|
||||
data-key="TicketService"
|
||||
url="TicketServices"
|
||||
model="TicketService"
|
||||
:filter="crudModelFilter"
|
||||
:data-required="crudModelRequiredData"
|
||||
auto-load
|
||||
v-model:selected="selected"
|
||||
>
|
||||
<template #moreBeforeActions>
|
||||
<QBtn
|
||||
color="primary"
|
||||
:label="t('service.pay')"
|
||||
:disabled="!selected.length"
|
||||
@click.stop="createRefund()"
|
||||
/>
|
||||
</template>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
row-key="$index"
|
||||
selection="multiple"
|
||||
v-model:selected="selected"
|
||||
table-header-class="text-left"
|
||||
>
|
||||
<template #body-cell-description="{ row, col }">
|
||||
<QTd auto-width>
|
||||
<VnSelectDialog
|
||||
:label="col.label"
|
||||
v-model="row.ticketServiceTypeFk"
|
||||
:options="ticketServiceOptions"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
hide-selected
|
||||
>
|
||||
<template #form>
|
||||
<TicketCreateServiceType
|
||||
@on-data-saved="ticketServiceTypeFetchRef.fetch()"
|
||||
/>
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-quantity="{ row, col }">
|
||||
<QTd auto-width>
|
||||
<VnInput
|
||||
:label="col.label"
|
||||
v-model.number="row.quantity"
|
||||
type="number"
|
||||
min="0"
|
||||
:info="t('service.quantityInfo')"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-price="{ row, col }">
|
||||
<QTd auto-width>
|
||||
<VnInput
|
||||
:label="col.label"
|
||||
v-model.number="row.price"
|
||||
type="number"
|
||||
min="0"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</CrudModel>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||
<QBtn fab color="primary" icon="add" @click="ticketServiceCrudRef.insert()" />
|
||||
</QPageSticky>
|
||||
</template>
|
|
@ -0,0 +1,121 @@
|
|||
<script setup>
|
||||
import { ref, computed, watch, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import TicketCreateTracking from './TicketCreateTracking.vue';
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
|
||||
import { toDateTimeFormat } from 'src/filters/date.js';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const createTrackingDialogRef = ref(null);
|
||||
const paginateRef = ref(null);
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async (val) => {
|
||||
paginateFilter.where.ticketFk = val;
|
||||
paginateRef.value.fetch();
|
||||
}
|
||||
);
|
||||
|
||||
const paginateFilter = reactive({
|
||||
include: [
|
||||
{
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['id', 'name'],
|
||||
include: {
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
fields: ['id'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'state',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
order: ['created DESC'],
|
||||
where: {
|
||||
ticketFk: route.params.id,
|
||||
},
|
||||
});
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
label: t('tracking.state'),
|
||||
name: 'state',
|
||||
field: 'state',
|
||||
align: 'left',
|
||||
format: (val) => val.name,
|
||||
},
|
||||
{
|
||||
label: t('tracking.worker'),
|
||||
name: 'worker',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('tracking.created'),
|
||||
name: 'created',
|
||||
field: 'created',
|
||||
align: 'left',
|
||||
format: (val) => toDateTimeFormat(val),
|
||||
},
|
||||
]);
|
||||
|
||||
const openCreateModal = () => createTrackingDialogRef.value.show();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<VnPaginate
|
||||
ref="paginateRef"
|
||||
data-key="TicketTracking"
|
||||
:filter="paginateFilter"
|
||||
url="TicketTrackings"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
row-key="id"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
class="full-width q-mt-md"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
>
|
||||
<template #body-cell-worker="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QBtn flat color="primary">
|
||||
{{ row.user?.name }}
|
||||
<WorkerDescriptorProxy :id="row.user?.worker?.id" />
|
||||
</QBtn>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<QDialog
|
||||
ref="createTrackingDialogRef"
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
>
|
||||
<TicketCreateTracking @on-request-created="paginateRef.fetch()" />
|
||||
</QDialog>
|
||||
<QPageSticky :offset="[20, 20]">
|
||||
<QBtn @click="openCreateModal()" color="primary" fab icon="add" />
|
||||
<QTooltip class="text-no-wrap">
|
||||
{{ t('tracking.addState') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
|
@ -0,0 +1,153 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import axios from 'axios';
|
||||
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const salesRef = ref(null);
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async () => {
|
||||
await nextTick();
|
||||
salesRef.value?.fetch();
|
||||
}
|
||||
);
|
||||
|
||||
const salesFilter = computed(() => ({
|
||||
include: { relation: 'item' },
|
||||
order: 'concept',
|
||||
where: { ticketFk: route.params.id },
|
||||
limit: 20,
|
||||
}));
|
||||
|
||||
const sales = ref([]);
|
||||
const packingTypeVolume = ref([]);
|
||||
const rows = computed(() => sales.value);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
label: t('volume.item'),
|
||||
name: 'item',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('volume.description'),
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('volume.packingType'),
|
||||
name: 'quantity',
|
||||
field: (row) => row.item.itemPackingTypeFk,
|
||||
align: 'left',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
label: t('volume.quantity'),
|
||||
name: 'quantity',
|
||||
field: 'quantity',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('volume.volumeQuantity'),
|
||||
name: 'quantity',
|
||||
field: (row) => row.saleVolume?.volume,
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
const applyVolumes = async (salesData) => {
|
||||
try {
|
||||
if (!salesData.length) return;
|
||||
|
||||
sales.value = salesData;
|
||||
const ticket = sales.value[0].ticketFk;
|
||||
const { data } = await axios.get(`Tickets/${ticket}/getVolume`);
|
||||
const volumes = new Map(data.saleVolume.map((volume) => [volume.saleFk, volume]));
|
||||
|
||||
sales.value.forEach((sale) => {
|
||||
sale.saleVolume = volumes.get(sale.id);
|
||||
});
|
||||
|
||||
packingTypeVolume.value = data.packingTypeVolume;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
stateStore.rightDrawer = true;
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="salesRef"
|
||||
url="sales"
|
||||
:filter="salesFilter"
|
||||
@on-fetch="(data) => applyVolumes(data)"
|
||||
auto-load
|
||||
/>
|
||||
<RightMenu v-if="packingTypeVolume.length">
|
||||
<template #right-panel>
|
||||
<QCard
|
||||
v-for="(packingType, index) in packingTypeVolume"
|
||||
:key="index"
|
||||
class="q-pa-md q-mb-md q-ma-md color-vn-text"
|
||||
bordered
|
||||
flat
|
||||
style="border-color: black"
|
||||
>
|
||||
<QCardSection class="column items-center" horizontal>
|
||||
<span>
|
||||
{{ t('volume.type') }}:
|
||||
{{ dashIfEmpty(packingType.description) }}
|
||||
</span>
|
||||
</QCardSection>
|
||||
<QCardSection class="column items-center" horizontal>
|
||||
<span> {{ t('volume.volume') }}: {{ packingType.volume }} </span>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</template>
|
||||
</RightMenu>
|
||||
<QTable
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
row-key="id"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
class="full-width q-mt-md"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
>
|
||||
<template #body-cell-item="{ row }">
|
||||
<QTd>
|
||||
<QBtn flat color="primary">
|
||||
{{ row.itemFk }}
|
||||
<ItemDescriptorProxy :id="row.itemFk" />
|
||||
</QBtn>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-description="{ row }">
|
||||
<QTd>
|
||||
<div class="column">
|
||||
<span>{{ row.item.name }}</span>
|
||||
<span class="color-vn-label">{{ row.item.subName }}</span>
|
||||
<FetchedTags :item="row.item" :max-length="6" />
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
|
@ -1,3 +1,19 @@
|
|||
card:
|
||||
search: Search tickets
|
||||
searchInfo: You can search by ticket id or alias
|
||||
volume:
|
||||
item: Item
|
||||
description: Description
|
||||
packingType: Packing Type
|
||||
quantity: Quantity
|
||||
volumeQuantity: m³ per quantity
|
||||
type: Type
|
||||
volume: Volume
|
||||
ticketNotes:
|
||||
observationType: Observation type
|
||||
description: Description
|
||||
removeNote: Remove note
|
||||
addNote: Add note
|
||||
ticketSale:
|
||||
id: Id
|
||||
visible: Visible
|
||||
|
@ -113,9 +129,6 @@ basicData:
|
|||
negativesConfirmMessage: Negatives are going to be generated, are you sure you want to advance all the lines?
|
||||
chooseAnOption: Choose an option
|
||||
unroutedTicket: The ticket has been unrouted
|
||||
card:
|
||||
search: Search tickets
|
||||
searchInfo: You can search by ticket id or alias
|
||||
purchaseRequest:
|
||||
id: Id
|
||||
description: Description
|
||||
|
@ -136,3 +149,18 @@ weeklyTickets:
|
|||
salesperson: Salesperson
|
||||
search: Search weekly tickets
|
||||
searchInfo: Search weekly tickets by id or client id
|
||||
service:
|
||||
pay: Pay
|
||||
description: Description
|
||||
quantity: Quantity
|
||||
price: Price
|
||||
removeService: Remove service
|
||||
newService: New service type
|
||||
addService: Add service
|
||||
quantityInfo: To create services with negative amounts mark the service on the source ticket and press the pay button.
|
||||
createRefundSuccess: 'The following refund ticket have been created: { ticketId }'
|
||||
tracking:
|
||||
state: State
|
||||
worker: Worker
|
||||
created: Created
|
||||
addState: Add state
|
||||
|
|
|
@ -1,6 +1,34 @@
|
|||
service:
|
||||
pay: Abonar
|
||||
description: Descripción
|
||||
quantity: Cantidad
|
||||
price: Precio
|
||||
removeService: Quitar servicio
|
||||
newService: Nuevo tipo de servicio
|
||||
addService: Añadir servicio
|
||||
quantityInfo: Para crear sevicios con cantidades negativas marcar servicio en el ticket origen y apretar el boton abonar.
|
||||
createRefundSuccess: 'Se ha creado siguiente ticket de abono: { ticketId }'
|
||||
tracking:
|
||||
state: Estado
|
||||
worker: Trabajador
|
||||
created: Fecha creación
|
||||
addState: Añadir estado
|
||||
card:
|
||||
search: Buscar tickets
|
||||
searchInfo: Buscar tickets por identificador o alias
|
||||
volume:
|
||||
item: Artículo
|
||||
description: Descripción
|
||||
packingType: Encajado
|
||||
quantity: Cantidad
|
||||
volumeQuantity: m³ por cantidad
|
||||
type: Tipo
|
||||
volume: Volumen
|
||||
ticketNotes:
|
||||
observationType: Tipo de observación
|
||||
description: Descripción
|
||||
removeNote: Quitar nota
|
||||
addNote: Añadir nota
|
||||
purchaseRequest:
|
||||
Id: Id
|
||||
description: Descripción
|
||||
|
@ -112,8 +140,6 @@ futureTickets:
|
|||
moveTicketSuccess: Tickets movidos correctamente
|
||||
searchInfo: Buscar tickets por fecha
|
||||
futureTicket: Tickets a futuro
|
||||
Search ticket: Buscar tickets
|
||||
You can search by ticket id or alias: Puedes buscar por id o alias del ticket
|
||||
ticketSale:
|
||||
id: Id
|
||||
visible: Visible
|
||||
|
@ -138,3 +164,5 @@ ticketSale:
|
|||
shipped: F. Envío
|
||||
agency: Agencia
|
||||
address: Consignatario
|
||||
Search ticket: Buscar tickets
|
||||
You can search by ticket id or alias: Puedes buscar por id o alias del ticket
|
||||
|
|
|
@ -7,6 +7,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
|||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -20,6 +21,7 @@ const warehousesOptions = ref([]);
|
|||
const continentsOptions = ref([]);
|
||||
const agenciesOptions = ref([]);
|
||||
const suppliersOptions = ref([]);
|
||||
const warehousesByContinent = ref({});
|
||||
|
||||
const add = (paramsObj, key) => {
|
||||
if (paramsObj[key] === undefined) {
|
||||
|
@ -34,6 +36,28 @@ const decrement = (paramsObj, key) => {
|
|||
|
||||
paramsObj[key]--;
|
||||
};
|
||||
|
||||
const warehouses = async () => {
|
||||
const warehousesResponse = await axios.get('Warehouses');
|
||||
const countriesResponse = await axios.get('Countries');
|
||||
const continentsResponse = await axios.get('Continents');
|
||||
|
||||
const countryContinentMap = countriesResponse.data.reduce((acc, country) => {
|
||||
acc[country.id] = country.continentFk;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
continentsResponse.data.forEach((continent) => {
|
||||
const countriesInContinent = Object.keys(countryContinentMap).filter(
|
||||
(countryId) => countryContinentMap[countryId] === continent.id.toString()
|
||||
);
|
||||
|
||||
warehousesByContinent.value[continent.code] = warehousesResponse.data.filter(
|
||||
(warehouse) => countriesInContinent.includes(warehouse.countryFk.toString())
|
||||
);
|
||||
});
|
||||
};
|
||||
warehouses();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -116,7 +140,6 @@ const decrement = (paramsObj, key) => {
|
|||
<VnSelect
|
||||
:label="t('params.agencyModeFk')"
|
||||
v-model="params.agencyModeFk"
|
||||
@update:model-value="searchFn()"
|
||||
:options="agenciesOptions"
|
||||
option-value="agencyFk"
|
||||
option-label="name"
|
||||
|
@ -147,12 +170,26 @@ const decrement = (paramsObj, key) => {
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItem v-if="warehousesByContinent[params.continent]">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('params.warehouseOutFk')"
|
||||
v-model="params.warehouseOutFk"
|
||||
:options="warehousesByContinent[params.continent]"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem v-else>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('params.warehouseOutFk')"
|
||||
v-model="params.warehouseOutFk"
|
||||
@update:model-value="searchFn()"
|
||||
:options="warehousesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
|
@ -168,7 +205,6 @@ const decrement = (paramsObj, key) => {
|
|||
<VnSelect
|
||||
:label="t('params.warehouseInFk')"
|
||||
v-model="params.warehouseInFk"
|
||||
@update:model-value="searchFn()"
|
||||
:options="warehousesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
|
@ -184,7 +220,6 @@ const decrement = (paramsObj, key) => {
|
|||
<VnSelect
|
||||
:label="t('supplier.pageTitles.supplier')"
|
||||
v-model="params.cargoSupplierFk"
|
||||
@update:model-value="searchFn()"
|
||||
:options="suppliersOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
|
@ -200,7 +235,6 @@ const decrement = (paramsObj, key) => {
|
|||
<VnSelect
|
||||
:label="t('params.continent')"
|
||||
v-model="params.continent"
|
||||
@update:model-value="searchFn()"
|
||||
:options="continentsOptions"
|
||||
option-value="code"
|
||||
option-label="name"
|
||||
|
|
|
@ -56,7 +56,6 @@ const swapEntry = (from, to, key) => {
|
|||
};
|
||||
|
||||
function setNotifications(data) {
|
||||
console.log('data: ', data);
|
||||
active.value = new Map(data.active);
|
||||
available.value = new Map(data.available);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
|||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||
import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
@ -161,7 +162,14 @@ const filter = {
|
|||
<VnTitle :text="t('worker.summary.userData')" />
|
||||
<VnLv :label="t('worker.summary.userId')" :value="worker.user.id" />
|
||||
<VnLv :label="t('worker.card.name')" :value="worker.user.nickname" />
|
||||
<VnLv :label="t('worker.summary.role')" :value="worker.user.role.name" />
|
||||
<VnLv :label="t('worker.summary.role')">
|
||||
<template #value>
|
||||
<span class="link">
|
||||
{{ worker.user.role.name }}
|
||||
<RoleDescriptorProxy :id="worker.user.role.id" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :value="worker?.sip?.extension">
|
||||
<template #label>
|
||||
{{ t('worker.summary.sipExtension') }}
|
||||
|
|
|
@ -19,6 +19,10 @@ export default {
|
|||
'TicketSale',
|
||||
'TicketLog',
|
||||
'TicketPurchaseRequest',
|
||||
'TicketService',
|
||||
'TicketTracking',
|
||||
'TicketVolume',
|
||||
'TicketNotes',
|
||||
],
|
||||
},
|
||||
children: [
|
||||
|
@ -29,8 +33,8 @@ export default {
|
|||
redirect: { name: 'TicketList' },
|
||||
children: [
|
||||
{
|
||||
name: 'TicketList',
|
||||
path: 'list',
|
||||
name: 'TicketList',
|
||||
meta: {
|
||||
title: 'list',
|
||||
icon: 'view_list',
|
||||
|
@ -38,8 +42,8 @@ export default {
|
|||
component: () => import('src/pages/Ticket/TicketList.vue'),
|
||||
},
|
||||
{
|
||||
name: 'TicketCreate',
|
||||
path: 'create',
|
||||
name: 'TicketCreate',
|
||||
meta: {
|
||||
title: 'createTicket',
|
||||
icon: 'vn:ticketAdd',
|
||||
|
@ -48,8 +52,8 @@ export default {
|
|||
component: () => import('src/pages/Ticket/TicketCreate.vue'),
|
||||
},
|
||||
{
|
||||
name: 'TicketWeekly',
|
||||
path: 'weekly',
|
||||
name: 'TicketWeekly',
|
||||
meta: {
|
||||
title: 'weeklyTickets',
|
||||
icon: 'access_time',
|
||||
|
@ -57,8 +61,8 @@ export default {
|
|||
component: () => import('src/pages/Ticket/TicketWeekly.vue'),
|
||||
},
|
||||
{
|
||||
name: 'TicketFuture',
|
||||
path: 'future',
|
||||
name: 'TicketFuture',
|
||||
meta: {
|
||||
title: 'futureTickets',
|
||||
icon: 'keyboard_double_arrow_right',
|
||||
|
@ -66,8 +70,8 @@ export default {
|
|||
component: () => import('src/pages/Ticket/TicketFuture.vue'),
|
||||
},
|
||||
{
|
||||
name: 'TicketAdvance',
|
||||
path: 'advance',
|
||||
name: 'TicketAdvance',
|
||||
meta: {
|
||||
title: 'ticketAdvance',
|
||||
icon: 'keyboard_double_arrow_left',
|
||||
|
@ -83,8 +87,8 @@ export default {
|
|||
redirect: { name: 'TicketSummary' },
|
||||
children: [
|
||||
{
|
||||
name: 'TicketSummary',
|
||||
path: 'summary',
|
||||
name: 'TicketSummary',
|
||||
meta: {
|
||||
title: 'summary',
|
||||
icon: 'launch',
|
||||
|
@ -92,8 +96,8 @@ export default {
|
|||
component: () => import('src/pages/Ticket/Card/TicketSummary.vue'),
|
||||
},
|
||||
{
|
||||
name: 'TicketBasicData',
|
||||
path: 'basic-data',
|
||||
name: 'TicketBasicData',
|
||||
meta: {
|
||||
title: 'basicData',
|
||||
icon: 'vn:settings',
|
||||
|
@ -102,8 +106,8 @@ export default {
|
|||
import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'),
|
||||
},
|
||||
{
|
||||
name: 'TicketSale',
|
||||
path: 'sale',
|
||||
name: 'TicketSale',
|
||||
meta: {
|
||||
title: 'sale',
|
||||
icon: 'vn:lines',
|
||||
|
@ -120,6 +124,15 @@ export default {
|
|||
component: () =>
|
||||
import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'),
|
||||
},
|
||||
{
|
||||
path: 'tracking',
|
||||
name: 'TicketTracking',
|
||||
meta: {
|
||||
title: 'tracking',
|
||||
icon: 'vn:eye',
|
||||
},
|
||||
component: () => import('src/pages/Ticket/Card/TicketTracking.vue'),
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
name: 'TicketLog',
|
||||
|
@ -147,6 +160,34 @@ export default {
|
|||
},
|
||||
component: () => import('src/pages/Ticket/Card/TicketSms.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: 'service',
|
||||
name: 'TicketService',
|
||||
meta: {
|
||||
title: 'services',
|
||||
icon: 'vn:services',
|
||||
},
|
||||
component: () => import('src/pages/Ticket/Card/TicketService.vue'),
|
||||
},
|
||||
{
|
||||
path: 'volume',
|
||||
name: 'TicketVolume',
|
||||
meta: {
|
||||
title: 'volume',
|
||||
icon: 'vn:volume',
|
||||
},
|
||||
component: () => import('src/pages/Ticket/Card/TicketVolume.vue'),
|
||||
},
|
||||
{
|
||||
path: 'observation',
|
||||
name: 'TicketNotes',
|
||||
meta: {
|
||||
title: 'notes',
|
||||
icon: 'vn:notes',
|
||||
},
|
||||
component: () => import('src/pages/Ticket/Card/TicketNotes.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -2,13 +2,10 @@ describe('AgencyWorkCenter', () => {
|
|||
beforeEach(() => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.login('developer');
|
||||
cy.visit(`/#/agency`);
|
||||
cy.visit(`/#/agency/11/workCenter`);
|
||||
});
|
||||
|
||||
it('assign workCenter', () => {
|
||||
cy.visit(`/#/agency`);
|
||||
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
|
||||
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
|
||||
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
|
||||
cy.get(
|
||||
'.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'
|
||||
|
@ -17,8 +14,6 @@ describe('AgencyWorkCenter', () => {
|
|||
});
|
||||
|
||||
it('delete workCenter', () => {
|
||||
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
|
||||
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
|
||||
cy.get('.q-item__section--side > .q-btn > .q-btn__content > .q-icon').click();
|
||||
cy.get('.q-notification__message').should(
|
||||
'have.text',
|
||||
|
@ -27,9 +22,6 @@ describe('AgencyWorkCenter', () => {
|
|||
});
|
||||
|
||||
it('error on duplicate workCenter', () => {
|
||||
cy.visit(`/#/agency`);
|
||||
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
|
||||
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
|
||||
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
|
||||
cy.get(
|
||||
'.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
describe('InvoiceInDescriptor', () => {
|
||||
const dialogBtns = '.q-card__actions button';
|
||||
const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section';
|
||||
const isBookedField =
|
||||
'.q-card:nth-child(3) .vn-label-value:nth-child(5) > .value > span';
|
||||
const isBookedField = '.q-card:nth-child(3) .vn-label-value:nth-child(5) .q-checkbox';
|
||||
|
||||
it('should booking and unbooking the invoice properly', () => {
|
||||
cy.viewport(1280, 720);
|
||||
|
@ -12,10 +11,10 @@ describe('InvoiceInDescriptor', () => {
|
|||
cy.openActionsDescriptor();
|
||||
cy.get(firstDescritorOpt).click();
|
||||
cy.get(dialogBtns).eq(1).click();
|
||||
cy.get(isBookedField).should('have.attr', 'title', 'true');
|
||||
cy.get(isBookedField).should('have.attr', 'aria-checked', 'true');
|
||||
|
||||
cy.get(firstDescritorOpt).click();
|
||||
cy.get(dialogBtns).eq(1).click();
|
||||
cy.get(isBookedField).should('have.attr', 'title', 'false');
|
||||
cy.get(isBookedField).should('have.attr', 'aria-checked', 'false');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,12 +5,11 @@ describe('InvoiceInList', () => {
|
|||
':nth-child(1) > :nth-child(1) > .justify-between > .flex > .q-chip > .q-chip__content';
|
||||
const firstDetailBtn = '.q-card:nth-child(1) .q-btn:nth-child(2)';
|
||||
const summaryHeaders = '.summaryBody .header-link';
|
||||
const screen = '.q-page-container > .q-drawer-container > .fullscreen';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.login('developer');
|
||||
cy.visit(`/#/invoice-in/list`);
|
||||
cy.get(screen).click();
|
||||
});
|
||||
|
||||
it('should redirect on clicking a invoice', () => {
|
||||
|
|
|
@ -8,9 +8,9 @@ describe('ParkingList', () => {
|
|||
const summaryHeader = '.summaryBody .header';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.login('developer');
|
||||
cy.visit(`/#/parking/list`);
|
||||
cy.closeSideMenu();
|
||||
});
|
||||
|
||||
it('should redirect on clicking a parking', () => {
|
||||
|
|
|
@ -3,13 +3,10 @@ describe('VnSearchBar', () => {
|
|||
const employeeId = ' #1';
|
||||
const salesPersonId = ' #18';
|
||||
const idGap = '.q-item > .q-item__label';
|
||||
const cardList = '.vn-card-list';
|
||||
|
||||
let url;
|
||||
const vnTableRow = '.q-virtual-scroll__content';
|
||||
beforeEach(() => {
|
||||
cy.login('developer');
|
||||
cy.visit('#/customer/list');
|
||||
cy.url().then((currentUrl) => (url = currentUrl));
|
||||
});
|
||||
|
||||
it('should redirect to customer summary page', () => {
|
||||
|
@ -19,12 +16,12 @@ describe('VnSearchBar', () => {
|
|||
|
||||
it('should stay on the list page if there are several results or none', () => {
|
||||
cy.writeSearchbar('salesA{enter}');
|
||||
checkCardListAndUrl(2);
|
||||
checkTableLength(2);
|
||||
|
||||
cy.clearSearchbar();
|
||||
|
||||
cy.writeSearchbar('0{enter}');
|
||||
checkCardListAndUrl(0);
|
||||
checkTableLength(0);
|
||||
});
|
||||
|
||||
const searchAndCheck = (searchTerm, expectedText) => {
|
||||
|
@ -33,10 +30,7 @@ describe('VnSearchBar', () => {
|
|||
cy.get(idGap).should('have.text', expectedText);
|
||||
};
|
||||
|
||||
const checkCardListAndUrl = (expectedLength) => {
|
||||
cy.get(cardList).then(($cardList) => {
|
||||
expect($cardList.find('.q-card').length).to.equal(expectedLength);
|
||||
cy.url().then((currentUrl) => expect(currentUrl).to.contain(url));
|
||||
});
|
||||
const checkTableLength = (expectedLength) => {
|
||||
cy.get(vnTableRow).find('tr').should('have.length', expectedLength);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -221,10 +221,6 @@ Cypress.Commands.add('openLeftMenu', (element) => {
|
|||
if (element) cy.waitForElement(element);
|
||||
cy.get('.q-toolbar > .q-btn--round.q-btn--dense > .q-btn__content > .q-icon').click();
|
||||
});
|
||||
Cypress.Commands.add('closeSideMenu', (element) => {
|
||||
if (element) cy.waitForElement(element);
|
||||
cy.get('.fullscreen.q-drawer__backdrop:not(.hidden)').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clearSearchbar', (element) => {
|
||||
if (element) cy.waitForElement(element);
|
||||
|
|
|
@ -5,11 +5,14 @@ import jsconfigPaths from 'vite-jsconfig-paths';
|
|||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||
import path from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'happy-dom',
|
||||
setupFiles: 'test/vitest/setup-file.js',
|
||||
include: [
|
||||
// Matches vitest tests in any subfolder of 'src' or into 'test/vitest/__tests__'
|
||||
// Matches all files with extension 'js', 'jsx', 'ts' and 'tsx'
|
||||
'test/vitest/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
|
||||
],
|
||||
},
|
Loading…
Reference in New Issue