def handle(self, *args, **options):

        self.stdout.write("Processing products...\n")
        products = set()
        total = StockState.objects.count()
        failed = []
        for i, ss in enumerate(StockState.objects.all()):
            if i % 500 == 0:
                self.stdout.write('done {}/{}'.format(i, total))
            if ss.product_id not in products:
                try:
                    product = Product.get(ss.product_id)
                    assert product.doc_type == 'Product'
                    products.add(ss.product_id)
                except (ResourceNotFound, AssertionError):
                    try:
                        case = CommCareCase.get(ss.case_id)
                    except ResourceNotFound:
                        case = CommCareCase()
                    failed.append((ss, case))
        if failed:
            for ss, case in failed:
                self.stdout.write('No product with ID "{}" found! case ID: {}, domain {}'.format(
                    ss.product_id, ss.case_id, case.domain
                ))
            self.stderr.write('{}/{} stock states FAILED check'.format(len(failed), total))
예제 #2
0
def make_supply_point_product(supply_point_case, product_uuid, owner_id=None):
    domain = supply_point_case.domain
    id = uuid.uuid4().hex
    user_id = get_commtrack_user_id(domain)
    owner_id = owner_id or get_owner_id(supply_point_case) or user_id
    username = const.COMMTRACK_USERNAME
    product_name = Product.get(product_uuid).name
    caseblock = CaseBlock(case_id=id,
                          create=True,
                          version=V2,
                          case_name=product_name,
                          user_id=user_id,
                          owner_id=owner_id,
                          case_type=const.SUPPLY_POINT_PRODUCT_CASE_TYPE,
                          update={"product": product_uuid},
                          index={
                              const.PARENT_CASE_REF:
                              (const.SUPPLY_POINT_CASE_TYPE,
                               supply_point_case._id),
                          })
    casexml = ElementTree.tostring(caseblock.as_xml())
    submit_case_blocks(casexml,
                       domain,
                       username,
                       user_id,
                       xmlns=const.COMMTRACK_SUPPLY_POINT_PRODUCT_XMLNS)
    return SupplyPointProductCase.get(id)
