Beispiel #1
0
    def test_since(self):
        enddate = datetime(2013, 7, 21, 12, 30, 45)
        datespan_inclusive = DateSpan.since(7, enddate)
        self.assertEqual(datespan_inclusive.enddate, datetime(2013, 7, 21, 0, 0, 0))
        self.assertEqual(datespan_inclusive.startdate, datetime(2013, 7, 15, 0, 0, 0))

        datespan_non_inclusive = DateSpan.since(7, enddate, inclusive=False)
        self.assertEqual(datespan_non_inclusive.enddate, datetime(2013, 7, 21, 0, 0, 0))
        self.assertEqual(datespan_non_inclusive.startdate, datetime(2013, 7, 14, 0, 0, 0))
Beispiel #2
0
    def test_since(self):
        enddate = datetime(2013, 7, 21, 12, 30, 45)
        datespan_inclusive = DateSpan.since(7, enddate)
        self.assertEqual(datespan_inclusive.enddate, datetime(2013, 7, 21, 0, 0, 0))
        self.assertEqual(datespan_inclusive.startdate, datetime(2013, 7, 15, 0, 0, 0))

        datespan_non_inclusive = DateSpan.since(7, enddate, inclusive=False)
        self.assertEqual(datespan_non_inclusive.enddate, datetime(2013, 7, 21, 0, 0, 0))
        self.assertEqual(datespan_non_inclusive.startdate, datetime(2013, 7, 14, 0, 0, 0))
Beispiel #3
0
 def default_datespan(self):
     datespan = DateSpan.since(self.datespan_default_days,
                               timezone=self.timezone,
                               inclusive=self.inclusive)
     datespan.max_days = self.datespan_max_days
     datespan.is_default = True
     return datespan
Beispiel #4
0
    def update_context(self):
        self.context["datespan_name"] = self.name

        range = self.request.GET.get('range', None)
        if range:
            dates = str(range).split(_(' to '))
            self.request.datespan.startdate = datetime.datetime.combine(
                iso_string_to_date(dates[0]), datetime.time())
            self.request.datespan.enddate = datetime.datetime.combine(
                iso_string_to_date(dates[1]), datetime.time())

        self.datespan = DateSpan.since(self.default_days, timezone=self.timezone, inclusive=self.inclusive)
        if self.request.datespan.is_valid():
            self.datespan.startdate = self.request.datespan.startdate
            self.datespan.enddate = self.request.datespan.enddate
        self.context['timezone'] = self.timezone.zone
        self.context['datespan'] = self.datespan

        report_labels = json.dumps({
            'year_to_date': _('Year to Date'), 'last_month': _('Last Month'),
            'last_quarter': _('Last Quarter'), 'last_two_quarters': _('Last Two Quarters'),
            'last_three_quarters': _('Last Three Quarters'), 'last_year': _('Last Year'),
            'last_two_years': _('Last Two Years'), 'last_three_years': _('Last Three Years'),
            'last_four_years': _('Last Four Years')
        })

        self.context['report_labels'] = report_labels
        self.context['separator'] = _(' to ')
Beispiel #5
0
 def datespan(self):
     datespan = DateSpan.since(self.default_days, enddate=datetime.date.today(), timezone=self.timezone)
     if self.get_start_date(self.request) is not None:
         datespan.startdate = self.get_start_date(self.request)
     if self.get_end_date(self.request) is not None:
         datespan.enddate = self.get_end_date(self.request)
     return datespan
Beispiel #6
0
 def wrapped_func(*args, **kwargs):
     # attempt to find the request object from all the argument
     # values, checking first the args and then the kwargs
     req = None
     for arg in args:
         if _is_http_request(arg):
             req = arg
             break
     if not req:
         for arg in kwargs.values():
             if _is_http_request(arg):
                 req = arg
                 break
     if req:
         dict = req.POST if req.method == "POST" else req.GET
         def date_or_nothing(param):
             return datetime.strptime(dict[param], format_string)\
                      if param in dict and dict[param] else None
         try:
             startdate = date_or_nothing(from_param)
             enddate = date_or_nothing(to_param)
         except ValueError, e:
             return HttpResponseBadRequest(unicode(e))
         if startdate or enddate:
             req.datespan = DateSpan(startdate, enddate, format_string)
         else:
             # default to the last N days
             req.datespan = DateSpan.since(default_days, format=format_string, inclusive=inclusive)
             req.datespan.is_default = True
