From e736741835c3ad223db227e1ae05dc9203d811b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=CC=8Cimon=20Macek?=
Date: Wed, 18 Dec 2024 23:14:02 +0100
Subject: [PATCH 01/20] create organization
---
src/components/form/FormFieldSelectTable.vue | 31 +++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/src/components/form/FormFieldSelectTable.vue b/src/components/form/FormFieldSelectTable.vue
index b53e45a1b..508c5ac7a 100644
--- a/src/components/form/FormFieldSelectTable.vue
+++ b/src/components/form/FormFieldSelectTable.vue
@@ -57,6 +57,7 @@ import { useOrganizations } from '../../composables/useOrganizations';
import { useSelectTable } from '../../composables/useSelectTable';
import { useValidation } from '../../composables/useValidation';
import { useApiPostTeam } from '../../composables/useApiPostTeam';
+import { useApiPostOrganization } from '../../composables/useApiPostOrganization';
// enums
import { OrganizationType, OrganizationLevel } from '../types/Organization';
@@ -187,6 +188,7 @@ export default defineComponent({
};
const { createTeam } = useApiPostTeam(logger);
+ const { createOrganization } = useApiPostOrganization(logger);
const registerChallengeStore = useRegisterChallengeStore();
/**
@@ -196,8 +198,35 @@ export default defineComponent({
* @returns {Promise}
*/
const submitDialogForm = async (): Promise => {
+ // create organization
if (props.organizationLevel === OrganizationLevel.organization) {
- // TODO: Create a new company
+ if (!props.organizationType) {
+ logger?.info('No organization type provided.');
+ return;
+ }
+
+ logger?.info('Create organization.');
+ const data = await createOrganization(
+ organizationNew.value.name,
+ organizationNew.value.vatId,
+ props.organizationType,
+ );
+
+ if (data?.id) {
+ logger?.debug(
+ `New organization was created with ID <${data.id}> and name <${data.name}>.`,
+ );
+ // emit `create:option` event
+ emit('create:option', data);
+ // close dialog
+ isDialogOpen.value = false;
+ logger?.info('Close add organization modal dialog.');
+ // store data in v-model (emits to parent component)
+ inputValue.value = data.id;
+ logger?.debug(
+ `New organization model ID set to <${inputValue.value}>.`,
+ );
+ }
} else if (props.organizationLevel === OrganizationLevel.team) {
logger?.info('Create team.');
const subsidiaryId = registerChallengeStore.getSubsidiaryId;
From da0854418a55b5ea2dbae33b3f0dec9a82fcea63 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=CC=8Cimon=20Macek?=
Date: Wed, 18 Dec 2024 23:40:39 +0100
Subject: [PATCH 02/20] create organization + subsidiary
---
src/components/form/FormFieldSelectTable.vue | 79 +++++++++++++++----
.../form/FormSelectOrganization.vue | 16 +++-
2 files changed, 74 insertions(+), 21 deletions(-)
diff --git a/src/components/form/FormFieldSelectTable.vue b/src/components/form/FormFieldSelectTable.vue
index 508c5ac7a..556dd4a7e 100644
--- a/src/components/form/FormFieldSelectTable.vue
+++ b/src/components/form/FormFieldSelectTable.vue
@@ -58,6 +58,7 @@ import { useSelectTable } from '../../composables/useSelectTable';
import { useValidation } from '../../composables/useValidation';
import { useApiPostTeam } from '../../composables/useApiPostTeam';
import { useApiPostOrganization } from '../../composables/useApiPostOrganization';
+import { useApiPostSubsidiary } from '../../composables/useApiPostSubsidiary';
// enums
import { OrganizationType, OrganizationLevel } from '../types/Organization';
@@ -160,14 +161,6 @@ export default defineComponent({
// controls dialog visibility
const isDialogOpen = ref(false);
- // close dialog
- const onClose = (): void => {
- if (formRef.value) {
- formRef.value.reset();
- }
- isDialogOpen.value = false;
- };
-
/**
* Validates the form.
* If form is valid it submits the data.
@@ -188,8 +181,20 @@ export default defineComponent({
};
const { createTeam } = useApiPostTeam(logger);
- const { createOrganization } = useApiPostOrganization(logger);
+ const { isLoading: isLoadingCreateOrganization, createOrganization } =
+ useApiPostOrganization(logger);
+ const { isLoading: isLoadingCreateSubsidiary, createSubsidiary } =
+ useApiPostSubsidiary(logger);
const registerChallengeStore = useRegisterChallengeStore();
+ const subsidiaryId = computed({
+ get: (): number | null => registerChallengeStore.getSubsidiaryId,
+ set: (value: number | null) =>
+ registerChallengeStore.setSubsidiaryId(value),
+ });
+ const isLoading = computed(
+ () =>
+ isLoadingCreateOrganization.value || isLoadingCreateSubsidiary.value,
+ );
/**
* Submit dialog form based on organization level
@@ -216,16 +221,47 @@ export default defineComponent({
logger?.debug(
`New organization was created with ID <${data.id}> and name <${data.name}>.`,
);
- // emit `create:option` event
- emit('create:option', data);
- // close dialog
- isDialogOpen.value = false;
- logger?.info('Close add organization modal dialog.');
- // store data in v-model (emits to parent component)
+
+ // create subsidiary
+ logger?.info('Create subsidiary.');
+ const subsidiaryData = await createSubsidiary(
+ data.id,
+ organizationNew.value.address,
+ );
+ if (subsidiaryData) {
+ logger?.debug(
+ `New subsidiary was created with data <${JSON.stringify(subsidiaryData, null, 2)}>.`,
+ );
+ if (subsidiaryData.id) {
+ // set subsidiary ID in store
+ subsidiaryId.value = subsidiaryData.id;
+ logger?.debug(
+ `Subsidiary ID model set to <${subsidiaryId.value}>.`,
+ );
+ } else {
+ logger?.error('New subsidiary ID not found.');
+ }
+ }
+
+ /**
+ * Set modelValue
+ * This will save the organization ID into the store.
+ * That will in turn trigger the loading of subsidiary options
+ * so we do not need to manually handle the new subsidiary option.
+ */
inputValue.value = data.id;
logger?.debug(
`New organization model ID set to <${inputValue.value}>.`,
);
+ /**
+ * Emit `create:option` event
+ * This will add new organization to the options list.
+ */
+ emit('create:option', data);
+ // close dialog
+ onClose();
+ } else {
+ logger?.error('New organization ID not found.');
}
} else if (props.organizationLevel === OrganizationLevel.team) {
logger?.info('Create team.');
@@ -242,8 +278,7 @@ export default defineComponent({
// emit `create:option` event
emit('create:option', data);
// close dialog
- isDialogOpen.value = false;
- logger?.info('Close add team modal dialog.');
+ onClose();
// store data in v-model (emits to parent component)
inputValue.value = data.id;
logger?.debug(`New team model ID set to <${inputValue.value}>.`);
@@ -251,6 +286,15 @@ export default defineComponent({
}
};
+ // close dialog
+ const onClose = (): void => {
+ if (formRef.value) {
+ formRef.value.reset();
+ }
+ isDialogOpen.value = false;
+ logger?.info('Close add option modal dialog.');
+ };
+
const { getOrganizationLabels } = useOrganizations();
const { getSelectTableLabels } = useSelectTable();
@@ -307,6 +351,7 @@ export default defineComponent({
teamNew,
titleDialog,
isFilled,
+ isLoading,
onClose,
onSubmit,
OrganizationType,
diff --git a/src/components/form/FormSelectOrganization.vue b/src/components/form/FormSelectOrganization.vue
index d5c25cfd5..663432f15 100644
--- a/src/components/form/FormSelectOrganization.vue
+++ b/src/components/form/FormSelectOrganization.vue
@@ -33,7 +33,8 @@ import { OrganizationLevel, OrganizationType } from '../types/Organization';
// types
import type { FormSelectOption } from '../types/Form';
import type { Logger } from '../types/Logger';
-
+import type { OrganizationOption } from '../types/Organization';
+import type { PostOrganizationResponse } from '../types/apiOrganization';
// stores
import { useRegisterChallengeStore } from 'src/stores/registerChallenge';
@@ -47,7 +48,7 @@ export default defineComponent({
const logger = inject('vuejs3-logger') as Logger | null;
const opts = ref([]);
const formFieldSelectTableRef = ref(null);
- const { options, isLoading, loadOrganizations } =
+ const { options, organizations, isLoading, loadOrganizations } =
useApiGetOrganizations(logger);
const registerChallengeStore = useRegisterChallengeStore();
@@ -79,7 +80,7 @@ export default defineComponent({
loadOrganizations(newValue).then(() => {
logger?.info('All organizations data was loaded from the API.');
// Lazy loading
- opts.value = options;
+ opts.value = options.value;
});
}
},
@@ -92,6 +93,11 @@ export default defineComponent({
formFieldSelectTableRef.value.selectOrganizationRef.validate();
};
+ const onCreateOption = (data: PostOrganizationResponse): void => {
+ const newOrganization: OrganizationOption = data;
+ organizations.value.push(newOrganization);
+ };
+
return {
formFieldSelectTableRef,
isLoading,
@@ -101,6 +107,7 @@ export default defineComponent({
OrganizationLevel,
organizationType,
onCloseAddSubsidiaryDialog,
+ onCreateOption,
};
},
});
@@ -111,11 +118,12 @@ export default defineComponent({
Date: Thu, 19 Dec 2024 13:22:20 +0100
Subject: [PATCH 03/20] remove outdated comment
---
src/components/form/FormAddCompany.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/form/FormAddCompany.vue b/src/components/form/FormAddCompany.vue
index 8006876a6..66e21879b 100644
--- a/src/components/form/FormAddCompany.vue
+++ b/src/components/form/FormAddCompany.vue
@@ -154,7 +154,7 @@ export default defineComponent({
{{ $t('form.company.textSubsidiaryAddress') }}
-
+
Date: Mon, 23 Dec 2024 16:00:56 +0100
Subject: [PATCH 04/20] add test for creating organization in SelectTable
---
.../__tests__/FormFieldSelectTable.cy.js | 109 +++++++++++++++
src/components/form/FormFieldSelectTable.vue | 127 ++++++++++--------
.../form/FormSelectOrganization.vue | 3 +
.../fixtures/apiPostSubsidiaryResponse.json | 8 +-
test/cypress/support/commands.js | 30 +++++
5 files changed, 216 insertions(+), 61 deletions(-)
diff --git a/src/components/__tests__/FormFieldSelectTable.cy.js b/src/components/__tests__/FormFieldSelectTable.cy.js
index e4a1ec2ab..7a1f340e6 100644
--- a/src/components/__tests__/FormFieldSelectTable.cy.js
+++ b/src/components/__tests__/FormFieldSelectTable.cy.js
@@ -1,18 +1,24 @@
+import { ref } from 'vue';
import FormFieldSelectTable from 'components/form/FormFieldSelectTable.vue';
import { rideToWorkByBikeConfig } from 'src/boot/global_vars';
import { i18n } from '../../boot/i18n';
import { useApiGetOrganizations } from 'src/composables/useApiGetOrganizations';
import { createPinia, setActivePinia } from 'pinia';
+import { vModelAdapter } from 'app/test/cypress/utils';
import {
OrganizationLevel,
OrganizationType,
} from 'src/components/types/Organization';
+import { interceptOrganizationsApi } from '../../../test/cypress/support/commonTests';
import { useRegisterChallengeStore } from 'src/stores/registerChallenge';
+// variables
const { contactEmail } = rideToWorkByBikeConfig;
+const model = ref(null);
describe('', () => {
let options;
+ let organizationId;
let subsidiaryId;
before(() => {
@@ -32,6 +38,11 @@ describe('', () => {
},
);
});
+ cy.fixture('formFieldCompanyCreate').then(
+ (formFieldCompanyCreateResponse) => {
+ organizationId = formFieldCompanyCreateResponse.id;
+ },
+ );
// set common subsidiaryId from fixture
cy.fixture('formOrganizationOptions').then((formOrganizationOptions) => {
subsidiaryId = formOrganizationOptions[0].subsidiaries[0].id;
@@ -95,8 +106,19 @@ describe('', () => {
context('organization company', () => {
beforeEach(() => {
cy.interceptCitiesGetApi(rideToWorkByBikeConfig, i18n);
+ interceptOrganizationsApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ OrganizationType.company,
+ );
+ cy.interceptSubsidiaryPostApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ organizationId,
+ );
cy.mount(FormFieldSelectTable, {
props: {
+ ...vModelAdapter(model),
options: options,
organizationLevel: OrganizationLevel.organization,
organizationType: OrganizationType.company,
@@ -219,6 +241,93 @@ describe('', () => {
cy.dataCy('dialog-button-submit').click();
cy.dataCy('dialog-add-option').should('not.exist');
});
+
+ it.only('allows to add a new organization', () => {
+ cy.fixture('apiPostSubsidiaryRequest').then(
+ (apiPostSubsidiaryRequest) => {
+ cy.fixture('formFieldCompanyCreateRequest').then(
+ (formFieldCompanyCreateRequest) => {
+ cy.dataCy('button-add-option').click();
+ // dialog
+ cy.dataCy('dialog-add-option').should('be.visible');
+ cy.dataCy('dialog-add-option')
+ .find('h3')
+ .should('be.visible')
+ .and('contain', i18n.global.t('form.company.titleAddCompany'));
+ // fill form
+ cy.dataCy('form-add-company-name')
+ .find('input')
+ .type(formFieldCompanyCreateRequest.name);
+ cy.dataCy('form-add-company-vat-id')
+ .find('input')
+ .type(formFieldCompanyCreateRequest.vatId);
+ cy.dataCy('form-add-subsidiary-street')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.street);
+ cy.dataCy('form-add-subsidiary-house-number')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.street_number);
+ cy.dataCy('form-add-subsidiary-city')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.city);
+ cy.dataCy('form-add-subsidiary-zip')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.psc);
+ cy.dataCy('form-add-subsidiary-city-challenge').click();
+ cy.get('.q-menu')
+ .should('be.visible')
+ .find('.q-item')
+ .first()
+ .click();
+ // submit form
+ cy.dataCy('dialog-button-submit').click();
+ // wait for API call
+ cy.waitForOrganizationCreateApi();
+ // verify that dialog is closed
+ cy.dataCy('dialog-add-option').should('not.exist');
+ cy.fixture('formFieldCompanyCreate').then(
+ (formFieldCompanyCreateResponse) => {
+ // test emitted events
+ cy.wrap(Cypress.vueWrapper.emitted('create:option')).should(
+ 'have.length',
+ 1,
+ );
+ cy.wrap(
+ Cypress.vueWrapper.emitted('create:option')[0][0],
+ ).should('deep.equal', formFieldCompanyCreateResponse);
+ // test that model value was updated
+ cy.wrap(model)
+ .its('value')
+ .should('eq', formFieldCompanyCreateResponse.id);
+ },
+ );
+ // open dialog again
+ cy.dataCy('button-add-option').click();
+ cy.dataCy('dialog-add-option').should('be.visible');
+ // verify that dialog form was reset
+ cy.dataCy('form-add-company-name')
+ .find('input')
+ .should('have.value', '');
+ cy.dataCy('form-add-company-vat-id')
+ .find('input')
+ .should('have.value', '');
+ cy.dataCy('form-add-subsidiary-street')
+ .find('input')
+ .should('have.value', '');
+ cy.dataCy('form-add-subsidiary-house-number')
+ .find('input')
+ .should('have.value', '');
+ cy.dataCy('form-add-subsidiary-city')
+ .find('input')
+ .should('have.value', '');
+ cy.dataCy('form-add-subsidiary-zip')
+ .find('input')
+ .should('have.value', '');
+ },
+ );
+ },
+ );
+ });
});
context('organization company selected', () => {
diff --git a/src/components/form/FormFieldSelectTable.vue b/src/components/form/FormFieldSelectTable.vue
index 556dd4a7e..f25c60a52 100644
--- a/src/components/form/FormFieldSelectTable.vue
+++ b/src/components/form/FormFieldSelectTable.vue
@@ -75,6 +75,22 @@ import {
FormTeamFields,
} from '../types/Form';
+// utils
+import { deepObjectWithSimplePropsCopy } from '../../utils';
+
+const emptyFormCompanyFields: FormCompanyFields = {
+ name: '',
+ vatId: '',
+ address: {
+ street: '',
+ houseNumber: '',
+ city: '',
+ zip: '',
+ cityChallenge: null,
+ department: '',
+ },
+};
+
export default defineComponent({
name: 'FormFieldSelectTable',
components: {
@@ -112,21 +128,12 @@ export default defineComponent({
// user input for filtering
const query = ref('');
const formRef = ref(null);
- const organizationNew = ref({
- name: '',
- vatId: '',
- address: {
- street: '',
- houseNumber: '',
- city: '',
- zip: '',
- cityChallenge: null,
- department: '',
- },
- });
- const teamNew = ref({
- name: '',
- });
+ const organizationNew = ref(
+ deepObjectWithSimplePropsCopy(
+ emptyFormCompanyFields,
+ ) as FormCompanyFields,
+ );
+ const teamNew = ref({ name: '' });
const selectOrganizationRef = ref(null);
/**
@@ -203,66 +210,66 @@ export default defineComponent({
* @returns {Promise}
*/
const submitDialogForm = async (): Promise => {
- // create organization
if (props.organizationLevel === OrganizationLevel.organization) {
+ // create organization
if (!props.organizationType) {
logger?.info('No organization type provided.');
return;
}
-
logger?.info('Create organization.');
- const data = await createOrganization(
+ const organizationData = await createOrganization(
organizationNew.value.name,
organizationNew.value.vatId,
props.organizationType,
);
- if (data?.id) {
- logger?.debug(
- `New organization was created with ID <${data.id}> and name <${data.name}>.`,
- );
-
- // create subsidiary
- logger?.info('Create subsidiary.');
- const subsidiaryData = await createSubsidiary(
- data.id,
- organizationNew.value.address,
- );
- if (subsidiaryData) {
- logger?.debug(
- `New subsidiary was created with data <${JSON.stringify(subsidiaryData, null, 2)}>.`,
- );
- if (subsidiaryData.id) {
- // set subsidiary ID in store
- subsidiaryId.value = subsidiaryData.id;
- logger?.debug(
- `Subsidiary ID model set to <${subsidiaryId.value}>.`,
- );
- } else {
- logger?.error('New subsidiary ID not found.');
- }
- }
+ if (!organizationData?.id) {
+ logger?.error('New organization ID not found.');
+ return;
+ }
+ logger?.debug(
+ `New organization was created with ID <${organizationData.id}> and name <${organizationData.name}>.`,
+ );
+ // create subsidiary
+ logger?.info('Create subsidiary.');
+ const subsidiaryData = await createSubsidiary(
+ organizationData.id,
+ organizationNew.value.address,
+ );
+ if (subsidiaryData?.id) {
/**
- * Set modelValue
- * This will save the organization ID into the store.
- * That will in turn trigger the loading of subsidiary options
- * so we do not need to manually handle the new subsidiary option.
+ * We are not using early return because if creating subsidiary fails
+ * we still want to run subsequent actions (setting modelValue).
*/
- inputValue.value = data.id;
logger?.debug(
- `New organization model ID set to <${inputValue.value}>.`,
+ `New subsidiary was created with data <${JSON.stringify(subsidiaryData, null, 2)}>.`,
);
- /**
- * Emit `create:option` event
- * This will add new organization to the options list.
- */
- emit('create:option', data);
- // close dialog
- onClose();
+ // set subsidiary ID in store
+ subsidiaryId.value = subsidiaryData.id;
+ logger?.debug(`Subsidiary ID model set to <${subsidiaryId.value}>.`);
} else {
- logger?.error('New organization ID not found.');
+ logger?.error('New subsidiary data not found.');
}
+
+ /**
+ * Set modelValue (organization ID)
+ * This will save the organization ID into the store.
+ * We are waiting until createSubsidiary action is done because saving
+ * modelValue triggers reloading subsidiary options list and so we
+ * do not need to manually append the new subsidiary option.
+ */
+ inputValue.value = organizationData.id;
+ logger?.debug(
+ `New organization model ID set to <${inputValue.value}>.`,
+ );
+ /**
+ * Emit `create:option` event
+ * This will add new organization to the options list.
+ */
+ emit('create:option', organizationData);
+ // close dialog
+ onClose();
} else if (props.organizationLevel === OrganizationLevel.team) {
logger?.info('Create team.');
const subsidiaryId = registerChallengeStore.getSubsidiaryId;
@@ -291,6 +298,12 @@ export default defineComponent({
if (formRef.value) {
formRef.value.reset();
}
+ // reset organizationNew and teamNew
+ organizationNew.value = deepObjectWithSimplePropsCopy(
+ emptyFormCompanyFields,
+ ) as FormCompanyFields;
+ teamNew.value = { name: '' };
+ // close dialog
isDialogOpen.value = false;
logger?.info('Close add option modal dialog.');
};
diff --git a/src/components/form/FormSelectOrganization.vue b/src/components/form/FormSelectOrganization.vue
index 663432f15..bae5f0023 100644
--- a/src/components/form/FormSelectOrganization.vue
+++ b/src/components/form/FormSelectOrganization.vue
@@ -95,6 +95,9 @@ export default defineComponent({
const onCreateOption = (data: PostOrganizationResponse): void => {
const newOrganization: OrganizationOption = data;
+ logger?.debug(
+ `Add new organization to organizations array <${JSON.stringify(newOrganization, null, 2)}>.`,
+ );
organizations.value.push(newOrganization);
};
diff --git a/test/cypress/fixtures/apiPostSubsidiaryResponse.json b/test/cypress/fixtures/apiPostSubsidiaryResponse.json
index b04af5756..8f67a4309 100644
--- a/test/cypress/fixtures/apiPostSubsidiaryResponse.json
+++ b/test/cypress/fixtures/apiPostSubsidiaryResponse.json
@@ -4,9 +4,9 @@
"active": true,
"address": {
"street": "Subsidiary Street",
- "street_number": "4",
- "recipient": "Recipient 4",
- "psc": 50004,
- "city": "City 4"
+ "street_number": "1",
+ "recipient": "Subsidiary Recipient",
+ "city": "Subsidiary City",
+ "psc": 12345
}
}
diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js
index 443ee4be1..79db117a2 100644
--- a/test/cypress/support/commands.js
+++ b/test/cypress/support/commands.js
@@ -1445,3 +1445,33 @@ Cypress.Commands.add('applyInvalidVoucher', (config, i18n) => {
.should('have.value', invalid);
});
});
+
+/**
+ * Wait for intercept organization creation API call and compare request/response object
+ * Wait for `@createOrganization` intercept
+ */
+Cypress.Commands.add('waitForOrganizationCreateApi', () => {
+ cy.fixture('formFieldCompanyCreateRequest').then(
+ (formFieldCompanyCreateRequest) => {
+ cy.fixture('formFieldCompanyCreate').then(
+ (formFieldCompanyCreateResponse) => {
+ cy.wait('@createOrganization').then(({ request, response }) => {
+ expect(request.headers.authorization).to.include(bearerTokeAuth);
+ expect(request.body).to.deep.equal({
+ name: formFieldCompanyCreateRequest.name,
+ vatId: formFieldCompanyCreateRequest.vatId,
+ organization_type:
+ formFieldCompanyCreateRequest.organization_type,
+ });
+ if (response) {
+ expect(response.statusCode).to.equal(httpSuccessfullStatus);
+ expect(response.body).to.deep.equal(
+ formFieldCompanyCreateResponse,
+ );
+ }
+ });
+ },
+ );
+ },
+ );
+});
From 60629f8b540f73e21f33596dc43967e60654c6d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=CC=8Cimon=20Macek?=
Date: Mon, 23 Dec 2024 16:02:33 +0100
Subject: [PATCH 05/20] remove only
---
src/components/__tests__/FormFieldSelectTable.cy.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/__tests__/FormFieldSelectTable.cy.js b/src/components/__tests__/FormFieldSelectTable.cy.js
index 7a1f340e6..046dd1ef9 100644
--- a/src/components/__tests__/FormFieldSelectTable.cy.js
+++ b/src/components/__tests__/FormFieldSelectTable.cy.js
@@ -242,7 +242,7 @@ describe('', () => {
cy.dataCy('dialog-add-option').should('not.exist');
});
- it.only('allows to add a new organization', () => {
+ it('allows to add a new organization', () => {
cy.fixture('apiPostSubsidiaryRequest').then(
(apiPostSubsidiaryRequest) => {
cy.fixture('formFieldCompanyCreateRequest').then(
From f3aebdb2460ac95d575baad0e0aefa2c316ea285 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=CC=8Cimon=20Macek?=
Date: Mon, 23 Dec 2024 18:37:11 +0100
Subject: [PATCH 06/20] organization and subsidiary creation
---
.../__tests__/FormFieldSelectTable.cy.js | 36 +---
.../__tests__/FormSelectOrganization.cy.js | 174 +++++++++++++++++-
.../form/FormFieldCompanyAddress.vue | 5 +
src/components/form/FormFieldSelectTable.vue | 56 +++---
.../form/FormSelectOrganization.vue | 36 +++-
src/composables/useApiGetSubsidiaries.ts | 10 +-
.../apiGetSubsidiariesResponseNew.json | 18 ++
test/cypress/fixtures/formFieldCompany.json | 2 +-
.../fixtures/formFieldCompanyNext.json | 2 +-
test/cypress/support/commands.js | 89 ++++++++-
10 files changed, 363 insertions(+), 65 deletions(-)
create mode 100644 test/cypress/fixtures/apiGetSubsidiariesResponseNew.json
diff --git a/src/components/__tests__/FormFieldSelectTable.cy.js b/src/components/__tests__/FormFieldSelectTable.cy.js
index 046dd1ef9..ad0534ee0 100644
--- a/src/components/__tests__/FormFieldSelectTable.cy.js
+++ b/src/components/__tests__/FormFieldSelectTable.cy.js
@@ -248,41 +248,21 @@ describe('', () => {
cy.fixture('formFieldCompanyCreateRequest').then(
(formFieldCompanyCreateRequest) => {
cy.dataCy('button-add-option').click();
- // dialog
+ // verify that dialog is visible
cy.dataCy('dialog-add-option').should('be.visible');
cy.dataCy('dialog-add-option')
.find('h3')
.should('be.visible')
.and('contain', i18n.global.t('form.company.titleAddCompany'));
- // fill form
- cy.dataCy('form-add-company-name')
- .find('input')
- .type(formFieldCompanyCreateRequest.name);
- cy.dataCy('form-add-company-vat-id')
- .find('input')
- .type(formFieldCompanyCreateRequest.vatId);
- cy.dataCy('form-add-subsidiary-street')
- .find('input')
- .type(apiPostSubsidiaryRequest.address.street);
- cy.dataCy('form-add-subsidiary-house-number')
- .find('input')
- .type(apiPostSubsidiaryRequest.address.street_number);
- cy.dataCy('form-add-subsidiary-city')
- .find('input')
- .type(apiPostSubsidiaryRequest.address.city);
- cy.dataCy('form-add-subsidiary-zip')
- .find('input')
- .type(apiPostSubsidiaryRequest.address.psc);
- cy.dataCy('form-add-subsidiary-city-challenge').click();
- cy.get('.q-menu')
- .should('be.visible')
- .find('.q-item')
- .first()
- .click();
- // submit form
+ // fill in the form
+ cy.fillOrganizationSubsidiaryForm(
+ formFieldCompanyCreateRequest,
+ apiPostSubsidiaryRequest,
+ );
+ // submit tghe form
cy.dataCy('dialog-button-submit').click();
// wait for API call
- cy.waitForOrganizationCreateApi();
+ cy.waitForOrganizationPostApi();
// verify that dialog is closed
cy.dataCy('dialog-add-option').should('not.exist');
cy.fixture('formFieldCompanyCreate').then(
diff --git a/src/components/__tests__/FormSelectOrganization.cy.js b/src/components/__tests__/FormSelectOrganization.cy.js
index 4be4adb57..3d8dd27de 100644
--- a/src/components/__tests__/FormSelectOrganization.cy.js
+++ b/src/components/__tests__/FormSelectOrganization.cy.js
@@ -1,8 +1,14 @@
+import { computed } from 'vue';
import { createPinia, setActivePinia } from 'pinia';
import FormSelectOrganization from 'components/form/FormSelectOrganization.vue';
import { i18n } from '../../boot/i18n';
import { OrganizationType } from 'src/components/types/Organization';
import { useRegisterChallengeStore } from 'src/stores/registerChallenge';
+import {
+ interceptOrganizationsApi,
+ waitForOrganizationsApi,
+} from '../../../test/cypress/support/commonTests';
+import { rideToWorkByBikeConfig } from 'src/boot/global_vars';
// selectors
const selectorFormFieldCompanyAddress = 'form-company-address';
@@ -10,13 +16,33 @@ const selectorFormFieldSelectTable = 'form-select-table-company';
const selectorFormSelectOrganization = 'form-select-organization';
describe('', () => {
+ let organizationId;
+
it('has translation for all strings', () => {
cy.testLanguageStringsInContext([], 'index.component', i18n);
});
+ before(() => {
+ setActivePinia(createPinia());
+ cy.fixture('formFieldCompanyCreate').then(
+ (formFieldCompanyCreateResponse) => {
+ organizationId = formFieldCompanyCreateResponse.id;
+ },
+ );
+ });
+
context('desktop', () => {
beforeEach(() => {
- setActivePinia(createPinia());
+ cy.interceptSubsidiariesGetApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ organizationId,
+ );
+ interceptOrganizationsApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ OrganizationType.company,
+ );
cy.mount(FormSelectOrganization, {
props: {},
});
@@ -28,7 +54,16 @@ describe('', () => {
context('mobile', () => {
beforeEach(() => {
- setActivePinia(createPinia());
+ cy.interceptSubsidiariesGetApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ organizationId,
+ );
+ interceptOrganizationsApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ OrganizationType.company,
+ );
cy.mount(FormSelectOrganization, {
props: {},
});
@@ -37,6 +72,109 @@ describe('', () => {
coreTests();
});
+
+ context('create new organization and subsidiary', () => {
+ beforeEach(() => {
+ cy.interceptCitiesGetApi(rideToWorkByBikeConfig, i18n);
+ cy.interceptSubsidiariesNewGetApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ organizationId,
+ );
+ interceptOrganizationsApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ OrganizationType.company,
+ );
+ cy.interceptSubsidiaryPostApi(
+ rideToWorkByBikeConfig,
+ i18n,
+ organizationId,
+ );
+ cy.mount(FormSelectOrganization, {
+ props: {},
+ });
+ cy.viewport('iphone-6');
+ });
+
+ it('allows to create a new organization and subsidiary', () => {
+ cy.fixture('formFieldCompany').then((formFieldCompanyResponse) => {
+ cy.fixture('apiPostSubsidiaryRequest').then(
+ (apiPostSubsidiaryRequest) => {
+ cy.fixture('apiPostSubsidiaryResponse').then(
+ (apiPostSubsidiaryResponse) => {
+ cy.fixture('formFieldCompanyCreateRequest').then(
+ (formFieldCompanyCreateRequest) => {
+ cy.fixture('formFieldCompanyCreate').then(
+ (formFieldCompanyCreateResponse) => {
+ cy.wrap(useRegisterChallengeStore()).then((store) => {
+ store.setOrganizationType(OrganizationType.company);
+ const organizationId = computed(
+ () => store.getOrganizationId,
+ );
+ const subsidiaryId = computed(
+ () => store.getSubsidiaryId,
+ );
+ // open dialog form
+ cy.dataCy('button-add-option').click();
+ // verify that dialog is visible
+ cy.dataCy('dialog-add-option').should('be.visible');
+ cy.dataCy('dialog-add-option')
+ .find('h3')
+ .should('be.visible')
+ .and(
+ 'contain',
+ i18n.global.t('form.company.titleAddCompany'),
+ );
+ // fill in the form
+ cy.fillOrganizationSubsidiaryForm(
+ formFieldCompanyCreateRequest,
+ apiPostSubsidiaryRequest,
+ );
+ // submit the form
+ cy.dataCy('dialog-button-submit').click();
+ // check that POST organization API was called
+ cy.waitForOrganizationPostApi();
+ // check that POST subsidiary API was called
+ cy.waitForSubsidiaryPostApi();
+ // check that a new organization was added to the list
+ cy.dataCy('form-select-table-options')
+ .find('.q-radio__label')
+ .should(
+ 'have.length',
+ formFieldCompanyResponse.count + 1,
+ );
+ cy.dataCy('form-select-table-options')
+ .find('.q-radio__label')
+ .contains(formFieldCompanyCreateRequest.name)
+ .should('exist');
+ // check that organizationId was saved in store
+ cy.wrap(organizationId)
+ .its('value')
+ .should('equal', formFieldCompanyCreateResponse.id);
+ // check that subsidiaryId was saved in store
+ cy.wrap(subsidiaryId)
+ .its('value')
+ .should('equal', apiPostSubsidiaryResponse.id);
+ // check that subsidiaries were refetched
+ cy.waitForSubsidiariesNewApi();
+ // check that the subsidary (address) input has correct value
+ cy.dataCy('form-company-address').should(
+ 'contain',
+ apiPostSubsidiaryRequest.address.street,
+ );
+ });
+ },
+ );
+ },
+ );
+ },
+ );
+ },
+ );
+ });
+ });
+ });
});
function coreTests() {
@@ -78,4 +216,36 @@ function coreTests() {
.should('have.attr', 'data-organization-type')
.and('equal', OrganizationType.family);
});
+
+ it('allows to select an organization', () => {
+ cy.wrap(useRegisterChallengeStore()).then((store) => {
+ store.setOrganizationType(OrganizationType.company);
+ const organizationId = computed(() => store.getOrganizationId);
+ // check that options are available
+ cy.fixture('formFieldCompany').then((formFieldCompanyResponse) => {
+ cy.fixture('formFieldCompanyNext').then(
+ (formFieldCompanyNextResponse) => {
+ // wait for API call to finish
+ waitForOrganizationsApi(
+ formFieldCompanyResponse,
+ formFieldCompanyNextResponse,
+ );
+ // check that options exist
+ cy.dataCy('form-select-table-options')
+ .find('.q-radio__label')
+ .should('have.length', formFieldCompanyResponse.count);
+ // select first option
+ cy.dataCy('form-select-table-options')
+ .find('.q-radio__label')
+ .first()
+ .click();
+ // check that option was saved in store
+ cy.wrap(organizationId)
+ .its('value')
+ .should('equal', formFieldCompanyResponse.results[0].id);
+ },
+ );
+ });
+ });
+ });
}
diff --git a/src/components/form/FormFieldCompanyAddress.vue b/src/components/form/FormFieldCompanyAddress.vue
index ee2c4629e..b9e9d931c 100644
--- a/src/components/form/FormFieldCompanyAddress.vue
+++ b/src/components/form/FormFieldCompanyAddress.vue
@@ -89,6 +89,7 @@ export default defineComponent({
const {
subsidiaries,
isLoading: isLoadingSubsidiaries,
+ appendSubsidiaryResults,
loadSubsidiaries,
} = useApiGetSubsidiaries(logger);
const { isLoading: isLoadingCreateSubsidiary, createSubsidiary } =
@@ -126,6 +127,9 @@ export default defineComponent({
`Register challenge store organization ID updated to <${newValue}>.`,
);
subsidiaryId.value = null;
+ logger?.debug(
+ `Subsidiary ID reset to <${subsidiaryId.value}> as organization ID changed.`,
+ );
if (newValue) {
logger?.info('Loading subsidiaries.');
loadSubsidiaries(newValue);
@@ -260,6 +264,7 @@ export default defineComponent({
isLoadingCreateSubsidiary,
onClose,
onSubmit,
+ appendSubsidiaryResults,
};
},
});
diff --git a/src/components/form/FormFieldSelectTable.vue b/src/components/form/FormFieldSelectTable.vue
index f25c60a52..cd5bc42eb 100644
--- a/src/components/form/FormFieldSelectTable.vue
+++ b/src/components/form/FormFieldSelectTable.vue
@@ -121,7 +121,7 @@ export default defineComponent({
default: null,
},
},
- emits: ['update:modelValue', 'create:option'],
+ emits: ['update:modelValue', 'create:option', 'create:subsidiary'],
setup(props, { emit }) {
const logger = inject('vuejs3-logger') as Logger | null;
@@ -192,20 +192,21 @@ export default defineComponent({
useApiPostOrganization(logger);
const { isLoading: isLoadingCreateSubsidiary, createSubsidiary } =
useApiPostSubsidiary(logger);
+ const isLoading = computed(
+ () =>
+ isLoadingCreateOrganization.value || isLoadingCreateSubsidiary.value,
+ );
+
const registerChallengeStore = useRegisterChallengeStore();
const subsidiaryId = computed({
get: (): number | null => registerChallengeStore.getSubsidiaryId,
set: (value: number | null) =>
registerChallengeStore.setSubsidiaryId(value),
});
- const isLoading = computed(
- () =>
- isLoadingCreateOrganization.value || isLoadingCreateSubsidiary.value,
- );
/**
* Submit dialog form based on organization level
- * If `company`, create a new company
+ * If `company`, create a new company (and subsidiary)
* If `team`, create a new team
* @returns {Promise}
*/
@@ -230,6 +231,22 @@ export default defineComponent({
logger?.debug(
`New organization was created with ID <${organizationData.id}> and name <${organizationData.name}>.`,
);
+ /**
+ * Set modelValue (organization ID)
+ * ! Careful, this automatically resets subsidiary ID to null
+ * Subsidiary creation is done after this and result
+ * is manually appended to subsidiay options array.
+ */
+ logger?.debug(
+ `Updating organization model ID from <${inputValue.value}> to <${organizationData.id}>`,
+ );
+ inputValue.value = organizationData.id;
+ /**
+ * Emit `create:option` event
+ * This appends new organization to the options list.
+ */
+ emit('create:option', organizationData);
+
// create subsidiary
logger?.info('Create subsidiary.');
const subsidiaryData = await createSubsidiary(
@@ -238,36 +255,21 @@ export default defineComponent({
);
if (subsidiaryData?.id) {
- /**
- * We are not using early return because if creating subsidiary fails
- * we still want to run subsequent actions (setting modelValue).
- */
logger?.debug(
`New subsidiary was created with data <${JSON.stringify(subsidiaryData, null, 2)}>.`,
);
// set subsidiary ID in store
- subsidiaryId.value = subsidiaryData.id;
+ logger?.debug(
+ `Updating subsidiary ID from <${subsidiaryId.value}> to <${subsidiaryData.id}>`,
+ );
+ registerChallengeStore.setSubsidiaryId(subsidiaryData.id);
logger?.debug(`Subsidiary ID model set to <${subsidiaryId.value}>.`);
} else {
logger?.error('New subsidiary data not found.');
}
+ // emit event to append data to subsidiary options
+ emit('create:subsidiary', subsidiaryData);
- /**
- * Set modelValue (organization ID)
- * This will save the organization ID into the store.
- * We are waiting until createSubsidiary action is done because saving
- * modelValue triggers reloading subsidiary options list and so we
- * do not need to manually append the new subsidiary option.
- */
- inputValue.value = organizationData.id;
- logger?.debug(
- `New organization model ID set to <${inputValue.value}>.`,
- );
- /**
- * Emit `create:option` event
- * This will add new organization to the options list.
- */
- emit('create:option', organizationData);
// close dialog
onClose();
} else if (props.organizationLevel === OrganizationLevel.team) {
diff --git a/src/components/form/FormSelectOrganization.vue b/src/components/form/FormSelectOrganization.vue
index bae5f0023..ed24eedc4 100644
--- a/src/components/form/FormSelectOrganization.vue
+++ b/src/components/form/FormSelectOrganization.vue
@@ -18,7 +18,7 @@
*/
// libraries
-import { defineComponent, computed, inject, watch, ref } from 'vue';
+import { defineComponent, computed, inject, nextTick, watch, ref } from 'vue';
// components
import FormFieldSelectTable from '../form/FormFieldSelectTable.vue';
@@ -33,8 +33,12 @@ import { OrganizationLevel, OrganizationType } from '../types/Organization';
// types
import type { FormSelectOption } from '../types/Form';
import type { Logger } from '../types/Logger';
-import type { OrganizationOption } from '../types/Organization';
+import type {
+ OrganizationOption,
+ OrganizationSubsidiary,
+} from '../types/Organization';
import type { PostOrganizationResponse } from '../types/apiOrganization';
+
// stores
import { useRegisterChallengeStore } from 'src/stores/registerChallenge';
@@ -46,6 +50,11 @@ export default defineComponent({
},
setup() {
const logger = inject('vuejs3-logger') as Logger | null;
+ // template ref for form-field-company-address
+ const formFieldCompanyAddressRef = ref<
+ typeof FormFieldCompanyAddress | null
+ >(null);
+
const opts = ref([]);
const formFieldSelectTableRef = ref(null);
const { options, organizations, isLoading, loadOrganizations } =
@@ -93,12 +102,30 @@ export default defineComponent({
formFieldSelectTableRef.value.selectOrganizationRef.validate();
};
- const onCreateOption = (data: PostOrganizationResponse): void => {
+ const onCreateOption = async (
+ data: PostOrganizationResponse,
+ ): Promise => {
const newOrganization: OrganizationOption = data;
logger?.debug(
`Add new organization to organizations array <${JSON.stringify(newOrganization, null, 2)}>.`,
);
organizations.value.push(newOrganization);
+ logger?.debug(
+ `Organizations array updated to <${JSON.stringify(organizations.value, null, 2)}>.`,
+ );
+ // lazy load new options
+ await nextTick();
+ opts.value = options.value;
+ };
+
+ const onCreateSubsidiary = (data: OrganizationSubsidiary): void => {
+ logger?.debug(
+ `Append new subsidiary <${JSON.stringify(data, null, 2)}> to subsidiary options.`,
+ );
+ // append new subsidiary to subsidiary options
+ if (formFieldCompanyAddressRef.value) {
+ formFieldCompanyAddressRef.value.appendSubsidiaryResults([data]);
+ }
};
return {
@@ -111,6 +138,7 @@ export default defineComponent({
organizationType,
onCloseAddSubsidiaryDialog,
onCreateOption,
+ onCreateSubsidiary,
};
},
});
@@ -127,9 +155,11 @@ export default defineComponent({
:data-organization-type="organizationType"
ref="formFieldSelectTableRef"
@create:option="onCreateOption"
+ @create:subsidiary="onCreateSubsidiary"
data-cy="form-select-table-company"
/>
;
isLoading: Ref;
loadSubsidiaries: (organizationId: number) => Promise;
+ appendSubsidiaryResults: (results: OrganizationSubsidiary[]) => void;
};
/**
@@ -78,7 +79,7 @@ export const useApiGetSubsidiaries = (
});
if (data?.results?.length) {
- subsidiaries.value.push(...data.results);
+ appendSubsidiaryResults(data.results);
}
// if data has multiple pages, fetch all pages
@@ -112,7 +113,7 @@ export const useApiGetSubsidiaries = (
// store results
if (data?.results?.length) {
- subsidiaries.value.push(...data.results);
+ appendSubsidiaryResults(data.results);
}
// if data has multiple pages, fetch all pages
@@ -121,9 +122,14 @@ export const useApiGetSubsidiaries = (
}
};
+ const appendSubsidiaryResults = (results: OrganizationSubsidiary[]) => {
+ subsidiaries.value.push(...results);
+ };
+
return {
subsidiaries,
isLoading,
loadSubsidiaries,
+ appendSubsidiaryResults,
};
};
diff --git a/test/cypress/fixtures/apiGetSubsidiariesResponseNew.json b/test/cypress/fixtures/apiGetSubsidiariesResponseNew.json
new file mode 100644
index 000000000..a5ba31ff9
--- /dev/null
+++ b/test/cypress/fixtures/apiGetSubsidiariesResponseNew.json
@@ -0,0 +1,18 @@
+{
+ "count": 1,
+ "next": null,
+ "previous": null,
+ "results": [
+ {
+ "id": 5351,
+ "address": {
+ "street": "Subsidiary Street",
+ "street_number": "1",
+ "recipient": "Subsidiary Recipient",
+ "city": "Subsidiary City",
+ "psc": 12345
+ },
+ "teams": []
+ }
+ ]
+}
diff --git a/test/cypress/fixtures/formFieldCompany.json b/test/cypress/fixtures/formFieldCompany.json
index 22a69584b..f76fbc3d2 100644
--- a/test/cypress/fixtures/formFieldCompany.json
+++ b/test/cypress/fixtures/formFieldCompany.json
@@ -1,5 +1,5 @@
{
- "count": 10,
+ "count": 20,
"next": "https://test.dopracenakole.cz/rest/organizations/company/?limit=10&offset=10",
"previous": null,
"results": [
diff --git a/test/cypress/fixtures/formFieldCompanyNext.json b/test/cypress/fixtures/formFieldCompanyNext.json
index ebb2e7e33..90d447af1 100644
--- a/test/cypress/fixtures/formFieldCompanyNext.json
+++ b/test/cypress/fixtures/formFieldCompanyNext.json
@@ -1,5 +1,5 @@
{
- "count": 10,
+ "count": 20,
"next": null,
"previous": null,
"results": [
diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js
index 79db117a2..a8de7d987 100644
--- a/test/cypress/support/commands.js
+++ b/test/cypress/support/commands.js
@@ -1450,7 +1450,7 @@ Cypress.Commands.add('applyInvalidVoucher', (config, i18n) => {
* Wait for intercept organization creation API call and compare request/response object
* Wait for `@createOrganization` intercept
*/
-Cypress.Commands.add('waitForOrganizationCreateApi', () => {
+Cypress.Commands.add('waitForOrganizationPostApi', () => {
cy.fixture('formFieldCompanyCreateRequest').then(
(formFieldCompanyCreateRequest) => {
cy.fixture('formFieldCompanyCreate').then(
@@ -1475,3 +1475,90 @@ Cypress.Commands.add('waitForOrganizationCreateApi', () => {
},
);
});
+
+/**
+ * Fill organization and subsidiary form with data
+ * @param {Object} formFieldCompanyCreateRequest - Organization data
+ * @param {Object} apiPostSubsidiaryRequest - Subsidiary data
+ */
+Cypress.Commands.add(
+ 'fillOrganizationSubsidiaryForm',
+ (formFieldCompanyCreateRequest, apiPostSubsidiaryRequest) => {
+ // fill organization data
+ cy.dataCy('form-add-company-name')
+ .find('input')
+ .type(formFieldCompanyCreateRequest.name);
+ cy.dataCy('form-add-company-vat-id')
+ .find('input')
+ .type(formFieldCompanyCreateRequest.vatId);
+ // fill subsidiary address data
+ cy.dataCy('form-add-subsidiary-street')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.street);
+ cy.dataCy('form-add-subsidiary-house-number')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.street_number);
+ cy.dataCy('form-add-subsidiary-city')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.city);
+ cy.dataCy('form-add-subsidiary-zip')
+ .find('input')
+ .type(apiPostSubsidiaryRequest.address.psc);
+ cy.dataCy('form-add-subsidiary-department').type(
+ apiPostSubsidiaryRequest.address.recipient,
+ );
+ // select city challenge
+ cy.dataCy('form-add-subsidiary-city-challenge').click();
+ cy.get('.q-menu').should('be.visible').find('.q-item').first().click();
+ },
+);
+
+/**
+ * Intercept subsidiaries GET API call for newly created subsidiary
+ * Provides `@getSubsidiariesNew` alias
+ * @param {Object} config - App global config
+ * @param {Object|String} i18n - i18n instance or locale lang string e.g. en
+ * @param {Number} organizationId - Organization ID
+ */
+Cypress.Commands.add(
+ 'interceptSubsidiariesNewGetApi',
+ (config, i18n, organizationId) => {
+ const { apiBase, apiDefaultLang, urlApiOrganizations, urlApiSubsidiaries } =
+ config;
+ const apiBaseUrl = getApiBaseUrlWithLang(
+ null,
+ apiBase,
+ apiDefaultLang,
+ i18n,
+ );
+ const urlApiSubsidiariesLocalized = `${apiBaseUrl}${urlApiOrganizations}${organizationId}/${urlApiSubsidiaries}`;
+
+ cy.fixture('apiGetSubsidiariesResponseNew').then(
+ (subsidiariesResponseNew) => {
+ // intercept subsidiaries API call
+ cy.intercept('GET', urlApiSubsidiariesLocalized, {
+ statusCode: httpSuccessfullStatus,
+ body: subsidiariesResponseNew,
+ }).as('getSubsidiariesNew');
+ },
+ );
+ },
+);
+
+/**
+ * Wait for intercept subsidiaries API call and compare request/response object
+ * Wait for `@getSubsidiariesNew` intercept
+ */
+Cypress.Commands.add('waitForSubsidiariesNewApi', () => {
+ cy.fixture('apiGetSubsidiariesResponseNew').then(
+ (subsidiariesResponseNew) => {
+ cy.wait('@getSubsidiariesNew').then(({ request, response }) => {
+ expect(request.headers.authorization).to.include(bearerTokeAuth);
+ if (response) {
+ expect(response.statusCode).to.equal(httpSuccessfullStatus);
+ expect(response.body).to.deep.equal(subsidiariesResponseNew);
+ }
+ });
+ },
+ );
+});
From b0ada87b0b3cec59266831d9b6059f6b2d189f4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=CC=8Cimon=20Macek?=
Date: Mon, 23 Dec 2024 22:50:24 +0100
Subject: [PATCH 07/20] update loading subsidiaries via store and post-creation
update
---
.../__tests__/FormSelectOrganization.cy.js | 7 ---
.../form/FormFieldCompanyAddress.vue | 41 +++------------
src/components/form/FormFieldSelectTable.vue | 43 +++++++++++-----
.../form/FormSelectOrganization.vue | 34 ++++++-------
src/stores/registerChallenge.ts | 23 ++++++++-
test/cypress/support/commands.js | 50 -------------------
6 files changed, 73 insertions(+), 125 deletions(-)
diff --git a/src/components/__tests__/FormSelectOrganization.cy.js b/src/components/__tests__/FormSelectOrganization.cy.js
index 3d8dd27de..826f4ce9c 100644
--- a/src/components/__tests__/FormSelectOrganization.cy.js
+++ b/src/components/__tests__/FormSelectOrganization.cy.js
@@ -76,11 +76,6 @@ describe('', () => {
context('create new organization and subsidiary', () => {
beforeEach(() => {
cy.interceptCitiesGetApi(rideToWorkByBikeConfig, i18n);
- cy.interceptSubsidiariesNewGetApi(
- rideToWorkByBikeConfig,
- i18n,
- organizationId,
- );
interceptOrganizationsApi(
rideToWorkByBikeConfig,
i18n,
@@ -156,8 +151,6 @@ describe('', () => {
cy.wrap(subsidiaryId)
.its('value')
.should('equal', apiPostSubsidiaryResponse.id);
- // check that subsidiaries were refetched
- cy.waitForSubsidiariesNewApi();
// check that the subsidary (address) input has correct value
cy.dataCy('form-company-address').should(
'contain',
diff --git a/src/components/form/FormFieldCompanyAddress.vue b/src/components/form/FormFieldCompanyAddress.vue
index b9e9d931c..62099e110 100644
--- a/src/components/form/FormFieldCompanyAddress.vue
+++ b/src/components/form/FormFieldCompanyAddress.vue
@@ -33,7 +33,6 @@ import DialogDefault from 'src/components/global/DialogDefault.vue';
import FormAddSubsidiary from 'src/components/form/FormAddSubsidiary.vue';
// composables
-import { useApiGetSubsidiaries } from 'src/composables/useApiGetSubsidiaries';
import { useValidation } from 'src/composables/useValidation';
import { useApiPostSubsidiary } from '../../composables/useApiPostSubsidiary';
@@ -86,19 +85,16 @@ export default defineComponent({
) as FormCompanyAddressFields,
);
- const {
- subsidiaries,
- isLoading: isLoadingSubsidiaries,
- appendSubsidiaryResults,
- loadSubsidiaries,
- } = useApiGetSubsidiaries(logger);
+ const store = useRegisterChallengeStore();
+
+ const isLoadingSubsidiaries = computed(() => store.isLoadingSubsidiaries);
+ const subsidiaries = computed(() => store.subsidiaries);
+
const { isLoading: isLoadingCreateSubsidiary, createSubsidiary } =
useApiPostSubsidiary(logger);
const isLoading = computed(
() => isLoadingSubsidiaries.value || isLoadingCreateSubsidiary.value,
);
-
- const store = useRegisterChallengeStore();
/**
* If organization ID is set, load subsidiaries.
* This ensures, that options are loaded on page refresh.
@@ -106,7 +102,7 @@ export default defineComponent({
onMounted(async () => {
if (store.getOrganizationId) {
logger?.info('Loading subsidiaries.');
- await loadSubsidiaries(store.getOrganizationId);
+ await store.loadSubsidiariesToStore(logger);
} else {
logger?.debug(
`Organization was not selected <${store.getOrganizationId}>,` +
@@ -114,31 +110,9 @@ export default defineComponent({
);
}
});
- /**
- * Watch for organization ID changes.
- * This clears the subsidiary options and state after organization change.
- * Then loads subsidiaries for the new organization.
- * Should not be triggered on mounted not to clear saved subsidiary ID.
- */
- watch(
- () => store.getOrganizationId,
- (newValue) => {
- logger?.debug(
- `Register challenge store organization ID updated to <${newValue}>.`,
- );
- subsidiaryId.value = null;
- logger?.debug(
- `Subsidiary ID reset to <${subsidiaryId.value}> as organization ID changed.`,
- );
- if (newValue) {
- logger?.info('Loading subsidiaries.');
- loadSubsidiaries(newValue);
- }
- },
- );
const options = computed(() =>
- subsidiaries.value?.map((subsidiary) => ({
+ store.getSubsidiaries?.map((subsidiary) => ({
label: getAddressString(subsidiary.address),
value: subsidiary.id,
})),
@@ -264,7 +238,6 @@ export default defineComponent({
isLoadingCreateSubsidiary,
onClose,
onSubmit,
- appendSubsidiaryResults,
};
},
});
diff --git a/src/components/form/FormFieldSelectTable.vue b/src/components/form/FormFieldSelectTable.vue
index cd5bc42eb..56e8b2422 100644
--- a/src/components/form/FormFieldSelectTable.vue
+++ b/src/components/form/FormFieldSelectTable.vue
@@ -74,6 +74,7 @@ import {
FormSelectTableOption,
FormTeamFields,
} from '../types/Form';
+import type { OrganizationSubsidiary } from '../types/Organization';
// utils
import { deepObjectWithSimplePropsCopy } from '../../utils';
@@ -121,7 +122,7 @@ export default defineComponent({
default: null,
},
},
- emits: ['update:modelValue', 'create:option', 'create:subsidiary'],
+ emits: ['update:modelValue', 'create:option'],
setup(props, { emit }) {
const logger = inject('vuejs3-logger') as Logger | null;
@@ -204,6 +205,14 @@ export default defineComponent({
registerChallengeStore.setSubsidiaryId(value),
});
+ const onChangeOption = (value: number | null): void => {
+ logger?.debug(`Organization option changed <${value}>`);
+ logger?.debug('Resetting subsidiary ID to null');
+ registerChallengeStore.setSubsidiaryId(null);
+ logger?.debug('Reloading subsidiaries');
+ registerChallengeStore.loadSubsidiariesToStore(logger);
+ };
+
/**
* Submit dialog form based on organization level
* If `company`, create a new company (and subsidiary)
@@ -231,20 +240,14 @@ export default defineComponent({
logger?.debug(
`New organization was created with ID <${organizationData.id}> and name <${organizationData.name}>.`,
);
- /**
- * Set modelValue (organization ID)
- * ! Careful, this automatically resets subsidiary ID to null
- * Subsidiary creation is done after this and result
- * is manually appended to subsidiay options array.
- */
+
logger?.debug(
`Updating organization model ID from <${inputValue.value}> to <${organizationData.id}>`,
);
+ // set organization ID in store
inputValue.value = organizationData.id;
- /**
- * Emit `create:option` event
- * This appends new organization to the options list.
- */
+
+ // emit event to append data to organization options
emit('create:option', organizationData);
// create subsidiary
@@ -264,11 +267,23 @@ export default defineComponent({
);
registerChallengeStore.setSubsidiaryId(subsidiaryData.id);
logger?.debug(`Subsidiary ID model set to <${subsidiaryId.value}>.`);
+ // create a new subsidiary array in store
+ const newSubsidiary: OrganizationSubsidiary = {
+ id: subsidiaryData.id,
+ address: {
+ street: subsidiaryData.street,
+ houseNumber: subsidiaryData.houseNumber,
+ city: subsidiaryData.city,
+ zip: subsidiaryData.zip,
+ cityChallenge: subsidiaryData.cityChallenge,
+ department: subsidiaryData.department,
+ },
+ teams: [],
+ };
+ registerChallengeStore.setSubsidiaries([newSubsidiary]);
} else {
logger?.error('New subsidiary data not found.');
}
- // emit event to append data to subsidiary options
- emit('create:subsidiary', subsidiaryData);
// close dialog
onClose();
@@ -369,6 +384,7 @@ export default defineComponent({
isLoading,
onClose,
onSubmit,
+ onChangeOption,
OrganizationType,
OrganizationLevel,
selectOrganizationRef,
@@ -440,6 +456,7 @@ export default defineComponent({
:label="item.label"
color="primary"
data-cy="form-select-table-option"
+ @update:model-value="onChangeOption"
/>