Example #1
0
 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())
Example #2
0
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)]
Example #3
0
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)]
Example #4
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
Example #5
0
    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)
Example #6
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
Example #7
0
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
Example #8
0
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
Example #9
0
 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())
Example #10
0
 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)
Example #11
0
 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())
Example #12
0
    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)
Example #13
0
    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)
Example #14
0
 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)
Example #15
0
 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)
Example #16
0
 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)
Example #17
0
def _get_resupply_amounts(hsa):
    return [
        req.sms_format() for req in StockRequest.pending_requests().filter(
            supply_point=hsa.supply_point)
    ]
Example #18
0
    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)
Example #19
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())