def order_fill_stats(locations, type=None, datespan=None):
    """
    With a list of locations - display reporting
    rates associated with those locations.
    This method only looks at closed orders
    """
    if locations:
        if datespan == None:
            # default to last 30 days
            datespan = DateSpan.since(30)
        base_points = SupplyPoint.objects.filter(location__in=locations, active=True)
        if type is not None:
            base_points = base_points.filter(type__code=type)
        if base_points.count() > 0:
            base_reqs = StockRequest.objects.filter(
                supply_point__in=base_points, requested_on__gte=datespan.startdate, requested_on__lte=datespan.enddate
            )
            rec_reqs = base_reqs.filter(status=StockRequestStatus.RECEIVED)
            totals = base_reqs.values("product").annotate(total=Count("pk"))
            rec_totals = rec_reqs.values("product").annotate(total=Count("pk"))
            eo_totals = base_reqs.filter(is_emergency=True).values("product").annotate(total=Count("pk"))
            stocked_out = rec_reqs.filter(amount_received=0).values("product").annotate(total=Count("pk"))
            not_stocked_out = rec_reqs.filter(amount_received__gt=0).exclude(
                response_status=StockRequestStatus.STOCKED_OUT
            )
            under_supplied = (
                not_stocked_out.filter(amount_requested__gt=F("amount_received"))
                .values("product")
                .annotate(total=Count("pk"))
            )
            well_supplied = (
                not_stocked_out.filter(amount_requested=F("amount_received"))
                .values("product")
                .annotate(total=Count("pk"))
            )
            over_supplied = (
                not_stocked_out.filter(amount_requested__lt=F("amount_received"))
                .values("product")
                .annotate(total=Count("pk"))
            )
            main_data = {}
            for row in totals:
                main_data[row["product"]] = defaultdict(lambda x: 0)
                main_data[row["product"]]["product"] = Product.objects.get(pk=row["product"])
                main_data[row["product"]]["total"] = row["total"]

            def _update_main_data(main, to_update, tag):
                for row in to_update:
                    main[row["product"]][tag] = row["total"]

            _update_main_data(main_data, rec_totals, "filled")
            _update_main_data(main_data, eo_totals, "emergency")
            _update_main_data(main_data, stocked_out, "stocked_out")
            _update_main_data(main_data, under_supplied, "under_supplied")
            _update_main_data(main_data, well_supplied, "well_supplied")
            _update_main_data(main_data, over_supplied, "over_supplied")
            return r_2_s_helper("logistics/partials/order_fill_stats.html", {"data": main_data, "datespan": datespan})

    return ""  # no data, no report
def stockonhand_table(supply_point, datespan=None):
    if datespan is None:
        datespan = DateSpan.since(settings.LOGISTICS_REPORTING_CYCLE_IN_DAYS)
    sohs = supply_point.stocked_productstocks().order_by("product__name")
    # update the stock quantities to match whatever reporting period has been specified
    for soh in sohs:
        soh.quantity = supply_point.historical_stock_by_date(soh.product, datespan.end_of_end_day)
    return r_2_s_helper("logistics/partials/stockonhand_table_full.html", {"stockonhands": sohs, "datespan": datespan})
Beispiel #9
0
 def __init__(self, filter, value):
     assert filter.type == 'date'
     # todo: might want some better way to set defaults
     if value is None:
         # default to one week
         value = DateSpan.since(7)
     assert isinstance(value, DateSpan)
     super(DateFilterValue, self).__init__(filter, value)
Beispiel #10
0
 def datespan(self):
     datespan = DateSpan.since(self.default_days,
                               format="%Y-%m-%d",
                               timezone=self.timezone)
     if self.request.datespan.is_valid() and self.slug == 'datespan':
         datespan.startdate = self.request.datespan.startdate
         datespan.enddate = self.request.datespan.enddate
     return datespan
Beispiel #11
0
 def update_context(self):
     self.context["datespan_name"] = self.name
     self.datespan = DateSpan.since(self.default_days, timezone=self.timezone, inclusive=self.inclusive)
     if self.request.datespan.is_valid():
         self.datespan.startdate = self.request.datespan.startdate
         self.datespan.enddate = self.request.datespan.enddate
     self.context['timezone'] = self.timezone.zone
     self.context['datespan'] = self.datespan
Beispiel #12
0
 def update_context(self):
     self.context["datespan_name"] = self.name
     self.datespan = DateSpan.since(7, format="%Y-%m-%d", timezone=self.timezone)
     if self.request.datespan.is_valid():
         self.datespan.startdate = self.request.datespan.startdate
         self.datespan.enddate = self.request.datespan.enddate
     self.context['timezone'] = self.timezone.zone
     self.context['datespan'] = self.datespan
Beispiel #13
0
 def datespan(self):
     datespan = DateSpan.since(self.default_days,
                               timezone=self.timezone,
                               inclusive=self.inclusive)
     if self.request.datespan.is_valid() and self.slug == 'datespan':
         datespan.startdate = self.request.datespan.startdate
         datespan.enddate = self.request.datespan.enddate
     return datespan
Beispiel #14
0
 def datespan(self):
     datespan = DateSpan.since(self.default_days,
                               enddate=datetime.date.today(),
                               timezone=self.timezone)
     if self.get_start_date(self.request) is not None:
         datespan.startdate = self.get_start_date(self.request)
     if self.get_end_date(self.request) is not None:
         datespan.enddate = self.get_end_date(self.request)
     return datespan
