diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1f911d62..119f702f7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,100 @@
+# Version 24.24 - 2024-06-11
+
+### Added 🆕
+
+- feat: 6942 hashtag in key : value summary by:jgallego
+- feat: #6957: Rename FetchedTags instance tag by:Javier Segarra
+- feat: refactor template by:Javier Segarra
+- feat: refs #6600 Add option to add comment for photo motivation by:jorgep
+- feat: refs #6942 test e2e tobook & toUnbook by:jorgep
+- feat: refs #6942 to book summary button & reactive value by:jorgep
+- feat: refs #6942 to unbook by:jorgep
+- feat: refs #6942 url update by:jorgep
+- feat: refs #6942 use correct currency in InvoiceIn components by:jorgep
+- feat: refs #6942 vat rate total by:jorgep
+- feat: refs #7494 new icons (7494-icons) by:alexm
+- feat: refs #7494 new icons by:alexm
+- feat: refs #7542 drop space by:jorgep
+- feat: refs #7542 empty by:jorgep
+- fix: refs #6942 changes and new features by:jorgep
+- fix: style by:Javier Segarra
+- style: color transparent when is fetive by:Javier Segarra
+- style: fix color when is empty by:Javier Segarra
+- style: reset poc style (6957_refactorFetechedTags) by:Javier Segarra
+- style: reset poc style by:Javier Segarra
+- style updates by:Javier Segarra
+
+### Changed 📦
+
+- feat: refactor template by:Javier Segarra
+- perf: 6957 add color as new shared variable by:Javier Segarra
+- perf: 6957 change fetchedTags color by:Javier Segarra
+- perf: remove local tree variable by:Javier Segarra
+- refactor: add flat by:alexm
+- refactor: refs #6600 replace QInput to VnInput by:jorgep
+- refactor: refs #6652 improved defaulter section by:Jon
+- refactor: refs #6942 Fix getTotalAmount function to correctly calculate the total amount in InvoiceInDueDay.vue by:jorgep
+- refactor: refs #6942 new summary layout by:jorgep
+- refactor: refs #6942 store key & actions by:jorgep
+- refactor: refs #6942 summary by:jorgep
+- refactor: refs #6942 use router hook by:jorgep
+- refactor: refs #6942 WIP summary layout by:jorgep
+
+### Fixed 🛠️
+
+- fix: 9-12 by:Javier Segarra
+- fix: defaulter icon by:alexm
+- fix: refs #5186 validation by:jorgep
+- fix: refs #6095 add reFfk null on search by:pablone
+- fix: refs #6942 cardDescriptor use store if its popup or different source data by:jorgep
+- fix: refs #6942 changes and new features by:jorgep
+- fix: refs #6942 drop comments by:jorgep
+- fix: refs #6942 drop console by:jorgep
+- fix: refs #6942 drop console.log by:jorgep
+- fix: refs #6942 e2e test (origin/6942-warmfix-fixFormModel) by:jorgep
+- fix: refs #6942 e2e tests by:jorgep
+- fix: refs #6942 e2e tests by:jorgep
+- fix: refs #6942 fix emit on data saved by:jorgep
+- fix: refs #6942 fix emit on reset by:jorgep
+- fix: refs #6942 fix vncard by:jorgep
+- fix: refs #6942 formModel & CardDescriptor by:jorgep
+- fix: refs #6942 formModel watch changes & invoiceInCreate by:jorgep
+- fix: refs #6942 import by:jorgep
+- fix: refs #6942 reloading by:jorgep
+- fix: refs #6942 rollback by:jorgep
+- fix: refs #6942 selectable expense by:jorgep
+- fix: refs #6942 skip e2e tests by:jorgep
+- fix: refs #6942 table bottom highlight & drop isBooked field by:jorgep
+- fix: refs #6942 tests e2e by:jorgep
+- fix: refs #6942 tests & summary table spacing by:jorgep
+- fix: refs #6942 unit tests by:jorgep
+- fix: refs #6942 vnLocation by:jorgep
+- fix: refs #6942 wip: formModel by:jorgep
+- fix: refs #7542 use right panel by:jorgep
+- fix: searchbar redirect by:alexm
+- fix: style by:Javier Segarra
+- fix: WorkerCalendarItem by:Javier Segarra
+- mini fix by:wbuezas
+- refs #6111 clean code fix changes by:carlossa
+- refs #6111 fix merge, fix column by:carlossa
+- refs #6111 fix qtable, actions, scroll by:carlossa
+- refs #6111 fix routeList by:carlossa
+- refs #6111 fix sticky by:carlossa
+- refs #6111 fix trad remove logs by:carlossa
+- refs #6111 fix visibleColumns by:carlossa
+- refs #6111 routeList fix by:carlossa
+- refs #6332 fix calendar by:carlossa
+- refs #6332 fix colors by:carlossa
+- refs #6332 fix festive by:carlossa
+- refs #6820 fix BasicData Tickets by:carlossa
+- refs #6820 fix error front by:carlossa
+- refs #6820 fix traduction by:carlossa
+- refs #7391 fix textarea by:carlossa
+- refs #7396 fix summary by:carlossa
+- Search childs fix by:wbuezas
+- small fix by:wbuezas
+- style: fix color when is empty by:Javier Segarra
+
# Changelog
All notable changes to this project will be documented in this file.
@@ -11,6 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto
- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa
+- (Route) => Ahora se muestran todos los cmrs
## [2418.01]
diff --git a/Jenkinsfile b/Jenkinsfile
index 7cad5ef41..1766e3aea 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -94,7 +94,7 @@ pipeline {
sh 'quasar build'
script {
def packageJson = readJSON file: 'package.json'
- env.VERSION = packageJson.version
+ env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
}
dockerBuild()
}
@@ -106,7 +106,7 @@ pipeline {
steps {
script {
def packageJson = readJSON file: 'package.json'
- env.VERSION = packageJson.version
+ env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
}
withKubeConfig([
serverUrl: "$KUBERNETES_API",
diff --git a/changelog.sh b/changelog.sh
new file mode 100644
index 000000000..8cd7b4716
--- /dev/null
+++ b/changelog.sh
@@ -0,0 +1,34 @@
+features_types=(chore feat style)
+changes_types=(refactor perf)
+fix_types=(fix revert)
+file="CHANGELOG.md"
+file_tmp="temp_log.txt"
+file_current_tmp="temp_current_log.txt"
+
+setType(){
+ echo "### $1" >> $file_tmp
+ arr=("$@")
+ echo "" > $file_current_tmp
+ for i in "${arr[@]}"
+ do
+ git log --grep="$i" --oneline --no-merges --format="- %s %d by:%an" master..test >> $file_current_tmp
+ done
+ # remove duplicates
+ sort -o $file_current_tmp -u $file_current_tmp
+ cat $file_current_tmp >> $file_tmp
+ echo "" >> $file_tmp
+ # remove tmp current file
+ [ -e $file_current_tmp ] && rm $file_current_tmp
+}
+
+echo "# Version XX.XX - XXXX-XX-XX" >> $file_tmp
+echo "" >> $file_tmp
+
+setType "Added 🆕" "${features_types[@]}"
+setType "Changed 📦" "${changes_types[@]}"
+setType "Fixed 🛠️" "${fix_types[@]}"
+
+cat $file >> $file_tmp
+mv $file_tmp $file
+
+
diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue
index 377708143..426d99b9a 100644
--- a/src/components/FormModel.vue
+++ b/src/components/FormModel.vue
@@ -12,6 +12,7 @@ import SkeletonForm from 'components/ui/SkeletonForm.vue';
import VnConfirm from './ui/VnConfirm.vue';
import { tMobile } from 'src/composables/tMobile';
import { useArrayData } from 'src/composables/useArrayData';
+import { useRoute } from 'vue-router';
const { push } = useRouter();
const quasar = useQuasar();
@@ -20,6 +21,7 @@ const stateStore = useStateStore();
const { t } = useI18n();
const { validate } = useValidator();
const { notify } = useNotify();
+const route = useRoute();
const $props = defineProps({
url: {
@@ -28,7 +30,7 @@ const $props = defineProps({
},
model: {
type: String,
- default: '',
+ default: null,
},
filter: {
type: Object,
@@ -82,17 +84,18 @@ const $props = defineProps({
description: 'It is used for redirect on click "save and continue"',
},
});
-
const emit = defineEmits(['onFetch', 'onDataSaved']);
-
+const modelValue = computed(
+ () => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`
+);
const componentIsRendered = ref(false);
-const arrayData = useArrayData($props.model);
+const arrayData = useArrayData(modelValue);
const isLoading = ref(false);
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
const isResetting = ref(false);
const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({});
-const formData = computed(() => state.get($props.model));
+const formData = computed(() => state.get(modelValue));
const formUrl = computed(() => $props.url);
const defaultButtons = computed(() => ({
save: {
@@ -114,7 +117,7 @@ onMounted(async () => {
nextTick(() => (componentIsRendered.value = true));
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
- state.set($props.model, $props.formInitialData);
+ state.set(modelValue, $props.formInitialData);
if ($props.autoLoad && !$props.formInitialData && $props.url) await fetch();
else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
@@ -161,8 +164,8 @@ onBeforeRouteLeave((to, from, next) => {
onUnmounted(() => {
// Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas.
- if (hasChanges.value) return state.set($props.model, originalData.value);
- if ($props.clearStoreOnUnmount) state.unset($props.model);
+ if (hasChanges.value) return state.set(modelValue, originalData.value);
+ if ($props.clearStoreOnUnmount) state.unset(modelValue);
});
async function fetch() {
@@ -174,7 +177,7 @@ async function fetch() {
updateAndEmit('onFetch', data);
} catch (e) {
- state.set($props.model, {});
+ state.set(modelValue, {});
originalData.value = {};
}
}
@@ -235,11 +238,11 @@ function filter(value, update, filterOptions) {
}
function updateAndEmit(evt, val, res) {
- state.set($props.model, val);
+ state.set(modelValue, val);
originalData.value = val && JSON.parse(JSON.stringify(val));
if (!$props.url) arrayData.store.data = val;
- emit(evt, state.get($props.model), res);
+ emit(evt, state.get(modelValue), res);
}
defineExpose({ save, isLoading, hasChanges });
diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue
index 6144f975d..c84a55122 100644
--- a/src/components/common/VnInput.vue
+++ b/src/components/common/VnInput.vue
@@ -13,6 +13,10 @@ const $props = defineProps({
type: Boolean,
default: false,
},
+ info: {
+ type: String,
+ default: '',
+ },
});
const { t } = useI18n();
@@ -83,6 +87,11 @@ const inputRules = [
v-if="hover && value && !$attrs.disabled"
@click="value = null"
>
+
+
+ {{ info }}
+
+
diff --git a/src/components/ui/CardList.vue b/src/components/ui/CardList.vue
index e8392b13e..c9b062457 100644
--- a/src/components/ui/CardList.vue
+++ b/src/components/ui/CardList.vue
@@ -28,7 +28,7 @@ const toggleCardCheck = (item) => {
{{ $props.title }}
-
+
{{ t('ID') }}: {{ $props.id }}
diff --git a/src/composables/useAcl.js b/src/composables/useAcl.js
new file mode 100644
index 000000000..46aaa3c25
--- /dev/null
+++ b/src/composables/useAcl.js
@@ -0,0 +1,33 @@
+import axios from 'axios';
+import { useState } from './useState';
+
+export function useAcl() {
+ const state = useState();
+
+ async function fetch() {
+ const { data } = await axios.get('VnUsers/acls');
+ const acls = {};
+ data.forEach((acl) => {
+ acls[acl.model] = acls[acl.model] || {};
+ acls[acl.model][acl.property] = acls[acl.model][acl.property] || {};
+ acls[acl.model][acl.property][acl.accessType] = true;
+ });
+
+ state.setAcls(acls);
+ }
+
+ function hasAny(model, prop, accessType) {
+ const acls = state.getAcls().value[model];
+ if (acls)
+ return ['*', prop].some((key) => {
+ const acl = acls[key];
+ return acl && (acl['*'] || acl[accessType]);
+ });
+ }
+
+ return {
+ fetch,
+ hasAny,
+ state,
+ };
+}
diff --git a/src/composables/useSession.js b/src/composables/useSession.js
index 56bce0279..ca2abef00 100644
--- a/src/composables/useSession.js
+++ b/src/composables/useSession.js
@@ -1,5 +1,6 @@
import { useState } from './useState';
import { useRole } from './useRole';
+import { useAcl } from './useAcl';
import { useUserConfig } from './useUserConfig';
import axios from 'axios';
import useNotify from './useNotify';
@@ -88,6 +89,7 @@ export function useSession() {
setSession(data);
await useRole().fetch();
+ await useAcl().fetch();
await useUserConfig().fetch();
await useTokenConfig().fetch();
diff --git a/src/composables/useState.js b/src/composables/useState.js
index f20209494..c2ac1740c 100644
--- a/src/composables/useState.js
+++ b/src/composables/useState.js
@@ -15,6 +15,7 @@ if (sessionStorage.getItem('user'))
user.value = JSON.parse(sessionStorage.getItem('user'));
const roles = ref([]);
+const acls = ref([]);
const tokenConfig = ref({});
const drawer = ref(true);
const headerMounted = ref(false);
@@ -42,6 +43,14 @@ export function useState() {
function setRoles(data) {
roles.value = data;
}
+
+ function getAcls() {
+ return computed(() => acls.value);
+ }
+
+ function setAcls(data) {
+ acls.value = data;
+ }
function getTokenConfig() {
return computed(() => {
return tokenConfig.value;
@@ -69,6 +78,8 @@ export function useState() {
setUser,
getRoles,
setRoles,
+ getAcls,
+ setAcls,
getTokenConfig,
setTokenConfig,
set,
diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index 8ccdf640f..5ef3a5472 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -396,6 +396,7 @@ entry:
type: Type
color: Color
id: ID
+ printedStickers: Printed stickers
notes:
observationType: Observation type
descriptor:
@@ -472,6 +473,7 @@ ticket:
agency: Agency
zone: Zone
warehouse: Warehouse
+ collection: Collection
route: Route
invoice: Invoice
shipped: Shipped
@@ -581,6 +583,9 @@ claim:
created: Created
state: State
pickup: Pick up
+ null: No
+ agency: Agency
+ delivery: Delivery
photo:
fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}'
noData: 'There are no images/videos, click here or drag and drop the file'
@@ -962,7 +967,7 @@ roadmap:
route:
pageTitles:
routes: Routes
- cmrsList: External CMRs list
+ cmrsList: CMRs list
RouteList: List
routeCreate: New route
basicData: Basic Data
@@ -1177,6 +1182,7 @@ item:
available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary
+ producer: Producer
list:
id: Identifier
grouping: Grouping
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 452421343..cfadd57b3 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -394,6 +394,7 @@ entry:
type: Tipo
color: Color
id: ID
+ printedStickers: Etiquetas impresas
notes:
observationType: Tipo de observación
descriptor:
@@ -470,6 +471,7 @@ ticket:
agency: Agencia
zone: Zona
warehouse: Almacén
+ collection: Colección
route: Ruta
invoice: Factura
shipped: Enviado
@@ -950,7 +952,7 @@ roadmap:
route:
pageTitles:
routes: Rutas
- cmrsList: Listado de CMRs externos
+ cmrsList: Listado de CMRs
RouteList: Listado
routeCreate: Nueva ruta
basicData: Datos básicos
@@ -1166,6 +1168,7 @@ item:
available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta
+ producer: Productor
list:
id: Identificador
grouping: Grouping
diff --git a/src/pages/Account/AccountAccounts.vue b/src/pages/Account/AccountAccounts.vue
new file mode 100644
index 000000000..3d7dda899
--- /dev/null
+++ b/src/pages/Account/AccountAccounts.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+es:
+ Roles synchronized!: ¡Roles sincronizados!
+ Synchronizing in the background: Sincronizando en segundo plano
+
diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue
index 82d171ce7..bd7f0f9ae 100644
--- a/src/pages/Account/AccountAcls.vue
+++ b/src/pages/Account/AccountAcls.vue
@@ -8,12 +8,12 @@ import VnSearchbar from 'components/ui/VnSearchbar.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import AclFilter from './Acls/AclFilter.vue';
+import AclFormView from './Acls/AclFormView.vue';
import { useVnConfirm } from 'composables/useVnConfirm';
import { useStateStore } from 'stores/useStateStore';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
-import AclFormView from './Acls/AclFormView.vue';
defineProps({
id: {
diff --git a/src/pages/Account/AccountConnections.vue b/src/pages/Account/AccountConnections.vue
new file mode 100644
index 000000000..98208e5f2
--- /dev/null
+++ b/src/pages/Account/AccountConnections.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ killSession(row.id)
+ )
+ "
+ outline
+ />
+
+
+
+
+
+
+
+ {{ t('connections.refresh') }}
+
+
+
+
+
+
+es:
+ Session killed: Sesión matada
+ Session will be killed: Se va a matar la sesión
+ Are you sure you want to continue?: ¿Seguro que quieres continuar?
+
diff --git a/src/pages/Account/AccountLdap.vue b/src/pages/Account/AccountLdap.vue
new file mode 100644
index 000000000..77c4d89f8
--- /dev/null
+++ b/src/pages/Account/AccountLdap.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+ {{ t('ldap.testConnection') }}
+
+
+
+
+
+
+ (hasData = $event)"
+ :toggle-indeterminate="false"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+es:
+ LDAP connection established!: ¡Conexión con LDAP establecida!
+
diff --git a/src/pages/Account/AccountSamba.vue b/src/pages/Account/AccountSamba.vue
new file mode 100644
index 000000000..25428a674
--- /dev/null
+++ b/src/pages/Account/AccountSamba.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+ {{ t('samba.testConnection') }}
+
+
+
+
+
+
+ (hasData = $event)"
+ :toggle-indeterminate="false"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+es:
+ Samba connection established!: ¡Conexión con LDAP establecida!
+
diff --git a/src/pages/Account/Alias/Card/AliasBasicData.vue b/src/pages/Account/Alias/Card/AliasBasicData.vue
index 035ba0e8b..ba940cda5 100644
--- a/src/pages/Account/Alias/Card/AliasBasicData.vue
+++ b/src/pages/Account/Alias/Card/AliasBasicData.vue
@@ -1,11 +1,9 @@
diff --git a/src/pages/Account/locale/en.yml b/src/pages/Account/locale/en.yml
index babedae70..dca9b45d9 100644
--- a/src/pages/Account/locale/en.yml
+++ b/src/pages/Account/locale/en.yml
@@ -67,6 +67,7 @@ ldap:
groupDN: Group DN
testConnection: Test connection
success: LDAP connection established!
+ password: Password
samba:
enableSync: Enable synchronization
domainController: Domain controller
@@ -78,6 +79,21 @@ samba:
verifyCertificate: Verify certificate
testConnection: Test connection
success: Samba connection established!
+accounts:
+ homedir: Homedir base
+ shell: Shell
+ idBase: User and role base id
+ min: Min
+ max: Max
+ warn: Warn
+ inact: Inact
+ syncAll: Synchronize all
+ syncRoles: Synchronize roles
+connections:
+ refresh: Refresh
+ username: Username
+ created: Created
+ killSession: Kill session
acls:
role: Role
accessType: Access type
diff --git a/src/pages/Account/locale/es.yml b/src/pages/Account/locale/es.yml
index 36125f361..896cc8ea9 100644
--- a/src/pages/Account/locale/es.yml
+++ b/src/pages/Account/locale/es.yml
@@ -70,6 +70,7 @@ mailAlias:
name: Nombre
isPublic: Público
ldap:
+ password: Contraseña
enableSync: Habilitar sincronización
server: Servidor
rdn: RDN
@@ -86,9 +87,24 @@ samba:
userAD: Usuario AD
passwordAD: Contraseña AD
domainPart: DN usuarios (sin la parte del dominio)
- Verify certificate: Verificar certificado
+ verifyCertificate: Verificar certificado
testConnection: Probar conexión
success: ¡Conexión con Samba establecida!
+accounts:
+ homedir: Directorio base para carpetas de usuario
+ shell: Intérprete de línea de comandos
+ idBase: Id base usuarios y roles
+ min: Min
+ max: Max
+ warn: Warn
+ inact: Inact
+ syncAll: Sincronizar todo
+ syncRoles: Sincronizar roles
+connections:
+ refresh: Actualizar
+ username: Nombre de usuario
+ created: Creado
+ killSession: Matar sesión
acls:
role: Rol
accessType: Tipo de acceso
diff --git a/src/pages/Customer/Card/CustomerBalance.vue b/src/pages/Customer/Card/CustomerBalance.vue
index 0886383de..02f230c03 100644
--- a/src/pages/Customer/Card/CustomerBalance.vue
+++ b/src/pages/Customer/Card/CustomerBalance.vue
@@ -234,7 +234,7 @@ const showBalancePdf = (balance) => {
{{ row.userName }}
-
+
diff --git a/src/pages/Entry/Card/EntryBuys.vue b/src/pages/Entry/Card/EntryBuys.vue
index a378266aa..b08c40810 100644
--- a/src/pages/Entry/Card/EntryBuys.vue
+++ b/src/pages/Entry/Card/EntryBuys.vue
@@ -82,6 +82,16 @@ const tableColumnComponents = computed(() => ({
},
event: getInputEvents,
},
+ printedStickers: {
+ component: VnInput,
+ props: {
+ type: 'number',
+ min: 0,
+ class: 'input-number',
+ dense: true,
+ },
+ event: getInputEvents,
+ },
weight: {
component: VnInput,
props: {
@@ -147,7 +157,7 @@ const entriesTableColumns = computed(() => {
return [
{
label: t('entry.summary.item'),
- field: 'id',
+ field: 'itemFk',
name: 'item',
align: 'left',
},
@@ -169,6 +179,12 @@ const entriesTableColumns = computed(() => {
name: 'stickers',
align: 'left',
},
+ {
+ label: t('entry.buys.printedStickers'),
+ field: 'printedStickers',
+ name: 'printedStickers',
+ align: 'left',
+ },
{
label: t('entry.summary.weight'),
field: 'weight',
@@ -216,7 +232,6 @@ const entriesTableColumns = computed(() => {
});
const copyOriginalRowsData = (rows) => {
- // el objetivo de esto es guardar los valores iniciales de todas las rows para evitar guardar cambios si la data no cambió al disparar los eventos
originalRowDataCopy.value = JSON.parse(JSON.stringify(rows));
};
@@ -386,19 +401,16 @@ const lockIconType = (groupingMode, mode) => {
-
+
-
+
{{ props.row.item.itemType.code }}
-
- {{ props.row.item.id }}
-
{{ props.row.item.size }}
@@ -413,10 +425,6 @@ const lockIconType = (groupingMode, mode) => {
-
-
-
-
@@ -466,11 +474,13 @@ const lockIconType = (groupingMode, mode) => {
diff --git a/src/pages/Item/Card/ItemDescriptor.vue b/src/pages/Item/Card/ItemDescriptor.vue
index 305d29b8b..155c9eb4c 100644
--- a/src/pages/Item/Card/ItemDescriptor.vue
+++ b/src/pages/Item/Card/ItemDescriptor.vue
@@ -16,6 +16,7 @@ import useCardDescription from 'src/composables/useCardDescription';
import { useSession } from 'src/composables/useSession';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
+import { dashIfEmpty } from 'src/filters';
const $props = defineProps({
id: {
@@ -182,6 +183,10 @@ const openCloneDialog = async () => {
+
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
-
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
+import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n();
const props = defineProps({
@@ -16,10 +16,11 @@ const props = defineProps({
});
const countries = ref();
+const warehouses = ref();
-
(countries = data)" auto-load />
+ (warehouses = data)" auto-load />
@@ -93,13 +94,13 @@ const countries = ref();
-
-
+
-
+
+
+
+
+
-
en:
params:
diff --git a/src/pages/Route/Cmr/CmrList.vue b/src/pages/Route/Cmr/CmrList.vue
index 154e49977..cbfc3751a 100644
--- a/src/pages/Route/Cmr/CmrList.vue
+++ b/src/pages/Route/Cmr/CmrList.vue
@@ -14,6 +14,7 @@ const { t } = useI18n();
const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia();
const selected = ref([]);
+const warehouses = ref([]);
const columns = computed(() => [
{
@@ -63,6 +64,13 @@ const columns = computed(() => [
sortable: true,
headerStyle: 'padding-left: 33px',
},
+ {
+ name: 'warehouseFk',
+ label: t('globals.warehouse'),
+ field: ({ warehouseFk }) => warehouseFk,
+ align: 'center',
+ sortable: true,
+ },
{
name: 'icons',
align: 'center',
@@ -99,7 +107,7 @@ function downloadPdfs() {
+
+
+ {{ warehouses.find(({ id }) => id === value)?.name }}
+
+
diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue
index f55669619..0715cf7ba 100644
--- a/src/pages/Ticket/Card/TicketSummary.vue
+++ b/src/pages/Ticket/Card/TicketSummary.vue
@@ -35,6 +35,7 @@ const ticket = ref();
const salesLines = ref(null);
const editableStates = ref([]);
const ticketUrl = ref();
+const grafanaUrl = 'https://grafana.verdnatura.es';
onMounted(async () => {
ticketUrl.value = (await getUrl('ticket/')) + entityId.value + '/';
@@ -159,6 +160,20 @@ async function changeState(value) {
:label="t('ticket.summary.warehouse')"
:value="ticket.warehouse?.name"
/>
+
+
+
+ {{ ticket.ticketCollections[0]?.collectionFk }}
+
+
+
@@ -487,4 +502,7 @@ async function changeState(value) {
.fetched-tags {
max-width: 70%;
}
+.grafana {
+ color: $primary-light;
+}
diff --git a/src/pages/Worker/Card/WorkerChangePasswordForm.vue b/src/pages/Worker/Card/WorkerChangePasswordForm.vue
new file mode 100644
index 000000000..ef75ba55a
--- /dev/null
+++ b/src/pages/Worker/Card/WorkerChangePasswordForm.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+es:
+ Reset password: Restablecer contraseña
+ New password: Nueva contraseña
+ Repeat password: Repetir contraseña
+ You must enter a new password: Debes introducir la nueva contraseña
+ Passwords don't match: Las contraseñas no coinciden
+
diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue
index daa290ea1..86c846aca 100644
--- a/src/pages/Worker/Card/WorkerDescriptor.vue
+++ b/src/pages/Worker/Card/WorkerDescriptor.vue
@@ -6,8 +6,10 @@ import { useSession } from 'src/composables/useSession';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
+import WorkerChangePasswordForm from 'src/pages/Worker/Card/WorkerChangePasswordForm.vue';
import useCardDescription from 'src/composables/useCardDescription';
import { useState } from 'src/composables/useState';
+import axios from 'axios';
const $props = defineProps({
id: {
@@ -25,12 +27,16 @@ const route = useRoute();
const { t } = useI18n();
const { getTokenMultimedia } = useSession();
const state = useState();
+const user = state.getUser();
+const changePasswordFormDialog = ref(null);
+const cardDescriptorRef = ref(null);
const entityId = computed(() => {
return $props.id || route.params.id;
});
const worker = ref();
+const workerExcluded = ref(false);
const filter = {
include: [
{
@@ -71,14 +77,44 @@ function getWorkerAvatar() {
const token = getTokenMultimedia();
return `/api/Images/user/160x160/${entityId.value}/download?access_token=${token}`;
}
+
const data = ref(useCardDescription());
const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.user.nickname, entity.id);
};
+
+const openChangePasswordForm = () => changePasswordFormDialog.value.show();
+
+const getIsExcluded = async () => {
+ try {
+ const { data } = await axios.get(
+ `WorkerDisableExcludeds/${entityId.value}/exists`
+ );
+ if (!data) return;
+ workerExcluded.value = data.exists;
+ } catch (err) {
+ console.error('Error getting worker excluded: ', err);
+ }
+};
+
+const handleExcluded = async () => {
+ if (workerExcluded.value)
+ await axios.delete(`WorkerDisableExcludeds/${entityId.value}`);
+ else
+ await axios.post(`WorkerDisableExcludeds`, {
+ workerFk: entityId.value,
+ dated: new Date(),
+ });
+
+ workerExcluded.value = !workerExcluded.value;
+};
+
+const refetch = async () => await cardDescriptorRef.value.getData();
{
(data) => {
worker = data;
setData(data);
+ getIsExcluded();
}
"
>
+
+
+
+ {{
+ workerExcluded
+ ? t('Click to allow the user to be disabled')
+ : t('Click to exclude the user from getting disabled')
+ }}
+
+
+
+
+ {{ t('Change password') }}
+
+
+
+
+
+
@@ -139,3 +200,10 @@ const setData = (entity) => {
height: 256px;
}
+
+
+es:
+ Click to allow the user to be disabled: Marcar para deshabilitar
+ Click to exclude the user from getting disabled: Marcar para no deshabilitar
+ Change password: Cambiar contraseña
+
diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue
index d9a650d9a..f466c4f9a 100644
--- a/src/pages/Worker/Card/WorkerTimeControl.vue
+++ b/src/pages/Worker/Card/WorkerTimeControl.vue
@@ -460,7 +460,7 @@ onMounted(async () => {
style="margin-left: 1px"
/>
-
+
{
{
url-create="Workers/new"
model="worker"
:form-initial-data="formData"
- @on-data-saved="({ id }) => $router.push({ path: `/worker/${id}` })"
+ @on-data-saved="(__, { id }) => $router.push({ path: `/worker/${id}` })"
>
diff --git a/src/pages/Worker/WorkerDepartmentTree.vue b/src/pages/Worker/WorkerDepartmentTree.vue
index 34340b019..392539c86 100644
--- a/src/pages/Worker/WorkerDepartmentTree.vue
+++ b/src/pages/Worker/WorkerDepartmentTree.vue
@@ -6,12 +6,11 @@ import { useQuasar } from 'quasar';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
import CreateDepartmentChild from './CreateDepartmentChild.vue';
import axios from 'axios';
-import useNotify from 'src/composables/useNotify.js';
import { useRouter } from 'vue-router';
+import VnConfirm from 'src/components/ui/VnConfirm.vue';
const quasar = useQuasar();
const { t } = useI18n();
-const { notify } = useNotify();
const state = useState();
const router = useRouter();
@@ -62,25 +61,28 @@ const removeNode = (node) => {
const { id, parentFk } = node;
quasar
.dialog({
- title: t('Are you sure you want to delete it?'),
- message: t('Delete department'),
- ok: {
- push: true,
- color: 'primary',
+ component: VnConfirm,
+ componentProps: {
+ title: t('Are you sure you want to delete it?'),
+ message: t('Delete department'),
+ promise: () => remove(id),
},
- cancel: true,
})
- .onOk(async () => {
- try {
- await axios.post(`/Departments/${id}/removeChild`, id);
- notify(t('department.departmentRemoved'), 'positive');
- await fetchNodeLeaves(parentFk);
- } catch (err) {
- console.error('Error removing department');
- }
- });
+ .onOk(async () => await fetchNodeLeaves(parentFk));
};
+async function remove(id) {
+ try {
+ await axios.post(`/Departments/${id}/removeChild`, { id });
+ quasar.notify({
+ message: t('department.departmentRemoved'),
+ type: 'positive',
+ });
+ } catch (err) {
+ console.error('Error removing department');
+ }
+}
+
const showCreateNodeForm = (nodeId) => {
showCreateNodeFormVal.value = true;
creationNodeSelectedId.value = nodeId;
diff --git a/src/pages/Worker/locale/en.yml b/src/pages/Worker/locale/en.yml
new file mode 100644
index 000000000..f947b30e7
--- /dev/null
+++ b/src/pages/Worker/locale/en.yml
@@ -0,0 +1 @@
+passwordRequirements: 'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n'
diff --git a/src/pages/Worker/locale/es.yml b/src/pages/Worker/locale/es.yml
index f74cb4fdb..d2478535a 100644
--- a/src/pages/Worker/locale/es.yml
+++ b/src/pages/Worker/locale/es.yml
@@ -3,3 +3,4 @@ You can search by worker id or name: Puedes buscar por id o nombre del trabajado
Locker: Taquilla
Internal: Interno
External: Externo
+passwordRequirements: 'La contraseña debe tener al menos { length } caracteres de longitud, {nAlpha} caracteres alfabéticos, {nUpper} letras mayúsculas, {nDigits} dígitos y {nPunct} símbolos (Ej: $%&.)'
diff --git a/src/router/index.js b/src/router/index.js
index 7a0aedcae..41ff4c1da 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -13,6 +13,7 @@ import { useRole } from 'src/composables/useRole';
import { useUserConfig } from 'src/composables/useUserConfig';
import { toLowerCamel } from 'src/filters';
import { useTokenConfig } from 'src/composables/useTokenConfig';
+import { useAcl } from 'src/composables/useAcl';
const state = useState();
const session = useSession();
@@ -55,6 +56,7 @@ export default route(function (/* { store, ssrContext } */) {
const stateRoles = state.getRoles().value;
if (stateRoles.length === 0) {
await useRole().fetch();
+ await useAcl().fetch();
await useUserConfig().fetch();
await useTokenConfig().fetch();
}
diff --git a/src/router/modules/account.js b/src/router/modules/account.js
index bc95719ef..f325a8dcd 100644
--- a/src/router/modules/account.js
+++ b/src/router/modules/account.js
@@ -11,7 +11,16 @@ export default {
component: RouterView,
redirect: { name: 'AccountMain' },
menus: {
- main: ['AccountList', 'AccountAliasList', 'AccountRoles', 'AccountAcls'],
+ main: [
+ 'AccountList',
+ 'AccountAliasList',
+ 'AccountRoles',
+ 'AccountAccounts',
+ 'AccountLdap',
+ 'AccountSamba',
+ 'AccountAcls',
+ 'AccountConnections',
+ ],
card: [],
},
children: [
@@ -30,6 +39,15 @@ export default {
},
component: () => import('src/pages/Account/AccountList.vue'),
},
+ {
+ path: 'role-list',
+ name: 'AccountRoles',
+ meta: {
+ title: 'roles',
+ icon: 'group',
+ },
+ component: () => import('src/pages/Account/Role/AccountRoles.vue'),
+ },
{
path: 'alias-list',
name: 'AccountAliasList',
@@ -39,6 +57,45 @@ export default {
},
component: () => import('src/pages/Account/AccountAliasList.vue'),
},
+ {
+ path: 'connections',
+ name: 'AccountConnections',
+ meta: {
+ title: 'connections',
+ icon: 'check',
+ },
+ component: () => import('src/pages/Account/AccountConnections.vue'),
+ },
+ {
+ path: 'accounts',
+ name: 'AccountAccounts',
+ meta: {
+ title: 'accounts',
+ icon: 'accessibility',
+ roles: ['itManagement'],
+ },
+ component: () => import('src/pages/Account/AccountAccounts.vue'),
+ },
+ {
+ path: 'ldap',
+ name: 'AccountLdap',
+ meta: {
+ title: 'ldap',
+ icon: 'account_tree',
+ roles: ['itManagement'],
+ },
+ component: () => import('src/pages/Account/AccountLdap.vue'),
+ },
+ {
+ path: 'samba',
+ name: 'AccountSamba',
+ meta: {
+ title: 'samba',
+ icon: 'preview',
+ roles: ['itManagement'],
+ },
+ component: () => import('src/pages/Account/AccountSamba.vue'),
+ },
{
path: 'acls',
name: 'AccountAcls',
@@ -53,15 +110,6 @@ export default {
name: 'AccountAclForm',
component: () => import('src/pages/Account/Acls/AclFormView.vue'),
},
- {
- path: 'role-list',
- name: 'AccountRoles',
- meta: {
- title: 'roles',
- icon: 'group',
- },
- component: () => import('src/pages/Account/Role/AccountRoles.vue'),
- },
],
},
],
diff --git a/src/router/routes.js b/src/router/routes.js
index 8d5202dda..805eefb8c 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -10,6 +10,7 @@ import wagon from './modules/wagon';
import supplier from './modules/Supplier';
import travel from './modules/travel';
import department from './modules/department';
+import role from './modules/role';
import ItemType from './modules/itemType';
import shelving from 'src/router/modules/shelving';
import order from 'src/router/modules/order';
@@ -21,7 +22,6 @@ import zone from 'src/router/modules/zone';
import account from './modules/account';
import monitor from 'src/router/modules/monitor';
import mailAlias from './modules/mailAlias';
-import role from './modules/role';
const routes = [
{
diff --git a/test/vitest/__tests__/composables/useAcl.spec.js b/test/vitest/__tests__/composables/useAcl.spec.js
new file mode 100644
index 000000000..a2b44b5e7
--- /dev/null
+++ b/test/vitest/__tests__/composables/useAcl.spec.js
@@ -0,0 +1,88 @@
+import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest';
+import { axios, flushPromises } from 'app/test/vitest/helper';
+import { useAcl } from 'src/composables/useAcl';
+
+describe('useAcl', () => {
+ const acl = useAcl();
+ const mockAcls = [
+ {
+ model: 'Address',
+ property: '*',
+ accessType: '*',
+ permission: 'ALLOW',
+ principalType: 'ROLE',
+ principalId: 'employee',
+ },
+ {
+ model: 'Worker',
+ property: 'holidays',
+ accessType: 'READ',
+ permission: 'ALLOW',
+ principalType: 'ROLE',
+ principalId: 'employee',
+ },
+ {
+ model: 'Url',
+ property: 'getByUser',
+ accessType: 'READ',
+ permission: 'ALLOW',
+ principalType: 'ROLE',
+ principalId: '$everyone',
+ },
+ {
+ model: 'TpvTransaction',
+ property: 'start',
+ accessType: 'WRITE',
+ permission: 'ALLOW',
+ principalType: 'ROLE',
+ principalId: '$authenticated',
+ },
+ ];
+
+ beforeAll(async () => {
+ vi.spyOn(axios, 'get').mockResolvedValue({ data: mockAcls });
+ await acl.fetch();
+ });
+
+ afterAll(async () => await flushPromises());
+
+ describe('hasAny', () => {
+ it('should return false if no roles matched', async () => {
+ expect(acl.hasAny('Worker', 'updateAttributes', 'WRITE')).toBeFalsy();
+ });
+
+ it('should return false if no roles matched', async () => {
+ expect(acl.hasAny('Worker', 'holidays', 'READ')).toBeTruthy();
+ });
+
+ describe('*', () => {
+ it('should return true if an acl matched', async () => {
+ expect(acl.hasAny('Address', '*', 'WRITE')).toBeTruthy();
+ });
+
+ it('should return false if no acls matched', async () => {
+ expect(acl.hasAny('Worker', '*', 'READ')).toBeFalsy();
+ });
+ });
+
+ describe('$authenticated', () => {
+ it('should return false if no acls matched', async () => {
+ expect(acl.hasAny('Url', 'getByUser', '*')).toBeFalsy();
+ });
+
+ it('should return true if an acl matched', async () => {
+ expect(acl.hasAny('Url', 'getByUser', 'READ')).toBeTruthy();
+ });
+ });
+
+ describe('$everyone', () => {
+ it('should return false if no acls matched', async () => {
+ expect(acl.hasAny('TpvTransaction', 'start', 'READ')).toBeFalsy();
+ });
+
+ it('should return false if an acl matched', async () => {
+ expect(acl.hasAny('TpvTransaction', 'start', 'WRITE')).toBeTruthy();
+ });
+ });
+ });
+});
diff --git a/test/vitest/__tests__/composables/useSession.spec.js b/test/vitest/__tests__/composables/useSession.spec.js
index 2292859a9..831acbf18 100644
--- a/test/vitest/__tests__/composables/useSession.spec.js
+++ b/test/vitest/__tests__/composables/useSession.spec.js
@@ -1,5 +1,5 @@
import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest';
-import { axios, flushPromises } from 'app/test/vitest/helper';
+import { axios } from 'app/test/vitest/helper';
import { useSession } from 'composables/useSession';
import { useState } from 'composables/useState';
@@ -87,13 +87,17 @@ describe('session', () => {
},
},
];
+ beforeEach(() => {
+ vi.spyOn(axios, 'get').mockImplementation((url) => {
+ if (url === 'VnUsers/acls') return Promise.resolve({ data: [] });
+ return Promise.resolve({
+ data: { roles: rolesData, user: expectedUser },
+ });
+ });
+ });
it('should fetch the user roles and then set token in the sessionStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
- vi.spyOn(axios, 'get').mockResolvedValue({
- data: { roles: rolesData, user: expectedUser },
- });
-
const expectedToken = 'mySessionToken';
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
const keepLogin = false;
@@ -117,10 +121,6 @@ describe('session', () => {
it('should fetch the user roles and then set token in the localStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
- vi.spyOn(axios, 'get').mockResolvedValue({
- data: { roles: rolesData, user: expectedUser },
- });
-
const expectedToken = 'myLocalToken';
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
const keepLogin = true;
diff --git a/test/vitest/__tests__/pages/Login/Login.spec.js b/test/vitest/__tests__/pages/Login/Login.spec.js
index 6e2de9870..9b9968736 100644
--- a/test/vitest/__tests__/pages/Login/Login.spec.js
+++ b/test/vitest/__tests__/pages/Login/Login.spec.js
@@ -23,8 +23,9 @@ describe('Login', () => {
},
};
vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } });
- vi.spyOn(axios, 'get').mockResolvedValue({
- data: { roles: [], user: expectedUser , multimediaToken: {id:'multimediaToken' }},
+ vi.spyOn(axios, 'get').mockImplementation((url) => {
+ if (url === 'VnUsers/acls') return Promise.resolve({ data: [] });
+ return Promise.resolve({data: { roles: [], user: expectedUser , multimediaToken: {id:'multimediaToken' }}});
});
vi.spyOn(vm.quasar, 'notify');