diff --git a/CHANGELOG.md b/CHANGELOG.md
index 250aa01a2..778d04638 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- (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/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/common/VnRadio.vue b/src/components/common/VnRadio.vue
new file mode 100644
index 000000000..4eeb9dbe9
--- /dev/null
+++ b/src/components/common/VnRadio.vue
@@ -0,0 +1,6 @@
+
+
+
+
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/components/ui/VnSubToolbar.vue b/src/components/ui/VnSubToolbar.vue
index c0d129613..8c86c056a 100644
--- a/src/components/ui/VnSubToolbar.vue
+++ b/src/components/ui/VnSubToolbar.vue
@@ -18,7 +18,7 @@ onMounted(() => {
const observer = new MutationObserver(
() =>
(hasContent.value =
- actions.value.childNodes.length + data.value.childNodes.length)
+ actions.value?.childNodes?.length + data.value?.childNodes?.length)
);
if (actions.value) observer.observe(actions.value, opts);
if (data.value) observer.observe(data.value, opts);
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 e672a16e9..4c4ab848f 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -962,7 +962,7 @@ roadmap:
route:
pageTitles:
routes: Routes
- cmrsList: External CMRs list
+ cmrsList: CMRs list
RouteList: List
routeCreate: New route
basicData: Basic Data
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index a7f599edb..cb3b3f590 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -950,7 +950,7 @@ roadmap:
route:
pageTitles:
routes: Rutas
- cmrsList: Listado de CMRs externos
+ cmrsList: Listado de CMRs
RouteList: Listado
routeCreate: Nueva ruta
basicData: Datos básicos
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/locale/en.yml b/src/pages/Account/locale/en.yml
index babedae70..bbc1da69e 100644
--- a/src/pages/Account/locale/en.yml
+++ b/src/pages/Account/locale/en.yml
@@ -78,6 +78,11 @@ samba:
verifyCertificate: Verify certificate
testConnection: Test connection
success: Samba connection established!
+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..97bcd1d00 100644
--- a/src/pages/Account/locale/es.yml
+++ b/src/pages/Account/locale/es.yml
@@ -89,6 +89,11 @@ samba:
Verify certificate: Verificar certificado
testConnection: Probar conexión
success: ¡Conexión con Samba establecida!
+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/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue
index 656f8d5fd..807ccdae4 100644
--- a/src/pages/Entry/Card/EntryDescriptor.vue
+++ b/src/pages/Entry/Card/EntryDescriptor.vue
@@ -34,7 +34,7 @@ const entryFilter = {
{
relation: 'travel',
scope: {
- fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'],
+ fields: ['id', 'landed', 'shipped', 'agencyModeFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
@@ -125,10 +125,8 @@ watch;
:label="t('entry.descriptor.agency')"
:value="entity.travel?.agency?.name"
/>
-
+
+
{
:value="entry.travel.agency.name"
/>
-
+
{
v-model="entry.travel.isDelivered"
:disable="true"
/>
-
+
{
-
+
useArrayData().store.data);
+const arrayData = useArrayData();
+const invoiceIn = computed(() => arrayData.store.data);
const userConfig = ref(null);
const invoiceId = computed(() => +route.params.id);
diff --git a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue
index dc62440c8..7735747f9 100644
--- a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue
+++ b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue
@@ -12,7 +12,7 @@ const { push, currentRoute } = useRouter();
const { t } = useI18n();
const invoiceId = +currentRoute.value.params.id;
-const arrayData = useArrayData(currentRoute.value.meta.moduleName);
+const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data);
const invoiceInCorrectionRef = ref();
const filter = {
diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue
index c435bf368..68dc5be4b 100644
--- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue
+++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue
@@ -27,9 +27,9 @@ const quasar = useQuasar();
const { hasAny } = useRole();
const { t } = useI18n();
const { openReport, sendEmail } = usePrintService();
-const { store } = useArrayData(currentRoute.value.meta.moduleName);
+const arrayData = useArrayData();
-const invoiceIn = computed(() => store.data);
+const invoiceIn = computed(() => arrayData.store.data);
const cardDescriptorRef = ref();
const correctionDialogRef = ref();
const entityId = computed(() => $props.id || +currentRoute.value.params.id);
@@ -184,7 +184,7 @@ async function toUnbook() {
: t('isNotLinked', { bookEntry });
quasar.notify({ type, message });
- if (!isLinked) store.data.isBooked = false;
+ if (!isLinked) arrayData.store.data.isBooked = false;
}
async function deleteInvoice() {
diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue
index 67c197636..a7b9110f5 100644
--- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue
+++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue
@@ -11,7 +11,8 @@ import { useArrayData } from 'src/composables/useArrayData';
const { t } = useI18n();
const route = useRoute();
-const currency = computed(() => useArrayData().store.data?.currency?.code);
+const arrayData = useArrayData();
+const currency = computed(() => arrayData.store.data?.currency?.code);
const invoceInIntrastat = ref([]);
const rowsSelected = ref([]);
const countries = ref([]);
diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue
index b7f8adf50..428e7a7a1 100644
--- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue
+++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue
@@ -14,9 +14,10 @@ import VnTitle from 'src/components/common/VnTitle.vue';
const props = defineProps({ id: { type: [Number, String], default: 0 } });
const { t } = useI18n();
const route = useRoute();
+const arrayData = useArrayData();
const entityId = computed(() => props.id || +route.params.id);
-const invoiceIn = computed(() => useArrayData().store.data);
+const invoiceIn = computed(() => arrayData.store.data);
const currency = computed(() => invoiceIn.value?.currency?.code);
const invoiceInUrl = ref();
const amountsNotMatch = ref(null);
diff --git a/src/pages/Route/Cmr/CmrFilter.vue b/src/pages/Route/Cmr/CmrFilter.vue
index 553f19431..32040ddd6 100644
--- a/src/pages/Route/Cmr/CmrFilter.vue
+++ b/src/pages/Route/Cmr/CmrFilter.vue
@@ -1,11 +1,11 @@
-
(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/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/WorkerCreate.vue b/src/pages/Worker/WorkerCreate.vue
index 9d962e450..5e81a3070 100644
--- a/src/pages/Worker/WorkerCreate.vue
+++ b/src/pages/Worker/WorkerCreate.vue
@@ -1,59 +1,29 @@
-
- onFetchWorkerConfig(data)"
- :filter="workerConfigFilter"
- auto-load
- />
-
(companiesOptions = data)"
@@ -97,16 +65,31 @@ onMounted(async () => {
@on-fetch="(data) => (bankEntitiesOptions = data)"
auto-load
/>
-
-
+
+
+
+
+
+
$router.push({ path: `/worker/${id}` })"
>
-
+
{
:label="t('worker.create.lastName')"
:rules="validate('Worker.lastNames')"
/>
-
-
-
-
-
-
- handleLocation(data, location)"
- >
-
-
-
-
-
-
+
{
:rules="validate('Worker.email')"
/>
-
+
{
-
+
+
+
+
+
+
+ handleLocation(data, location)"
+ :disable="formData.isFreelance"
+ >
+
+
+
+
+
+
{
map-options
hide-selected
:rules="validate('Worker.payMethodFk')"
+ :disable="formData.isFreelance"
+ @update:model-value="(val) => !val && delete formData.payMethodFk"
/>
@@ -232,10 +222,11 @@ onMounted(async () => {
hide-selected
:roles-allowed-to-create="['salesAssistant', 'hr']"
:rules="validate('Worker.bankEntity')"
+ :disable="formData.isFreelance"
>
bankEntitiesOptions.push(data)"
/>
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 a960dffe6..d2478535a 100644
--- a/src/pages/Worker/locale/es.yml
+++ b/src/pages/Worker/locale/es.yml
@@ -1,3 +1,6 @@
Search worker: Buscar trabajador
You can search by worker id or name: Puedes buscar por id o nombre del trabajador
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..019c91f4d 100644
--- a/src/router/modules/account.js
+++ b/src/router/modules/account.js
@@ -11,7 +11,13 @@ export default {
component: RouterView,
redirect: { name: 'AccountMain' },
menus: {
- main: ['AccountList', 'AccountAliasList', 'AccountRoles', 'AccountAcls'],
+ main: [
+ 'AccountList',
+ 'AccountAliasList',
+ 'AccountRoles',
+ 'AccountAcls',
+ 'AccountConnections',
+ ],
card: [],
},
children: [
@@ -39,6 +45,15 @@ 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: 'acls',
name: 'AccountAcls',
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/cypress/integration/worker/workerCreate.spec.js b/test/cypress/integration/worker/workerCreate.spec.js
new file mode 100644
index 000000000..26ce899c8
--- /dev/null
+++ b/test/cypress/integration/worker/workerCreate.spec.js
@@ -0,0 +1,59 @@
+describe('WorkerCreate', () => {
+ const externalRadio = '.q-toolbar .q-radio:nth-child(2)';
+ const notification = '.q-notification__message';
+ const developerBossId = 120;
+
+ const internal = {
+ Fi: { val: '78457139E' },
+ 'Web user': { val: 'manolo' },
+ Name: { val: 'Manolo' },
+ 'Last name': { val: 'Hurtado' },
+ 'Personal email': { val: 'manolo@mydomain.com' },
+ Street: { val: 'S/ DEFAULTWORKERSTREET' },
+ Location: { val: 1, type: 'select' },
+ Phone: { val: '123456789' },
+ 'Worker code': { val: 'DWW' },
+ Boss: { val: developerBossId, type: 'select' },
+ Birth: { val: '2022-12-11T23:00:00.000Z', type: 'date', day: 11 },
+ };
+ const external = {
+ Fi: { val: 'Z4531219V' },
+ 'Web user': { val: 'pepe' },
+ Name: { val: 'PEPE' },
+ 'Last name': { val: 'GARCIA' },
+ 'Personal email': { val: 'pepe@gmail.com' },
+ 'Worker code': { val: 'PG' },
+ Boss: { val: developerBossId, type: 'select' },
+ };
+
+ beforeEach(() => {
+ cy.viewport(1280, 720);
+ cy.login('hr');
+ cy.visit('/#/worker/create');
+ });
+
+ it('should throw an error if a pay method has not been selected', () => {
+ cy.fillInForm(internal);
+ cy.saveCard();
+ cy.get(notification).should(
+ 'contains.text',
+ 'That payment method requires an IBAN'
+ );
+ });
+
+ it('should create an internal', () => {
+ cy.fillInForm({
+ ...internal,
+ 'Pay method': { val: 'PayMethod one', type: 'select' },
+ });
+ cy.saveCard();
+ cy.get(notification).should('contains.text', 'Data created');
+ });
+
+ it('should create an external', () => {
+ cy.get(externalRadio).click();
+ cy.fillInForm(external);
+ cy.saveCard();
+ cy.get(notification).should('contains.text', 'Data created');
+ });
+});
diff --git a/test/cypress/integration/worker/workerList.spec.js b/test/cypress/integration/worker/workerList.spec.js
index 1e9292626..9808fd157 100644
--- a/test/cypress/integration/worker/workerList.spec.js
+++ b/test/cypress/integration/worker/workerList.spec.js
@@ -8,13 +8,13 @@ describe('WorkerList', () => {
});
it('should load workers', () => {
- cy.get(workerFieldNames).eq(0).should('have.text', 'jessicajones');
- cy.get(workerFieldNames).eq(1).should('have.text', 'brucebanner');
- cy.get(workerFieldNames).eq(2).should('have.text', 'charlesxavier');
+ cy.get(workerFieldNames).eq(2).should('have.text', 'jessicajones');
+ cy.get(workerFieldNames).eq(3).should('have.text', 'brucebanner');
+ cy.get(workerFieldNames).eq(4).should('have.text', 'charlesxavier');
});
it('should open the worker summary', () => {
- cy.openListSummary(0);
+ cy.openListSummary(2);
cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones');
cy.get('.summary .header-link')
.eq(0)
diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js
index a237622e6..055cb8021 100755
--- a/test/cypress/support/commands.js
+++ b/test/cypress/support/commands.js
@@ -86,6 +86,36 @@ Cypress.Commands.add('selectOption', (selector, option) => {
cy.get('.q-menu .q-item').contains(option).click();
});
+Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => {
+ const days = '.q-date__calendar-days .q-date__calendar-item--in';
+ cy.waitForElement('.q-form > .q-card');
+ cy.get(`${form} input`).each(([el]) => {
+ cy.wrap(el)
+ .invoke('attr', 'aria-label')
+ .then((ariaLabel) => {
+ const field = obj[ariaLabel];
+ if (!field) return;
+
+ const { type, val, day } = field;
+ switch (type) {
+ case 'select':
+ cy.wrap(el).type(val);
+ cy.get('.q-menu .q-item').contains(val).click();
+ break;
+ case 'date':
+ cy.wrap(el).click();
+ cy.get(days)
+ .eq(day ? day - 1 : 0)
+ .click();
+ break;
+ default:
+ cy.wrap(el).type(val);
+ break;
+ }
+ });
+ });
+});
+
Cypress.Commands.add('checkOption', (selector) => {
cy.get(selector).find('.q-checkbox__inner').click();
});
@@ -198,11 +228,15 @@ Cypress.Commands.add('closeSideMenu', (element) => {
Cypress.Commands.add('clearSearchbar', (element) => {
if (element) cy.waitForElement(element);
- cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').clear();
+ cy.get(
+ '#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input'
+ ).clear();
});
Cypress.Commands.add('writeSearchbar', (value) => {
- cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').type(value);
+ cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').type(
+ value
+ );
});
Cypress.Commands.add('validateContent', (selector, expectedValue) => {
cy.get(selector).should('have.text', expectedValue);
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');