Beispiel #15
0
def datespan_in_request(from_param="from",
                        to_param="to",
                        format_string=ISO_DATE_FORMAT,
                        default_days=30,
                        inclusive=True,
                        default_function=None):
    """
    Wraps a request with dates based on url params or defaults and
    Checks date validity.
    """
    # you can pass in a function to say what the default should be,
    # if you don't it will pull the value from the last default_days
    # in. If you override default_function, default_days is ignored.
    if default_function is None:
        default_function = lambda: DateSpan.since(
            default_days, format=format_string, inclusive=inclusive)

    # this is loosely modeled after example number 4 of decorator
    # usage here: http://www.python.org/dev/peps/pep-0318/
    def get_dates(f):
        def wrapped_func(*args, **kwargs):
            # attempt to find the request object from all the argument
            # values, checking first the args and then the kwargs
            req = request_from_args_or_kwargs(*args, **kwargs)
            if req:
                req_dict = req.POST if req.method == "POST" else req.GET

                def date_or_nothing(param):
                    date = req_dict.get(param, None)
                    return datetime.strptime(date,
                                             format_string) if date else None

                try:
                    startdate = date_or_nothing(from_param)
                    enddate = date_or_nothing(to_param)
                except ValueError as e:
                    return HttpResponseBadRequest(six.text_type(e))
                if startdate or enddate:
                    req.datespan = DateSpan(startdate, enddate, format_string)
                else:
                    req.datespan = default_function()
                    req.datespan.is_default = True

            return f(*args, **kwargs)

        if hasattr(f, "func_name"):
            wrapped_func.__name__ = f.__name__
            # preserve doc strings
            wrapped_func.__doc__ = f.__doc__
            return wrapped_func
        else:
            # this means it wasn't actually a view.
            return f

    return get_dates
Beispiel #16
0
    def default_datespan(self):
        # DateSpan.since() will make enddate default to yesterday when it's None
        enddate = None
        if self.default_datespan_end_date_to_today:
            enddate = ServerTime(datetime.utcnow()).user_time(self.timezone).done().date()

        datespan = DateSpan.since(self.datespan_default_days, enddate=enddate, inclusive=self.inclusive,
            timezone=self.timezone)
        datespan.max_days = self.datespan_max_days
        datespan.is_default = True
        return datespan
def stockonhand_table(supply_point, datespan=None):
    if datespan is None:
        datespan = DateSpan.since(settings.LOGISTICS_REPORTING_CYCLE_IN_DAYS)
    sohs = supply_point.stocked_productstocks().order_by('product__name')
    # update the stock quantities to match whatever reporting period has been specified
    for soh in sohs:
        soh.quantity = supply_point.historical_stock_by_date(
            soh.product, datespan.end_of_end_day)
    return r_2_s_helper("logistics/partials/stockonhand_table_full.html", {
        "stockonhands": sohs,
        "datespan": datespan
    })
Beispiel #18
0
    def default_datespan(self):
        # DateSpan.since() will make enddate default to yesterday when it's None
        enddate = None
        if self.default_datespan_end_date_to_today:
            enddate = ServerTime(datetime.utcnow()).user_time(
                self.timezone).done().date()

        datespan = DateSpan.since(self.datespan_default_days,
                                  enddate=enddate,
                                  inclusive=self.inclusive,
                                  timezone=self.timezone)
        datespan.max_days = self.datespan_max_days
        datespan.is_default = True
        return datespan
Beispiel #19
0
def datespan_in_request(from_param="from", to_param="to",
                        format_string=ISO_DATE_FORMAT, default_days=30,
                        inclusive=True, default_function=None):
    """
    Wraps a request with dates based on url params or defaults and
    Checks date validity.
    """
    # you can pass in a function to say what the default should be,
    # if you don't it will pull the value from the last default_days
    # in. If you override default_function, default_days is ignored.
    if default_function is None:
        default_function = lambda: DateSpan.since(default_days,
                                                  format=format_string,
                                                  inclusive=inclusive)

    # this is loosely modeled after example number 4 of decorator
    # usage here: http://www.python.org/dev/peps/pep-0318/
    def get_dates(f):
        def wrapped_func(*args, **kwargs):
            # attempt to find the request object from all the argument
            # values, checking first the args and then the kwargs
            req = request_from_args_or_kwargs(*args, **kwargs)
            if req:
                req_dict = req.POST if req.method == "POST" else req.GET
                def date_or_nothing(param):
                    date = req_dict.get(param, None)
                    return datetime.strptime(date, format_string) if date else None
                try:
                    startdate = date_or_nothing(from_param)
                    enddate = date_or_nothing(to_param)
                except ValueError as e:
                    return HttpResponseBadRequest(unicode(e))
                if startdate or enddate:
                    req.datespan = DateSpan(startdate, enddate, format_string)
                else:
                    req.datespan = default_function()
                    req.datespan.is_default = True

            return f(*args, **kwargs)

        if hasattr(f, "func_name"):
            wrapped_func.func_name = f.func_name
            # preserve doc strings
            wrapped_func.__doc__ = f.__doc__
            return wrapped_func
        else:
            # this means it wasn't actually a view.
            return f
    return get_dates
Beispiel #20
0
    def update_context(self):
        self.context["datespan_name"] = self.name

        range = self.request.GET.get('range', None)
        if range is not None:
            dates = str(range).split(_(' to '))
            self.request.datespan.startdate = datetime.datetime.combine(
                iso_string_to_date(dates[0]), datetime.time())
            self.request.datespan.enddate = datetime.datetime.combine(
                iso_string_to_date(dates[1]), datetime.time())

        self.datespan = DateSpan.since(self.default_days,
                                       timezone=self.timezone,
                                       inclusive=self.inclusive)
        if self.request.datespan.is_valid():
            self.datespan.startdate = self.request.datespan.startdate
            self.datespan.enddate = self.request.datespan.enddate
        self.context['timezone'] = self.timezone.zone
        self.context['datespan'] = self.datespan

        report_labels = json.dumps({
            'year_to_date':
            _('Year to Date'),
            'last_month':
            _('Last Month'),
            'last_quarter':
            _('Last Quarter'),
            'last_two_quarters':
            _('Last Two Quarters'),
            'last_three_quarters':
            _('Last Three Quarters'),
            'last_year':
            _('Last Year'),
            'last_two_years':
            _('Last Two Years'),
            'last_three_years':
            _('Last Three Years'),
            'last_four_years':
            _('Last Four Years')
        })

        self.context['report_labels'] = report_labels
        self.context['separator'] = _(' to ')
