@Test public void shouldSucceedWithValidStockEvent() { setupEvent(); when(facilityRepository.getById(fId)).thenReturn(defaultFacility); when(productService.getByCode(pCode)).thenReturn(defaultProduct); when(stockAdjustmentReasonRepository.getAdjustmentReasonByName(reasonName)).thenReturn(reason); when(stockCardService.getOrCreateStockCard(fId, pCode)).thenReturn(dummyCard); when(lotRepository.getLotOnHandByStockCardAndLot(eq(dummyCard.getId()), any(Long.class))).thenReturn(null); setupPermissionCalls(Collections.singletonList(new Right("MANAGE_STOCK", RightType.REQUISITION))); ResponseEntity response = controller.processStock(fId, Collections.singletonList(event), request); StockCardEntry entry = new StockCardEntry(dummyCard, StockCardEntryType.ADJUSTMENT, event.getQuantity() * -1, null, null); entry.setAdjustmentReason(reason); verify(stockCardService).addStockCardEntries(Collections.singletonList(entry)); assertThat(response.getStatusCode(), is(HttpStatus.OK)); }
@RequestMapping(value = "facilities/{facilityId}/stockCards", method = POST, headers = ACCEPT_JSON) @ApiOperation(value="Update stock cards at a facility.", notes = "Updates stock cards at a facility. This is done by providing a list of stock events." + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards in which to update.</li>" + "</ul>" + "<p>" + "<p>Body parameters (required):" + "<ul>" + "<li>" + "<strong>stock events</strong> (Array of stock event objects) - a list of stock events to " + "process for update." + "<p>" + "<p>Stock event properties" + "<ul>" + "<li><strong>type</strong> (String, required) - type code of stock event (choices are ISSUE, " + "RECEIPT, ADJUSTMENT).</li>" + "<li><strong>facilityId</strong> (Long, required for ISSUE/RECEIPT types) - facility id where" + "stock is going to/coming from.</li>" + "<li><strong>productCode</strong> (String, required) - product code of the stock being " + "processed.</li>" + "<li><strong>quantity</strong> (Long, required) - quantity of stock being processed. Specify as a " + "positive number. For ISSUE, this amount is decremented, for RECEIPT, this amount is incremented, " + "for ADJUSTMENT, it depends on the adjustment reason.</li>" + "<li><strong>reasonName</strong> (String, required for ADJUSTMENT types) - reason code for the " + "adjustment.</li>" + "<li><strong>lotId</strong> (Long, optional) - lot id of a particular lot that will be processed. " + "This and lot are optional; if lotId is specified, lot is ignored.</li>" + "<li><strong>lot</strong> (Object, optional) - lot object of a particular lot that will be " + "processed. If lot with the specified code, manufacturerName, and expirationDate do not exist, a " + "lot will be created.</li>" + "<li><strong>customProps</strong> (Object, optional) - an object of custom properties (keys and " + "values) to specify custom fields for the stock event." + "</ul>" + "</li>" + "</ul>" + "<p>" + "<p>Example stock event list JSON:" + "<pre><code>" + "[\n" + " {\n" + " \"type\": \"ADJUSTMENT\",\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"reasonName\": \"TRANSFER_IN\",\n" + " \"lotId\": 1\n" + " },\n" + " {\n" + " \"type\": \"ISSUE\",\n" + " \"facilityId\": 19077,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"customProps\": {\n" + " \"occurred\": \"2015-10-01\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"RECEIPT\",\n" + " \"facilityId\": 19074,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"lot\": {\n" + " \"lotCode\": \"C1\",\n" + " \"manufacturerName\": \"Manufacturer 3\",\n" + " \"expirationDate\": \"2016-07-01\"\n" + " },\n" + " \"customProps\": {\n" + " \"vvmStatus\": \"1\"\n" + " }\n" + " }\n" + "]\n" + "</code></pre>") @Transactional public ResponseEntity processStock(@PathVariable long facilityId, @RequestBody(required = true) List<StockEvent> events, HttpServletRequest request) { if(null == events || 0 >= events.size()) return OpenLmisResponse.success(messageService.message("success.stock.event.none")); if(null == facilityRepository.getById(facilityId)) return OpenLmisResponse.error(messageService.message("error.facility.unknown"), HttpStatus.BAD_REQUEST); Long userId = loggedInUserId(request); List<StockCardEntry> entries = new ArrayList<>(); for(StockEvent event : events) { logger.debug("Processing event: " + event); if(!event.isValid()) return OpenLmisResponse.error(messageService.message("error.stock.event.invalid"), HttpStatus.BAD_REQUEST); String productCode = event.getProductCode(); if(null == productService.getByCode(productCode)) return OpenLmisResponse.error(messageService.message("error.product.unknown"), HttpStatus.BAD_REQUEST); StockAdjustmentReason reason = null; if (StockEventType.ADJUSTMENT == event.getType()) { reason = stockAdjustmentReasonRepository.getAdjustmentReasonByName( event.getReasonName()); if(null == reason) return OpenLmisResponse.error(messageService.message("error.stockadjustmentreason.unknown"), HttpStatus.BAD_REQUEST); } List<Right> rights = roleRightsService.getRightsForUserFacilityAndProductCode(userId, facilityId, productCode); if (!any(rights, with("MANAGE_STOCK"))) { return OpenLmisResponse.error(messageService.message("error.permission.stock.card.manage"), HttpStatus.FORBIDDEN); } StockCard card = service.getOrCreateStockCard(facilityId, productCode); if(null == card) return OpenLmisResponse.error(messageService.message("error.stock.card.get"), HttpStatus.INTERNAL_SERVER_ERROR); StringBuilder str = new StringBuilder(); Long lotId = event.getLotId(); Lot lotObj = event.getLot(); LotOnHand lotOnHand = service.getLotOnHand(lotId, lotObj, productCode, card, str); if (!str.toString().equals("")) { return OpenLmisResponse.error(messageService.message(str.toString()), HttpStatus.BAD_REQUEST); } long quantity = event.getPositiveOrNegativeQuantity(reason); StockCardEntryType entryType = StockCardEntryType.ADJUSTMENT; switch (event.getType()) { case ISSUE: entryType = StockCardEntryType.DEBIT; break; case RECEIPT: entryType = StockCardEntryType.CREDIT; break; case ADJUSTMENT: entryType = StockCardEntryType.ADJUSTMENT; break; default: break; } Date occurred = event.getOccurred(); String referenceNumber = event.getReferenceNumber(); StockCardEntry entry = new StockCardEntry(card, entryType, quantity, occurred, referenceNumber); entry.setAdjustmentReason(reason); entry.setLotOnHand(lotOnHand); Map<String, String> customProps = event.getCustomProps(); if (null != customProps) { for (String k : customProps.keySet()) { entry.addKeyValue(k, customProps.get(k)); } } entry.setCreatedBy(userId); entry.setModifiedBy(userId); entries.add(entry); } service.addStockCardEntries(entries); return OpenLmisResponse.success(messageService.message("success.stock.adjusted")); }
StockCardController extends BaseController { @RequestMapping(value = "facilities/{facilityId}/stockCards", method = POST, headers = ACCEPT_JSON) @ApiOperation(value="Update stock cards at a facility.", notes = "Updates stock cards at a facility. This is done by providing a list of stock events." + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards in which to update.</li>" + "</ul>" + "<p>" + "<p>Body parameters (required):" + "<ul>" + "<li>" + "<strong>stock events</strong> (Array of stock event objects) - a list of stock events to " + "process for update." + "<p>" + "<p>Stock event properties" + "<ul>" + "<li><strong>type</strong> (String, required) - type code of stock event (choices are ISSUE, " + "RECEIPT, ADJUSTMENT).</li>" + "<li><strong>facilityId</strong> (Long, required for ISSUE/RECEIPT types) - facility id where" + "stock is going to/coming from.</li>" + "<li><strong>productCode</strong> (String, required) - product code of the stock being " + "processed.</li>" + "<li><strong>quantity</strong> (Long, required) - quantity of stock being processed. Specify as a " + "positive number. For ISSUE, this amount is decremented, for RECEIPT, this amount is incremented, " + "for ADJUSTMENT, it depends on the adjustment reason.</li>" + "<li><strong>reasonName</strong> (String, required for ADJUSTMENT types) - reason code for the " + "adjustment.</li>" + "<li><strong>lotId</strong> (Long, optional) - lot id of a particular lot that will be processed. " + "This and lot are optional; if lotId is specified, lot is ignored.</li>" + "<li><strong>lot</strong> (Object, optional) - lot object of a particular lot that will be " + "processed. If lot with the specified code, manufacturerName, and expirationDate do not exist, a " + "lot will be created.</li>" + "<li><strong>customProps</strong> (Object, optional) - an object of custom properties (keys and " + "values) to specify custom fields for the stock event." + "</ul>" + "</li>" + "</ul>" + "<p>" + "<p>Example stock event list JSON:" + "<pre><code>" + "[\n" + " {\n" + " \"type\": \"ADJUSTMENT\",\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"reasonName\": \"TRANSFER_IN\",\n" + " \"lotId\": 1\n" + " },\n" + " {\n" + " \"type\": \"ISSUE\",\n" + " \"facilityId\": 19077,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"customProps\": {\n" + " \"occurred\": \"2015-10-01\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"RECEIPT\",\n" + " \"facilityId\": 19074,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"lot\": {\n" + " \"lotCode\": \"C1\",\n" + " \"manufacturerName\": \"Manufacturer 3\",\n" + " \"expirationDate\": \"2016-07-01\"\n" + " },\n" + " \"customProps\": {\n" + " \"vvmStatus\": \"1\"\n" + " }\n" + " }\n" + "]\n" + "</code></pre>") @Transactional public ResponseEntity processStock(@PathVariable long facilityId, @RequestBody(required = true) List<StockEvent> events, HttpServletRequest request) { if(null == events || 0 >= events.size()) return OpenLmisResponse.success(messageService.message("success.stock.event.none")); if(null == facilityRepository.getById(facilityId)) return OpenLmisResponse.error(messageService.message("error.facility.unknown"), HttpStatus.BAD_REQUEST); Long userId = loggedInUserId(request); List<StockCardEntry> entries = new ArrayList<>(); for(StockEvent event : events) { logger.debug("Processing event: " + event); if(!event.isValid()) return OpenLmisResponse.error(messageService.message("error.stock.event.invalid"), HttpStatus.BAD_REQUEST); String productCode = event.getProductCode(); if(null == productService.getByCode(productCode)) return OpenLmisResponse.error(messageService.message("error.product.unknown"), HttpStatus.BAD_REQUEST); StockAdjustmentReason reason = null; if (StockEventType.ADJUSTMENT == event.getType()) { reason = stockAdjustmentReasonRepository.getAdjustmentReasonByName( event.getReasonName()); if(null == reason) return OpenLmisResponse.error(messageService.message("error.stockadjustmentreason.unknown"), HttpStatus.BAD_REQUEST); } List<Right> rights = roleRightsService.getRightsForUserFacilityAndProductCode(userId, facilityId, productCode); if (!any(rights, with("MANAGE_STOCK"))) { return OpenLmisResponse.error(messageService.message("error.permission.stock.card.manage"), HttpStatus.FORBIDDEN); } StockCard card = service.getOrCreateStockCard(facilityId, productCode); if(null == card) return OpenLmisResponse.error(messageService.message("error.stock.card.get"), HttpStatus.INTERNAL_SERVER_ERROR); StringBuilder str = new StringBuilder(); Long lotId = event.getLotId(); Lot lotObj = event.getLot(); LotOnHand lotOnHand = service.getLotOnHand(lotId, lotObj, productCode, card, str); if (!str.toString().equals("")) { return OpenLmisResponse.error(messageService.message(str.toString()), HttpStatus.BAD_REQUEST); } long quantity = event.getPositiveOrNegativeQuantity(reason); StockCardEntryType entryType = StockCardEntryType.ADJUSTMENT; switch (event.getType()) { case ISSUE: entryType = StockCardEntryType.DEBIT; break; case RECEIPT: entryType = StockCardEntryType.CREDIT; break; case ADJUSTMENT: entryType = StockCardEntryType.ADJUSTMENT; break; default: break; } Date occurred = event.getOccurred(); String referenceNumber = event.getReferenceNumber(); StockCardEntry entry = new StockCardEntry(card, entryType, quantity, occurred, referenceNumber); entry.setAdjustmentReason(reason); entry.setLotOnHand(lotOnHand); Map<String, String> customProps = event.getCustomProps(); if (null != customProps) { for (String k : customProps.keySet()) { entry.addKeyValue(k, customProps.get(k)); } } entry.setCreatedBy(userId); entry.setModifiedBy(userId); entries.add(entry); } service.addStockCardEntries(entries); return OpenLmisResponse.success(messageService.message("success.stock.adjusted")); } }
StockCardController extends BaseController { @RequestMapping(value = "facilities/{facilityId}/stockCards", method = POST, headers = ACCEPT_JSON) @ApiOperation(value="Update stock cards at a facility.", notes = "Updates stock cards at a facility. This is done by providing a list of stock events." + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards in which to update.</li>" + "</ul>" + "<p>" + "<p>Body parameters (required):" + "<ul>" + "<li>" + "<strong>stock events</strong> (Array of stock event objects) - a list of stock events to " + "process for update." + "<p>" + "<p>Stock event properties" + "<ul>" + "<li><strong>type</strong> (String, required) - type code of stock event (choices are ISSUE, " + "RECEIPT, ADJUSTMENT).</li>" + "<li><strong>facilityId</strong> (Long, required for ISSUE/RECEIPT types) - facility id where" + "stock is going to/coming from.</li>" + "<li><strong>productCode</strong> (String, required) - product code of the stock being " + "processed.</li>" + "<li><strong>quantity</strong> (Long, required) - quantity of stock being processed. Specify as a " + "positive number. For ISSUE, this amount is decremented, for RECEIPT, this amount is incremented, " + "for ADJUSTMENT, it depends on the adjustment reason.</li>" + "<li><strong>reasonName</strong> (String, required for ADJUSTMENT types) - reason code for the " + "adjustment.</li>" + "<li><strong>lotId</strong> (Long, optional) - lot id of a particular lot that will be processed. " + "This and lot are optional; if lotId is specified, lot is ignored.</li>" + "<li><strong>lot</strong> (Object, optional) - lot object of a particular lot that will be " + "processed. If lot with the specified code, manufacturerName, and expirationDate do not exist, a " + "lot will be created.</li>" + "<li><strong>customProps</strong> (Object, optional) - an object of custom properties (keys and " + "values) to specify custom fields for the stock event." + "</ul>" + "</li>" + "</ul>" + "<p>" + "<p>Example stock event list JSON:" + "<pre><code>" + "[\n" + " {\n" + " \"type\": \"ADJUSTMENT\",\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"reasonName\": \"TRANSFER_IN\",\n" + " \"lotId\": 1\n" + " },\n" + " {\n" + " \"type\": \"ISSUE\",\n" + " \"facilityId\": 19077,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"customProps\": {\n" + " \"occurred\": \"2015-10-01\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"RECEIPT\",\n" + " \"facilityId\": 19074,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"lot\": {\n" + " \"lotCode\": \"C1\",\n" + " \"manufacturerName\": \"Manufacturer 3\",\n" + " \"expirationDate\": \"2016-07-01\"\n" + " },\n" + " \"customProps\": {\n" + " \"vvmStatus\": \"1\"\n" + " }\n" + " }\n" + "]\n" + "</code></pre>") @Transactional public ResponseEntity processStock(@PathVariable long facilityId, @RequestBody(required = true) List<StockEvent> events, HttpServletRequest request) { if(null == events || 0 >= events.size()) return OpenLmisResponse.success(messageService.message("success.stock.event.none")); if(null == facilityRepository.getById(facilityId)) return OpenLmisResponse.error(messageService.message("error.facility.unknown"), HttpStatus.BAD_REQUEST); Long userId = loggedInUserId(request); List<StockCardEntry> entries = new ArrayList<>(); for(StockEvent event : events) { logger.debug("Processing event: " + event); if(!event.isValid()) return OpenLmisResponse.error(messageService.message("error.stock.event.invalid"), HttpStatus.BAD_REQUEST); String productCode = event.getProductCode(); if(null == productService.getByCode(productCode)) return OpenLmisResponse.error(messageService.message("error.product.unknown"), HttpStatus.BAD_REQUEST); StockAdjustmentReason reason = null; if (StockEventType.ADJUSTMENT == event.getType()) { reason = stockAdjustmentReasonRepository.getAdjustmentReasonByName( event.getReasonName()); if(null == reason) return OpenLmisResponse.error(messageService.message("error.stockadjustmentreason.unknown"), HttpStatus.BAD_REQUEST); } List<Right> rights = roleRightsService.getRightsForUserFacilityAndProductCode(userId, facilityId, productCode); if (!any(rights, with("MANAGE_STOCK"))) { return OpenLmisResponse.error(messageService.message("error.permission.stock.card.manage"), HttpStatus.FORBIDDEN); } StockCard card = service.getOrCreateStockCard(facilityId, productCode); if(null == card) return OpenLmisResponse.error(messageService.message("error.stock.card.get"), HttpStatus.INTERNAL_SERVER_ERROR); StringBuilder str = new StringBuilder(); Long lotId = event.getLotId(); Lot lotObj = event.getLot(); LotOnHand lotOnHand = service.getLotOnHand(lotId, lotObj, productCode, card, str); if (!str.toString().equals("")) { return OpenLmisResponse.error(messageService.message(str.toString()), HttpStatus.BAD_REQUEST); } long quantity = event.getPositiveOrNegativeQuantity(reason); StockCardEntryType entryType = StockCardEntryType.ADJUSTMENT; switch (event.getType()) { case ISSUE: entryType = StockCardEntryType.DEBIT; break; case RECEIPT: entryType = StockCardEntryType.CREDIT; break; case ADJUSTMENT: entryType = StockCardEntryType.ADJUSTMENT; break; default: break; } Date occurred = event.getOccurred(); String referenceNumber = event.getReferenceNumber(); StockCardEntry entry = new StockCardEntry(card, entryType, quantity, occurred, referenceNumber); entry.setAdjustmentReason(reason); entry.setLotOnHand(lotOnHand); Map<String, String> customProps = event.getCustomProps(); if (null != customProps) { for (String k : customProps.keySet()) { entry.addKeyValue(k, customProps.get(k)); } } entry.setCreatedBy(userId); entry.setModifiedBy(userId); entries.add(entry); } service.addStockCardEntries(entries); return OpenLmisResponse.success(messageService.message("success.stock.adjusted")); } StockCardController(MessageService messageService,
                        FacilityRepository facilityRepository,
                        ProductService productService,
                        StockAdjustmentReasonRepository stockAdjustmentReasonRepository,
                        StockCardRepository stockCardRepository,
                        LotRepository lotRepository,
                        ProgramProductService programProductService,
                        ProgramService programService,
                        RoleRightsService roleRightsService,
                        StockCardService service); }
StockCardController extends BaseController { @RequestMapping(value = "facilities/{facilityId}/stockCards", method = POST, headers = ACCEPT_JSON) @ApiOperation(value="Update stock cards at a facility.", notes = "Updates stock cards at a facility. This is done by providing a list of stock events." + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards in which to update.</li>" + "</ul>" + "<p>" + "<p>Body parameters (required):" + "<ul>" + "<li>" + "<strong>stock events</strong> (Array of stock event objects) - a list of stock events to " + "process for update." + "<p>" + "<p>Stock event properties" + "<ul>" + "<li><strong>type</strong> (String, required) - type code of stock event (choices are ISSUE, " + "RECEIPT, ADJUSTMENT).</li>" + "<li><strong>facilityId</strong> (Long, required for ISSUE/RECEIPT types) - facility id where" + "stock is going to/coming from.</li>" + "<li><strong>productCode</strong> (String, required) - product code of the stock being " + "processed.</li>" + "<li><strong>quantity</strong> (Long, required) - quantity of stock being processed. Specify as a " + "positive number. For ISSUE, this amount is decremented, for RECEIPT, this amount is incremented, " + "for ADJUSTMENT, it depends on the adjustment reason.</li>" + "<li><strong>reasonName</strong> (String, required for ADJUSTMENT types) - reason code for the " + "adjustment.</li>" + "<li><strong>lotId</strong> (Long, optional) - lot id of a particular lot that will be processed. " + "This and lot are optional; if lotId is specified, lot is ignored.</li>" + "<li><strong>lot</strong> (Object, optional) - lot object of a particular lot that will be " + "processed. If lot with the specified code, manufacturerName, and expirationDate do not exist, a " + "lot will be created.</li>" + "<li><strong>customProps</strong> (Object, optional) - an object of custom properties (keys and " + "values) to specify custom fields for the stock event." + "</ul>" + "</li>" + "</ul>" + "<p>" + "<p>Example stock event list JSON:" + "<pre><code>" + "[\n" + " {\n" + " \"type\": \"ADJUSTMENT\",\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"reasonName\": \"TRANSFER_IN\",\n" + " \"lotId\": 1\n" + " },\n" + " {\n" + " \"type\": \"ISSUE\",\n" + " \"facilityId\": 19077,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"customProps\": {\n" + " \"occurred\": \"2015-10-01\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"RECEIPT\",\n" + " \"facilityId\": 19074,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"lot\": {\n" + " \"lotCode\": \"C1\",\n" + " \"manufacturerName\": \"Manufacturer 3\",\n" + " \"expirationDate\": \"2016-07-01\"\n" + " },\n" + " \"customProps\": {\n" + " \"vvmStatus\": \"1\"\n" + " }\n" + " }\n" + "]\n" + "</code></pre>") @Transactional public ResponseEntity processStock(@PathVariable long facilityId, @RequestBody(required = true) List<StockEvent> events, HttpServletRequest request) { if(null == events || 0 >= events.size()) return OpenLmisResponse.success(messageService.message("success.stock.event.none")); if(null == facilityRepository.getById(facilityId)) return OpenLmisResponse.error(messageService.message("error.facility.unknown"), HttpStatus.BAD_REQUEST); Long userId = loggedInUserId(request); List<StockCardEntry> entries = new ArrayList<>(); for(StockEvent event : events) { logger.debug("Processing event: " + event); if(!event.isValid()) return OpenLmisResponse.error(messageService.message("error.stock.event.invalid"), HttpStatus.BAD_REQUEST); String productCode = event.getProductCode(); if(null == productService.getByCode(productCode)) return OpenLmisResponse.error(messageService.message("error.product.unknown"), HttpStatus.BAD_REQUEST); StockAdjustmentReason reason = null; if (StockEventType.ADJUSTMENT == event.getType()) { reason = stockAdjustmentReasonRepository.getAdjustmentReasonByName( event.getReasonName()); if(null == reason) return OpenLmisResponse.error(messageService.message("error.stockadjustmentreason.unknown"), HttpStatus.BAD_REQUEST); } List<Right> rights = roleRightsService.getRightsForUserFacilityAndProductCode(userId, facilityId, productCode); if (!any(rights, with("MANAGE_STOCK"))) { return OpenLmisResponse.error(messageService.message("error.permission.stock.card.manage"), HttpStatus.FORBIDDEN); } StockCard card = service.getOrCreateStockCard(facilityId, productCode); if(null == card) return OpenLmisResponse.error(messageService.message("error.stock.card.get"), HttpStatus.INTERNAL_SERVER_ERROR); StringBuilder str = new StringBuilder(); Long lotId = event.getLotId(); Lot lotObj = event.getLot(); LotOnHand lotOnHand = service.getLotOnHand(lotId, lotObj, productCode, card, str); if (!str.toString().equals("")) { return OpenLmisResponse.error(messageService.message(str.toString()), HttpStatus.BAD_REQUEST); } long quantity = event.getPositiveOrNegativeQuantity(reason); StockCardEntryType entryType = StockCardEntryType.ADJUSTMENT; switch (event.getType()) { case ISSUE: entryType = StockCardEntryType.DEBIT; break; case RECEIPT: entryType = StockCardEntryType.CREDIT; break; case ADJUSTMENT: entryType = StockCardEntryType.ADJUSTMENT; break; default: break; } Date occurred = event.getOccurred(); String referenceNumber = event.getReferenceNumber(); StockCardEntry entry = new StockCardEntry(card, entryType, quantity, occurred, referenceNumber); entry.setAdjustmentReason(reason); entry.setLotOnHand(lotOnHand); Map<String, String> customProps = event.getCustomProps(); if (null != customProps) { for (String k : customProps.keySet()) { entry.addKeyValue(k, customProps.get(k)); } } entry.setCreatedBy(userId); entry.setModifiedBy(userId); entries.add(entry); } service.addStockCardEntries(entries); return OpenLmisResponse.success(messageService.message("success.stock.adjusted")); } StockCardController(MessageService messageService,
                        FacilityRepository facilityRepository,
                        ProductService productService,
                        StockAdjustmentReasonRepository stockAdjustmentReasonRepository,
                        StockCardRepository stockCardRepository,
                        LotRepository lotRepository,
                        ProgramProductService programProductService,
                        ProgramService programService,
                        RoleRightsService roleRightsService,
                        StockCardService service); @RequestMapping(value = "facilities/{facilityId}/products/{productCode}/stockCard", method = GET, headers = ACCEPT_JSON) @ApiOperation(value = "Get information about the stock card for the specified facility and product.", notes = "Gets stock card information, by facility and product." + "<p>If no view permissions are found for this stock card, will return 403 Forbidden." + "<p>" + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock card.</li>" + "<li><strong>productCode</strong> (String) - product for the stock card.</li>" + "</ul>" + "<p>" + "<p>Request parameters:" + "<ul>" + "<li><strong>entries</strong> (Integer, optional, default = 1) - Number of stock card entries to " + "get in the result.</li>" + "</ul>") ResponseEntity getStockCard(@PathVariable Long facilityId,
                                       @PathVariable String productCode,
                                       @RequestParam(value = "entries", defaultValue = "1")Integer entries,
                                       @RequestParam(value = "includeEmptyLots", required = false, defaultValue = "false") boolean includeEmptyLots,
                                       HttpServletRequest request); @RequestMapping(value = "facilities/{facilityId}/stockCards/{stockCardId}", method = GET, headers = ACCEPT_JSON) @ApiOperation(value = "Get information about the specified stock card for the specified facility.", notes = "Gets stock card information, by facility and stock card id." + "<p>If facility does not have specified stock card id, will return 404 Not Found." + "<p>If no view permissions are found for this stock card, will return 403 Forbidden." + "<p>" + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock card.</li>" + "<li><strong>stockCardId</strong> (Long) - the specified stock card.</li>" + "</ul>" + "<p>" + "<p>Request parameters:" + "<ul>" + "<li><strong>entries</strong> (Integer, optional, default = 1) - Number of stock card entries to " + "get in the result.</li>" + "</ul>") ResponseEntity getStockCardById(@PathVariable Long facilityId, @PathVariable Long stockCardId,
                                           @RequestParam(value = "entries", defaultValue = "1")Integer entries,
                                           @RequestParam(value = "includeEmptyLots", required = false, defaultValue = "false") boolean includeEmptyLots,
                                           HttpServletRequest request); @RequestMapping(value = "facilities/{facilityId}/stockCards", method = GET, headers = ACCEPT_JSON) @ApiOperation(value = "Get information about all stock cards for the specified facility.", notes = "Gets all stock card information, by facility." + "<p>If no stock cards exist, will return 404 Not Found." + "<p>If no view permissions are found for any existing stock card, will return an empty list." + "<p>" + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards.</li>" + "</ul>" + "<p>" + "<p>Request parameters:" + "<ul>" + "<li><strong>entries</strong> (Integer, optional, default = 1) - Number of stock card entries to " + "get in the result.</li>" + "<li><strong>countOnly</strong> (Boolean, optional, default = false) - Get only the count of " + "stock cards.</li>" + "</ul>") ResponseEntity getStockCards(@PathVariable Long facilityId,
                                        @RequestParam(value = "entries", defaultValue = "1") Integer entries,
                                        @RequestParam(value = "countOnly", defaultValue = "false") Boolean countOnly,
                                        @RequestParam(value = "includeEmptyLots", required = false, defaultValue = "false") boolean includeEmptyLots,
                                        HttpServletRequest request); @RequestMapping(value = "facilities/{facilityId}/stockCards", method = POST, headers = ACCEPT_JSON) @ApiOperation(value="Update stock cards at a facility.", notes = "Updates stock cards at a facility. This is done by providing a list of stock events." + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards in which to update.</li>" + "</ul>" + "<p>" + "<p>Body parameters (required):" + "<ul>" + "<li>" + "<strong>stock events</strong> (Array of stock event objects) - a list of stock events to " + "process for update." + "<p>" + "<p>Stock event properties" + "<ul>" + "<li><strong>type</strong> (String, required) - type code of stock event (choices are ISSUE, " + "RECEIPT, ADJUSTMENT).</li>" + "<li><strong>facilityId</strong> (Long, required for ISSUE/RECEIPT types) - facility id where" + "stock is going to/coming from.</li>" + "<li><strong>productCode</strong> (String, required) - product code of the stock being " + "processed.</li>" + "<li><strong>quantity</strong> (Long, required) - quantity of stock being processed. Specify as a " + "positive number. For ISSUE, this amount is decremented, for RECEIPT, this amount is incremented, " + "for ADJUSTMENT, it depends on the adjustment reason.</li>" + "<li><strong>reasonName</strong> (String, required for ADJUSTMENT types) - reason code for the " + "adjustment.</li>" + "<li><strong>lotId</strong> (Long, optional) - lot id of a particular lot that will be processed. " + "This and lot are optional; if lotId is specified, lot is ignored.</li>" + "<li><strong>lot</strong> (Object, optional) - lot object of a particular lot that will be " + "processed. If lot with the specified code, manufacturerName, and expirationDate do not exist, a " + "lot will be created.</li>" + "<li><strong>customProps</strong> (Object, optional) - an object of custom properties (keys and " + "values) to specify custom fields for the stock event." + "</ul>" + "</li>" + "</ul>" + "<p>" + "<p>Example stock event list JSON:" + "<pre><code>" + "[\n" + " {\n" + " \"type\": \"ADJUSTMENT\",\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"reasonName\": \"TRANSFER_IN\",\n" + " \"lotId\": 1\n" + " },\n" + " {\n" + " \"type\": \"ISSUE\",\n" + " \"facilityId\": 19077,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"customProps\": {\n" + " \"occurred\": \"2015-10-01\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"RECEIPT\",\n" + " \"facilityId\": 19074,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"lot\": {\n" + " \"lotCode\": \"C1\",\n" + " \"manufacturerName\": \"Manufacturer 3\",\n" + " \"expirationDate\": \"2016-07-01\"\n" + " },\n" + " \"customProps\": {\n" + " \"vvmStatus\": \"1\"\n" + " }\n" + " }\n" + "]\n" + "</code></pre>") @Transactional ResponseEntity processStock(@PathVariable long facilityId,
                                       @RequestBody(required = true) List<StockEvent> events,
                                       HttpServletRequest request); }
StockCardController extends BaseController { @RequestMapping(value = "facilities/{facilityId}/stockCards", method = POST, headers = ACCEPT_JSON) @ApiOperation(value="Update stock cards at a facility.", notes = "Updates stock cards at a facility. This is done by providing a list of stock events." + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards in which to update.</li>" + "</ul>" + "<p>" + "<p>Body parameters (required):" + "<ul>" + "<li>" + "<strong>stock events</strong> (Array of stock event objects) - a list of stock events to " + "process for update." + "<p>" + "<p>Stock event properties" + "<ul>" + "<li><strong>type</strong> (String, required) - type code of stock event (choices are ISSUE, " + "RECEIPT, ADJUSTMENT).</li>" + "<li><strong>facilityId</strong> (Long, required for ISSUE/RECEIPT types) - facility id where" + "stock is going to/coming from.</li>" + "<li><strong>productCode</strong> (String, required) - product code of the stock being " + "processed.</li>" + "<li><strong>quantity</strong> (Long, required) - quantity of stock being processed. Specify as a " + "positive number. For ISSUE, this amount is decremented, for RECEIPT, this amount is incremented, " + "for ADJUSTMENT, it depends on the adjustment reason.</li>" + "<li><strong>reasonName</strong> (String, required for ADJUSTMENT types) - reason code for the " + "adjustment.</li>" + "<li><strong>lotId</strong> (Long, optional) - lot id of a particular lot that will be processed. " + "This and lot are optional; if lotId is specified, lot is ignored.</li>" + "<li><strong>lot</strong> (Object, optional) - lot object of a particular lot that will be " + "processed. If lot with the specified code, manufacturerName, and expirationDate do not exist, a " + "lot will be created.</li>" + "<li><strong>customProps</strong> (Object, optional) - an object of custom properties (keys and " + "values) to specify custom fields for the stock event." + "</ul>" + "</li>" + "</ul>" + "<p>" + "<p>Example stock event list JSON:" + "<pre><code>" + "[\n" + " {\n" + " \"type\": \"ADJUSTMENT\",\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"reasonName\": \"TRANSFER_IN\",\n" + " \"lotId\": 1\n" + " },\n" + " {\n" + " \"type\": \"ISSUE\",\n" + " \"facilityId\": 19077,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"customProps\": {\n" + " \"occurred\": \"2015-10-01\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"RECEIPT\",\n" + " \"facilityId\": 19074,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"lot\": {\n" + " \"lotCode\": \"C1\",\n" + " \"manufacturerName\": \"Manufacturer 3\",\n" + " \"expirationDate\": \"2016-07-01\"\n" + " },\n" + " \"customProps\": {\n" + " \"vvmStatus\": \"1\"\n" + " }\n" + " }\n" + "]\n" + "</code></pre>") @Transactional public ResponseEntity processStock(@PathVariable long facilityId, @RequestBody(required = true) List<StockEvent> events, HttpServletRequest request) { if(null == events || 0 >= events.size()) return OpenLmisResponse.success(messageService.message("success.stock.event.none")); if(null == facilityRepository.getById(facilityId)) return OpenLmisResponse.error(messageService.message("error.facility.unknown"), HttpStatus.BAD_REQUEST); Long userId = loggedInUserId(request); List<StockCardEntry> entries = new ArrayList<>(); for(StockEvent event : events) { logger.debug("Processing event: " + event); if(!event.isValid()) return OpenLmisResponse.error(messageService.message("error.stock.event.invalid"), HttpStatus.BAD_REQUEST); String productCode = event.getProductCode(); if(null == productService.getByCode(productCode)) return OpenLmisResponse.error(messageService.message("error.product.unknown"), HttpStatus.BAD_REQUEST); StockAdjustmentReason reason = null; if (StockEventType.ADJUSTMENT == event.getType()) { reason = stockAdjustmentReasonRepository.getAdjustmentReasonByName( event.getReasonName()); if(null == reason) return OpenLmisResponse.error(messageService.message("error.stockadjustmentreason.unknown"), HttpStatus.BAD_REQUEST); } List<Right> rights = roleRightsService.getRightsForUserFacilityAndProductCode(userId, facilityId, productCode); if (!any(rights, with("MANAGE_STOCK"))) { return OpenLmisResponse.error(messageService.message("error.permission.stock.card.manage"), HttpStatus.FORBIDDEN); } StockCard card = service.getOrCreateStockCard(facilityId, productCode); if(null == card) return OpenLmisResponse.error(messageService.message("error.stock.card.get"), HttpStatus.INTERNAL_SERVER_ERROR); StringBuilder str = new StringBuilder(); Long lotId = event.getLotId(); Lot lotObj = event.getLot(); LotOnHand lotOnHand = service.getLotOnHand(lotId, lotObj, productCode, card, str); if (!str.toString().equals("")) { return OpenLmisResponse.error(messageService.message(str.toString()), HttpStatus.BAD_REQUEST); } long quantity = event.getPositiveOrNegativeQuantity(reason); StockCardEntryType entryType = StockCardEntryType.ADJUSTMENT; switch (event.getType()) { case ISSUE: entryType = StockCardEntryType.DEBIT; break; case RECEIPT: entryType = StockCardEntryType.CREDIT; break; case ADJUSTMENT: entryType = StockCardEntryType.ADJUSTMENT; break; default: break; } Date occurred = event.getOccurred(); String referenceNumber = event.getReferenceNumber(); StockCardEntry entry = new StockCardEntry(card, entryType, quantity, occurred, referenceNumber); entry.setAdjustmentReason(reason); entry.setLotOnHand(lotOnHand); Map<String, String> customProps = event.getCustomProps(); if (null != customProps) { for (String k : customProps.keySet()) { entry.addKeyValue(k, customProps.get(k)); } } entry.setCreatedBy(userId); entry.setModifiedBy(userId); entries.add(entry); } service.addStockCardEntries(entries); return OpenLmisResponse.success(messageService.message("success.stock.adjusted")); } StockCardController(MessageService messageService,
                        FacilityRepository facilityRepository,
                        ProductService productService,
                        StockAdjustmentReasonRepository stockAdjustmentReasonRepository,
                        StockCardRepository stockCardRepository,
                        LotRepository lotRepository,
                        ProgramProductService programProductService,
                        ProgramService programService,
                        RoleRightsService roleRightsService,
                        StockCardService service); @RequestMapping(value = "facilities/{facilityId}/products/{productCode}/stockCard", method = GET, headers = ACCEPT_JSON) @ApiOperation(value = "Get information about the stock card for the specified facility and product.", notes = "Gets stock card information, by facility and product." + "<p>If no view permissions are found for this stock card, will return 403 Forbidden." + "<p>" + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock card.</li>" + "<li><strong>productCode</strong> (String) - product for the stock card.</li>" + "</ul>" + "<p>" + "<p>Request parameters:" + "<ul>" + "<li><strong>entries</strong> (Integer, optional, default = 1) - Number of stock card entries to " + "get in the result.</li>" + "</ul>") ResponseEntity getStockCard(@PathVariable Long facilityId,
                                       @PathVariable String productCode,
                                       @RequestParam(value = "entries", defaultValue = "1")Integer entries,
                                       @RequestParam(value = "includeEmptyLots", required = false, defaultValue = "false") boolean includeEmptyLots,
                                       HttpServletRequest request); @RequestMapping(value = "facilities/{facilityId}/stockCards/{stockCardId}", method = GET, headers = ACCEPT_JSON) @ApiOperation(value = "Get information about the specified stock card for the specified facility.", notes = "Gets stock card information, by facility and stock card id." + "<p>If facility does not have specified stock card id, will return 404 Not Found." + "<p>If no view permissions are found for this stock card, will return 403 Forbidden." + "<p>" + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock card.</li>" + "<li><strong>stockCardId</strong> (Long) - the specified stock card.</li>" + "</ul>" + "<p>" + "<p>Request parameters:" + "<ul>" + "<li><strong>entries</strong> (Integer, optional, default = 1) - Number of stock card entries to " + "get in the result.</li>" + "</ul>") ResponseEntity getStockCardById(@PathVariable Long facilityId, @PathVariable Long stockCardId,
                                           @RequestParam(value = "entries", defaultValue = "1")Integer entries,
                                           @RequestParam(value = "includeEmptyLots", required = false, defaultValue = "false") boolean includeEmptyLots,
                                           HttpServletRequest request); @RequestMapping(value = "facilities/{facilityId}/stockCards", method = GET, headers = ACCEPT_JSON) @ApiOperation(value = "Get information about all stock cards for the specified facility.", notes = "Gets all stock card information, by facility." + "<p>If no stock cards exist, will return 404 Not Found." + "<p>If no view permissions are found for any existing stock card, will return an empty list." + "<p>" + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards.</li>" + "</ul>" + "<p>" + "<p>Request parameters:" + "<ul>" + "<li><strong>entries</strong> (Integer, optional, default = 1) - Number of stock card entries to " + "get in the result.</li>" + "<li><strong>countOnly</strong> (Boolean, optional, default = false) - Get only the count of " + "stock cards.</li>" + "</ul>") ResponseEntity getStockCards(@PathVariable Long facilityId,
                                        @RequestParam(value = "entries", defaultValue = "1") Integer entries,
                                        @RequestParam(value = "countOnly", defaultValue = "false") Boolean countOnly,
                                        @RequestParam(value = "includeEmptyLots", required = false, defaultValue = "false") boolean includeEmptyLots,
                                        HttpServletRequest request); @RequestMapping(value = "facilities/{facilityId}/stockCards", method = POST, headers = ACCEPT_JSON) @ApiOperation(value="Update stock cards at a facility.", notes = "Updates stock cards at a facility. This is done by providing a list of stock events." + "<p>Path parameters (required):" + "<ul>" + "<li><strong>facilityId</strong> (Long) - facility for the stock cards in which to update.</li>" + "</ul>" + "<p>" + "<p>Body parameters (required):" + "<ul>" + "<li>" + "<strong>stock events</strong> (Array of stock event objects) - a list of stock events to " + "process for update." + "<p>" + "<p>Stock event properties" + "<ul>" + "<li><strong>type</strong> (String, required) - type code of stock event (choices are ISSUE, " + "RECEIPT, ADJUSTMENT).</li>" + "<li><strong>facilityId</strong> (Long, required for ISSUE/RECEIPT types) - facility id where" + "stock is going to/coming from.</li>" + "<li><strong>productCode</strong> (String, required) - product code of the stock being " + "processed.</li>" + "<li><strong>quantity</strong> (Long, required) - quantity of stock being processed. Specify as a " + "positive number. For ISSUE, this amount is decremented, for RECEIPT, this amount is incremented, " + "for ADJUSTMENT, it depends on the adjustment reason.</li>" + "<li><strong>reasonName</strong> (String, required for ADJUSTMENT types) - reason code for the " + "adjustment.</li>" + "<li><strong>lotId</strong> (Long, optional) - lot id of a particular lot that will be processed. " + "This and lot are optional; if lotId is specified, lot is ignored.</li>" + "<li><strong>lot</strong> (Object, optional) - lot object of a particular lot that will be " + "processed. If lot with the specified code, manufacturerName, and expirationDate do not exist, a " + "lot will be created.</li>" + "<li><strong>customProps</strong> (Object, optional) - an object of custom properties (keys and " + "values) to specify custom fields for the stock event." + "</ul>" + "</li>" + "</ul>" + "<p>" + "<p>Example stock event list JSON:" + "<pre><code>" + "[\n" + " {\n" + " \"type\": \"ADJUSTMENT\",\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"reasonName\": \"TRANSFER_IN\",\n" + " \"lotId\": 1\n" + " },\n" + " {\n" + " \"type\": \"ISSUE\",\n" + " \"facilityId\": 19077,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"customProps\": {\n" + " \"occurred\": \"2015-10-01\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"RECEIPT\",\n" + " \"facilityId\": 19074,\n" + " \"productCode\": \"V001\",\n" + " \"quantity\": 50,\n" + " \"lot\": {\n" + " \"lotCode\": \"C1\",\n" + " \"manufacturerName\": \"Manufacturer 3\",\n" + " \"expirationDate\": \"2016-07-01\"\n" + " },\n" + " \"customProps\": {\n" + " \"vvmStatus\": \"1\"\n" + " }\n" + " }\n" + "]\n" + "</code></pre>") @Transactional ResponseEntity processStock(@PathVariable long facilityId,
                                       @RequestBody(required = true) List<StockEvent> events,
                                       HttpServletRequest request);  }