From 9872814495b69f377953e89a49f84331194afb38 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 3 Aug 2026 00:14:19 +0530 Subject: [PATCH 1/6] fix: stop tb village save on create + edit dropdown gaps - Stop TB Create was hardcoding villageID/villageName to null, silently dropping the admin's village selection; it now saves them the same way Edit already does. - Old-style (pre-Nikshay) Stop TB users no longer get a guessed district auto-selected on Edit via AMRIT name-matching, and no longer inherit a stale AMRIT district ID into the Nikshay district field. District/TU/Facility/Village now come up blank for these rows so the admin picks fresh Nikshay values. - Edit's Stop TB Block/TU/Facility/Village dropdowns now have the same search box and Select All control Create already has. - MMU/TM's Edit Village dropdown was also missing search (had Select All only) - added for consistency. Co-Authored-By: Claude Sonnet 5 --- .../work-location-mapping.component.html | 60 ++++- .../work-location-mapping.component.spec.ts | 229 +++++++++++------- .../work-location-mapping.component.ts | 97 +++++--- 3 files changed, 253 insertions(+), 133 deletions(-) diff --git a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html index 4ee1ab7d..4855897f 100644 --- a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html +++ b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html @@ -992,8 +992,15 @@

[(ngModel)]="selectedNikshayBlock" name="nikshayBlockEdit" (selectionChange)="onNikshayBlockChange()" + (openedChange)="$event ? null : nikshayBlockSearch = ''" > - + + {{ item.tUName }} @@ -1008,9 +1015,21 @@

[(ngModel)]="selectedNikshayTUs" name="nikshayTUsEdit" (selectionChange)="onNikshayTUChange()" + (openedChange)="$event ? null : nikshayTUSearch = ''" required > - + + + {{ item.tUName }} @@ -1025,9 +1044,21 @@

[(ngModel)]="selectedNikshayFacilities" name="nikshayFacilitiesEdit" (selectionChange)="onNikshayFacilityChange()" + (openedChange)="$event ? null : nikshayFacilitySearch = ''" required > - + + + {{ item.facilityName }} @@ -1041,9 +1072,21 @@

[style.width]="'100%'" [(ngModel)]="selectedNikshayVillages" name="nikshayVillagesEdit" + (openedChange)="$event ? null : nikshayVillageSearch = ''" required > - + + + {{ item.villageName }} @@ -1110,7 +1153,14 @@