def district_dashboard(request, template="logistics/district_dashboard.html"):
    districts = get_districts()
    if request.location is None:
        # pick a random location to start
        location_code = settings.COUNTRY
        request.location = get_object_or_404(Location, code=location_code)
        facilities = SupplyPoint.objects.all()
        #request.location = districts[0]
    else:
        facilities = request.location.all_child_facilities()
    report = ReportingBreakdown(facilities,
                                DateSpan.since(settings.LOGISTICS_DAYS_UNTIL_LATE_PRODUCT_REPORT),
                                days_for_late=settings.LOGISTICS_DAYS_UNTIL_LATE_PRODUCT_REPORT)
    return render_to_response(template,
                              {"reporting_data": report,
                               "graph_width": 200,
                               "graph_height": 200,
                               "districts": districts.order_by("code"),
                               "location": request.location},
                              context_instance=RequestContext(request))
Beispiel #22
0
def district_dashboard(request, template="logistics/district_dashboard.html"):
    districts = get_districts()
    if request.location is None:
        # pick a random location to start
        location_code = settings.COUNTRY
        request.location = get_object_or_404(Location, code=location_code)
        facilities = SupplyPoint.objects.all()
        #request.location = districts[0]
    else:
        facilities = request.location.all_child_facilities()
    report = ReportingBreakdown(facilities, 
                                DateSpan.since(settings.LOGISTICS_DAYS_UNTIL_LATE_PRODUCT_REPORT), 
                                days_for_late = settings.LOGISTICS_DAYS_UNTIL_LATE_PRODUCT_REPORT)
    return render_to_response(template,
                              {"reporting_data": report,
                               "graph_width": 200,
                               "graph_height": 200,
                               "districts": districts.order_by("code"),
                               "location": request.location},
                              context_instance=RequestContext(request))
Beispiel #23
0
def get_data(domain, user=None, datespan=None):
    """
    Returns a data structure like:
    
    { <Form display name>:
         { <date>: { count: <count>, 
                     max: <time in ms>, 
                     min: <time in ms>,
                     sum: <time in ms> 
                   }
         }   
    }
    """
    if datespan is None:
        datespan = DateSpan.since(days=30, format="%Y-%m-%dT%H:%M:%S")
    
    all_data = defaultdict(lambda: defaultdict(lambda: 0))
    startkey = ["udx", domain, user, datespan.startdate_param] if user \
                else ["dx", domain, datespan.startdate_param]
    endkey = ["udx", domain, user, datespan.enddate_param] if user \
                else ["dx", domain, datespan.enddate_param]
    view = get_db().view("formtrends/form_duration_by_user", 
                         startkey=startkey,
                         endkey=endkey,
                         group=True,
                         reduce=True)
    for row in view:
        date = row["key"][-2]
        xmlns = row["key"][-1]
        form_name = xmlns_to_name(domain, xmlns, app_id=None)
        data = all_data[form_name]
        if not date in data:
            data[date] = defaultdict(lambda: 0)
        thisrow = row["value"]
        for key, val in thisrow.items():
            data[date][key] = data[date][key] + thisrow[key]
    return all_data
Beispiel #24
0
def get_data(domain, user=None, datespan=None):
    """
    Returns a data structure like:
    
    { <Form display name>:
         { <date>: { count: <count>, 
                     max: <time in ms>, 
                     min: <time in ms>,
                     sum: <time in ms> 
                   }
         }   
    }
    """
    if datespan is None:
        datespan = DateSpan.since(days=30, format="%Y-%m-%dT%H:%M:%S")
    
    all_data = defaultdict(lambda: defaultdict(lambda: 0))
    startkey = ["udx", domain, user, datespan.startdate_param] if user \
                else ["dx", domain, datespan.startdate_param]
    endkey = ["udx", domain, user, datespan.enddate_param] if user \
                else ["dx", domain, datespan.enddate_param]
    view = get_db().view("formtrends/form_duration_by_user", 
                         startkey=startkey,
                         endkey=endkey,
                         group=True,
                         reduce=True)
    for row in view:
        date = row["key"][-2]
        xmlns = row["key"][-1]
        form_name = xmlns_to_name(domain, xmlns, app_id=None)
        data = all_data[form_name]
        if not date in data:
            data[date] = defaultdict(lambda: 0)
        thisrow = row["value"]
        for key, val in thisrow.items():
            data[date][key] = data[date][key] + thisrow[key]
    return all_data
Beispiel #25
0
 def datespan(self):
     datespan = DateSpan.since(self.default_days, timezone=self.timezone, inclusive=self.inclusive)
     if self.request.datespan.is_valid() and self.slug == "datespan":
         datespan.startdate = self.request.datespan.startdate
         datespan.enddate = self.request.datespan.enddate
     return datespan
