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))
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
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 ')
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
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})
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)
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
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
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
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
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
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 })
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 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
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))
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))
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
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
def default_datespan(self): datespan = DateSpan.since(self.datespan_default_days, timezone=self.timezone, inclusive=self.inclusive) datespan.is_default = True return datespan
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
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
def default_value(self): # default to a week's worth of data. return DateSpan.since(7)
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