예제 #3
0
def product_edit(request, domain, prod_id=None):
    if prod_id:
        try:
            product = Product.get(prod_id)
        except ResourceNotFound:
            raise Http404
    else:
        product = Product(domain=domain)

    if request.method == "POST":
        form = ProductForm(product, request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Product saved!')
            return HttpResponseRedirect(
                reverse('commtrack_product_list', kwargs={'domain': domain}))
    else:
        form = ProductForm(product)

    context = {
        'domain': domain,
        'product': product,
        'form': form,
    }

    template = "commtrack/manage/product.html"
    return render(request, template, context)
예제 #4
0
    def get_prod_data(self):
        sp_ids = get_relevant_supply_point_ids(self.domain, self.active_location)

        stock_states = StockState.objects.filter(case_id__in=sp_ids).order_by('product_id')

        product_grouping = {}
        for state in stock_states:
            status = state.stock_category()
            if state.product_id in product_grouping:
                product_grouping[state.product_id][status] += 1
                product_grouping[state.product_id]['facility_count'] += 1

            else:
                product_grouping[state.product_id] = {
                    'obj': Product.get(state.product_id),
                    'stockout': 0,
                    'understock': 0,
                    'overstock': 0,
                    'adequate': 0,
                    'nodata': 0,
                    'facility_count': 1
                }
                product_grouping[state.product_id][status] = 1

        for product in product_grouping.values():
            yield [
                product['obj'].name,
                product['facility_count'],
                100.0 * product['stockout'] / product['facility_count'],
                100.0 * product['understock'] / product['facility_count'],
                100.0 * product['adequate'] / product['facility_count'],
                100.0 * product['overstock'] / product['facility_count'],
                100.0 * product['nodata'] / product['facility_count'],
            ]
예제 #5
0
    def get_data(self):
        startkey = [self.domain, self.active_location._id if self.active_location else None]
        product_cases = SPPCase.view('commtrack/product_cases',
                                     startkey=startkey,
                                     endkey=startkey + [{}],
                                     include_docs=True)
        if self.program_id:
            product_cases = filter(lambda c: Product.get(c.product).program_id == self.program_id, product_cases)
        def latest_case(cases):
            # getting last report date should probably be moved to a util function in a case wrapper class
            return max(cases, key=lambda c: getattr(c, 'last_reported', datetime(2000, 1, 1)).date())
        cases_by_site = map_reduce(lambda c: [(tuple(c.location_),)],
                                   lambda v: reporting_status(latest_case(v), self.start_date, self.end_date),
                                   data=product_cases, include_docs=True)

        # TODO if aggregating, won't want to fetch all these locs (will only want to fetch aggregation sites)
        locs = dict((loc._id, loc) for loc in Location.view(
                '_all_docs',
                keys=[path[-1] for path in cases_by_site.keys()],
                include_docs=True))

        for path, status in cases_by_site.iteritems():
            loc = locs[path[-1]]

            yield {
                'loc_id': loc._id,
                'loc_path': loc.path,
                'name': loc.name,
                'type': loc.location_type,
                'reporting_status': status,
                'geo': loc._geopoint,
            }
예제 #6
0
def make_supply_point_product(supply_point_case, product_uuid, owner_id=None):
    domain = supply_point_case.domain
    id = uuid.uuid4().hex
    user_id = const.get_commtrack_user_id(domain)
    owner_id = owner_id or get_owner_id(supply_point_case) or user_id
    username = const.COMMTRACK_USERNAME
    product_name = Product.get(product_uuid).name
    caseblock = CaseBlock(
        case_id=id,
        create=True,
        version=V2,
        case_name=product_name,
        user_id=user_id,
        owner_id=owner_id,
        case_type=const.SUPPLY_POINT_PRODUCT_CASE_TYPE,
        update={
            "product": product_uuid
        },
        index={
            const.PARENT_CASE_REF: (const.SUPPLY_POINT_CASE_TYPE,
                                    supply_point_case._id),
        }
    )
    casexml = ElementTree.tostring(caseblock.as_xml())
    submit_case_blocks(casexml, domain, username, user_id,
                       xmlns=const.COMMTRACK_SUPPLY_POINT_PRODUCT_XMLNS)
    sppc = SupplyPointProductCase.get(id)
    sppc.bind_to_location(supply_point_case.location)
    sppc.save()
    return sppc
예제 #7
0
파일: views.py 프로젝트: birdsarah/core-hq
def product_edit(request, domain, prod_id=None): 
    if prod_id:
        try:
            product = Product.get(prod_id)
        except ResourceNotFound:
            raise Http404
    else:
        product = Product(domain=domain)

    if request.method == "POST":
        form = ProductForm(product, request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Product saved!')
            return HttpResponseRedirect(reverse('commtrack_product_list', kwargs={'domain': domain}))
    else:
        form = ProductForm(product)

    context = {
        'domain': domain,
        'product': product,
        'form': form,
    }

    template="commtrack/manage/product.html"
    return render(request, template, context)
예제 #8
0
 def save(self):
     for field in self.fields:
         val = self.cleaned_data[field]
         product = Product.get(field.split("_")[1])
         assert product.domain == self.domain, "Product {} attempted to be updated in domain {}".format(
             product._id, self.domain
         )
         set_default_consumption_for_product(self.domain, product._id, val)
예제 #9
0
def delivery_update(requisition_cases, openlmis_endpoint):
    order_id = requisition_cases[0].get_case_property("order_id")
    products = []
    for rec in requisition_cases:
        product = Product.get(rec.product_id)
        products.append({'productCode': product.code, 'quantityReceived': rec.amount_received})
    delivery_data = {'podLineItems': products}
    return openlmis_endpoint.confirm_delivery(order_id, delivery_data)
예제 #10
0
 def expected_notification_message(self, req, amounts):
     summary = sorted(
         ['%s:%d' % (str(Product.get(p).code), amt) for p, amt in amounts])
     return const.notification_template(
         req.get_next_action().action).format(
             name='Unknown',  # TODO currently not storing requester
             summary=' '.join(summary),
             loc=self.sp.location.site_code,
             keyword=req.get_next_action().keyword)
예제 #11
0
    def aggregated_data(self, stock_states):
        product_aggregation = {}
        for state in stock_states:
            if state.product_id in product_aggregation:
                product = product_aggregation[state.product_id]
                product['current_stock'] = self.format_decimal(
                    product['current_stock'] + state.stock_on_hand
                )

                if product['total_consumption'] is None:
                    product['total_consumption'] = state.get_consumption()
                elif state.get_consumption() is not None:
                    product['total_consumption'] += state.get_consumption()

                product['count'] += 1

                if product['total_consumption'] is not None:
                    product['consumption'] = product['total_consumption'] / product['count']
                else:
                    product['consumption'] = None

                product['category'] = stock_category(
                    product['current_stock'],
                    product['consumption'],
                    Domain.get_by_name(self.domain)
                )
                product['months_remaining'] = months_of_stock_remaining(
                    product['current_stock'],
                    product['consumption']
                )
            else:
                product = Product.get(state.product_id)
                consumption = state.get_consumption()

                product_aggregation[state.product_id] = {
                    'product_id': product._id,
                    'location_id': None,
                    'product_name': product.name,
                    'location_lineage': None,
                    'resupply_quantity_needed': None,
                    'current_stock': self.format_decimal(state.stock_on_hand),
                    'total_consumption': consumption,
                    'count': 1,
                    'consumption': consumption,
                    'category': stock_category(
                        state.stock_on_hand,
                        consumption,
                        Domain.get_by_name(self.domain)
                    ),
                    'months_remaining': months_of_stock_remaining(
                        state.stock_on_hand,
                        consumption
                    )
                }

        return product_aggregation.values()
예제 #12
0
 def expected_notification_message(self, req, amounts):
     summary = sorted(
         ['%s:%d' % (str(Product.get(p).code), amt) for p, amt in amounts]
     )
     return const.notification_template(req.get_next_action().action).format(
         name='Unknown',  # TODO currently not storing requester
         summary=' '.join(summary),
         loc=self.sp.location.site_code,
         keyword=req.get_next_action().keyword
     )
예제 #13
0
 def save(self):
     for field in self.fields:
         val = self.cleaned_data[field]
         product = Product.get(field.split('_')[1])
         assert product.domain == self.domain, 'Product {} attempted to be updated in domain {}'.format(
             product._id, self.domain)
         set_default_consumption_for_product(
             self.domain,
             product._id,
             val,
         )
예제 #14
0
def approve_requisition(requisition_cases, openlmis_endpoint):
    products = []
    for rec in requisition_cases:
        product = Product.get(rec.product_id)
        products.append({"productCode": product.code, "quantityApproved": rec.amount_approved})

    approve_data = {
        "products": products
    }

    return openlmis_endpoint.approve_requisition(approve_data, requisition_cases[0].external_id)
예제 #15
0
    def get_data(self, slugs=None):
        startkey = [self.domain, self.active_location._id if self.active_location else None]
        if self.active_product:
            startkey.append(self.active_product['_id'])

        product_cases = SPPCase.view('commtrack/product_cases', startkey=startkey, endkey=startkey + [{}], include_docs=True)
        if self.program_id:
            product_cases = filter(lambda c: Product.get(c.product).program_id == self.program_id, product_cases)
        if self.config.get('aggregate'):
            return self.aggregate_cases(product_cases, slugs)
        else:
            return self.raw_cases(product_cases, slugs)
예제 #16
0
    def get_prod_data(self):
        sp_ids = get_relevant_supply_point_ids(self.domain, self.active_location)

        stock_states = StockState.include_archived.filter(
            case_id__in=sp_ids,
            last_modified_date__lte=self.datespan.enddate_utc,
            last_modified_date__gte=self.datespan.startdate_utc,
            section_id=STOCK_SECTION_TYPE
        )
        if not self.archived_products:
            stock_states = stock_states.exclude(
                sql_product__is_archived=True
            )

        stock_states = stock_states.order_by('product_id')

        if self.program_id:
            stock_states = stock_states.filter(
                product_id__in=product_ids_filtered_by_program(
                    self.domain,
                    self.program_id
                )
            )

        product_grouping = {}
        for state in stock_states:
            status = state.stock_category
            if state.product_id in product_grouping:
                product_grouping[state.product_id][status] += 1
                product_grouping[state.product_id]['facility_count'] += 1

            else:
                product_grouping[state.product_id] = {
                    'obj': Product.get(state.product_id),
                    'stockout': 0,
                    'understock': 0,
                    'overstock': 0,
                    'adequate': 0,
                    'nodata': 0,
                    'facility_count': 1
                }
                product_grouping[state.product_id][status] = 1

        for product in product_grouping.values():
            yield [
                product['obj'].name,
                product['facility_count'],
                100.0 * product['stockout'] / product['facility_count'],
                100.0 * product['understock'] / product['facility_count'],
                100.0 * product['adequate'] / product['facility_count'],
                100.0 * product['overstock'] / product['facility_count'],
                100.0 * product['nodata'] / product['facility_count'],
            ]
예제 #17
0
def unarchive_product(request, domain, prod_id, archive=True):
    """
    Unarchive product
    """
    product = Product.get(prod_id)
    product.unarchive()
    return HttpResponse(json.dumps(dict(
        success=True,
        message=_("Product '{product_name}' has successfully been {action}.").format(
            product_name=product.name,
            action="unarchived",
        )
    )))
예제 #18
0
 def leaf_node_data(self, stock_states):
     for state in stock_states:
         product = Product.get(state.product_id)
         yield {
             'category': state.stock_category,
             'product_id': product._id,
             'consumption': state.daily_consumption,
             'months_remaining': state.months_remaining,
             'location_id': state.case_id,
             'product_name': product.name,
             'current_stock': state.stock_on_hand,
             'location_lineage': None
         }
예제 #19
0
 def aggregated_data(self, stock_states):
     for state in stock_states:
         product = Product.get(state['product_id'])
         yield {
             'category': stock_category(state['total_stock'], state['avg_consumption']),
             'product_id': product._id,
             'consumption': state['avg_consumption'],
             'months_remaining': months_of_stock_remaining(state['total_stock'], state['avg_consumption']),
             'location_id': None,
             'product_name': product.name,
             'current_stock': state['total_stock'],
             'location_lineage': None
         }
예제 #20
0
def unarchive_product(request, domain, prod_id, archive=True):
    """
    Unarchive product
    """
    product = Product.get(prod_id)
    product.unarchive()
    return json_response({
        'success': True,
        'message': _("Product '{product_name}' has successfully been {action}.").format(
            product_name=product.name,
            action="unarchived",
        )
    })
예제 #21
0
 def leaf_node_data(self, stock_states):
     for state in stock_states:
         product = Product.get(state.product_id)
         yield {
             'category': state.stock_category,
             'product_id': product._id,
             'consumption': state.get_monthly_consumption(),
             'months_remaining': state.months_remaining,
             'location_id': SupplyPointCase.get(state.case_id).location_id,
             'product_name': product.name,
             'current_stock': self.format_decimal(state.stock_on_hand),
             'location_lineage': None,
             'resupply_quantity_needed': state.resupply_quantity_needed
         }
예제 #22
0
    def test_delete(self):
        # assign some product to the new program
        self.products[0].program_id = self.new_program._id
        self.products[0].save()

        # make sure start state is okay
        self.assertEqual(
            2,
            len(Program.by_domain(self.domain.name))
        )
        self.assertEqual(
            2,
            Product.by_program_id(self.domain.name, self.default_program._id).count()
        )
        self.assertEqual(
            1,
            Product.by_program_id(self.domain.name, self.new_program._id).count()
        )
        self.assertEqual(
            self.new_program._id,
            self.products[0].program_id
        )
        self.assertEqual(
            self.new_program._id,
            SQLProduct.objects.get(product_id=self.products[0]._id).program_id
        )

        # stash the id before we delete
        new_program_id = self.new_program._id
        self.new_program.delete()

        with self.assertRaises(ResourceNotFound):
            Program.get(new_program_id)

        self.assertEqual(
            1,
            len(Program.by_domain(self.domain.name))
        )
        self.assertEqual(
            3,
            Product.by_program_id(self.domain.name, self.default_program._id).count()
        )
        self.assertEqual(
            self.default_program._id,
            Product.get(self.products[0]._id).program_id
        )
        self.assertEqual(
            self.default_program._id,
            SQLProduct.objects.get(product_id=self.products[0]._id).program_id
        )
예제 #23
0
 def leaf_node_data(self, stock_states):
     for state in stock_states:
         product = Product.get(state.product_id)
         yield {
             'category': state.stock_category,
             'product_id': product._id,
             'consumption': state.get_consumption() * Decimal(DAYS_IN_MONTH) if state.get_consumption() is not None else None,
             'months_remaining': state.months_remaining,
             'location_id': SupplyPointCase.get(state.case_id).location_id,
             'product_name': product.name,
             'current_stock': self.format_decimal(state.stock_on_hand),
             'location_lineage': None,
             'resupply_quantity_needed': state.resupply_quantity_needed
         }
예제 #24
0
 def leaf_node_data(self, stock_states):
     for state in stock_states:
         product = Product.get(state.product_id)
         yield {
             'category': state.stock_category,
             'product_id': product._id,
             'consumption': state.daily_consumption * 30 if state.daily_consumption else None,
             'months_remaining': state.months_remaining,
             'location_id': SupplyPointCase.get(state.case_id).location_id,
             'product_name': product.name,
             'current_stock': state.stock_on_hand,
             'location_lineage': None,
             'resupply_quantity_needed': state.resupply_quantity_needed
         }
예제 #25
0
    def get_prod_data(self):
        sp_ids = get_relevant_supply_point_ids(self.domain,
                                               self.active_location)

        stock_states = StockState.include_archived.filter(
            case_id__in=sp_ids,
            last_modified_date__lte=self.datespan.enddate_utc,
            last_modified_date__gte=self.datespan.startdate_utc,
            section_id=STOCK_SECTION_TYPE)
        if not self.archived_products:
            stock_states = stock_states.exclude(sql_product__is_archived=True)

        stock_states = stock_states.order_by('product_id')

        if self.program_id:
            stock_states = stock_states.filter(
                product_id__in=product_ids_filtered_by_program(
                    self.domain, self.program_id))

        product_grouping = {}
        for state in stock_states:
            status = state.stock_category
            if state.product_id in product_grouping:
                product_grouping[state.product_id][status] += 1
                product_grouping[state.product_id]['facility_count'] += 1

            else:
                product_grouping[state.product_id] = {
                    'obj': Product.get(state.product_id),
                    'stockout': 0,
                    'understock': 0,
                    'overstock': 0,
                    'adequate': 0,
                    'nodata': 0,
                    'facility_count': 1
                }
                product_grouping[state.product_id][status] = 1

        for product in product_grouping.values():
            yield [
                product['obj'].name,
                product['facility_count'],
                100.0 * product['stockout'] / product['facility_count'],
                100.0 * product['understock'] / product['facility_count'],
                100.0 * product['adequate'] / product['facility_count'],
                100.0 * product['overstock'] / product['facility_count'],
                100.0 * product['nodata'] / product['facility_count'],
            ]
예제 #26
0
def approve_requisition(requisition_cases, openlmis_endpoint):
    groups = defaultdict( list )
    for case in requisition_cases:
        groups[case.external_id].append(case)

    for group in groups.keys():
        if(group):
            cases = groups.get(group)
            products = []
            approver = CommCareUser.get(cases[0].user_id)
            for rec in cases:
                product = Product.get(rec.product_id)
                products.append({"productCode": product.code, "quantityApproved": rec.amount_approved})

            approve_data = {
                "approverName": approver.human_friendly_name,
                "products": products
            }
            openlmis_endpoint.approve_requisition(approve_data, group)
예제 #27
0
    def get_prod_data(self):
        startkey = [self.domain, self.active_location._id if self.active_location else None]
        product_cases = SPPCase.view('commtrack/product_cases', startkey=startkey, endkey=startkey + [{}], include_docs=True)
        if self.program_id:
            product_cases = filter(lambda c: Product.get(c.product).program_id == self.program_id, product_cases)
        cases_by_product = map_reduce(lambda c: [(c.product,)], data=product_cases, include_docs=True)
        products = Product.view('_all_docs', keys=cases_by_product.keys(), include_docs=True)

        def status(case):
            return case.current_stock_category if is_timely(case, 1000) else 'nonreporting'

        status_by_product = dict((p, map_reduce(lambda c: [(status(c),)], len, data=cases)) for p, cases in cases_by_product.iteritems())

        cols = ['stockout', 'understock', 'adequate', 'overstock', 'nodata'] #'nonreporting', 'nodata']
        for p in sorted(products, key=lambda p: p.name):
            cases = cases_by_product.get(p._id, [])
            results = status_by_product.get(p._id, {})
            def val(key):
                return results.get(key, 0) / float(len(cases))
            yield [p.name, len(cases)] + [100. * val(key) for key in cols]
    def handle(self, *args, **options):

        self.stdout.write("Processing products...\n")
        products = set()
        total = StockState.objects.count()
        failed = []
        for i, ss in enumerate(StockState.objects.all()):
            if i % 500 == 0:
                self.stdout.write('done {}/{}'.format(i, total))
            if ss.product_id not in products:
                try:
                    product = Product.get(ss.product_id)
                    assert product.doc_type == 'Product'
                    products.add(ss.product_id)
                except (ResourceNotFound, AssertionError):
                    try:
                        case = CommCareCase.get(ss.case_id)
                    except ResourceNotFound:
                        case = CommCareCase()
                    failed.append((ss, case))
예제 #29
0
 def product(self):
     try:
         return Product.get(self.product_id)
     except ResourceNotFound:
         raise Http404()
예제 #30
0
 def active_product(self):
     prod_id = self.config.get('product_id')
     if prod_id:
         return Product.get(prod_id)
예제 #31
0
 def product_name(product_id):
     return Product.get(product_id).name
예제 #32
0
 def active_product(self):
     prod_id = self.config.get('product_id')
     if prod_id:
         return Product.get(prod_id)
예제 #33
0
 def product_name(product_id):
     return Product.get(product_id).name
예제 #34
0
 def product(self):
     try:
         return Product.get(self.product_id)
     except ResourceNotFound:
         raise Http404()
예제 #35
0
class StockStatusDataSource(ReportDataSource, CommtrackDataSourceMixin):
    """
    Config:
        domain: The domain to report on.
        location_id: ID of location to get data for. Omit for all locations.
        product_id: ID of product to get data for. Omit for all products.
        aggregate: True to aggregate the indicators by product for the current location.

    Data Slugs:
        product_name: Name of the product
        product_id: ID of the product
        location_id: The ID of the current location.
        location_lineage: The lineage of the current location.
        current_stock: The current stock level
        consumption: The current monthly consumption rate
        months_remaining: The number of months remaining until stock out
        category: The status category. See casexml.apps.stock.models.StockState.stock_category
        resupply_quantity_needed: Max amount - current amount

    """
    slug = 'agg_stock_status'

    SLUG_PRODUCT_NAME = 'product_name'
    SLUG_PRODUCT_ID = 'product_id'
    SLUG_MONTHS_REMAINING = 'months_remaining'
    SLUG_CONSUMPTION = 'consumption'
    SLUG_CURRENT_STOCK = 'current_stock'
    SLUG_LOCATION_ID = 'location_id'
    SLUG_LOCATION_LINEAGE = 'location_lineage'
    SLUG_STOCKOUT_SINCE = 'stockout_since'
    SLUG_STOCKOUT_DURATION = 'stockout_duration'
    SLUG_LAST_REPORTED = 'last_reported'
    SLUG_CATEGORY = 'category'
    SLUG_RESUPPLY_QUANTITY_NEEDED = 'resupply_quantity_needed'

    _slug_attrib_map = {
        SLUG_PRODUCT_NAME: lambda s: Product.get(s.product_id).name,
        SLUG_PRODUCT_ID: lambda s: s.product_id,
        SLUG_LOCATION_ID: lambda s: SupplyPointCase.get(s.case_id).location_[-1],
        # SLUG_LOCATION_LINEAGE: lambda p: list(reversed(p.location_[:-1])),
        SLUG_CURRENT_STOCK: 'stock_on_hand',
        SLUG_CONSUMPTION: lambda s: s.get_consumption() * 30 if s.get_consumption() is not None else None,
        SLUG_MONTHS_REMAINING: 'months_remaining',
        SLUG_CATEGORY: 'stock_category',
        # SLUG_STOCKOUT_SINCE: 'stocked_out_since',
        # SLUG_STOCKOUT_DURATION: 'stockout_duration_in_months',
        SLUG_LAST_REPORTED: 'last_modified_date',
        SLUG_RESUPPLY_QUANTITY_NEEDED: 'resupply_quantity_needed',
    }

    def slugs(self):
        return self._slug_attrib_map.keys()

    def filter_by_program(self, stock_states):
        return stock_states.filter(
            product_id__in=product_ids_filtered_by_program(
                self.domain,
                self.program_id
            )
        )

    def get_data(self, slugs=None):
        sp_ids = get_relevant_supply_point_ids(self.domain, self.active_location)

        if len(sp_ids) == 1:
            stock_states = StockState.objects.filter(
                case_id=sp_ids[0],
                section_id=STOCK_SECTION_TYPE,
                last_modified_date__lte=self.end_date,
                last_modified_date__gte=self.start_date,
            )

            if self.program_id:
                stock_states = self.filter_by_program(stock_states)

            return self.leaf_node_data(stock_states)
        else:
            stock_states = StockState.objects.filter(
                case_id__in=sp_ids,
                section_id=STOCK_SECTION_TYPE,
                last_modified_date__lte=self.end_date,
                last_modified_date__gte=self.start_date,
            )

            if self.program_id:
                stock_states = self.filter_by_program(stock_states)

            if self.config.get('aggregate'):
                return self.aggregated_data(stock_states)
            else:
                return self.raw_product_states(stock_states, slugs)

    def format_decimal(self, d):
        # https://docs.python.org/2/library/decimal.html#decimal-faq
        if d is not None:
            return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
        else:
            return None

    def leaf_node_data(self, stock_states):
        for state in stock_states:
            product = Product.get(state.product_id)
            yield {
                'category': state.stock_category,
                'product_id': product._id,
                'consumption': state.get_consumption() * 30 if state.get_consumption() is not None else None,
                'months_remaining': state.months_remaining,
                'location_id': SupplyPointCase.get(state.case_id).location_id,
                'product_name': product.name,
                'current_stock': self.format_decimal(state.stock_on_hand),
                'location_lineage': None,
                'resupply_quantity_needed': state.resupply_quantity_needed
            }

    def aggregated_data(self, stock_states):
        product_aggregation = {}
        for state in stock_states:
            if state.product_id in product_aggregation:
                product = product_aggregation[state.product_id]
                product['current_stock'] = self.format_decimal(
                    product['current_stock'] + state.stock_on_hand
                )

                if product['total_consumption'] is None:
                    product['total_consumption'] = state.get_consumption()
                elif state.get_consumption() is not None:
                    product['total_consumption'] += state.get_consumption()

                product['count'] += 1

                if product['total_consumption'] is not None:
                    product['consumption'] = product['total_consumption'] / product['count']
                else:
                    product['consumption'] = None

                product['category'] = stock_category(
                    product['current_stock'],
                    product['consumption'],
                    Domain.get_by_name(self.domain)
                )
                product['months_remaining'] = months_of_stock_remaining(
                    product['current_stock'],
                    product['consumption']
                )
            else:
                product = Product.get(state.product_id)
                consumption = state.get_consumption()

                product_aggregation[state.product_id] = {
                    'product_id': product._id,
                    'location_id': None,
                    'product_name': product.name,
                    'location_lineage': None,
                    'resupply_quantity_needed': None,
                    'current_stock': self.format_decimal(state.stock_on_hand),
                    'total_consumption': consumption,
                    'count': 1,
                    'consumption': consumption,
                    'category': stock_category(
                        state.stock_on_hand,
                        consumption,
                        Domain.get_by_name(self.domain)
                    ),
                    'months_remaining': months_of_stock_remaining(
                        state.stock_on_hand,
                        consumption
                    )
                }

        return product_aggregation.values()

    def raw_product_states(self, stock_states, slugs):
        def _slug_attrib(slug, attrib, product, output):
            if not slugs or slug in slugs:
                if callable(attrib):
                    output[slug] = attrib(product)
                else:
                    output[slug] = getattr(product, attrib, '')

        for state in stock_states:
            out = {}
            for slug, attrib in self._slug_attrib_map.items():
                _slug_attrib(slug, attrib, state, out)

            yield out