Beispiel #26
0
 def default_datespan(self):
     datespan = DateSpan.since(self.datespan_default_days, timezone=self.timezone, inclusive=self.inclusive)
     datespan.is_default = True
     return datespan
Beispiel #27
0
    def __init__(self, supply_points, datespan=None, include_late=False,
                 days_for_late=5, MNE=False, request=None):
        self.supply_points = supply_points
        if not datespan:
            datespan = DateSpan.since(30)
        self.datespan = datespan
        
        self._request = request
        self.include_late = include_late
        self.days_for_late = days_for_late
        date_for_late = datespan.startdate + timedelta(days=days_for_late)

        if supply_points.filter(active=False).exists(): self.supply_points = self.supply_points.filter(active=True)

        reports_in_range = ProductReport.objects.filter\
            (report_type__code=Reports.SOH,
             report_date__gte=datespan.startdate,
             report_date__lte=datespan.enddate_param,
             supply_point__in=supply_points,
             supply_point__active=True)
        
        reported_in_range = reports_in_range.values_list\
            ("supply_point", flat=True).distinct()
        
        reported_on_time_in_range = reports_in_range.filter\
            (report_date__lte=date_for_late).values_list\
            ("supply_point", flat=True).distinct()
        
        non_reporting = supply_points.exclude(pk__in=reported_in_range)
        reported = SupplyPoint.objects.filter(pk__in=reported_in_range, active=True)
        reported_late = reported.exclude(pk__in=reported_on_time_in_range)
        reported_on_time = SupplyPoint.objects.filter\
            (pk__in=reported_on_time_in_range, active=True)

        requests_in_range = StockRequest.objects.select_related().filter(\
            requested_on__gte=datespan.startdate,
            requested_on__lte=datespan.enddate,
            supply_point__in=supply_points
        )

        if MNE:
            emergency_requests = requests_in_range.filter(is_emergency=True)

            emergency_requesters = emergency_requests.values_list("supply_point", flat=True).distinct()

            filled_requests = requests_in_range.exclude(received_on=None).exclude(status='canceled')
            discrepancies = filled_requests.exclude(amount_requested=F('amount_received'))
            discrepancies_list = discrepancies.values_list("product", flat=True) #not distinct!
            orders_list = filled_requests.values_list("product", flat=True)

            # We could save a lot of time here if the primary key for Product were its sms_code.
            # Unfortunately, it isn't, so we have to remap keys->codes.
            _p = {}
            for p in Product.objects.filter(pk__in=orders_list.distinct()): _p[p.pk] = p.sms_code
            def _map_codes(dict):
                nd = {}
                for d in dict:
                    nd[_p[d]] = dict[d]
                return nd

            # Discrepancies are defined as a difference of 20% or more in an order.

            self.discrepancies_p = {}
            self.discrepancies_tot_p = {}
            self.discrepancies_pct_p = {}
            self.discrepancies_avg_p = {}
            self.filled_orders_p = {}
            for product in orders_list.distinct():
                self.discrepancies_p[product] = len(filter(lambda r: (r.amount_received >= (1.2 * r.amount_requested) or
                                                                         r.amount_received <= (.8 * r.amount_requested)), [x for x in discrepancies if x.product.pk == product]))

                z = [r.amount_requested - r.amount_received for r in discrepancies.filter(product__pk=product)]
                self.discrepancies_tot_p[product] = sum(z)
                if self.discrepancies_p[product]: self.discrepancies_avg_p[product] = self.discrepancies_tot_p[product] / self.discrepancies_p[product]
                self.filled_orders_p[product] = len([x for x in orders_list if x == product])
                self.discrepancies_pct_p[product] = calc_percentage(self.discrepancies_p[product], self.filled_orders_p[product])


            self.discrepancies_p = _map_codes(self.discrepancies_p)
            self.discrepancies_tot_p = _map_codes(self.discrepancies_tot_p)
            self.discrepancies_pct_p = _map_codes(self.discrepancies_pct_p)
            self.discrepancies_avg_p = _map_codes(self.discrepancies_avg_p)
            self.filled_orders_p = _map_codes(self.filled_orders_p)


            self.avg_req_time = None
            self.req_times = []
            if filled_requests:
                secs = [_seconds(f.received_on - f.requested_on) for f in filled_requests]
                self.avg_req_time = timedelta(seconds=(sum(secs) / len(secs)))
                self.req_times = secs

        # fully reporting / non reporting
        full = []
        partial = []
        unconfigured = []
        stockouts = []
        no_stockouts = []
        no_stockouts_p = {}
        stockouts_p = {}
        totals_p = {}
        stockouts_duration_p = {}
        stockouts_avg_duration_p = {}
        for sp in reported.all():
            
            found_reports = reports_in_range.filter(supply_point=sp)
            found_products = set(found_reports.values_list("product", flat=True).distinct())
            needed_products = set([c.pk for c in sp.commodities_stocked()])
            if needed_products:
                if needed_products - found_products:
                    partial.append(sp)
                else:
                    full.append(sp)
            else:
                unconfigured.append(sp)
            if MNE:
                prods = Product.objects.filter(pk__in=list(found_products))
                for p in prods:
                    if not p.code in stockouts_p: stockouts_p[p.sms_code] = 0
                    if not p.code in no_stockouts_p: no_stockouts_p[p.sms_code] = 0
                    if not p.code in totals_p: totals_p[p.sms_code] = 0
                    if found_reports.filter(product=p, quantity=0):
                        stockouts_p[p.sms_code] += 1
                    else:
                        no_stockouts_p[p.sms_code] += 1
                    totals_p[p.sms_code] += 1

                if found_reports.filter(quantity=0):
                    stockouts.append(sp.pk)
