@@ -318,7 +318,10 @@ public void deleteStudyProduct(Container container, User user, int rowId)
318318 deleteProductAntigens (container , user , rowId );
319319
320320 // delete the associated doses and routes for this product
321- Table .delete (StudyDesignSchema .getInstance ().getTableInfoDoseAndRoute (), new SimpleFilter (FieldKey .fromParts ("ProductId" ), rowId ));
321+ // GitHub Kanban #1929: scope to the container in addition to ProductId
322+ SimpleFilter doseAndRouteFilter = SimpleFilter .createContainerFilter (container );
323+ doseAndRouteFilter .addCondition (FieldKey .fromParts ("ProductId" ), rowId );
324+ Table .delete (StudyDesignSchema .getInstance ().getTableInfoDoseAndRoute (), doseAndRouteFilter );
322325
323326 // delete the associated treatment study product mappings (provision table)
324327 SimpleFilter filter = SimpleFilter .createContainerFilter (container );
@@ -368,14 +371,18 @@ public DoseAndRoute saveStudyProductDoseAndRoute(Container container, User user,
368371
369372 public Collection <DoseAndRoute > getStudyProductsDoseAndRoute (Container container , User user , int productId )
370373 {
371- SimpleFilter filter = new SimpleFilter (FieldKey .fromParts ("ProductId" ), productId );
374+ // GitHub Kanban #1929: scope to the container in addition to ProductId
375+ SimpleFilter filter = SimpleFilter .createContainerFilter (container );
376+ filter .addCondition (FieldKey .fromParts ("ProductId" ), productId );
372377 return new TableSelector (StudyDesignSchema .getInstance ().getTableInfoDoseAndRoute (), filter , null ).getCollection (DoseAndRoute .class );
373378 }
374379
375380 @ Nullable
376381 public DoseAndRoute getDoseAndRoute (Container container , String dose , String route , int productId )
377382 {
378- SimpleFilter filter = new SimpleFilter (FieldKey .fromParts ("ProductId" ), productId );
383+ // GitHub Kanban #1929: scope to the container in addition to ProductId
384+ SimpleFilter filter = SimpleFilter .createContainerFilter (container );
385+ filter .addCondition (FieldKey .fromParts ("ProductId" ), productId );
379386 if (dose != null )
380387 filter .addCondition (FieldKey .fromParts ("Dose" ), dose );
381388 else
@@ -989,6 +996,70 @@ private void tearDown()
989996 assertTrue (ContainerManager .delete (_junitStudy .getContainer (), _context .getUser ()));
990997 }
991998 }
999+
1000+ // GitHub Kanban #1929: getStudyProductsDoseAndRoute / getDoseAndRoute / deleteStudyProduct container scoping checks
1001+ @ Test
1002+ public void testDoseAndRouteContainerScoping () throws Exception
1003+ {
1004+ TestContext context = TestContext .get ();
1005+ User user = context .getUser ();
1006+ Container containerA = null ;
1007+ Container containerB = null ;
1008+ try
1009+ {
1010+ containerA = createStudyContainer (context , GUID .makeHash ());
1011+ containerB = createStudyContainer (context , GUID .makeHash ());
1012+
1013+ int productIdA = insertStudyProduct (containerA , user , "Immunogen A" , "Immunogen" );
1014+ int productIdB = insertStudyProduct (containerB , user , "Immunogen B" , "Immunogen" );
1015+
1016+ _manager .saveStudyProductDoseAndRoute (containerA , user , new DoseAndRoute ("Dose A" , "Route A" , productIdA , containerA ));
1017+ _manager .saveStudyProductDoseAndRoute (containerB , user , new DoseAndRoute ("Dose B" , "Route B" , productIdB , containerB ));
1018+
1019+ // getStudyProductsDoseAndRoute: scoped by container, not ProductId alone
1020+ assertEquals ("Dose/route should be returned within its own container" , 1 ,
1021+ _manager .getStudyProductsDoseAndRoute (containerB , user , productIdB ).size ());
1022+ assertEquals ("Dose/route must not be returned from another container" , 0 ,
1023+ _manager .getStudyProductsDoseAndRoute (containerA , user , productIdB ).size ());
1024+
1025+ // getDoseAndRoute: same scoping
1026+ assertNotNull ("getDoseAndRoute should find the row within its own container" ,
1027+ _manager .getDoseAndRoute (containerB , "Dose B" , "Route B" , productIdB ));
1028+ assertNull ("getDoseAndRoute must not find the row from another container" ,
1029+ _manager .getDoseAndRoute (containerA , "Dose B" , "Route B" , productIdB ));
1030+
1031+ // deleteStudyProduct's dose/route delete is now container scoped; deleting the product in its own
1032+ // container removes its dose/route (the cross-container case is unreachable since ProductId is unique).
1033+ _manager .deleteStudyProduct (containerB , user , productIdB );
1034+ assertEquals ("deleteStudyProduct should remove the dose/route within its own container" , 0 ,
1035+ _manager .getStudyProductsDoseAndRoute (containerB , user , productIdB ).size ());
1036+ }
1037+ finally
1038+ {
1039+ if (null != containerB )
1040+ ContainerManager .delete (containerB , user );
1041+ if (null != containerA )
1042+ ContainerManager .delete (containerA , user );
1043+ }
1044+ }
1045+
1046+ private Container createStudyContainer (TestContext context , String name )
1047+ {
1048+ Container junit = JunitUtil .getTestContainer ();
1049+ Container c = ContainerManager .createContainer (junit , name , context .getUser ());
1050+ Set <Module > modules = new HashSet <>(c .getActiveModules ());
1051+ modules .add (ModuleLoader .getInstance ().getModule ("studydesign" ));
1052+ c .setActiveModules (modules );
1053+ StudyService .get ().createStudy (c , context .getUser (), "Junit Study " + name , TimepointType .VISIT , true );
1054+ return c ;
1055+ }
1056+
1057+ private int insertStudyProduct (Container c , User user , String label , String role )
1058+ {
1059+ UserSchema schema = QueryService .get ().getUserSchema (user , c , StudyDesignQuerySchema .STUDY_SCHEMA_NAME );
1060+ TableInfo ti = ((FilteredTable ) schema .getTable (StudyDesignQuerySchema .PRODUCT_TABLE_NAME )).getRealTable ();
1061+ return Table .insert (user , ti , new ProductImpl (c , label , role )).getRowId ();
1062+ }
9921063 }
9931064
9941065 @ TestWhen (TestWhen .When .BVT )
0 commit comments