{{ item.villageName }} [{{ item.districtBranchID }}]
diff --git a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.spec.ts b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.spec.ts
index 025f261..9f935f4 100644
--- a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.spec.ts
+++ b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.spec.ts
@@ -19,124 +19,177 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { WorkLocationMappingComponent } from './work-location-mapping.component';
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
-import { ActivatedRoute, Params } from '@angular/router';
-import { Observable } from 'rxjs/Observable';
-import 'rxjs/add/observable/of';
-import { dataService } from '../services/dataService/data.service';
-import { Router } from '@angular/router';
-import { Md2Module } from 'md2';
import { FormsModule } from '@angular/forms';
-import { fakeAsync } from '@angular/core/testing';
-import { By } from '@angular/platform-browser';
-import { tick } from '@angular/core/testing';
-import { ConfirmationDialogsService } from '../services/dialog/confirmation.service';
-import { WorkLocationMapping } from '../services/ProviderAdminServices/work-location-mapping.service';
+import { of } from 'rxjs';
+import { WorkLocationMappingComponent } from './work-location-mapping.component';
+import { WorkLocationMapping } from '../services/work-location-mapping.service';
+import { ConfirmationDialogsService } from 'src/app/core/services/dialog/confirmation.service';
+import { VillageMasterService } from 'src/app/core/services/adminServices/AdminVillage/village-master-service.service';
+import { SessionStorageService } from 'Common-UI/src/registrar/services/session-storage.service';
+import { FacilityMasterService } from 'src/app/core/services/inventory-services/facilitytypemaster.service';
+
let component: WorkLocationMappingComponent;
let fixture: ComponentFixture;
-const FakeConfirmationDialogsService = {};
-
-const providerForFakeConfirmationDialogsService = {
- provide: ConfirmationDialogsService,
- useValue: FakeConfirmationDialogsService,
-};
-const FakeDataService = {
- service_providerID: 'serviceProviderID',
- uname: 'admin',
+const FakeConfirmationDialogsService = {
+ alert: (_msg?: any, _type?: any) => undefined,
};
-const providerForFakeDataService = {
- provide: dataService,
- useValue: FakeDataService,
+const FakeWorkLocationMapping = {
+ getServices: (_userID: any) => of({ data: [] }),
+ getMappedWorkLocationList: (_serviceProviderID: any) => of({ data: [] }),
+ getUserName: (_serviceProviderID: any) => of({ data: [] }),
};
-class FakeWorkLocationMapping {
- getUserName(data) {
- return Observable.of([
- {
- userID: '1',
- },
- ]);
- }
- getMappedWorkLocationList() {
- return Observable.of([
- {
- userLangID: '1',
- },
- ]);
- }
- getAllServiceLinesByProvider(data) {
- return Observable.of([
- {
- languageID: '1',
- LanguageName: 'english',
- },
- ]);
- }
-}
-const providerForWorkLocationMapping = {
- provide: WorkLocationMapping,
- useClass: FakeWorkLocationMapping,
+const FakeVillageMasterService = {};
+const FakeFacilityMasterService = {};
+
+const sessionValues: Record = {
+ service_providerID: 'SP1',
+ uid: 'U1',
+ uname: 'admin',
+};
+const FakeSessionStorageService = {
+ getItem: (key: string) => sessionValues[key] ?? null,
};
-function InitializeAdminTestBed() {
- beforeEach(async(() => {
+function initTestBed() {
+ beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [WorkLocationMappingComponent],
schemas: [NO_ERRORS_SCHEMA],
- imports: [Md2Module, FormsModule],
+ imports: [FormsModule],
providers: [
- providerForFakeConfirmationDialogsService,
- providerForFakeDataService,
- providerForWorkLocationMapping,
+ {
+ provide: ConfirmationDialogsService,
+ useValue: FakeConfirmationDialogsService,
+ },
+ { provide: WorkLocationMapping, useValue: FakeWorkLocationMapping },
+ { provide: VillageMasterService, useValue: FakeVillageMasterService },
+ { provide: SessionStorageService, useValue: FakeSessionStorageService },
+ { provide: FacilityMasterService, useValue: FakeFacilityMasterService },
],
}).compileComponents();
}));
beforeEach(() => {
+ // Deliberately not calling fixture.detectChanges(): the real template
+ // uses mat-table/matSort/matPaginator directives whose modules aren't
+ // imported here, and these tests only exercise component logic
+ // (setWorkLocationObject etc.), not the rendered DOM. ngOnInit is
+ // still run explicitly so session-storage-derived fields are set.
fixture = TestBed.createComponent(WorkLocationMappingComponent);
component = fixture.componentInstance;
- fixture.detectChanges();
+ component.ngOnInit();
});
}
-describe('Work-Location-mapping', () => {
- fdescribe('When the component is getting loaded, then ngOninit', () => {
- InitializeAdminTestBed();
+
+describe('WorkLocationMappingComponent', () => {
+ describe('When the component is loaded, then ngOnInit', () => {
+ initTestBed();
it('should be created', () => {
expect(component).toBeTruthy();
});
- it('should be defined', () => {
- expect(component).toBeDefined();
- });
- it('checking the value of uname should not be null and shoul have some value username', () => {
- expect(component.createdBy).not.toBe('1');
+
+ it('should read createdBy from session storage', () => {
expect(component.createdBy).toBe('admin');
});
- it('should set the ProviderServiceId after OnInit', () => {
- expect(component.serviceProviderID).not.toBe('');
- expect(component.serviceProviderID).toBe('serviceProviderID');
+
+ it('should read serviceProviderID from session storage', () => {
+ expect(component.serviceProviderID).toBe('SP1');
+ });
+ });
+
+ // These tests exercise setWorkLocationObject() directly — it is pure,
+ // synchronous request-building logic (no HTTP), so it can be verified
+ // without a running backend. They confirm the on-Create village-save
+ // behavior for each service line, including the known Stop TB gap:
+ // TU/Facility save correctly, but Village is hardcoded to null on the
+ // Create path (see setWorkLocationObject's isStopTBServiceline branch).
+ describe('setWorkLocationObject — village save behavior per serviceline (Create)', () => {
+ initTestBed();
+
+ const baseUser = { userID: 1, userName: 'worker1' };
+ const baseRole = { roleID: 5, roleName: 'Field Worker' };
+
+ it('MMU: saves villageID/villageName from the plain village picker', () => {
+ component.isStopTBServiceline = false;
+ component.isFacilityServiceline = false;
+
+ const objectToBeAdded: any = {
+ user: baseUser,
+ serviceline: { serviceID: 9, serviceName: 'MMU', isNational: false },
+ state: { stateID: 2, stateName: 'Karnataka', providerServiceMapID: 77 },
+ district: { districtID: 20, districtName: 'Bidar' },
+ Serviceblock: { blockID: 300, blockName: 'Bhalki' },
+ Servicevillage: [{ villageName: 'Alamkeri', districtBranchID: 555 }],
+ };
+
+ component.setWorkLocationObject(objectToBeAdded, baseRole, false, false);
+
+ const saved = component.bufferArray.data[0];
+ expect(saved.villageID).toEqual([555]);
+ expect(saved.villageName).toEqual(['Alamkeri']);
+ });
+
+ it('FLW/HWC: saves villageID/villageName resolved via the facility sub-component', () => {
+ component.isStopTBServiceline = false;
+ component.isFacilityServiceline = true;
+ component.currentFacilityMappingData = {
+ villageIDs: [7, 8],
+ villageNames: ['V7', 'V8'],
+ facilityID: 99,
+ };
+
+ const objectToBeAdded: any = {
+ user: baseUser,
+ serviceline: { serviceID: 3, serviceName: 'HWC', isNational: false },
+ state: { stateID: 2, stateName: 'Karnataka', providerServiceMapID: 77 },
+ district: { districtID: 20, districtName: 'Bidar' },
+ };
+
+ component.setWorkLocationObject(objectToBeAdded, baseRole, false, false);
+
+ const saved = component.bufferArray.data[0];
+ expect(saved.villageID).toEqual([7, 8]);
+ expect(saved.villageName).toEqual(['V7', 'V8']);
+ expect(saved.facilityID).toBe(99);
+ });
+
+ it('Stop TB: saves TU, Facility, and Village all together on Create', () => {
+ component.isStopTBServiceline = true;
+ component.selectedNikshayTUs = [
+ { nikshayTUID: 8903, tUName: 'Bhalki-TU' },
+ ];
+ component.selectedNikshayFacilities = [
+ { nikshayFacilityID: 648139, facilityName: 'Shadole hospital' },
+ ];
+ component.selectedNikshayVillages = [
+ { nikshayVillageID: 100, villageName: 'Alamkeri' },
+ ];
+
+ const objectToBeAdded: any = {
+ user: baseUser,
+ serviceline: {
+ serviceID: 11,
+ serviceName: 'Stop TB',
+ isNational: false,
+ },
+ state: { stateID: 2, stateName: 'Karnataka', providerServiceMapID: 77 },
+ district: { nikshayDistrictID: 312, districtName: 'Bidar' },
+ };
+
+ component.setWorkLocationObject(objectToBeAdded, baseRole, false, false);
+
+ const saved = component.bufferArray.data[0];
+ expect(saved.nikshayTUID).toBe('8903');
+ expect(saved.nikshayFacilityID).toBe('648139');
+ // Village selection is now saved on Create too, same as Edit.
+ expect(saved.villageID).toEqual([100]);
+ expect(saved.villageName).toEqual(['Alamkeri']);
});
- // it(' getUserName should be called after OnInit', () => {
- // spyOn(component, 'getUserName');
- // component.ngOnInit();
- // expect(component.getUserName).toHaveBeenCalled;
- // expect(component.userNamesList).not.toBe('');
- // });
- // it('getAllMappedWorkLocations method should be called after OnInit', () => {
- // spyOn(component, 'getAllMappedWorkLocations');
- // component.ngOnInit();
- // expect(component.getAllMappedWorkLocations).toHaveBeenCalled;
- // expect(component.mappedWorkLocationsList).not.toBe('');
- // });
- // it(' getAllServicelines should be called after OnInit', () => {
- // spyOn(component, 'getAllServicelines');
- // component.ngOnInit();
- // expect(component.getAllServicelines).toHaveBeenCalled;
- // expect(component.services_array).not.toBe('');
- // });
});
});
diff --git a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.ts b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.ts
index acea7df..8113a2f 100644
--- a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.ts
+++ b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.ts
@@ -571,6 +571,16 @@ export class WorkLocationMappingComponent
const data = response?.data || {};
const existingTUIDs = splitIDs(data.nikshayTUID);
const existingFacilityIDs = splitIDs(data.nikshayFacilityID);
+ // BlockID/BlockName on m_userservicerolemapping ARE the saved
+ // "Select Block" value — stamped by onNikshayBlockChange() at save
+ // time, independent of how many TUs ended up in NikshayTUID.
+ // getNikshayUserMappingData's raw-table response does NOT include
+ // these two columns (verified against live UAT response — only
+ // nikshayTUID/nikshayFacilityID/districtID come back), so pull it
+ // from edit_Details instead — the row is already sitting there
+ // with blockID/blockName intact, sourced from getUserRoleMapped
+ // (v_userservicerolemapping) when the row was opened for Edit.
+ const savedBlockID = parseInt(this.edit_Details?.blockID, 10);
// DistrictID is repurposed to hold the Nikshay district ID directly
// for Stop TB rows (AMRIT's own DistrictID is never used for Stop
@@ -588,25 +598,30 @@ export class WorkLocationMappingComponent
existingTUIDs,
existingFacilityIDs,
uniqueVillageIDs,
+ savedBlockID,
);
return;
}
- // Old-style user, or a fresh row with nothing selected yet: no
- // saved DistrictID to resolve from. Try a best-effort name match
- // against the row's AMRIT WorkingDistrictName first (only ever
- // populated for genuinely old, pre-Nikshay rows — see
- // loadNikshayDistrictListForEdit) — if that doesn't resolve, the
- // District dropdown's OPTIONS still populate from the one thing
- // reliably known (State), and the actual pick is left to the
- // admin. The dropdown is editable for Stop TB (see template);
- // picking a district calls onNikshayDistrictChangeEdit().
+ // Old-style user (mapped through the pre-Nikshay AMRIT flow), or a
+ // fresh row with nothing selected yet: no saved Nikshay DistrictID
+ // to resolve from. District/TU/Facility/Village are left fully
+ // blank — deliberately NOT guessed from the row's old AMRIT
+ // WorkingDistrictName, since that name space doesn't reliably line
+ // up with Nikshay's. Only User/Serviceline/Role carry over from
+ // the old row; the admin picks fresh Nikshay values and Update
+ // re-saves this worker on the new schema. district_duringEdit is
+ // explicitly cleared here — editGroupedRow/editRow already set it
+ // to the row's old AMRIT district ID earlier for every service
+ // line, which would otherwise show as a bogus selection against
+ // the Nikshay district list. The District dropdown's OPTIONS still
+ // populate from the one thing reliably known (State); the dropdown
+ // is editable for Stop TB (see template) and picking a district
+ // calls onNikshayDistrictChangeEdit().
+ this.district_duringEdit = null;
const rowStateName = this.edit_Details?.stateName;
if (rowStateName) {
- this.loadNikshayDistrictListForEdit(
- rowStateName,
- this.edit_Details?.workingDistrictName,
- );
+ this.loadNikshayDistrictListForEdit(rowStateName);
}
});
}
@@ -638,10 +653,7 @@ export class WorkLocationMappingComponent
// real WorkingDistrictName (rows saved through the newer Nikshay flow
// never set WorkingLocationID at all, so this never fires for them —
// they fall through to the plain empty-options case, same as before).
- private loadNikshayDistrictListForEdit(
- stateName: string,
- amritDistrictName?: string,
- ) {
+ private loadNikshayDistrictListForEdit(stateName: string) {
const STATE_NAME_ALIASES: Record = {
chattisgarh: 'chhattisgarh',
};
@@ -650,9 +662,6 @@ export class WorkLocationMappingComponent
return STATE_NAME_ALIASES[n] || n;
};
const normStateName = normalize(stateName);
- const normAmritDistrictName = amritDistrictName
- ? normalize(amritDistrictName)
- : null;
const resolveAndLoad = (states: any[]) => {
const nikshayState = (states || []).find(
@@ -664,15 +673,6 @@ export class WorkLocationMappingComponent
.pipe(takeUntil(this.destroy$))
.subscribe((distResponse: any) => {
this.nikshayDistrictList = distResponse.data || [];
- if (normAmritDistrictName) {
- const matched = this.nikshayDistrictList.find(
- (d: any) => normalize(d.districtName) === normAmritDistrictName,
- );
- if (matched) {
- this.district_duringEdit = matched.nikshayDistrictID;
- this.onNikshayDistrictChangeEdit();
- }
- }
});
};
@@ -750,6 +750,7 @@ export class WorkLocationMappingComponent
existingTUIDs: any[],
existingFacilityIDs: any[],
uniqueVillageIDs: any[],
+ savedBlockID?: number,
) {
this.worklocationmapping
.getNikshayTUs(nikshayDistrictID)
@@ -759,6 +760,17 @@ export class WorkLocationMappingComponent
this.selectedNikshayTUs = this.nikshayTUList.filter((t: any) =>
existingTUIDs.includes(t.nikshayTUID),
);
+ // "Select Block" is a single-select quick-add convenience over this
+ // same TU list (see onNikshayBlockChange), but the saved BlockID IS
+ // its own real value on the row (stamped there at save time) —
+ // independent of how many TUs ended up in NikshayTUID afterwards.
+ // Look it up directly instead of guessing off selectedNikshayTUs.
+ this.selectedNikshayBlock =
+ savedBlockID && !isNaN(savedBlockID)
+ ? this.nikshayTUList.find(
+ (t: any) => t.nikshayTUID === savedBlockID,
+ ) || null
+ : null;
if (!this.selectedNikshayTUs.length) return; // old user: nothing to pre-select, but list is loaded and usable
const tuIDs = this.selectedNikshayTUs.map((t: any) => t.nikshayTUID);
@@ -2225,6 +2237,13 @@ export class WorkLocationMappingComponent
facilityNameArrTB.push(facility.facilityName);
});
+ const villageIDArrTB: any[] = [];
+ const villageNameArrTB: any[] = [];
+ (this.selectedNikshayVillages || []).forEach((village: any) => {
+ villageIDArrTB.push(village.nikshayVillageID);
+ villageNameArrTB.push(village.villageName);
+ });
+
const stopTBWorkLocationObj: any = {
previleges: [],
userID: objectToBeAdded.user.userID,
@@ -2243,12 +2262,20 @@ export class WorkLocationMappingComponent
// districtID — reading .districtID here always came back null.
districtID: objectToBeAdded.district?.nikshayDistrictID || null,
district: objectToBeAdded.district?.districtName || null,
- // Block is a single-value legacy field (both ID and Name) — kept as
- // just the first selected TU for backward compatibility with any
- // generic block-comparison logic elsewhere. The full multi-TU list
- // lives in NikshayTUID/NikshayTUName, not here.
- blockID: tuIDArrTB.length ? tuIDArrTB[0] : null,
- blockName: tuNameArrTB.length ? tuNameArrTB[0] : null,
+ // Block is a single-value legacy field (both ID and Name). Store
+ // whichever TU the admin actually picked via "Select Block"
+ // (selectedNikshayBlock) — not just whichever TU happened to land
+ // first in the multi-select array, which was order-dependent and
+ // didn't necessarily match what "Select Block" showed on screen.
+ // Falls back to the first selected TU only when Block was never
+ // touched (e.g. admin picked TUs solely via "Select TU"). The full
+ // multi-TU list lives in NikshayTUID/NikshayTUName, not here.
+ blockID:
+ this.selectedNikshayBlock?.nikshayTUID ??
+ (tuIDArrTB.length ? tuIDArrTB[0] : null),
+ blockName:
+ this.selectedNikshayBlock?.tUName ??
+ (tuNameArrTB.length ? tuNameArrTB[0] : null),
nikshayTUID: tuIDArrTB.length ? tuIDArrTB.join(',') : null,
nikshayTUName: tuNameArrTB.length ? tuNameArrTB.join(', ') : null,
nikshayFacilityID: facilityIDArrTB.length
@@ -2257,17 +2284,21 @@ export class WorkLocationMappingComponent
nikshayFacilityName: facilityNameArrTB.length
? facilityNameArrTB.join(', ')
: null,
- // Villageid intentionally does NOT take the Nikshay Village
- // picker's selection — it holds AMRIT village IDs everywhere else
- // in the system, including the mobile app's own beneficiary
- // worklist match. Writing Nikshay village IDs here breaks that
- // match (two different numbering systems for the same real
- // place). A brand-new row has no existing value to preserve, so
- // null is safe here (unlike the edit path, which must round-trip
- // the existing value instead). Revisit once a Nikshay-village-to-
- // AMRIT-village bridge table exists to translate safely.
- villageID: null,
- villageName: null,
+ // Villageid takes the Nikshay Village picker's selection directly,
+ // same as the Edit path (see updateGroupedWorkLocation). This
+ // column holds AMRIT village IDs everywhere else in the system,
+ // including the mobile app's own beneficiary worklist match
+ // (BenFlowStatus.villageID) — writing Nikshay village IDs here
+ // means that match won't resolve for these villages until a
+ // Nikshay-village-to-AMRIT-village bridge table exists to
+ // translate between the two numbering systems. Previously this was
+ // left null specifically on Create to avoid that collision, but
+ // Edit was already writing it the same way — silently dropping the
+ // admin's village selection on every new Stop TB worker until they
+ // were separately edited was a worse outcome than the collision
+ // risk both paths already carry identically.
+ villageID: villageIDArrTB.length ? villageIDArrTB : null,
+ villageName: villageNameArrTB.length ? villageNameArrTB : null,
Inbound: 'N/A',
Outbound: 'N/A',
teleConsultation: [null],
@@ -4331,6 +4362,23 @@ export class WorkLocationMappingComponent
this.collectUserExistingVillages();
}
+ // Edit-mode search for the plain (MMU/TM) Village dropdown — mirrors
+ // Create's villageSearch/filteredVillagesList, kept separate since Edit
+ // uses its own array (editVillageArr) and a villageName-string model
+ // (serviceEditvillage) rather than Create's village-object model.
+ serviceEditVillageSearch = '';
+
+ get filteredEditVillageList(): any[] {
+ if (!this.serviceEditVillageSearch) return this.editVillageArr;
+ const s = this.serviceEditVillageSearch.toLowerCase();
+ const selectedNames = new Set(this.serviceEditvillage || []);
+ return this.editVillageArr.filter(
+ (v: any) =>
+ selectedNames.has(v.villageName) ||
+ (v.villageName || '').toLowerCase().includes(s),
+ );
+ }
+
get allEditVillagesSelected(): boolean {
if (!this.editVillageArr?.length) return false;
const selected = new Set(this.serviceEditvillage || []);
@@ -4788,6 +4836,18 @@ export class WorkLocationMappingComponent
// ── Grouped row helpers ──
+ // Village lists (especially Stop TB's, sourced from an entire TU/facility)
+ // can run to hundreds of comma-joined names. The list table renders this
+ // in a fixed-width scrollable cell (see .village-cell-scroll) instead of
+ // stretching the row or the whole table, so joining with ", " here is
+ // just for readability in that cell and its tooltip.
+ asVillageText(villageName: any): string {
+ if (!villageName) return '';
+ return Array.isArray(villageName)
+ ? villageName.join(', ')
+ : String(villageName);
+ }
+
getUniqueRoles(roles: RoleEntry[]): RoleEntry[] {
if (!roles) return [];
const seen = new Set();
@@ -5315,15 +5375,19 @@ export class WorkLocationMappingComponent
? (this.selectedNikshayVillages || []).map((v: any) => v.villageName)
: [];
+ // Same as Create (setWorkLocationObject): store whichever TU the admin
+ // actually picked via "Select Block" (selectedNikshayBlock), not just
+ // whichever TU landed first in the multi-select array — otherwise a
+ // saved row's blockID silently drifts to array order instead of the
+ // admin's actual Block pick, and re-editing shows the wrong block.
+ // Falls back to the first selected TU only when Block was never touched.
const blockIDToUse = this.isStopTBServicelineEdit
- ? nikshayTUIDArr.length
- ? nikshayTUIDArr[0]
- : null
+ ? this.selectedNikshayBlock?.nikshayTUID ??
+ (nikshayTUIDArr.length ? nikshayTUIDArr[0] : null)
: this.ServiceEditblock;
const blockNameToUse = this.isStopTBServicelineEdit
- ? nikshayTUNameArr.length
- ? nikshayTUNameArr[0]
- : null
+ ? this.selectedNikshayBlock?.tUName ??
+ (nikshayTUNameArr.length ? nikshayTUNameArr[0] : null)
: this.blockname;
const villageIDToUse = this.isStopTBServicelineEdit
? nikshayVillageIDArr.length