#                    prods = found_reports.values_list("product", flat=True).distinct()
                    for p in prods:
                        duration = 0
                        count = 0
                        last_stockout = None
                        ordered_reports = found_reports.filter(product=p).order_by("report_date")
                        if ordered_reports.count():
                            # Check if a stockout carried over from last period.
                            last_prev_report = ProductReport.objects.filter(product=p,
                                                                            supply_point=sp,
                                                                            report_date__lt=ordered_reports[0].report_date).order_by("-report_date")
                            if last_prev_report.count() and last_prev_report[0].quantity == 0:
                                last_stockout = datespan.startdate

                            # Check if a stockout carries over into next period.


                        for r in ordered_reports:
                            if last_stockout and r.quantity > 0: # Stockout followed by receipt.
                                duration += _seconds(r.report_date - last_stockout)
                                last_stockout = None
                                count += 1
                            elif not last_stockout and r.quantity == 0: # Beginning of a stockout period.
                                last_stockout = r.report_date
                            else: # In the middle of a stock period, or the middle of a stockout period; either way, we don't care.
                                pass

                        if last_stockout:
                            first_next_report = ProductReport.objects.filter(product=p,
                                supply_point=sp,
                                quantity__gt=0,
                                report_date__gt=ordered_reports.order_by("-report_date")[0].report_date)
                            if first_next_report.count() and first_next_report[0].quantity > 0:
                                duration += _seconds(datespan.enddate - last_stockout)

                        if p.sms_code in stockouts_duration_p and duration:
                            stockouts_duration_p[p.sms_code] += [duration]
                        elif duration:
                            stockouts_duration_p[p.sms_code] = [duration]
                else:
                    no_stockouts.append(sp.pk)
        if MNE:
            no_stockouts_pct_p = {}

            for key in no_stockouts_p:
                if totals_p[key] > 0:
                    no_stockouts_pct_p[key] = calc_percentage(no_stockouts_p[key], totals_p[key])

            for key in stockouts_duration_p:
                stockouts_avg_duration_p[key] = timedelta(seconds=sum(stockouts_duration_p[key]) / len(stockouts_duration_p[key]))

            self.stockouts = stockouts
            self.emergency = emergency_requesters
            self.stockouts_emergency = set(stockouts).intersection(set(emergency_requesters))
            self.stockouts_p = stockouts_p
            self.no_stockouts_pct_p = no_stockouts_pct_p
            self.no_stockouts_p = no_stockouts_p
            self.totals_p = totals_p
            self.stockouts_duration_p = stockouts_duration_p
            self.stockouts_avg_duration_p = stockouts_avg_duration_p
        # ro 10/14/11 - not sure why querysets are necessary. 
        # something changed in the djtables tables spec with the new ordering features?
        self.full = SupplyPoint.objects.filter(pk__in=[f.pk for f in full])
        self.partial = SupplyPoint.objects.filter(pk__in=[p.pk for p in partial])
        self.unconfigured = unconfigured
        
        self.non_reporting = non_reporting
        self.reported = reported
        self.reported_on_time = reported_on_time
        self.reported_late = reported_late
Beispiel #28
0
 def default_datespan(self):
     datespan = DateSpan.since(self.datespan_default_days, format="%Y-%m-%d", timezone=self.timezone)
     datespan.is_default = True
     return datespan
Beispiel #29
0
 def default_datespan(self):
     datespan = DateSpan.since(self.datespan_default_days, format="%Y-%m-%d", timezone=self.timezone)
     datespan.is_default = True
     return datespan
def order_fill_stats(locations, type=None, datespan=None):
    """
    With a list of locations - display reporting
    rates associated with those locations.
    This method only looks at closed orders
    """
    if locations:
        if datespan == None:
            # default to last 30 days
            datespan = DateSpan.since(30)
        base_points = SupplyPoint.objects.filter(location__in=locations,
                                                 active=True)
        if type is not None:
            base_points = base_points.filter(type__code=type)
        if base_points.count() > 0:
            base_reqs = StockRequest.objects.filter(
                supply_point__in=base_points,
                requested_on__gte=datespan.startdate,
                requested_on__lte=datespan.enddate)
            rec_reqs = base_reqs.filter(status=StockRequestStatus.RECEIVED)
            totals = base_reqs.values('product').annotate(total=Count('pk'))
            rec_totals = rec_reqs.values('product').annotate(total=Count('pk'))
            eo_totals = base_reqs.filter(
                is_emergency=True).values('product').annotate(
                    total=Count('pk'))
            stocked_out = rec_reqs.filter(
                amount_received=0).values('product').annotate(
                    total=Count('pk'))
            not_stocked_out = rec_reqs.filter(amount_received__gt=0).exclude(
                response_status=StockRequestStatus.STOCKED_OUT)
            under_supplied = not_stocked_out.filter(amount_requested__gt=F(
                'amount_received')).values('product').annotate(
                    total=Count('pk'))
            well_supplied = not_stocked_out.filter(amount_requested=F(
                'amount_received')).values('product').annotate(
                    total=Count('pk'))
            over_supplied = not_stocked_out.filter(amount_requested__lt=F(
                'amount_received')).values('product').annotate(
                    total=Count('pk'))
            main_data = {}
            for row in totals:
                main_data[row["product"]] = defaultdict(lambda x: 0)
                main_data[row["product"]]["product"] = Product.objects.get(
                    pk=row["product"])
                main_data[row["product"]]["total"] = row["total"]

            def _update_main_data(main, to_update, tag):
                for row in to_update:
                    main[row["product"]][tag] = row["total"]

            _update_main_data(main_data, rec_totals, "filled")
            _update_main_data(main_data, eo_totals, "emergency")
            _update_main_data(main_data, stocked_out, "stocked_out")
            _update_main_data(main_data, under_supplied, "under_supplied")
            _update_main_data(main_data, well_supplied, "well_supplied")
            _update_main_data(main_data, over_supplied, "over_supplied")
            return r_2_s_helper("logistics/partials/order_fill_stats.html", {
                "data": main_data,
                "datespan": datespan
            })

    return ""  # no data, no report
