Compare commits
485 Commits
8944-Fixed
...
dev
Author | SHA1 | Date |
---|---|---|
|
51cb5ea3f3 | |
|
5beeedb70a | |
|
966e903e42 | |
|
88ea1cbbba | |
|
5ac7f1bf27 | |
|
2d61c9cb79 | |
|
3369b090d6 | |
|
a052c68e67 | |
|
2f669b7d3a | |
|
ec5caef07c | |
|
36fa06548b | |
|
3fbfeeebbd | |
|
6c18e6d40d | |
|
bc018289c3 | |
|
284e1a2ae2 | |
|
d70ad13889 | |
|
0dcbc49e35 | |
|
7c2dac4e2c | |
|
752c004946 | |
|
61cee39f6a | |
|
1f8a82468a | |
|
52fdc1f8de | |
|
a8f6a31cef | |
|
ea77273d5b | |
|
3229f77148 | |
|
807db25750 | |
|
fa31c49ebd | |
|
361183357b | |
|
25f72fed31 | |
|
69a9cb25bd | |
|
e6a73d7b42 | |
|
1f716f104b | |
|
9257e7ee07 | |
|
6684c59473 | |
|
042fff67df | |
|
ca7d05f753 | |
|
e9e9aea92d | |
|
00c74d8f53 | |
|
33eb77864f | |
|
a6d6ad92f6 | |
|
13499f3226 | |
|
0221ca31e7 | |
|
be9794fcd8 | |
|
101298f681 | |
|
fada268c16 | |
|
07ed28e25d | |
|
26bfec1c15 | |
|
1cfb03c3cb | |
|
7135aa6f40 | |
|
cc016a4a8c | |
|
a39be108a3 | |
|
fc6dfc364b | |
|
3b4fc76651 | |
|
621eb92863 | |
|
9a7f4cdd8c | |
|
0bcc54cbbe | |
|
bc6b369112 | |
|
749fb7a4a8 | |
|
e13c8457cc | |
|
33b6d0fe89 | |
|
0ec8283ba5 | |
|
67b51d7833 | |
|
547d0a2895 | |
|
ae122fb056 | |
|
b677266f4c | |
|
0b5cc7d00f | |
|
8e5b5f15f1 | |
|
f76305fdac | |
|
58dd3ad787 | |
|
cabee31109 | |
|
66ab8b20ee | |
|
5c482cf738 | |
|
acd9c97324 | |
|
df11584452 | |
|
1396758761 | |
|
2c11e3a524 | |
|
124eca710c | |
|
f9ba48e410 | |
|
86f1a5b9bb | |
|
6c1926e371 | |
|
0505fb69b6 | |
|
2bdb73dfde | |
|
3c95b4c976 | |
|
0ae1027891 | |
|
4886b5459b | |
|
6fd247badd | |
|
b8f38a4ae8 | |
|
a677abf35b | |
|
f4d91701fa | |
|
df4a8a6818 | |
|
3ff508328b | |
|
f1f2c30c24 | |
|
9834c7cf93 | |
|
efb8a1a4ca | |
|
f71ed84af7 | |
|
5cba4773f1 | |
|
e89cf14447 | |
|
b87730932e | |
|
54f0c094f8 | |
|
f532119a7f | |
|
19feb140e2 | |
|
5a6da51246 | |
|
9e3c7430e7 | |
|
9e589cd48e | |
|
5559b5fdcf | |
|
e4dfd3d53a | |
|
f9e9bc5dde | |
|
8f01bfdcb3 | |
|
07c078093f | |
|
08c67e3bcf | |
|
d4290a3cde | |
|
3a30853f6a | |
|
feef61bf32 | |
|
68e41c3609 | |
|
791e6fb2fa | |
|
ae0e3eae20 | |
|
1e29bbbd00 | |
|
88229099d3 | |
|
4fa143ca0e | |
|
328fc50e40 | |
|
ca223ef8ca | |
|
e04442efc6 | |
|
cda333fabd | |
|
ed2f2dc273 | |
|
bfa8d6eec5 | |
|
c2d615bc6c | |
|
2621f59a4c | |
|
cd59f880bb | |
|
9bf30642b9 | |
|
77846710f2 | |
|
441876d53e | |
|
bcfb4b1d39 | |
|
a62d973c82 | |
|
aaa5d94a39 | |
|
924cc86bb7 | |
|
1ffb5eca15 | |
|
a7829ceab2 | |
|
f4d6d4ad54 | |
|
c255b2ec44 | |
|
1c1ad34b18 | |
|
cf92ea8f47 | |
|
13f461f878 | |
|
c03562ed47 | |
|
37a2adb0ad | |
|
8e816382c6 | |
|
32bfede734 | |
|
fab57457d2 | |
|
e714b38b63 | |
|
175a1ff681 | |
|
ba864eae09 | |
|
f5bac7d2d5 | |
|
2f8e8cf33a | |
|
3f972991af | |
|
21d36a0d37 | |
|
e3936c63c6 | |
|
7b2b6a81eb | |
|
7fcea1dfdc | |
|
0fbaa79563 | |
|
4057110a82 | |
|
cf64b7069b | |
|
2316b08c2a | |
|
e6e4588d7c | |
|
f4171d8efa | |
|
41db6ec043 | |
|
baa902f25e | |
|
c95f4bb387 | |
|
eeeeda0c68 | |
|
79e87833b9 | |
|
567ce5fe99 | |
|
5a92f1a6cc | |
|
67f8d718e8 | |
|
4befe90289 | |
|
8e88c199e5 | |
|
b44a397c81 | |
|
0c701b23b6 | |
|
d87308844b | |
|
044c06e963 | |
|
e595201501 | |
|
0b1b0b40a2 | |
|
4bbdd3f162 | |
|
a80f6f9016 | |
|
3940ac3c82 | |
|
534bb9f121 | |
|
ac59c79baf | |
|
9f7994319b | |
|
df6c5bde04 | |
|
327afe8311 | |
|
2f29eaa843 | |
|
e3e414e827 | |
|
464a739a6b | |
|
2d0cb72774 | |
|
07f58bbec0 | |
|
0a992094cf | |
|
5be5287267 | |
|
359f1cc4db | |
|
d4337b32b4 | |
|
34a2306db2 | |
|
7a3e70fc6e | |
|
26507c6f6b | |
|
1535be957f | |
|
3cf7aeae87 | |
|
ea93ed42d5 | |
|
c59b862057 | |
|
f020db7fe1 | |
|
cc397f6b20 | |
|
3712fe4c4d | |
|
88a9299aa9 | |
|
8f6c2c1bfa | |
|
9906b2a34d | |
|
3295c18847 | |
|
1e21abe978 | |
|
2f2942a23f | |
|
8764eeca92 | |
|
5882f47f5f | |
|
54a905d436 | |
|
311e75e5ab | |
|
ce0936d11c | |
|
7b4534e7fd | |
|
cda841b956 | |
|
505489fae6 | |
|
7dbe5c24ec | |
|
ac7c64d66d | |
|
4e0e84b703 | |
|
ad1bde3f43 | |
|
5808aa6806 | |
|
bde19f0d4c | |
|
7f6a385e1d | |
|
ead6766c45 | |
|
30539dfed7 | |
|
9cf7e33afb | |
|
ab542c1872 | |
|
9f56fc292b | |
|
dd59b950c5 | |
|
3d0191eb0c | |
|
ca4a822ce2 | |
|
8120320a15 | |
|
8de345f33b | |
|
0f4e7c95ab | |
|
8b04dbc7ec | |
|
e5b1fc1001 | |
|
def62c5e12 | |
|
b093e7398d | |
|
c3ae161eb0 | |
|
7dcfeb0fc7 | |
|
c1b6d96aef | |
|
640bc21022 | |
|
f3b06c84a5 | |
|
649686f128 | |
|
43258214e1 | |
|
8e02826b76 | |
|
c723608d6b | |
|
2a8feaa5d1 | |
|
8879b15e4e | |
|
1007a884b9 | |
|
02a4960712 | |
|
254d5562dd | |
|
40899c4c3d | |
|
ffe502cd99 | |
|
191af3f917 | |
|
bce37182ed | |
|
b9788bc097 | |
|
b3c418551c | |
|
aacfb2a77d | |
|
b8231c4878 | |
|
a63926ff48 | |
|
b687c80559 | |
|
d1fee6d692 | |
|
936ee1b7f7 | |
|
eb0e63441e | |
|
ad178825db | |
|
8eb81fadf3 | |
|
5ff944e670 | |
|
c9e1e9a334 | |
|
abb741ed1d | |
|
cd76a006a2 | |
|
e1be86774b | |
|
e382852349 | |
|
95c58acb79 | |
|
8cf8b7d4ec | |
|
59bb3c4ad2 | |
|
1bfc2684f7 | |
|
8fb7e5637b | |
|
f351734d84 | |
|
a6a3d5a3d5 | |
|
81e572e66d | |
|
a56cdf8053 | |
|
0b40134cfe | |
|
20ca8f8072 | |
|
9782288d97 | |
|
70fdbb8210 | |
|
a9acc240e9 | |
|
9639c56ce0 | |
|
d64788732e | |
|
9af63555c3 | |
|
76df3413b9 | |
|
50cb0e243b | |
|
cb1fa3c7f5 | |
|
7b4f4880b5 | |
|
240b927a02 | |
|
41e4cc13b1 | |
|
22f77fc70c | |
|
adf33c14ae | |
|
16a86282fc | |
|
65f77b41e6 | |
|
5383df6946 | |
|
ab6dc5d2ca | |
|
f7280e6f25 | |
|
f8c3bfe2cd | |
|
ce69b5e274 | |
|
a0a5c13865 | |
|
0dfb5a47b7 | |
|
78df8c877d | |
|
5ef83fd53e | |
|
23a13a0347 | |
|
d10549939b | |
|
7e2f93d767 | |
|
d45d84d707 | |
|
49bf269b4d | |
|
ff38cd7590 | |
|
b21c5752b8 | |
|
0af8b8af2c | |
|
23dbd2a862 | |
|
efa68e9489 | |
|
c2a470c25e | |
|
c89bfc488c | |
|
103230e869 | |
|
9abd8a1841 | |
|
b3285f6783 | |
|
0c04c06024 | |
|
eb8e0d1334 | |
|
d0f4f77640 | |
|
bf79797024 | |
|
ca0af4b3f6 | |
|
d1f676e6ff | |
|
d2452caef8 | |
|
da02cc3622 | |
|
b63ae45121 | |
|
b911211000 | |
|
c6d4e2ffe5 | |
|
2f5ef1d0a0 | |
|
1511264aea | |
|
8250660a32 | |
|
020cf18a73 | |
|
22390d9aef | |
|
d81b8c7fc3 | |
|
3e9d239902 | |
|
d79d37dcaf | |
|
541b9f1b5f | |
|
2f0f0c73cd | |
|
b1d637f382 | |
|
2c7913e51e | |
|
adc4385f1e | |
|
b2584c021b | |
|
dd3a7c7755 | |
|
24f8d1c2e5 | |
|
ee632880f9 | |
|
ab5d85f392 | |
|
2e61630f85 | |
|
18f43489a3 | |
|
c900f17efa | |
|
00ac4305a1 | |
|
0251735c82 | |
|
1d568fa7df | |
|
9418c9f897 | |
|
b38c8cde0d | |
|
ce54da0039 | |
|
00d1db03a8 | |
|
1b660a15de | |
|
ce8127b723 | |
|
b2202d7843 | |
|
92a282d71c | |
|
f5ba659214 | |
|
21c7b2088b | |
|
9c13cd921e | |
|
2cbbd92021 | |
|
eeab1d228f | |
|
ca596b418a | |
|
99d2614f6d | |
|
ccbcaa13b7 | |
|
d65aa3e31c | |
|
8a23bb8734 | |
|
807540b512 | |
|
de5c62e481 | |
|
3eeb23f89d | |
|
dc7338ee8a | |
|
4287efa700 | |
|
10548a00d2 | |
|
eb373f10b3 | |
|
50cf47436c | |
|
6c49c9ea82 | |
|
679b9f5d5b | |
|
f509616f86 | |
|
db2a0b2685 | |
|
f01eb6c49c | |
|
4ad9a3e613 | |
|
e4f0d13a9b | |
|
587f106818 | |
|
f4ecf9ad51 | |
|
c3f60c68bc | |
|
6122b64268 | |
|
905b15594c | |
|
5307becf31 | |
|
7d62f7161b | |
|
1c594173fe | |
|
d8bd78b5ce | |
|
e8ba5761cc | |
|
31e337c77b | |
|
83ecfca6b8 | |
|
39a5143953 | |
|
16c70af66a | |
|
8e47a4134d | |
|
f15865ee42 | |
|
0ff6cc5dbe | |
|
6c0fcc38b0 | |
|
31f1850704 | |
|
2f20abf388 | |
|
b25bd19bb5 | |
|
197ebed39d | |
|
7f3151b1a4 | |
|
b57b5f7cee | |
|
693f6829f4 | |
|
33ea74d0a4 | |
|
5cb8a4a9dc | |
|
3029265402 | |
|
d86fb54917 | |
|
84385730cd | |
|
4b9e533ccd | |
|
ee150a6f26 | |
|
fc245fb6ac | |
|
89d7fccacd | |
|
e10dac995d | |
|
e8068c146d | |
|
b93e5d0e40 | |
|
20627c04f7 | |
|
31ac582c16 | |
|
9c3d92a244 | |
|
2dd82a55e8 | |
|
903345203b | |
|
b650d2b438 | |
|
acd78fc859 | |
|
6d2fa8493c | |
|
fcba8c7fcd | |
|
36de9e411e | |
|
c65a0f131c | |
|
800530f943 | |
|
49dd8e2432 | |
|
b950b4845b | |
|
622307697f | |
|
737eb74117 | |
|
666d524553 | |
|
22293eb2f8 | |
|
4e49a73173 | |
|
c57600b0f0 | |
|
b92cf74d85 | |
|
859ceeddf8 | |
|
99322a39e4 | |
|
ea3a2c28cc | |
|
20e99b5689 | |
|
138b3322b7 | |
|
7de71c96b4 | |
|
040e642dd6 | |
|
d33eae43f9 | |
|
b0dd3879d0 | |
|
a942b6648c | |
|
51e42a6dc8 | |
|
71d3d0830e | |
|
128a271eba | |
|
2eea5b453a | |
|
fa187614da | |
|
430cddb554 | |
|
be36d0c864 | |
|
f1f9a6ac16 | |
|
512611a309 | |
|
b8be248af9 | |
|
ef797a3675 | |
|
7ebf4cd38f | |
|
5332e3ab60 | |
|
0fac79ea4d | |
|
3961c4e276 | |
|
e2e34d2f2d | |
|
282014f873 | |
|
16a54bf02e | |
|
59fdf0fd00 | |
|
08e68e47b8 | |
|
ce827722e3 |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
@ -1,227 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
|
||||||
* 1. DO NOT edit this file directly as it won't do anything.
|
|
||||||
* 2. EDIT the original quasar.config file INSTEAD.
|
|
||||||
* 3. DO NOT git commit this file. It should be ignored.
|
|
||||||
*
|
|
||||||
* This file is still here because there was an error in
|
|
||||||
* the original quasar.config file and this allows you to
|
|
||||||
* investigate the Node.js stack error.
|
|
||||||
*
|
|
||||||
* After you fix the original file, this file will be
|
|
||||||
* deleted automatically.
|
|
||||||
**/
|
|
||||||
|
|
||||||
|
|
||||||
// quasar.config.js
|
|
||||||
import { configure } from "quasar/wrappers";
|
|
||||||
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
|
|
||||||
import path from "path";
|
|
||||||
var __quasar_inject_dirname__ = "/home/jsegarra/Projects/salix-front";
|
|
||||||
var target = `http://${process.env.CI ? "back" : "localhost"}:3000`;
|
|
||||||
var quasar_config_default = configure(function() {
|
|
||||||
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: "node20"
|
|
||||||
},
|
|
||||||
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) {
|
|
||||||
delete viteConf.build.polyfillModulePreload;
|
|
||||||
viteConf.build.modulePreload = {
|
|
||||||
polyfill: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
// viteVuePluginOptions: {},
|
|
||||||
alias: {
|
|
||||||
composables: path.join(__quasar_inject_dirname__, "./src/composables"),
|
|
||||||
filters: path.join(__quasar_inject_dirname__, "./src/filters")
|
|
||||||
},
|
|
||||||
vitePlugins: [
|
|
||||||
[
|
|
||||||
VueI18nPlugin({
|
|
||||||
strictMessage: false,
|
|
||||||
runtimeOnly: false,
|
|
||||||
include: [
|
|
||||||
path.resolve(__quasar_inject_dirname__, "./src/i18n/locale/**"),
|
|
||||||
path.resolve(__quasar_inject_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,
|
|
||||||
logLevel: "debug",
|
|
||||||
changeOrigin: true,
|
|
||||||
secure: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
open: false,
|
|
||||||
allowedHosts: [
|
|
||||||
"front",
|
|
||||||
// Agrega este nombre de host
|
|
||||||
"localhost"
|
|
||||||
// Opcional, para pruebas locales
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 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: 3e3,
|
|
||||||
// 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) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
export {
|
|
||||||
quasar_config_default as default
|
|
||||||
};
|
|
|
@ -131,11 +131,10 @@ async function fetch(data) {
|
||||||
const rows = keyData ? data[keyData] : data;
|
const rows = keyData ? data[keyData] : data;
|
||||||
resetData(rows);
|
resetData(rows);
|
||||||
emit('onFetch', rows);
|
emit('onFetch', rows);
|
||||||
$props.insertOnLoad && await insert();
|
$props.insertOnLoad && (await insert());
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function resetData(data) {
|
function resetData(data) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
if (data && Array.isArray(data)) {
|
if (data && Array.isArray(data)) {
|
||||||
|
@ -146,15 +145,22 @@ function resetData(data) {
|
||||||
formData.value = JSON.parse(JSON.stringify(data));
|
formData.value = JSON.parse(JSON.stringify(data));
|
||||||
|
|
||||||
if (watchChanges.value) watchChanges.value(); //destroy watcher
|
if (watchChanges.value) watchChanges.value(); //destroy watcher
|
||||||
watchChanges.value = watch(formData, (nVal) => {
|
watchChanges.value = watch(
|
||||||
|
formData,
|
||||||
|
(nVal) => {
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
const filteredNewData = nVal.filter(row => !isRowEmpty(row) || row[$props.primaryKey]);
|
const filteredNewData = nVal.filter(
|
||||||
const filteredOriginal = originalData.value.filter(row => row[$props.primaryKey]);
|
(row) => !isRowEmpty(row) || row[$props.primaryKey],
|
||||||
|
);
|
||||||
|
const filteredOriginal = originalData.value.filter(
|
||||||
|
(row) => row[$props.primaryKey],
|
||||||
|
);
|
||||||
|
|
||||||
const changes = getDifferences(filteredOriginal, filteredNewData);
|
const changes = getDifferences(filteredOriginal, filteredNewData);
|
||||||
hasChanges.value = !isEmpty(changes);
|
hasChanges.value = !isEmpty(changes);
|
||||||
|
},
|
||||||
}, { deep: true });
|
{ deep: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async function reset() {
|
async function reset() {
|
||||||
await fetch(originalData.value);
|
await fetch(originalData.value);
|
||||||
|
@ -185,7 +191,6 @@ async function onSubmit() {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
await saveChanges($props.saveFn ? formData.value : null);
|
await saveChanges($props.saveFn ? formData.value : null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmitAndGo() {
|
async function onSubmitAndGo() {
|
||||||
|
@ -194,8 +199,8 @@ async function onSubmitAndGo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveChanges(data) {
|
async function saveChanges(data) {
|
||||||
formData.value = formData.value.filter(row =>
|
formData.value = formData.value.filter(
|
||||||
row[$props.primaryKey] || !isRowEmpty(row)
|
(row) => row[$props.primaryKey] || !isRowEmpty(row),
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($props.saveFn) {
|
if ($props.saveFn) {
|
||||||
|
@ -228,7 +233,7 @@ async function saveChanges(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) {
|
async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) {
|
||||||
formData.value = formData.value.filter(row => !isRowEmpty(row));
|
formData.value = formData.value.filter((row) => !isRowEmpty(row));
|
||||||
|
|
||||||
const lastRow = formData.value.at(-1);
|
const lastRow = formData.value.at(-1);
|
||||||
const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false;
|
const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false;
|
||||||
|
@ -239,20 +244,18 @@ async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault
|
||||||
const nRow = Object.assign({ $index }, pushData);
|
const nRow = Object.assign({ $index }, pushData);
|
||||||
formData.value.push(nRow);
|
formData.value.push(nRow);
|
||||||
|
|
||||||
const hasChange = Object.keys(nRow).some(key => !isChange(nRow, key));
|
const hasChange = Object.keys(nRow).some((key) => !isChange(nRow, key));
|
||||||
if (hasChange) hasChanges.value = true;
|
if (hasChange) hasChanges.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRowEmpty(row) {
|
function isRowEmpty(row) {
|
||||||
return Object.keys(row).every(key => isChange(row, key));
|
return Object.keys(row).every((key) => isChange(row, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isChange(row, key) {
|
||||||
function isChange(row,key){
|
|
||||||
return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key);
|
return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function remove(data) {
|
async function remove(data) {
|
||||||
if (!data.length)
|
if (!data.length)
|
||||||
return quasar.notify({
|
return quasar.notify({
|
||||||
|
@ -270,7 +273,9 @@ async function remove(data) {
|
||||||
(form) => !preRemove.some((index) => index == form.$index),
|
(form) => !preRemove.some((index) => index == form.$index),
|
||||||
);
|
);
|
||||||
formData.value = newData;
|
formData.value = newData;
|
||||||
hasChanges.value = JSON.stringify(removeIndexField(formData.value)) !== JSON.stringify(removeIndexField(originalData.value));
|
hasChanges.value =
|
||||||
|
JSON.stringify(removeIndexField(formData.value)) !==
|
||||||
|
JSON.stringify(removeIndexField(originalData.value));
|
||||||
}
|
}
|
||||||
if (ids.length) {
|
if (ids.length) {
|
||||||
quasar
|
quasar
|
||||||
|
@ -286,7 +291,7 @@ async function remove(data) {
|
||||||
})
|
})
|
||||||
.onOk(async () => {
|
.onOk(async () => {
|
||||||
newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
|
newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
|
||||||
fetch(newData);
|
await reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -254,17 +254,13 @@ async function save() {
|
||||||
old: originalData.value,
|
old: originalData.value,
|
||||||
});
|
});
|
||||||
if ($props.reload) await arrayData.fetch({});
|
if ($props.reload) await arrayData.fetch({});
|
||||||
|
if ($props.goTo) push({ path: $props.goTo });
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveAndGo() {
|
|
||||||
await save();
|
|
||||||
push({ path: $props.goTo });
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
formData.value = JSON.parse(JSON.stringify(originalData.value));
|
formData.value = JSON.parse(JSON.stringify(originalData.value));
|
||||||
updateAndEmit('onFetch', { val: originalData.value });
|
updateAndEmit('onFetch', { val: originalData.value });
|
||||||
|
@ -385,7 +381,7 @@ defineExpose({
|
||||||
<QBtnDropdown
|
<QBtnDropdown
|
||||||
data-cy="saveAndContinueDefaultBtn"
|
data-cy="saveAndContinueDefaultBtn"
|
||||||
v-if="$props.goTo"
|
v-if="$props.goTo"
|
||||||
@click="saveAndGo"
|
@click="submitForm"
|
||||||
:label="
|
:label="
|
||||||
tMobile('globals.saveAndContinue') +
|
tMobile('globals.saveAndContinue') +
|
||||||
' ' +
|
' ' +
|
||||||
|
@ -405,7 +401,7 @@ defineExpose({
|
||||||
<QItem
|
<QItem
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
@click="save"
|
@click="submitForm"
|
||||||
:title="t('globals.save')"
|
:title="t('globals.save')"
|
||||||
>
|
>
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
|
|
|
@ -182,9 +182,10 @@ const col = computed(() => {
|
||||||
if ($props.default && !newColumn.component) newColumn.component = $props.default;
|
if ($props.default && !newColumn.component) newColumn.component = $props.default;
|
||||||
|
|
||||||
if (typeof newColumn.component !== 'string') {
|
if (typeof newColumn.component !== 'string') {
|
||||||
newColumn.attrs = { ...newColumn.component.attrs, autofocus: $props.autofocus };
|
newColumn.attrs = { ...newColumn.component?.attrs, autofocus: $props.autofocus };
|
||||||
newColumn.event = { ...newColumn.component.event, ...$props?.eventHandlers };
|
newColumn.event = { ...newColumn.component?.event, ...$props?.eventHandlers };
|
||||||
}
|
}
|
||||||
|
|
||||||
return newColumn;
|
return newColumn;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useClipboard } from 'src/composables/useClipboard';
|
||||||
|
|
||||||
|
const { copyText } = useClipboard();
|
||||||
|
const target = ref();
|
||||||
|
const qmenuRef = ref();
|
||||||
|
const colField = ref();
|
||||||
|
let colValue = '';
|
||||||
|
let textValue = '';
|
||||||
|
|
||||||
|
defineExpose({ handler });
|
||||||
|
|
||||||
|
const arrayData = defineModel({
|
||||||
|
type: Object,
|
||||||
|
});
|
||||||
|
|
||||||
|
function handler(event) {
|
||||||
|
const clickedElement = event.target.closest('td');
|
||||||
|
if (!clickedElement) return;
|
||||||
|
event.preventDefault();
|
||||||
|
target.value = event.target;
|
||||||
|
qmenuRef.value.show();
|
||||||
|
colField.value = clickedElement.getAttribute('data-col-field');
|
||||||
|
colValue = isNaN(+clickedElement.getAttribute('data-col-value'))
|
||||||
|
? clickedElement.getAttribute('data-col-value')
|
||||||
|
: +clickedElement.getAttribute('data-col-value');
|
||||||
|
textValue = getDeepestText(clickedElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeepestText(node) {
|
||||||
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, {
|
||||||
|
acceptNode: (textNode) => {
|
||||||
|
return textNode.nodeValue.trim()
|
||||||
|
? NodeFilter.FILTER_ACCEPT
|
||||||
|
: NodeFilter.FILTER_REJECT;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let lastText = '';
|
||||||
|
while (walker.nextNode()) {
|
||||||
|
lastText = walker.currentNode.nodeValue.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastText;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectionFilter() {
|
||||||
|
await arrayData.value.addFilter({ params: { [colField.value]: colValue } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectionExclude() {
|
||||||
|
await arrayData.value.addFilter({
|
||||||
|
params: { [colField.value]: { neq: colValue } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectionRemoveFilter() {
|
||||||
|
await arrayData.value.addFilter({ params: { [colField.value]: undefined } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeAllFilters() {
|
||||||
|
await arrayData.value.applyFilter({ params: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyValue() {
|
||||||
|
copyText(textValue, {
|
||||||
|
component: {
|
||||||
|
copyValue: textValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<QMenu
|
||||||
|
ref="qmenuRef"
|
||||||
|
:target
|
||||||
|
class="column q-pa-sm justify-left"
|
||||||
|
auto-close
|
||||||
|
no-parent-event
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
icon="filter_list"
|
||||||
|
@click="selectionFilter()"
|
||||||
|
class="text-weight-regular"
|
||||||
|
align="left"
|
||||||
|
:label="$t('Filter by selection')"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
icon="dangerous"
|
||||||
|
@click="selectionExclude()"
|
||||||
|
class="text-weight-regular"
|
||||||
|
align="left"
|
||||||
|
:label="$t('Exclude selection')"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
icon="filter_list_off"
|
||||||
|
@click="selectionRemoveFilter()"
|
||||||
|
class="text-weight-regular"
|
||||||
|
align="left"
|
||||||
|
:label="$t('Remove filter')"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
icon="filter_list_off"
|
||||||
|
@click="removeAllFilters()"
|
||||||
|
class="text-weight-regular"
|
||||||
|
align="left"
|
||||||
|
:label="$t('Remove all filters')"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
icon="file_copy"
|
||||||
|
@click="copyValue()"
|
||||||
|
class="text-weight-regular"
|
||||||
|
align="left"
|
||||||
|
:label="$t('Copy value')"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
</QMenu>
|
||||||
|
</template>
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Filter by selection: Filtro por selección
|
||||||
|
Exclude selection: Excluir selección
|
||||||
|
Remove filter: Quitar filtro por selección
|
||||||
|
Remove all filters: Eliminar todos los filtros
|
||||||
|
Copy value: Copiar valor
|
||||||
|
</i18n>
|
|
@ -43,7 +43,9 @@ const columnFilter = computed(() => $props.column?.columnFilter);
|
||||||
|
|
||||||
const updateEvent = { 'update:modelValue': addFilter };
|
const updateEvent = { 'update:modelValue': addFilter };
|
||||||
const enterEvent = {
|
const enterEvent = {
|
||||||
'keyup.enter': () => addFilter(model.value),
|
keyup: ({ key }) => {
|
||||||
|
if (key === 'Enter') addFilter(model.value);
|
||||||
|
},
|
||||||
remove: () => addFilter(null),
|
remove: () => addFilter(null),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,7 +112,6 @@ const components = {
|
||||||
component: markRaw(VnCheckbox),
|
component: markRaw(VnCheckbox),
|
||||||
event: updateEvent,
|
event: updateEvent,
|
||||||
attrs: {
|
attrs: {
|
||||||
class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit',
|
|
||||||
'toggle-indeterminate': true,
|
'toggle-indeterminate': true,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
},
|
},
|
||||||
|
@ -136,6 +137,9 @@ async function addFilter(value, name) {
|
||||||
value = value === '' ? undefined : value;
|
value = value === '' ? undefined : value;
|
||||||
let field = columnFilter.value?.name ?? $props.column.name ?? name;
|
let field = columnFilter.value?.name ?? $props.column.name ?? name;
|
||||||
|
|
||||||
|
delete arrayData.store?.userParams?.[field];
|
||||||
|
delete arrayData.store?.filter?.where?.[field];
|
||||||
|
|
||||||
if (columnFilter.value?.inWhere) {
|
if (columnFilter.value?.inWhere) {
|
||||||
if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field;
|
if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field;
|
||||||
return await arrayData.addFilterWhere({ [field]: value });
|
return await arrayData.addFilterWhere({ [field]: value });
|
||||||
|
|
|
@ -33,6 +33,7 @@ import VnTableOrder from 'src/components/VnTable/VnOrder.vue';
|
||||||
import VnTableFilter from './VnTableFilter.vue';
|
import VnTableFilter from './VnTableFilter.vue';
|
||||||
import { getColAlign } from 'src/composables/getColAlign';
|
import { getColAlign } from 'src/composables/getColAlign';
|
||||||
import RightMenu from '../common/RightMenu.vue';
|
import RightMenu from '../common/RightMenu.vue';
|
||||||
|
import VnContextMenu from './VnContextMenu.vue';
|
||||||
import VnScroll from '../common/VnScroll.vue';
|
import VnScroll from '../common/VnScroll.vue';
|
||||||
import VnCheckboxMenu from '../common/VnCheckboxMenu.vue';
|
import VnCheckboxMenu from '../common/VnCheckboxMenu.vue';
|
||||||
import VnCheckbox from '../common/VnCheckbox.vue';
|
import VnCheckbox from '../common/VnCheckbox.vue';
|
||||||
|
@ -150,6 +151,10 @@ const $props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: 'vnTable',
|
default: 'vnTable',
|
||||||
},
|
},
|
||||||
|
selectionFn: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -178,8 +183,9 @@ const app = inject('app');
|
||||||
const tableHeight = useTableHeight();
|
const tableHeight = useTableHeight();
|
||||||
const vnScrollRef = ref(null);
|
const vnScrollRef = ref(null);
|
||||||
|
|
||||||
const editingRow = ref(null);
|
const editingRow = ref();
|
||||||
const editingField = ref(null);
|
const editingField = ref();
|
||||||
|
const contextMenuRef = ref({});
|
||||||
const isTableMode = computed(() => mode.value == TABLE_MODE);
|
const isTableMode = computed(() => mode.value == TABLE_MODE);
|
||||||
const selectRegex = /select/;
|
const selectRegex = /select/;
|
||||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||||
|
@ -216,6 +222,7 @@ onBeforeMount(() => {
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if ($props.isEditable) document.addEventListener('click', clickHandler);
|
if ($props.isEditable) document.addEventListener('click', clickHandler);
|
||||||
|
document.addEventListener('contextmenu', contextMenuRef.value.handler);
|
||||||
mode.value =
|
mode.value =
|
||||||
quasar.platform.is.mobile && !$props.disableOption?.card
|
quasar.platform.is.mobile && !$props.disableOption?.card
|
||||||
? CARD_MODE
|
? CARD_MODE
|
||||||
|
@ -239,6 +246,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
onUnmounted(async () => {
|
onUnmounted(async () => {
|
||||||
if ($props.isEditable) document.removeEventListener('click', clickHandler);
|
if ($props.isEditable) document.removeEventListener('click', clickHandler);
|
||||||
|
document.removeEventListener('contextmenu', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -331,10 +339,10 @@ function stopEventPropagation(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload(params) {
|
async function reload(params) {
|
||||||
selected.value = [];
|
selected.value = [];
|
||||||
selectAll.value = false;
|
selectAll.value = false;
|
||||||
CrudModelRef.value.reload(params);
|
await CrudModelRef.value.reload(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function columnName(col) {
|
function columnName(col) {
|
||||||
|
@ -375,28 +383,34 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEditableColumn(column) {
|
function isEditableColumn(column, row) {
|
||||||
const isEditableCol = column?.isEditable ?? true;
|
const isEditableCol =
|
||||||
|
typeof column?.isEditable == 'function'
|
||||||
|
? column?.isEditable(row)
|
||||||
|
: (column?.isEditable ?? true);
|
||||||
|
|
||||||
const isVisible = column?.visible ?? true;
|
const isVisible = column?.visible ?? true;
|
||||||
const hasComponent = column?.component;
|
const hasComponent = column?.component;
|
||||||
|
|
||||||
return $props.isEditable && isVisible && hasComponent && isEditableCol;
|
return $props.isEditable && isVisible && hasComponent && isEditableCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasEditableFormat(column) {
|
function hasEditableFormat(column, row) {
|
||||||
if (isEditableColumn(column)) return 'editable-text';
|
if (isEditableColumn(column, row)) return 'editable-text';
|
||||||
}
|
}
|
||||||
|
|
||||||
const clickHandler = async (event) => {
|
const clickHandler = async (event) => {
|
||||||
const clickedElement = event.target.closest('td');
|
const el = event.target;
|
||||||
const isDateElement = event.target.closest('.q-date');
|
const clickedElement = el.closest('td');
|
||||||
const isTimeElement = event.target.closest('.q-time');
|
const isDateElement = el.closest('.q-date');
|
||||||
const isQSelectDropDown = event.target.closest('.q-select__dropdown-icon');
|
const isTimeElement = el.closest('.q-time');
|
||||||
|
const isQSelectDropDown = el.closest('.q-select__dropdown-icon');
|
||||||
|
const isDialog = el.closest('.q-dialog');
|
||||||
|
|
||||||
if (isDateElement || isTimeElement || isQSelectDropDown) return;
|
if (isDateElement || isTimeElement || isQSelectDropDown || isDialog) return;
|
||||||
|
|
||||||
if (clickedElement === null) {
|
if (clickedElement === null) {
|
||||||
await destroyInput(editingRow.value, editingField.value);
|
destroyInput(editingRow.value, editingField.value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rowIndex = clickedElement.getAttribute('data-row-index');
|
const rowIndex = clickedElement.getAttribute('data-row-index');
|
||||||
|
@ -406,20 +420,25 @@ const clickHandler = async (event) => {
|
||||||
if (editingRow.value !== null && editingField.value !== null) {
|
if (editingRow.value !== null && editingField.value !== null) {
|
||||||
if (editingRow.value == rowIndex && editingField.value == colField) return;
|
if (editingRow.value == rowIndex && editingField.value == colField) return;
|
||||||
|
|
||||||
await destroyInput(editingRow.value, editingField.value);
|
destroyInput(editingRow.value, editingField.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditableColumn(column)) {
|
if (
|
||||||
await renderInput(Number(rowIndex), colField, clickedElement);
|
isEditableColumn(
|
||||||
|
column,
|
||||||
|
CrudModelRef.value.formData[rowIndex ?? editingRow.value],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
renderInput(Number(rowIndex), colField, clickedElement);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleTabKey(event, rowIndex, colField) {
|
function handleTabKey(event, rowIndex, colField) {
|
||||||
if (editingRow.value == rowIndex && editingField.value == colField)
|
if (editingRow.value == rowIndex && editingField.value == colField)
|
||||||
await destroyInput(editingRow.value, editingField.value);
|
destroyInput(editingRow.value, editingField.value);
|
||||||
|
|
||||||
const direction = event.shiftKey ? -1 : 1;
|
const direction = event.shiftKey ? -1 : 1;
|
||||||
const { nextRowIndex, nextColumnName } = await handleTabNavigation(
|
const { nextRowIndex, nextColumnName } = handleTabNavigation(
|
||||||
rowIndex,
|
rowIndex,
|
||||||
colField,
|
colField,
|
||||||
direction,
|
direction,
|
||||||
|
@ -428,10 +447,10 @@ async function handleTabKey(event, rowIndex, colField) {
|
||||||
if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return;
|
if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
await renderInput(nextRowIndex, nextColumnName, null);
|
renderInput(nextRowIndex, nextColumnName, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderInput(rowId, field, clickedElement) {
|
function renderInput(rowId, field, clickedElement) {
|
||||||
editingField.value = field;
|
editingField.value = field;
|
||||||
editingRow.value = rowId;
|
editingRow.value = rowId;
|
||||||
|
|
||||||
|
@ -440,6 +459,7 @@ async function renderInput(rowId, field, clickedElement) {
|
||||||
const row = CrudModelRef.value.formData[rowId];
|
const row = CrudModelRef.value.formData[rowId];
|
||||||
const oldValue = CrudModelRef.value.formData[rowId][column?.name];
|
const oldValue = CrudModelRef.value.formData[rowId][column?.name];
|
||||||
|
|
||||||
|
if (column.disable) return;
|
||||||
if (!clickedElement)
|
if (!clickedElement)
|
||||||
clickedElement = document.querySelector(
|
clickedElement = document.querySelector(
|
||||||
`[data-row-index="${rowId}"][data-col-field="${field}"]`,
|
`[data-row-index="${rowId}"][data-col-field="${field}"]`,
|
||||||
|
@ -468,18 +488,22 @@ async function renderInput(rowId, field, clickedElement) {
|
||||||
} else row[column.name] = value;
|
} else row[column.name] = value;
|
||||||
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
||||||
},
|
},
|
||||||
keyup: async (event) => {
|
keyup: (event) => {
|
||||||
if (event.key === 'Enter')
|
if (event.key === 'Enter') {
|
||||||
await destroyInput(rowId, field, clickedElement);
|
destroyInput(rowId, field, clickedElement);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
keydown: async (event) => {
|
keydown: (event) => {
|
||||||
|
column?.cellEvent?.['keydown']?.(event, row);
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
await handleTabKey(event, rowId, field);
|
handleTabKey(event, rowId, field);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
await destroyInput(rowId, field, clickedElement);
|
destroyInput(rowId, field, clickedElement);
|
||||||
|
event.stopPropagation();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -512,25 +536,32 @@ async function updateSelectValue(value, column, row, oldValue) {
|
||||||
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function destroyInput(rowIndex, field, clickedElement) {
|
function destroyInput(rowIndex, field, clickedElement) {
|
||||||
if (!clickedElement)
|
if (!clickedElement)
|
||||||
clickedElement = document.querySelector(
|
clickedElement = document.querySelector(
|
||||||
`[data-row-index="${rowIndex}"][data-col-field="${field}"]`,
|
`[data-row-index="${rowIndex}"][data-col-field="${field}"]`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (clickedElement) {
|
if (clickedElement) {
|
||||||
await nextTick();
|
const column = $props.columns.find((col) => col.name === field);
|
||||||
|
if (typeof column?.beforeDestroy === 'function')
|
||||||
|
column.beforeDestroy(CrudModelRef.value.formData[rowIndex]);
|
||||||
|
|
||||||
|
nextTick().then(() => {
|
||||||
render(null, clickedElement);
|
render(null, clickedElement);
|
||||||
Array.from(clickedElement.childNodes).forEach((child) => {
|
Array.from(clickedElement.childNodes).forEach((child) => {
|
||||||
child.style.visibility = 'visible';
|
child.style.visibility = 'visible';
|
||||||
child.style.position = '';
|
child.style.position = '';
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editingRow.value !== rowIndex || editingField.value !== field) return;
|
if (editingRow.value !== rowIndex || editingField.value !== field) return;
|
||||||
editingRow.value = null;
|
editingRow.value = null;
|
||||||
editingField.value = null;
|
editingField.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTabNavigation(rowIndex, colName, direction) {
|
function handleTabNavigation(rowIndex, colName, direction) {
|
||||||
const columns = $props.columns;
|
const columns = $props.columns;
|
||||||
const totalColumns = columns.length;
|
const totalColumns = columns.length;
|
||||||
let currentColumnIndex = columns.findIndex((col) => col.name === colName);
|
let currentColumnIndex = columns.findIndex((col) => col.name === colName);
|
||||||
|
@ -542,7 +573,13 @@ async function handleTabNavigation(rowIndex, colName, direction) {
|
||||||
iterations++;
|
iterations++;
|
||||||
newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns;
|
newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns;
|
||||||
|
|
||||||
if (isEditableColumn(columns[newColumnIndex])) break;
|
if (
|
||||||
|
isEditableColumn(
|
||||||
|
columns[newColumnIndex],
|
||||||
|
CrudModelRef.value.formData[rowIndex],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
break;
|
||||||
} while (iterations < totalColumns);
|
} while (iterations < totalColumns);
|
||||||
|
|
||||||
if (iterations >= totalColumns + 1) return;
|
if (iterations >= totalColumns + 1) return;
|
||||||
|
@ -648,7 +685,9 @@ const rowCtrlClickFunction = computed(() => {
|
||||||
});
|
});
|
||||||
const handleHeaderSelection = (evt, data) => {
|
const handleHeaderSelection = (evt, data) => {
|
||||||
if (evt === 'updateSelected' && selectAll.value) {
|
if (evt === 'updateSelected' && selectAll.value) {
|
||||||
selected.value = tableRef.value.rows;
|
const fn = $props.selectionFn;
|
||||||
|
const rows = tableRef.value.rows;
|
||||||
|
selected.value = fn ? fn(rows) : rows;
|
||||||
} else if (evt === 'selectAll') {
|
} else if (evt === 'selectAll') {
|
||||||
selected.value = data;
|
selected.value = data;
|
||||||
} else {
|
} else {
|
||||||
|
@ -694,7 +733,6 @@ const handleHeaderSelection = (evt, data) => {
|
||||||
:search-url="searchUrl"
|
:search-url="searchUrl"
|
||||||
:disable-infinite-scroll="isTableMode"
|
:disable-infinite-scroll="isTableMode"
|
||||||
:before-save-fn="removeTextValue"
|
:before-save-fn="removeTextValue"
|
||||||
@save-changes="reload"
|
|
||||||
:has-sub-toolbar="$props.hasSubToolbar ?? isEditable"
|
:has-sub-toolbar="$props.hasSubToolbar ?? isEditable"
|
||||||
:auto-load="hasParams || $attrs['auto-load']"
|
:auto-load="hasParams || $attrs['auto-load']"
|
||||||
>
|
>
|
||||||
|
@ -722,7 +760,15 @@ const handleHeaderSelection = (evt, data) => {
|
||||||
:virtual-scroll="isTableMode"
|
:virtual-scroll="isTableMode"
|
||||||
@virtual-scroll="onVirtualScroll"
|
@virtual-scroll="onVirtualScroll"
|
||||||
@row-click="(event, row) => handleRowClick(event, row)"
|
@row-click="(event, row) => handleRowClick(event, row)"
|
||||||
@update:selected="emit('update:selected', $event)"
|
@update:selected="
|
||||||
|
(evt) => {
|
||||||
|
if ($props.selectionFn) selected = $props.selectionFn(evt);
|
||||||
|
emit(
|
||||||
|
'update:selected',
|
||||||
|
selectionFn ? selectionFn(selected) : selected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"
|
||||||
@selection="(details) => handleSelection(details, rows)"
|
@selection="(details) => handleSelection(details, rows)"
|
||||||
:hide-selected-banner="true"
|
:hide-selected-banner="true"
|
||||||
:data-cy
|
:data-cy
|
||||||
|
@ -835,6 +881,7 @@ const handleHeaderSelection = (evt, data) => {
|
||||||
]"
|
]"
|
||||||
:data-row-index="rowIndex"
|
:data-row-index="rowIndex"
|
||||||
:data-col-field="col?.name"
|
:data-col-field="col?.name"
|
||||||
|
:data-col-value="row?.[col?.name]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="no-padding no-margin"
|
class="no-padding no-margin"
|
||||||
|
@ -859,19 +906,19 @@ const handleHeaderSelection = (evt, data) => {
|
||||||
: getToggleIcon(row[col?.name])
|
: getToggleIcon(row[col?.name])
|
||||||
"
|
"
|
||||||
style="color: var(--vn-text-color)"
|
style="color: var(--vn-text-color)"
|
||||||
:class="hasEditableFormat(col)"
|
:class="hasEditableFormat(col, row)"
|
||||||
size="14px"
|
size="14px"
|
||||||
/>
|
/>
|
||||||
<QIcon
|
<QIcon
|
||||||
v-else-if="col?.component === 'checkbox'"
|
v-else-if="col?.component === 'checkbox'"
|
||||||
:name="getCheckboxIcon(row[col?.name])"
|
:name="getCheckboxIcon(row[col?.name])"
|
||||||
style="color: var(--vn-text-color)"
|
style="color: var(--vn-text-color)"
|
||||||
:class="hasEditableFormat(col)"
|
:class="hasEditableFormat(col, row)"
|
||||||
size="14px"
|
size="14px"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
:class="hasEditableFormat(col)"
|
:class="hasEditableFormat(col, row)"
|
||||||
:style="
|
:style="
|
||||||
typeof col?.style == 'function'
|
typeof col?.style == 'function'
|
||||||
? col.style(row)
|
? col.style(row)
|
||||||
|
@ -897,7 +944,11 @@ const handleHeaderSelection = (evt, data) => {
|
||||||
v-for="(btn, index) of col.actions"
|
v-for="(btn, index) of col.actions"
|
||||||
v-show="btn.show ? btn.show(row) : true"
|
v-show="btn.show ? btn.show(row) : true"
|
||||||
:key="index"
|
:key="index"
|
||||||
:title="btn.title"
|
:title="
|
||||||
|
typeof btn.title === 'function'
|
||||||
|
? btn.title(row)
|
||||||
|
: btn.title
|
||||||
|
"
|
||||||
:icon="btn.icon"
|
:icon="btn.icon"
|
||||||
class="q-pa-xs"
|
class="q-pa-xs"
|
||||||
flat
|
flat
|
||||||
|
@ -1145,6 +1196,7 @@ const handleHeaderSelection = (evt, data) => {
|
||||||
</template>
|
</template>
|
||||||
</FormModelPopup>
|
</FormModelPopup>
|
||||||
</QDialog>
|
</QDialog>
|
||||||
|
<VnContextMenu ref="contextMenuRef" v-model="arrayData" />
|
||||||
<VnScroll
|
<VnScroll
|
||||||
ref="vnScrollRef"
|
ref="vnScrollRef"
|
||||||
v-if="isTableMode"
|
v-if="isTableMode"
|
||||||
|
@ -1205,7 +1257,7 @@ es:
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-header {
|
.bg-header {
|
||||||
background-color: var(--vn-accent-color);
|
background-color: var(--vn-section-color);
|
||||||
color: var(--vn-text-color);
|
color: var(--vn-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,12 @@ function columnName(col) {
|
||||||
<template #tags="{ tag, formatFn, getLocale }">
|
<template #tags="{ tag, formatFn, getLocale }">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
<strong>{{ getLocale(`${tag.label}`) }}: </strong>
|
<strong>{{ getLocale(`${tag.label}`) }}: </strong>
|
||||||
<span>{{ formatFn(tag.value) }}</span>
|
<span
|
||||||
|
:class="{
|
||||||
|
'text-decoration-line-through': typeof chip === 'object',
|
||||||
|
}"
|
||||||
|
>{{ formatFn(tag) }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
|
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||||
|
import { createWrapper } from 'app/test/vitest/helper';
|
||||||
|
import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
|
||||||
|
|
||||||
|
describe('VnAccountNumber', () => {
|
||||||
|
let wrapper;
|
||||||
|
let input;
|
||||||
|
let vnInput;
|
||||||
|
let spyShort;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = createWrapper(VnAccountNumber);
|
||||||
|
wrapper = wrapper.wrapper;
|
||||||
|
input = wrapper.find('input');
|
||||||
|
vnInput = wrapper.findComponent({ name: 'VnInput' });
|
||||||
|
spyShort = vi.spyOn(wrapper.vm, 'useAccountShortToStandard');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter out non-numeric characters on input event', async () => {
|
||||||
|
await input.setValue('abc123.45!@#');
|
||||||
|
const emitted = wrapper.emitted('update:modelValue');
|
||||||
|
expect(emitted.pop()[0]).toBe('123.45');
|
||||||
|
expect(spyShort).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply conversion on blur when valid short value is provided', async () => {
|
||||||
|
await input.setValue('123.45');
|
||||||
|
await vnInput.trigger('blur');
|
||||||
|
|
||||||
|
const emitted = wrapper.emitted('update:modelValue');
|
||||||
|
expect(emitted.pop()[0]).toBe('1230000045');
|
||||||
|
expect(spyShort).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change value for invalid input values', async () => {
|
||||||
|
await input.setValue('123');
|
||||||
|
await vnInput.trigger('blur');
|
||||||
|
|
||||||
|
const emitted = wrapper.emitted('update:modelValue');
|
||||||
|
expect(emitted.pop()[0]).toBe('123');
|
||||||
|
expect(spyShort).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -36,8 +36,6 @@ const validate = async () => {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
await props.submitFn(newPassword, oldPassword);
|
await props.submitFn(newPassword, oldPassword);
|
||||||
emit('onSubmit');
|
emit('onSubmit');
|
||||||
} catch (e) {
|
|
||||||
notify('errors.writeRequest', 'negative');
|
|
||||||
} finally {
|
} finally {
|
||||||
changePassDialog.value.hide();
|
changePassDialog.value.hide();
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|
|
@ -17,8 +17,6 @@ const $props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['blur']);
|
|
||||||
|
|
||||||
const componentArray = computed(() => {
|
const componentArray = computed(() => {
|
||||||
if (typeof $props.prop === 'object') return [$props.prop];
|
if (typeof $props.prop === 'object') return [$props.prop];
|
||||||
return $props.prop;
|
return $props.prop;
|
||||||
|
@ -57,7 +55,6 @@ function toValueAttrs(attrs) {
|
||||||
v-bind="mix(toComponent).attrs"
|
v-bind="mix(toComponent).attrs"
|
||||||
v-on="mix(toComponent).event ?? {}"
|
v-on="mix(toComponent).event ?? {}"
|
||||||
v-model="model"
|
v-model="model"
|
||||||
@blur="emit('blur')"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -7,7 +7,6 @@ import axios from 'axios';
|
||||||
import { usePrintService } from 'composables/usePrintService';
|
import { usePrintService } from 'composables/usePrintService';
|
||||||
|
|
||||||
import VnUserLink from '../ui/VnUserLink.vue';
|
import VnUserLink from '../ui/VnUserLink.vue';
|
||||||
import { downloadFile } from 'src/composables/downloadFile';
|
|
||||||
import VnImg from 'components/ui/VnImg.vue';
|
import VnImg from 'components/ui/VnImg.vue';
|
||||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||||
import VnDms from 'src/components/common/VnDms.vue';
|
import VnDms from 'src/components/common/VnDms.vue';
|
||||||
|
|
|
@ -6,13 +6,7 @@ import { useRequired } from 'src/composables/useRequired';
|
||||||
const $attrs = useAttrs();
|
const $attrs = useAttrs();
|
||||||
const { isRequired, requiredFieldRule } = useRequired($attrs);
|
const { isRequired, requiredFieldRule } = useRequired($attrs);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const emit = defineEmits([
|
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
|
||||||
'update:modelValue',
|
|
||||||
'update:options',
|
|
||||||
'keyup.enter',
|
|
||||||
'remove',
|
|
||||||
'blur',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
@ -136,8 +130,6 @@ const handleUppercase = () => {
|
||||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||||
:type="$attrs.type"
|
:type="$attrs.type"
|
||||||
:class="{ required: isRequired }"
|
:class="{ required: isRequired }"
|
||||||
@keyup.enter="emit('keyup.enter')"
|
|
||||||
@blur="emit('blur')"
|
|
||||||
@keydown="handleKeydown"
|
@keydown="handleKeydown"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
:rules="mixinRules"
|
:rules="mixinRules"
|
||||||
|
|
|
@ -51,6 +51,7 @@ const validateAndCleanInput = (value) => {
|
||||||
|
|
||||||
const manageDate = (date) => {
|
const manageDate = (date) => {
|
||||||
inputValue.value = date.split('/').reverse().join('/');
|
inputValue.value = date.split('/').reverse().join('/');
|
||||||
|
formatDate();
|
||||||
isPopupOpen.value = false;
|
isPopupOpen.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -170,7 +171,7 @@ const handleEnter = (event) => {
|
||||||
:input-style="{ color: textColor }"
|
:input-style="{ color: textColor }"
|
||||||
@click="isPopupOpen = !isPopupOpen"
|
@click="isPopupOpen = !isPopupOpen"
|
||||||
@keydown="isPopupOpen = false"
|
@keydown="isPopupOpen = false"
|
||||||
@blur="formatDate"
|
@focusout="formatDate"
|
||||||
@keydown.enter.prevent="handleEnter"
|
@keydown.enter.prevent="handleEnter"
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"
|
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, watch, computed, ref, useAttrs } from 'vue';
|
||||||
|
import { date } from 'quasar';
|
||||||
|
import VnDate from './VnDate.vue';
|
||||||
|
import { useRequired } from 'src/composables/useRequired';
|
||||||
|
|
||||||
|
const $attrs = useAttrs();
|
||||||
|
const { isRequired, requiredFieldRule } = useRequired($attrs);
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
isOutlined: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isPopupOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const model = defineModel({
|
||||||
|
type: [String, Date, Array],
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const vnInputDateRef = ref(null);
|
||||||
|
|
||||||
|
const dateFormat = 'DD/MM/YYYY';
|
||||||
|
const isPopupOpen = ref();
|
||||||
|
const hover = ref();
|
||||||
|
const mask = ref();
|
||||||
|
|
||||||
|
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
|
||||||
|
|
||||||
|
const formattedDate = computed({
|
||||||
|
get() {
|
||||||
|
if (!model.value) return model.value;
|
||||||
|
if ($props.multiple) {
|
||||||
|
return model.value
|
||||||
|
.map((d) => date.formatDate(new Date(d), dateFormat))
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
return date.formatDate(new Date(model.value), dateFormat);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
if (value == model.value) return;
|
||||||
|
if ($props.multiple) return; // No permitir edición manual en modo múltiple
|
||||||
|
|
||||||
|
let newDate;
|
||||||
|
if (value) {
|
||||||
|
// parse input
|
||||||
|
if (value.includes('/') && value.length >= 10) {
|
||||||
|
if (value.at(2) == '/') value = value.split('/').reverse().join('/');
|
||||||
|
value = date.formatDate(
|
||||||
|
new Date(value).toISOString(),
|
||||||
|
'YYYY-MM-DDTHH:mm:ss.SSSZ',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const [year, month, day] = value.split('-').map((e) => parseInt(e));
|
||||||
|
newDate = new Date(year, month - 1, day);
|
||||||
|
if (model.value && !$props.multiple) {
|
||||||
|
const orgDate =
|
||||||
|
model.value instanceof Date ? model.value : new Date(model.value);
|
||||||
|
|
||||||
|
newDate.setHours(
|
||||||
|
orgDate.getHours(),
|
||||||
|
orgDate.getMinutes(),
|
||||||
|
orgDate.getSeconds(),
|
||||||
|
orgDate.getMilliseconds(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isNaN(newDate)) model.value = newDate.toISOString();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const popupDate = computed(() => {
|
||||||
|
if (!model.value) return model.value;
|
||||||
|
if ($props.multiple) {
|
||||||
|
return model.value.map((d) => date.formatDate(new Date(d), 'YYYY/MM/DD'));
|
||||||
|
}
|
||||||
|
return date.formatDate(new Date(model.value), 'YYYY/MM/DD');
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
// fix quasar bug
|
||||||
|
mask.value = '##/##/####';
|
||||||
|
if ($props.multiple && !model.value) {
|
||||||
|
model.value = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => model.value,
|
||||||
|
(val) => (formattedDate.value = val),
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const styleAttrs = computed(() => {
|
||||||
|
return $props.isOutlined
|
||||||
|
? {
|
||||||
|
dense: true,
|
||||||
|
outlined: true,
|
||||||
|
rounded: true,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
});
|
||||||
|
|
||||||
|
const manageDate = (dates) => {
|
||||||
|
if ($props.multiple) {
|
||||||
|
model.value = dates.map((d) => new Date(d).toISOString());
|
||||||
|
} else {
|
||||||
|
formattedDate.value = dates;
|
||||||
|
}
|
||||||
|
if ($props.isPopupOpen) isPopupOpen.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div @mouseover="hover = true" @mouseleave="hover = false">
|
||||||
|
<QInput
|
||||||
|
ref="vnInputDateRef"
|
||||||
|
v-model="formattedDate"
|
||||||
|
class="vn-input-date"
|
||||||
|
:mask="$props.multiple ? undefined : mask"
|
||||||
|
placeholder="dd/mm/aaaa"
|
||||||
|
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||||
|
:class="{ required: isRequired }"
|
||||||
|
:rules="mixinRules"
|
||||||
|
:clearable="false"
|
||||||
|
@click="isPopupOpen = !isPopupOpen"
|
||||||
|
@keydown="isPopupOpen = false"
|
||||||
|
hide-bottom-space
|
||||||
|
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon
|
||||||
|
name="close"
|
||||||
|
size="xs"
|
||||||
|
v-if="
|
||||||
|
($attrs.clearable == undefined || $attrs.clearable) &&
|
||||||
|
hover &&
|
||||||
|
model &&
|
||||||
|
!$attrs.disable
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
vnInputDateRef.focus();
|
||||||
|
model = null;
|
||||||
|
isPopupOpen = false;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<QMenu
|
||||||
|
v-if="$q.screen.gt.xs"
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
v-model="isPopupOpen"
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top start"
|
||||||
|
:no-focus="true"
|
||||||
|
:no-parent-event="true"
|
||||||
|
>
|
||||||
|
<VnDate
|
||||||
|
v-model="popupDate"
|
||||||
|
@update:model-value="manageDate"
|
||||||
|
:multiple="multiple"
|
||||||
|
/>
|
||||||
|
</QMenu>
|
||||||
|
<QDialog v-else v-model="isPopupOpen">
|
||||||
|
<VnDate
|
||||||
|
v-model="popupDate"
|
||||||
|
@update:model-value="manageDate"
|
||||||
|
:multiple="multiple"
|
||||||
|
/>
|
||||||
|
</QDialog>
|
||||||
|
</QInput>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Open date: Abrir fecha
|
||||||
|
</i18n>
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { date } from 'quasar';
|
import { date } from 'quasar';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
@ -21,7 +21,6 @@ const stateStore = useStateStore();
|
||||||
const validationsStore = useValidator();
|
const validationsStore = useValidator();
|
||||||
const { models } = validationsStore;
|
const { models } = validationsStore;
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
model: {
|
model: {
|
||||||
|
@ -273,7 +272,7 @@ onUnmounted(() => {
|
||||||
:data-key
|
:data-key
|
||||||
:url="dataKey + 's'"
|
:url="dataKey + 's'"
|
||||||
:user-filter="filter"
|
:user-filter="filter"
|
||||||
:filter="{ where: { and: [{ originFk: route.params.id }] } }"
|
:user-params="{ originFk: route.params.id }"
|
||||||
:skeleton="false"
|
:skeleton="false"
|
||||||
auto-load
|
auto-load
|
||||||
@on-fetch="setLogTree"
|
@on-fetch="setLogTree"
|
||||||
|
|
|
@ -124,6 +124,7 @@ const {
|
||||||
} = toRefs($props);
|
} = toRefs($props);
|
||||||
const myOptions = ref([]);
|
const myOptions = ref([]);
|
||||||
const myOptionsOriginal = ref([]);
|
const myOptionsOriginal = ref([]);
|
||||||
|
const myOptionsMap = ref(new Map());
|
||||||
const vnSelectRef = ref();
|
const vnSelectRef = ref();
|
||||||
const lastVal = ref();
|
const lastVal = ref();
|
||||||
const noOneText = t('globals.noOne');
|
const noOneText = t('globals.noOne');
|
||||||
|
@ -140,7 +141,7 @@ const styleAttrs = computed(() => {
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
});
|
});
|
||||||
const isLoading = ref(false);
|
const hasFocus = ref(false);
|
||||||
const useURL = computed(() => $props.url);
|
const useURL = computed(() => $props.url);
|
||||||
const value = computed({
|
const value = computed({
|
||||||
get() {
|
get() {
|
||||||
|
@ -166,6 +167,10 @@ const computedSortBy = computed(() => {
|
||||||
return $props.sortBy || $props.optionLabel + ' ASC';
|
return $props.sortBy || $props.optionLabel + ' ASC';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const valueIsObject = computed(
|
||||||
|
() => modelValue.value && typeof modelValue.value == 'object',
|
||||||
|
);
|
||||||
|
|
||||||
const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
|
const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
|
||||||
|
|
||||||
watch(options, (newValue) => {
|
watch(options, (newValue) => {
|
||||||
|
@ -173,12 +178,22 @@ watch(options, (newValue) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(modelValue, async (newValue) => {
|
watch(modelValue, async (newValue) => {
|
||||||
|
if (newValue?.neq) newValue = newValue.neq;
|
||||||
if (!myOptions?.value?.some((option) => option[optionValue.value] == newValue))
|
if (!myOptions?.value?.some((option) => option[optionValue.value] == newValue))
|
||||||
await fetchFilter(newValue);
|
await fetchFilter(newValue);
|
||||||
|
|
||||||
if ($props.noOne) myOptions.value.unshift(noOneOpt.value);
|
if ($props.noOne) myOptions.value.unshift(noOneOpt.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => myOptionsOriginal.value,
|
||||||
|
(newValue) => {
|
||||||
|
for (const item of newValue) {
|
||||||
|
myOptionsMap.value.set(item[optionValue.value], item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setOptions(options.value);
|
setOptions(options.value);
|
||||||
if (useURL.value && $props.modelValue && !findKeyInOptions())
|
if (useURL.value && $props.modelValue && !findKeyInOptions())
|
||||||
|
@ -187,7 +202,7 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const someIsLoading = computed(
|
const someIsLoading = computed(
|
||||||
() => (isLoading.value || !!arrayData?.isLoading?.value) && !isMenuOpened.value,
|
() => !!arrayData?.isLoading?.value && !isMenuOpened.value,
|
||||||
);
|
);
|
||||||
function findKeyInOptions() {
|
function findKeyInOptions() {
|
||||||
if (!$props.options) return;
|
if (!$props.options) return;
|
||||||
|
@ -224,6 +239,9 @@ function filter(val, options) {
|
||||||
|
|
||||||
async function fetchFilter(val) {
|
async function fetchFilter(val) {
|
||||||
if (!$props.url) return;
|
if (!$props.url) return;
|
||||||
|
if (val && typeof val == 'object') {
|
||||||
|
val = val.neq;
|
||||||
|
}
|
||||||
|
|
||||||
const { fields, include, limit } = $props;
|
const { fields, include, limit } = $props;
|
||||||
const sortBy = computedSortBy.value;
|
const sortBy = computedSortBy.value;
|
||||||
|
@ -298,13 +316,11 @@ async function onScroll({ to, direction, from, index }) {
|
||||||
if (from === 0 && index === 0) return;
|
if (from === 0 && index === 0) return;
|
||||||
if (!useURL.value && !$props.fetchRef) return;
|
if (!useURL.value && !$props.fetchRef) return;
|
||||||
if (direction === 'decrease') return;
|
if (direction === 'decrease') return;
|
||||||
if (to === lastIndex && arrayData.store.hasMoreData && !isLoading.value) {
|
if (to === lastIndex && arrayData.store.hasMoreData) {
|
||||||
isLoading.value = true;
|
|
||||||
await arrayData.loadMore();
|
await arrayData.loadMore();
|
||||||
setOptions(arrayData.store.data);
|
setOptions(arrayData.store.data);
|
||||||
vnSelectRef.value.scrollTo(lastIndex);
|
vnSelectRef.value.scrollTo(lastIndex);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
isLoading.value = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,22 +363,30 @@ function getCaption(opt) {
|
||||||
if (optionCaption.value === false) return;
|
if (optionCaption.value === false) return;
|
||||||
return opt[optionCaption.value] || opt[optionValue.value];
|
return opt[optionCaption.value] || opt[optionValue.value];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOptionLabel(property) {
|
||||||
|
if (!myOptionsMap.value.size) return;
|
||||||
|
let value = modelValue.value;
|
||||||
|
if (property) {
|
||||||
|
value = modelValue.value[property];
|
||||||
|
}
|
||||||
|
return myOptionsMap.value.get(value)?.[optionLabel.value];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<QSelect
|
<QSelect
|
||||||
|
ref="vnSelectRef"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
:options="myOptions"
|
:options="myOptions"
|
||||||
:option-label="optionLabel"
|
:option-label="optionLabel"
|
||||||
:option-value="optionValue"
|
:option-value="optionValue"
|
||||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
v-bind="{ ...$attrs, ...styleAttrs, hideSelected: hasFocus }"
|
||||||
@filter="filterHandler"
|
@filter="filterHandler"
|
||||||
:emit-value="nullishToTrue($attrs['emit-value'])"
|
:emit-value="nullishToTrue($attrs['emit-value'])"
|
||||||
:map-options="nullishToTrue($attrs['map-options'])"
|
:map-options="nullishToTrue($attrs['map-options'])"
|
||||||
:use-input="nullishToTrue($attrs['use-input'])"
|
:use-input="hasFocus || !value"
|
||||||
:hide-selected="nullishToTrue($attrs['hide-selected'])"
|
:fill-input="false"
|
||||||
:fill-input="nullishToTrue($attrs['fill-input'])"
|
|
||||||
ref="vnSelectRef"
|
|
||||||
lazy-rules
|
lazy-rules
|
||||||
:class="{ required: isRequired }"
|
:class="{ required: isRequired }"
|
||||||
:rules="mixinRules"
|
:rules="mixinRules"
|
||||||
|
@ -372,10 +396,20 @@ function getCaption(opt) {
|
||||||
:loading="someIsLoading"
|
:loading="someIsLoading"
|
||||||
@virtual-scroll="onScroll"
|
@virtual-scroll="onScroll"
|
||||||
@popup-hide="isMenuOpened = false"
|
@popup-hide="isMenuOpened = false"
|
||||||
@popup-show="isMenuOpened = true"
|
@popup-show="
|
||||||
|
async () => {
|
||||||
|
isMenuOpened = true;
|
||||||
|
hasFocus = true;
|
||||||
|
await $nextTick();
|
||||||
|
vnSelectRef?.$el?.querySelector('input')?.focus();
|
||||||
|
}
|
||||||
|
"
|
||||||
@keydown="handleKeyDown"
|
@keydown="handleKeyDown"
|
||||||
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
|
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
|
||||||
:data-url="url"
|
:data-url="url"
|
||||||
|
@blur="hasFocus = false"
|
||||||
|
@update:model-value="() => vnSelectRef.blur()"
|
||||||
|
:is-required="false"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<QIcon
|
<QIcon
|
||||||
|
@ -411,7 +445,12 @@ function getCaption(opt) {
|
||||||
</template>
|
</template>
|
||||||
<template #option="{ opt, itemProps }">
|
<template #option="{ opt, itemProps }">
|
||||||
<QItem v-bind="itemProps">
|
<QItem v-bind="itemProps">
|
||||||
<QItemSection v-if="typeof opt !== 'object'"> {{ opt }}</QItemSection>
|
<QItemSection v-if="typeof optionLabel === 'function'">{{
|
||||||
|
optionLabel(opt)
|
||||||
|
}}</QItemSection>
|
||||||
|
<QItemSection v-else-if="typeof opt !== 'object'">
|
||||||
|
{{ opt }}</QItemSection
|
||||||
|
>
|
||||||
<QItemSection v-else-if="opt[optionValue] == opt[optionLabel]">
|
<QItemSection v-else-if="opt[optionValue] == opt[optionLabel]">
|
||||||
<QItemLabel>{{ opt[optionLabel] }}</QItemLabel>
|
<QItemLabel>{{ opt[optionLabel] }}</QItemLabel>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
|
@ -425,6 +464,17 @@ function getCaption(opt) {
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
</template>
|
</template>
|
||||||
|
<template #selected v-if="valueIsObject && nullishToTrue($attrs['emit-value'])">
|
||||||
|
<span class="nowrap">
|
||||||
|
<span
|
||||||
|
class="text-strike"
|
||||||
|
v-if="modelValue?.neq"
|
||||||
|
v-text="getOptionLabel('neq')"
|
||||||
|
:title="getOptionLabel('neq')"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ JSON.stringify(modelValue) }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
</QSelect>
|
</QSelect>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -432,4 +482,12 @@ function getCaption(opt) {
|
||||||
.q-field--outlined {
|
.q-field--outlined {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
.q-field__native {
|
||||||
|
@extend .nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nowrap {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, useTemplateRef } from 'vue';
|
import { computed, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
|
|
||||||
import VnSelectDialog from './VnSelectDialog.vue';
|
import VnSelectDialog from './VnSelectDialog.vue';
|
||||||
import CreateNewExpenseForm from '../CreateNewExpenseForm.vue';
|
import CreateNewExpenseForm from '../CreateNewExpenseForm.vue';
|
||||||
import FetchData from '../FetchData.vue';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
|
||||||
const model = defineModel({ type: [String, Number, Object] });
|
const model = defineModel({ type: [String, Number, Object] });
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const expenses = ref([]);
|
const arrayData = useArrayData('expenses', { url: 'Expenses' });
|
||||||
const selectDialogRef = useTemplateRef('selectDialogRef');
|
const expenses = computed(() => arrayData.store.data);
|
||||||
|
|
||||||
async function autocompleteExpense(evt) {
|
onMounted(async () => await arrayData.fetch({}));
|
||||||
const val = evt.target.value;
|
|
||||||
if (!val || isNaN(val)) return;
|
async function updateModel(evt) {
|
||||||
const lookup = expenses.value.find(({ id }) => id == useAccountShortToStandard(val));
|
await arrayData.fetch({});
|
||||||
if (selectDialogRef.value)
|
model.value = evt.id;
|
||||||
selectDialogRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -30,18 +28,11 @@ async function autocompleteExpense(evt) {
|
||||||
:filter-options="['id', 'name']"
|
:filter-options="['id', 'name']"
|
||||||
:tooltip="t('Create a new expense')"
|
:tooltip="t('Create a new expense')"
|
||||||
:acls="[{ model: 'Expense', props: '*', accessType: 'WRITE' }]"
|
:acls="[{ model: 'Expense', props: '*', accessType: 'WRITE' }]"
|
||||||
@keydown.tab.prevent="autocompleteExpense"
|
|
||||||
>
|
>
|
||||||
<template #form>
|
<template #form>
|
||||||
<CreateNewExpenseForm @on-data-saved="$refs.expensesRef.fetch()" />
|
<CreateNewExpenseForm @on-data-saved="updateModel" />
|
||||||
</template>
|
</template>
|
||||||
</VnSelectDialog>
|
</VnSelectDialog>
|
||||||
<FetchData
|
|
||||||
ref="expensesRef"
|
|
||||||
url="Expenses"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (expenses = data)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
|
|
|
@ -162,7 +162,6 @@ async function fetch() {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
.label {
|
.label {
|
||||||
color: var(--vn-label-color);
|
color: var(--vn-label-color);
|
||||||
width: 9em;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: wrap;
|
white-space: wrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
|
@ -236,10 +236,6 @@ const toModule = computed(() => {
|
||||||
.label {
|
.label {
|
||||||
color: var(--vn-label-color);
|
color: var(--vn-label-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
&:not(:has(a))::after {
|
|
||||||
content: ':';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&.ellipsis > .value {
|
&.ellipsis > .value {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
|
@ -186,6 +186,7 @@ async function remove(key) {
|
||||||
function formatValue(value) {
|
function formatValue(value) {
|
||||||
if (typeof value === 'boolean') return value ? t('Yes') : t('No');
|
if (typeof value === 'boolean') return value ? t('Yes') : t('No');
|
||||||
if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value);
|
if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value);
|
||||||
|
if (value && typeof value === 'object') return '';
|
||||||
|
|
||||||
return `"${value}"`;
|
return `"${value}"`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import noImage from '/no-user.png';
|
import noUser from '/no-user.png';
|
||||||
import { useRole } from 'src/composables/useRole';
|
import { useRole } from 'src/composables/useRole';
|
||||||
|
import { getDarkSuffix } from 'src/composables/getDarkSuffix';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
storage: {
|
storage: {
|
||||||
|
@ -43,7 +44,7 @@ const getUrl = (zoom = false) => {
|
||||||
return `/api/${$props.storage}/${$props.id}/downloadFile?access_token=${token}`;
|
return `/api/${$props.storage}/${$props.id}/downloadFile?access_token=${token}`;
|
||||||
return isEmployee
|
return isEmployee
|
||||||
? `/api/${$props.storage}/${$props.collection}/${curResolution}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
|
? `/api/${$props.storage}/${$props.collection}/${curResolution}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
|
||||||
: noImage;
|
: noUser;
|
||||||
};
|
};
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
timeStamp.value = `timestamp=${Date.now()}`;
|
timeStamp.value = `timestamp=${Date.now()}`;
|
||||||
|
@ -60,6 +61,7 @@ defineExpose({
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@click.stop="show = $props.zoom"
|
@click.stop="show = $props.zoom"
|
||||||
spinner-color="primary"
|
spinner-color="primary"
|
||||||
|
:error-src="`/no_image${getDarkSuffix()}.png`"
|
||||||
/>
|
/>
|
||||||
<QDialog v-if="$props.zoom" v-model="show">
|
<QDialog v-if="$props.zoom" v-model="show">
|
||||||
<QImg
|
<QImg
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Dark } from 'quasar';
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { getDarkSuffix } from 'src/composables/getDarkSuffix';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
logo: {
|
logo: {
|
||||||
|
@ -12,7 +12,7 @@ const $props = defineProps({
|
||||||
const src = computed({
|
const src = computed({
|
||||||
get() {
|
get() {
|
||||||
return new URL(
|
return new URL(
|
||||||
`../../assets/${$props.logo}${Dark.isActive ? '_dark' : ''}.svg`,
|
`../../assets/${$props.logo}${getDarkSuffix()}.svg`,
|
||||||
import.meta.url,
|
import.meta.url,
|
||||||
).href;
|
).href;
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,12 +42,10 @@ const val = computed(() => $props.value);
|
||||||
<div v-if="label || $slots.label" class="label">
|
<div v-if="label || $slots.label" class="label">
|
||||||
<slot name="label">
|
<slot name="label">
|
||||||
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
|
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
|
||||||
<span style="color: var(--vn-label-color)">
|
<span style="color: var(--vn-label-color)"> {{ label }}: </span>
|
||||||
{{ label }}
|
|
||||||
</span>
|
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="value" v-if="value || $slots.value">
|
<div class="value">
|
||||||
<slot name="value">
|
<slot name="value">
|
||||||
<span :title="value" style="text-overflow: ellipsis">
|
<span :title="value" style="text-overflow: ellipsis">
|
||||||
{{ dash ? dashIfEmpty(value) : value }}
|
{{ dash ? dashIfEmpty(value) : value }}
|
||||||
|
@ -75,21 +73,13 @@ const val = computed(() => $props.value);
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label,
|
|
||||||
.value {
|
|
||||||
white-space: pre-line;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.copy {
|
.copy {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.q-checkbox.disabled) {
|
:deep(.q-checkbox.disabled) {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { toPercentage } from 'filters/index';
|
import { toCurrency, toPercentage } from 'filters/index';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
@ -8,6 +8,10 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
format: {
|
||||||
|
type: String,
|
||||||
|
default: 'percentage', // 'currency'
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const valueClass = computed(() =>
|
const valueClass = computed(() =>
|
||||||
|
@ -21,7 +25,10 @@ const formattedValue = computed(() => props.value);
|
||||||
<template>
|
<template>
|
||||||
<span :class="valueClass">
|
<span :class="valueClass">
|
||||||
<QIcon :name="iconName" size="sm" class="value-icon" />
|
<QIcon :name="iconName" size="sm" class="value-icon" />
|
||||||
{{ toPercentage(formattedValue) }}
|
<span v-if="$props.format === 'percentage'">{{
|
||||||
|
toPercentage(formattedValue)
|
||||||
|
}}</span>
|
||||||
|
<span v-if="$props.format === 'currency'">{{ toCurrency(formattedValue) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ export function getColAlign(col) {
|
||||||
case 'number':
|
case 'number':
|
||||||
align = 'right';
|
align = 'right';
|
||||||
break;
|
break;
|
||||||
case 'time':
|
|
||||||
case 'date':
|
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
align = 'center';
|
align = 'center';
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { Dark } from 'quasar';
|
||||||
|
export function getDarkSuffix() {
|
||||||
|
return Dark.isActive ? '_dark' : '';
|
||||||
|
}
|
|
@ -313,6 +313,7 @@ export function useArrayData(key, userOptions) {
|
||||||
const { params, limit } = getCurrentFilter();
|
const { params, limit } = getCurrentFilter();
|
||||||
store.currentFilter = JSON.parse(JSON.stringify(params));
|
store.currentFilter = JSON.parse(JSON.stringify(params));
|
||||||
delete store.currentFilter.filter.include;
|
delete store.currentFilter.filter.include;
|
||||||
|
delete store.currentFilter.filter.fields;
|
||||||
store.currentFilter.filter = JSON.stringify(store.currentFilter.filter);
|
store.currentFilter.filter = JSON.stringify(store.currentFilter.filter);
|
||||||
return { params, limit };
|
return { params, limit };
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ body.body--light {
|
||||||
--vn-black-text-color: black;
|
--vn-black-text-color: black;
|
||||||
--vn-text-color-contrast: white;
|
--vn-text-color-contrast: white;
|
||||||
--vn-link-color: #1e90ff;
|
--vn-link-color: #1e90ff;
|
||||||
|
--vn-input-underline-color: #bdbdbd;
|
||||||
|
--vn-input-icons-color: #797979;
|
||||||
|
|
||||||
background-color: var(--vn-page-color);
|
background-color: var(--vn-page-color);
|
||||||
|
|
||||||
|
@ -29,7 +31,7 @@ body.body--light {
|
||||||
body.body--dark {
|
body.body--dark {
|
||||||
--vn-header-color: #5d5d5d;
|
--vn-header-color: #5d5d5d;
|
||||||
--vn-page-color: #222;
|
--vn-page-color: #222;
|
||||||
--vn-section-color: #3d3d3d;
|
--vn-section-color: #3c3b3b;
|
||||||
--vn-section-hover-color: #747474;
|
--vn-section-hover-color: #747474;
|
||||||
--vn-text-color: white;
|
--vn-text-color: white;
|
||||||
--vn-label-color: #a8a8a8;
|
--vn-label-color: #a8a8a8;
|
||||||
|
@ -40,6 +42,8 @@ body.body--dark {
|
||||||
--vn-black-text-color: black;
|
--vn-black-text-color: black;
|
||||||
--vn-text-color-contrast: black;
|
--vn-text-color-contrast: black;
|
||||||
--vn-link-color: #66bfff;
|
--vn-link-color: #66bfff;
|
||||||
|
--vn-input-underline-color: #545353;
|
||||||
|
--vn-input-icons-color: #888787;
|
||||||
|
|
||||||
background-color: var(--vn-page-color);
|
background-color: var(--vn-page-color);
|
||||||
|
|
||||||
|
@ -155,7 +159,6 @@ select:-webkit-autofill {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilo para el asterisco en campos requeridos */
|
|
||||||
.q-field.required .q-field__label:after {
|
.q-field.required .q-field__label:after {
|
||||||
content: ' *';
|
content: ' *';
|
||||||
}
|
}
|
||||||
|
@ -290,6 +293,18 @@ input::-webkit-inner-spin-button {
|
||||||
.expand {
|
.expand {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
th {
|
||||||
|
border-bottom: 1px solid var(--vn-page-color) !important;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border-color: var(--vn-page-color);
|
||||||
|
}
|
||||||
|
div.q-field__append.q-field__marginal {
|
||||||
|
color: var(--vn-input-icons-color) !important;
|
||||||
|
}
|
||||||
|
.q-field__control:before {
|
||||||
|
border-color: var(--vn-input-underline-color) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-photo-btn {
|
.edit-photo-btn {
|
||||||
|
|
|
@ -352,6 +352,7 @@ globals:
|
||||||
vehicle: Vehicle
|
vehicle: Vehicle
|
||||||
entryPreAccount: Pre-account
|
entryPreAccount: Pre-account
|
||||||
management: Worker management
|
management: Worker management
|
||||||
|
assignedInvoices: Assigned Invoices
|
||||||
unsavedPopup:
|
unsavedPopup:
|
||||||
title: Unsaved changes will be lost
|
title: Unsaved changes will be lost
|
||||||
subtitle: Are you sure exit without saving?
|
subtitle: Are you sure exit without saving?
|
||||||
|
@ -512,26 +513,6 @@ entry:
|
||||||
isRaid: Raid
|
isRaid: Raid
|
||||||
invoiceNumber: Invoice
|
invoiceNumber: Invoice
|
||||||
reference: Ref/Alb/Guide
|
reference: Ref/Alb/Guide
|
||||||
params:
|
|
||||||
isExcludedFromAvailable: Excluir del inventario
|
|
||||||
isOrdered: Pedida
|
|
||||||
isConfirmed: Lista para etiquetar
|
|
||||||
isReceived: Recibida
|
|
||||||
isRaid: Redada
|
|
||||||
landed: Fecha
|
|
||||||
supplierFk: Proveedor
|
|
||||||
invoiceNumber: Nº Factura
|
|
||||||
reference: Ref/Alb/Guía
|
|
||||||
agencyModeId: Agencia
|
|
||||||
isBooked: Asentado
|
|
||||||
companyFk: Empresa
|
|
||||||
travelFk: Envio
|
|
||||||
evaNotes: Notas
|
|
||||||
warehouseOutFk: Origen
|
|
||||||
warehouseInFk: Destino
|
|
||||||
entryTypeDescription: Tipo entrada
|
|
||||||
invoiceAmount: Importe
|
|
||||||
dated: Fecha
|
|
||||||
ticket:
|
ticket:
|
||||||
params:
|
params:
|
||||||
ticketFk: Ticket ID
|
ticketFk: Ticket ID
|
||||||
|
@ -897,6 +878,7 @@ components:
|
||||||
minPrice: Min. Price
|
minPrice: Min. Price
|
||||||
itemFk: Item id
|
itemFk: Item id
|
||||||
dated: Date
|
dated: Date
|
||||||
|
date: Date
|
||||||
userPanel:
|
userPanel:
|
||||||
copyToken: Token copied to clipboard
|
copyToken: Token copied to clipboard
|
||||||
settings: Settings
|
settings: Settings
|
||||||
|
|
|
@ -355,6 +355,7 @@ globals:
|
||||||
vehicle: Vehículo
|
vehicle: Vehículo
|
||||||
entryPreAccount: Precontabilizar
|
entryPreAccount: Precontabilizar
|
||||||
management: Gestión de trabajadores
|
management: Gestión de trabajadores
|
||||||
|
assignedInvoices: Facturas vinculadas
|
||||||
unsavedPopup:
|
unsavedPopup:
|
||||||
title: Los cambios que no haya guardado se perderán
|
title: Los cambios que no haya guardado se perderán
|
||||||
subtitle: ¿Seguro que quiere salir sin guardar?
|
subtitle: ¿Seguro que quiere salir sin guardar?
|
||||||
|
@ -981,6 +982,7 @@ components:
|
||||||
minPrice: Precio mínimo
|
minPrice: Precio mínimo
|
||||||
itemFk: Id item
|
itemFk: Id item
|
||||||
dated: Fecha
|
dated: Fecha
|
||||||
|
date: Fecha
|
||||||
userPanel:
|
userPanel:
|
||||||
copyToken: Token copiado al portapapeles
|
copyToken: Token copiado al portapapeles
|
||||||
settings: Configuración
|
settings: Configuración
|
||||||
|
|
|
@ -7,6 +7,8 @@ import FetchData from 'components/FetchData.vue';
|
||||||
import VnSelect from 'components/common/VnSelect.vue';
|
import VnSelect from 'components/common/VnSelect.vue';
|
||||||
import { tMobile } from 'composables/tMobile';
|
import { tMobile } from 'composables/tMobile';
|
||||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
@ -19,6 +21,7 @@ const claimResponsibles = ref([]);
|
||||||
const claimRedeliveries = ref([]);
|
const claimRedeliveries = ref([]);
|
||||||
const selected = ref([]);
|
const selected = ref([]);
|
||||||
const saveButtonRef = ref();
|
const saveButtonRef = ref();
|
||||||
|
const arrayData = useArrayData('Claim');
|
||||||
|
|
||||||
const developmentsFilter = computed(() => {
|
const developmentsFilter = computed(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -105,6 +108,32 @@ const columns = computed(() => [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const handleWorker = async (row) => {
|
||||||
|
const { claimResponsibleFk } = row;
|
||||||
|
|
||||||
|
if (!claimResponsibleFk) {
|
||||||
|
row.workerFk = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commercialResponsible = claimResponsibles?.value?.find(
|
||||||
|
(responsible) => responsible.code === 'com',
|
||||||
|
);
|
||||||
|
|
||||||
|
const claim = arrayData.store.data;
|
||||||
|
|
||||||
|
if (claimResponsibleFk === commercialResponsible?.id) {
|
||||||
|
row.workerFk = claim.workerFk;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await axios.get(
|
||||||
|
`ClaimDevelopments/${claim.ticketFk}/getResponsible/${claimResponsibleFk}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
row.workerFk = data?.userFk ?? null;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
<FetchData
|
||||||
|
@ -166,6 +195,20 @@ const columns = computed(() => [
|
||||||
input-debounce="0"
|
input-debounce="0"
|
||||||
hide-selected
|
hide-selected
|
||||||
/>
|
/>
|
||||||
|
<VnSelect
|
||||||
|
v-else-if="col.name == 'claimResponsible'"
|
||||||
|
v-model="row[col.model]"
|
||||||
|
:url="col.url"
|
||||||
|
:where="col.where"
|
||||||
|
:sort-by="col.sortBy"
|
||||||
|
:options="col.options"
|
||||||
|
:option-value="col.optionValue"
|
||||||
|
:option-label="col.optionLabel"
|
||||||
|
@update:modelValue="handleWorker(row)"
|
||||||
|
:autofocus="col.tabIndex == 1"
|
||||||
|
input-debounce="0"
|
||||||
|
hide-selected
|
||||||
|
/>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
v-else
|
v-else
|
||||||
v-model="row[col.model]"
|
v-model="row[col.model]"
|
||||||
|
|
|
@ -2,14 +2,20 @@
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
import { toCurrency, toDateHourMin } from 'src/filters';
|
import { toCurrency, toDateHourMin } from 'src/filters';
|
||||||
import VnTable from 'src/components/VnTable/VnTable.vue';
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||||
|
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
|
|
||||||
|
@ -45,26 +51,45 @@ const columns = computed(() => [
|
||||||
align: 'right',
|
align: 'right',
|
||||||
field: 'rating',
|
field: 'rating',
|
||||||
label: t('customer.summary.rating'),
|
label: t('customer.summary.rating'),
|
||||||
name: 'rating',
|
name: 'rating'
|
||||||
create: true,
|
|
||||||
columnCreate: {
|
|
||||||
component: 'number',
|
|
||||||
autofocus: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'right',
|
align: 'right',
|
||||||
field: 'recommendedCredit',
|
field: 'recommendedCredit',
|
||||||
format: ({ recommendedCredit }) => toCurrency(recommendedCredit),
|
format: ({ recommendedCredit }) => toCurrency(recommendedCredit),
|
||||||
label: t('customer.summary.recommendCredit'),
|
label: t('customer.summary.recommendCredit'),
|
||||||
name: 'recommendedCredit',
|
name: 'recommendedCredit'
|
||||||
create: true,
|
|
||||||
columnCreate: {
|
|
||||||
component: 'number',
|
|
||||||
autofocus: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const defaultInitialData = {
|
||||||
|
rating: null,
|
||||||
|
recommendedCredit: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRating = async (data) => {
|
||||||
|
await axios.post(`Clients/${route.params.id}/setRating`, data);
|
||||||
|
tableRef.value?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async (data) => {
|
||||||
|
if (data.rating || data.recommendedCredit) {
|
||||||
|
await createRating(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
quasar.dialog({
|
||||||
|
component: VnConfirm,
|
||||||
|
componentProps: {
|
||||||
|
title: t('terminationTitle'),
|
||||||
|
message: t('terminationMessage'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.onOk(async () => {
|
||||||
|
await createRating({ rating: 0, recommendedCredit: 0 });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -72,8 +97,7 @@ const columns = computed(() => [
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
data-key="ClientInformas"
|
data-key="ClientInformas"
|
||||||
url="ClientInformas"
|
url="ClientInformas"
|
||||||
:filter="filter"
|
:user-filter="filter"
|
||||||
:order="['created DESC']"
|
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:right-search="false"
|
:right-search="false"
|
||||||
:is-editable="false"
|
:is-editable="false"
|
||||||
|
@ -82,22 +106,43 @@ const columns = computed(() => [
|
||||||
:disable-option="{ card: true }"
|
:disable-option="{ card: true }"
|
||||||
auto-load
|
auto-load
|
||||||
:create="{
|
:create="{
|
||||||
urlCreate: `Clients/${route.params.id}/setRating`,
|
|
||||||
title: 'Create rating',
|
title: 'Create rating',
|
||||||
onDataSaved: () => tableRef.reload(),
|
onDataSaved: ()=> tableRef.reload(),
|
||||||
formInitialData: {},
|
formInitialData: defaultInitialData,
|
||||||
|
saveFn: handleSave
|
||||||
|
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #column-employee="{ row }">
|
<template #column-employee="{ row }">
|
||||||
<span class="link">{{ row.worker.user.nickname }}</span>
|
<span class="link">{{ row.worker.user.nickname }}</span>
|
||||||
<WorkerDescriptorProxy :id="row.worker.id" />
|
<WorkerDescriptorProxy :id="row.worker.id" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #more-create-dialog="{ data }">
|
||||||
|
<VnRow>
|
||||||
|
<VnInputNumber
|
||||||
|
v-model="data.rating"
|
||||||
|
:label="t('customer.summary.rating')"
|
||||||
|
:required="!!data.recommendedCredit"
|
||||||
|
/>
|
||||||
|
<VnInputNumber
|
||||||
|
v-model="data.recommendedCredit"
|
||||||
|
:label="t('customer.summary.recommendCredit')"
|
||||||
|
:required="!!data.rating"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
</template>
|
||||||
</VnTable>
|
</VnTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
|
en:
|
||||||
|
terminationTitle: Confirm contract termination
|
||||||
|
terminationMessage: Are you sure you want to terminate the contract? This action will set the rating and recommended credit to 0.
|
||||||
es:
|
es:
|
||||||
Recommended credit: Crédito recomendado
|
Recommended credit: Crédito recomendado
|
||||||
Since: Desde
|
Since: Desde
|
||||||
Employee: Empleado
|
Employee: Empleado
|
||||||
|
Create rating: Crear calificación
|
||||||
|
terminationTitle: Confirmar baja de contrato
|
||||||
|
terminationMessage: ¿Está seguro que desea dar de baja el contrato? Esta acción establecerá la calificación y el crédito recomendado en 0.
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -3,12 +3,13 @@ import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { toCurrency, toDateHourMin } from 'src/filters';
|
import { toCurrency, toDateHourMin } from 'src/filters';
|
||||||
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import VnTable from 'components/VnTable/VnTable.vue';
|
import VnTable from 'components/VnTable/VnTable.vue';
|
||||||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const arrayData = useArrayData('Customer');
|
||||||
const filter = computed(() => {
|
const filter = computed(() => {
|
||||||
return {
|
return {
|
||||||
include: [
|
include: [
|
||||||
|
@ -77,7 +78,10 @@ const columns = computed(() => [
|
||||||
:create="{
|
:create="{
|
||||||
urlUpdate: `Clients/${route.params.id}`,
|
urlUpdate: `Clients/${route.params.id}`,
|
||||||
title: t('New credit'),
|
title: t('New credit'),
|
||||||
onDataSaved: () => tableRef.reload(),
|
onDataSaved: () => {
|
||||||
|
arrayData.fetch({ append: false });
|
||||||
|
tableRef.reload();
|
||||||
|
},
|
||||||
formInitialData: { credit: tableData.at(0)?.amount },
|
formInitialData: { credit: tableData.at(0)?.amount },
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { QBadge, QBtn, QCheckbox } from 'quasar';
|
import { QBadge, QBtn, QCheckbox } from 'quasar';
|
||||||
|
|
||||||
import { downloadFile } from 'src/composables/downloadFile';
|
import { usePrintService } from 'composables/usePrintService';
|
||||||
import { toDateTimeFormat } from 'src/filters/date';
|
import { toDateTimeFormat } from 'src/filters/date';
|
||||||
|
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
@ -15,7 +15,7 @@ import CustomerFileManagementActions from 'src/pages/Customer/components/Custome
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { openReport } = usePrintService();
|
||||||
const ClientDmsRef = ref(null);
|
const ClientDmsRef = ref(null);
|
||||||
const rows = ref([]);
|
const rows = ref([]);
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ const tableColumnComponents = {
|
||||||
file: {
|
file: {
|
||||||
component: QBtn,
|
component: QBtn,
|
||||||
props: () => ({ flat: true }),
|
props: () => ({ flat: true }),
|
||||||
event: ({ row }) => downloadFile(row.dmsFk),
|
event: ({ row }) => openReport(`dms/${row.dmsFk}/downloadFile`, {}, '_blank'),
|
||||||
},
|
},
|
||||||
employee: {
|
employee: {
|
||||||
component: QBtn,
|
component: QBtn,
|
||||||
|
|
|
@ -55,7 +55,6 @@ const filterBanks = {
|
||||||
fields: ['id', 'bank', 'accountingTypeFk'],
|
fields: ['id', 'bank', 'accountingTypeFk'],
|
||||||
include: { relation: 'accountingType' },
|
include: { relation: 'accountingType' },
|
||||||
order: 'id',
|
order: 'id',
|
||||||
limit: 30,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterClientFindOne = {
|
const filterClientFindOne = {
|
||||||
|
@ -200,7 +199,6 @@ async function getAmountPaid() {
|
||||||
option-label="bank"
|
option-label="bank"
|
||||||
:include="{ relation: 'accountingType' }"
|
:include="{ relation: 'accountingType' }"
|
||||||
sort-by="id"
|
sort-by="id"
|
||||||
:limit="0"
|
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(value, options) => setPaymentType(data, value, options)
|
(value, options) => setPaymentType(data, value, options)
|
||||||
"
|
"
|
||||||
|
|
|
@ -8,6 +8,6 @@ import filter from './EntryFilter.js';
|
||||||
data-key="Entry"
|
data-key="Entry"
|
||||||
url="Entries"
|
url="Entries"
|
||||||
:descriptor="EntryDescriptor"
|
:descriptor="EntryDescriptor"
|
||||||
:filter="{ ...filter, where: { id: $route.params.id } }"
|
:filter="filter"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -52,5 +52,22 @@ export default {
|
||||||
fields: ['code', 'description'],
|
fields: ['code', 'description'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
relation: 'dms',
|
||||||
|
scope: {
|
||||||
|
fields: [
|
||||||
|
'dmsTypeFk',
|
||||||
|
'reference',
|
||||||
|
'hardCopyNumber',
|
||||||
|
'workerFk',
|
||||||
|
'description',
|
||||||
|
'hasFile',
|
||||||
|
'file',
|
||||||
|
'created',
|
||||||
|
'companyFk',
|
||||||
|
'warehouseFk',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, markRaw, useTemplateRef, onBeforeMount, watch } from 'vue';
|
import {
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
markRaw,
|
||||||
|
useTemplateRef,
|
||||||
|
onBeforeMount,
|
||||||
|
watch,
|
||||||
|
onUnmounted,
|
||||||
|
} from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toDate, toCurrency } from 'src/filters';
|
import { toDate, toCurrency } from 'src/filters';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
@ -19,7 +27,10 @@ import { useQuasar } from 'quasar';
|
||||||
import InvoiceInDescriptorProxy from '../InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
|
import InvoiceInDescriptorProxy from '../InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
|
||||||
import { useStateStore } from 'src/stores/useStateStore';
|
import { useStateStore } from 'src/stores/useStateStore';
|
||||||
import { downloadFile } from 'src/composables/downloadFile';
|
import { downloadFile } from 'src/composables/downloadFile';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useAcl } from 'src/composables/useAcl';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
@ -27,8 +38,6 @@ const user = useState().getUser();
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const updateDialog = ref();
|
const updateDialog = ref();
|
||||||
const uploadDialog = ref();
|
const uploadDialog = ref();
|
||||||
let maxDays;
|
|
||||||
let defaultDays;
|
|
||||||
const dataKey = 'entryPreaccountingFilter';
|
const dataKey = 'entryPreaccountingFilter';
|
||||||
const url = 'Entries/preAccountingFilter';
|
const url = 'Entries/preAccountingFilter';
|
||||||
const arrayData = useArrayData(dataKey);
|
const arrayData = useArrayData(dataKey);
|
||||||
|
@ -45,12 +54,16 @@ const defaultDmsDescription = ref();
|
||||||
const dmsTypeId = ref();
|
const dmsTypeId = ref();
|
||||||
const selectedRows = ref([]);
|
const selectedRows = ref([]);
|
||||||
const totalAmount = ref();
|
const totalAmount = ref();
|
||||||
|
const hasDiferentDms = ref(false);
|
||||||
|
let maxDays;
|
||||||
|
let defaultDays;
|
||||||
|
let supplierRef;
|
||||||
|
let dmsFk;
|
||||||
const totalSelectedAmount = computed(() => {
|
const totalSelectedAmount = computed(() => {
|
||||||
if (!selectedRows.value.length) return 0;
|
if (!selectedRows.value.length) return 0;
|
||||||
return selectedRows.value.reduce((acc, entry) => acc + entry.amount, 0);
|
return selectedRows.value.reduce((acc, entry) => acc + entry.amount, 0);
|
||||||
});
|
});
|
||||||
let supplierRef;
|
|
||||||
let dmsFk;
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
@ -63,6 +76,7 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'invoiceNumber',
|
name: 'invoiceNumber',
|
||||||
label: t('entry.preAccount.invoiceNumber'),
|
label: t('entry.preAccount.invoiceNumber'),
|
||||||
|
width: 'max-content',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'company',
|
name: 'company',
|
||||||
|
@ -73,7 +87,7 @@ const columns = computed(() => [
|
||||||
optionLabel: 'code',
|
optionLabel: 'code',
|
||||||
options: companies.value,
|
options: companies.value,
|
||||||
},
|
},
|
||||||
orderBy: false,
|
class: 'shrink',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'warehouse',
|
name: 'warehouse',
|
||||||
|
@ -102,6 +116,7 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'reference',
|
name: 'reference',
|
||||||
label: t('entry.preAccount.reference'),
|
label: t('entry.preAccount.reference'),
|
||||||
|
width: 'max-content',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'shipped',
|
name: 'shipped',
|
||||||
|
@ -136,6 +151,7 @@ const columns = computed(() => [
|
||||||
class: 'fit',
|
class: 'fit',
|
||||||
event: 'update',
|
event: 'update',
|
||||||
},
|
},
|
||||||
|
width: 'max-content',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'country',
|
name: 'country',
|
||||||
|
@ -145,6 +161,7 @@ const columns = computed(() => [
|
||||||
name: 'countryFk',
|
name: 'countryFk',
|
||||||
options: countries.value,
|
options: countries.value,
|
||||||
},
|
},
|
||||||
|
class: 'shrink',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'description',
|
name: 'description',
|
||||||
|
@ -165,11 +182,12 @@ const columns = computed(() => [
|
||||||
component: 'number',
|
component: 'number',
|
||||||
name: 'payDem',
|
name: 'payDem',
|
||||||
},
|
},
|
||||||
|
class: 'shrink',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'fiscalCode',
|
name: 'fiscalCode',
|
||||||
label: t('entry.preAccount.fiscalCode'),
|
label: t('entry.preAccount.fiscalCode'),
|
||||||
format: ({ fiscalCode }) => t(fiscalCode),
|
format: ({ fiscalCode }, dashIfEmpty) => t(dashIfEmpty(fiscalCode)),
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'select',
|
component: 'select',
|
||||||
name: 'fiscalCode',
|
name: 'fiscalCode',
|
||||||
|
@ -208,20 +226,21 @@ const columns = computed(() => [
|
||||||
]);
|
]);
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
|
const filter = JSON.parse(route.query.entryPreaccountingFilter ?? '{}');
|
||||||
const { data } = await axios.get('EntryConfigs/findOne', {
|
const { data } = await axios.get('EntryConfigs/findOne', {
|
||||||
params: { filter: JSON.stringify({ fields: ['maxDays', 'defaultDays'] }) },
|
params: { filter: JSON.stringify({ fields: ['maxDays', 'defaultDays'] }) },
|
||||||
});
|
});
|
||||||
maxDays = data.maxDays;
|
maxDays = data.maxDays;
|
||||||
defaultDays = data.defaultDays;
|
defaultDays = data.defaultDays;
|
||||||
daysAgo.value = arrayData.store.userParams.daysAgo || defaultDays;
|
daysAgo.value = filter.daysAgo || defaultDays;
|
||||||
isBooked.value = arrayData.store.userParams.isBooked || false;
|
isBooked.value = filter.isBooked || false;
|
||||||
stateStore.leftDrawer = false;
|
stateStore.leftDrawer = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(selectedRows, (nVal, oVal) => {
|
onUnmounted(() => (stateStore.leftDrawer = true));
|
||||||
const lastRow = nVal.at(-1);
|
|
||||||
if (lastRow?.isBooked) selectedRows.value.pop();
|
watch(selectedRows, async (nVal, oVal) => {
|
||||||
if (nVal.length > oVal.length && lastRow.invoiceInFk)
|
if (nVal.length > oVal.length && nVal.at(-1).invoiceInFk)
|
||||||
quasar.dialog({
|
quasar.dialog({
|
||||||
component: VnConfirm,
|
component: VnConfirm,
|
||||||
componentProps: { title: t('entry.preAccount.hasInvoice') },
|
componentProps: { title: t('entry.preAccount.hasInvoice') },
|
||||||
|
@ -232,15 +251,22 @@ function filterByDaysAgo(val) {
|
||||||
if (!val) val = defaultDays;
|
if (!val) val = defaultDays;
|
||||||
else if (val > maxDays) val = maxDays;
|
else if (val > maxDays) val = maxDays;
|
||||||
daysAgo.value = val;
|
daysAgo.value = val;
|
||||||
arrayData.store.userParams.daysAgo = daysAgo.value;
|
table.value.reload({
|
||||||
table.value.reload();
|
userParams: { ...arrayData.store.userParams, daysAgo: daysAgo.value },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function preAccount() {
|
async function preAccount() {
|
||||||
const entries = selectedRows.value;
|
const entries = selectedRows.value;
|
||||||
const { companyFk, isAgricultural, landed } = entries.at(0);
|
const { companyFk, isAgricultural, landed } = entries.at(0);
|
||||||
|
supplierRef = null;
|
||||||
|
dmsFk = undefined;
|
||||||
|
hasDiferentDms.value = false;
|
||||||
try {
|
try {
|
||||||
dmsFk = entries.find(({ gestDocFk }) => gestDocFk)?.gestDocFk;
|
entries.forEach(({ gestDocFk }) => {
|
||||||
|
if (!dmsFk && gestDocFk) dmsFk = gestDocFk;
|
||||||
|
if (dmsFk && gestDocFk && dmsFk != gestDocFk) hasDiferentDms.value = true;
|
||||||
|
});
|
||||||
if (isAgricultural) {
|
if (isAgricultural) {
|
||||||
const year = new Date(landed).getFullYear();
|
const year = new Date(landed).getFullYear();
|
||||||
supplierRef = (
|
supplierRef = (
|
||||||
|
@ -297,8 +323,6 @@ async function createInvoice() {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
supplierRef = null;
|
|
||||||
dmsFk = undefined;
|
|
||||||
selectedRows.value.length = 0;
|
selectedRows.value.length = 0;
|
||||||
table.value.reload();
|
table.value.reload();
|
||||||
}
|
}
|
||||||
|
@ -359,6 +383,7 @@ async function createInvoice() {
|
||||||
:search-remove-params="false"
|
:search-remove-params="false"
|
||||||
/>
|
/>
|
||||||
<VnTable
|
<VnTable
|
||||||
|
v-if="isBooked !== undefined && daysAgo !== undefined"
|
||||||
v-model:selected="selectedRows"
|
v-model:selected="selectedRows"
|
||||||
:data-key
|
:data-key
|
||||||
:columns
|
:columns
|
||||||
|
@ -377,10 +402,12 @@ async function createInvoice() {
|
||||||
@on-fetch="
|
@on-fetch="
|
||||||
(data) => (totalAmount = data?.reduce((acc, entry) => acc + entry.amount, 0))
|
(data) => (totalAmount = data?.reduce((acc, entry) => acc + entry.amount, 0))
|
||||||
"
|
"
|
||||||
|
:selection-fn="(rows) => rows.filter((row) => !row.isBooked)"
|
||||||
auto-load
|
auto-load
|
||||||
>
|
>
|
||||||
<template #top-left>
|
<template #top-left>
|
||||||
<QBtn
|
<QBtn
|
||||||
|
v-if="useAcl().hasAcl('Entry', 'addInvoiceIn', 'WRITE')"
|
||||||
data-cy="preAccount_btn"
|
data-cy="preAccount_btn"
|
||||||
icon="account_balance"
|
icon="account_balance"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
@ -437,6 +464,11 @@ async function createInvoice() {
|
||||||
:title="t('entry.preAccount.dialog.title')"
|
:title="t('entry.preAccount.dialog.title')"
|
||||||
:message="t('entry.preAccount.dialog.message')"
|
:message="t('entry.preAccount.dialog.message')"
|
||||||
>
|
>
|
||||||
|
<template #customHTML v-if="hasDiferentDms">
|
||||||
|
<p class="text-negative">
|
||||||
|
{{ t('differentGestdoc') }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<QBtn
|
<QBtn
|
||||||
data-cy="updateFileYes"
|
data-cy="updateFileYes"
|
||||||
|
@ -471,9 +503,11 @@ en:
|
||||||
IntraCommunity: Intra-community
|
IntraCommunity: Intra-community
|
||||||
NonCommunity: Non-community
|
NonCommunity: Non-community
|
||||||
CanaryIslands: Canary Islands
|
CanaryIslands: Canary Islands
|
||||||
|
differentGestdoc: The entries have different gestdoc
|
||||||
es:
|
es:
|
||||||
IntraCommunity: Intracomunitaria
|
IntraCommunity: Intracomunitaria
|
||||||
NonCommunity: Extracomunitaria
|
NonCommunity: Extracomunitaria
|
||||||
CanaryIslands: Islas Canarias
|
CanaryIslands: Islas Canarias
|
||||||
National: Nacional
|
National: Nacional
|
||||||
|
differentGestdoc: Las entradas tienen diferentes gestdoc
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -130,7 +130,7 @@ entry:
|
||||||
supplierFk: Supplier
|
supplierFk: Supplier
|
||||||
country: Country
|
country: Country
|
||||||
description: Entry type
|
description: Entry type
|
||||||
payDem: Payment term
|
payDem: P. term
|
||||||
isBooked: B
|
isBooked: B
|
||||||
isReceived: R
|
isReceived: R
|
||||||
entryType: Entry type
|
entryType: Entry type
|
||||||
|
@ -138,7 +138,7 @@ entry:
|
||||||
fiscalCode: Account type
|
fiscalCode: Account type
|
||||||
daysAgo: Max 365 days
|
daysAgo: Max 365 days
|
||||||
search: Search
|
search: Search
|
||||||
searchInfo: You can search by supplier name or nickname
|
searchInfo: You can search by supplier name, nickname or tax number
|
||||||
btn: Pre-account
|
btn: Pre-account
|
||||||
hasInvoice: This entry has already an invoice in
|
hasInvoice: This entry has already an invoice in
|
||||||
success: It has been successfully pre-accounted
|
success: It has been successfully pre-accounted
|
||||||
|
|
|
@ -81,7 +81,7 @@ entry:
|
||||||
supplierFk: Proveedor
|
supplierFk: Proveedor
|
||||||
country: País
|
country: País
|
||||||
description: Tipo de Entrada
|
description: Tipo de Entrada
|
||||||
payDem: Plazo de pago
|
payDem: P. pago
|
||||||
isBooked: C
|
isBooked: C
|
||||||
isReceived: R
|
isReceived: R
|
||||||
entryType: Tipo de entrada
|
entryType: Tipo de entrada
|
||||||
|
@ -89,7 +89,7 @@ entry:
|
||||||
fiscalCode: Tipo de cuenta
|
fiscalCode: Tipo de cuenta
|
||||||
daysAgo: Máximo 365 días
|
daysAgo: Máximo 365 días
|
||||||
search: Buscar
|
search: Buscar
|
||||||
searchInfo: Puedes buscar por nombre o alias de proveedor
|
searchInfo: Puedes buscar por nombre, alias o cif de proveedor
|
||||||
btn: Precontabilizar
|
btn: Precontabilizar
|
||||||
hasInvoice: Esta entrada ya tiene una f. recibida
|
hasInvoice: Esta entrada ya tiene una f. recibida
|
||||||
success: Se ha precontabilizado correctamente
|
success: Se ha precontabilizado correctamente
|
||||||
|
|
|
@ -3,15 +3,11 @@ import { ref, computed, onBeforeMount } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { toDate } from 'src/filters';
|
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
import { getTotal } from 'src/composables/getTotal';
|
import { getTotal } from 'src/composables/getTotal';
|
||||||
import CrudModel from 'src/components/CrudModel.vue';
|
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
|
||||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
|
||||||
import { toCurrency } from 'filters/index';
|
import { toCurrency } from 'filters/index';
|
||||||
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
@ -19,256 +15,187 @@ const { t } = useI18n();
|
||||||
const arrayData = useArrayData();
|
const arrayData = useArrayData();
|
||||||
const invoiceIn = computed(() => arrayData.store.data);
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
const currency = computed(() => invoiceIn.value?.currency?.code);
|
const currency = computed(() => invoiceIn.value?.currency?.code);
|
||||||
|
|
||||||
const rowsSelected = ref([]);
|
const rowsSelected = ref([]);
|
||||||
const invoiceInFormRef = ref();
|
const invoiceInDueDayTableRef = ref();
|
||||||
const invoiceId = +route.params.id;
|
const invoiceId = +route.params.id;
|
||||||
const filter = { where: { invoiceInFk: invoiceId } };
|
const filter = { where: { invoiceInFk: invoiceId } };
|
||||||
const areRows = ref(false);
|
const areRows = ref(false);
|
||||||
const totalTaxableBase = ref();
|
const totalTaxableBase = ref();
|
||||||
const noMatch = computed(() => totalAmount.value != totalTaxableBase.value);
|
const totalVat = ref();
|
||||||
|
|
||||||
|
const tableRows = computed(
|
||||||
|
() => invoiceInDueDayTableRef.value?.CrudModelRef?.formData || [],
|
||||||
|
);
|
||||||
|
const totalAmount = computed(() => getTotal(tableRows.value, 'amount'));
|
||||||
|
|
||||||
|
const noMatch = computed(
|
||||||
|
() =>
|
||||||
|
totalAmount.value != totalTaxableBase.value &&
|
||||||
|
totalAmount.value != totalVat.value,
|
||||||
|
);
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'duedate',
|
name: 'dueDated',
|
||||||
label: t('Date'),
|
label: t('Date'),
|
||||||
field: (row) => toDate(row.dueDated),
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
tabIndex: 1,
|
tabIndex: 1,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
component: 'date',
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'bank',
|
name: 'bankFk',
|
||||||
label: t('Bank'),
|
label: t('Bank'),
|
||||||
field: (row) => row.bankFk,
|
|
||||||
model: 'bankFk',
|
|
||||||
optionLabel: 'bank',
|
|
||||||
url: 'Accountings',
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
tabIndex: 2,
|
tabIndex: 2,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'Accountings',
|
||||||
|
optionLabel: 'bank',
|
||||||
|
optionValue: 'id',
|
||||||
|
fields: ['id', 'bank'],
|
||||||
|
'emit-value': true,
|
||||||
|
},
|
||||||
|
format: ({ bank }) => bank?.bank,
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'amount',
|
name: 'amount',
|
||||||
label: t('Amount'),
|
label: t('Amount'),
|
||||||
field: (row) => row.amount,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
tabIndex: 3,
|
tabIndex: 3,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
component: 'number',
|
||||||
|
attrs: {
|
||||||
|
clearable: true,
|
||||||
|
'clear-icon': 'close',
|
||||||
|
},
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'foreignvalue',
|
name: 'foreignValue',
|
||||||
label: t('Foreign value'),
|
label: t('Foreign value'),
|
||||||
field: (row) => row.foreignValue,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
tabIndex: 4,
|
tabIndex: 4,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
component: 'number',
|
||||||
|
isEditable: isNotEuro(currency.value),
|
||||||
|
create: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const totalAmount = computed(() => getTotal(invoiceInFormRef.value.formData, 'amount'));
|
|
||||||
|
|
||||||
const isNotEuro = (code) => code != 'EUR';
|
const isNotEuro = (code) => code != 'EUR';
|
||||||
|
|
||||||
async function insert() {
|
async function insert() {
|
||||||
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
|
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
|
||||||
await invoiceInFormRef.value.reload();
|
await invoiceInDueDayTableRef.value.reload();
|
||||||
notify(t('globals.dataSaved'), 'positive');
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setTaxableBase() {
|
async function setTaxableBase() {
|
||||||
|
const ref = invoiceInDueDayTableRef.value;
|
||||||
|
if (ref) ref.create = null;
|
||||||
const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`);
|
const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`);
|
||||||
totalTaxableBase.value = data.totalTaxableBase;
|
totalTaxableBase.value = data.totalTaxableBase;
|
||||||
|
totalVat.value = data.totalVat;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(async () => await setTaxableBase());
|
onBeforeMount(async () => await setTaxableBase());
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<CrudModel
|
<div class="invoice-in-due-day">
|
||||||
|
<VnTable
|
||||||
v-if="invoiceIn"
|
v-if="invoiceIn"
|
||||||
ref="invoiceInFormRef"
|
ref="invoiceInDueDayTableRef"
|
||||||
data-key="InvoiceInDueDays"
|
data-key="InvoiceInDueDays"
|
||||||
url="InvoiceInDueDays"
|
url="InvoiceInDueDays"
|
||||||
|
save-url="InvoiceInDueDays/crud"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
|
:user-filter="{
|
||||||
|
include: { relation: 'bank', scope: { fields: ['id', 'bank'] } },
|
||||||
|
}"
|
||||||
auto-load
|
auto-load
|
||||||
:data-required="{ invoiceInFk: invoiceId }"
|
:data-required="{ invoiceInFk: invoiceId }"
|
||||||
v-model:selected="rowsSelected"
|
v-model:selected="rowsSelected"
|
||||||
@on-fetch="(data) => (areRows = !!data.length)"
|
@on-fetch="(data) => (areRows = !!data.length)"
|
||||||
@save-changes="setTaxableBase"
|
@save-changes="setTaxableBase"
|
||||||
|
:columns="columns"
|
||||||
|
:is-editable="true"
|
||||||
|
:table="{ selection: 'multiple', 'row-key': '$index' }"
|
||||||
|
footer
|
||||||
|
:right-search="false"
|
||||||
|
:column-search="false"
|
||||||
|
:disable-option="{ card: true }"
|
||||||
|
class="q-pa-none"
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #column-footer-amount>
|
||||||
<QTable
|
|
||||||
v-model:selected="rowsSelected"
|
|
||||||
selection="multiple"
|
|
||||||
:columns
|
|
||||||
:rows
|
|
||||||
row-key="$index"
|
|
||||||
:grid="$q.screen.lt.sm"
|
|
||||||
>
|
|
||||||
<template #body-cell-duedate="{ row }">
|
|
||||||
<QTd>
|
|
||||||
<VnInputDate v-model="row.dueDated" />
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-bank="{ row, col }">
|
|
||||||
<QTd>
|
|
||||||
<VnSelect
|
|
||||||
v-model="row[col.model]"
|
|
||||||
:url="col.url"
|
|
||||||
:option-label="col.optionLabel"
|
|
||||||
:option-value="col.optionValue"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
<QItemSection>
|
|
||||||
<QItemLabel>{{
|
|
||||||
`${scope.opt.id}: ${scope.opt.bank}`
|
|
||||||
}}</QItemLabel>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelect>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-amount="{ row }">
|
|
||||||
<QTd>
|
|
||||||
<VnInputNumber
|
|
||||||
v-model="row.amount"
|
|
||||||
:is-outlined="false"
|
|
||||||
clearable
|
|
||||||
clear-icon="close"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-foreignvalue="{ row }">
|
|
||||||
<QTd>
|
|
||||||
<VnInputNumber
|
|
||||||
:class="{
|
|
||||||
'no-pointer-events': !isNotEuro(currency),
|
|
||||||
}"
|
|
||||||
:disable="!isNotEuro(currency)"
|
|
||||||
v-model="row.foreignValue"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #bottom-row>
|
|
||||||
<QTr class="bg">
|
|
||||||
<QTd />
|
|
||||||
<QTd />
|
|
||||||
<QTd />
|
|
||||||
<QTd>
|
|
||||||
<QChip
|
<QChip
|
||||||
dense
|
dense
|
||||||
:color="noMatch ? 'negative' : 'transparent'"
|
:color="noMatch ? 'negative' : 'transparent'"
|
||||||
class="q-pa-xs"
|
class="q-pa-xs"
|
||||||
:title="
|
:title="noMatch ? t('invoiceIn.noMatch', { totalTaxableBase }) : ''"
|
||||||
noMatch
|
|
||||||
? t('invoiceIn.noMatch', { totalTaxableBase })
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ toCurrency(totalAmount) }}
|
{{ toCurrency(totalAmount) }}
|
||||||
</QChip>
|
</QChip>
|
||||||
</QTd>
|
</template>
|
||||||
<QTd>
|
<template #column-footer-foreignValue>
|
||||||
<template v-if="isNotEuro(invoiceIn.currency.code)">
|
<template v-if="isNotEuro(currency)">
|
||||||
{{
|
{{
|
||||||
getTotal(rows, 'foreignValue', {
|
getTotal(tableRows, 'foreignValue', {
|
||||||
currency: invoiceIn.currency.code,
|
currency: currency,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
</QTd>
|
|
||||||
</QTr>
|
|
||||||
</template>
|
</template>
|
||||||
<template #item="props">
|
</VnTable>
|
||||||
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
<QPageSticky :offset="[20, 20]" style="z-index: 2">
|
||||||
<QCard>
|
|
||||||
<QCardSection>
|
|
||||||
<QCheckbox v-model="props.selected" dense />
|
|
||||||
</QCardSection>
|
|
||||||
<QSeparator />
|
|
||||||
<QList>
|
|
||||||
<QItem>
|
|
||||||
<VnInputDate
|
|
||||||
class="full-width"
|
|
||||||
:label="t('Date')"
|
|
||||||
v-model="props.row.dueDated"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('Bank')"
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row['bankFk']"
|
|
||||||
url="Accountings"
|
|
||||||
option-label="bank"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
<QItemSection>
|
|
||||||
<QItemLabel>{{
|
|
||||||
`${scope.opt.id}: ${scope.opt.bank}`
|
|
||||||
}}</QItemLabel>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelect>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnInputNumber
|
|
||||||
:label="t('Amount')"
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row.amount"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnInputNumber
|
|
||||||
:label="t('Foreign value')"
|
|
||||||
class="full-width"
|
|
||||||
:class="{
|
|
||||||
'no-pointer-events': !isNotEuro(currency),
|
|
||||||
}"
|
|
||||||
:disable="!isNotEuro(currency)"
|
|
||||||
v-model="props.row.foreignValue"
|
|
||||||
clearable
|
|
||||||
clear-icon="close"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
</QList>
|
|
||||||
</QCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</QTable>
|
|
||||||
</template>
|
|
||||||
</CrudModel>
|
|
||||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
|
||||||
<QBtn
|
<QBtn
|
||||||
color="primary"
|
|
||||||
icon="add"
|
|
||||||
v-shortcut="'+'"
|
|
||||||
size="lg"
|
|
||||||
round
|
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
if (!areRows) insert();
|
if (!areRows) return insert();
|
||||||
else
|
|
||||||
invoiceInFormRef.insert({
|
invoiceInDueDayTableRef.create = {
|
||||||
amount: (totalTaxableBase - totalAmount).toFixed(2),
|
urlCreate: 'InvoiceInDueDays',
|
||||||
|
onDataSaved: () => invoiceInDueDayTableRef.reload(),
|
||||||
|
title: t('Create due day'),
|
||||||
|
formInitialData: {
|
||||||
invoiceInFk: invoiceId,
|
invoiceInFk: invoiceId,
|
||||||
});
|
dueDated: Date.vnNew(),
|
||||||
|
amount: (totalTaxableBase - totalAmount).toFixed(2),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
invoiceInDueDayTableRef.showForm = true;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
color="primary"
|
||||||
|
fab
|
||||||
|
icon="add"
|
||||||
|
v-shortcut="'+'"
|
||||||
|
data-cy="invoiceInDueDayAdd"
|
||||||
/>
|
/>
|
||||||
|
<QTooltip class="text-no-wrap">
|
||||||
|
{{ t('Create due day') }}
|
||||||
|
</QTooltip>
|
||||||
</QPageSticky>
|
</QPageSticky>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.bg {
|
|
||||||
background-color: var(--vn-light-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-chip {
|
.q-chip {
|
||||||
color: var(--vn-text-color);
|
color: var(--vn-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invoice-in-due-day {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
:deep(.full-width) {
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
|
@ -276,4 +203,6 @@ onBeforeMount(async () => await setTaxableBase());
|
||||||
Bank: Caja
|
Bank: Caja
|
||||||
Amount: Importe
|
Amount: Importe
|
||||||
Foreign value: Divisa
|
Foreign value: Divisa
|
||||||
|
invoiceIn.noMatch: El total {totalTaxableBase} no coincide con el importe
|
||||||
|
Create due day: Crear Vencimiento
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -3,71 +3,83 @@ import { computed, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { getTotal } from 'src/composables/getTotal';
|
import { getTotal } from 'src/composables/getTotal';
|
||||||
import CrudModel from 'src/components/CrudModel.vue';
|
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const invoceInIntrastat = ref([]);
|
|
||||||
const rowsSelected = ref([]);
|
const rowsSelected = ref([]);
|
||||||
const countries = ref([]);
|
const countries = ref([]);
|
||||||
const intrastats = ref([]);
|
const intrastats = ref([]);
|
||||||
const invoiceInFormRef = ref();
|
const invoiceInIntrastatRef = ref();
|
||||||
const invoiceInId = computed(() => +route.params.id);
|
const invoiceInId = computed(() => +route.params.id);
|
||||||
const filter = { where: { invoiceInFk: invoiceInId.value } };
|
const filter = computed(() => ({ where: { invoiceInFk: invoiceInId.value } }));
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'code',
|
name: 'intrastatFk',
|
||||||
label: t('Code'),
|
label: t('Code'),
|
||||||
field: (row) => row.intrastatFk,
|
component: 'select',
|
||||||
|
columnFilter: false,
|
||||||
|
attrs: {
|
||||||
options: intrastats.value,
|
options: intrastats.value,
|
||||||
model: 'intrastatFk',
|
|
||||||
optionValue: 'id',
|
optionValue: 'id',
|
||||||
optionLabel: (row) => `${row.id}: ${row.description}`,
|
optionLabel: (row) => `${row.id}: ${row.description}`,
|
||||||
sortable: true,
|
'data-cy': 'intrastat-code',
|
||||||
tabIndex: 1,
|
sortBy: 'id',
|
||||||
align: 'left',
|
fields: ['id', 'description'],
|
||||||
|
filterOptions: ['id', 'description']
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(getCode(row)),
|
||||||
|
create: true,
|
||||||
|
isEditable: true,
|
||||||
|
width: 'max-content',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'amount',
|
name: 'amount',
|
||||||
label: t('amount'),
|
label: t('amount'),
|
||||||
field: (row) => row.amount,
|
component: 'number',
|
||||||
sortable: true,
|
create: true,
|
||||||
tabIndex: 2,
|
isEditable: true,
|
||||||
align: 'left',
|
columnFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'net',
|
name: 'net',
|
||||||
label: t('net'),
|
label: t('net'),
|
||||||
field: (row) => row.net,
|
component: 'number',
|
||||||
sortable: true,
|
create: true,
|
||||||
tabIndex: 3,
|
isEditable: true,
|
||||||
align: 'left',
|
columnFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'stems',
|
name: 'stems',
|
||||||
label: t('stems'),
|
label: t('stems'),
|
||||||
field: (row) => row.stems,
|
component: 'number',
|
||||||
sortable: true,
|
create: true,
|
||||||
tabIndex: 4,
|
isEditable: true,
|
||||||
align: 'left',
|
columnFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'country',
|
name: 'countryFk',
|
||||||
label: t('country'),
|
label: t('country'),
|
||||||
field: (row) => row.countryFk,
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
options: countries.value,
|
options: countries.value,
|
||||||
model: 'countryFk',
|
|
||||||
optionValue: 'id',
|
|
||||||
optionLabel: 'code',
|
optionLabel: 'code',
|
||||||
sortable: true,
|
},
|
||||||
tabIndex: 5,
|
create: true,
|
||||||
align: 'left',
|
isEditable: true,
|
||||||
|
columnFilter: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const tableRows = computed(
|
||||||
|
() => invoiceInIntrastatRef.value?.CrudModelRef?.formData || [],
|
||||||
|
);
|
||||||
|
|
||||||
|
function getCode(row) {
|
||||||
|
const code = intrastats.value.find(({ id }) => id === row.intrastatFk);
|
||||||
|
return code ? `${code.id}: ${code.description}` : null;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
<FetchData
|
||||||
|
@ -82,165 +94,51 @@ const columns = computed(() => [
|
||||||
auto-load
|
auto-load
|
||||||
@on-fetch="(data) => (intrastats = data)"
|
@on-fetch="(data) => (intrastats = data)"
|
||||||
/>
|
/>
|
||||||
<div class="invoiceIn-intrastat">
|
<div class="invoice-in-intrastat">
|
||||||
<CrudModel
|
<VnTable
|
||||||
ref="invoiceInFormRef"
|
ref="invoiceInIntrastatRef"
|
||||||
data-key="InvoiceInIntrastats"
|
data-key="InvoiceInIntrastats"
|
||||||
url="InvoiceInIntrastats"
|
url="InvoiceInIntrastats"
|
||||||
|
save-url="InvoiceInIntrastats/crud"
|
||||||
|
search-url="InvoiceInIntrastats"
|
||||||
auto-load
|
auto-load
|
||||||
:data-required="{ invoiceInFk: invoiceInId }"
|
:filter
|
||||||
:filter="filter"
|
:user-filter
|
||||||
:insert-on-load="true"
|
|
||||||
v-model:selected="rowsSelected"
|
v-model:selected="rowsSelected"
|
||||||
@on-fetch="(data) => (invoceInIntrastat = data)"
|
:columns
|
||||||
|
:is-editable="true"
|
||||||
|
:table="{ selection: 'multiple', 'row-key': '$index' }"
|
||||||
|
:create="{
|
||||||
|
urlCreate: 'InvoiceInIntrastats',
|
||||||
|
title: t('Create Intrastat'),
|
||||||
|
formInitialData: { invoiceInFk: invoiceInId },
|
||||||
|
onDataSaved: () => invoiceInIntrastatRef.reload(),
|
||||||
|
}"
|
||||||
|
footer
|
||||||
|
data-cy="invoice-in-intrastat-table"
|
||||||
|
:right-search="false"
|
||||||
|
:disable-option="{ card: true }"
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #column-footer-amount>
|
||||||
<QTable
|
{{ getTotal(tableRows, 'amount', { currency: 'default' }) }}
|
||||||
v-model:selected="rowsSelected"
|
|
||||||
selection="multiple"
|
|
||||||
:columns="columns"
|
|
||||||
:rows="rows"
|
|
||||||
row-key="$index"
|
|
||||||
:grid="$q.screen.lt.sm"
|
|
||||||
>
|
|
||||||
<template #body-cell="{ row, col }">
|
|
||||||
<QTd>
|
|
||||||
<VnInputNumber v-model="row[col.name]" />
|
|
||||||
</QTd>
|
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-code="{ row, col }">
|
<template #column-footer-net>
|
||||||
<QTd>
|
{{ getTotal(tableRows, 'net') }}
|
||||||
<VnSelect
|
|
||||||
v-model="row[col.model]"
|
|
||||||
:options="col.options"
|
|
||||||
:option-value="col.optionValue"
|
|
||||||
:option-label="col.optionLabel"
|
|
||||||
:filter-options="['id', 'description']"
|
|
||||||
data-cy="intrastat-code"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
{{ `${scope.opt.id}: ${scope.opt.description}` }}
|
|
||||||
</QItem>
|
|
||||||
</template>
|
</template>
|
||||||
</VnSelect>
|
<template #column-footer-stems>
|
||||||
</QTd>
|
{{ getTotal(tableRows, 'stems', { decimalPlaces: 0 }) }}
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-country="{ row, col }">
|
</VnTable>
|
||||||
<QTd>
|
|
||||||
<VnSelect
|
|
||||||
v-model="row[col.model]"
|
|
||||||
:options="col.options"
|
|
||||||
:option-value="col.optionValue"
|
|
||||||
:option-label="col.optionLabel"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #bottom-row>
|
|
||||||
<QTr class="bg">
|
|
||||||
<QTd />
|
|
||||||
<QTd />
|
|
||||||
<QTd>
|
|
||||||
{{ getTotal(rows, 'amount', { currency: 'default' }) }}
|
|
||||||
</QTd>
|
|
||||||
<QTd>
|
|
||||||
{{ getTotal(rows, 'net') }}
|
|
||||||
</QTd>
|
|
||||||
<QTd>
|
|
||||||
{{ getTotal(rows, 'stems', { decimalPlaces: 0 }) }}
|
|
||||||
</QTd>
|
|
||||||
<QTd />
|
|
||||||
</QTr>
|
|
||||||
</template>
|
|
||||||
<template #item="props">
|
|
||||||
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
|
||||||
<QCard>
|
|
||||||
<QCardSection>
|
|
||||||
<QCheckbox v-model="props.selected" dense />
|
|
||||||
</QCardSection>
|
|
||||||
<QSeparator />
|
|
||||||
<QList>
|
|
||||||
<QItem>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('Code')"
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row['intrastatFk']"
|
|
||||||
:options="intrastats"
|
|
||||||
option-value="id"
|
|
||||||
:option-label="
|
|
||||||
(row) => `${row.id}:${row.description}`
|
|
||||||
"
|
|
||||||
:filter-options="['id', 'description']"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
{{
|
|
||||||
`${scope.opt.id}: ${scope.opt.description}`
|
|
||||||
}}
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelect>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-for="(value, index) of [
|
|
||||||
'amount',
|
|
||||||
'net',
|
|
||||||
'stems',
|
|
||||||
]"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<VnInputNumber
|
|
||||||
:label="t(value)"
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row[value]"
|
|
||||||
clearable
|
|
||||||
clear-icon="close"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('country')"
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row['countryFk']"
|
|
||||||
:options="countries"
|
|
||||||
option-value="id"
|
|
||||||
option-label="code"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
</QList>
|
|
||||||
</QCard>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</QTable>
|
|
||||||
</template>
|
|
||||||
</CrudModel>
|
|
||||||
</div>
|
|
||||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
|
||||||
<QBtn
|
|
||||||
color="primary"
|
|
||||||
icon="add"
|
|
||||||
v-shortcut="'+'"
|
|
||||||
size="lg"
|
|
||||||
round
|
|
||||||
@click="invoiceInFormRef.insert()"
|
|
||||||
/>
|
|
||||||
</QPageSticky>
|
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
.invoiceIn-intrastat {
|
.invoice-in-intrastat {
|
||||||
> .q-card {
|
|
||||||
.vn-label-value {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1em;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
.label {
|
}
|
||||||
flex: 1;
|
:deep(.full-width) {
|
||||||
}
|
max-width: 900px;
|
||||||
.value {
|
|
||||||
flex: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<i18n>
|
<i18n>
|
||||||
|
@ -258,4 +156,5 @@ const columns = computed(() => [
|
||||||
Total amount: Total importe
|
Total amount: Total importe
|
||||||
Total net: Total neto
|
Total net: Total neto
|
||||||
Total stems: Total tallos
|
Total stems: Total tallos
|
||||||
|
Create Intrastat: Crear Intrastat
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -11,6 +11,8 @@ import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorP
|
||||||
import InvoiceIntoBook from '../InvoiceInToBook.vue';
|
import InvoiceIntoBook from '../InvoiceInToBook.vue';
|
||||||
import VnTitle from 'src/components/common/VnTitle.vue';
|
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||||
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
|
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
|
||||||
|
import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue';
|
||||||
|
import { getTotal } from 'src/composables/getTotal';
|
||||||
|
|
||||||
const props = defineProps({ id: { type: [Number, String], default: 0 } });
|
const props = defineProps({ id: { type: [Number, String], default: 0 } });
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -161,6 +163,22 @@ const intrastatColumns = ref([
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const vehicleColumns = ref([
|
||||||
|
{
|
||||||
|
name: 'numberPlate',
|
||||||
|
label: 'globals.vehicle',
|
||||||
|
field: (row) => row.vehicleInvoiceIn?.numberPlate,
|
||||||
|
format: (value) => value,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
label: 'invoiceIn.list.amount',
|
||||||
|
field: (row) => toCurrency(row.vehicleInvoiceIn?.amount),
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
invoiceInUrl.value = `${await getUrl('')}invoiceIn/${entityId.value}/`;
|
invoiceInUrl.value = `${await getUrl('')}invoiceIn/${entityId.value}/`;
|
||||||
});
|
});
|
||||||
|
@ -218,6 +236,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
<VnTitle
|
<VnTitle
|
||||||
:url="getLink('basic-data')"
|
:url="getLink('basic-data')"
|
||||||
:text="t('globals.pageTitles.basicData')"
|
:text="t('globals.pageTitles.basicData')"
|
||||||
|
data-cy="basicDataTitleLink"
|
||||||
/>
|
/>
|
||||||
<div class="vn-card-group">
|
<div class="vn-card-group">
|
||||||
<div class="vn-card-content">
|
<div class="vn-card-content">
|
||||||
|
@ -282,7 +301,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
:value="entity.expenseDeductible?.name"
|
:value="entity.expenseDeductible?.name"
|
||||||
/>
|
/>
|
||||||
<VnLv
|
<VnLv
|
||||||
:label="t('invoiceIn.card.company')"
|
:label="t('globals.company')"
|
||||||
:value="entity.company?.code"
|
:value="entity.company?.code"
|
||||||
/>
|
/>
|
||||||
<VnLv
|
<VnLv
|
||||||
|
@ -319,8 +338,12 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
</div>
|
</div>
|
||||||
</QCard>
|
</QCard>
|
||||||
<!--Vat-->
|
<!--Vat-->
|
||||||
<QCard v-if="entity.invoiceInTax.length" class="vat">
|
<QCard v-if="entity.invoiceInTax.length" class="col-extend">
|
||||||
<VnTitle :url="getLink('vat')" :text="t('invoiceIn.card.vat')" />
|
<VnTitle
|
||||||
|
:url="getLink('vat')"
|
||||||
|
:text="t('globals.pageTitles.vat')"
|
||||||
|
data-cy="vatTitleLink"
|
||||||
|
/>
|
||||||
<QTable
|
<QTable
|
||||||
:columns="vatColumns"
|
:columns="vatColumns"
|
||||||
:rows="entity.invoiceInTax"
|
:rows="entity.invoiceInTax"
|
||||||
|
@ -363,6 +386,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
<QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd>
|
<QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd>
|
||||||
<QTd></QTd>
|
<QTd></QTd>
|
||||||
<QTd></QTd>
|
<QTd></QTd>
|
||||||
|
<QTd></QTd>
|
||||||
<QTd>{{ toCurrency(getTotalTax(entity.invoiceInTax)) }}</QTd>
|
<QTd>{{ toCurrency(getTotalTax(entity.invoiceInTax)) }}</QTd>
|
||||||
<QTd>{{
|
<QTd>{{
|
||||||
entity.totals.totalTaxableBaseForeignValue &&
|
entity.totals.totalTaxableBaseForeignValue &&
|
||||||
|
@ -371,13 +395,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
currency,
|
currency,
|
||||||
)
|
)
|
||||||
}}</QTd>
|
}}</QTd>
|
||||||
|
<QTd></QTd>
|
||||||
</QTr>
|
</QTr>
|
||||||
</template>
|
</template>
|
||||||
</QTable>
|
</QTable>
|
||||||
</QCard>
|
</QCard>
|
||||||
<!--Due Day-->
|
<!--Due Day-->
|
||||||
<QCard v-if="entity.invoiceInDueDay.length" class="due-day">
|
<QCard v-if="entity.invoiceInDueDay.length" class="col-shrink">
|
||||||
<VnTitle :url="getLink('due-day')" :text="t('invoiceIn.card.dueDay')" />
|
<VnTitle
|
||||||
|
:url="getLink('due-day')"
|
||||||
|
:text="t('globals.pageTitles.dueDay')"
|
||||||
|
data-cy="dueDayTitleLink"
|
||||||
|
/>
|
||||||
<QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat>
|
<QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat>
|
||||||
<template #header="dueDayProps">
|
<template #header="dueDayProps">
|
||||||
<QTr :props="dueDayProps" class="bg">
|
<QTr :props="dueDayProps" class="bg">
|
||||||
|
@ -412,10 +441,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
</QTable>
|
</QTable>
|
||||||
</QCard>
|
</QCard>
|
||||||
<!--Intrastat-->
|
<!--Intrastat-->
|
||||||
<QCard v-if="entity.invoiceInIntrastat.length">
|
<QCard v-if="entity.invoiceInIntrastat.length" class="col-extend">
|
||||||
<VnTitle
|
<VnTitle
|
||||||
:url="getLink('intrastat')"
|
:url="getLink('intrastat')"
|
||||||
:text="t('invoiceIn.card.intrastat')"
|
:text="t('globals.pageTitles.intrastat')"
|
||||||
|
data-cy="intrastatTitleLink"
|
||||||
/>
|
/>
|
||||||
<QTable
|
<QTable
|
||||||
:columns="intrastatColumns"
|
:columns="intrastatColumns"
|
||||||
|
@ -449,6 +479,53 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
</template>
|
</template>
|
||||||
</QTable>
|
</QTable>
|
||||||
</QCard>
|
</QCard>
|
||||||
|
<!-- Vehicle -->
|
||||||
|
<QCard v-if="entity?.vehicleInvoiceIn?.length" class="col-shrink">
|
||||||
|
<VnTitle
|
||||||
|
:url="getLink('vehicle')"
|
||||||
|
:text="t('globals.vehicle')"
|
||||||
|
data-cy="vehicleTitleLink"
|
||||||
|
/>
|
||||||
|
<QTable :columns="vehicleColumns" :rows="entity.vehicleInvoiceIn" flat>
|
||||||
|
<template #header="vehicleProps">
|
||||||
|
<QTr :props="vehicleProps" class="bg">
|
||||||
|
<QTh
|
||||||
|
v-for="col in vehicleProps.cols"
|
||||||
|
:key="col.name"
|
||||||
|
:props="vehicleProps"
|
||||||
|
>
|
||||||
|
{{ t(col.label) }}
|
||||||
|
</QTh>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
<template #body="vehicleProps">
|
||||||
|
<QTr :props="vehicleProps">
|
||||||
|
<QTd>
|
||||||
|
<span class="link" data-cy="invoiceInSummary_vehicle">
|
||||||
|
{{ vehicleProps.row.vehicle.numberPlate }}
|
||||||
|
<VehicleDescriptorProxy
|
||||||
|
:id="vehicleProps.row.vehicleFk"
|
||||||
|
/> </span
|
||||||
|
></QTd>
|
||||||
|
<QTd align="left">{{
|
||||||
|
toCurrency(vehicleProps.row.amount)
|
||||||
|
}}</QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
<template #bottom-row>
|
||||||
|
<QTr class="bg">
|
||||||
|
<QTd></QTd>
|
||||||
|
<QTd>
|
||||||
|
{{
|
||||||
|
toCurrency(
|
||||||
|
getTotal(entity.vehicleInvoiceIn, 'amount'),
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</QCard>
|
||||||
</template>
|
</template>
|
||||||
</CardSummary>
|
</CardSummary>
|
||||||
</template>
|
</template>
|
||||||
|
@ -462,15 +539,15 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
||||||
|
|
||||||
@media (min-width: $breakpoint-md) {
|
@media (min-width: $breakpoint-md) {
|
||||||
.summaryBody {
|
.summaryBody {
|
||||||
.vat {
|
.col-extend {
|
||||||
flex: 65%;
|
flex: 65%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.due-day {
|
.col-shrink {
|
||||||
flex: 30%;
|
flex: 30%;
|
||||||
}
|
}
|
||||||
.vat,
|
.col-extend,
|
||||||
.due-day {
|
.col-shrink {
|
||||||
.q-table th {
|
.q-table th {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,177 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, nextTick } from 'vue';
|
import { ref, computed, markRaw, useTemplateRef, onBeforeMount } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
import { getTotal } from 'src/composables/getTotal';
|
import { getTotal } from 'src/composables/getTotal';
|
||||||
import { toCurrency } from 'src/filters';
|
import { toCurrency } from 'src/filters';
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
|
||||||
import CrudModel from 'src/components/CrudModel.vue';
|
|
||||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
|
||||||
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
|
|
||||||
import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue';
|
|
||||||
import { getExchange } from 'src/composables/getExchange';
|
import { getExchange } from 'src/composables/getExchange';
|
||||||
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
|
import VnSelectExpense from 'src/components/common/VnSelectExpense.vue';
|
||||||
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
|
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const arrayData = useArrayData();
|
const arrayData = useArrayData();
|
||||||
|
const expensesArrayData = useArrayData('expenses', { url: 'Expenses' });
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const invoiceIn = computed(() => arrayData.store.data);
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
const currency = computed(() => invoiceIn.value?.currency?.code);
|
const currency = computed(() => invoiceIn.value?.currency?.code);
|
||||||
const expenses = ref([]);
|
const expenses = computed(() => expensesArrayData.store.data);
|
||||||
const sageTaxTypes = ref([]);
|
const sageTaxTypes = ref([]);
|
||||||
const sageTransactionTypes = ref([]);
|
const sageTransactionTypes = ref([]);
|
||||||
const rowsSelected = ref([]);
|
const rowsSelected = ref([]);
|
||||||
const invoiceInFormRef = ref();
|
const invoiceInVatTableRef = useTemplateRef('invoiceInVatTableRef');
|
||||||
|
defineProps({ actionIcon: { type: String, default: 'add' } });
|
||||||
|
|
||||||
defineProps({
|
function taxRate(invoiceInTax) {
|
||||||
actionIcon: {
|
const sageTaxTypeId = invoiceInTax.taxTypeSageFk;
|
||||||
type: String,
|
const taxRateSelection = sageTaxTypes.value.find(
|
||||||
default: 'add',
|
(transaction) => transaction.id == sageTaxTypeId,
|
||||||
},
|
);
|
||||||
});
|
const taxTypeSage = taxRateSelection?.rate ?? 0;
|
||||||
|
const taxableBase = invoiceInTax?.taxableBase ?? 0;
|
||||||
|
|
||||||
|
return (taxTypeSage / 100) * taxableBase;
|
||||||
|
}
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'expense',
|
name: 'expenseFk',
|
||||||
label: t('Expense'),
|
label: t('Expense'),
|
||||||
field: (row) => row.expenseFk,
|
component: markRaw(VnSelectExpense),
|
||||||
options: expenses.value,
|
format: (row, dashIfEmpty) => {
|
||||||
model: 'expenseFk',
|
const expense = expenses.value?.find((e) => e.id === row.expenseFk);
|
||||||
optionValue: 'id',
|
return expense ? `${expense.id}: ${expense.name}` : dashIfEmpty(null);
|
||||||
optionLabel: (row) => `${row.id}: ${row.name}`,
|
},
|
||||||
sortable: true,
|
sortable: true,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
|
width: 'max-content',
|
||||||
|
cellEvent: {
|
||||||
|
keydown: (evt, row) => {
|
||||||
|
if (evt.key !== 'Tab') return;
|
||||||
|
const val = evt.target.value;
|
||||||
|
if (!val || isNaN(val)) return;
|
||||||
|
row.expenseFk = expenses.value.find(
|
||||||
|
(e) => e.id === useAccountShortToStandard(val),
|
||||||
|
)?.id;
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'taxablebase',
|
name: 'taxableBase',
|
||||||
label: t('Taxable base'),
|
label: t('Taxable base'),
|
||||||
field: (row) => row.taxableBase,
|
component: 'number',
|
||||||
model: 'taxableBase',
|
attrs: {
|
||||||
|
clearable: true,
|
||||||
|
'clear-icon': 'close',
|
||||||
|
},
|
||||||
sortable: true,
|
sortable: true,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'isDeductible',
|
name: 'isDeductible',
|
||||||
label: t('invoiceIn.isDeductible'),
|
label: t('invoiceIn.isDeductible'),
|
||||||
field: (row) => row.isDeductible,
|
component: 'checkbox',
|
||||||
model: 'isDeductible',
|
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
|
createAttrs: {
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sageiva',
|
name: 'taxTypeSageFk',
|
||||||
label: t('Sage iva'),
|
label: t('Sage iva'),
|
||||||
field: (row) => row.taxTypeSageFk,
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
options: sageTaxTypes.value,
|
options: sageTaxTypes.value,
|
||||||
model: 'taxTypeSageFk',
|
|
||||||
optionValue: 'id',
|
optionValue: 'id',
|
||||||
optionLabel: (row) => `${row.id}: ${row.vat}`,
|
optionLabel: (row) => `${row.id}: ${row.vat}`,
|
||||||
|
filterOptions: ['id', 'vat'],
|
||||||
|
'data-cy': 'vat-sageiva',
|
||||||
|
},
|
||||||
|
format: ({ taxTypeSageFk }, dashIfEmpty) => {
|
||||||
|
const taxType = sageTaxTypes.value.find((t) => t.id === taxTypeSageFk);
|
||||||
|
return taxType ? `${taxType.id}: ${taxType.vat}` : dashIfEmpty(taxTypeSageFk);
|
||||||
|
},
|
||||||
sortable: true,
|
sortable: true,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
|
width: 'max-content',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sagetransaction',
|
name: 'transactionTypeSageFk',
|
||||||
label: t('Sage transaction'),
|
label: t('Sage transaction'),
|
||||||
field: (row) => row.transactionTypeSageFk,
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
options: sageTransactionTypes.value,
|
options: sageTransactionTypes.value,
|
||||||
model: 'transactionTypeSageFk',
|
|
||||||
optionValue: 'id',
|
optionValue: 'id',
|
||||||
optionLabel: (row) => `${row.id}: ${row.transaction}`,
|
optionLabel: (row) => `${row.id}: ${row.transaction}`,
|
||||||
|
filterOptions: ['id', 'transaction'],
|
||||||
|
},
|
||||||
|
format: ({ transactionTypeSageFk }, dashIfEmpty) => {
|
||||||
|
const transType = sageTransactionTypes.value.find(
|
||||||
|
(t) => t.id === transactionTypeSageFk,
|
||||||
|
);
|
||||||
|
return transType
|
||||||
|
? `${transType.id}: ${transType.transaction}`
|
||||||
|
: dashIfEmpty(transactionTypeSageFk);
|
||||||
|
},
|
||||||
sortable: true,
|
sortable: true,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
isEditable: true,
|
||||||
|
create: true,
|
||||||
|
width: 'max-content',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'rate',
|
name: 'rate',
|
||||||
label: t('Rate'),
|
label: t('Rate'),
|
||||||
sortable: true,
|
sortable: false,
|
||||||
field: (row) => taxRate(row, row.taxTypeSageFk),
|
format: (row) => taxRate(row).toFixed(2),
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'foreignvalue',
|
name: 'foreignValue',
|
||||||
label: t('Foreign value'),
|
label: t('Foreign value'),
|
||||||
|
component: 'number',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: (row) => row.foreignValue,
|
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
create: true,
|
||||||
|
isEditable: isNotEuro(currency.value),
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.foreignValue),
|
||||||
|
cellEvent: {
|
||||||
|
'update:modelValue': async (value, oldValue, row) =>
|
||||||
|
await handleForeignValueUpdate(value, row),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'total',
|
name: 'total',
|
||||||
label: 'Total',
|
label: t('Total'),
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
format: (row) => (Number(row.taxableBase || 0) + Number(taxRate(row))).toFixed(2),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const tableRows = computed(
|
||||||
|
() => invoiceInVatTableRef.value?.CrudModelRef?.formData || [],
|
||||||
|
);
|
||||||
const taxableBaseTotal = computed(() => {
|
const taxableBaseTotal = computed(() => {
|
||||||
return getTotal(invoiceInFormRef.value.formData, 'taxableBase');
|
return getTotal(tableRows.value, 'taxableBase');
|
||||||
});
|
});
|
||||||
|
|
||||||
const taxRateTotal = computed(() => {
|
const taxRateTotal = computed(() => {
|
||||||
return getTotal(invoiceInFormRef.value.formData, null, {
|
return tableRows.value.reduce((sum, row) => sum + Number(taxRate(row)), 0);
|
||||||
cb: taxRate,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const combinedTotal = computed(() => {
|
const combinedTotal = computed(() => {
|
||||||
return +taxableBaseTotal.value + +taxRateTotal.value;
|
return +taxableBaseTotal.value + +taxRateTotal.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const filter = {
|
const filter = computed(() => ({
|
||||||
fields: [
|
fields: [
|
||||||
'id',
|
'id',
|
||||||
'invoiceInFk',
|
'invoiceInFk',
|
||||||
|
@ -131,389 +185,75 @@ const filter = {
|
||||||
where: {
|
where: {
|
||||||
invoiceInFk: route.params.id,
|
invoiceInFk: route.params.id,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
|
onBeforeMount(async () => await expensesArrayData.fetch({}));
|
||||||
|
|
||||||
const isNotEuro = (code) => code != 'EUR';
|
const isNotEuro = (code) => code != 'EUR';
|
||||||
|
|
||||||
function taxRate(invoiceInTax) {
|
async function handleForeignValueUpdate(val, row) {
|
||||||
const sageTaxTypeId = invoiceInTax.taxTypeSageFk;
|
if (!isNotEuro(currency.value)) return;
|
||||||
const taxRateSelection = sageTaxTypes.value.find(
|
row.taxableBase = await getExchange(
|
||||||
(transaction) => transaction.id == sageTaxTypeId,
|
val,
|
||||||
|
invoiceIn.value?.currencyFk,
|
||||||
|
invoiceIn.value?.issued,
|
||||||
);
|
);
|
||||||
const taxTypeSage = taxRateSelection?.rate ?? 0;
|
|
||||||
const taxableBase = invoiceInTax?.taxableBase ?? 0;
|
|
||||||
|
|
||||||
return ((taxTypeSage / 100) * taxableBase).toFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function autocompleteExpense(evt, row, col, ref) {
|
|
||||||
const val = evt.target.value;
|
|
||||||
if (!val) return;
|
|
||||||
|
|
||||||
const param = isNaN(val) ? row[col.model] : val;
|
|
||||||
const lookup = expenses.value.find(
|
|
||||||
({ id }) => id == useAccountShortToStandard(param),
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCursor(ref) {
|
|
||||||
nextTick(() => {
|
|
||||||
const select = ref.vnSelectDialogRef
|
|
||||||
? ref.vnSelectDialogRef.vnSelectRef
|
|
||||||
: ref.vnSelectRef;
|
|
||||||
select.$el.querySelector('input').setSelectionRange(0, 0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
|
||||||
ref="expensesRef"
|
|
||||||
url="Expenses"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (expenses = data)"
|
|
||||||
/>
|
|
||||||
<FetchData url="SageTaxTypes" auto-load @on-fetch="(data) => (sageTaxTypes = data)" />
|
<FetchData url="SageTaxTypes" auto-load @on-fetch="(data) => (sageTaxTypes = data)" />
|
||||||
<FetchData
|
<FetchData
|
||||||
url="sageTransactionTypes"
|
url="sageTransactionTypes"
|
||||||
auto-load
|
auto-load
|
||||||
@on-fetch="(data) => (sageTransactionTypes = data)"
|
@on-fetch="(data) => (sageTransactionTypes = data)"
|
||||||
/>
|
/>
|
||||||
<CrudModel
|
<VnTable
|
||||||
ref="invoiceInFormRef"
|
|
||||||
v-if="invoiceIn"
|
v-if="invoiceIn"
|
||||||
|
ref="invoiceInVatTableRef"
|
||||||
data-key="InvoiceInTaxes"
|
data-key="InvoiceInTaxes"
|
||||||
url="InvoiceInTaxes"
|
url="InvoiceInTaxes"
|
||||||
|
save-url="InvoiceInTaxes/crud"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
:data-required="{ invoiceInFk: $route.params.id }"
|
:data-required="{ invoiceInFk: $route.params.id }"
|
||||||
:insert-on-load="true"
|
|
||||||
auto-load
|
auto-load
|
||||||
v-model:selected="rowsSelected"
|
v-model:selected="rowsSelected"
|
||||||
|
:columns="columns"
|
||||||
|
:is-editable="true"
|
||||||
|
:table="{ selection: 'multiple', 'row-key': '$index' }"
|
||||||
|
footer
|
||||||
|
:right-search="false"
|
||||||
|
:column-search="false"
|
||||||
|
:disable-option="{ card: true }"
|
||||||
|
class="q-pa-none"
|
||||||
|
:create="{
|
||||||
|
urlCreate: 'InvoiceInTaxes',
|
||||||
|
title: t('Add tax'),
|
||||||
|
formInitialData: { invoiceInFk: $route.params.id, isDeductible: true },
|
||||||
|
onDataSaved: () => invoiceInVatTableRef.reload(),
|
||||||
|
}"
|
||||||
:go-to="`/invoice-in/${$route.params.id}/due-day`"
|
:go-to="`/invoice-in/${$route.params.id}/due-day`"
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #column-footer-taxableBase>
|
||||||
<QTable
|
|
||||||
v-model:selected="rowsSelected"
|
|
||||||
selection="multiple"
|
|
||||||
:columns="columns"
|
|
||||||
:rows="rows"
|
|
||||||
row-key="$index"
|
|
||||||
:grid="$q.screen.lt.sm"
|
|
||||||
>
|
|
||||||
<template #body-cell-expense="{ row, col }">
|
|
||||||
<QTd>
|
|
||||||
<VnSelectDialog
|
|
||||||
:ref="`expenseRef-${row.$index}`"
|
|
||||||
v-model="row[col.model]"
|
|
||||||
:options="col.options"
|
|
||||||
:option-value="col.optionValue"
|
|
||||||
:option-label="col.optionLabel"
|
|
||||||
:filter-options="['id', 'name']"
|
|
||||||
:tooltip="t('Create a new expense')"
|
|
||||||
:acls="[
|
|
||||||
{ model: 'Expense', props: '*', accessType: 'WRITE' },
|
|
||||||
]"
|
|
||||||
@keydown.tab.prevent="
|
|
||||||
autocompleteExpense(
|
|
||||||
$event,
|
|
||||||
row,
|
|
||||||
col,
|
|
||||||
$refs[`expenseRef-${row.$index}`],
|
|
||||||
)
|
|
||||||
"
|
|
||||||
@update:model-value="
|
|
||||||
setCursor($refs[`expenseRef-${row.$index}`])
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
{{ `${scope.opt.id}: ${scope.opt.name}` }}
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
<template #form>
|
|
||||||
<CreateNewExpenseForm
|
|
||||||
@on-data-saved="$refs.expensesRef.fetch()"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VnSelectDialog>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-isDeductible="{ row }">
|
|
||||||
<QTd align="center">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="row.isDeductible"
|
|
||||||
data-cy="isDeductible_checkbox"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-taxablebase="{ row }">
|
|
||||||
<QTd shrink>
|
|
||||||
<VnInputNumber
|
|
||||||
clear-icon="close"
|
|
||||||
v-model="row.taxableBase"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-sageiva="{ row, col }">
|
|
||||||
<QTd>
|
|
||||||
<VnSelect
|
|
||||||
:ref="`sageivaRef-${row.$index}`"
|
|
||||||
v-model="row[col.model]"
|
|
||||||
:options="col.options"
|
|
||||||
:option-value="col.optionValue"
|
|
||||||
:option-label="col.optionLabel"
|
|
||||||
:filter-options="['id', 'vat']"
|
|
||||||
data-cy="vat-sageiva"
|
|
||||||
@update:model-value="
|
|
||||||
setCursor($refs[`sageivaRef-${row.$index}`])
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
<QItemSection>
|
|
||||||
<QItemLabel>{{ scope.opt.vat }}</QItemLabel>
|
|
||||||
<QItemLabel>
|
|
||||||
{{ `#${scope.opt.id}` }}
|
|
||||||
</QItemLabel>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelect>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-sagetransaction="{ row, col }">
|
|
||||||
<QTd>
|
|
||||||
<VnSelect
|
|
||||||
:ref="`sagetransactionRef-${row.$index}`"
|
|
||||||
v-model="row[col.model]"
|
|
||||||
:options="col.options"
|
|
||||||
:option-value="col.optionValue"
|
|
||||||
:option-label="col.optionLabel"
|
|
||||||
:filter-options="['id', 'transaction']"
|
|
||||||
@update:model-value="
|
|
||||||
setCursor($refs[`sagetransactionRef-${row.$index}`])
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
<QItemSection>
|
|
||||||
<QItemLabel>{{
|
|
||||||
scope.opt.transaction
|
|
||||||
}}</QItemLabel>
|
|
||||||
<QItemLabel>
|
|
||||||
{{ `#${scope.opt.id}` }}
|
|
||||||
</QItemLabel>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelect>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-foreignvalue="{ row }">
|
|
||||||
<QTd shrink>
|
|
||||||
<VnInputNumber
|
|
||||||
:class="{
|
|
||||||
'no-pointer-events': !isNotEuro(currency),
|
|
||||||
}"
|
|
||||||
:disable="!isNotEuro(currency)"
|
|
||||||
v-model="row.foreignValue"
|
|
||||||
@update:model-value="
|
|
||||||
async (val) => {
|
|
||||||
if (!isNotEuro(currency)) return;
|
|
||||||
row.taxableBase = await getExchange(
|
|
||||||
val,
|
|
||||||
row.currencyFk,
|
|
||||||
invoiceIn.issued,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #bottom-row>
|
|
||||||
<QTr class="bg">
|
|
||||||
<QTd />
|
|
||||||
<QTd />
|
|
||||||
<QTd>
|
|
||||||
{{ toCurrency(taxableBaseTotal) }}
|
{{ toCurrency(taxableBaseTotal) }}
|
||||||
</QTd>
|
</template>
|
||||||
<QTd />
|
<template #column-footer-rate>
|
||||||
<QTd />
|
|
||||||
<QTd />
|
|
||||||
<QTd>
|
|
||||||
{{ toCurrency(taxRateTotal) }}
|
{{ toCurrency(taxRateTotal) }}
|
||||||
</QTd>
|
</template>
|
||||||
<QTd />
|
<template #column-footer-total>
|
||||||
<QTd>
|
|
||||||
{{ toCurrency(combinedTotal) }}
|
{{ toCurrency(combinedTotal) }}
|
||||||
</QTd>
|
|
||||||
</QTr>
|
|
||||||
</template>
|
</template>
|
||||||
|
</VnTable>
|
||||||
<template #item="props">
|
|
||||||
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
|
||||||
<QCard bordered flat class="q-my-xs">
|
|
||||||
<QCardSection>
|
|
||||||
<QCheckbox v-model="props.selected" dense />
|
|
||||||
</QCardSection>
|
|
||||||
<QSeparator />
|
|
||||||
<QList>
|
|
||||||
<QItem>
|
|
||||||
<VnSelectDialog
|
|
||||||
:label="t('Expense')"
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row['expenseFk']"
|
|
||||||
:options="expenses"
|
|
||||||
option-value="id"
|
|
||||||
:option-label="(row) => `${row.id}:${row.name}`"
|
|
||||||
:filter-options="['id', 'name']"
|
|
||||||
:tooltip="t('Create a new expense')"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
{{ `${scope.opt.id}: ${scope.opt.name}` }}
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
<template #form>
|
|
||||||
<CreateNewExpenseForm />
|
|
||||||
</template>
|
|
||||||
</VnSelectDialog>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnInputNumber
|
|
||||||
:label="t('Taxable base')"
|
|
||||||
:class="{
|
|
||||||
'no-pointer-events': isNotEuro(currency),
|
|
||||||
}"
|
|
||||||
class="full-width"
|
|
||||||
:disable="isNotEuro(currency)"
|
|
||||||
clear-icon="close"
|
|
||||||
v-model="props.row.taxableBase"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('Sage iva')"
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row['taxTypeSageFk']"
|
|
||||||
:options="sageTaxTypes"
|
|
||||||
option-value="id"
|
|
||||||
:option-label="(row) => `${row.id}:${row.vat}`"
|
|
||||||
:filter-options="['id', 'vat']"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
<QItemSection>
|
|
||||||
<QItemLabel>{{
|
|
||||||
scope.opt.vat
|
|
||||||
}}</QItemLabel>
|
|
||||||
<QItemLabel>
|
|
||||||
{{ `#${scope.opt.id}` }}
|
|
||||||
</QItemLabel>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelect>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnSelect
|
|
||||||
class="full-width"
|
|
||||||
v-model="props.row['transactionTypeSageFk']"
|
|
||||||
:options="sageTransactionTypes"
|
|
||||||
option-value="id"
|
|
||||||
:option-label="
|
|
||||||
(row) => `${row.id}:${row.transaction}`
|
|
||||||
"
|
|
||||||
:filter-options="['id', 'transaction']"
|
|
||||||
>
|
|
||||||
<template #option="scope">
|
|
||||||
<QItem v-bind="scope.itemProps">
|
|
||||||
<QItemSection>
|
|
||||||
<QItemLabel>{{
|
|
||||||
scope.opt.transaction
|
|
||||||
}}</QItemLabel>
|
|
||||||
<QItemLabel>
|
|
||||||
{{ `#${scope.opt.id}` }}
|
|
||||||
</QItemLabel>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelect>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
{{ toCurrency(taxRate(props.row), currency) }}
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<VnInputNumber
|
|
||||||
:label="t('Foreign value')"
|
|
||||||
class="full-width"
|
|
||||||
:class="{
|
|
||||||
'no-pointer-events': !isNotEuro(currency),
|
|
||||||
}"
|
|
||||||
:disable="!isNotEuro(currency)"
|
|
||||||
v-model="props.row.foreignValue"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
</QList>
|
|
||||||
</QCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</QTable>
|
|
||||||
</template>
|
|
||||||
</CrudModel>
|
|
||||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
|
||||||
<QBtn
|
|
||||||
color="primary"
|
|
||||||
icon="add"
|
|
||||||
size="lg"
|
|
||||||
v-shortcut="'+'"
|
|
||||||
round
|
|
||||||
@click="invoiceInFormRef.insert()"
|
|
||||||
>
|
|
||||||
<QTooltip>{{ t('Add tax') }}</QTooltip>
|
|
||||||
</QBtn>
|
|
||||||
</QPageSticky>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.bg {
|
|
||||||
background-color: var(--vn-light-gray);
|
|
||||||
}
|
|
||||||
@media (max-width: $breakpoint-xs) {
|
|
||||||
.q-dialog {
|
|
||||||
.q-card {
|
|
||||||
&__section:not(:first-child) {
|
|
||||||
.q-item {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.q-checkbox {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.q-item {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
.default-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 50px;
|
|
||||||
background-color: $primary;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
Expense: Gasto
|
Expense: Gasto
|
||||||
Create a new expense: Crear nuevo gasto
|
Create a new expense: Crear nuevo gasto
|
||||||
Add tax: Crear gasto
|
Add tax: Añadir Gasto/IVA # Changed label slightly
|
||||||
Taxable base: Base imp.
|
Taxable base: Base imp.
|
||||||
Sage tax: Sage iva
|
Sage iva: Sage iva # Kept original label
|
||||||
Sage transaction: Sage transacción
|
Sage transaction: Sage transacción
|
||||||
Rate: Tasa
|
Rate: Cuota # Changed label
|
||||||
Foreign value: Divisa
|
Foreign value: Divisa
|
||||||
|
Total: Total
|
||||||
|
invoiceIn.isDeductible: Deducible
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
<script setup>
|
||||||
|
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||||
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
|
import { toCurrency } from 'src/filters';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue';
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { openConfirmationModal } = useVnConfirm();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
const dataKey = 'InvoiceInVehicleList';
|
||||||
|
const filter = {
|
||||||
|
include: [{ relation: 'vehicle', scope: { fields: ['id', 'numberPlate'] } }],
|
||||||
|
};
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'vehicleFk',
|
||||||
|
label: t('globals.vehicle'),
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'vehicles',
|
||||||
|
fields: ['id', 'numberPlate'],
|
||||||
|
optionLabel: 'numberPlate',
|
||||||
|
optionFilterValue: 'numberPlate',
|
||||||
|
find: {
|
||||||
|
value: 'vehicleFk',
|
||||||
|
label: 'vehiclePlateNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: true,
|
||||||
|
format: (row) => row.vehicle?.numberPlate,
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'amount',
|
||||||
|
label: t('invoiceIn.list.amount'),
|
||||||
|
component: 'number',
|
||||||
|
create: true,
|
||||||
|
format: (row) => toCurrency(row.amount),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
name: 'tableActions',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: t('invoiceIn.unlinkVehicle'),
|
||||||
|
icon: 'delete',
|
||||||
|
action: (row) =>
|
||||||
|
openConfirmationModal(
|
||||||
|
t('invoiceIn.unlinkVehicle'),
|
||||||
|
t('invoiceIn.unlinkVehicleConfirmation'),
|
||||||
|
() => unassignVehicle(row.id),
|
||||||
|
),
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function unassignVehicle(id) {
|
||||||
|
try {
|
||||||
|
await axios.delete(`VehicleInvoiceIns/${id}`);
|
||||||
|
notify(t('invoiceIn.unlinkedVehicle'), 'positive');
|
||||||
|
tableRef.value.reload();
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VnTable
|
||||||
|
ref="tableRef"
|
||||||
|
:data-key="dataKey"
|
||||||
|
url="VehicleInvoiceIns"
|
||||||
|
:user-filter="filter"
|
||||||
|
:filter="{ where: { invoiceInFk: route.params.id } }"
|
||||||
|
:columns="columns"
|
||||||
|
:column-search="false"
|
||||||
|
:right-search="false"
|
||||||
|
:create="{
|
||||||
|
urlCreate: 'VehicleInvoiceIns',
|
||||||
|
title: t('invoiceIn.linkVehicleToInvoiceIn'),
|
||||||
|
onDataSaved: () => tableRef.reload(),
|
||||||
|
formInitialData: { invoiceInFk: route.params.id },
|
||||||
|
}"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #column-vehicleFk="{ row }">
|
||||||
|
<span class="link" @click.stop>
|
||||||
|
{{ row.vehicle?.numberPlate }}
|
||||||
|
<VehicleDescriptorProxy :id="row?.vehicleFk" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</VnTable>
|
||||||
|
</template>
|
|
@ -41,7 +41,7 @@ async function checkToBook(id) {
|
||||||
params: {
|
params: {
|
||||||
where: JSON.stringify({
|
where: JSON.stringify({
|
||||||
invoiceInFk: id,
|
invoiceInFk: id,
|
||||||
dueDated: { gte: Date.vnNew() },
|
dueDated: { lte: Date.vnNew() },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -34,14 +34,6 @@ invoiceIn:
|
||||||
originalInvoice: Original invoice
|
originalInvoice: Original invoice
|
||||||
entry: Entry
|
entry: Entry
|
||||||
emailEmpty: The email can't be empty
|
emailEmpty: The email can't be empty
|
||||||
card:
|
|
||||||
client: Client
|
|
||||||
company: Company
|
|
||||||
customerCard: Customer card
|
|
||||||
ticketList: Ticket List
|
|
||||||
vat: Vat
|
|
||||||
dueDay: Due day
|
|
||||||
intrastat: Intrastat
|
|
||||||
summary:
|
summary:
|
||||||
currency: Currency
|
currency: Currency
|
||||||
issued: Expedition date
|
issued: Expedition date
|
||||||
|
@ -69,4 +61,9 @@ invoiceIn:
|
||||||
isBooked: Is booked
|
isBooked: Is booked
|
||||||
account: Ledger account
|
account: Ledger account
|
||||||
correctingFk: Rectificative
|
correctingFk: Rectificative
|
||||||
|
issued: Issued
|
||||||
noMatch: No match with the vat({totalTaxableBase})
|
noMatch: No match with the vat({totalTaxableBase})
|
||||||
|
linkVehicleToInvoiceIn: Link vehicle to invoice
|
||||||
|
unlinkedVehicle: Unlinked vehicle
|
||||||
|
unlinkVehicle: Unlink vehicle
|
||||||
|
unlinkVehicleConfirmation: This vehicle will be unlinked from this invoice! Continue anyway?
|
||||||
|
|
|
@ -33,13 +33,6 @@ invoiceIn:
|
||||||
originalInvoice: Factura origen
|
originalInvoice: Factura origen
|
||||||
entry: Entrada
|
entry: Entrada
|
||||||
emailEmpty: El email no puede estar vacío
|
emailEmpty: El email no puede estar vacío
|
||||||
card:
|
|
||||||
client: Cliente
|
|
||||||
company: Empresa
|
|
||||||
customerCard: Ficha del cliente
|
|
||||||
ticketList: Listado de tickets
|
|
||||||
vat: Iva
|
|
||||||
dueDay: Fecha de vencimiento
|
|
||||||
summary:
|
summary:
|
||||||
currency: Divisa
|
currency: Divisa
|
||||||
docNumber: Número documento
|
docNumber: Número documento
|
||||||
|
@ -52,7 +45,7 @@ invoiceIn:
|
||||||
expense: Gasto
|
expense: Gasto
|
||||||
taxableBase: Base imp.
|
taxableBase: Base imp.
|
||||||
rate: Tasa
|
rate: Tasa
|
||||||
sageTransaction: Sage transación
|
sageTransaction: Sage transacción
|
||||||
dueDay: Fecha
|
dueDay: Fecha
|
||||||
bank: Caja
|
bank: Caja
|
||||||
foreignValue: Divisa
|
foreignValue: Divisa
|
||||||
|
@ -67,4 +60,9 @@ invoiceIn:
|
||||||
isBooked: Contabilizada
|
isBooked: Contabilizada
|
||||||
account: Cuenta contable
|
account: Cuenta contable
|
||||||
correctingFk: Rectificativa
|
correctingFk: Rectificativa
|
||||||
|
issued: Fecha de emisión
|
||||||
noMatch: No cuadra con el iva({totalTaxableBase})
|
noMatch: No cuadra con el iva({totalTaxableBase})
|
||||||
|
linkVehicleToInvoiceIn: Vincular vehículo a factura
|
||||||
|
unlinkedVehicle: Vehículo desvinculado
|
||||||
|
unlinkVehicle: Desvincular vehículo
|
||||||
|
unlinkVehicleConfirmation: Este vehículo se desvinculará de esta factura! ¿Continuar de todas formas?
|
||||||
|
|
|
@ -61,7 +61,7 @@ const onIntrastatCreated = (response, formData) => {
|
||||||
:clear-store-on-unmount="false"
|
:clear-store-on-unmount="false"
|
||||||
>
|
>
|
||||||
<template #form="{ data }">
|
<template #form="{ data }">
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('item.basicData.type')"
|
:label="t('item.basicData.type')"
|
||||||
v-model="data.typeFk"
|
v-model="data.typeFk"
|
||||||
|
@ -71,6 +71,7 @@ const onIntrastatCreated = (response, formData) => {
|
||||||
hide-selected
|
hide-selected
|
||||||
map-options
|
map-options
|
||||||
required
|
required
|
||||||
|
data-cy="itemBasicDataItemType"
|
||||||
>
|
>
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<QItem v-bind="scope.itemProps">
|
<QItem v-bind="scope.itemProps">
|
||||||
|
@ -83,23 +84,30 @@ const onIntrastatCreated = (response, formData) => {
|
||||||
</QItem>
|
</QItem>
|
||||||
</template>
|
</template>
|
||||||
</VnSelect>
|
</VnSelect>
|
||||||
<VnInput :label="t('item.basicData.reference')" v-model="data.comment" />
|
<VnInput
|
||||||
|
:label="t('item.basicData.reference')"
|
||||||
|
v-model="data.comment"
|
||||||
|
data-cy="itemBasicDataReference"
|
||||||
|
/>
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.relevancy')"
|
:label="t('item.basicData.relevancy')"
|
||||||
type="number"
|
type="number"
|
||||||
v-model="data.relevancy"
|
v-model="data.relevancy"
|
||||||
|
data-cy="itemBasicDataRelevancy"
|
||||||
/>
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow class="row q-gutter-md q-mb-md">
|
<VnRow class="q-py-sm">
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.stems')"
|
:label="t('item.basicData.stems')"
|
||||||
type="number"
|
type="number"
|
||||||
v-model="data.stems"
|
v-model="data.stems"
|
||||||
|
data-cy="itemBasicDataStems"
|
||||||
/>
|
/>
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.multiplier')"
|
:label="t('item.basicData.multiplier')"
|
||||||
type="number"
|
type="number"
|
||||||
v-model="data.stemMultiplier"
|
v-model="data.stemMultiplier"
|
||||||
|
data-cy="itemBasicDataMultiplier"
|
||||||
/>
|
/>
|
||||||
<VnSelectDialog
|
<VnSelectDialog
|
||||||
:label="t('item.basicData.generic')"
|
:label="t('item.basicData.generic')"
|
||||||
|
@ -112,6 +120,7 @@ const onIntrastatCreated = (response, formData) => {
|
||||||
map-options
|
map-options
|
||||||
hide-selected
|
hide-selected
|
||||||
action-icon="filter_alt"
|
action-icon="filter_alt"
|
||||||
|
data-cy="itemBasicDataGeneric"
|
||||||
>
|
>
|
||||||
<template #form>
|
<template #form>
|
||||||
<FilterItemForm
|
<FilterItemForm
|
||||||
|
@ -129,7 +138,12 @@ const onIntrastatCreated = (response, formData) => {
|
||||||
</template>
|
</template>
|
||||||
</VnSelectDialog>
|
</VnSelectDialog>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
|
<VnCheckbox
|
||||||
|
v-model="data.isCustomInspectionRequired"
|
||||||
|
:label="t('item.basicData.isCustomInspectionRequired')"
|
||||||
|
data-cy="itemBasicDataCustomInspection"
|
||||||
|
/>
|
||||||
<VnSelectDialog
|
<VnSelectDialog
|
||||||
:label="t('item.basicData.intrastat')"
|
:label="t('item.basicData.intrastat')"
|
||||||
v-model="data.intrastatFk"
|
v-model="data.intrastatFk"
|
||||||
|
@ -138,6 +152,8 @@ const onIntrastatCreated = (response, formData) => {
|
||||||
option-label="description"
|
option-label="description"
|
||||||
map-options
|
map-options
|
||||||
hide-selected
|
hide-selected
|
||||||
|
data-cy="itemBasicDataIntrastat"
|
||||||
|
style="max-width: 31%"
|
||||||
>
|
>
|
||||||
<template #form>
|
<template #form>
|
||||||
<CreateIntrastatForm
|
<CreateIntrastatForm
|
||||||
|
@ -165,78 +181,81 @@ const onIntrastatCreated = (response, formData) => {
|
||||||
option-label="name"
|
option-label="name"
|
||||||
hide-selected
|
hide-selected
|
||||||
map-options
|
map-options
|
||||||
|
data-cy="itemBasicDataExpense"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
|
<VnCheckbox
|
||||||
|
v-model="data.hasKgPrice"
|
||||||
|
:label="t('item.basicData.hasKgPrice')"
|
||||||
|
data-cy="itemBasicDataHasKgPrice"
|
||||||
|
/>
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.weightByPiece')"
|
:label="t('item.basicData.weightByPiece')"
|
||||||
v-model.number="data.weightByPiece"
|
v-model.number="data.weightByPiece"
|
||||||
:min="0"
|
:min="0"
|
||||||
type="number"
|
type="number"
|
||||||
|
data-cy="itemBasicDataWeightByPiece"
|
||||||
/>
|
/>
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.boxUnits')"
|
:label="t('item.basicData.boxUnits')"
|
||||||
v-model.number="data.packingOut"
|
v-model.number="data.packingOut"
|
||||||
:min="0"
|
:min="0"
|
||||||
type="number"
|
type="number"
|
||||||
|
data-cy="itemBasicDataBoxUnits"
|
||||||
/>
|
/>
|
||||||
|
</VnRow>
|
||||||
|
<VnRow class="q-py-sm">
|
||||||
|
<VnCheckbox
|
||||||
|
v-model="data.isActive"
|
||||||
|
:label="t('item.basicData.isActive')"
|
||||||
|
data-cy="itemBasicDataIsActive"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VnCheckbox
|
||||||
|
v-model="data.isFragile"
|
||||||
|
:label="t('item.basicData.isFragile')"
|
||||||
|
:info="t('item.basicData.isFragileTooltip')"
|
||||||
|
data-cy="itemBasicDataIsFragile"
|
||||||
|
/>
|
||||||
|
<VnCheckbox
|
||||||
|
v-model="data.isPhotoRequested"
|
||||||
|
:label="t('item.basicData.isPhotoRequested')"
|
||||||
|
:info="t('item.basicData.isPhotoRequestedTooltip')"
|
||||||
|
data-cy="itemBasicDataIsPhotoRequested"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
<VnRow class="q-py-sm">
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.recycledPlastic')"
|
:label="t('item.basicData.recycledPlastic')"
|
||||||
v-model.number="data.recycledPlastic"
|
v-model.number="data.recycledPlastic"
|
||||||
:min="0"
|
:min="0"
|
||||||
type="number"
|
type="number"
|
||||||
|
data-cy="itemBasicDataRecycledPlastic"
|
||||||
/>
|
/>
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.nonRecycledPlastic')"
|
:label="t('item.basicData.nonRecycledPlastic')"
|
||||||
v-model.number="data.nonRecycledPlastic"
|
v-model.number="data.nonRecycledPlastic"
|
||||||
:min="0"
|
:min="0"
|
||||||
type="number"
|
type="number"
|
||||||
|
data-cy="itemBasicDataNonRecycledPlastic"
|
||||||
/>
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow class="row q-gutter-md q-mb-md">
|
<VnRow class="q-py-sm">
|
||||||
<QCheckbox
|
|
||||||
v-model="data.isActive"
|
|
||||||
:label="t('item.basicData.isActive')"
|
|
||||||
/>
|
|
||||||
<QCheckbox
|
|
||||||
v-model="data.hasKgPrice"
|
|
||||||
:label="t('item.basicData.hasKgPrice')"
|
|
||||||
/>
|
|
||||||
<QCheckbox
|
|
||||||
v-model="data.isCustomInspectionRequired"
|
|
||||||
:label="t('item.basicData.isCustomInspectionRequired')"
|
|
||||||
/>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow class="row q-gutter-md q-mb-md">
|
|
||||||
<VnCheckbox
|
|
||||||
v-model="data.isFragile"
|
|
||||||
:label="t('item.basicData.isFragile')"
|
|
||||||
:info="t('item.basicData.isFragileTooltip')"
|
|
||||||
class="q-mr-sm"
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
<VnCheckbox
|
|
||||||
v-model="data.isPhotoRequested"
|
|
||||||
:label="t('item.basicData.isPhotoRequested')"
|
|
||||||
:info="t('item.basicData.isPhotoRequestedTooltip')"
|
|
||||||
class="q-mr-sm"
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow>
|
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('item.basicData.description')"
|
:label="t('item.basicData.description')"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
v-model="data.description"
|
v-model="data.description"
|
||||||
fill-input
|
fill-input
|
||||||
|
data-cy="itemBasicDataDescription"
|
||||||
/>
|
/>
|
||||||
<VnInput
|
<VnInput
|
||||||
v-show="data.isPhotoRequested"
|
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:label="t('globals.comment')"
|
:label="t('item.basicData.photoMotivation')"
|
||||||
v-model="data.photoMotivation"
|
v-model="data.photoMotivation"
|
||||||
fill-input
|
fill-input
|
||||||
|
data-cy="itemBasicDataPhotoMotivation"
|
||||||
/>
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -102,20 +102,21 @@ const columns = computed(() => [
|
||||||
label: t('itemDiary.in'),
|
label: t('itemDiary.in'),
|
||||||
field: 'invalue',
|
field: 'invalue',
|
||||||
name: 'in',
|
name: 'in',
|
||||||
align: 'left',
|
align: 'right',
|
||||||
format: (val) => dashIfEmpty(val),
|
format: (val) => dashIfEmpty(val),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('itemDiary.out'),
|
label: t('itemDiary.out'),
|
||||||
field: 'out',
|
field: 'out',
|
||||||
name: 'out',
|
name: 'out',
|
||||||
align: 'left',
|
align: 'right',
|
||||||
format: (val) => dashIfEmpty(val),
|
format: (val) => dashIfEmpty(val),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('itemDiary.balance'),
|
label: t('itemDiary.balance'),
|
||||||
name: 'balance',
|
name: 'balance',
|
||||||
align: 'left',
|
align: 'right',
|
||||||
|
class: 'q-px-sm',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -174,7 +175,11 @@ async function updateWarehouse(warehouseFk) {
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
<FetchData
|
||||||
url="Warehouses"
|
url="Warehouses"
|
||||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
:filter="{
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
order: 'name ASC',
|
||||||
|
where: { isDestiny: true },
|
||||||
|
}"
|
||||||
auto-load
|
auto-load
|
||||||
@on-fetch="(data) => (warehousesOptions = data)"
|
@on-fetch="(data) => (warehousesOptions = data)"
|
||||||
/>
|
/>
|
||||||
|
@ -217,7 +222,8 @@ async function updateWarehouse(warehouseFk) {
|
||||||
<QTable
|
<QTable
|
||||||
:rows="itemBalances"
|
:rows="itemBalances"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
class="full-width q-mt-md"
|
class="full-width q-mt-md q-px-md"
|
||||||
|
style="background-color: var(--vn-section-color)"
|
||||||
:no-data-label="t('globals.noResults')"
|
:no-data-label="t('globals.noResults')"
|
||||||
>
|
>
|
||||||
<template #body-cell-claim="{ row }">
|
<template #body-cell-claim="{ row }">
|
||||||
|
@ -294,14 +300,14 @@ async function updateWarehouse(warehouseFk) {
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-in="{ row }">
|
<template #body-cell-in="{ row }">
|
||||||
<QTd @click.stop>
|
<QTd @click.stop class="text-right">
|
||||||
<span :class="{ 'is-in': row.invalue }">
|
<span :class="{ 'is-in': row.invalue }">
|
||||||
{{ dashIfEmpty(row.invalue) }}
|
{{ dashIfEmpty(row.invalue) }}
|
||||||
</span>
|
</span>
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-balance="{ row }">
|
<template #body-cell-balance="{ row }">
|
||||||
<QTd @click.stop>
|
<QTd @click.stop class="text-right">
|
||||||
<QBadge
|
<QBadge
|
||||||
class="balance-negative"
|
class="balance-negative"
|
||||||
:color="
|
:color="
|
||||||
|
|
|
@ -48,7 +48,7 @@ const columns = computed(() => [
|
||||||
label: t('itemDiary.warehouse'),
|
label: t('itemDiary.warehouse'),
|
||||||
name: 'warehouse',
|
name: 'warehouse',
|
||||||
field: 'warehouse',
|
field: 'warehouse',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('lastEntries.landed'),
|
label: t('lastEntries.landed'),
|
||||||
|
@ -60,7 +60,7 @@ const columns = computed(() => [
|
||||||
label: t('lastEntries.entry'),
|
label: t('lastEntries.entry'),
|
||||||
name: 'entry',
|
name: 'entry',
|
||||||
field: 'stateName',
|
field: 'stateName',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
format: (val) => dashIfEmpty(val),
|
format: (val) => dashIfEmpty(val),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -75,14 +75,14 @@ const columns = computed(() => [
|
||||||
label: t('lastEntries.printedStickers'),
|
label: t('lastEntries.printedStickers'),
|
||||||
name: 'printedStickers',
|
name: 'printedStickers',
|
||||||
field: 'printedStickers',
|
field: 'printedStickers',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
format: (val) => dashIfEmpty(val),
|
format: (val) => dashIfEmpty(val),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('lastEntries.label'),
|
label: t('lastEntries.label'),
|
||||||
name: 'stickers',
|
name: 'stickers',
|
||||||
field: 'stickers',
|
field: 'stickers',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
format: (val) => dashIfEmpty(val),
|
format: (val) => dashIfEmpty(val),
|
||||||
style: (row) => highlightedRow(row),
|
style: (row) => highlightedRow(row),
|
||||||
},
|
},
|
||||||
|
@ -90,39 +90,39 @@ const columns = computed(() => [
|
||||||
label: 'Packing',
|
label: 'Packing',
|
||||||
name: 'packing',
|
name: 'packing',
|
||||||
field: 'packing',
|
field: 'packing',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('lastEntries.grouping'),
|
label: t('lastEntries.grouping'),
|
||||||
name: 'grouping',
|
name: 'grouping',
|
||||||
field: 'grouping',
|
field: 'grouping',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('itemBasicData.stems'),
|
label: t('itemBasicData.stems'),
|
||||||
name: 'stems',
|
name: 'stems',
|
||||||
field: 'stems',
|
field: 'stems',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
style: (row) => highlightedRow(row),
|
style: (row) => highlightedRow(row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('lastEntries.quantity'),
|
label: t('lastEntries.quantity'),
|
||||||
name: 'quantity',
|
name: 'quantity',
|
||||||
field: 'quantity',
|
field: 'quantity',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
style: (row) => highlightedRow(row),
|
style: (row) => highlightedRow(row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('lastEntries.cost'),
|
label: t('lastEntries.cost'),
|
||||||
name: 'cost',
|
name: 'cost',
|
||||||
field: 'cost',
|
field: 'right',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Kg',
|
label: 'Kg',
|
||||||
name: 'weight',
|
name: 'weight',
|
||||||
field: 'weight',
|
field: 'weight',
|
||||||
align: 'center',
|
align: 'right',
|
||||||
style: (row) => highlightedRow(row),
|
style: (row) => highlightedRow(row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -136,7 +136,7 @@ const columns = computed(() => [
|
||||||
label: t('lastEntries.supplier'),
|
label: t('lastEntries.supplier'),
|
||||||
name: 'supplier',
|
name: 'supplier',
|
||||||
field: 'supplier',
|
field: 'supplier',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ function highlightedRow(row) {
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-entry="{ row }">
|
<template #body-cell-entry="{ row }">
|
||||||
<QTd @click.stop :style="highlightedRow(row)">
|
<QTd @click.stop :style="highlightedRow(row)">
|
||||||
<div class="full-width flex justify-center">
|
<div class="full-width text-right">
|
||||||
<EntryDescriptorProxy :id="row.entryFk" class="q-ma-none" dense />
|
<EntryDescriptorProxy :id="row.entryFk" class="q-ma-none" dense />
|
||||||
<span class="link">{{ row.entryFk }}</span>
|
<span class="link">{{ row.entryFk }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -282,16 +282,16 @@ function highlightedRow(row) {
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-printedStickers="{ row }">
|
<template #body-cell-printedStickers="{ row }">
|
||||||
<QTd @click.stop class="text-center" :style="highlightedRow(row)">
|
<QTd @click.stop class="text-right" :style="highlightedRow(row)">
|
||||||
<span style="color: var(--vn-label-color)">
|
<span style="color: var(--vn-label-color)">
|
||||||
{{ row.printedStickers }}</span
|
{{ row.printedStickers }}</span
|
||||||
>
|
>
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-packing="{ row }">
|
<template #body-cell-packing="{ row }">
|
||||||
<QTd @click.stop :style="highlightedRow(row)">
|
<QTd @click.stop :style="highlightedRow(row)" class="text-right">
|
||||||
<QBadge
|
<QBadge
|
||||||
class="center-content"
|
class="grouping-badge"
|
||||||
:class="getBadgeClass(row.groupingMode, 'packing')"
|
:class="getBadgeClass(row.groupingMode, 'packing')"
|
||||||
rounded
|
rounded
|
||||||
>
|
>
|
||||||
|
@ -301,9 +301,9 @@ function highlightedRow(row) {
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-grouping="{ row }">
|
<template #body-cell-grouping="{ row }">
|
||||||
<QTd @click.stop :style="highlightedRow(row)">
|
<QTd @click.stop :style="highlightedRow(row)" class="text-right">
|
||||||
<QBadge
|
<QBadge
|
||||||
class="center-content"
|
class="grouping-badge"
|
||||||
:class="getBadgeClass(row.groupingMode, 'grouping')"
|
:class="getBadgeClass(row.groupingMode, 'grouping')"
|
||||||
rounded
|
rounded
|
||||||
>
|
>
|
||||||
|
@ -313,7 +313,7 @@ function highlightedRow(row) {
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-cost="{ row }">
|
<template #body-cell-cost="{ row }">
|
||||||
<QTd @click.stop class="text-center" :style="highlightedRow(row)">
|
<QTd @click.stop class="text-right" :style="highlightedRow(row)">
|
||||||
<span>
|
<span>
|
||||||
{{ toCurrency(row.cost, 'EUR', 3) }}
|
{{ toCurrency(row.cost, 'EUR', 3) }}
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
|
@ -357,10 +357,7 @@ function highlightedRow(row) {
|
||||||
.q-badge--rounded {
|
.q-badge--rounded {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
.center-content {
|
.grouping-badge {
|
||||||
display: flex;
|
|
||||||
max-width: max-content;
|
|
||||||
margin: auto;
|
|
||||||
padding: 0 11px;
|
padding: 0 11px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
label: t('shelvings.item'),
|
label: t('shelvings.item'),
|
||||||
name: 'itemFk',
|
name: 'itemFk',
|
||||||
align: 'left',
|
align: 'right',
|
||||||
columnFilter: false,
|
columnFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -102,19 +102,20 @@ const columns = computed(() => [
|
||||||
name: 'label',
|
name: 'label',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
columnFilter: { inWhere: true },
|
columnFilter: { inWhere: true },
|
||||||
|
component: 'number',
|
||||||
format: (row) => (row.stock / row.packing).toFixed(2),
|
format: (row) => (row.stock / row.packing).toFixed(2),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('shelvings.packing'),
|
label: t('shelvings.packing'),
|
||||||
name: 'packing',
|
name: 'packing',
|
||||||
attrs: { inWhere: true },
|
attrs: { inWhere: true },
|
||||||
align: 'left',
|
component: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('globals.visible'),
|
label: t('globals.visible'),
|
||||||
name: 'stock',
|
name: 'stock',
|
||||||
attrs: { inWhere: true },
|
attrs: { inWhere: true },
|
||||||
align: 'left',
|
component: 'number',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -138,21 +139,12 @@ watchEffect(selectedRows);
|
||||||
<template>
|
<template>
|
||||||
<template v-if="stateStore.isHeaderMounted()">
|
<template v-if="stateStore.isHeaderMounted()">
|
||||||
<Teleport to="#st-data">
|
<Teleport to="#st-data">
|
||||||
<div class="q-pa-md q-mr-lg q-ma-xs" style="border: 2px solid #222">
|
|
||||||
<QCardSection horizontal>
|
|
||||||
<span class="text-weight-bold text-subtitle1 text-center full-width">
|
|
||||||
{{ t('shelvings.total') }}
|
|
||||||
</span>
|
|
||||||
</QCardSection>
|
|
||||||
<QCardSection class="column items-center" horizontal>
|
<QCardSection class="column items-center" horizontal>
|
||||||
<div>
|
<div>
|
||||||
<span class="details-label"
|
<span class="details-label">{{ t('shelvings.totalLabels') }} </span>
|
||||||
>{{ t('shelvings.totalLabels') }}
|
|
||||||
</span>
|
|
||||||
<span>: {{ totalLabels }}</span>
|
<span>: {{ totalLabels }}</span>
|
||||||
</div></QCardSection
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
</QCardSection>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
<Teleport to="#st-actions">
|
<Teleport to="#st-actions">
|
||||||
<QBtn
|
<QBtn
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { dashIfEmpty } from 'src/filters';
|
||||||
|
|
||||||
import CardSummary from 'components/ui/CardSummary.vue';
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
import VnLv from 'src/components/ui/VnLv.vue';
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
@ -48,7 +49,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
||||||
<ItemDescriptorMenu :entity-id="entityId" :warehouse-fk="warehouseFk" />
|
<ItemDescriptorMenu :entity-id="entityId" :warehouse-fk="warehouseFk" />
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ entity: { item, tags, visible, available, botanical } }">
|
<template #body="{ entity: { item, tags, visible, available, botanical } }">
|
||||||
<QCard class="vn-one photo">
|
<QCard class="vn-one photo" v-if="$route.name != 'ItemSummary'">
|
||||||
<ItemDescriptorImage
|
<ItemDescriptorImage
|
||||||
:entity-id="entityId"
|
:entity-id="entityId"
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
|
@ -56,14 +57,22 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
||||||
:show-edit-button="false"
|
:show-edit-button="false"
|
||||||
/>
|
/>
|
||||||
</QCard>
|
</QCard>
|
||||||
<QCard class="vn-one">
|
<QCard class="vn-two">
|
||||||
<VnTitle
|
<VnTitle
|
||||||
:url="getUrl(entityId, 'basic-data')"
|
:url="getUrl(entityId, 'basic-data')"
|
||||||
:text="t('globals.summary.basicData')"
|
:text="t('globals.summary.basicData')"
|
||||||
/>
|
/>
|
||||||
|
<div class="vn-card-group">
|
||||||
|
<div class="vn-card-content">
|
||||||
<VnLv :label="t('globals.name')" :value="item.name" />
|
<VnLv :label="t('globals.name')" :value="item.name" />
|
||||||
<VnLv :label="t('item.summary.completeName')" :value="item.longName" />
|
<VnLv
|
||||||
<VnLv :label="t('item.summary.family')" :value="item.itemType.name" />
|
:label="t('item.summary.completeName')"
|
||||||
|
:value="item.longName"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('item.summary.family')"
|
||||||
|
:value="item.itemType.name"
|
||||||
|
/>
|
||||||
<VnLv :label="t('globals.size')" :value="item.size" />
|
<VnLv :label="t('globals.size')" :value="item.size" />
|
||||||
<VnLv :label="t('globals.origin')" :value="item.origin.name" />
|
<VnLv :label="t('globals.origin')" :value="item.origin.name" />
|
||||||
<VnLv :label="t('item.summary.stems')" :value="item.stems" />
|
<VnLv :label="t('item.summary.stems')" :value="item.stems" />
|
||||||
|
@ -86,15 +95,24 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
||||||
:label="t('item.summary.doPhoto')"
|
:label="t('item.summary.doPhoto')"
|
||||||
v-model="item.isPhotoRequested"
|
v-model="item.isPhotoRequested"
|
||||||
:disable="true"
|
:disable="true"
|
||||||
|
size="xs"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VnLv>
|
</VnLv>
|
||||||
</QCard>
|
<VnLv :label="t('globals.description')">
|
||||||
<QCard class="vn-one">
|
<template #value>
|
||||||
<VnTitle
|
<span
|
||||||
:url="getUrl(entityId, 'basic-data')"
|
style="
|
||||||
:text="t('item.summary.otherData')"
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
"
|
||||||
|
v-text="dashIfEmpty(item.description)"
|
||||||
/>
|
/>
|
||||||
|
</template>
|
||||||
|
</VnLv>
|
||||||
|
</div>
|
||||||
|
<div class="vn-card-content">
|
||||||
<VnLv
|
<VnLv
|
||||||
:label="t('item.summary.intrastatCode')"
|
:label="t('item.summary.intrastatCode')"
|
||||||
:value="item.intrastat.id"
|
:value="item.intrastat.id"
|
||||||
|
@ -104,11 +122,23 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
||||||
:value="item.intrastat.description"
|
:value="item.intrastat.description"
|
||||||
/>
|
/>
|
||||||
<VnLv :label="t('item.summary.ref')" :value="item.comment" />
|
<VnLv :label="t('item.summary.ref')" :value="item.comment" />
|
||||||
<VnLv :label="t('item.summary.relevance')" :value="item.relevancy" />
|
<VnLv
|
||||||
<VnLv :label="t('item.summary.weight')" :value="item.weightByPiece" />
|
:label="t('item.summary.relevance')"
|
||||||
|
:value="item.relevancy"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('item.summary.weight')"
|
||||||
|
:value="item.weightByPiece"
|
||||||
|
/>
|
||||||
<VnLv :label="t('item.summary.units')" :value="item.packingOut" />
|
<VnLv :label="t('item.summary.units')" :value="item.packingOut" />
|
||||||
<VnLv :label="t('item.summary.expense')" :value="item.expense.name" />
|
<VnLv
|
||||||
<VnLv :label="t('item.summary.generic')" :value="item.genericFk" />
|
:label="t('item.summary.expense')"
|
||||||
|
:value="item.expense.name"
|
||||||
|
/>
|
||||||
|
<VnLv
|
||||||
|
:label="t('item.summary.generic')"
|
||||||
|
:value="item.genericFk"
|
||||||
|
/>
|
||||||
<VnLv
|
<VnLv
|
||||||
:label="t('item.summary.recycledPlastic')"
|
:label="t('item.summary.recycledPlastic')"
|
||||||
:value="item.recycledPlastic"
|
:value="item.recycledPlastic"
|
||||||
|
@ -117,23 +147,18 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
||||||
:label="t('item.summary.nonRecycledPlastic')"
|
:label="t('item.summary.nonRecycledPlastic')"
|
||||||
:value="item.nonRecycledPlastic"
|
:value="item.nonRecycledPlastic"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</QCard>
|
</QCard>
|
||||||
<QCard class="vn-one">
|
<QCard class="vn-one">
|
||||||
<VnTitle :url="getUrl(entityId, 'tags')" :text="t('globals.tags')" />
|
<VnTitle :url="getUrl(entityId, 'tags')" :text="t('globals.tags')" />
|
||||||
<VnLv
|
<VnLv
|
||||||
v-for="(tag, index) in tags"
|
v-for="(tag, index) in tags"
|
||||||
:key="index"
|
:key="index"
|
||||||
:label="`${tag.priority} ${tag.tag.name}:`"
|
:label="`${tag.priority} ${tag.tag.name}`"
|
||||||
:value="tag.value"
|
:value="tag.value"
|
||||||
/>
|
/>
|
||||||
</QCard>
|
</QCard>
|
||||||
<QCard class="vn-one" v-if="item.description">
|
|
||||||
<VnTitle
|
|
||||||
:url="getUrl(entityId, 'basic-data')"
|
|
||||||
:text="t('globals.description')"
|
|
||||||
/>
|
|
||||||
<p v-text="item.description" />
|
|
||||||
</QCard>
|
|
||||||
<QCard class="vn-one">
|
<QCard class="vn-one">
|
||||||
<VnTitle :url="getUrl(entityId, 'tax')" :text="t('item.summary.tax')" />
|
<VnTitle :url="getUrl(entityId, 'tax')" :text="t('item.summary.tax')" />
|
||||||
<VnLv
|
<VnLv
|
||||||
|
|
|
@ -17,7 +17,7 @@ const itemTagsRef = ref();
|
||||||
const tagOptions = ref([]);
|
const tagOptions = ref([]);
|
||||||
const valueOptionsMap = ref(new Map());
|
const valueOptionsMap = ref(new Map());
|
||||||
const getSelectedTagValues = async (tag) => {
|
const getSelectedTagValues = async (tag) => {
|
||||||
if (!tag.tagFk && tag.tag.isFree) return;
|
if (!tag.tagFk && tag.tag?.isFree) return;
|
||||||
const filter = {
|
const filter = {
|
||||||
fields: ['value'],
|
fields: ['value'],
|
||||||
order: 'value ASC',
|
order: 'value ASC',
|
||||||
|
@ -25,6 +25,7 @@ const getSelectedTagValues = async (tag) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const params = { filter: JSON.stringify(filter) };
|
const params = { filter: JSON.stringify(filter) };
|
||||||
|
if (!tag.tagFk) return;
|
||||||
const { data } = await axios.get(`Tags/${tag.tagFk}/filterValue`, {
|
const { data } = await axios.get(`Tags/${tag.tagFk}/filterValue`, {
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
|
@ -79,19 +80,12 @@ const insertTag = (rows) => {
|
||||||
itemFk: route.params.id,
|
itemFk: route.params.id,
|
||||||
tag: {
|
tag: {
|
||||||
isFree: true,
|
isFree: true,
|
||||||
value: undefined,
|
|
||||||
name: undefined,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
}"
|
}"
|
||||||
:data-default="{
|
:data-default="{
|
||||||
tag: {
|
tag: {
|
||||||
isFree: true,
|
isFree: true,
|
||||||
value: undefined,
|
|
||||||
name: undefined,
|
|
||||||
},
|
},
|
||||||
tagFk: undefined,
|
|
||||||
priority: undefined,
|
|
||||||
}"
|
}"
|
||||||
:default-remove="false"
|
:default-remove="false"
|
||||||
:user-filter="{
|
:user-filter="{
|
||||||
|
@ -113,12 +107,12 @@ const insertTag = (rows) => {
|
||||||
<VnRow
|
<VnRow
|
||||||
v-for="(row, index) in rows"
|
v-for="(row, index) in rows"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="items-center"
|
class="items-center q-py-sm"
|
||||||
>
|
>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('itemTags.tag')"
|
:label="t('itemTags.tag')"
|
||||||
:options="tagOptions"
|
:options="tagOptions"
|
||||||
:model-value="row.tag"
|
:model-value="row.tagFk"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
hide-selected
|
hide-selected
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
|
@ -139,9 +133,7 @@ const insertTag = (rows) => {
|
||||||
emit-value
|
emit-value
|
||||||
use-input
|
use-input
|
||||||
class="col"
|
class="col"
|
||||||
:is-clearable="false"
|
|
||||||
:required="false"
|
:required="false"
|
||||||
:rules="validate('itemTag.tagFk')"
|
|
||||||
:use-like="false"
|
:use-like="false"
|
||||||
sort-by="value"
|
sort-by="value"
|
||||||
/>
|
/>
|
||||||
|
@ -152,7 +144,6 @@ const insertTag = (rows) => {
|
||||||
v-model="row.value"
|
v-model="row.value"
|
||||||
:label="t('itemTags.value')"
|
:label="t('itemTags.value')"
|
||||||
:is-clearable="false"
|
:is-clearable="false"
|
||||||
@keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)"
|
|
||||||
:data-cy="`tag${row?.tag?.name}Value`"
|
:data-cy="`tag${row?.tag?.name}Value`"
|
||||||
/>
|
/>
|
||||||
<VnInput
|
<VnInput
|
||||||
|
@ -161,7 +152,6 @@ const insertTag = (rows) => {
|
||||||
v-model="row.priority"
|
v-model="row.priority"
|
||||||
:required="true"
|
:required="true"
|
||||||
:rules="validate('itemTag.priority')"
|
:rules="validate('itemTag.priority')"
|
||||||
@keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)"
|
|
||||||
/>
|
/>
|
||||||
<div class="row justify-center" style="flex: 0">
|
<div class="row justify-center" style="flex: 0">
|
||||||
<QIcon
|
<QIcon
|
||||||
|
@ -188,11 +178,8 @@ const insertTag = (rows) => {
|
||||||
v-shortcut="'+'"
|
v-shortcut="'+'"
|
||||||
fab
|
fab
|
||||||
data-cy="createNewTag"
|
data-cy="createNewTag"
|
||||||
>
|
:title="t('globals.add')"
|
||||||
<QTooltip>
|
/>
|
||||||
{{ t('itemTags.addTag') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QBtn>
|
|
||||||
</QPageSticky>
|
</QPageSticky>
|
||||||
</template>
|
</template>
|
||||||
</CrudModel>
|
</CrudModel>
|
||||||
|
|
|
@ -64,6 +64,7 @@ const submitTaxes = async (data) => {
|
||||||
auto-load
|
auto-load
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #body="{ rows }">
|
||||||
|
<div style="display: flex; justify-content: center">
|
||||||
<QCard class="q-px-lg q-py-md">
|
<QCard class="q-px-lg q-py-md">
|
||||||
<VnRow
|
<VnRow
|
||||||
v-for="(row, index) in rows"
|
v-for="(row, index) in rows"
|
||||||
|
@ -85,6 +86,7 @@ const submitTaxes = async (data) => {
|
||||||
/>
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
</QCard>
|
</QCard>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</CrudModel>
|
</CrudModel>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onBeforeMount } from 'vue';
|
import { ref, computed, onBeforeMount } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import VnImg from 'src/components/ui/VnImg.vue';
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
import VnTable from 'components/VnTable/VnTable.vue';
|
import VnTable from 'components/VnTable/VnTable.vue';
|
||||||
import { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
|
@ -18,16 +17,13 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import VnSection from 'src/components/common/VnSection.vue';
|
import VnSection from 'src/components/common/VnSection.vue';
|
||||||
|
|
||||||
const entityId = computed(() => route.params.id);
|
|
||||||
const { openCloneDialog } = cloneItem();
|
const { openCloneDialog } = cloneItem();
|
||||||
const { viewSummary } = useSummaryDialog();
|
const { viewSummary } = useSummaryDialog();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const route = useRoute();
|
|
||||||
const dataKey = 'ItemList';
|
const dataKey = 'ItemList';
|
||||||
const validPriorities = ref([]);
|
const validPriorities = ref([]);
|
||||||
const defaultTag = ref();
|
const defaultItem = ref(null);
|
||||||
const defaultPriority = ref();
|
|
||||||
|
|
||||||
const itemFilter = {
|
const itemFilter = {
|
||||||
include: [
|
include: [
|
||||||
|
@ -59,15 +55,14 @@ const itemFilter = {
|
||||||
};
|
};
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
label: '',
|
|
||||||
name: 'image',
|
name: 'image',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
columnFilter: false,
|
columnFilter: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'right',
|
||||||
label: t('item.list.id'),
|
label: t('item.list.id'),
|
||||||
name: 'id',
|
name: 'id',
|
||||||
align: 'left',
|
|
||||||
isId: true,
|
isId: true,
|
||||||
chip: {
|
chip: {
|
||||||
condition: () => true,
|
condition: () => true,
|
||||||
|
@ -75,36 +70,36 @@ const columns = computed(() => [
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'right',
|
||||||
label: t('entry.summary.grouping'),
|
label: t('entry.summary.grouping'),
|
||||||
name: 'grouping',
|
name: 'grouping',
|
||||||
align: 'left',
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'number',
|
component: 'number',
|
||||||
inWhere: true,
|
inWhere: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'right',
|
||||||
label: t('entry.summary.packing'),
|
label: t('entry.summary.packing'),
|
||||||
name: 'packing',
|
name: 'packing',
|
||||||
align: 'left',
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'number',
|
component: 'number',
|
||||||
inWhere: true,
|
inWhere: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'left',
|
||||||
label: t('globals.description'),
|
label: t('globals.description'),
|
||||||
name: 'description',
|
name: 'description',
|
||||||
align: 'left',
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
name: 'search',
|
name: 'search',
|
||||||
},
|
},
|
||||||
columnClass: 'expand',
|
columnClass: 'expand',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'right',
|
||||||
label: t('item.list.stems'),
|
label: t('item.list.stems'),
|
||||||
name: 'stems',
|
name: 'stems',
|
||||||
align: 'left',
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'number',
|
component: 'number',
|
||||||
inWhere: true,
|
inWhere: true,
|
||||||
|
@ -112,19 +107,20 @@ const columns = computed(() => [
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'right',
|
||||||
label: t('globals.size'),
|
label: t('globals.size'),
|
||||||
name: 'size',
|
name: 'size',
|
||||||
align: 'left',
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'number',
|
component: 'number',
|
||||||
inWhere: true,
|
inWhere: true,
|
||||||
},
|
},
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
|
columnClass: 'expand',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'left',
|
||||||
label: t('item.list.typeName'),
|
label: t('item.list.typeName'),
|
||||||
name: 'typeFk',
|
name: 'typeFk',
|
||||||
align: 'left',
|
|
||||||
component: 'select',
|
component: 'select',
|
||||||
attrs: {
|
attrs: {
|
||||||
url: 'ItemTypes',
|
url: 'ItemTypes',
|
||||||
|
@ -173,26 +169,17 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('globals.intrastat'),
|
label: t('globals.intrastat'),
|
||||||
name: 'intrastat',
|
name: 'intrastatFk',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
component: 'select',
|
component: 'select',
|
||||||
attrs: {
|
attrs: {
|
||||||
url: 'Intrastats',
|
url: 'Intrastats',
|
||||||
optionValue: 'description',
|
fields: ['id', 'description'],
|
||||||
optionLabel: 'description',
|
optionLabel: 'description',
|
||||||
},
|
optionValue: 'id',
|
||||||
columnFilter: {
|
|
||||||
name: 'intrastat',
|
|
||||||
attrs: {
|
|
||||||
url: 'Intrastats',
|
|
||||||
optionValue: 'description',
|
|
||||||
optionLabel: 'description',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
columnField: {
|
|
||||||
component: null,
|
|
||||||
},
|
},
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.intrastat),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.list.origin'),
|
label: t('item.list.origin'),
|
||||||
|
@ -238,21 +225,15 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.list.weight'),
|
label: t('item.list.weightByPiece'),
|
||||||
toolTip: t('item.list.weightByPiece'),
|
toolTip: t('item.list.weightByPiece'),
|
||||||
name: 'weightByPiece',
|
name: 'weightByPiece',
|
||||||
component: 'input',
|
component: 'input',
|
||||||
columnField: {
|
|
||||||
component: null,
|
|
||||||
},
|
|
||||||
columnFilter: {
|
|
||||||
inWhere: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'right',
|
||||||
label: t('item.list.stemMultiplier'),
|
label: t('item.list.stemMultiplier'),
|
||||||
name: 'stemMultiplier',
|
name: 'stemMultiplier',
|
||||||
align: 'left',
|
|
||||||
component: 'input',
|
component: 'input',
|
||||||
columnField: {
|
columnField: {
|
||||||
component: null,
|
component: null,
|
||||||
|
@ -301,7 +282,6 @@ const columns = computed(() => [
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
title: t('globals.clone'),
|
title: t('globals.clone'),
|
||||||
|
|
||||||
icon: 'vn:clone',
|
icon: 'vn:clone',
|
||||||
action: openCloneDialog,
|
action: openCloneDialog,
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
|
@ -317,15 +297,10 @@ const columns = computed(() => [
|
||||||
]);
|
]);
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
const { data } = await axios.get('ItemConfigs');
|
const { data } = await axios.get('ItemConfigs/findOne');
|
||||||
defaultTag.value = data[0].defaultTag;
|
defaultItem.value = data;
|
||||||
defaultPriority.value = data[0].defaultPriority;
|
|
||||||
data.forEach((priority) => {
|
|
||||||
validPriorities.value = priority.validPriorities;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VnSection
|
<VnSection
|
||||||
:data-key="dataKey"
|
:data-key="dataKey"
|
||||||
|
@ -338,27 +313,29 @@ onBeforeMount(async () => {
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #advanced-menu>
|
<template #advanced-menu>
|
||||||
<ItemListFilter data-key="ItemList" />
|
<ItemListFilter :data-key="dataKey" />
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<VnTable
|
<VnTable
|
||||||
v-if="defaultTag"
|
v-if="defaultItem"
|
||||||
ref="tableRef"
|
|
||||||
:data-key="dataKey"
|
:data-key="dataKey"
|
||||||
:columns="columns"
|
ref="tableRef"
|
||||||
:right-search="false"
|
search-url="ItemList"
|
||||||
redirect="Item"
|
url="Items/filter"
|
||||||
|
:filter="itemFilter"
|
||||||
:create="{
|
:create="{
|
||||||
urlCreate: 'Items/new',
|
urlCreate: 'Items/new',
|
||||||
title: t('item.list.newItem'),
|
title: t('item.list.newItem'),
|
||||||
onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`),
|
onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`),
|
||||||
formInitialData: {
|
formInitialData: {
|
||||||
editorFk: entityId,
|
tag: defaultItem?.defaultTag,
|
||||||
tag: defaultTag,
|
priority: defaultItem?.defaultPriority,
|
||||||
priority: defaultPriority,
|
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
:is-editable="false"
|
:columns="columns"
|
||||||
|
redirect="Item"
|
||||||
|
:right-search="false"
|
||||||
|
auto-load
|
||||||
>
|
>
|
||||||
<template #column-image="{ row }">
|
<template #column-image="{ row }">
|
||||||
<VnImg
|
<VnImg
|
||||||
|
@ -374,10 +351,18 @@ onBeforeMount(async () => {
|
||||||
<ItemDescriptorProxy :id="row.id" />
|
<ItemDescriptorProxy :id="row.id" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template #column-description="{ row }">
|
||||||
|
<span class="row column full-width justify-between items-start">
|
||||||
|
{{ row?.name }}
|
||||||
|
<span class="subName">
|
||||||
|
{{ row?.subName?.toUpperCase() }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<FetchedTags :item="row" :columns="3" />
|
||||||
|
</template>
|
||||||
<template #column-typeName="{ row }">
|
<template #column-typeName="{ row }">
|
||||||
<span class="link" @click.stop>
|
<span class="link" @click.stop>
|
||||||
{{ row.typeName }}
|
{{ row.typeName }}
|
||||||
{{ row.typeFk }}
|
|
||||||
<ItemTypeDescriptorProxy :id="row.typeFk" />
|
<ItemTypeDescriptorProxy :id="row.typeFk" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -387,20 +372,11 @@ onBeforeMount(async () => {
|
||||||
<WorkerDescriptorProxy :id="row.buyerFk" />
|
<WorkerDescriptorProxy :id="row.buyerFk" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #column-description="{ row }">
|
|
||||||
<div class="row column full-width justify-between items-start">
|
|
||||||
{{ row?.name }}
|
|
||||||
<div v-if="row?.subName" class="subName">
|
|
||||||
{{ row?.subName.toUpperCase() }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FetchedTags :item="row" :columns="3" />
|
|
||||||
</template>
|
|
||||||
<template #more-create-dialog="{ data }">
|
<template #more-create-dialog="{ data }">
|
||||||
<VnInput
|
<VnInput
|
||||||
v-model="data.provisionalName"
|
v-model="data.provisionalName"
|
||||||
:label="t('globals.description')"
|
:label="t('Provisional name')"
|
||||||
:is-required="true"
|
:required="true"
|
||||||
/>
|
/>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
url="Tags"
|
url="Tags"
|
||||||
|
@ -410,7 +386,7 @@ onBeforeMount(async () => {
|
||||||
option-label="name"
|
option-label="name"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
:is-required="true"
|
:is-required="true"
|
||||||
:sort-by="['name ASC']"
|
:order="['name ASC']"
|
||||||
>
|
>
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<QItem v-bind="scope.itemProps">
|
<QItem v-bind="scope.itemProps">
|
||||||
|
@ -427,7 +403,7 @@ onBeforeMount(async () => {
|
||||||
:options="validPriorities"
|
:options="validPriorities"
|
||||||
v-model="data.priority"
|
v-model="data.priority"
|
||||||
:label="t('item.create.priority')"
|
:label="t('item.create.priority')"
|
||||||
:is-required="true"
|
:required="true"
|
||||||
/>
|
/>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
url="ItemTypes"
|
url="ItemTypes"
|
||||||
|
@ -436,7 +412,7 @@ onBeforeMount(async () => {
|
||||||
:fields="['id', 'code', 'name']"
|
:fields="['id', 'code', 'name']"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
:is-required="true"
|
:required="true"
|
||||||
>
|
>
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<QItem v-bind="scope.itemProps">
|
<QItem v-bind="scope.itemProps">
|
||||||
|
@ -456,7 +432,7 @@ onBeforeMount(async () => {
|
||||||
:fields="['id', 'description']"
|
:fields="['id', 'description']"
|
||||||
option-label="description"
|
option-label="description"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
:is-required="true"
|
:required="true"
|
||||||
>
|
>
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<QItem v-bind="scope.itemProps">
|
<QItem v-bind="scope.itemProps">
|
||||||
|
@ -476,7 +452,7 @@ onBeforeMount(async () => {
|
||||||
:fields="['id', 'code', 'name']"
|
:fields="['id', 'code', 'name']"
|
||||||
option-label="code"
|
option-label="code"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
:is-required="true"
|
:required="true"
|
||||||
>
|
>
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<QItem v-bind="scope.itemProps">
|
<QItem v-bind="scope.itemProps">
|
||||||
|
@ -503,7 +479,5 @@ onBeforeMount(async () => {
|
||||||
</style>
|
</style>
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
New item: Nuevo artículo
|
Provisional name: Nombre provisional
|
||||||
Create Item: Crear artículo
|
|
||||||
You can search by id: Puedes buscar por id
|
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -8,7 +8,6 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
|
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
|
||||||
|
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import { useValidator } from 'src/composables/useValidator';
|
import { useValidator } from 'src/composables/useValidator';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
@ -85,26 +84,6 @@ const removeTag = (index, params, search) => {
|
||||||
applyTags(params, search);
|
applyTags(params, search);
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyFieldFilters = (params) => {
|
|
||||||
fieldFiltersValues.value.forEach((fieldFilter) => {
|
|
||||||
if (
|
|
||||||
fieldFilter.selectedField &&
|
|
||||||
(fieldFilter.value !== null ||
|
|
||||||
fieldFilter.value !== '' ||
|
|
||||||
fieldFilter.value !== undefined)
|
|
||||||
) {
|
|
||||||
params[fieldFilter.name] = fieldFilter.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
arrayData.applyFilter({ params });
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeFieldFilter = (index, params, search) => {
|
|
||||||
delete params[fieldFiltersValues.value[index].name];
|
|
||||||
(fieldFiltersValues.value || []).splice(index, 1);
|
|
||||||
applyFieldFilters(params, search);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
stateStore.rightDrawer = true;
|
stateStore.rightDrawer = true;
|
||||||
if (arrayData.store?.userParams?.categoryFk)
|
if (arrayData.store?.userParams?.categoryFk)
|
||||||
|
@ -125,7 +104,6 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fill fieldFiltersValues with existent userParams
|
|
||||||
if (arrayData.store?.userParams) {
|
if (arrayData.store?.userParams) {
|
||||||
fieldFiltersValues.value = Object.entries(arrayData.store?.userParams)
|
fieldFiltersValues.value = Object.entries(arrayData.store?.userParams)
|
||||||
.filter(([key, value]) => value && _moreFields.includes(key))
|
.filter(([key, value]) => value && _moreFields.includes(key))
|
||||||
|
@ -249,6 +227,16 @@ onMounted(async () => {
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('params.isFloramondo')"
|
||||||
|
v-model="params.isFloramondo"
|
||||||
|
toggle-indeterminate
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
<!-- Tags filter -->
|
<!-- Tags filter -->
|
||||||
<QItemLabel header>
|
<QItemLabel header>
|
||||||
{{ t('params.tags') }}
|
{{ t('params.tags') }}
|
||||||
|
@ -315,74 +303,6 @@ onMounted(async () => {
|
||||||
@click="removeTag(index, params, searchFn)"
|
@click="removeTag(index, params, searchFn)"
|
||||||
/>
|
/>
|
||||||
</QItem>
|
</QItem>
|
||||||
<!-- Filter fields -->
|
|
||||||
<QItemLabel header
|
|
||||||
>{{ t('More fields') }}
|
|
||||||
<QIcon
|
|
||||||
name="add_circle"
|
|
||||||
class="fill-icon-on-hover q-ml-md"
|
|
||||||
size="sm"
|
|
||||||
color="primary"
|
|
||||||
@click="fieldFiltersValues.push({})"
|
|
||||||
/></QItemLabel>
|
|
||||||
<QItem v-for="(fieldFilter, index) in fieldFiltersValues" :key="index">
|
|
||||||
<QItemSection class="col">
|
|
||||||
<VnSelect
|
|
||||||
class="full-width"
|
|
||||||
:label="t('params.tag')"
|
|
||||||
:model-value="fieldFilter.selectedField"
|
|
||||||
:options="moreFields"
|
|
||||||
option-label="label"
|
|
||||||
option-value="label"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
:emit-value="false"
|
|
||||||
use-input
|
|
||||||
:is-clearable="false"
|
|
||||||
@update:model-value="
|
|
||||||
($event) => {
|
|
||||||
fieldFilter.name = $event.name;
|
|
||||||
fieldFilter.value = null;
|
|
||||||
fieldFilter.selectedField = $event;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection class="col">
|
|
||||||
<VnCheckbox
|
|
||||||
v-if="fieldFilter.selectedField?.type === 'boolean'"
|
|
||||||
v-model="fieldFilter.value"
|
|
||||||
:label="t('params.value')"
|
|
||||||
@update:model-value="applyFieldFilters(params, searchFn)"
|
|
||||||
/>
|
|
||||||
<VnInput
|
|
||||||
v-else
|
|
||||||
v-model="fieldFilter.value"
|
|
||||||
:label="t('params.value')"
|
|
||||||
:disable="!fieldFilter.selectedField"
|
|
||||||
filled
|
|
||||||
@keydown.enter="applyFieldFilters(params, searchFn)"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection side
|
|
||||||
><QIcon
|
|
||||||
name="delete"
|
|
||||||
class="fill-icon-on-hover q-ml-xs"
|
|
||||||
size="sm"
|
|
||||||
color="primary"
|
|
||||||
@click="removeFieldFilter(index, params, searchFn)"
|
|
||||||
/></QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem>
|
|
||||||
<QItemSection>
|
|
||||||
<QCheckbox
|
|
||||||
:label="t('params.isFloramondo')"
|
|
||||||
v-model="params.isFloramondo"
|
|
||||||
toggle-indeterminate
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
</template>
|
||||||
</VnFilterPanel>
|
</VnFilterPanel>
|
||||||
</template>
|
</template>
|
||||||
|
@ -410,6 +330,17 @@ en:
|
||||||
Green: Green
|
Green: Green
|
||||||
Handmade: Handmade
|
Handmade: Handmade
|
||||||
Plant: Plant
|
Plant: Plant
|
||||||
|
packing: Packing
|
||||||
|
grouping: Grouping
|
||||||
|
stems: Stems
|
||||||
|
size: Size
|
||||||
|
intrastatFk: Intrastat
|
||||||
|
ori:
|
||||||
|
id: Origin
|
||||||
|
workerFk: Buyer
|
||||||
|
weightByPiece: Weight/stem
|
||||||
|
stemMultiplier: Stem multiplier
|
||||||
|
landed: Landed date
|
||||||
es:
|
es:
|
||||||
More fields: Más campos
|
More fields: Más campos
|
||||||
params:
|
params:
|
||||||
|
@ -433,4 +364,15 @@ es:
|
||||||
Green: Verde
|
Green: Verde
|
||||||
Handmade: Hecho a mano
|
Handmade: Hecho a mano
|
||||||
Plant: Planta
|
Plant: Planta
|
||||||
|
packing: Packing
|
||||||
|
grouping: Grouping
|
||||||
|
stems: Tallos
|
||||||
|
size: Altura
|
||||||
|
intrastatFk: Intrastat
|
||||||
|
ori:
|
||||||
|
id: Origen
|
||||||
|
workerFk: Comprador
|
||||||
|
weightByPiece: Peso/tallo
|
||||||
|
stemMultiplier: Multiplicador de tallos
|
||||||
|
landed: Fecha de entrega
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -1,30 +1,27 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
|
|
||||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
|
||||||
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { toCurrency } from 'filters/index';
|
import { toCurrency } from 'filters/index';
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import { toDate, dashIfEmpty } from 'src/filters';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import ItemRequestDenyForm from './ItemRequestDenyForm.vue';
|
|
||||||
import { toDate } from 'src/filters';
|
|
||||||
import VnTable from 'components/VnTable/VnTable.vue';
|
import VnTable from 'components/VnTable/VnTable.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||||
|
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
|
||||||
|
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||||
import ItemRequestFilter from './ItemRequestFilter.vue';
|
import ItemRequestFilter from './ItemRequestFilter.vue';
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||||
|
import FormModelPopup from 'src/components/FormModelPopup.vue';
|
||||||
|
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const denyFormRef = ref(null);
|
const denyFormRef = ref(null);
|
||||||
const denyRequestId = ref(null);
|
const denyRequestId = ref(null);
|
||||||
const denyRequestIndex = ref(null);
|
|
||||||
const itemRequestsOptions = ref([]);
|
|
||||||
const userParams = {
|
const userParams = {
|
||||||
state: 'pending',
|
|
||||||
daysOnward: 7,
|
daysOnward: 7,
|
||||||
};
|
};
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
|
@ -34,9 +31,13 @@ onMounted(async () => {
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
|
name: 'id',
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
label: t('globals.ticketId'),
|
label: t('globals.ticketId'),
|
||||||
name: 'ticketFk',
|
name: 'ticketFk',
|
||||||
align: 'left',
|
|
||||||
isId: true,
|
isId: true,
|
||||||
chip: {
|
chip: {
|
||||||
condition: () => true,
|
condition: () => true,
|
||||||
|
@ -44,15 +45,16 @@ const columns = computed(() => [
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
align: 'center',
|
||||||
label: t('globals.shipped'),
|
label: t('globals.shipped'),
|
||||||
name: 'shipped',
|
name: 'shipped',
|
||||||
align: 'left',
|
|
||||||
component: 'date',
|
component: 'date',
|
||||||
columnField: {
|
columnField: {
|
||||||
component: null,
|
component: null,
|
||||||
},
|
},
|
||||||
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)),
|
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)),
|
||||||
columnClass: 'shrink',
|
columnClass: 'shrink',
|
||||||
|
isEditable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('globals.description'),
|
label: t('globals.description'),
|
||||||
|
@ -78,6 +80,7 @@ const columns = computed(() => [
|
||||||
component: null,
|
component: null,
|
||||||
},
|
},
|
||||||
columnClass: 'shrink',
|
columnClass: 'shrink',
|
||||||
|
isEditable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
@ -91,6 +94,7 @@ const columns = computed(() => [
|
||||||
component: null,
|
component: null,
|
||||||
},
|
},
|
||||||
format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
|
format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
|
||||||
|
isEditable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.buyRequest.requested'),
|
label: t('item.buyRequest.requested'),
|
||||||
|
@ -102,7 +106,6 @@ const columns = computed(() => [
|
||||||
name: 'price',
|
name: 'price',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
format: (row) => toCurrency(row.price),
|
format: (row) => toCurrency(row.price),
|
||||||
columnClass: 'shrink',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.buyRequest.attender'),
|
label: t('item.buyRequest.attender'),
|
||||||
|
@ -120,22 +123,54 @@ const columns = computed(() => [
|
||||||
columnField: {
|
columnField: {
|
||||||
component: null,
|
component: null,
|
||||||
},
|
},
|
||||||
columnClass: 'shrink',
|
isEditable: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: t('globals.item'),
|
label: t('globals.item'),
|
||||||
name: 'item',
|
name: 'itemFk',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
component: 'input',
|
component: 'input',
|
||||||
columnClass: 'expand',
|
isEditable: ({ isOk }) => isOk === null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.buyRequest.achieved'),
|
label: t('item.buyRequest.achieved'),
|
||||||
name: 'achieved',
|
name: 'saleQuantity',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
component: 'input',
|
component: 'input',
|
||||||
columnClass: 'shrink',
|
columnClass: 'shrink',
|
||||||
|
isEditable: ({ itemFk, isOk }) => {
|
||||||
|
if (itemFk && isOk === null) return true;
|
||||||
|
},
|
||||||
|
beforeDestroy: (row) => {
|
||||||
|
if (!row.saleQuantity) {
|
||||||
|
return tableRef.value.reload();
|
||||||
|
}
|
||||||
|
axios
|
||||||
|
.post(`TicketRequests/${row.id}/confirm`, {
|
||||||
|
id: row.id,
|
||||||
|
itemFk: parseInt(row.itemFk),
|
||||||
|
quantity: parseInt(row.saleQuantity),
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return axios.get(`Items/findOne`, {
|
||||||
|
params: { where: { id: row.itemFk } },
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
row.itemDescription = response.data.name;
|
||||||
|
row.state = 1;
|
||||||
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
|
return tableRef.value.reload();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
notify(
|
||||||
|
error?.response?.data?.error?.message || error.message,
|
||||||
|
'negative',
|
||||||
|
);
|
||||||
|
return tableRef.value.reload();
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.buyRequest.concept'),
|
label: t('item.buyRequest.concept'),
|
||||||
|
@ -143,12 +178,12 @@ const columns = computed(() => [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
component: 'input',
|
component: 'input',
|
||||||
columnClass: 'expand',
|
isEditable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('globals.state'),
|
label: t('globals.state'),
|
||||||
name: 'state',
|
name: 'state',
|
||||||
format: (row) => getState(row.isOk),
|
format: ({ isOk }) => getState(isOk),
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -163,7 +198,23 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
align: 'right',
|
align: 'right',
|
||||||
label: '',
|
label: '',
|
||||||
name: 'denyOptions',
|
name: 'tableActions',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: (row) => row.response,
|
||||||
|
icon: 'insert_drive_file',
|
||||||
|
isPrimary: true,
|
||||||
|
show: (row) => row?.response?.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Discard'),
|
||||||
|
icon: 'thumb_down',
|
||||||
|
fill: true,
|
||||||
|
isPrimary: true,
|
||||||
|
show: ({ isOk }) => isOk === null,
|
||||||
|
action: (row) => showDenyRequestForm(row.id),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -181,54 +232,17 @@ const getBadgeColor = (date) => {
|
||||||
if (difference > 0) return 'alert';
|
if (difference > 0) return 'alert';
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeQuantity = async (request) => {
|
|
||||||
if (request.saleFk) {
|
|
||||||
const params = {
|
|
||||||
quantity: request.saleQuantity,
|
|
||||||
};
|
|
||||||
|
|
||||||
await axios.patch(`Sales/${request.saleFk}`, params);
|
|
||||||
}
|
|
||||||
await confirmRequest(request);
|
|
||||||
notify(t('globals.dataSaved'), 'positive');
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmRequest = async (request) => {
|
|
||||||
if (!request.itemFk || !request.saleQuantity) return;
|
|
||||||
const params = {
|
|
||||||
itemFk: request.itemFk,
|
|
||||||
quantity: request.saleQuantity,
|
|
||||||
attenderFk: request.attenderFk,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await axios.post(`TicketRequests/${request.id}/confirm`, params);
|
|
||||||
request.itemDescription = data.concept;
|
|
||||||
request.isOk = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getState = (isOk) => {
|
const getState = (isOk) => {
|
||||||
if (isOk === null) return t('Pending');
|
if (isOk === null) return t('Pending');
|
||||||
else if (isOk) return t('Accepted');
|
else if (isOk) return t('Accepted');
|
||||||
else return t('Denied');
|
else return t('Denied');
|
||||||
};
|
};
|
||||||
|
|
||||||
const showDenyRequestForm = (requestId, rowIndex) => {
|
const showDenyRequestForm = (requestId) => {
|
||||||
denyRequestId.value = requestId;
|
denyRequestId.value = requestId;
|
||||||
denyRequestIndex.value = rowIndex;
|
|
||||||
denyFormRef.value.show();
|
denyFormRef.value.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDenyAccept = (_, responseData) => {
|
|
||||||
itemRequestsOptions.value[denyRequestIndex.value].isOk = responseData.isOk;
|
|
||||||
itemRequestsOptions.value[denyRequestIndex.value].attenderFk =
|
|
||||||
responseData.attenderFk;
|
|
||||||
itemRequestsOptions.value[denyRequestIndex.value].response = responseData.response;
|
|
||||||
denyRequestId.value = null;
|
|
||||||
denyRequestIndex.value = null;
|
|
||||||
tableRef.value.reload();
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RightMenu>
|
<RightMenu>
|
||||||
<template #right-panel>
|
<template #right-panel>
|
||||||
|
@ -240,12 +254,13 @@ const onDenyAccept = (_, responseData) => {
|
||||||
data-key="itemRequest"
|
data-key="itemRequest"
|
||||||
url="ticketRequests/filter"
|
url="ticketRequests/filter"
|
||||||
order="shipped ASC, isOk ASC"
|
order="shipped ASC, isOk ASC"
|
||||||
:columns="columns"
|
|
||||||
:user-params="userParams"
|
:user-params="userParams"
|
||||||
:right-search="false"
|
:is-editable="true"
|
||||||
auto-load
|
:columns="columns"
|
||||||
:disable-option="{ card: true }"
|
:disable-option="{ card: true }"
|
||||||
chip-locale="item.params"
|
:right-search="false"
|
||||||
|
:default-remove="false"
|
||||||
|
auto-load
|
||||||
>
|
>
|
||||||
<template #column-ticketFk="{ row }">
|
<template #column-ticketFk="{ row }">
|
||||||
<span class="link">
|
<span class="link">
|
||||||
|
@ -254,16 +269,14 @@ const onDenyAccept = (_, responseData) => {
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #column-shipped="{ row }">
|
<template #column-shipped="{ row }">
|
||||||
<QTd>
|
|
||||||
<QBadge
|
<QBadge
|
||||||
:color="getBadgeColor(row.shipped)"
|
:color="getBadgeColor(row.shipped)"
|
||||||
text-color="black"
|
text-color="black"
|
||||||
class="q-pa-sm"
|
class="q-pa-xs"
|
||||||
style="font-size: 14px"
|
style="font-size: 14px"
|
||||||
>
|
>
|
||||||
{{ toDate(row.shipped) }}
|
{{ toDate(row.shipped) }}
|
||||||
</QBadge>
|
</QBadge>
|
||||||
</QTd>
|
|
||||||
</template>
|
</template>
|
||||||
<template #column-attenderName="{ row }">
|
<template #column-attenderName="{ row }">
|
||||||
<span class="link" @click.stop>
|
<span class="link" @click.stop>
|
||||||
|
@ -284,74 +297,34 @@ const onDenyAccept = (_, responseData) => {
|
||||||
<DepartmentDescriptorProxy :id="row.departmentFk" />
|
<DepartmentDescriptorProxy :id="row.departmentFk" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #column-item="{ row }">
|
|
||||||
<span>
|
|
||||||
<VnInput v-model.number="row.itemFk" dense />
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #column-achieved="{ row }">
|
|
||||||
<span>
|
|
||||||
<VnInput
|
|
||||||
ref="achievedRef"
|
|
||||||
type="number"
|
|
||||||
v-model.number="row.saleQuantity"
|
|
||||||
:disable="!row.itemFk || row.isOk != null"
|
|
||||||
@blur="changeQuantity(row)"
|
|
||||||
@keyup.enter="$refs.achievedRef.vnInputRef.blur()"
|
|
||||||
dense
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #column-concept="{ row }">
|
<template #column-concept="{ row }">
|
||||||
<span @click.stop disabled="row.isOk != null">
|
<span :class="{ link: row.itemDescription }" @click.stop>
|
||||||
{{ row.itemDescription }}
|
{{ dashIfEmpty(row.itemDescription) }}
|
||||||
|
<ItemDescriptorProxy v-if="row.itemFk" :id="row.itemFk" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #moreFilterPanel="{ params }">
|
|
||||||
<VnInputNumber
|
|
||||||
:label="t('params.scopeDays')"
|
|
||||||
v-model.number="params.daysOnward"
|
|
||||||
@keyup.enter="(evt) => handleScopeDays(evt.target.value)"
|
|
||||||
@remove="handleScopeDays()"
|
|
||||||
class="q-px-xs q-pr-lg"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
lazy-rules
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #column-denyOptions="{ row, rowIndex }">
|
|
||||||
<QIcon
|
|
||||||
v-if="row.response?.length"
|
|
||||||
name="insert_drive_file"
|
|
||||||
color="primary"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<QTooltip>
|
|
||||||
{{ row.response }}
|
|
||||||
</QTooltip>
|
|
||||||
</QIcon>
|
|
||||||
<QIcon
|
|
||||||
v-if="row.isOk == null"
|
|
||||||
name="thumb_down"
|
|
||||||
color="primary"
|
|
||||||
size="sm"
|
|
||||||
class="fill-icon"
|
|
||||||
@click="showDenyRequestForm(row.id, rowIndex)"
|
|
||||||
>
|
|
||||||
<QTooltip>
|
|
||||||
{{ t('Discard') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QIcon>
|
|
||||||
</template>
|
|
||||||
</VnTable>
|
</VnTable>
|
||||||
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
|
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
|
||||||
<ItemRequestDenyForm :request-id="denyRequestId" @on-data-saved="onDenyAccept" />
|
<FormModelPopup
|
||||||
|
:url-create="`TicketRequests/${denyRequestId}/deny`"
|
||||||
|
:title="t('Specify the reasons to deny this request')"
|
||||||
|
:form-initial-data="{ id: denyRequestId }"
|
||||||
|
@on-data-saved="tableRef.reload()"
|
||||||
|
>
|
||||||
|
<template #form-inputs="{ data }">
|
||||||
|
<VnInput
|
||||||
|
ref="textAreaRef"
|
||||||
|
type="textarea"
|
||||||
|
v-model="data.observation"
|
||||||
|
fill-input
|
||||||
|
:required="true"
|
||||||
|
auto-grow
|
||||||
|
data-cy="discardTextArea"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</FormModelPopup>
|
||||||
</QDialog>
|
</QDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
Discard: Descartar
|
Discard: Descartar
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
<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 'src/components/FormModelPopup.vue';
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
requestId: {
|
|
||||||
type: Number,
|
|
||||||
default: null,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['onDataSaved']);
|
|
||||||
const { t } = useI18n();
|
|
||||||
const textAreaRef = ref(null);
|
|
||||||
const bankEntityFormData = reactive({});
|
|
||||||
|
|
||||||
const onDataSaved = (formData, requestResponse) => {
|
|
||||||
emit('onDataSaved', formData, requestResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick();
|
|
||||||
textAreaRef.value.focus();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<FormModelPopup
|
|
||||||
:url-create="`TicketRequests/${$props.requestId}/deny`"
|
|
||||||
:title="t('Specify the reasons to deny this request')"
|
|
||||||
:form-initial-data="bankEntityFormData"
|
|
||||||
@on-data-saved="onDataSaved"
|
|
||||||
>
|
|
||||||
<template #form-inputs="{ data }">
|
|
||||||
<VnRow>
|
|
||||||
<div class="col">
|
|
||||||
<VnInput
|
|
||||||
ref="textAreaRef"
|
|
||||||
type="textarea"
|
|
||||||
v-model="data.observation"
|
|
||||||
fill-input
|
|
||||||
:required="true"
|
|
||||||
autogrow
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</VnRow>
|
|
||||||
</template>
|
|
||||||
</FormModelPopup>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n>
|
|
||||||
es:
|
|
||||||
Specify the reasons to deny this request: Especifica las razones para descartar la petición
|
|
||||||
</i18n>
|
|
|
@ -189,7 +189,7 @@ onMounted(async () => {
|
||||||
<QCheckbox
|
<QCheckbox
|
||||||
:label="t('params.mine')"
|
:label="t('params.mine')"
|
||||||
v-model="params.mine"
|
v-model="params.mine"
|
||||||
:toggle-indeterminate="false"
|
:toggle-indeterminate="null"
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
|
@ -212,6 +212,13 @@ en:
|
||||||
state: State
|
state: State
|
||||||
daysOnward: Days onward
|
daysOnward: Days onward
|
||||||
myTeam: My team
|
myTeam: My team
|
||||||
|
shipped: Shipped
|
||||||
|
description: Description
|
||||||
|
departmentFk: Department
|
||||||
|
quantity: Quantity
|
||||||
|
price: Price
|
||||||
|
item: Item
|
||||||
|
concept: Concept
|
||||||
dateFiltersTooltip: Cannot choose a range of dates and days onward at the same time
|
dateFiltersTooltip: Cannot choose a range of dates and days onward at the same time
|
||||||
denied: Denied
|
denied: Denied
|
||||||
accepted: Accepted
|
accepted: Accepted
|
||||||
|
@ -230,6 +237,13 @@ es:
|
||||||
state: Estado
|
state: Estado
|
||||||
daysOnward: Días en adelante
|
daysOnward: Días en adelante
|
||||||
myTeam: Mi equipo
|
myTeam: Mi equipo
|
||||||
|
shipped: Enviado
|
||||||
|
description: Descripción
|
||||||
|
departmentFk: Departamento
|
||||||
|
quantity: Cantidad
|
||||||
|
price: Precio
|
||||||
|
item: Artículo
|
||||||
|
concept: Concepto
|
||||||
dateFiltersTooltip: No se puede seleccionar un rango de fechas y días en adelante a la vez
|
dateFiltersTooltip: No se puede seleccionar un rango de fechas y días en adelante a la vez
|
||||||
denied: Denegada
|
denied: Denegada
|
||||||
accepted: Aceptada
|
accepted: Aceptada
|
||||||
|
|
|
@ -42,11 +42,11 @@ const itemPackingTypesOptions = ref([]);
|
||||||
/>
|
/>
|
||||||
<FormModel :url-update="`ItemTypes/${route.params.id}`" model="ItemType" auto-load>
|
<FormModel :url-update="`ItemTypes/${route.params.id}`" model="ItemType" auto-load>
|
||||||
<template #form="{ data }">
|
<template #form="{ data }">
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
<VnInput v-model="data.code" :label="t('itemType.shared.code')" />
|
<VnInput v-model="data.code" :label="t('itemType.shared.code')" />
|
||||||
<VnInput v-model="data.name" :label="t('itemType.shared.name')" />
|
<VnInput v-model="data.name" :label="t('itemType.shared.name')" />
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
url="Workers/search"
|
url="Workers/search"
|
||||||
v-model="data.workerFk"
|
v-model="data.workerFk"
|
||||||
|
@ -58,11 +58,7 @@ const itemPackingTypesOptions = ref([]);
|
||||||
hide-selected
|
hide-selected
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VnAvatar
|
<VnAvatar :worker-id="data.workerFk" color="primary" />
|
||||||
:worker-id="data.workerFk"
|
|
||||||
color="primary"
|
|
||||||
:title="title"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<QItem v-bind="scope.itemProps">
|
<QItem v-bind="scope.itemProps">
|
||||||
|
@ -85,7 +81,7 @@ const itemPackingTypesOptions = ref([]);
|
||||||
hide-selected
|
hide-selected
|
||||||
/>
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
v-model="data.temperatureFk"
|
v-model="data.temperatureFk"
|
||||||
:label="t('itemType.shared.temperature')"
|
:label="t('itemType.shared.temperature')"
|
||||||
|
@ -96,7 +92,7 @@ const itemPackingTypesOptions = ref([]);
|
||||||
/>
|
/>
|
||||||
<VnInput v-model="data.life" :label="t('itemType.summary.life')" />
|
<VnInput v-model="data.life" :label="t('itemType.summary.life')" />
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
v-model="data.itemPackingTypeFk"
|
v-model="data.itemPackingTypeFk"
|
||||||
:label="t('itemType.shared.itemPackingType')"
|
:label="t('itemType.shared.itemPackingType')"
|
||||||
|
@ -107,7 +103,7 @@ const itemPackingTypesOptions = ref([]);
|
||||||
/>
|
/>
|
||||||
<VnInput v-model="data.maxRefs" :label="t('itemType.shared.maxRefs')" />
|
<VnInput v-model="data.maxRefs" :label="t('itemType.shared.maxRefs')" />
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow class="q-py-sm">
|
||||||
<QCheckbox
|
<QCheckbox
|
||||||
v-model="data.isFragile"
|
v-model="data.isFragile"
|
||||||
:label="t('itemType.shared.fragile')"
|
:label="t('itemType.shared.fragile')"
|
||||||
|
|
|
@ -8,7 +8,8 @@ import filter from './ItemTypeFilter.js';
|
||||||
<VnCard
|
<VnCard
|
||||||
data-key="ItemType"
|
data-key="ItemType"
|
||||||
url="ItemTypes"
|
url="ItemTypes"
|
||||||
:filter="filter"
|
:filter
|
||||||
|
:id-in-where="true"
|
||||||
:descriptor="ItemTypeDescriptor"
|
:descriptor="ItemTypeDescriptor"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -30,7 +30,6 @@ const entityId = computed(() => {
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
title="code"
|
title="code"
|
||||||
data-key="ItemType"
|
data-key="ItemType"
|
||||||
:to-module="{ name: 'ItemTypeList' }"
|
|
||||||
>
|
>
|
||||||
<template #body="{ entity }">
|
<template #body="{ entity }">
|
||||||
<VnLv :label="$t('itemType.shared.code')" :value="entity.code" />
|
<VnLv :label="$t('itemType.shared.code')" :value="entity.code" />
|
||||||
|
|
|
@ -17,3 +17,10 @@ itemType:
|
||||||
isUnconventionalSize: Is unconventional size
|
isUnconventionalSize: Is unconventional size
|
||||||
search: Search item type
|
search: Search item type
|
||||||
searchInfo: Search item type by id, name or code
|
searchInfo: Search item type by id, name or code
|
||||||
|
params:
|
||||||
|
id: Id
|
||||||
|
code: Code
|
||||||
|
name: Name
|
||||||
|
categoryFk: Category
|
||||||
|
workerFk: Comprador
|
||||||
|
temperatureFk: Temperature
|
||||||
|
|
|
@ -17,3 +17,10 @@ itemType:
|
||||||
isUnconventionalSize: Es de tamaño poco convencional
|
isUnconventionalSize: Es de tamaño poco convencional
|
||||||
search: Buscar familia
|
search: Buscar familia
|
||||||
searchInfo: Buscar familia por id, nombre o código
|
searchInfo: Buscar familia por id, nombre o código
|
||||||
|
params:
|
||||||
|
id: Id
|
||||||
|
code: Código
|
||||||
|
name: Nombre
|
||||||
|
categoryFk: Reino
|
||||||
|
workerFk: Comprador
|
||||||
|
temperatureFk: Temperatura
|
||||||
|
|
|
@ -25,6 +25,10 @@ const exprBuilder = (param, value) => {
|
||||||
return {
|
return {
|
||||||
code: { like: `%${value}%` },
|
code: { like: `%${value}%` },
|
||||||
};
|
};
|
||||||
|
case 'temperatureFk':
|
||||||
|
return {
|
||||||
|
temperatureFk: value,
|
||||||
|
};
|
||||||
case 'search':
|
case 'search':
|
||||||
if (value) {
|
if (value) {
|
||||||
if (!isNaN(value)) {
|
if (!isNaN(value)) {
|
||||||
|
@ -51,16 +55,19 @@ const exprBuilder = (param, value) => {
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'right',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
label: t('id'),
|
label: 'Id',
|
||||||
isId: true,
|
isId: true,
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
name: 'code',
|
name: 'code',
|
||||||
label: t('code'),
|
label: t('itemType.shared.code'),
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
},
|
},
|
||||||
|
@ -71,8 +78,7 @@ const columns = computed(() => [
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'left',
|
label: t('itemType.shared.worker'),
|
||||||
label: t('worker'),
|
|
||||||
name: 'workerFk',
|
name: 'workerFk',
|
||||||
component: 'select',
|
component: 'select',
|
||||||
attrs: {
|
attrs: {
|
||||||
|
@ -80,7 +86,6 @@ const columns = computed(() => [
|
||||||
optionLabel: 'nickname',
|
optionLabel: 'nickname',
|
||||||
optionValue: 'id',
|
optionValue: 'id',
|
||||||
},
|
},
|
||||||
format: (row) => row.worker?.user?.name,
|
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
columnField: { component: null },
|
columnField: { component: null },
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
|
@ -95,6 +100,7 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
inWhere: true,
|
inWhere: true,
|
||||||
},
|
},
|
||||||
|
format: (row) => row.worker?.user?.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
@ -104,19 +110,24 @@ const columns = computed(() => [
|
||||||
attrs: {
|
attrs: {
|
||||||
options: itemCategoriesOptions.value,
|
options: itemCategoriesOptions.value,
|
||||||
},
|
},
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
cardVisible: false,
|
cardVisible: false,
|
||||||
visible: false,
|
format: (row, dashIfEmpty) => dashIfEmpty(row.category?.name),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
name: 'Temperature',
|
name: 'temperatureFk',
|
||||||
label: t('Temperature'),
|
label: t('Temperature'),
|
||||||
component: 'select',
|
component: 'select',
|
||||||
attrs: {
|
attrs: {
|
||||||
options: temperatureOptions.value,
|
options: temperatureOptions.value,
|
||||||
},
|
},
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
cardVisible: false,
|
cardVisible: false,
|
||||||
visible: false,
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
@ -141,9 +152,10 @@ const columns = computed(() => [
|
||||||
:array-data-props="{
|
:array-data-props="{
|
||||||
url: 'ItemTypes',
|
url: 'ItemTypes',
|
||||||
order: 'name ASC',
|
order: 'name ASC',
|
||||||
exprBuilder,
|
exprBuilder: exprBuilder,
|
||||||
userFilter: {
|
userFilter: {
|
||||||
include: {
|
include: [
|
||||||
|
{
|
||||||
relation: 'worker',
|
relation: 'worker',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['id'],
|
fields: ['id'],
|
||||||
|
@ -155,6 +167,13 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
relation: 'category',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
@ -169,7 +188,7 @@ const columns = computed(() => [
|
||||||
formInitialData: {},
|
formInitialData: {},
|
||||||
}"
|
}"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
auto-load
|
:right-search="false"
|
||||||
redirect="item/item-type"
|
redirect="item/item-type"
|
||||||
>
|
>
|
||||||
<template #column-workerFk="{ row }">
|
<template #column-workerFk="{ row }">
|
||||||
|
@ -208,6 +227,7 @@ const columns = computed(() => [
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
|
params:
|
||||||
id: Id
|
id: Id
|
||||||
code: Código
|
code: Código
|
||||||
worker: Trabajador
|
worker: Trabajador
|
||||||
|
@ -215,6 +235,7 @@ const columns = computed(() => [
|
||||||
Temperature: Temperatura
|
Temperature: Temperatura
|
||||||
Create ItemTypes: Crear familia
|
Create ItemTypes: Crear familia
|
||||||
en:
|
en:
|
||||||
|
params:
|
||||||
code: Code
|
code: Code
|
||||||
worker: Worker
|
worker: Worker
|
||||||
ItemCategory: ItemCategory
|
ItemCategory: ItemCategory
|
||||||
|
|
|
@ -74,12 +74,13 @@ const closeForm = () => {
|
||||||
class="editOption"
|
class="editOption"
|
||||||
:label="t('Field to edit')"
|
:label="t('Field to edit')"
|
||||||
:options="fieldsOptions"
|
:options="fieldsOptions"
|
||||||
hide-selected
|
|
||||||
option-label="label"
|
option-label="label"
|
||||||
|
option-value="name"
|
||||||
v-model="selectedField"
|
v-model="selectedField"
|
||||||
data-cy="EditFixedPriceSelectOption"
|
data-cy="EditFixedPriceSelectOption"
|
||||||
@update:model-value="newValue = null"
|
@update:model-value="newValue = null"
|
||||||
:class="{ 'is-select': selectedField?.component === 'select' }"
|
:class="{ 'is-select': selectedField?.component === 'select' }"
|
||||||
|
:emit-value="false"
|
||||||
>
|
>
|
||||||
<template #option="{ opt, itemProps }">
|
<template #option="{ opt, itemProps }">
|
||||||
<QItem v-bind="itemProps" class="q-pa-xs row items-center">
|
<QItem v-bind="itemProps" class="q-pa-xs row items-center">
|
||||||
|
|
|
@ -8,10 +8,11 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
|
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import { useState } from 'src/composables/useState';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
const MATCH = 'match';
|
const MATCH = 'match';
|
||||||
const { notifyResults } = displayResults();
|
const { notifyResults } = displayResults();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
|
@ -42,7 +43,7 @@ const ticketConfig = ref({});
|
||||||
const proposalTableRef = ref(null);
|
const proposalTableRef = ref(null);
|
||||||
|
|
||||||
const sale = computed(() => $props.sales[0]);
|
const sale = computed(() => $props.sales[0]);
|
||||||
const saleFk = computed(() => sale.value.saleFk);
|
const saleFk = computed(() => sale.value?.saleFk);
|
||||||
const filter = computed(() => ({
|
const filter = computed(() => ({
|
||||||
where: $props.filter,
|
where: $props.filter,
|
||||||
|
|
||||||
|
@ -55,10 +56,6 @@ const defaultColumnAttrs = {
|
||||||
sortable: false,
|
sortable: false,
|
||||||
};
|
};
|
||||||
const emit = defineEmits(['onDialogClosed', 'itemReplaced']);
|
const emit = defineEmits(['onDialogClosed', 'itemReplaced']);
|
||||||
|
|
||||||
const conditionalValuePrice = (price) =>
|
|
||||||
price > 1 + ticketConfig.value.lackAlertPrice / 100 ? 'match' : 'not-match';
|
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
...defaultColumnAttrs,
|
...defaultColumnAttrs,
|
||||||
|
@ -97,7 +94,15 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
label: t('item.list.color'),
|
label: t('item.list.producer'),
|
||||||
|
name: 'subName',
|
||||||
|
field: 'subName',
|
||||||
|
columnClass: 'expand',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
label: t('proposal.tag5'),
|
||||||
name: 'tag5',
|
name: 'tag5',
|
||||||
field: 'value5',
|
field: 'value5',
|
||||||
columnClass: 'expand',
|
columnClass: 'expand',
|
||||||
|
@ -105,7 +110,7 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
label: t('item.list.stems'),
|
label: t('proposal.tag6'),
|
||||||
name: 'tag6',
|
name: 'tag6',
|
||||||
field: 'value6',
|
field: 'value6',
|
||||||
columnClass: 'expand',
|
columnClass: 'expand',
|
||||||
|
@ -113,12 +118,27 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
label: t('item.list.producer'),
|
label: t('proposal.tag7'),
|
||||||
name: 'tag7',
|
name: 'tag7',
|
||||||
field: 'value7',
|
field: 'value7',
|
||||||
columnClass: 'expand',
|
columnClass: 'expand',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
label: t('proposal.tag8'),
|
||||||
|
name: 'tag8',
|
||||||
|
field: 'value8',
|
||||||
|
columnClass: 'expand',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
label: t('proposal.advanceable'),
|
||||||
|
name: 'advanceable',
|
||||||
|
field: 'advanceable',
|
||||||
|
columnClass: 'expand',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
...defaultColumnAttrs,
|
...defaultColumnAttrs,
|
||||||
label: t('proposal.price2'),
|
label: t('proposal.price2'),
|
||||||
|
@ -156,7 +176,6 @@ const columns = computed(() => [
|
||||||
{
|
{
|
||||||
title: t('Replace'),
|
title: t('Replace'),
|
||||||
icon: 'change_circle',
|
icon: 'change_circle',
|
||||||
show: (row) => isSelectionAvailable(row),
|
|
||||||
action: change,
|
action: change,
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
|
@ -164,19 +183,26 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function extractMatchValues(obj) {
|
const priceStatusClass = (proposalPrice) => {
|
||||||
return Object.keys(obj)
|
const originalPrice = sale.value?.price;
|
||||||
.filter((key) => key.startsWith(MATCH))
|
const { lackAlertPrice: lackAlert } = ticketConfig.value;
|
||||||
.map((key) => parseInt(key.replace(MATCH, ''), 10));
|
if (!originalPrice || !ticketConfig.value || typeof lackAlert !== 'number') {
|
||||||
}
|
return 'price-ok';
|
||||||
const gradientStyle = (value) => {
|
}
|
||||||
|
|
||||||
|
const percentage = ((proposalPrice - originalPrice) / originalPrice) * 100;
|
||||||
|
|
||||||
|
return percentage > lackAlert ? 'price-alert' : 'price-ok';
|
||||||
|
};
|
||||||
|
|
||||||
|
const gradientStyleClass = (row) => {
|
||||||
let color = 'white';
|
let color = 'white';
|
||||||
const perc = parseFloat(value);
|
const value = parseFloat(row);
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case perc >= 0 && perc < 33:
|
case value >= 0 && value < 33:
|
||||||
color = 'primary';
|
color = 'primary';
|
||||||
break;
|
break;
|
||||||
case perc >= 33 && perc < 66:
|
case value >= 33 && value < 66:
|
||||||
color = 'warning';
|
color = 'warning';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -186,59 +212,89 @@ const gradientStyle = (value) => {
|
||||||
}
|
}
|
||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const extractMatchValues = (obj) => {
|
||||||
|
return Object.keys(obj)
|
||||||
|
.filter((key) => key.startsWith(MATCH))
|
||||||
|
.map((key) => parseInt(key.replace(MATCH, ''), 10));
|
||||||
|
};
|
||||||
|
|
||||||
const statusConditionalValue = (row) => {
|
const statusConditionalValue = (row) => {
|
||||||
const matches = extractMatchValues(row);
|
const matches = extractMatchValues(row);
|
||||||
const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0);
|
const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0);
|
||||||
return 100 * (value / matches.length);
|
return 100 * (value / matches.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSelectionAvailable = (itemProposal) => {
|
const canReplace = (itemProposal) => {
|
||||||
const { price2 } = itemProposal;
|
if (!canReplaceByPrice(itemProposal)) {
|
||||||
const salePrice = sale.value.price;
|
return false;
|
||||||
const byPrice = (100 * price2) / salePrice > ticketConfig.value.lackAlertPrice;
|
|
||||||
if (byPrice) {
|
|
||||||
return byPrice;
|
|
||||||
}
|
}
|
||||||
const byQuantity =
|
return canReplaceByQuantity(itemProposal);
|
||||||
(100 * itemProposal.available) / Math.abs($props.itemLack.lack) <
|
};
|
||||||
ticketConfig.value.lackAlertPrice;
|
const differenceByPrice = ({ price2: proposalPrice }) => {
|
||||||
return byQuantity;
|
const { price: salePrice } = sale.value;
|
||||||
|
const percentage = ((proposalPrice - salePrice) / salePrice) * 100;
|
||||||
|
return percentage;
|
||||||
|
};
|
||||||
|
const canReplaceByPrice = (itemProposal) =>
|
||||||
|
differenceByPrice(itemProposal) < ticketConfig.value.lackAlertPrice;
|
||||||
|
|
||||||
|
const differenceByQuantity = ({ available }) => {
|
||||||
|
const { quantity: saleQuantity } = sale.value;
|
||||||
|
const percentage = ((saleQuantity - available) / available) * 100;
|
||||||
|
return percentage;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function change({ itemFk: substitutionFk }) {
|
const canReplaceByQuantity = (itemProposal) =>
|
||||||
try {
|
differenceByQuantity(itemProposal) < ticketConfig.value.lackAlertPrice;
|
||||||
const promises = $props.sales.map(({ saleFk, quantity }) => {
|
|
||||||
const params = {
|
async function change(itemSubstitution) {
|
||||||
|
if (!canReplaceByPrice(itemSubstitution)) {
|
||||||
|
notify(t('notAvailableByPrice'), 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!canReplaceByQuantity(itemSubstitution)) {
|
||||||
|
notify(t('notAvailableByQuantity'), 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { itemFk: substitutionFk } = itemSubstitution;
|
||||||
|
let body;
|
||||||
|
const promises = $props.sales.map(({ saleFk, quantity, ticketFk }) => {
|
||||||
|
body = {
|
||||||
saleFk,
|
saleFk,
|
||||||
substitutionFk,
|
substitutionFk,
|
||||||
quantity,
|
quantity,
|
||||||
|
ticketFk,
|
||||||
};
|
};
|
||||||
return axios.post('Sales/replaceItem', params);
|
return axios.post('Sales/replaceItem', body);
|
||||||
});
|
});
|
||||||
const results = await Promise.allSettled(promises);
|
const results = await Promise.allSettled(promises);
|
||||||
|
|
||||||
notifyResults(results, 'saleFk');
|
notifyResults(results, 'ticketFk');
|
||||||
emit('itemReplaced', {
|
emit('itemReplaced', {
|
||||||
|
...body,
|
||||||
type: 'refresh',
|
type: 'refresh',
|
||||||
quantity: quantity.value,
|
|
||||||
itemProposal: proposalSelected.value[0],
|
itemProposal: proposalSelected.value[0],
|
||||||
});
|
});
|
||||||
proposalSelected.value = [];
|
proposalSelected.value = [];
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTicketConfig(data) {
|
async function handleTicketConfig(data) {
|
||||||
ticketConfig.value = data[0];
|
ticketConfig.value = data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterRows(data) {
|
||||||
|
const filteredRows = data.sort((a, b) => canReplace(b) - canReplace(a));
|
||||||
|
proposalTableRef.value.CrudModelRef.formData = filteredRows;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
<FetchData
|
||||||
url="TicketConfigs"
|
url="TicketConfigs"
|
||||||
:filter="{ fields: ['lackAlertPrice'] }"
|
:filter="{ fields: ['lackAlertPrice'] }"
|
||||||
@on-fetch="handleTicketConfig"
|
@on-fetch="handleTicketConfig"
|
||||||
></FetchData>
|
auto-load
|
||||||
|
/>
|
||||||
<QInnerLoading
|
<QInnerLoading
|
||||||
:showing="isLoading"
|
:showing="isLoading"
|
||||||
:label="t && t('globals.pleaseWait')"
|
:label="t && t('globals.pleaseWait')"
|
||||||
|
@ -255,13 +311,23 @@ async function handleTicketConfig(data) {
|
||||||
:user-filter="filter"
|
:user-filter="filter"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
class="full-width q-mt-md"
|
class="full-width q-mt-md"
|
||||||
|
@on-fetch="filterRows"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:row-click="change"
|
:row-click="change"
|
||||||
:is-editable="false"
|
:is-editable="false"
|
||||||
:right-search="false"
|
:right-search="false"
|
||||||
:without-header="true"
|
|
||||||
:disable-option="{ card: true, table: true }"
|
:disable-option="{ card: true, table: true }"
|
||||||
>
|
>
|
||||||
|
<template #top-right>
|
||||||
|
<QBtn
|
||||||
|
:disable="false"
|
||||||
|
flat
|
||||||
|
class="q-mr-sm"
|
||||||
|
color="primary"
|
||||||
|
icon="refresh"
|
||||||
|
@click="proposalTableRef.reload()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<template #column-longName="{ row }">
|
<template #column-longName="{ row }">
|
||||||
<QTd
|
<QTd
|
||||||
class="flex"
|
class="flex"
|
||||||
|
@ -269,15 +335,17 @@ async function handleTicketConfig(data) {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="middle full-width"
|
class="middle full-width"
|
||||||
:class="[`proposal-${gradientStyle(statusConditionalValue(row))}`]"
|
:class="[
|
||||||
|
`proposal-${gradientStyleClass(statusConditionalValue(row))}`,
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<QTooltip> {{ statusConditionalValue(row) }}% </QTooltip>
|
<QTooltip> {{ statusConditionalValue(row) }}% </QTooltip>
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 2 0 100%; align-content: center">
|
<div style="flex: 2 0 100%; align-content: center">
|
||||||
<div>
|
<span class="link" @click.stop>
|
||||||
<span class="link">{{ row.longName }}</span>
|
{{ row.longName }}
|
||||||
<ItemDescriptorProxy :id="row.id" />
|
<ItemDescriptorProxy :id="row.id" />
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
|
@ -290,6 +358,9 @@ async function handleTicketConfig(data) {
|
||||||
<template #column-tag7="{ row }">
|
<template #column-tag7="{ row }">
|
||||||
<span :class="{ match: !row.match7 }">{{ row.value7 }}</span>
|
<span :class="{ match: !row.match7 }">{{ row.value7 }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template #column-tag8="{ row }">
|
||||||
|
<span :class="{ match: !row.match8 }">{{ row.value8 }}</span>
|
||||||
|
</template>
|
||||||
<template #column-counter="{ row }">
|
<template #column-counter="{ row }">
|
||||||
<span
|
<span
|
||||||
:class="{
|
:class="{
|
||||||
|
@ -304,8 +375,20 @@ async function handleTicketConfig(data) {
|
||||||
</template>
|
</template>
|
||||||
<template #column-price2="{ row }">
|
<template #column-price2="{ row }">
|
||||||
<div class="flex column items-center content-center">
|
<div class="flex column items-center content-center">
|
||||||
<VnStockValueDisplay :value="(sales[0].price - row.price2) / 100" />
|
<QTooltip :offset="[0, 5]" anchor="top middle" self="bottom middle">
|
||||||
<span :class="[conditionalValuePrice(row.price2)]">{{
|
<div>{{ $t('proposal.price2') }}: {{ toCurrency(row.price2) }}</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('proposal.itemOldPrice') }}:{{
|
||||||
|
toCurrency(sales[0]?.price)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div>{{ $t('%€') }}: {{ differenceByPrice(row) }}%</div>
|
||||||
|
</QTooltip>
|
||||||
|
<VnStockValueDisplay
|
||||||
|
:format="'currency'"
|
||||||
|
:value="row.price2 - sales[0]?.price"
|
||||||
|
/>
|
||||||
|
<span :class="[priceStatusClass(row.price2)]">{{
|
||||||
toCurrency(row.price2)
|
toCurrency(row.price2)
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -319,12 +402,26 @@ async function handleTicketConfig(data) {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
flex: 2 0 5px;
|
flex: 2 0 5px;
|
||||||
}
|
}
|
||||||
|
.price-alert {
|
||||||
|
color: $negative;
|
||||||
|
&.q-tooltip {
|
||||||
|
background-color: $negative;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-ok {
|
||||||
|
color: inherit;
|
||||||
|
&.q-tooltip {
|
||||||
|
background-color: $positive;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.match {
|
.match {
|
||||||
color: $negative;
|
color: $negative;
|
||||||
}
|
}
|
||||||
.not-match {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
.proposal-warning {
|
.proposal-warning {
|
||||||
background-color: $warning;
|
background-color: $warning;
|
||||||
}
|
}
|
||||||
|
@ -344,3 +441,14 @@ async function handleTicketConfig(data) {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
notAvailable: Not available for replacement
|
||||||
|
notAvailableByPrice: Not available for replacement by price
|
||||||
|
notAvailableByQuantity: Not available for replacement by quantity
|
||||||
|
es:
|
||||||
|
notAvailable: No disponible para reemplazo
|
||||||
|
notAvailableByPrice: No disponible para reemplazo por precio
|
||||||
|
notAvailableByQuantity: No disponible para reemplazo por cantidad
|
||||||
|
Replace: Remplazar
|
||||||
|
</i18n>
|
||||||
|
|
|
@ -23,33 +23,32 @@ const $props = defineProps({
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { dialogRef } = useDialogPluginComponent();
|
|
||||||
|
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
||||||
|
useDialogPluginComponent();
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'onDialogClosed',
|
'onDialogClosed',
|
||||||
|
'onDialogOk',
|
||||||
'itemReplaced',
|
'itemReplaced',
|
||||||
...useDialogPluginComponent.emits,
|
...useDialogPluginComponent.emits,
|
||||||
]);
|
]);
|
||||||
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
|
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
|
||||||
|
const itemReplaced = (data) => {
|
||||||
|
onDialogOK(data);
|
||||||
|
dialogRef.value.hide();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<QDialog ref="dialogRef" transition-show="scale" transition-hide="scale">
|
<QDialog ref="dialogRef" transition-show="scale" transition-hide="scale">
|
||||||
<QCard class="dialog-width">
|
<QCard class="dialog-width">
|
||||||
<QCardSection class="row items-center q-pb-none">
|
<QCardSection class="row items-center q-pb-none">
|
||||||
<span class="text-h6 text-grey">{{ $t('itemProposal') }}</span>
|
<span class="text-h6 text-grey" v-text="$t('itemProposal')" />
|
||||||
<QSpace />
|
<QSpace />
|
||||||
<QBtn icon="close" flat round dense v-close-popup />
|
<QBtn icon="close" flat round dense v-close-popup />
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
<QCardSection>
|
<QCardSection>
|
||||||
<ItemProposal
|
<ItemProposal v-bind="$props" @item-replaced="itemReplaced"
|
||||||
v-bind="$props"
|
/></QCardSection>
|
||||||
@item-replaced="
|
|
||||||
(data) => {
|
|
||||||
emit('itemReplaced', data);
|
|
||||||
dialogRef.hide();
|
|
||||||
}
|
|
||||||
"
|
|
||||||
></ItemProposal
|
|
||||||
></QCardSection>
|
|
||||||
</QCard>
|
</QCard>
|
||||||
</QDialog>
|
</QDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -99,9 +99,6 @@ item:
|
||||||
concept: Concept
|
concept: Concept
|
||||||
denyOptions: Deny
|
denyOptions: Deny
|
||||||
scopeDays: Scope days
|
scopeDays: Scope days
|
||||||
searchbar:
|
|
||||||
label: Search item
|
|
||||||
info: You can search by id
|
|
||||||
descriptor:
|
descriptor:
|
||||||
item: Item
|
item: Item
|
||||||
buyer: Buyer
|
buyer: Buyer
|
||||||
|
@ -158,6 +155,7 @@ item:
|
||||||
isPhotoRequestedTooltip: This item does need a photo
|
isPhotoRequestedTooltip: This item does need a photo
|
||||||
isCustomInspectionRequired: Needs physical inspection (PIF)
|
isCustomInspectionRequired: Needs physical inspection (PIF)
|
||||||
description: Description
|
description: Description
|
||||||
|
photoMotivation: Comment for the photographer
|
||||||
fixedPrice:
|
fixedPrice:
|
||||||
itemFk: Item ID
|
itemFk: Item ID
|
||||||
groupingPrice: Grouping price
|
groupingPrice: Grouping price
|
||||||
|
@ -218,7 +216,7 @@ item:
|
||||||
genus: Genus
|
genus: Genus
|
||||||
specie: Specie
|
specie: Specie
|
||||||
search: 'Search item'
|
search: 'Search item'
|
||||||
searchInfo: 'You can search by id'
|
searchInfo: 'You can search by id or barcode'
|
||||||
regularizeStock: Regularize stock
|
regularizeStock: Regularize stock
|
||||||
itemProposal: Items proposal
|
itemProposal: Items proposal
|
||||||
proposal:
|
proposal:
|
||||||
|
@ -231,6 +229,11 @@ proposal:
|
||||||
value6: value6
|
value6: value6
|
||||||
value7: value7
|
value7: value7
|
||||||
value8: value8
|
value8: value8
|
||||||
|
tag5: Tag5
|
||||||
|
tag6: Tag6
|
||||||
|
tag7: Tag7
|
||||||
|
tag8: Tag8
|
||||||
|
advanceable: Advanceable
|
||||||
available: Available
|
available: Available
|
||||||
minQuantity: minQuantity
|
minQuantity: minQuantity
|
||||||
price2: Price
|
price2: Price
|
||||||
|
|
|
@ -73,13 +73,6 @@ itemTags:
|
||||||
addTag: Añadir etiqueta
|
addTag: Añadir etiqueta
|
||||||
tag: Etiqueta
|
tag: Etiqueta
|
||||||
value: Valor
|
value: Valor
|
||||||
itemType:
|
|
||||||
shared:
|
|
||||||
code: Código
|
|
||||||
name: Nombre
|
|
||||||
worker: Trabajador
|
|
||||||
category: Reino
|
|
||||||
temperature: Temperatura
|
|
||||||
searchbar:
|
searchbar:
|
||||||
label: Buscar artículo
|
label: Buscar artículo
|
||||||
info: Buscar por id de artículo
|
info: Buscar por id de artículo
|
||||||
|
@ -155,15 +148,16 @@ item:
|
||||||
weightByPiece: Peso (gramos)/tallo
|
weightByPiece: Peso (gramos)/tallo
|
||||||
boxUnits: Unidades/caja
|
boxUnits: Unidades/caja
|
||||||
recycledPlastic: Plastico reciclado
|
recycledPlastic: Plastico reciclado
|
||||||
nonRecycledPlastic: Plático no reciclado
|
nonRecycledPlastic: Plástico no reciclado
|
||||||
isActive: Activo
|
isActive: Activo
|
||||||
hasKgPrice: Precio en kg
|
hasKgPrice: Precio en kg
|
||||||
isFragile: Frágil
|
isFragile: Frágil
|
||||||
isFragileTooltip: Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...)
|
isFragileTooltip: Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...)
|
||||||
isPhotoRequested: Hacer foto
|
isPhotoRequested: Hacer foto
|
||||||
isPhotoRequestedTooltip: Este artículo necesita una foto
|
isPhotoRequestedTooltip: Este artículo necesita una foto
|
||||||
isCustomInspectionRequired: Necesita inspección física (PIF)
|
isCustomInspectionRequired: Necesita insp. física (PIF)
|
||||||
description: Descripción
|
description: Descripción
|
||||||
|
photoMotivation: Comentario para el fotógrafo
|
||||||
fixedPrice:
|
fixedPrice:
|
||||||
itemFk: ID Artículo
|
itemFk: ID Artículo
|
||||||
groupingPrice: Precio grouping
|
groupingPrice: Precio grouping
|
||||||
|
@ -212,6 +206,8 @@ item:
|
||||||
minSalesQuantity: Cantidad mínima de venta
|
minSalesQuantity: Cantidad mínima de venta
|
||||||
genus: Genus
|
genus: Genus
|
||||||
specie: Specie
|
specie: Specie
|
||||||
|
search: 'Buscar artículo'
|
||||||
|
searchInfo: 'Puedes buscar por id de artículo o código de barras'
|
||||||
regularizeStock: Regularizar stock
|
regularizeStock: Regularizar stock
|
||||||
buyRequest:
|
buyRequest:
|
||||||
ticketId: 'ID Ticket'
|
ticketId: 'ID Ticket'
|
||||||
|
@ -237,11 +233,16 @@ proposal:
|
||||||
value6: value6
|
value6: value6
|
||||||
value7: value7
|
value7: value7
|
||||||
value8: value8
|
value8: value8
|
||||||
|
tag5: Tag5
|
||||||
|
tag6: Tag6
|
||||||
|
tag7: Tag7
|
||||||
|
tag8: Tag8
|
||||||
available: Disponible
|
available: Disponible
|
||||||
minQuantity: Min. cantidad
|
minQuantity: Min. cantidad
|
||||||
price2: Precio
|
price2: Precio
|
||||||
located: Ubicado
|
located: Ubicado
|
||||||
counter: Contador
|
counter: Contador
|
||||||
|
advanceable: Adelantable
|
||||||
difference: Diferencial
|
difference: Diferencial
|
||||||
groupingPrice: Precio Grouping
|
groupingPrice: Precio Grouping
|
||||||
itemOldPrice: Precio itemOld
|
itemOldPrice: Precio itemOld
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
@ -34,34 +34,42 @@ const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const arrayData = useArrayData(props.dataKey);
|
const arrayData = useArrayData(props.dataKey);
|
||||||
|
|
||||||
const categoryList = ref(null);
|
const categoryList = ref(null);
|
||||||
const typeList = ref([]);
|
const typeList = ref([]);
|
||||||
const searchByTag = ref(null);
|
const searchByTag = ref(null);
|
||||||
|
|
||||||
const vnFilterPanelRef = ref();
|
const vnFilterPanelRef = ref();
|
||||||
const orderByList = ref([
|
const orderByListStatic = [
|
||||||
{ id: 'relevancy DESC, name', name: t('params.relevancy'), priority: 999 },
|
{ id: 'relevancy DESC, name', name: t('params.relevancy'), priority: 999 },
|
||||||
{ id: 'showOrder, price', name: t('params.colorAndPrice'), priority: 999 },
|
{ id: 'showOrder, price', name: t('params.colorAndPrice'), priority: 999 },
|
||||||
{ id: 'name', name: t('params.name'), priority: 999 },
|
{ id: 'name', name: t('params.name'), priority: 999 },
|
||||||
{ id: 'price', name: t('params.price'), priority: 999 },
|
{ id: 'price', name: t('params.price'), priority: 999 },
|
||||||
]);
|
];
|
||||||
const orderWayList = ref([
|
const orderWayList = [
|
||||||
{ id: 'ASC', name: t('params.ASC') },
|
{ id: 'ASC', name: t('params.ASC') },
|
||||||
{ id: 'DESC', name: t('params.DESC') },
|
{ id: 'DESC', name: t('params.DESC') },
|
||||||
]);
|
];
|
||||||
const orderBySelected = ref('relevancy DESC, name');
|
const orderBySelected = ref('relevancy DESC, name');
|
||||||
const orderWaySelected = ref('ASC');
|
const orderWaySelected = ref('ASC');
|
||||||
|
const orderByList = computed(() =>
|
||||||
|
orderByListStatic.concat(
|
||||||
|
props.tags.map((tag) => ({
|
||||||
|
...tag,
|
||||||
|
field: tag.id,
|
||||||
|
isTag: true,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const resetCategory = (params, search) => {
|
function resetCategory(params, search) {
|
||||||
typeList.value = null;
|
typeList.value = null;
|
||||||
params.categoryFk = null;
|
params.categoryFk = null;
|
||||||
params.typeFk = null;
|
params.typeFk = null;
|
||||||
arrayData.store.userFilter = null;
|
arrayData.store.userFilter = null;
|
||||||
search();
|
search();
|
||||||
};
|
}
|
||||||
|
|
||||||
const selectCategory = async (params, category, search) => {
|
async function selectCategory(params, category, search) {
|
||||||
if (vnFilterPanelRef.value.params.categoryFk === category?.id) {
|
if (vnFilterPanelRef.value.params.categoryFk === category?.id) {
|
||||||
resetCategory(params, search);
|
resetCategory(params, search);
|
||||||
return;
|
return;
|
||||||
|
@ -69,16 +77,16 @@ const selectCategory = async (params, category, search) => {
|
||||||
params.typeFk = null;
|
params.typeFk = null;
|
||||||
params.categoryFk = category.id;
|
params.categoryFk = category.id;
|
||||||
await loadTypes(category?.id);
|
await loadTypes(category?.id);
|
||||||
};
|
}
|
||||||
|
|
||||||
const loadTypes = async (id) => {
|
async function loadTypes(id) {
|
||||||
const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, {
|
const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, {
|
||||||
params: { itemCategoryId: id },
|
params: { itemCategoryId: id },
|
||||||
});
|
});
|
||||||
typeList.value = data;
|
typeList.value = data;
|
||||||
};
|
}
|
||||||
|
|
||||||
const applyTags = (tagInfo, params, search) => {
|
function applyTags(tagInfo, params, search) {
|
||||||
if (!tagInfo || !tagInfo.values.length) {
|
if (!tagInfo || !tagInfo.values.length) {
|
||||||
params.tagGroups = null;
|
params.tagGroups = null;
|
||||||
search();
|
search();
|
||||||
|
@ -88,7 +96,7 @@ const applyTags = (tagInfo, params, search) => {
|
||||||
if (!params.tagGroups) params.tagGroups = [];
|
if (!params.tagGroups) params.tagGroups = [];
|
||||||
params.tagGroups.push(tagInfo);
|
params.tagGroups.push(tagInfo);
|
||||||
search();
|
search();
|
||||||
};
|
}
|
||||||
|
|
||||||
async function onSearchByTag(value) {
|
async function onSearchByTag(value) {
|
||||||
if (!value.target.value) return;
|
if (!value.target.value) return;
|
||||||
|
@ -102,16 +110,16 @@ async function onSearchByTag(value) {
|
||||||
searchByTag.value = null;
|
searchByTag.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeTagGroupParam = (params, search, valIndex) => {
|
function removeTagGroupParam(params, search, valIndex) {
|
||||||
if (!valIndex && valIndex !== 0) {
|
if (!valIndex && valIndex !== 0) {
|
||||||
params.tagGroups = null;
|
params.tagGroups = null;
|
||||||
} else {
|
} else {
|
||||||
params.tagGroups.splice(valIndex, 1);
|
params.tagGroups.splice(valIndex, 1);
|
||||||
}
|
}
|
||||||
search();
|
search();
|
||||||
};
|
}
|
||||||
|
|
||||||
const setCategoryList = (data) => {
|
function setCategoryList(data) {
|
||||||
categoryList.value = (data || [])
|
categoryList.value = (data || [])
|
||||||
.filter((category) => category.display)
|
.filter((category) => category.display)
|
||||||
.map((category) => ({
|
.map((category) => ({
|
||||||
|
@ -121,17 +129,22 @@ const setCategoryList = (data) => {
|
||||||
|
|
||||||
vnFilterPanelRef.value.params.categoryFk &&
|
vnFilterPanelRef.value.params.categoryFk &&
|
||||||
loadTypes(vnFilterPanelRef.value.params.categoryFk);
|
loadTypes(vnFilterPanelRef.value.params.categoryFk);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getCategoryClass = (category, params) => {
|
function getCategoryClass(category, params) {
|
||||||
if (category.id === params?.categoryFk) {
|
if (category.id === params?.categoryFk) {
|
||||||
return 'active';
|
return 'active';
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function addOrder(value, field, params) {
|
function addOrder(value, field, params) {
|
||||||
let { orderBy } = params;
|
let { orderBy } = params;
|
||||||
orderBy = JSON.parse(orderBy);
|
orderBy = JSON.parse(orderBy);
|
||||||
|
|
||||||
|
if (field == 'field') {
|
||||||
|
orderBy.isTag = !orderByListStatic.some((tag) => tag.id === value);
|
||||||
|
}
|
||||||
|
|
||||||
orderBy[field] = value;
|
orderBy[field] = value;
|
||||||
params.orderBy = JSON.stringify(orderBy);
|
params.orderBy = JSON.stringify(orderBy);
|
||||||
vnFilterPanelRef.value.search();
|
vnFilterPanelRef.value.search();
|
||||||
|
|
|
@ -68,6 +68,19 @@ onMounted(async () => {
|
||||||
<template #menu="{ entity }">
|
<template #menu="{ entity }">
|
||||||
<RouteDescriptorMenu :route="entity" />
|
<RouteDescriptorMenu :route="entity" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions="{ entity }">
|
||||||
|
<QCardActions class="flex justify-center" style="padding-inline: 0">
|
||||||
|
<QBtn
|
||||||
|
size="md"
|
||||||
|
icon="vn:delivery"
|
||||||
|
color="primary"
|
||||||
|
:href="`https://grafana.verdnatura.es/d/edkvyi479dbeob/pronostico-de-entregas?orgId=1&var-vRouteFk=${entity.id}`"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<QTooltip>{{ $t('route.deliveryForecast') }}</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</QCardActions>
|
||||||
|
</template>
|
||||||
</EntityDescriptor>
|
</EntityDescriptor>
|
||||||
</template>
|
</template>
|
||||||
<i18n>
|
<i18n>
|
||||||
|
|
|
@ -4,6 +4,7 @@ export default {
|
||||||
'id',
|
'id',
|
||||||
'workerFk',
|
'workerFk',
|
||||||
'agencyModeFk',
|
'agencyModeFk',
|
||||||
|
'dated',
|
||||||
'created',
|
'created',
|
||||||
'm3',
|
'm3',
|
||||||
'warehouseFk',
|
'warehouseFk',
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { QIcon } from 'quasar';
|
import { QIcon } from 'quasar';
|
||||||
import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters';
|
import { dashIfEmpty, toCurrency, toDate, toDateHourMinSec, toHour } from 'src/filters';
|
||||||
import { openBuscaman } from 'src/utils/buscaman';
|
import { openBuscaman } from 'src/utils/buscaman';
|
||||||
import CardSummary from 'components/ui/CardSummary.vue';
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||||
|
@ -80,6 +80,20 @@ const ticketColumns = ref([
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'delivered',
|
||||||
|
label: t('route.delivered'),
|
||||||
|
field: (row) => dashIfEmpty(toDateHourMinSec(row?.delivered)),
|
||||||
|
sortable: false,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'estimated',
|
||||||
|
label: t('route.estimated'),
|
||||||
|
field: (row) => dashIfEmpty(toDateHourMinSec(row?.estimated)),
|
||||||
|
sortable: false,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'packages',
|
name: 'packages',
|
||||||
label: t('route.summary.packages'),
|
label: t('route.summary.packages'),
|
||||||
|
@ -89,7 +103,7 @@ const ticketColumns = ref([
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'volume',
|
name: 'volume',
|
||||||
label: t('route.summary.m3'),
|
label: 'm³',
|
||||||
field: (row) => row?.volume,
|
field: (row) => row?.volume,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
@ -267,61 +281,3 @@ const ticketColumns = ref([
|
||||||
</CardSummary>
|
</CardSummary>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<i18n>
|
|
||||||
en:
|
|
||||||
route:
|
|
||||||
summary:
|
|
||||||
date: Date
|
|
||||||
agency: Agency
|
|
||||||
vehicle: Vehicle
|
|
||||||
driver: Driver
|
|
||||||
cost: Cost
|
|
||||||
started: Started time
|
|
||||||
finished: Finished time
|
|
||||||
kmStart: Km start
|
|
||||||
kmEnd: Km end
|
|
||||||
volume: Volume
|
|
||||||
packages: Packages
|
|
||||||
description: Description
|
|
||||||
tickets: Tickets
|
|
||||||
order: Order
|
|
||||||
street: Street
|
|
||||||
city: City
|
|
||||||
pc: PC
|
|
||||||
client: Client
|
|
||||||
state: State
|
|
||||||
m3: m³
|
|
||||||
packaging: Packaging
|
|
||||||
ticket: Ticket
|
|
||||||
closed: Closed
|
|
||||||
open: Open
|
|
||||||
yes: Yes
|
|
||||||
no: No
|
|
||||||
es:
|
|
||||||
route:
|
|
||||||
summary:
|
|
||||||
date: Fecha
|
|
||||||
agency: Agencia
|
|
||||||
vehicle: Vehículo
|
|
||||||
driver: Conductor
|
|
||||||
cost: Costo
|
|
||||||
started: Hora inicio
|
|
||||||
finished: Hora fin
|
|
||||||
kmStart: Km inicio
|
|
||||||
kmEnd: Km fin
|
|
||||||
volume: Volumen
|
|
||||||
packages: Bultos
|
|
||||||
description: Descripción
|
|
||||||
tickets: Tickets
|
|
||||||
order: Orden
|
|
||||||
street: Dirección fiscal
|
|
||||||
city: Población
|
|
||||||
pc: CP
|
|
||||||
client: Cliente
|
|
||||||
state: Estado
|
|
||||||
packaging: Encajado
|
|
||||||
closed: Cerrada
|
|
||||||
open: Abierta
|
|
||||||
yes: Sí
|
|
||||||
no: No
|
|
||||||
</i18n>
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { dashIfEmpty } from 'src/filters';
|
import { dashIfEmpty, toDateHourMinSec } from 'src/filters';
|
||||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||||
import VnInput from 'components/common/VnInput.vue';
|
import VnInput from 'components/common/VnInput.vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
@ -24,49 +24,63 @@ const selectedRows = ref([]);
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'order',
|
name: 'order',
|
||||||
label: t('Order'),
|
label: t('route.ticket.order'),
|
||||||
field: (row) => dashIfEmpty(row?.priority),
|
field: (row) => dashIfEmpty(row?.priority),
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'client',
|
name: 'client',
|
||||||
label: t('Client'),
|
label: t('route.ticket.client'),
|
||||||
field: (row) => row?.nickname,
|
field: (row) => row?.nickname,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'street',
|
name: 'street',
|
||||||
label: t('Street'),
|
label: t('route.ticket.street'),
|
||||||
field: (row) => row?.street,
|
field: (row) => row?.street,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'pc',
|
name: 'pc',
|
||||||
label: t('PC'),
|
label: t('route.ticket.PC'),
|
||||||
field: (row) => row?.postalCode,
|
field: (row) => row?.postalCode,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'city',
|
name: 'city',
|
||||||
label: t('City'),
|
label: t('route.ticket.city'),
|
||||||
field: (row) => row?.city,
|
field: (row) => row?.city,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'warehouse',
|
name: 'warehouse',
|
||||||
label: t('Warehouse'),
|
label: t('route.ticket.warehouse'),
|
||||||
field: (row) => row?.warehouseName,
|
field: (row) => row?.warehouseName,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'delivered',
|
||||||
|
label: t('route.delivered'),
|
||||||
|
field: (row) => dashIfEmpty(toDateHourMinSec(row?.delivered)),
|
||||||
|
sortable: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'estimated',
|
||||||
|
label: t('route.estimated'),
|
||||||
|
field: (row) => dashIfEmpty(toDateHourMinSec(row?.estimated)),
|
||||||
|
sortable: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'packages',
|
name: 'packages',
|
||||||
label: t('Packages'),
|
label: t('route.ticket.packages'),
|
||||||
field: (row) => row?.packages,
|
field: (row) => row?.packages,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
@ -80,14 +94,14 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'packaging',
|
name: 'packaging',
|
||||||
label: t('Packaging'),
|
label: t('route.ticket.packaging'),
|
||||||
field: (row) => row?.ipt,
|
field: (row) => row?.ipt,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ticket',
|
name: 'ticket',
|
||||||
label: t('Ticket'),
|
label: t('route.ticket.ticket'),
|
||||||
field: (row) => row?.id,
|
field: (row) => row?.id,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
@ -188,8 +202,8 @@ const confirmRemove = (ticket) => {
|
||||||
.dialog({
|
.dialog({
|
||||||
component: VnConfirm,
|
component: VnConfirm,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
title: t('Confirm removal from route'),
|
title: t('route.ticket.confirmRemoval'),
|
||||||
message: t('Are you sure you want to remove this ticket from the route?'),
|
message: t('route.ticket.confirmRemovalConfirmation'),
|
||||||
promise: () => removeTicket(ticket),
|
promise: () => removeTicket(ticket),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -219,7 +233,7 @@ const openSmsDialog = async () => {
|
||||||
quasar.dialog({
|
quasar.dialog({
|
||||||
component: SendSmsDialog,
|
component: SendSmsDialog,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
title: t('Send SMS to the selected tickets'),
|
title: t('route.ticket.sendSmsTickets'),
|
||||||
url: 'Routes/sendSms',
|
url: 'Routes/sendSms',
|
||||||
destinationFk: clientsId.toString(),
|
destinationFk: clientsId.toString(),
|
||||||
destination: clientsPhone.toString(),
|
destination: clientsPhone.toString(),
|
||||||
|
@ -240,18 +254,25 @@ const openSmsDialog = async () => {
|
||||||
<QDialog v-model="confirmationDialog">
|
<QDialog v-model="confirmationDialog">
|
||||||
<QCard style="min-width: 350px">
|
<QCard style="min-width: 350px">
|
||||||
<QCardSection>
|
<QCardSection>
|
||||||
<p class="text-h6 q-ma-none">{{ t('Select the starting date') }}</p>
|
<p class="text-h6 q-ma-none">
|
||||||
|
{{ t('route.ticket.selectStartingDate') }}
|
||||||
|
</p>
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
|
|
||||||
<QCardSection class="q-pt-none">
|
<QCardSection class="q-pt-none">
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
:label="t('Stating date')"
|
:label="t('route.ticket.startingDate')"
|
||||||
v-model="startingDate"
|
v-model="startingDate"
|
||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
<QCardActions align="right">
|
<QCardActions align="right">
|
||||||
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
|
<QBtn
|
||||||
|
flat
|
||||||
|
:label="t('globals.cancel')"
|
||||||
|
v-close-popup
|
||||||
|
class="text-primary"
|
||||||
|
/>
|
||||||
<QBtn color="primary" v-close-popup @click="cloneRoutes">
|
<QBtn color="primary" v-close-popup @click="cloneRoutes">
|
||||||
{{ t('globals.clone') }}
|
{{ t('globals.clone') }}
|
||||||
</QBtn>
|
</QBtn>
|
||||||
|
@ -262,7 +283,7 @@ const openSmsDialog = async () => {
|
||||||
<QToolbar class="justify-end">
|
<QToolbar class="justify-end">
|
||||||
<div id="st-actions" class="q-pa-sm">
|
<div id="st-actions" class="q-pa-sm">
|
||||||
<QBtn icon="vn:wand" color="primary" class="q-mr-sm" @click="sortRoutes">
|
<QBtn icon="vn:wand" color="primary" class="q-mr-sm" @click="sortRoutes">
|
||||||
<QTooltip>{{ t('Sort routes') }}</QTooltip>
|
<QTooltip>{{ t('route.ticket.sortRoutes') }}</QTooltip>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
<QBtn
|
<QBtn
|
||||||
icon="vn:buscaman"
|
icon="vn:buscaman"
|
||||||
|
@ -271,7 +292,7 @@ const openSmsDialog = async () => {
|
||||||
:disable="!selectedRows?.length"
|
:disable="!selectedRows?.length"
|
||||||
@click="goToBuscaman()"
|
@click="goToBuscaman()"
|
||||||
>
|
>
|
||||||
<QTooltip>{{ t('Open buscaman') }}</QTooltip>
|
<QTooltip>{{ t('route.ticket.openBuscaman') }}</QTooltip>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
<QBtn
|
<QBtn
|
||||||
icon="filter_alt"
|
icon="filter_alt"
|
||||||
|
@ -280,7 +301,7 @@ const openSmsDialog = async () => {
|
||||||
:disable="!selectedRows?.length"
|
:disable="!selectedRows?.length"
|
||||||
@click="deletePriorities"
|
@click="deletePriorities"
|
||||||
>
|
>
|
||||||
<QTooltip>{{ t('Delete priority') }}</QTooltip>
|
<QTooltip>{{ t('route.ticket.deletePriority') }}</QTooltip>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
<QBtn
|
<QBtn
|
||||||
icon="format_list_numbered"
|
icon="format_list_numbered"
|
||||||
|
@ -288,11 +309,7 @@ const openSmsDialog = async () => {
|
||||||
class="q-mr-sm"
|
class="q-mr-sm"
|
||||||
@click="setOrderedPriority"
|
@click="setOrderedPriority"
|
||||||
>
|
>
|
||||||
<QTooltip
|
<QTooltip>{{ t('route.ticket.renumberAllTickets') }} </QTooltip>
|
||||||
>{{
|
|
||||||
t('Renumber all tickets in the order you see on the screen')
|
|
||||||
}}
|
|
||||||
</QTooltip>
|
|
||||||
</QBtn>
|
</QBtn>
|
||||||
<QBtn
|
<QBtn
|
||||||
icon="sms"
|
icon="sms"
|
||||||
|
@ -301,7 +318,7 @@ const openSmsDialog = async () => {
|
||||||
:disable="!selectedRows?.length"
|
:disable="!selectedRows?.length"
|
||||||
@click="openSmsDialog"
|
@click="openSmsDialog"
|
||||||
>
|
>
|
||||||
<QTooltip>{{ t('Send SMS to all clients') }}</QTooltip>
|
<QTooltip>{{ t('route.ticket.sendSmsClients') }}</QTooltip>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
</div>
|
</div>
|
||||||
</QToolbar>
|
</QToolbar>
|
||||||
|
@ -339,7 +356,11 @@ const openSmsDialog = async () => {
|
||||||
@click="setHighestPriority(row, rows)"
|
@click="setHighestPriority(row, rows)"
|
||||||
>
|
>
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
{{ t('Assign highest priority') }}
|
{{
|
||||||
|
t(
|
||||||
|
'route.ticket.assignHighestPriority',
|
||||||
|
)
|
||||||
|
}}
|
||||||
</QTooltip>
|
</QTooltip>
|
||||||
</QIcon>
|
</QIcon>
|
||||||
<VnInput
|
<VnInput
|
||||||
|
@ -354,7 +375,9 @@ const openSmsDialog = async () => {
|
||||||
<QTd>
|
<QTd>
|
||||||
<span class="link" @click="goToBuscaman(row)">
|
<span class="link" @click="goToBuscaman(row)">
|
||||||
{{ value }}
|
{{ value }}
|
||||||
<QTooltip>{{ t('Open buscaman') }}</QTooltip>
|
<QTooltip>{{
|
||||||
|
t('route.ticket.openBuscaman')
|
||||||
|
}}</QTooltip>
|
||||||
</span>
|
</span>
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
|
@ -411,7 +434,7 @@ const openSmsDialog = async () => {
|
||||||
@click="openTicketsDialog"
|
@click="openTicketsDialog"
|
||||||
>
|
>
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
{{ t('Add ticket') }}
|
{{ t('route.ticket.addTicket') }}
|
||||||
</QTooltip>
|
</QTooltip>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
</QPageSticky>
|
</QPageSticky>
|
||||||
|
@ -432,24 +455,3 @@ const openSmsDialog = async () => {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<i18n>
|
|
||||||
es:
|
|
||||||
Order: Orden
|
|
||||||
Street: Dirección fiscal
|
|
||||||
City: Población
|
|
||||||
PC: CP
|
|
||||||
Client: Cliente
|
|
||||||
Warehouse: Almacén
|
|
||||||
Packages: Bultos
|
|
||||||
Packaging: Encajado
|
|
||||||
Confirm removal from route: Quitar de la ruta
|
|
||||||
Are you sure you want to remove this ticket from the route?: ¿Seguro que quieres quitar este ticket de la ruta?
|
|
||||||
Sort routes: Ordenar rutas
|
|
||||||
Open buscaman: Abrir buscaman
|
|
||||||
Delete priority: Borrar orden
|
|
||||||
Renumber all tickets in the order you see on the screen: Renumerar todos los tickets con el orden que ves por pantalla
|
|
||||||
Assign highest priority: Asignar máxima prioridad
|
|
||||||
Send SMS to all clients: Mandar sms a todos los clientes de las rutas
|
|
||||||
Send SMS to the selected tickets: Enviar SMS a los tickets seleccionados
|
|
||||||
Add ticket: Añadir ticket
|
|
||||||
</i18n>
|
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script setup>
|
||||||
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
|
import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
|
||||||
|
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||||
|
import { toDate, toCurrency } from 'src/filters/index';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
const dataKey = 'VehicleInvoiceIn';
|
||||||
|
const { openConfirmationModal } = useVnConfirm();
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'issued',
|
||||||
|
label: t('invoiceIn.list.issued'),
|
||||||
|
columnFilter: {
|
||||||
|
component: 'date',
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.issued)),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'supplierFk',
|
||||||
|
label: t('invoiceIn.list.supplier'),
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'Suppliers',
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: ({ supplierName }) => supplierName,
|
||||||
|
columnClass: 'expand',
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'supplierRef',
|
||||||
|
label: t('invoiceIn.supplierRef'),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'amount',
|
||||||
|
label: t('invoiceIn.list.amount'),
|
||||||
|
format: ({ amount }) => toCurrency(amount),
|
||||||
|
columnFilter: false,
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
name: 'tableActions',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: t('vehicle.ticket.unassignInvoice'),
|
||||||
|
icon: 'delete',
|
||||||
|
action: (row) =>
|
||||||
|
openConfirmationModal(
|
||||||
|
t('vehicle.ticket.unassignInvoice'),
|
||||||
|
t('vehicle.ticket.unassignInvoiceConfirmation'),
|
||||||
|
() => unassignInvoice(row.id),
|
||||||
|
),
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function unassignInvoice(id) {
|
||||||
|
try {
|
||||||
|
await axios.delete(`VehicleInvoiceIns/${id}`);
|
||||||
|
notify(t('vehicle.ticket.unassignedInvoice'), 'positive');
|
||||||
|
tableRef.value.reload();
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VnTable
|
||||||
|
ref="tableRef"
|
||||||
|
:data-key="dataKey"
|
||||||
|
:url="`vehicles/${route.params.id}/getInvoices`"
|
||||||
|
:columns="columns"
|
||||||
|
search-url="vehicleInvoiceIns"
|
||||||
|
:order="['issued DESC', 'supplierRef ASC']"
|
||||||
|
:create="{
|
||||||
|
urlCreate: 'VehicleInvoiceIns',
|
||||||
|
title: t('vehicle.ticket.assignInvoice'),
|
||||||
|
formInitialData: {
|
||||||
|
vehicleFk: parseInt(route.params.id, 10),
|
||||||
|
},
|
||||||
|
onDataSaved: ({ id }) => tableRef.reload(),
|
||||||
|
}"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #column-supplierFk="{ row }">
|
||||||
|
<span class="link" @click.stop>
|
||||||
|
{{ row.supplierName }}
|
||||||
|
<SupplierDescriptorProxy :id="row.supplierId" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #column-supplierRef="{ row }">
|
||||||
|
<span class="link" @click.stop>
|
||||||
|
{{ row.supplierRef }}
|
||||||
|
<InvoiceInDescriptorProxy :id="row.invoiceInFk" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #more-create-dialog="{ data }">
|
||||||
|
<VnSelect
|
||||||
|
url="invoiceIns"
|
||||||
|
:label="t('invoiceIn.supplierRef')"
|
||||||
|
:fields="['id', 'supplierRef', 'supplierFk']"
|
||||||
|
:filter-options="['id', 'supplierRef']"
|
||||||
|
v-model="data.invoiceInFk"
|
||||||
|
option-label="supplierRef"
|
||||||
|
:required="true"
|
||||||
|
>
|
||||||
|
</VnSelect>
|
||||||
|
<VnInputNumber
|
||||||
|
:label="t('invoiceIn.list.amount')"
|
||||||
|
v-model="data.amount"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VnTable>
|
||||||
|
</template>
|
|
@ -1,16 +1,20 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
|
||||||
|
import { downloadFile } from 'src/composables/downloadFile';
|
||||||
|
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
import CardSummary from 'components/ui/CardSummary.vue';
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
import VnLv from 'src/components/ui/VnLv.vue';
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
import VnTitle from 'src/components/common/VnTitle.vue';
|
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||||
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||||
|
import InvoiceInDescriptorProxy from 'src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
|
||||||
import VehicleFilter from '../VehicleFilter.js';
|
import VehicleFilter from '../VehicleFilter.js';
|
||||||
import { downloadFile } from 'src/composables/downloadFile';
|
|
||||||
import { dashIfEmpty } from 'src/filters';
|
|
||||||
|
|
||||||
const props = defineProps({ id: { type: [Number, String], default: null } });
|
const props = defineProps({ id: { type: [Number, String], default: null } });
|
||||||
|
|
||||||
|
const invoices = ref([]);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const entityId = computed(() => props.id || +route.params.id);
|
const entityId = computed(() => props.id || +route.params.id);
|
||||||
const baseLink = `#/${route.meta.moduleName.toLowerCase()}/vehicle/${entityId.value}`;
|
const baseLink = `#/${route.meta.moduleName.toLowerCase()}/vehicle/${entityId.value}`;
|
||||||
|
@ -23,6 +27,11 @@ const links = {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
<FetchData
|
||||||
|
:url="`Vehicles/${entityId}/getInvoices`"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (invoices = data)"
|
||||||
|
/>
|
||||||
<CardSummary
|
<CardSummary
|
||||||
data-key="Vehicle"
|
data-key="Vehicle"
|
||||||
:url="`Vehicles/${entityId}`"
|
:url="`Vehicles/${entityId}`"
|
||||||
|
@ -132,6 +141,45 @@ const links = {
|
||||||
</QList>
|
</QList>
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
</QCard>
|
</QCard>
|
||||||
|
<QCard class="vn-max">
|
||||||
|
<VnTitle
|
||||||
|
:url="links['invoice-in']"
|
||||||
|
:text="$t('globals.pageTitles.assignedInvoices')"
|
||||||
|
/>
|
||||||
|
<QTable :rows="invoices" style="text-align: center">
|
||||||
|
<template #body-cell="{ value }">
|
||||||
|
<QTd>{{ value }}</QTd>
|
||||||
|
</template>
|
||||||
|
<template #header="props">
|
||||||
|
<QTr class="tr-header" :props="props">
|
||||||
|
<QTh auto-width>{{ $t('invoiceIn.list.issued') }}</QTh>
|
||||||
|
<QTh auto-width>{{ $t('invoiceIn.list.supplier') }}</QTh>
|
||||||
|
<QTh auto-width>{{ $t('invoiceIn.supplierRef') }}</QTh>
|
||||||
|
<QTh auto-width>{{ $t('invoiceIn.list.amount') }}</QTh>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
<template #body="props">
|
||||||
|
<QTr :props="props">
|
||||||
|
<QTd>{{ toDate(props.row.issued) }}</QTd>
|
||||||
|
<QTd>
|
||||||
|
<span class="link">
|
||||||
|
{{ props.row.supplierName }}
|
||||||
|
<SupplierDescriptorProxy :id="props.row.supplierId" />
|
||||||
|
</span>
|
||||||
|
</QTd>
|
||||||
|
<QTd>
|
||||||
|
<span class="link">
|
||||||
|
{{ props.row.supplierRef }}
|
||||||
|
<InvoiceInDescriptorProxy
|
||||||
|
:id="props.row.supplierId"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</QTd>
|
||||||
|
<QTd>{{ toCurrency(props.row.amount) }}</QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</QCard>
|
||||||
</template>
|
</template>
|
||||||
</CardSummary>
|
</CardSummary>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -108,6 +108,13 @@ const columns = computed(() => [
|
||||||
options: countries.value,
|
options: countries.value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'isActive',
|
||||||
|
label: t('globals.active'),
|
||||||
|
visible: false,
|
||||||
|
cardVisible: false,
|
||||||
|
component: 'checkbox',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
align: 'right',
|
align: 'right',
|
||||||
name: 'tableActions',
|
name: 'tableActions',
|
||||||
|
|
|
@ -14,11 +14,16 @@ vehicle:
|
||||||
amountCooler: Amount cooler
|
amountCooler: Amount cooler
|
||||||
remove: Vehicle removed
|
remove: Vehicle removed
|
||||||
search: Search Vehicle
|
search: Search Vehicle
|
||||||
searchInfo: Search by id or number plate
|
searchInfo: Search by id
|
||||||
deleteTitle: This item will be deleted
|
deleteTitle: This item will be deleted
|
||||||
deleteSubtitle: Are you sure you want to continue?
|
deleteSubtitle: Are you sure you want to continue?
|
||||||
params:
|
params:
|
||||||
vehicleTypeFk: Type
|
vehicleTypeFk: Type
|
||||||
vehicleStateFk: State
|
vehicleStateFk: State
|
||||||
|
ticket:
|
||||||
|
assignInvoice: Assign invoice
|
||||||
|
unassignedInvoice: Unassigned invoice
|
||||||
|
unassignInvoice: Unassign invoice
|
||||||
|
unassignInvoiceConfirmation: This invoice will be unassigned from this vehicle! Continue anyway?
|
||||||
errors:
|
errors:
|
||||||
documentIdEmpty: The document identifier can't be empty
|
documentIdEmpty: The document identifier can't be empty
|
||||||
|
|
|
@ -14,11 +14,16 @@ vehicle:
|
||||||
nLeasing: Nº leasing
|
nLeasing: Nº leasing
|
||||||
remove: Vehículo eliminado
|
remove: Vehículo eliminado
|
||||||
search: Buscar Vehículo
|
search: Buscar Vehículo
|
||||||
searchInfo: Buscar por id o matrícula
|
searchInfo: Buscar por id
|
||||||
deleteTitle: Este elemento será eliminado
|
deleteTitle: Este elemento será eliminado
|
||||||
deleteSubtitle: ¿Seguro que quieres continuar?
|
deleteSubtitle: ¿Seguro que quieres continuar?
|
||||||
params:
|
params:
|
||||||
vehicleTypeFk: Tipo
|
vehicleTypeFk: Tipo
|
||||||
vehicleStateFk: Estado
|
vehicleStateFk: Estado
|
||||||
|
ticket:
|
||||||
|
assignInvoice: Vincular factura
|
||||||
|
unassignedInvoice: Factura desvinculada
|
||||||
|
unassignInvoice: Desvincular factura
|
||||||
|
unassignInvoiceConfirmation: Esta factura se desvinculará de este vehículo! ¿Continuar de todas formas?
|
||||||
errors:
|
errors:
|
||||||
documentIdEmpty: El número de documento no puede estar vacío
|
documentIdEmpty: El número de documento no puede estar vacío
|
||||||
|
|
|
@ -1,6 +1,33 @@
|
||||||
route:
|
route:
|
||||||
filter:
|
filter:
|
||||||
Served: Served
|
Served: Served
|
||||||
|
summary:
|
||||||
|
date: Date
|
||||||
|
agency: Agency
|
||||||
|
vehicle: Vehicle
|
||||||
|
driver: Driver
|
||||||
|
cost: Cost
|
||||||
|
started: Started time
|
||||||
|
finished: Finished time
|
||||||
|
kmStart: Km start
|
||||||
|
kmEnd: Km end
|
||||||
|
volume: Volume
|
||||||
|
packages: Packages
|
||||||
|
description: Description
|
||||||
|
tickets: Tickets
|
||||||
|
order: Order
|
||||||
|
street: Street
|
||||||
|
city: City
|
||||||
|
pc: PC
|
||||||
|
client: Client
|
||||||
|
state: State
|
||||||
|
m3: m³
|
||||||
|
packaging: Packaging
|
||||||
|
ticket: Ticket
|
||||||
|
closed: Closed
|
||||||
|
open: Open
|
||||||
|
yes: Yes
|
||||||
|
no: No
|
||||||
extendedList:
|
extendedList:
|
||||||
selectStartingDate: Select the starting date
|
selectStartingDate: Select the starting date
|
||||||
startingDate: Starting date
|
startingDate: Starting date
|
||||||
|
@ -51,6 +78,8 @@ route:
|
||||||
agencyModeName: Agency route
|
agencyModeName: Agency route
|
||||||
isOwn: Own
|
isOwn: Own
|
||||||
isAnyVolumeAllowed: Any volume allowed
|
isAnyVolumeAllowed: Any volume allowed
|
||||||
|
isActive: Active
|
||||||
|
issued: Issued
|
||||||
created: Created
|
created: Created
|
||||||
addressFromFk: Sender
|
addressFromFk: Sender
|
||||||
addressToFk: Destination
|
addressToFk: Destination
|
||||||
|
@ -75,3 +104,45 @@ route:
|
||||||
searchInfo: You can search by route reference
|
searchInfo: You can search by route reference
|
||||||
dated: Dated
|
dated: Dated
|
||||||
preview: Preview
|
preview: Preview
|
||||||
|
delivered: Delivered
|
||||||
|
estimated: Estimated
|
||||||
|
cmr:
|
||||||
|
search: Search Cmr
|
||||||
|
searchInfo: You can search Cmr by Id
|
||||||
|
params:
|
||||||
|
results: results
|
||||||
|
cmrFk: CMR id
|
||||||
|
hasCmrDms: Attached in gestdoc
|
||||||
|
true: Yes
|
||||||
|
false: No
|
||||||
|
ticketFk: Ticketd id
|
||||||
|
routeFk: Route id
|
||||||
|
countryFk: Country
|
||||||
|
clientFk: Client id
|
||||||
|
warehouseFk: Warehouse
|
||||||
|
shipped: Preparation date
|
||||||
|
viewCmr: View CMR
|
||||||
|
downloadCmrs: Download CMRs
|
||||||
|
search: General search
|
||||||
|
ticket:
|
||||||
|
order: Order
|
||||||
|
street: Street
|
||||||
|
city: City
|
||||||
|
PC: PC
|
||||||
|
client: Client
|
||||||
|
warehouse: Warehouse
|
||||||
|
packages: Packages
|
||||||
|
packaging: Packaging
|
||||||
|
ticket: Ticket
|
||||||
|
confirmRemoval: Confirm removal from route
|
||||||
|
confirmRemovalConfirmation: Are you sure you want to remove this ticket from the route?
|
||||||
|
selectStartingDate: Select the starting date
|
||||||
|
startingDate: Starting date
|
||||||
|
sortRoutes: Sort routes
|
||||||
|
openBuscaman: Open buscaman
|
||||||
|
deletePriority: Delete priority
|
||||||
|
renumberAllTickets: Renumber all tickets in the order you see on the screen
|
||||||
|
assignHighest: Assign highest priority
|
||||||
|
sendSmsTickets: Send SMS to the selected tickets
|
||||||
|
sendSmsClients: Send SMS to all clients
|
||||||
|
addTicket: Add ticket
|
||||||
|
|
|
@ -1,6 +1,31 @@
|
||||||
route:
|
route:
|
||||||
filter:
|
filter:
|
||||||
Served: Servida
|
Served: Servida
|
||||||
|
summary:
|
||||||
|
date: Fecha
|
||||||
|
agency: Agencia
|
||||||
|
vehicle: Vehículo
|
||||||
|
driver: Conductor
|
||||||
|
cost: Costo
|
||||||
|
started: Hora inicio
|
||||||
|
finished: Hora fin
|
||||||
|
kmStart: Km inicio
|
||||||
|
kmEnd: Km fin
|
||||||
|
volume: Volumen
|
||||||
|
packages: Bultos
|
||||||
|
description: Descripción
|
||||||
|
tickets: Tickets
|
||||||
|
order: Orden
|
||||||
|
street: Dirección fiscal
|
||||||
|
city: Población
|
||||||
|
pc: CP
|
||||||
|
client: Cliente
|
||||||
|
state: Estado
|
||||||
|
packaging: Encajado
|
||||||
|
closed: Cerrada
|
||||||
|
open: Abierta
|
||||||
|
yes: Sí
|
||||||
|
no: No
|
||||||
extendedList:
|
extendedList:
|
||||||
selectStartingDate: Seleccione la fecha de inicio
|
selectStartingDate: Seleccione la fecha de inicio
|
||||||
statingDate: Fecha de inicio
|
statingDate: Fecha de inicio
|
||||||
|
@ -52,6 +77,8 @@ route:
|
||||||
agencyAgreement: Agencia Acuerdo
|
agencyAgreement: Agencia Acuerdo
|
||||||
isOwn: Propio
|
isOwn: Propio
|
||||||
isAnyVolumeAllowed: Cualquier volumen
|
isAnyVolumeAllowed: Cualquier volumen
|
||||||
|
isActive: Activo
|
||||||
|
issued: F. emisión
|
||||||
created: Creado
|
created: Creado
|
||||||
addressFromFk: Remitente
|
addressFromFk: Remitente
|
||||||
addressToFk: Destinatario
|
addressToFk: Destinatario
|
||||||
|
@ -76,17 +103,42 @@ route:
|
||||||
searchInfo: Puedes buscar por referencia de la ruta
|
searchInfo: Puedes buscar por referencia de la ruta
|
||||||
dated: Fecha
|
dated: Fecha
|
||||||
preview: Vista previa
|
preview: Vista previa
|
||||||
|
delivered: Entregado
|
||||||
|
estimated: Pronóstico
|
||||||
cmr:
|
cmr:
|
||||||
list:
|
list:
|
||||||
results: resultados
|
results: resultados
|
||||||
cmrFk: Id CMR
|
cmrFk: Id CMR
|
||||||
hasCmrDms: Gestdoc
|
hasCmrDms: Gestdoc
|
||||||
'true': Sí
|
true: Sí
|
||||||
'false': 'No'
|
false: No
|
||||||
ticketFk: Id ticket
|
ticketFk: Id ticket
|
||||||
routeFk: Id ruta
|
routeFk: Id ruta
|
||||||
country: País
|
country: País
|
||||||
clientFk: Id cliente
|
clientFk: Id cliente
|
||||||
shipped: Fecha preparación
|
warehouseFk: Almacén
|
||||||
|
shipped: F. preparación
|
||||||
viewCmr: Ver CMR
|
viewCmr: Ver CMR
|
||||||
downloadCmrs: Descargar CMRs
|
downloadCmrs: Descargar CMRs
|
||||||
|
ticket:
|
||||||
|
order: Orden
|
||||||
|
street: Dirección fiscal
|
||||||
|
city: Población
|
||||||
|
PC: CP
|
||||||
|
client: Cliente
|
||||||
|
warehouse: Almacén
|
||||||
|
packages: Bultos
|
||||||
|
packaging: Encajado
|
||||||
|
ticket: Ticket
|
||||||
|
confirmRemoval: Quitar de la ruta
|
||||||
|
confirmRemovalConfirmation: ¿Seguro que quieres quitar este ticket de la ruta?
|
||||||
|
selectStartingDate: Seleccionar fecha de inicio
|
||||||
|
startingDate: F. Inicio
|
||||||
|
sortRoutes: Ordenar rutas
|
||||||
|
openBuscaman: Abrir buscaman
|
||||||
|
deletePriority: Borrar orden
|
||||||
|
renumberAllTickets: Renumerar todos los tickets con el orden que ves por pantalla
|
||||||
|
assignHighest: Asignar máxima prioridad
|
||||||
|
sendSmsTickets: Enviar SMS a los tickets seleccionados
|
||||||
|
sendSmsClients: Mandar sms a todos los clientes de las rutas
|
||||||
|
addTicket: Añadir ticket
|
||||||
|
|
|
@ -26,7 +26,8 @@ const { notify } = useNotify();
|
||||||
const totalRows = ref({});
|
const totalRows = ref({});
|
||||||
const arrayData = useArrayData('SupplierConsumption', {
|
const arrayData = useArrayData('SupplierConsumption', {
|
||||||
url: 'Suppliers/consumption',
|
url: 'Suppliers/consumption',
|
||||||
order: ['itemTypeFk', 'itemName', 'itemSize'],
|
order: ['shipped DESC', 'itemTypeFk', 'itemName', 'itemSize'],
|
||||||
|
limit: 0,
|
||||||
userFilter: { where: { supplierFk: route.params.id } },
|
userFilter: { where: { supplierFk: route.params.id } },
|
||||||
});
|
});
|
||||||
const headerColumns = computed(() => [
|
const headerColumns = computed(() => [
|
||||||
|
|
|
@ -187,11 +187,11 @@ const getRowUpdateInputEvents = (sale) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetChanges = async () => {
|
const resetChanges = async () => {
|
||||||
|
const _selectedRows = selectedRows.value;
|
||||||
await arrayData.fetch({ append: false });
|
await arrayData.fetch({ append: false });
|
||||||
tableRef.value.CrudModelRef.hasChanges = false;
|
tableRef.value.CrudModelRef.hasChanges = false;
|
||||||
await tableRef.value.reload();
|
await tableRef.value.reload();
|
||||||
|
tableRef.value.selected = _selectedRows;
|
||||||
selectedRows.value = [];
|
|
||||||
};
|
};
|
||||||
const changeQuantity = async (sale) => {
|
const changeQuantity = async (sale) => {
|
||||||
if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity)
|
if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity)
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
import split from './components/split';
|
import split from './components/split';
|
||||||
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
|
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
|
||||||
const { notifyResults } = displayResults();
|
const { notifyResults } = displayResults();
|
||||||
const emit = defineEmits(['ticketTransferred']);
|
const emit = defineEmits(['ticketTransferred']);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
ticket: {
|
ticket: {
|
||||||
|
@ -27,7 +29,7 @@ const splitSelectedRows = async () => {
|
||||||
<template>
|
<template>
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
class="q-mr-sm"
|
class="q-mr-sm"
|
||||||
:label="$t('New date')"
|
:label="t('New date')"
|
||||||
v-model="splitDate"
|
v-model="splitDate"
|
||||||
clearable
|
clearable
|
||||||
autofocus
|
autofocus
|
||||||
|
@ -41,6 +43,9 @@ const splitSelectedRows = async () => {
|
||||||
</style>
|
</style>
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
|
New date: Nueva fecha
|
||||||
|
Split: Separar
|
||||||
|
Transfer lines: Transferir líneas
|
||||||
Sales to transfer: Líneas a transferir
|
Sales to transfer: Líneas a transferir
|
||||||
Destination ticket: Ticket destinatario
|
Destination ticket: Ticket destinatario
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
export default async function (data, date) {
|
export default async function (data, landed) {
|
||||||
const reducedData = data.reduce((acc, item) => {
|
const reducedData = data.reduce((acc, item) => {
|
||||||
const existing = acc.find(({ ticketFk }) => ticketFk === item.id);
|
const existing = acc.find(({ ticketFk }) => ticketFk === item.id);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.sales.push(item.saleFk);
|
existing.sales.push(item.saleFk);
|
||||||
} else {
|
} else {
|
||||||
acc.push({ ticketFk: item.ticketFk, sales: [item.saleFk], date });
|
acc.push({ ticketFk: item.ticketFk, sales: [item.saleFk], landed });
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
@ -17,7 +17,6 @@ import ItemProposalProxy from 'src/pages/Item/components/ItemProposalProxy.vue';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const editableStates = ref([]);
|
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const changeItemDialogRef = ref(null);
|
const changeItemDialogRef = ref(null);
|
||||||
|
@ -70,14 +69,11 @@ const showItemProposal = () => {
|
||||||
})
|
})
|
||||||
.onOk(itemProposalEvt);
|
.onOk(itemProposalEvt);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isButtonDisabled = computed(() => selectedRows.value.length !== 1);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
|
||||||
url="States/editableStates"
|
|
||||||
@on-fetch="(data) => (editableStates = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
<FetchData
|
||||||
:url="`Items/${entityId}/getCard`"
|
:url="`Items/${entityId}/getCard`"
|
||||||
:fields="['longName']"
|
:fields="['longName']"
|
||||||
|
@ -99,11 +95,7 @@ const showItemProposal = () => {
|
||||||
>
|
>
|
||||||
<template #top-right>
|
<template #top-right>
|
||||||
<QBtnGroup push class="q-mr-lg" style="column-gap: 1px">
|
<QBtnGroup push class="q-mr-lg" style="column-gap: 1px">
|
||||||
<QBtn
|
<QBtn data-cy="transferLines" color="primary" :disable="isButtonDisabled">
|
||||||
data-cy="transferLines"
|
|
||||||
color="primary"
|
|
||||||
:disable="!(selectedRows.length === 1)"
|
|
||||||
>
|
|
||||||
<template #default>
|
<template #default>
|
||||||
<QIcon name="vn:splitline" />
|
<QIcon name="vn:splitline" />
|
||||||
<QIcon name="vn:ticket" />
|
<QIcon name="vn:ticket" />
|
||||||
|
@ -124,7 +116,7 @@ const showItemProposal = () => {
|
||||||
<QBtn
|
<QBtn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="showItemProposal"
|
@click="showItemProposal"
|
||||||
:disable="!(selectedRows.length === 1)"
|
:disable="isButtonDisabled"
|
||||||
data-cy="itemProposal"
|
data-cy="itemProposal"
|
||||||
>
|
>
|
||||||
<QIcon name="import_export" class="rotate-90" />
|
<QIcon name="import_export" class="rotate-90" />
|
||||||
|
@ -135,7 +127,7 @@ const showItemProposal = () => {
|
||||||
<VnPopupProxy
|
<VnPopupProxy
|
||||||
data-cy="changeItem"
|
data-cy="changeItem"
|
||||||
icon="sync"
|
icon="sync"
|
||||||
:disable="!(selectedRows.length === 1)"
|
:disable="isButtonDisabled"
|
||||||
:tooltip="t('negative.detail.modal.changeItem.title')"
|
:tooltip="t('negative.detail.modal.changeItem.title')"
|
||||||
>
|
>
|
||||||
<template #extraIcon> <QIcon name="vn:item" /> </template>
|
<template #extraIcon> <QIcon name="vn:item" /> </template>
|
||||||
|
@ -149,7 +141,7 @@ const showItemProposal = () => {
|
||||||
<VnPopupProxy
|
<VnPopupProxy
|
||||||
data-cy="changeState"
|
data-cy="changeState"
|
||||||
icon="sync"
|
icon="sync"
|
||||||
:disable="!(selectedRows.length === 1)"
|
:disable="isButtonDisabled"
|
||||||
:tooltip="t('negative.detail.modal.changeState.title')"
|
:tooltip="t('negative.detail.modal.changeState.title')"
|
||||||
>
|
>
|
||||||
<template #extraIcon> <QIcon name="vn:eye" /> </template>
|
<template #extraIcon> <QIcon name="vn:eye" /> </template>
|
||||||
|
@ -163,7 +155,7 @@ const showItemProposal = () => {
|
||||||
<VnPopupProxy
|
<VnPopupProxy
|
||||||
data-cy="changeQuantity"
|
data-cy="changeQuantity"
|
||||||
icon="sync"
|
icon="sync"
|
||||||
:disable="!(selectedRows.length === 1)"
|
:disable="isButtonDisabled"
|
||||||
:tooltip="t('negative.detail.modal.changeQuantity.title')"
|
:tooltip="t('negative.detail.modal.changeQuantity.title')"
|
||||||
@click="showChangeQuantityDialog = true"
|
@click="showChangeQuantityDialog = true"
|
||||||
>
|
>
|
||||||
|
|
|
@ -7,6 +7,8 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import VnInputDateTime from 'src/components/common/VnInputDateTime.vue';
|
import VnInputDateTime from 'src/components/common/VnInputDateTime.vue';
|
||||||
|
import VnInputDates from 'src/components/common/VnInputDates.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
dataKey: {
|
dataKey: {
|
||||||
|
@ -73,8 +75,8 @@ const setUserParams = (params) => {
|
||||||
<VnFilterPanel
|
<VnFilterPanel
|
||||||
:data-key="props.dataKey"
|
:data-key="props.dataKey"
|
||||||
:search-button="true"
|
:search-button="true"
|
||||||
|
:hidden-tags="['excludedDates']"
|
||||||
@set-user-params="setUserParams"
|
@set-user-params="setUserParams"
|
||||||
:unremovable-params="['warehouseFk']"
|
|
||||||
>
|
>
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
|
@ -92,7 +94,7 @@ const setUserParams = (params) => {
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(value) => {
|
() => {
|
||||||
setUserParams(params);
|
setUserParams(params);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@ -127,8 +129,19 @@ const setUserParams = (params) => {
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
</QItemSection> </QItem
|
</QItemSection>
|
||||||
><QItem>
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<VnInputDates
|
||||||
|
v-model="params.excludedDates"
|
||||||
|
filled
|
||||||
|
:label="t('negative.excludedDates')"
|
||||||
|
>
|
||||||
|
</VnInputDates>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
<QItemSection v-if="categoriesOptions">
|
<QItemSection v-if="categoriesOptions">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('negative.categoryFk')"
|
:label="t('negative.categoryFk')"
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { onBeforeMount } from 'vue';
|
||||||
import { dashIfEmpty, toDate, toHour } from 'src/filters';
|
import { dashIfEmpty, toDate, toHour } from 'src/filters';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
|
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
|
||||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||||
import TicketLackFilter from './TicketLackFilter.vue';
|
import TicketLackFilter from './TicketLackFilter.vue';
|
||||||
|
@ -45,10 +46,10 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
columnClass: 'shrink',
|
columnClass: 'shrink',
|
||||||
name: 'timed',
|
name: 'minTimed',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
label: t('negative.timed'),
|
label: t('negative.timed'),
|
||||||
format: ({ timed }) => toHour(timed),
|
format: ({ minTimed }) => toHour(minTimed),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
|
@ -64,9 +65,25 @@ const columns = computed(() => [
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'input',
|
component: 'input',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
inWhere: false,
|
columnClass: 'shrink',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'nextEntryFk',
|
||||||
|
align: 'center',
|
||||||
|
label: t('negative.nextEntryFk'),
|
||||||
|
format: ({ nextEntryFk }) => nextEntryFk,
|
||||||
|
sortable: false,
|
||||||
|
columnFilter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nextEntryLanded',
|
||||||
|
align: 'center',
|
||||||
|
label: t('negative.nextEntryLanded'),
|
||||||
|
format: ({ nextEntryLanded }) => toDate(nextEntryLanded),
|
||||||
|
sortable: false,
|
||||||
|
columnFilter: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'longName',
|
name: 'longName',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
@ -195,6 +212,12 @@ const setUserParams = (params) => {
|
||||||
<span @click.stop>{{ row.itemFk }}</span>
|
<span @click.stop>{{ row.itemFk }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #column-nextEntryFk="{ row }">
|
||||||
|
<span class="link" @click.stop>
|
||||||
|
{{ row.nextEntryFk }}
|
||||||
|
<EntryDescriptorProxy :id="row.nextEntryFk" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<template #column-longName="{ row }">
|
<template #column-longName="{ row }">
|
||||||
<span class="link" @click.stop>
|
<span class="link" @click.stop>
|
||||||
{{ row.longName }}
|
{{ row.longName }}
|
||||||
|
|
|
@ -35,6 +35,7 @@ const filterLack = ref({
|
||||||
order: 'ts.alertLevelCode ASC',
|
order: 'ts.alertLevelCode ASC',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const editableStates = ref([]);
|
||||||
const selectedRows = ref([]);
|
const selectedRows = ref([]);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
@ -135,9 +136,12 @@ const saveChange = async (field, { row }) => {
|
||||||
try {
|
try {
|
||||||
switch (field) {
|
switch (field) {
|
||||||
case 'alertLevelCode':
|
case 'alertLevelCode':
|
||||||
|
const { id: code } = editableStates.value.find(
|
||||||
|
({ name }) => name === row.code,
|
||||||
|
);
|
||||||
await axios.post(`Tickets/state`, {
|
await axios.post(`Tickets/state`, {
|
||||||
ticketFk: row.ticketFk,
|
ticketFk: row.ticketFk,
|
||||||
code: row[field],
|
code,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -160,6 +164,11 @@ function onBuysFetched(data) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="States/editableStates"
|
||||||
|
@on-fetch="(data) => (editableStates = data)"
|
||||||
|
auto-load
|
||||||
|
/>
|
||||||
<FetchData
|
<FetchData
|
||||||
ref="fetchItemLack"
|
ref="fetchItemLack"
|
||||||
:url="`Tickets/itemLack`"
|
:url="`Tickets/itemLack`"
|
||||||
|
@ -309,12 +318,12 @@ function onBuysFetched(data) {
|
||||||
</template>
|
</template>
|
||||||
<template #column-alertLevelCode="props">
|
<template #column-alertLevelCode="props">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
url="States/editableStates"
|
:options="editableStates"
|
||||||
auto-load
|
auto-load
|
||||||
hide-selected
|
hide-selected
|
||||||
option-value="id"
|
option-value="name"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
v-model="props.row.alertLevelCode"
|
v-model="props.row.code"
|
||||||
v-on="getInputEvents(props)"
|
v-on="getInputEvents(props)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -19,18 +19,18 @@ const $props = defineProps({
|
||||||
const updateItem = async () => {
|
const updateItem = async () => {
|
||||||
try {
|
try {
|
||||||
showChangeItemDialog.value = true;
|
showChangeItemDialog.value = true;
|
||||||
const rowsToUpdate = $props.selectedRows.map(({ saleFk, quantity }) =>
|
const rowsToUpdate = $props.selectedRows.map(({ saleFk, ticketFk, quantity }) =>
|
||||||
axios.post(`Sales/replaceItem`, {
|
axios.post(`Sales/replaceItem`, {
|
||||||
saleFk,
|
saleFk,
|
||||||
|
ticketFk,
|
||||||
substitutionFk: newItem.value,
|
substitutionFk: newItem.value,
|
||||||
quantity,
|
quantity,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const result = await Promise.allSettled(rowsToUpdate);
|
const result = await Promise.allSettled(rowsToUpdate);
|
||||||
notifyResults(result, 'saleFk');
|
notifyResults(result, 'ticketFk');
|
||||||
emit('update-item', newItem.value);
|
emit('update-item', newItem.value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating item:', err);
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -41,6 +41,7 @@ const updateItem = async () => {
|
||||||
<QCardSection class="row items-center justify-center column items-stretch">
|
<QCardSection class="row items-center justify-center column items-stretch">
|
||||||
<span>{{ $t('negative.detail.modal.changeItem.title') }}</span>
|
<span>{{ $t('negative.detail.modal.changeItem.title') }}</span>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
|
data-cy="New item_select"
|
||||||
url="Items/WithName"
|
url="Items/WithName"
|
||||||
:fields="['id', 'name']"
|
:fields="['id', 'name']"
|
||||||
:sort-by="['id DESC']"
|
:sort-by="['id DESC']"
|
||||||
|
|
|
@ -19,9 +19,9 @@ const $props = defineProps({
|
||||||
const updateState = async () => {
|
const updateState = async () => {
|
||||||
try {
|
try {
|
||||||
showChangeStateDialog.value = true;
|
showChangeStateDialog.value = true;
|
||||||
const rowsToUpdate = $props.selectedRows.map(({ id }) =>
|
const rowsToUpdate = $props.selectedRows.map(({ ticketFk }) =>
|
||||||
axios.post(`Tickets/state`, {
|
axios.post(`Tickets/state`, {
|
||||||
ticketFk: id,
|
ticketFk,
|
||||||
code: newState.value,
|
code: newState.value,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -49,8 +49,9 @@ const updateState = async () => {
|
||||||
v-model="newState"
|
v-model="newState"
|
||||||
:options="editableStates"
|
:options="editableStates"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
option-value="code"
|
option-value="id"
|
||||||
autofocus
|
autofocus
|
||||||
|
data-cy="New state_select"
|
||||||
/>
|
/>
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
<QCardActions align="right">
|
<QCardActions align="right">
|
||||||
|
|
|
@ -14,8 +14,6 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
import VnRow from 'src/components/ui/VnRow.vue';
|
import VnRow from 'src/components/ui/VnRow.vue';
|
||||||
import TicketFilter from './TicketFilter.vue';
|
import TicketFilter from './TicketFilter.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
|
||||||
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||||
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
|
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
|
||||||
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
||||||
|
@ -25,6 +23,7 @@ import TicketProblems from 'src/components/TicketProblems.vue';
|
||||||
import VnSection from 'src/components/common/VnSection.vue';
|
import VnSection from 'src/components/common/VnSection.vue';
|
||||||
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
|
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
|
||||||
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
|
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
|
||||||
|
import TicketNewPayment from './components/TicketNewPayment.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -73,11 +72,6 @@ const initializeFromQuery = () => {
|
||||||
|
|
||||||
const selectedRows = ref([]);
|
const selectedRows = ref([]);
|
||||||
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
|
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
|
||||||
const showForm = ref(false);
|
|
||||||
const dialogData = ref();
|
|
||||||
const companiesOptions = ref([]);
|
|
||||||
const accountingOptions = ref([]);
|
|
||||||
const amountToReturn = ref();
|
|
||||||
const dataKey = 'TicketList';
|
const dataKey = 'TicketList';
|
||||||
const formInitialData = ref({});
|
const formInitialData = ref({});
|
||||||
|
|
||||||
|
@ -372,7 +366,11 @@ function openBalanceDialog(ticket) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isSameClient) {
|
if (!isSameClient) {
|
||||||
throw new Error('You cannot make a payment on account from multiple clients');
|
notify(
|
||||||
|
t('You cannot make a payment on account from multiple clients'),
|
||||||
|
'negative',
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let ticketData of checkedTickets) {
|
for (let ticketData of checkedTickets) {
|
||||||
|
@ -381,101 +379,48 @@ function openBalanceDialog(ticket) {
|
||||||
description.value.push(ticketData.id);
|
description.value.push(ticketData.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const balanceCreateDialog = ref({
|
const dialogData = ref({
|
||||||
amountPaid: amountPaid.value,
|
amountPaid: amountPaid.value,
|
||||||
clientFk: clientFk.value,
|
clientFk: clientFk.value,
|
||||||
description: `Albaran: ${description.value.join(', ')}`,
|
description: `Albaran: ${description.value.join(', ')}`,
|
||||||
});
|
});
|
||||||
dialogData.value = balanceCreateDialog;
|
quasar.dialog({
|
||||||
showForm.value = true;
|
component: TicketNewPayment,
|
||||||
}
|
componentProps: {
|
||||||
|
clientId: clientFk.value,
|
||||||
async function onSubmit() {
|
formData: dialogData.value,
|
||||||
const { data: email } = await axios.get('Clients', {
|
|
||||||
params: {
|
|
||||||
filter: JSON.stringify({ where: { id: dialogData.value.value.clientFk } }),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data } = await axios.post(
|
|
||||||
`Clients/${dialogData.value.value.clientFk}/createReceipt`,
|
|
||||||
{
|
|
||||||
payed: dialogData.value.payed,
|
|
||||||
companyFk: dialogData.value.companyFk,
|
|
||||||
bankFk: dialogData.value.bankFk,
|
|
||||||
amountPaid: dialogData.value.value.amountPaid,
|
|
||||||
description: dialogData.value.value.description,
|
|
||||||
clientFk: dialogData.value.value.clientFk,
|
|
||||||
email: email[0].email,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data) notify('globals.dataSaved', 'positive');
|
|
||||||
showForm.value = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setAmountToReturn = (newAmountGiven) => {
|
function exprBuilder(param, value) {
|
||||||
const amountPaid = dialogData.value.value.amountPaid;
|
switch (param) {
|
||||||
|
case 'stateFk':
|
||||||
amountToReturn.value = newAmountGiven - amountPaid;
|
return { 'ts.stateFk': value };
|
||||||
};
|
case 'provinceFk':
|
||||||
|
return { 'a.provinceFk': value };
|
||||||
function setReference(data) {
|
case 'hour':
|
||||||
let newDescription = '';
|
return { 'z.hour': value };
|
||||||
|
case 'shipped':
|
||||||
switch (data) {
|
return {
|
||||||
case 1:
|
't.shipped': {
|
||||||
newDescription = `${t(
|
between: this.dateRange(value),
|
||||||
'ticketList.creditCard',
|
},
|
||||||
)}, ${dialogData.value.value.description.replace(
|
};
|
||||||
/^(Credit Card, |Cash, |Transfers, )/,
|
case 'departmentFk':
|
||||||
'',
|
return { 'c.departmentFk': value };
|
||||||
)}`;
|
case 'id':
|
||||||
break;
|
case 'refFk':
|
||||||
case 2:
|
case 'zoneFk':
|
||||||
newDescription = `${t(
|
case 'nickname':
|
||||||
'ticketList.cash',
|
case 'agencyModeFk':
|
||||||
)}, ${dialogData.value.value.description.replace(
|
case 'warehouseFk':
|
||||||
/^(Credit Card, |Cash, |Transfers, )/,
|
return { [`t.${param}`]: value };
|
||||||
'',
|
|
||||||
)}`;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
newDescription = `${newDescription.replace(
|
|
||||||
/^(Credit Card, |Cash, |Transfers, )/,
|
|
||||||
'',
|
|
||||||
)}`;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
newDescription = `${t(
|
|
||||||
'ticketList.transfers',
|
|
||||||
)}, ${dialogData.value.value.description.replace(
|
|
||||||
/^(Credit Card, |Cash, |Transfers, )/,
|
|
||||||
'',
|
|
||||||
)}`;
|
|
||||||
break;
|
|
||||||
case 3317:
|
|
||||||
newDescription = '';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogData.value.value.description = newDescription;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
|
||||||
url="Companies"
|
|
||||||
@on-fetch="(data) => (companiesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Accountings"
|
|
||||||
@on-fetch="(data) => (accountingOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<VnSection
|
<VnSection
|
||||||
:data-key="dataKey"
|
:data-key="dataKey"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
@ -657,17 +602,14 @@ function setReference(data) {
|
||||||
</VnSelect>
|
</VnSelect>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow>
|
||||||
<div class="col">
|
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
placeholder="dd-mm-aaa"
|
placeholder="dd-mm-aaa"
|
||||||
:label="t('globals.landed')"
|
:label="t('globals.landed')"
|
||||||
v-model="data.landed"
|
v-model="data.landed"
|
||||||
@update:model-value="() => fetchAvailableAgencies(data)"
|
@update:model-value="() => fetchAvailableAgencies(data)"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow>
|
||||||
<div class="col">
|
|
||||||
<VnSelect
|
<VnSelect
|
||||||
url="Warehouses"
|
url="Warehouses"
|
||||||
:sort-by="['name']"
|
:sort-by="['name']"
|
||||||
|
@ -680,10 +622,8 @@ function setReference(data) {
|
||||||
}"
|
}"
|
||||||
@update:model-value="() => fetchAvailableAgencies(data)"
|
@update:model-value="() => fetchAvailableAgencies(data)"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<VnRow>
|
<VnRow>
|
||||||
<div class="col">
|
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('globals.agency')"
|
:label="t('globals.agency')"
|
||||||
v-model="data.agencyModeId"
|
v-model="data.agencyModeId"
|
||||||
|
@ -692,7 +632,6 @@ function setReference(data) {
|
||||||
option-label="agencyMode"
|
option-label="agencyMode"
|
||||||
hide-selected
|
hide-selected
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</VnRow>
|
</VnRow>
|
||||||
</template>
|
</template>
|
||||||
</VnTable>
|
</VnTable>
|
||||||
|
@ -722,99 +661,6 @@ function setReference(data) {
|
||||||
{{ t('ticketList.accountPayment') }}
|
{{ t('ticketList.accountPayment') }}
|
||||||
</QTooltip>
|
</QTooltip>
|
||||||
</QPageSticky>
|
</QPageSticky>
|
||||||
<QDialog ref="dialogRef" v-model="showForm">
|
|
||||||
<QCard class="q-pa-md q-mb-md">
|
|
||||||
<QForm @submit="onSubmit()" class="q-pa-sm">
|
|
||||||
{{ t('ticketList.addPayment') }}
|
|
||||||
<VnRow>
|
|
||||||
<VnInputDate
|
|
||||||
:label="t('ticketList.date')"
|
|
||||||
v-model="dialogData.payed"
|
|
||||||
/>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('ticketList.company')"
|
|
||||||
v-model="dialogData.companyFk"
|
|
||||||
:options="companiesOptions"
|
|
||||||
option-label="code"
|
|
||||||
hide-selected
|
|
||||||
>
|
|
||||||
</VnSelect>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('ticketList.bank')"
|
|
||||||
v-model="dialogData.bankFk"
|
|
||||||
:options="accountingOptions"
|
|
||||||
option-label="bank"
|
|
||||||
hide-selected
|
|
||||||
@update:model-value="setReference"
|
|
||||||
/>
|
|
||||||
<VnInput
|
|
||||||
:label="t('ticketList.amount')"
|
|
||||||
v-model="dialogData.value.amountPaid"
|
|
||||||
/>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow v-if="dialogData.bankFk === 2">
|
|
||||||
<span>
|
|
||||||
{{ t('ticketList.cash') }}
|
|
||||||
</span>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow v-if="dialogData.bankFk === 2">
|
|
||||||
<VnInput
|
|
||||||
:label="t('ticketList.deliveredAmount')"
|
|
||||||
v-model="dialogData.value.amountGiven"
|
|
||||||
@update:model-value="setAmountToReturn"
|
|
||||||
type="number"
|
|
||||||
/>
|
|
||||||
<VnInput
|
|
||||||
:label="t('ticketList.amountToReturn')"
|
|
||||||
:model-value="amountToReturn"
|
|
||||||
type="number"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow v-if="dialogData.bankFk === 3 || dialogData.bankFk === 3117">
|
|
||||||
<VnInput
|
|
||||||
:label="t('ticketList.compensation')"
|
|
||||||
v-model="dialogData.value.compensation"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow>
|
|
||||||
<VnInput
|
|
||||||
:label="t('ticketList.reference')"
|
|
||||||
v-model="dialogData.value.description"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</VnRow>
|
|
||||||
<VnRow v-if="dialogData.bankFk === 2">
|
|
||||||
<QCheckbox
|
|
||||||
:label="t('ticketList.viewReceipt')"
|
|
||||||
v-model="dialogData.value.viewReceipt"
|
|
||||||
:toggle-indeterminate="false"
|
|
||||||
/>
|
|
||||||
<QCheckbox
|
|
||||||
:label="t('ticketList.sendEmail')"
|
|
||||||
v-model="dialogData.value.senEmail"
|
|
||||||
:toggle-indeterminate="false"
|
|
||||||
/>
|
|
||||||
</VnRow>
|
|
||||||
<div class="q-mt-lg row justify-end">
|
|
||||||
<QBtn
|
|
||||||
:label="t('globals.save')"
|
|
||||||
color="primary"
|
|
||||||
@click="onSubmit()"
|
|
||||||
/>
|
|
||||||
<QBtn
|
|
||||||
flat
|
|
||||||
:label="t('globals.close')"
|
|
||||||
color="primary"
|
|
||||||
v-close-popup
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</QForm>
|
|
||||||
</QCard>
|
|
||||||
</QDialog>
|
|
||||||
<QPageSticky v-if="hasSelectedRows" :offset="[20, 200]" style="z-index: 2">
|
<QPageSticky v-if="hasSelectedRows" :offset="[20, 200]" style="z-index: 2">
|
||||||
<QBtn
|
<QBtn
|
||||||
@click="sendDocuware(selectedRows)"
|
@click="sendDocuware(selectedRows)"
|
||||||
|
@ -842,4 +688,5 @@ es:
|
||||||
Zone: Zona
|
Zone: Zona
|
||||||
New ticket: Nuevo ticket
|
New ticket: Nuevo ticket
|
||||||
Component lack: Faltan componentes
|
Component lack: Faltan componentes
|
||||||
|
You cannot make a payment on account from multiple clients: No puedes hacer un pago a cuenta de varios clientes
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -0,0 +1,324 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
|
||||||
|
import { usePrintService } from 'src/composables/usePrintService';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
|
import FormModelPopup from 'src/components/FormModelPopup.vue';
|
||||||
|
import VnRow from 'src/components/ui/VnRow.vue';
|
||||||
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
|
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
const { sendEmail, openReport } = usePrintService();
|
||||||
|
const { dialogRef } = useDialogPluginComponent();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
formData: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
clientId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
promise: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeButton = ref(null);
|
||||||
|
const viewReceipt = ref();
|
||||||
|
const shouldSendEmail = ref(false);
|
||||||
|
const maxAmount = ref();
|
||||||
|
const accountingType = ref({});
|
||||||
|
const isCash = ref(false);
|
||||||
|
const formModelRef = ref(false);
|
||||||
|
const amountToReturn = ref();
|
||||||
|
const filterBanks = {
|
||||||
|
fields: ['id', 'bank', 'accountingTypeFk'],
|
||||||
|
include: { relation: 'accountingType' },
|
||||||
|
order: 'id',
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = useState();
|
||||||
|
const user = state.getUser();
|
||||||
|
const originalDescription = ref('');
|
||||||
|
const initialData = ref({
|
||||||
|
...$props.formData,
|
||||||
|
companyFk: user.value.companyFk,
|
||||||
|
payed: Date.vnNew(),
|
||||||
|
originalDescription: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
function setPaymentType(data, accounting) {
|
||||||
|
if (!accounting) return;
|
||||||
|
|
||||||
|
data.bankFk = accounting.id;
|
||||||
|
accountingType.value = accounting.accountingType;
|
||||||
|
|
||||||
|
data.description = [];
|
||||||
|
isCash.value = accountingType.value.code == 'cash';
|
||||||
|
viewReceipt.value = isCash.value;
|
||||||
|
|
||||||
|
switch (accountingType.value.code) {
|
||||||
|
case 'compensation':
|
||||||
|
data.description.push($props.formData.description);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (
|
||||||
|
accountingType.value.receiptDescription != null &&
|
||||||
|
accountingType.value.receiptDescription != ''
|
||||||
|
) {
|
||||||
|
data.description.push(accountingType.value.receiptDescription);
|
||||||
|
}
|
||||||
|
const originalDescription =
|
||||||
|
data.originalDescription || $props.formData.description;
|
||||||
|
if (originalDescription) {
|
||||||
|
data.description.push(originalDescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.description = data.description.join(', ');
|
||||||
|
data.payed = Date.vnNew();
|
||||||
|
if (accountingType.value.daysInFuture) {
|
||||||
|
data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
maxAmount.value = accountingType.value && accountingType.value.maxAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateFromAmount = (event) => {
|
||||||
|
initialData.value.amountToReturn = Number(
|
||||||
|
(parseFloat(initialData.value.deliveredAmount) + parseFloat(event) * -1).toFixed(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateFromDeliveredAmount = (event) => {
|
||||||
|
amountToReturn.value = Number((event - initialData.value.amountPaid).toFixed(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
function onBeforeSave(data) {
|
||||||
|
const exceededAmount = data.amountPaid > maxAmount.value;
|
||||||
|
if (isCash.value && exceededAmount)
|
||||||
|
return notify(t('Amount exceeded', { maxAmount: maxAmount.value }), 'negative');
|
||||||
|
|
||||||
|
if (isCash.value && shouldSendEmail.value && !data.email)
|
||||||
|
return notify(t('There is no assigned email for this client'), 'negative');
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDataSaved({ email, id }) {
|
||||||
|
try {
|
||||||
|
if (shouldSendEmail.value && isCash.value)
|
||||||
|
await sendEmail(`Receipts/${id}/receipt-email`, {
|
||||||
|
recipient: email,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank');
|
||||||
|
} finally {
|
||||||
|
if ($props.promise) $props.promise();
|
||||||
|
if (closeButton.value) closeButton.value.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSupplierClientReferences(data) {
|
||||||
|
if (!data) return (initialData.value.description = '');
|
||||||
|
const params = { bankAccount: data.compensationAccount };
|
||||||
|
const { data: reference } = await axios(`Clients/getClientOrSupplierReference`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
if (reference.supplierId) {
|
||||||
|
data.description = t('Supplier Compensation Reference', {
|
||||||
|
supplierId: reference.supplierId,
|
||||||
|
supplierName: reference.supplierName,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.description = t('Client Compensation Reference', {
|
||||||
|
clientId: reference.clientId,
|
||||||
|
clientName: reference.clientName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAmountPaid() {
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
clientFk: $props.clientId,
|
||||||
|
companyFk: initialData.value.companyFk,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await getClientRisk(filter);
|
||||||
|
initialData.value.amountPaid = (data?.length && data[0].amount) || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmit(formData) {
|
||||||
|
const clientFk = $props.clientId;
|
||||||
|
const {
|
||||||
|
data: [{ email }],
|
||||||
|
} = await axios.get('Clients', {
|
||||||
|
params: {
|
||||||
|
filter: JSON.stringify({ where: { id: clientFk } }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await axios.post(`Clients/${clientFk}/createReceipt`, {
|
||||||
|
payed: formData.payed,
|
||||||
|
companyFk: formData.companyFk,
|
||||||
|
bankFk: formData.bankFk,
|
||||||
|
amountPaid: formData.amountPaid,
|
||||||
|
description: formData.description,
|
||||||
|
clientFk,
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data) notify('globals.dataSaved', 'positive');
|
||||||
|
await onDataSaved(data);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QDialog ref="dialogRef" persistent>
|
||||||
|
<FormModelPopup
|
||||||
|
ref="formModelRef"
|
||||||
|
:form-initial-data="initialData"
|
||||||
|
:save-fn="onSubmit"
|
||||||
|
:prevent-submit="true"
|
||||||
|
:mapper="onBeforeSave"
|
||||||
|
>
|
||||||
|
<template #form-inputs="{ data, validate }">
|
||||||
|
<h5 class="q-mt-none">{{ t('New payment') }}</h5>
|
||||||
|
<VnRow>
|
||||||
|
<VnSelect
|
||||||
|
autofocus
|
||||||
|
:label="t('Bank')"
|
||||||
|
v-model="data.bankFk"
|
||||||
|
url="Accountings"
|
||||||
|
:filter="filterBanks"
|
||||||
|
option-label="bank"
|
||||||
|
:include="{ relation: 'accountingType' }"
|
||||||
|
sort-by="id"
|
||||||
|
@update:model-value="
|
||||||
|
(value, options) => setPaymentType(data, value, options)
|
||||||
|
"
|
||||||
|
:emit-value="false"
|
||||||
|
data-cy="paymentBank"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>
|
||||||
|
{{ scope.opt.id }}: {{ scope.opt.bank }}
|
||||||
|
</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelect>
|
||||||
|
<VnInputNumber
|
||||||
|
:label="t('Amount')"
|
||||||
|
:required="true"
|
||||||
|
@update:model-value="calculateFromAmount($event)"
|
||||||
|
clearable
|
||||||
|
v-model.number="data.amountPaid"
|
||||||
|
data-cy="paymentAmount"
|
||||||
|
:positive="false"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
<VnRow>
|
||||||
|
<VnInputDate
|
||||||
|
:label="t('Date')"
|
||||||
|
v-model="data.payed"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
<VnSelect
|
||||||
|
url="Companies"
|
||||||
|
:label="t('Company')"
|
||||||
|
:required="true"
|
||||||
|
:rules="validate('entry.companyFk')"
|
||||||
|
hide-selected
|
||||||
|
option-label="code"
|
||||||
|
v-model="data.companyFk"
|
||||||
|
@update:model-value="getAmountPaid()"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
<div v-if="accountingType.code == 'compensation'">
|
||||||
|
<div class="text-h6">
|
||||||
|
{{ t('Compensation') }}
|
||||||
|
</div>
|
||||||
|
<VnRow>
|
||||||
|
<VnAccountNumber
|
||||||
|
:label="t('Compensation account')"
|
||||||
|
clearable
|
||||||
|
v-model="data.compensationAccount"
|
||||||
|
@blur="getSupplierClientReferences(data)"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
</div>
|
||||||
|
<VnInput
|
||||||
|
:label="t('Reference')"
|
||||||
|
:required="true"
|
||||||
|
clearable
|
||||||
|
v-model="data.description"
|
||||||
|
/>
|
||||||
|
<div v-if="accountingType.code == 'cash'">
|
||||||
|
<div class="text-h6">{{ t('Cash') }}</div>
|
||||||
|
<VnRow>
|
||||||
|
<VnInputNumber
|
||||||
|
:label="t('Delivered amount')"
|
||||||
|
@update:model-value="calculateFromDeliveredAmount($event)"
|
||||||
|
clearable
|
||||||
|
v-model="data.deliveredAmount"
|
||||||
|
/>
|
||||||
|
<VnInputNumber
|
||||||
|
:label="t('Amount to return')"
|
||||||
|
disable
|
||||||
|
v-model="amountToReturn"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
<VnRow>
|
||||||
|
<QCheckbox v-model="viewReceipt" :label="t('View recipt')" />
|
||||||
|
<QCheckbox v-model="shouldSendEmail" :label="t('Send email')" />
|
||||||
|
</VnRow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModelPopup>
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
|
||||||
|
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
|
||||||
|
es:
|
||||||
|
New payment: Añadir pago
|
||||||
|
Date: Fecha
|
||||||
|
Company: Empresa
|
||||||
|
Bank: Caja
|
||||||
|
Amount: Importe
|
||||||
|
Reference: Referencia
|
||||||
|
Cash: Efectivo
|
||||||
|
Delivered amount: Cantidad entregada
|
||||||
|
Amount to return: Cantidad a devolver
|
||||||
|
View recipt: Ver recibido
|
||||||
|
Send email: Enviar correo
|
||||||
|
Compensation: Compensación
|
||||||
|
Compensation account: Cuenta para compensar
|
||||||
|
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
|
||||||
|
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
|
||||||
|
There is no assigned email for this client: No hay correo asignado para este cliente
|
||||||
|
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {maxAmount}
|
||||||
|
</i18n>
|
|
@ -206,7 +206,6 @@ ticketList:
|
||||||
toLines: Go to lines
|
toLines: Go to lines
|
||||||
addressNickname: Address nickname
|
addressNickname: Address nickname
|
||||||
ref: Reference
|
ref: Reference
|
||||||
hour: Hour
|
|
||||||
rounding: Rounding
|
rounding: Rounding
|
||||||
noVerifiedData: No verified data
|
noVerifiedData: No verified data
|
||||||
warehouse: Warehouse
|
warehouse: Warehouse
|
||||||
|
@ -215,6 +214,8 @@ ticketList:
|
||||||
clientFrozen: Client frozen
|
clientFrozen: Client frozen
|
||||||
componentLack: Component lack
|
componentLack: Component lack
|
||||||
negative:
|
negative:
|
||||||
|
nextEntryFk: Next entry
|
||||||
|
nextEntryLanded: Next entry landed
|
||||||
hour: Hour
|
hour: Hour
|
||||||
id: Id Article
|
id: Id Article
|
||||||
longName: Article
|
longName: Article
|
||||||
|
@ -225,6 +226,7 @@ negative:
|
||||||
value: Negative
|
value: Negative
|
||||||
itemFk: Article
|
itemFk: Article
|
||||||
producer: Producer
|
producer: Producer
|
||||||
|
excludedDates: Excluded dates
|
||||||
warehouse: Warehouse
|
warehouse: Warehouse
|
||||||
warehouseFk: Warehouse
|
warehouseFk: Warehouse
|
||||||
category: Category
|
category: Category
|
||||||
|
|
|
@ -215,6 +215,8 @@ ticketList:
|
||||||
addressNickname: Alias consignatario
|
addressNickname: Alias consignatario
|
||||||
ref: Referencia
|
ref: Referencia
|
||||||
negative:
|
negative:
|
||||||
|
nextEntryLanded: F. Entrada
|
||||||
|
nextEntryFk: Entrada
|
||||||
hour: Hora
|
hour: Hora
|
||||||
id: Id Articulo
|
id: Id Articulo
|
||||||
longName: Artículo
|
longName: Artículo
|
||||||
|
@ -225,7 +227,8 @@ negative:
|
||||||
origen: Origen
|
origen: Origen
|
||||||
value: Negativo
|
value: Negativo
|
||||||
warehouseFk: Almacen
|
warehouseFk: Almacen
|
||||||
producer: Producer
|
producer: Productor
|
||||||
|
excludedDates: Fechas excluidas
|
||||||
category: Categoría
|
category: Categoría
|
||||||
categoryFk: Familia
|
categoryFk: Familia
|
||||||
typeFk: Familia
|
typeFk: Familia
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue