def handle(self, text): # at the end of your receipt message you can write: # 'from xxx' to indicate the source of the supplies. # this is used in the stock transfer workflow # TODO: less brittle parsing if "from" in text.lower().split(): splittext = text.lower().split() text = " ".join(splittext[:splittext.index("from")]) supplier = " ".join(splittext[splittext.index("from") + 1:]) else: supplier = None # parse the report and save as normal receipt stock_report = ProductReportsHelper(self.msg.logistics_contact.supply_point, Reports.REC, self.msg.logger_msg) stock_report.parse(text) stock_report.save() # Close pending requests. This logic only applies if you are using the # StockRequest workflow, but should not break anything if you are not StockRequest.close_pending_from_receipt_report(stock_report, self.msg.logistics_contact) # fill in transfers, if there were any if supplier is not None: StockTransfer.create_from_receipt_report(stock_report, supplier) self.respond(_(config.Messages.RECEIPT_FROM_CONFIRM), products=" ".join(stock_report.reported_products()).strip(), supplier=supplier) else: self.respond(_(config.Messages.RECEIPT_CONFIRM), products=" ".join(stock_report.reported_products()).strip())
def health_center_unable_resupply_emergency(request): hsas = hsa_supply_points_below(request.location) return [HealthCenterUnableResupplyEmergencyAlert(s.supply_point, s.product)\ for s in StockRequest.pending_requests()\ .filter(is_emergency=True, status=StockRequestStatus.STOCKED_OUT, supply_point__in=hsas)]
def health_center_unable_resupply_stockout(request): r = [] hsas = hsa_supply_points_below(request.location) for s in StockRequest.pending_requests().filter(supply_point__in=hsas, status=StockRequestStatus.STOCKED_OUT): if s.supply_point.stock(s.product) == 0: r += [HealthCenterUnableResupplyStockoutAlert(s.supply_point, s.product)] return r
def handle_custom(self, text): now = datetime.utcnow() # Currently we just mark these stock requests stocked out. # Note that this has a different meaning for emergency orders # in which case we only confirm the emergency products. # However in the interest of simplicity we won't worry about that (yet?). pending_reqs = StockRequest.pending_requests().filter(supply_point=self.hsa.supply_point) for req in pending_reqs: req.mark_stockout(self.msg.logistics_contact, now) # if there were any emergency orders, only report those as stockouts # this is pretty confusing/hacky emergencies = pending_reqs.filter(is_emergency=True) stockouts = pending_reqs.filter(balance=0) if stockouts.count() > 0: reqs = stockouts elif emergencies.count() > 0: reqs = emergencies else: reqs = pending_reqs def _message_supervisors(message): supplier = self.msg.logistics_contact.supply_point.supplied_by if supplier is not None: supervisors = Contact.objects.filter(is_active=True, supply_point__location=supplier.location, role__in=[ContactRole.objects.get(code=config.Roles.DISTRICT_PHARMACIST), ContactRole.objects.get(code=config.Roles.IMCI_COORDINATOR)]) # note that if there are no supervisors registered, this will silently # not send notifications for super in supervisors: super.message(message, contact=self.msg.logistics_contact.name, supply_point=self.msg.logistics_contact.supply_point.name, products=", ".join(req.product.sms_code for req in reqs)) if emergencies.count() > 0: # Unable to resupply stocked out/emergency products. self.respond(config.Messages.HF_UNABLE_RESTOCK_EO, products=", ".join(req.product.sms_code for req in reqs)) self.hsa.message(config.Messages.HSA_UNABLE_RESTOCK_EO, hsa=self.hsa.name,products=", ".join(req.product.sms_code for req in reqs)) if stockouts.count() > 0: _message_supervisors(config.Messages.DISTRICT_UNABLE_RESTOCK_STOCKOUT) else: _message_supervisors(config.Messages.DISTRICT_UNABLE_RESTOCK_EO) else: self.respond(config.Messages.HF_UNABLE_RESTOCK_EO, products=", ".join(req.product.sms_code for req in reqs)) self.hsa.message(config.Messages.HSA_UNABLE_RESTOCK_ANYTHING, hsa=self.hsa.name) _message_supervisors(config.Messages.DISTRICT_UNABLE_RESTOCK_NORMAL) # this is pretty hacky, but set the SoH to 0 for the stocked out products # so that they show properly in things like alerts for req in reqs: self.msg.logistics_contact.supply_point.update_stock(req.product, 0)
def health_center_unable_resupply_stockout(request): r = [] hsas = hsa_supply_points_below(request.location) for s in StockRequest.pending_requests().filter( supply_point__in=hsas, status=StockRequestStatus.STOCKED_OUT): if s.supply_point.stock(s.product) == 0: r += [ HealthCenterUnableResupplyStockoutAlert( s.supply_point, s.product) ] return r
def hsa_below_emergency_quantity(request): ''' This query finds HSA/product pairs where the product is below emergency level but there are no pending requests. ''' hsas = hsa_supply_points_below(request.location) r = [] for p in ProductStock.objects.filter(is_active=True, supply_point__in=hsas, quantity__lte = F('product__emergency_order_level')): if not StockRequest.pending_requests().filter(product=p.product, supply_point=p.supply_point).exists(): r += [HSABelowEmergencyQuantityAlert(p.supply_point, p.product)] return r
def hsa_below_emergency_quantity(request): ''' This query finds HSA/product pairs where the product is below emergency level but there are no pending requests. ''' hsas = hsa_supply_points_below(request.location) r = [] for p in ProductStock.objects.filter( is_active=True, supply_point__in=hsas, quantity__lte=F('product__emergency_order_level')): if not StockRequest.pending_requests().filter( product=p.product, supply_point=p.supply_point).exists(): r += [HSABelowEmergencyQuantityAlert(p.supply_point, p.product)] return r
def _process_rec(self): stock_report = create_stock_report(Reports.REC, self.hsa.supply_point, self.report_data, self.msg.logger_msg) requests = StockRequest.close_pending_from_receipt_report(stock_report, self.hsa) if stock_report.errors: # TODO: respond better. self.respond(config.Messages.GENERIC_ERROR) else: self.respond(config.Messages.REPORT_RECEIPT_RESPONSE, reporter=self.msg.logistics_contact.name, hsa=self.hsa.name, products=" ".join(stock_report.reported_products()).strip())
def handle_custom(self, text): now = datetime.utcnow() pending_reqs = StockRequest.pending_requests().filter(supply_point=self.hsa.supply_point) for req in pending_reqs: req.approve(self.msg.logistics_contact, now, req.amount_requested) self.respond(config.Messages.APPROVAL_RESPONSE, hsa=self.hsa.name) self.hsa.message(config.Messages.APPROVAL_NOTICE, hsa=self.hsa.name) # this is really hacky, but set the SoH to non-zero for the reported products # so that they show no longer stocked out in things like alerts for req in pending_reqs: if self.msg.logistics_contact.supply_point.stock(req.product) == 0: self.msg.logistics_contact.supply_point.update_stock(req.product, 1)
def _process_rec(self): stock_report = create_stock_report(Reports.REC, self.hsa.supply_point, self.report_data, self.msg.logger_msg) requests = StockRequest.close_pending_from_receipt_report( stock_report, self.hsa) if stock_report.errors: # TODO: respond better. self.respond(config.Messages.GENERIC_ERROR) else: self.respond(config.Messages.REPORT_RECEIPT_RESPONSE, reporter=self.msg.logistics_contact.name, hsa=self.hsa.name, products=" ".join( stock_report.reported_products()).strip())
def handle_custom(self, text): now = datetime.utcnow() pending_reqs = StockRequest.pending_requests().filter( supply_point=self.hsa.supply_point) for req in pending_reqs: req.approve(self.msg.logistics_contact, now, req.amount_requested) self.respond(config.Messages.APPROVAL_RESPONSE, hsa=self.hsa.name) self.hsa.message(config.Messages.APPROVAL_NOTICE, hsa=self.hsa.name) # this is really hacky, but set the SoH to non-zero for the reported products # so that they show no longer stocked out in things like alerts for req in pending_reqs: if self.msg.logistics_contact.supply_point.stock(req.product) == 0: self.msg.logistics_contact.supply_point.update_stock( req.product, 1)
def handle(self, text): """ Check some preconditions, based on shared assumptions of these handlers. Return true if there is a precondition that wasn't met. If all preconditions are met, the variables for facility and name will be set. This method will manage some replies as well. """ # at some point we may want more granular permissions for these # operations, but for now we just share the one self.hsa = self.msg.logistics_contact stock_report = create_stock_report(self.get_report_type(), self.hsa.supply_point, text, self.msg.logger_msg) self.requests = StockRequest.create_from_report(stock_report, self.hsa) self.send_responses(stock_report)
def _process_emergency_soh(self): stock_report = create_stock_report(Reports.EMERGENCY_SOH, self.hsa.supply_point, self.report_data, self.msg.logger_msg) requests = StockRequest.create_from_report(stock_report, self.hsa) if stock_report.errors: # TODO: respond better. self.respond(config.Messages.GENERIC_ERROR) else: if self.msg.logistics_contact.role == ContactRole.objects.get(code=config.Roles.IN_CHARGE): self.respond(config.Messages.REPORT_SOH_RESPONSE, hsa=self.hsa.name, products=", ".join(req.sms_format() for req in requests), hsa_id=self.hsa.supply_point.code) else: assert(self.msg.logistics_contact.role == ContactRole.objects.get(code=config.Roles.HSA)) send_emergency_responses(self.msg, self.hsa, stock_report, requests)
def _process_soh(self): stock_report = create_stock_report(Reports.SOH, self.hsa.supply_point, self.report_data, self.msg.logger_msg) requests = StockRequest.create_from_report(stock_report, self.hsa) if stock_report.errors: # TODO: respond better. self.respond(config.Messages.GENERIC_ERROR) else: if self.msg.logistics_contact.role == ContactRole.objects.get( code=config.Roles.IN_CHARGE): self.respond(config.Messages.REPORT_SOH_RESPONSE, hsa=self.hsa.name, products=", ".join(req.sms_format() for req in requests), hsa_id=self.hsa.supply_point.code) else: assert ( self.msg.logistics_contact.role == ContactRole.objects.get( code=config.Roles.HSA)) send_soh_responses(self.msg, self.hsa, stock_report, requests)
def _get_resupply_amounts(hsa): return [ req.sms_format() for req in StockRequest.pending_requests().filter( supply_point=hsa.supply_point) ]
def handle_custom(self, text): now = datetime.utcnow() # Currently we just mark these stock requests stocked out. # Note that this has a different meaning for emergency orders # in which case we only confirm the emergency products. # However in the interest of simplicity we won't worry about that (yet?). pending_reqs = StockRequest.pending_requests().filter( supply_point=self.hsa.supply_point) for req in pending_reqs: req.mark_stockout(self.msg.logistics_contact, now) # if there were any emergency orders, only report those as stockouts # this is pretty confusing/hacky emergencies = pending_reqs.filter(is_emergency=True) stockouts = pending_reqs.filter(balance=0) if stockouts.count() > 0: reqs = stockouts elif emergencies.count() > 0: reqs = emergencies else: reqs = pending_reqs def _message_supervisors(message): supplier = self.msg.logistics_contact.supply_point.supplied_by if supplier is not None: supervisors = Contact.objects.filter( is_active=True, supply_point__location=supplier.location, role__in=[ ContactRole.objects.get( code=config.Roles.DISTRICT_PHARMACIST), ContactRole.objects.get( code=config.Roles.IMCI_COORDINATOR) ]) # note that if there are no supervisors registered, this will silently # not send notifications for super in supervisors: super.message(message, contact=self.msg.logistics_contact.name, supply_point=self.msg.logistics_contact. supply_point.name, products=", ".join(req.product.sms_code for req in reqs)) if emergencies.count() > 0: # Unable to resupply stocked out/emergency products. self.respond(config.Messages.HF_UNABLE_RESTOCK_EO, products=", ".join(req.product.sms_code for req in reqs)) self.hsa.message(config.Messages.HSA_UNABLE_RESTOCK_EO, hsa=self.hsa.name, products=", ".join(req.product.sms_code for req in reqs)) if stockouts.count() > 0: _message_supervisors( config.Messages.DISTRICT_UNABLE_RESTOCK_STOCKOUT) else: _message_supervisors( config.Messages.DISTRICT_UNABLE_RESTOCK_EO) else: self.respond(config.Messages.HF_UNABLE_RESTOCK_EO, products=", ".join(req.product.sms_code for req in reqs)) self.hsa.message(config.Messages.HSA_UNABLE_RESTOCK_ANYTHING, hsa=self.hsa.name) _message_supervisors( config.Messages.DISTRICT_UNABLE_RESTOCK_NORMAL) # this is pretty hacky, but set the SoH to 0 for the stocked out products # so that they show properly in things like alerts for req in reqs: self.msg.logistics_contact.supply_point.update_stock( req.product, 0)
def _get_resupply_amounts(hsa): return [req.sms_format() for req in StockRequest.pending_requests().filter(supply_point=hsa.supply_point)]
class ReceiptHandler(KeywordHandler, TaggingHandler): """ Allows SMS reporters to send in "rec jd 10 mc 30" to report 10 jadelle and 30 male condoms received """ keyword = logistics_keyword("rec|receipts|received") def help(self): self.respond( _("Please send in information about your receipts in the format 'rec <product> <amount> <product> <amount>...'" )) @logistics_contact_and_permission_required(config.Operations.REPORT_RECEIPT ) def handle(self, text): dupes = Message.objects.filter( direction="I", connection=self.msg.connection, text__iexact=self.msg.raw_text).exclude(pk=self.msg.logger_msg.pk) if settings.LOGISTICS_IGNORE_DUPE_RECEIPTS_WITHIN: dupe_ignore_threshold = datetime.utcnow() - timedelta( seconds=settings.LOGISTICS_IGNORE_DUPE_RECEIPTS_WITHIN) ignore = dupes.filter(date__gte=dupe_ignore_threshold) if ignore.count() and ProductReport.objects.filter( message__in=dupes).count(): return True # at the end of your receipt message you can write: # 'from xxx' to indicate the source of the supplies. # this is used in the stock transfer workflow # TODO: less brittle parsing if "from" in text.lower().split(): splittext = text.lower().split() text = " ".join(splittext[:splittext.index("from")]) supplier = " ".join(splittext[splittext.index("from") + 1:]) else: supplier = None # parse the report and save as normal receipt stock_report = ProductReportsHelper( self.msg.logistics_contact.supply_point, Reports.REC, self.msg.logger_msg) stock_report.parse(text) # check max stock levels max_level_function = get_max_level_function() if max_level_function: try: max_level_function(stock_report) except TooMuchStockError, e: # bit of a hack, also check if there was a recent message # that matched this and if so force it through override_threshold = datetime.utcnow() - timedelta(seconds=60 * 60 * 4) override = dupes.filter(date__gte=override_threshold) if override.count() == 0: self.respond( config.Messages.TOO_MUCH_STOCK % { 'keyword': self.msg.text.split()[0], 'req': e.amount, 'prod': e.product, 'max': e.max, }) return True stock_report.save() # Close pending requests. This logic only applies if you are using the # StockRequest workflow, but should not break anything if you are not StockRequest.close_pending_from_receipt_report( stock_report, self.msg.logistics_contact) # fill in transfers, if there were any if supplier is not None: StockTransfer.create_from_receipt_report(stock_report, supplier) self.respond(_(config.Messages.RECEIPT_FROM_CONFIRM), products=" ".join( stock_report.reported_products()).strip(), supplier=supplier) else: self.respond(_(config.Messages.RECEIPT_CONFIRM), products=" ".join( stock_report.reported_products()).strip())