Beispiel #31
0
 def default_value(self):
     # default to a week's worth of data.
     return DateSpan.since(7)
Beispiel #32
0
    def __init__(self,
                 supply_points,
                 datespan=None,
                 include_late=False,
                 days_for_late=5,
                 MNE=False,
                 request=None):

        self.supply_points = supply_points = supply_points.filter(active=True)

        if not datespan:
            datespan = DateSpan.since(30)
        self.datespan = datespan

        self._request = request
        self.include_late = include_late
        self.days_for_late = days_for_late
        date_for_late = datespan.startdate + timedelta(days=days_for_late)

        reports_in_range = ProductReport.objects.filter\
            (report_type__code=Reports.SOH,
             report_date__gte=datespan.startdate,
             report_date__lte=datespan.enddate,
             supply_point__in=supply_points)

        reported_in_range = reports_in_range.values_list\
            ("supply_point", flat=True).distinct()

        reported_on_time_in_range = reports_in_range.filter\
            (report_date__lte=date_for_late).values_list\
            ("supply_point", flat=True).distinct()

        non_reporting = supply_points.exclude(pk__in=reported_in_range)
        reported = SupplyPoint.objects.filter(pk__in=reported_in_range)
        reported_late = reported.exclude(pk__in=reported_on_time_in_range)
        reported_on_time = SupplyPoint.objects.filter\
            (pk__in=reported_on_time_in_range)

        requests_in_range = StockRequest.objects.select_related().filter(\
            requested_on__gte=datespan.startdate,
            requested_on__lte=datespan.enddate,
            supply_point__in=supply_points
        )

        if MNE:
            emergency_requests = requests_in_range.filter(is_emergency=True)

            emergency_requesters = emergency_requests.values_list(
                "supply_point", flat=True).distinct()

            filled_requests = requests_in_range.exclude(
                received_on=None).exclude(status='canceled')
            discrepancies = filled_requests.exclude(
                amount_requested=F('amount_received'))
            discrepancies_list = discrepancies.values_list(
                "product", flat=True)  #not distinct!
            orders_list = filled_requests.values_list("product", flat=True)

            # We could save a lot of time here if the primary key for Product were its sms_code.
            # Unfortunately, it isn't, so we have to remap keys->codes.
            _p = {}
            for p in Product.objects.filter(pk__in=orders_list.distinct()):
                _p[p.pk] = p.sms_code

            def _map_codes(dict):
                nd = {}
                for d in dict:
                    nd[_p[d]] = dict[d]
                return nd

            # Discrepancies are defined as a difference of 20% or more in an order.

            self.discrepancies_p = {}
            self.discrepancies_tot_p = {}
            self.discrepancies_pct_p = {}
            self.discrepancies_avg_p = {}
            self.filled_orders_p = {}
            for product in orders_list.distinct():
                self.discrepancies_p[product] = len(
                    filter(
                        lambda r:
                        (r.amount_received >=
                         (1.2 * r.amount_requested) or r.amount_received <=
                         (.8 * r.amount_requested)),
                        [x for x in discrepancies if x.product.pk == product]))

                z = [r.amount_requested - r.amount_received for r in \
                     discrepancies.filter(product__pk=product)]
                self.discrepancies_tot_p[product] = sum(z)
                if self.discrepancies_p[product]:
                    self.discrepancies_avg_p[product] = \
                        self.discrepancies_tot_p[product] / self.discrepancies_p[product]

                self.filled_orders_p[product] = len(
                    [x for x in orders_list if x == product])
                self.discrepancies_pct_p[product] = calc_percentage\
                    (self.discrepancies_p[product], self.filled_orders_p[product])

            self.discrepancies_p = _map_codes(self.discrepancies_p)
            self.discrepancies_tot_p = _map_codes(self.discrepancies_tot_p)
            self.discrepancies_pct_p = _map_codes(self.discrepancies_pct_p)
            self.discrepancies_avg_p = _map_codes(self.discrepancies_avg_p)
            self.filled_orders_p = _map_codes(self.filled_orders_p)

            self.avg_req_time = None
            self.req_times = []
            if filled_requests:
                secs = [
                    _seconds(f.received_on - f.requested_on)
                    for f in filled_requests
                ]
                self.avg_req_time = timedelta(seconds=(sum(secs) / len(secs)))
                self.req_times = secs

        # fully reporting / non reporting
        full = []
        partial = []
        unconfigured = []
        stockouts = []
        no_stockouts = []
        no_stockouts_p = {}
        stockouts_p = {}
        totals_p = {}
        stockouts_duration_p = {}
        stockouts_avg_duration_p = {}
        for sp in reported.all():

            found_reports = reports_in_range.filter(supply_point=sp)
            found_products = set(
                found_reports.values_list("product", flat=True).distinct())
            needed_products = set([c.pk for c in sp.commodities_stocked()])
            if needed_products:
                if needed_products - found_products:
                    partial.append(sp)
                else:
                    full.append(sp)
            else:
                unconfigured.append(sp)
            if MNE:
                prods = Product.objects.filter(pk__in=list(found_products))
                for p in prods:
                    if not p.code in stockouts_p: stockouts_p[p.sms_code] = 0
                    if not p.code in no_stockouts_p:
                        no_stockouts_p[p.sms_code] = 0
                    if not p.code in totals_p: totals_p[p.sms_code] = 0
                    if found_reports.filter(product=p, quantity=0):
                        stockouts_p[p.sms_code] += 1
                    else:
                        no_stockouts_p[p.sms_code] += 1
                    totals_p[p.sms_code] += 1

                if found_reports.filter(quantity=0):
                    stockouts.append(sp.pk)
                    #                    prods = found_reports.values_list("product", flat=True).distinct()
                    for p in prods:
                        duration = 0
                        count = 0
                        last_stockout = None
                        ordered_reports = found_reports.filter(
                            product=p).order_by("report_date")
                        if ordered_reports.count():
                            # Check if a stockout carried over from last period.
                            last_prev_report = ProductReport.objects.filter(
                                product=p,
                                supply_point=sp,
                                report_date__lt=ordered_reports[0].report_date
                            ).order_by("-report_date")
                            if last_prev_report.count(
                            ) and last_prev_report[0].quantity == 0:
                                last_stockout = datespan.startdate

                            # Check if a stockout carries over into next period.

                        for r in ordered_reports:
                            if last_stockout and r.quantity > 0:  # Stockout followed by receipt.
                                duration += _seconds(r.report_date -
                                                     last_stockout)
                                last_stockout = None
                                count += 1
                            elif not last_stockout and r.quantity == 0:  # Beginning of a stockout period.
                                last_stockout = r.report_date
                            else:  # In the middle of a stock period, or the middle of a stockout period; either way, we don't care.
                                pass

                        if last_stockout:
                            first_next_report = ProductReport.objects.filter(
                                product=p,
                                supply_point=sp,
                                quantity__gt=0,
                                report_date__gt=ordered_reports.order_by(
                                    "-report_date")[0].report_date)
                            if first_next_report.count(
                            ) and first_next_report[0].quantity > 0:
                                duration += _seconds(datespan.enddate -
                                                     last_stockout)

                        if p.sms_code in stockouts_duration_p and duration:
                            stockouts_duration_p[p.sms_code] += [duration]
                        elif duration:
                            stockouts_duration_p[p.sms_code] = [duration]
                else:
                    no_stockouts.append(sp.pk)
        if MNE:
            no_stockouts_pct_p = {}

            for key in no_stockouts_p:
                if totals_p[key] > 0:
                    no_stockouts_pct_p[key] = calc_percentage(
                        no_stockouts_p[key], totals_p[key])

            for key in stockouts_duration_p:
                stockouts_avg_duration_p[key] = timedelta(
                    seconds=sum(stockouts_duration_p[key]) /
                    len(stockouts_duration_p[key]))

            self.stockouts = stockouts
            self.emergency = emergency_requesters
            self.stockouts_emergency = set(stockouts).intersection(
                set(emergency_requesters))
            self.stockouts_p = stockouts_p
            self.no_stockouts_pct_p = no_stockouts_pct_p
            self.no_stockouts_p = no_stockouts_p
            self.totals_p = totals_p
            self.stockouts_duration_p = stockouts_duration_p
            self.stockouts_avg_duration_p = stockouts_avg_duration_p
        # ro 10/14/11 - not sure why querysets are necessary.
        # something changed in the djtables tables spec with the new ordering features?
        self.full = SupplyPoint.objects.filter(pk__in=[f.pk for f in full])
        self.partial = SupplyPoint.objects.filter(
            pk__in=[p.pk for p in partial])
        self.unconfigured = unconfigured

        self.non_reporting = non_reporting
        self.reported = reported
        self.reported_on_time = reported_on_time
        self.reported_late = reported_late
Beispiel #33
0
 def default_value(self):
     # default to a week's worth of data.
     return DateSpan.since(7)
Beispiel #34
0
 def datespan(self):
     datespan = DateSpan.since(self.default_days, format="%Y-%m-%d", timezone=self.timezone)
     if self.request.datespan.is_valid() and self.slug == 'datespan':
         datespan.startdate = self.request.datespan.startdate
         datespan.enddate = self.request.datespan.enddate
     return datespan