[(ngModel)]="serviceEditvillage" name="serviceEditvillage" required + (openedChange)="$event ? null : serviceEditVillageSearch = ''" > +
>Select All
{{ 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 025f2610..9f935f4c 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 acea7df4..152f8000 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 @@ -592,21 +592,25 @@ export class WorkLocationMappingComponent 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 +642,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 +651,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 +662,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(); - } - } }); }; @@ -2225,6 +2214,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, @@ -2257,17 +2253,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 +4331,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 || []); From 44b976cf4584c17206d983cd0ce54d518a0f441a Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 3 Aug 2026 00:43:38 +0530 Subject: [PATCH 2/6] fix: scrollable village cell in work location mapping tables Long comma-joined village lists (especially Stop TB's, sourced from an entire TU/facility) were either blowing out row height or, via the table-responsive wrapper, forcing the whole table to scroll horizontally past Edit/Deactivate. The Village cell now gets its own fixed-width, scrollable/draggable box with a hover tooltip showing the full list, so the rest of the columns stay on screen. Co-Authored-By: Claude Sonnet 5 --- .../work-location-mapping.component.css | 15 +++++++++++++++ .../work-location-mapping.component.html | 8 ++++++-- .../work-location-mapping.component.ts | 12 ++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.css b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.css index b519d3e4..b34a8f3f 100644 --- a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.css +++ b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.css @@ -6,6 +6,21 @@ } } +/* Village list cell in the mapping tables — long comma-joined village + lists (esp. Stop TB's) get their own fixed-width horizontal scroll + instead of stretching the row height or the whole table width, so the + rest of the columns (Edit/Deactivate etc.) stay on screen. */ +.village-cell-scroll { + max-width: 260px; + overflow-x: auto; + white-space: nowrap; + cursor: grab; +} + +.village-cell-scroll:active { + cursor: grabbing; +} + .role-chip { display: inline-block; padding: 2px 10px; diff --git a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html index 4855897f..ee73e5b1 100644 --- a/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html +++ b/src/app/app-provider-admin/provider-admin/activities/work-location-mapping/work-location-mapping.component.html @@ -72,7 +72,9 @@

Village - {{ element.villageName }} +
+ {{ asVillageText(element.villageName) }} +
@@ -741,7 +743,9 @@

Village - {{ element.villageName }} +
+ {{ asVillageText(element.villageName) }} +
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 152f8000..e10d9723 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 @@ -4805,6 +4805,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(); From e70209e17b938acb3c1bf9432b84f1c8c891cef1 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 7 Aug 2026 15:23:48 +0530 Subject: [PATCH 3/6] fix: pre-select Select Block with the saved TU in edit work location mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop TB's Select Block is a single-select quick-add convenience over the same TU list Select TU manages (see onNikshayBlockChange) — it isn't a separate saved value. loadNikshayTUsForEditContinued() restored selectedNikshayTUs (so Select TU showed correctly) but never wrote back to selectedNikshayBlock, so it rendered empty on Edit even though the TU/block was fully saved. Pre-fill it whenever exactly one TU is restored, the case for all live Stop TB rows. Co-Authored-By: Claude Sonnet 5 --- .../work-location-mapping.component.ts | 9 +++++++++ 1 file changed, 9 insertions(+) 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 e10d9723..87f333be 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 @@ -748,6 +748,15 @@ 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) — it has no value of its + // own on the backend. Pre-fill it to match whenever there's exactly + // one saved TU (the common case), otherwise leave it blank since + // there's no single TU to show against a multi-TU row. + this.selectedNikshayBlock = + this.selectedNikshayTUs.length === 1 + ? this.selectedNikshayTUs[0] + : 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); From 91de857de9c51b2f4fb815e59bbae6af4fd5f9cd Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 7 Aug 2026 16:57:32 +0530 Subject: [PATCH 4/6] fix: derive Select Block pre-fill from saved blockID, not TU count The previous fix guessed selectedNikshayBlock off selectedNikshayTUs.length == 1, which left Block blank whenever a row had more than one TU (a real, observed case). blockID/blockName on m_userservicerolemapping ARE the saved Select Block value in their own right, stamped by onNikshayBlockChange() at save time independent of how many TUs later joined NikshayTUID. Use getNikshayUserMappingData's blockID directly to look up the matching entry in nikshayTUList instead. Co-Authored-By: Claude Sonnet 5 --- .../work-location-mapping.component.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) 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 87f333be..3eb3f7b4 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,12 @@ 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 and independent of how many TUs ended up in + // NikshayTUID. This is the source of truth for what to + // pre-select there, not a guess off how many TUs come back. + const savedBlockID = parseInt(data.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,6 +594,7 @@ export class WorkLocationMappingComponent existingTUIDs, existingFacilityIDs, uniqueVillageIDs, + savedBlockID, ); return; } @@ -739,6 +746,7 @@ export class WorkLocationMappingComponent existingTUIDs: any[], existingFacilityIDs: any[], uniqueVillageIDs: any[], + savedBlockID?: number, ) { this.worklocationmapping .getNikshayTUs(nikshayDistrictID) @@ -749,13 +757,15 @@ export class WorkLocationMappingComponent existingTUIDs.includes(t.nikshayTUID), ); // "Select Block" is a single-select quick-add convenience over this - // same TU list (see onNikshayBlockChange) — it has no value of its - // own on the backend. Pre-fill it to match whenever there's exactly - // one saved TU (the common case), otherwise leave it blank since - // there's no single TU to show against a multi-TU row. + // 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 = - this.selectedNikshayTUs.length === 1 - ? this.selectedNikshayTUs[0] + 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 From 6e4005e704d3c4ee2f70b2323f816ee42e7dafec Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 7 Aug 2026 17:07:37 +0530 Subject: [PATCH 5/6] fix: save selectedNikshayBlock's TU as blockID/blockName, not array[0] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create's setWorkLocationObject and Edit's updateGroupedWorkLocation both wrote blockID/blockName from selectedNikshayTUs[0] — whichever TU happened to land first in the multi-select array — instead of the TU the admin actually picked in Select Block. That's order-dependent, not selection-dependent: adding a second TU after picking Block, or in a different order, silently changes what gets saved as the block with no relation to what Select Block showed on screen. Both paths now read selectedNikshayBlock.nikshayTUID/tUName directly, falling back to the first selected TU only when Block was never touched. Combined with the earlier edit-load fix (looking the saved blockID up in nikshayTUList), Select Block now round-trips correctly: whatever's picked is what's saved, and it's what shows back up next time the row is edited. Co-Authored-By: Claude Sonnet 5 --- .../work-location-mapping.component.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) 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 3eb3f7b4..18c59f47 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 @@ -2258,12 +2258,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 @@ -5363,15 +5371,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 From 382f1e9daf77426f98050b8922800a595bee3100 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 7 Aug 2026 17:32:25 +0530 Subject: [PATCH 6/6] fix: read saved blockID from edit_Details, not the raw-table endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getNikshayUserMappingData() (/admin-api/nikshay/location/userMapping) only returns nikshayTUID/nikshayFacilityID/districtID — verified against a live UAT response — it never carries blockID/blockName. The previous fix read data.blockID from exactly that response, so it was always undefined -> NaN -> the lookup silently no-op'd and Select Block stayed blank on every edit, regardless of what was actually saved. edit_Details already holds blockID/blockName intact — it's sourced from getUserRoleMapped (v_userservicerolemapping) when the row is opened for Edit, which does carry both columns. Pull savedBlockID from there instead. No backend change needed. Co-Authored-By: Claude Sonnet 5 --- .../work-location-mapping.component.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 18c59f47..8113a2fd 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 @@ -572,11 +572,15 @@ export class WorkLocationMappingComponent 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 and independent of how many TUs ended up in - // NikshayTUID. This is the source of truth for what to - // pre-select there, not a guess off how many TUs come back. - const savedBlockID = parseInt(data.blockID, 10); + // "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