diff --git a/webroot/src/app.config.ts b/webroot/src/app.config.ts index b1088e695..ff7cb7617 100644 --- a/webroot/src/app.config.ts +++ b/webroot/src/app.config.ts @@ -169,6 +169,13 @@ export enum ServerApiTypes { API_EXAMPLE = 'Example', } +export enum MilitaryAuditStatusTypes { + NOT_APPLICABLE = 'notApplicable', + APPROVED = 'approved', + DECLINED = 'declined', + TENTATIVE = 'tentative', +} + // ============================= // = Server Date Formats = // ============================= diff --git a/webroot/src/components/Icons/ClockStatus/ClockStatus.less b/webroot/src/components/Icons/ClockStatus/ClockStatus.less new file mode 100644 index 000000000..c4de35879 --- /dev/null +++ b/webroot/src/components/Icons/ClockStatus/ClockStatus.less @@ -0,0 +1,6 @@ +// +// ClockStatus.less +// CompactConnect +// +// Created by InspiringApps on 1/7/2026. +// diff --git a/webroot/src/components/Icons/ClockStatus/ClockStatus.spec.ts b/webroot/src/components/Icons/ClockStatus/ClockStatus.spec.ts new file mode 100644 index 000000000..df90164df --- /dev/null +++ b/webroot/src/components/Icons/ClockStatus/ClockStatus.spec.ts @@ -0,0 +1,19 @@ +// +// ClockStatus.spec.ts +// CompactConnect +// +// Created by InspiringApps on 1/7/2026. +// + +import { expect } from 'chai'; +import { mountShallow } from '@tests/helpers/setup'; +import ClockStatus from '@components/Icons/ClockStatus/ClockStatus.vue'; + +describe('ClockStatus component', async () => { + it('should mount the component', async () => { + const wrapper = await mountShallow(ClockStatus); + + expect(wrapper.exists()).to.equal(true); + expect(wrapper.findComponent(ClockStatus).exists()).to.equal(true); + }); +}); diff --git a/webroot/src/components/Icons/ClockStatus/ClockStatus.ts b/webroot/src/components/Icons/ClockStatus/ClockStatus.ts new file mode 100644 index 000000000..2c40f6e64 --- /dev/null +++ b/webroot/src/components/Icons/ClockStatus/ClockStatus.ts @@ -0,0 +1,18 @@ +// +// ClockStatus.ts +// CompactConnect +// +// Created by InspiringApps on 1/7/2026. +// + +import { Component, Vue, toNative } from 'vue-facing-decorator'; + +@Component({ + name: 'ClockStatus', +}) +class ClockStatus extends Vue { +} + +export default toNative(ClockStatus); + +// export default ClockStatus; diff --git a/webroot/src/components/Icons/ClockStatus/ClockStatus.vue b/webroot/src/components/Icons/ClockStatus/ClockStatus.vue new file mode 100644 index 000000000..ce1f918d1 --- /dev/null +++ b/webroot/src/components/Icons/ClockStatus/ClockStatus.vue @@ -0,0 +1,61 @@ + + + + + + diff --git a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts index 00ee052dd..13c33c34b 100644 --- a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts +++ b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts @@ -383,7 +383,7 @@ class LicenseeSearch extends mixins(MixinForm) { this.formData.privilegeState.value = 'co'; this.formData.privilegePurchaseStartDate.value = moment().startOf('month').format('YYYY-MM-DD'); this.formData.privilegePurchaseEndDate.value = moment().endOf('month').format('YYYY-MM-DD'); - // this.formData.militaryStatus.value = 'approved'; // @TODO: Adding this in next PR with military status updates + this.formData.militaryStatus.value = 'approved'; this.formData.investigationStatus.value = 'underInvestigation'; this.formData.encumberStartDate.value = moment().startOf('month').format('YYYY-MM-DD'); this.formData.encumberEndDate.value = moment().endOf('month').format('YYYY-MM-DD'); diff --git a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue index 07fda10bc..eb8d80ceb 100644 --- a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue +++ b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue @@ -94,12 +94,12 @@ aria-labelledby="privilege-purchase-dates-label" /> - +
{ + return this.licensee?.militaryAffiliations || []; + } + get affiliationType(): string { - let militaryStatus = ''; + let affiliation = ''; if (this.licensee) { const activeAffiliation = this.licensee.activeMilitaryAffiliation() as any; const isMilitary = this.licensee.isMilitaryStatusActive(); if (isMilitary && activeAffiliation?.affiliationType === 'militaryMember') { - militaryStatus = this.$tm('military.affiliationTypes.militaryMember'); + affiliation = this.$tm('military.affiliationTypes.militaryMember'); } else if (isMilitary && activeAffiliation?.affiliationType === 'militaryMemberSpouse') { - militaryStatus = this.$tm('military.affiliationTypes.militaryMemberSpouse'); + affiliation = this.$tm('military.affiliationTypes.militaryMemberSpouse'); } else { - militaryStatus = this.$tm('military.affiliationTypes.none'); + affiliation = this.$tm('military.affiliationTypes.none'); } } - return militaryStatus; + return affiliation; + } + + get auditStatusTypes(): typeof MilitaryAuditStatusTypes { + return MilitaryAuditStatusTypes; + } + + get auditStatus(): string { + return this.licensee?.militaryStatus || ''; + } + + get auditStatusName(): string { + return this.licensee?.militaryAuditStatusName() || ''; + } + + get isAuditStatusApproved(): boolean { + return this.auditStatus === MilitaryAuditStatusTypes.APPROVED; + } + + get isAuditStatusDeclined(): boolean { + return this.auditStatus === MilitaryAuditStatusTypes.DECLINED; + } + + get isAuditStatusPending(): boolean { + return this.auditStatus === MilitaryAuditStatusTypes.TENTATIVE; + } + + get shouldShowAuditButtons(): boolean { + return Boolean(this.currentCompactType && this.licenseeId && this.affiliations.length); + } + + get auditStatusNote(): string { + return this.licensee?.militaryStatusNote || ''; } get militaryDocumentHeader(): object { @@ -149,10 +205,6 @@ class MilitaryAffiliationInfoBlock extends mixins(MixinForm) { return this.isStatusActive || this.isStatusInitializing; } - get affiliations(): Array { - return this.licensee?.militaryAffiliations || []; - } - get sortOptions(): Array { return []; // Sorting not API supported } @@ -161,54 +213,153 @@ class MilitaryAffiliationInfoBlock extends mixins(MixinForm) { // Methods // initFormInputs(): void { + if (this.shouldShowMilitaryAuditApproveModal) { + this.initFormInputsAuditApprove(); + } else if (this.shouldShowMilitaryAuditDeclineModal) { + this.initFormInputsAuditDecline(); + } else if (this.shouldShowEndAffiliationModal) { + this.initFormInputsEndAffiliation(); + } + } + + // ======================================================= + // AUDIT STATUS + // ======================================================= + initFormInputsAuditApprove(): void { this.formData = reactive({ submitEnd: new FormInput({ isSubmitInput: true, - id: 'submit-end', + id: 'submit-approve', }), }); } - sortingChange(): boolean { - return false; // Sorting not API supported + initFormInputsAuditDecline(): void { + this.formData = reactive({ + auditDeclineNotes: new FormInput({ + id: 'decline-notes', + name: 'decline-notes', + label: computed(() => this.$t('military.auditDeclineConfirmNotesLabel')), + placeholder: computed(() => this.$t('military.auditDeclineConfirmNotesPlaceholder')), + validation: Joi.string().max(256).allow('', null).messages(this.joiMessages.string), + enforceMax: true, + }), + submitEnd: new FormInput({ + isSubmitInput: true, + id: 'submit-decline', + }), + }); } - paginationChange(): boolean { - return false; // Pagination not API supported + toggleMilitaryAuditApproveModal(): void { + this.shouldShowMilitaryAuditApproveModal = !this.shouldShowMilitaryAuditApproveModal; + this.isFormError = false; + this.modalErrorMessage = ''; + this.initFormInputs(); } - editInfo(): void { - if (this.currentCompactType) { - this.$router.push({ - name: 'MilitaryStatusUpdate', - params: { compact: this.currentCompactType } - }); + focusTrapAuditApprove(event: KeyboardEvent): void { + const firstTabIndex = document.getElementById('audit-approve-cancel-button'); + const lastTabIndex = document.getElementById(this.formData.submitEnd.id); + + if (event.shiftKey) { + // shift + tab to last input + if (document.activeElement === firstTabIndex) { + lastTabIndex?.focus(); + event.preventDefault(); + } + } else if (document.activeElement === lastTabIndex) { + // Tab to first input + firstTabIndex?.focus(); + event.preventDefault(); } } - async confirmEndMilitaryAffiliation(): Promise { - this.closeEndAffiliationModal(); - await this.$store.dispatch('user/endMilitaryAffiliationRequest'); - await this.$store.dispatch('user/getLicenseeAccountRequest'); + toggleMilitaryAuditDeclineModal(): void { + this.shouldShowMilitaryAuditDeclineModal = !this.shouldShowMilitaryAuditDeclineModal; + this.isFormError = false; + this.modalErrorMessage = ''; + this.initFormInputs(); } - startEndAffiliationFlow(): void { - this.shouldShowEndAffiliationModal = true; + focusTrapAuditDecline(event: KeyboardEvent): void { + const firstTabIndex = document.getElementById(this.formData.auditDeclineNotes.id); + const lastTabIndex = document.getElementById(this.formData.submitEnd.id); + + if (event.shiftKey) { + // shift + tab to last input + if (document.activeElement === firstTabIndex) { + lastTabIndex?.focus(); + event.preventDefault(); + } + } else if (document.activeElement === lastTabIndex) { + // Tab to first input + firstTabIndex?.focus(); + event.preventDefault(); + } } - focusOnModalCancelButton(): void { - const buttonComponent = this.$refs.noBackButton as InstanceType; - const button = buttonComponent?.$refs.button as HTMLElement; + async auditSubmit(auditAction: MilitaryAuditStatusTypes): Promise { + this.validateAll({ asTouched: true }); - button?.focus(); + if (this.isFormValid) { + this.startFormLoading(); + this.modalErrorMessage = ''; + + const { currentCompactType, licenseeId, formData } = this; + const payload: { militaryStatus: MilitaryAuditStatusTypes, militaryStatusNote?: string } = { + militaryStatus: auditAction, + }; + + if (formData.auditDeclineNotes?.value) { + payload.militaryStatusNote = formData.auditDeclineNotes.value; + } + + await this.$store.dispatch('users/updateMilitaryAuditRequest', { + compact: currentCompactType, + licenseeId, + data: payload, + }).catch((err) => { + this.modalErrorMessage = err?.message || this.$t('common.error'); + this.isFormError = true; + }); + + if (!this.isFormError) { + this.isFormSuccessful = true; + await this.$store.dispatch('license/getLicenseeRequest', { + compact: currentCompactType, + licenseeId + }).catch(() => { + // Continue + }); + this.shouldShowMilitaryAuditApproveModal = false; + this.shouldShowMilitaryAuditDeclineModal = false; + } + + this.endFormLoading(); + } } - closeEndAffiliationModal(): void { - this.shouldShowEndAffiliationModal = false; - this.$store.dispatch('setModalIsOpen', false); + // ======================================================= + // END AFFILIATION + // ======================================================= + initFormInputsEndAffiliation(): void { + this.formData = reactive({ + submitEnd: new FormInput({ + isSubmitInput: true, + id: 'submit-end', + }), + }); } - focusTrap(event: KeyboardEvent): void { + startEndAffiliationFlow(): void { + this.shouldShowEndAffiliationModal = true; + this.isFormError = false; + this.modalErrorMessage = ''; + this.initFormInputs(); + } + + focusTrapEndAffiliation(event: KeyboardEvent): void { const firstTabIndex = document.getElementById('no-back-button'); const lastTabIndex = document.getElementById(this.formData.submitEnd.id); @@ -224,6 +375,41 @@ class MilitaryAffiliationInfoBlock extends mixins(MixinForm) { event.preventDefault(); } } + + focusOnModalCancelButton(): void { + const buttonComponent = this.$refs.noBackButton as InstanceType; + const button = buttonComponent?.$refs.button as HTMLElement; + + button?.focus(); + } + + async confirmEndMilitaryAffiliation(): Promise { + this.closeEndAffiliationModal(); + await this.$store.dispatch('user/endMilitaryAffiliationRequest'); + await this.$store.dispatch('user/getLicenseeAccountRequest'); + } + + closeEndAffiliationModal(): void { + this.shouldShowEndAffiliationModal = false; + this.$store.dispatch('setModalIsOpen', false); + } + + sortingChange(): boolean { + return false; // Sorting not API supported + } + + paginationChange(): boolean { + return false; // Pagination not API supported + } + + editInfo(): void { + if (this.currentCompactType) { + this.$router.push({ + name: 'MilitaryStatusUpdate', + params: { compact: this.currentCompactType } + }); + } + } } export default toNative(MilitaryAffiliationInfoBlock); diff --git a/webroot/src/components/MilitaryAffiliationInfoBlock/MilitaryAffiliationInfoBlock.vue b/webroot/src/components/MilitaryAffiliationInfoBlock/MilitaryAffiliationInfoBlock.vue index 2a693df39..c6666dc57 100644 --- a/webroot/src/components/MilitaryAffiliationInfoBlock/MilitaryAffiliationInfoBlock.vue +++ b/webroot/src/components/MilitaryAffiliationInfoBlock/MilitaryAffiliationInfoBlock.vue @@ -8,13 +8,50 @@ diff --git a/webroot/src/locales/en.json b/webroot/src/locales/en.json index b9cf9ccd2..8ce476869 100644 --- a/webroot/src/locales/en.json +++ b/webroot/src/locales/en.json @@ -940,18 +940,29 @@ "militaryStatusTitle": "Military status", "updateMilitaryStatusTitle": "Update military status", "viewMilitaryStatus": "View military status", + "auditStatus": "Audit status", + "auditApprove": "Approve", + "auditApproveConfirmTitle": "Are you sure you want to approve this practitioner’s military status?", + "auditApproveConfirmDescription": "Approving confirms their submitted documentation and military-related benefits.", + "auditApproveConfirmAction": "Yes, approve status", + "auditDecline": "Decline", + "auditDeclineConfirmTitle": "Are you sure you want to decline this practitioner’s military status?", + "auditDeclineConfirmDescription": "Declining will revoke the user’s military status and remove their discount eligibility.", + "auditDeclineConfirmNotesLabel": "Decline notes for practitioner", + "auditDeclineConfirmNotesPlaceholder": "Enter the decline reason", + "auditDeclineConfirmAction": "Yes, decline status", "militaryStatusOptions": [ { - "key": "pending", - "name": "Pending" + "key": "tentative", + "name": "Needs review" }, { "key": "approved", "name": "Approved" }, { - "key": "denied", - "name": "Denied" + "key": "declined", + "name": "Declined" }, { "key": "notApplicable", diff --git a/webroot/src/locales/es.json b/webroot/src/locales/es.json index c8d42776a..105c1d7f0 100644 --- a/webroot/src/locales/es.json +++ b/webroot/src/locales/es.json @@ -923,18 +923,29 @@ "military": { "militaryStatusTitle": "Estado militar", "updateMilitaryStatusTitle": "Actualizar el estado militar", + "auditStatus": "Estado de la auditoría", + "auditApprove": "Aprobar", + "auditApproveConfirmTitle": "¿Está seguro de que desea aprobar el estado militar de este practicante?", + "auditApproveConfirmDescription": "Al aprobar se confirma la documentación presentada y los beneficios relacionados con el servicio militar.", + "auditApproveConfirmAction": "Sí, aprobar estado", + "auditDecline": "Rechazar", + "auditDeclineConfirmTitle": "¿Está seguro de que desea rechazar el estado militar de este practicante?", + "auditDeclineConfirmDescription": "Si rechaza la solicitud, se revocará el estado militar del usuario y se eliminará su elegibilidad para el descuento.", + "auditDeclineConfirmNotesLabel": "Notas de rechazo para el practicante", + "auditDeclineConfirmNotesPlaceholder": "Ingrese el motivo del rechazo", + "auditDeclineConfirmAction": "Sí, rechazar estado", "militaryStatusOptions": [ { - "key": "pending", - "name": "Pendiente" + "key": "tentative", + "name": "Necesita revisión" }, { "key": "approved", "name": "Aprobado" }, { - "key": "denied", - "name": "Denegado" + "key": "declined", + "name": "Rechazado" }, { "key": "notApplicable", diff --git a/webroot/src/models/Licensee/Licensee.model.spec.ts b/webroot/src/models/Licensee/Licensee.model.spec.ts index 71b5c8f93..e69de2ff0 100644 --- a/webroot/src/models/Licensee/Licensee.model.spec.ts +++ b/webroot/src/models/Licensee/Licensee.model.spec.ts @@ -60,6 +60,8 @@ describe('Licensee model', () => { expect(licensee.privilegeStates).to.matchPattern([]); expect(licensee.privileges).to.matchPattern([]); expect(licensee.militaryAffiliations).to.matchPattern([]); + expect(licensee.militaryStatus).to.equal(null); + expect(licensee.militaryStatusNote).to.equal(null); expect(licensee.lastUpdated).to.equal(null); expect(licensee.status).to.equal(LicenseeStatus.INACTIVE); @@ -78,6 +80,7 @@ describe('Licensee model', () => { expect(licensee.phoneNumberDisplay()).to.equal(''); expect(licensee.isMilitaryStatusActive()).to.equal(false); expect(licensee.activeMilitaryAffiliation()).to.equal(null); + expect(licensee.militaryAuditStatusName()).to.equal(''); expect(licensee.homeJurisdictionLicenses()).to.matchPattern([]); expect(licensee.activeHomeJurisdictionLicenses()).to.matchPattern([]); expect(licensee.inactiveHomeJurisdictionLicenses()).to.matchPattern([]); @@ -111,6 +114,8 @@ describe('Licensee model', () => { birthMonthDay: '01-16', ssnLastFour: '0000', militaryAffiliations: [new MilitaryAffiliation()], + militaryStatus: 'approved', + militaryStatusNote: 'test-note', licenseType: LicenseType.AUDIOLOGIST, licenseStates: [new State()], licenses: [ @@ -172,6 +177,8 @@ describe('Licensee model', () => { expect(licensee.privilegeStates[0]).to.be.an.instanceof(State); expect(licensee.privileges).to.be.an('array').with.length(1); expect(licensee.privileges[0]).to.be.an.instanceof(License); + expect(licensee.militaryStatus).to.equal(data.militaryStatus); + expect(licensee.militaryStatusNote).to.equal(data.militaryStatusNote); expect(licensee.lastUpdated).to.equal(data.lastUpdated); expect(licensee.status).to.equal(data.status); @@ -190,6 +197,7 @@ describe('Licensee model', () => { expect(licensee.phoneNumberDisplay()).to.equal('+1 323-455-8990'); expect(licensee.isMilitaryStatusActive()).to.equal(false); expect(licensee.activeMilitaryAffiliation()).to.equal(null); + expect(licensee.militaryAuditStatusName()).to.equal('Approved'); expect(licensee.homeJurisdictionLicenses()).to.matchPattern([]); expect(licensee.activeHomeJurisdictionLicenses()).to.matchPattern([]); expect(licensee.inactiveHomeJurisdictionLicenses()).to.matchPattern([]); @@ -332,6 +340,8 @@ describe('Licensee model', () => { }], status: 'inactive' }], + militaryStatus: 'unsupported', + militaryStatusNote: 'note', licenses: [ { id: 'test-id', @@ -436,6 +446,8 @@ describe('Licensee model', () => { expect(licensee.militaryAffiliations).to.be.an('array').with.length(2); expect(licensee.militaryAffiliations[0]).to.be.an.instanceof(MilitaryAffiliation); expect(licensee.militaryAffiliations[1]).to.be.an.instanceof(MilitaryAffiliation); + expect(licensee.militaryStatus).to.equal(data.militaryStatus); + expect(licensee.militaryStatusNote).to.equal(data.militaryStatusNote); expect(licensee.status).to.equal(data.licenseStatus); // Test methods @@ -467,6 +479,7 @@ describe('Licensee model', () => { }], status: 'active' }); + expect(licensee.militaryAuditStatusName()).to.equal(''); expect(licensee.homeJurisdictionLicenses()).to.matchPattern([ { id: 'providerId1-co-audiologist', diff --git a/webroot/src/models/Licensee/Licensee.model.ts b/webroot/src/models/Licensee/Licensee.model.ts index a15355592..621a4b3b7 100644 --- a/webroot/src/models/Licensee/Licensee.model.ts +++ b/webroot/src/models/Licensee/Licensee.model.ts @@ -50,6 +50,8 @@ export interface InterfaceLicensee { phoneNumber?: string | null; licenseType?: LicenseType | null; militaryAffiliations?: Array ; + militaryStatus?: string | null; + militaryStatusNote?: string | null; licenseStates?: Array; licenses?: Array; privilegeStates?: Array; @@ -81,6 +83,8 @@ export class Licensee implements InterfaceLicensee { public licenses? = []; public privilegeStates? = []; public militaryAffiliations? = []; + public militaryStatus? = null; + public militaryStatusNote? = null; public privileges? = []; public lastUpdated? = null; public status? = LicenseeStatus.INACTIVE; @@ -214,6 +218,14 @@ export class Licensee implements InterfaceLicensee { return this.militaryAffiliations?.find((affiliation: MilitaryAffiliation) => affiliation.status as any === 'active') || null; } + public militaryAuditStatusName(): string { + const auditStatusTypes = this.$tm('military.militaryStatusOptions') || []; + const auditStatusType = auditStatusTypes.find((translate) => translate.key === this.militaryStatus); + const auditStatusTypeName = auditStatusType?.name || ''; + + return auditStatusTypeName; + } + public homeJurisdictionLicenses(): Array { return this.licenses?.filter((license: License) => (license.issueState?.abbrev === this.homeJurisdiction?.abbrev)) || []; @@ -422,6 +434,8 @@ export class LicenseeSerializer { privilegeStates: [] as Array, privileges: [] as Array, militaryAffiliations: [] as Array, + militaryStatus: json.militaryStatus, + militaryStatusNote: json.militaryStatusNote, status: json.licenseStatus, lastUpdated: json.dateOfUpdate, }; diff --git a/webroot/src/network/data.api.ts b/webroot/src/network/data.api.ts index 25a4d34d4..834034eda 100644 --- a/webroot/src/network/data.api.ts +++ b/webroot/src/network/data.api.ts @@ -159,6 +159,17 @@ export class DataApi { return licenseDataApi.getAttestation(compact, attestationId); } + /** + * PATCH Update Military Audit Status for a licensee. + * @param {string} compact A compact type. + * @param {string} licenseeId The Licensee ID. + * @param {object} data The user request data. + * @return {Promise} The server response. + */ + public updateMilitaryAudit(compact, licenseeId, data) { + return licenseDataApi.updateMilitaryAudit(compact, licenseeId, data); + } + /** * POST Encumber License for a licensee. * @param {string} compact The compact string ID (aslp, octp, coun). diff --git a/webroot/src/network/licenseApi/data.api.ts b/webroot/src/network/licenseApi/data.api.ts index c4f5806ef..f10d4fe91 100644 --- a/webroot/src/network/licenseApi/data.api.ts +++ b/webroot/src/network/licenseApi/data.api.ts @@ -252,6 +252,19 @@ export class LicenseDataApi implements DataApiInterface { return response; } + /** + * PATCH Update Military Audit Status for a licensee. + * @param {string} compact A compact type. + * @param {string} licenseeId The Licensee ID. + * @param {object} data The user request data. + * @return {Promise} The server response. + */ + public async updateMilitaryAudit(compact: string, licenseeId: string, data: object) { + const serverResponse = await this.api.patch(`/v1/compacts/${compact}/providers/${licenseeId}/militaryAudit`, data); + + return serverResponse; + } + /** * POST Encumber License for a licensee. * @param {string} compact The compact string ID (aslp, octp, coun). diff --git a/webroot/src/network/mocks/mock.data.api.ts b/webroot/src/network/mocks/mock.data.api.ts index f91397613..3bf75f9c4 100644 --- a/webroot/src/network/mocks/mock.data.api.ts +++ b/webroot/src/network/mocks/mock.data.api.ts @@ -225,6 +225,18 @@ export class DataApi { return wait(500).then(() => response); } + // Update Military Audit Status By ID + public updateMilitaryAudit(compact, licenseeId, data) { + const response = { + message: 'success', + compact, + licenseeId, + data, + }; + + return wait(500).then(() => response); + } + // Encumber License for a licensee. public encumberLicense( compact, diff --git a/webroot/src/network/mocks/mock.data.ts b/webroot/src/network/mocks/mock.data.ts index 2c37f3251..377f6d626 100644 --- a/webroot/src/network/mocks/mock.data.ts +++ b/webroot/src/network/mocks/mock.data.ts @@ -458,9 +458,15 @@ export const licensees = { dateOfUpload: '2024-08-29', documentKeys: 'key', fileNames: ['military-document.pdf'], - status: 'active' + downloadLinks: [{ + fileName: 'military-document.pdf', + url: '/examples/military-document.pdf', + }], + status: 'active', } ], + militaryStatus: 'approved', + militaryStatusNote: 'All the docs look good!', licenseStatus: 'active', licenseJurisdiction: 'co', currentHomeJurisdiction: 'co', @@ -591,9 +597,15 @@ export const licensees = { dateOfUpload: '2024-08-29', documentKeys: 'key', fileNames: ['military-document.pdf'], - status: 'initializing' + downloadLinks: [{ + fileName: 'military-document.pdf', + url: '/examples/military-document.pdf', + }], + status: 'initializing', } ], + militaryStatus: 'declined', + militaryStatusNote: 'Could not view the required docs. Please upload new copies.', licenseStatus: 'active', licenseJurisdiction: 'co', currentHomeJurisdiction: 'co', @@ -1013,6 +1025,8 @@ export const licensees = { birthMonthDay: '1975-01-01', ssnLastFour: '2222', militaryAffiliations: [], + militaryStatus: 'notApplicable', + militaryStatusNote: '', licenseStatus: 'inactive', licenseJurisdiction: 'co', currentHomeJurisdiction: 'co', @@ -1088,6 +1102,8 @@ export const licensees = { birthMonthDay: '1965-01-01', ssnLastFour: '3333', militaryAffiliations: [], + militaryStatus: 'notApplicable', + militaryStatusNote: '', licenseStatus: 'active', licenseJurisdiction: 'co', currentHomeJurisdiction: 'co', @@ -1163,6 +1179,8 @@ export const licensees = { birthMonthDay: '1990-08-29', ssnLastFour: '4444', militaryAffiliations: [], + militaryStatus: 'notApplicable', + militaryStatusNote: '', npi: '6441445289', licenseStatus: 'active', licenseJurisdiction: 'ny', diff --git a/webroot/src/pages/LicensingDetail/LicensingDetail.vue b/webroot/src/pages/LicensingDetail/LicensingDetail.vue index 1d401b90a..75789b209 100644 --- a/webroot/src/pages/LicensingDetail/LicensingDetail.vue +++ b/webroot/src/pages/LicensingDetail/LicensingDetail.vue @@ -127,9 +127,7 @@ - +
diff --git a/webroot/src/pages/MilitaryStatus/MilitaryStatus.vue b/webroot/src/pages/MilitaryStatus/MilitaryStatus.vue index d1a402252..655497dbe 100644 --- a/webroot/src/pages/MilitaryStatus/MilitaryStatus.vue +++ b/webroot/src/pages/MilitaryStatus/MilitaryStatus.vue @@ -30,11 +30,7 @@
- + diff --git a/webroot/src/store/users/users.actions.ts b/webroot/src/store/users/users.actions.ts index 3990154fd..713733441 100644 --- a/webroot/src/store/users/users.actions.ts +++ b/webroot/src/store/users/users.actions.ts @@ -124,6 +124,24 @@ export default { deleteUserFailure: ({ commit }, error: Error) => { commit(MutationTypes.DELETE_USER_FAILURE, error); }, + // UPDATE MILITARY AUDIT STATUS + updateMilitaryAuditRequest: async ({ commit, dispatch }, { compact, licenseeId, data }: any) => { + commit(MutationTypes.UPDATE_MILITARY_AUDIT_REQUEST); + return dataApi.updateMilitaryAudit(compact, licenseeId, data).then(async (response) => { + dispatch('updateMilitaryAuditSuccess'); + + return response; + }).catch((error) => { + dispatch('updateMilitaryAuditFailure', error); + throw error; + }); + }, + updateMilitaryAuditSuccess: ({ commit }) => { + commit(MutationTypes.UPDATE_MILITARY_AUDIT_SUCCESS); + }, + updateMilitaryAuditFailure: ({ commit }, error: Error) => { + commit(MutationTypes.UPDATE_MILITARY_AUDIT_FAILURE, error); + }, // ENCUMBER USER LICENSE encumberLicenseRequest: async ({ commit, dispatch }, { compact, diff --git a/webroot/src/store/users/users.mutations.ts b/webroot/src/store/users/users.mutations.ts index d77dfd416..81a707ab2 100644 --- a/webroot/src/store/users/users.mutations.ts +++ b/webroot/src/store/users/users.mutations.ts @@ -28,6 +28,9 @@ export enum MutationTypes { DELETE_USER_REQUEST = '[Users] Delete User Request', DELETE_USER_FAILURE = '[Users] Delete User Failure', DELETE_USER_SUCCESS = '[Users] Delete User Success', + UPDATE_MILITARY_AUDIT_REQUEST = '[Users] Update Military Audit Request', + UPDATE_MILITARY_AUDIT_FAILURE = '[Users] Update Military Audit Failure', + UPDATE_MILITARY_AUDIT_SUCCESS = '[Users] Update Military Audit Success', ENCUMBER_LICENSE_REQUEST = '[Users] Encumber License Request', ENCUMBER_LICENSE_FAILURE = '[Users] Encumber License Failure', ENCUMBER_LICENSE_SUCCESS = '[Users] Encumber License Success', @@ -145,6 +148,18 @@ export default { state.isLoading = false; state.error = null; }, + [MutationTypes.UPDATE_MILITARY_AUDIT_REQUEST]: (state: any) => { + state.isLoading = true; + state.error = null; + }, + [MutationTypes.UPDATE_MILITARY_AUDIT_FAILURE]: (state: any, error: Error) => { + state.isLoading = false; + state.error = error; + }, + [MutationTypes.UPDATE_MILITARY_AUDIT_SUCCESS]: (state: any) => { + state.isLoading = false; + state.error = null; + }, [MutationTypes.ENCUMBER_LICENSE_REQUEST]: (state: any) => { state.isLoading = true; state.error = null; diff --git a/webroot/src/store/users/users.spec.ts b/webroot/src/store/users/users.spec.ts index 78c40064f..1013596ef 100644 --- a/webroot/src/store/users/users.spec.ts +++ b/webroot/src/store/users/users.spec.ts @@ -196,6 +196,31 @@ describe('Users Store Mutations', () => { expect(state.isLoading).to.equal(false); expect(state.error).to.equal(null); }); + it('should successfully update military audit status request', () => { + const state = {}; + + mutations[MutationTypes.UPDATE_MILITARY_AUDIT_REQUEST](state); + + expect(state.isLoading).to.equal(true); + expect(state.error).to.equal(null); + }); + it('should successfully update military audit status failure', () => { + const state = {}; + const error = new Error(); + + mutations[MutationTypes.UPDATE_MILITARY_AUDIT_FAILURE](state, error); + + expect(state.isLoading).to.equal(false); + expect(state.error).to.equal(error); + }); + it('should successfully update military audit status success', () => { + const state = {}; + + mutations[MutationTypes.UPDATE_MILITARY_AUDIT_SUCCESS](state); + + expect(state.isLoading).to.equal(false); + expect(state.error).to.equal(null); + }); it('should successfully encumber license request', () => { const state = {}; @@ -724,6 +749,36 @@ describe('Users Store Actions', async () => { expect(commit.calledOnce).to.equal(true); expect(commit.firstCall.args).to.matchPattern([MutationTypes.DELETE_USER_SUCCESS]); }); + it('should successfully start military audit status request', async () => { + const commit = sinon.spy(); + const dispatch = sinon.spy(); + const compact = 'aslp'; + const licenseeId = '1'; + const data = { test: true }; + + await actions.updateMilitaryAuditRequest({ commit, dispatch }, { compact, licenseeId, data }); + + expect(commit.calledOnce).to.equal(true); + expect(commit.firstCall.args).to.matchPattern([MutationTypes.UPDATE_MILITARY_AUDIT_REQUEST]); + expect(dispatch.calledOnce).to.equal(true); + }); + it('should successfully start military audit status failure', () => { + const commit = sinon.spy(); + const error = new Error(); + + actions.updateMilitaryAuditFailure({ commit }, error); + + expect(commit.calledOnce).to.equal(true); + expect(commit.firstCall.args).to.matchPattern([MutationTypes.UPDATE_MILITARY_AUDIT_FAILURE, error]); + }); + it('should successfully start military audit status success', () => { + const commit = sinon.spy(); + + actions.updateMilitaryAuditSuccess({ commit }); + + expect(commit.calledOnce).to.equal(true); + expect(commit.firstCall.args).to.matchPattern([MutationTypes.UPDATE_MILITARY_AUDIT_SUCCESS]); + }); it('should successfully start encumber-license request', async () => { const commit = sinon.spy(); const dispatch = sinon.spy();