Exemple #1
0
    def rows(self):
        rows = []
        unique_products = self.unique_products(
            get_supply_points(self.config['domain'], self.config['location_id']), all=(not self.config['export'])
        )
        if self.config['location_id']:
            for case_id, products in self.config['months_of_stock'].iteritems():

                sp = SQLLocation.objects.get(supply_point_id=case_id)

                url = make_url(
                    StockStatus,
                    self.config['domain'],
                    '?location_id=%s&filter_by_program=%s&startdate=%s&enddate=%s&report_type=%s',
                    (sp.location_id, self.config['program'] or ALL_OPTION, self.config['startdate'].date(),
                    self.config['enddate'].date(), self.config['report_type'])
                )

                row = [
                    link_format(sp.name, url) if not self.config.get('is_rendered_as_email', False) else sp.name
                ]

                for p in unique_products:
                    product_data = products.get(p.product_id)
                    if product_data:
                        value = '%.1f' % product_data
                    else:
                        value = '-'
                    row.append(value)
                rows.append(row)
        return rows
Exemple #2
0
    def post(self, request, *args, **kwargs):
        InputStockFormSet = formset_factory(InputStockForm)
        formset = InputStockFormSet(request.POST)
        if formset.is_valid():
            try:
                sql_location = SQLLocation.objects.get(site_code=kwargs['site_code'], domain=self.domain)
            except SQLLocation.DoesNotExist:
                raise Http404()
            text = ''
            for form in formset:
                product = Product.get(docid=form.cleaned_data['product_id'])
                if form.cleaned_data['stock_on_hand'] is not None:
                    text += '{} {}.{} '.format(
                        product.code, form.cleaned_data['stock_on_hand'], form.cleaned_data['receipts'] or 0
                    )

                amount = form.cleaned_data['default_consumption']
                if amount is not None:
                    set_default_consumption_for_supply_point(
                        self.domain, product.get_id, sql_location.supply_point_id, amount
                    )
            if text:
                WebSubmissionHandler(self.request.couch_user, self.domain, Msg(text), sql_location).handle()
            url = make_url(
                StockStatus,
                self.domain,
                '?location_id=%s&filter_by_program=%s&startdate='
                '&enddate=&report_type=&filter_by_product=%s',
                (sql_location.location_id, ALL_OPTION, ALL_OPTION)
            )
            return HttpResponseRedirect(url)
        context = self.get_context_data(**kwargs)
        context['formset'] = formset
        return self.render_to_response(context)
Exemple #3
0
    def post(self, request, *args, **kwargs):
        InputStockFormSet = formset_factory(InputStockForm)
        formset = InputStockFormSet(request.POST)
        if formset.is_valid():
            try:
                sql_location = SQLLocation.objects.get(site_code=kwargs['site_code'], domain=self.domain)
            except SQLLocation.DoesNotExist:
                raise Http404()
            text = ''
            for form in formset:
                product = Product.get(docid=form.cleaned_data['product_id'])
                if form.cleaned_data['stock_on_hand'] is not None:
                    text += '{} {}.{} '.format(
                        product.code, form.cleaned_data['stock_on_hand'], form.cleaned_data['receipts'] or 0
                    )

                amount = form.cleaned_data['default_consumption']
                if amount is not None:
                    set_default_consumption_for_supply_point(
                        self.domain, product.get_id, sql_location.supply_point_id, amount
                    )
            if text:
                WebSubmissionHandler(self.request.couch_user, self.domain, Msg(text), sql_location).handle()
            url = make_url(
                StockStatus,
                self.domain,
                '?location_id=%s&filter_by_program=%s&startdate='
                '&enddate=&report_type=&filter_by_product=%s',
                (sql_location.location_id, ALL_OPTION, ALL_OPTION)
            )
            return HttpResponseRedirect(url)
        context = self.get_context_data(**kwargs)
        context['formset'] = formset
        return self.render_to_response(context)
    def test_web_user_report_submission(self):
        self.client.login(username=self.username5, password=self.password5)
        view_url = reverse('input_stock', kwargs={'domain': TEST_DOMAIN, 'site_code': 'tsactive'})
        data = {
            'form-TOTAL_FORMS': 2,
            'form-INITIAL_FORMS': 2,
            'form-MAX_NUM_FORMS': 1000
        }
        tsactive = SQLLocation.objects.get(domain=TEST_DOMAIN, site_code='tsactive')

        data['form-0-product_id'] = self.ad.product_id
        data['form-0-product'] = 'ad'
        data['form-0-stock_on_hand'] = 20
        data['form-0-receipts'] = 30

        data['form-1-product_id'] = self.al.product_id
        data['form-1-product'] = 'al'
        data['form-1-stock_on_hand'] = 14
        data['form-1-receipts'] = 17

        response = self.client.post(view_url, data=data)
        url = make_url(
            StockStatus,
            self.domain,
            '?location_id=%s&filter_by_program=all&startdate='
            '&enddate=&report_type=&filter_by_product=all',
            (tsactive.location_id, )
        )
        self.assertRedirects(response, url)
        messages = SMS.objects.filter(domain=TEST_DOMAIN)
        self.assertEqual(messages.count(), 1)
        self.assertEqual(
            messages[0].text,
            'Dear test test2, thank you for reporting the commodities you have. You received ad 30 al 17.'
        )

        stock_states = StockState.objects.filter(case_id=tsactive.supply_point_id)
        stock_transactions = StockTransaction.objects.filter(case_id=tsactive.supply_point_id)

        self.assertEqual(stock_states.count(), 2)
        self.assertEqual(stock_transactions.count(), 6)
        self.assertEqual(stock_transactions.filter(type='consumption').count(), 2)
        self.assertEqual(stock_transactions.filter(type='stockonhand').count(), 2)
        self.assertEqual(stock_transactions.filter(type='receipts').count(), 2)

        ad_consumption = stock_transactions.filter(type='consumption', product_id=self.ad.product_id)[0].quantity
        al_consumption = stock_transactions.filter(type='consumption', product_id=self.al.product_id)[0].quantity

        self.assertEqual(ad_consumption, Decimal(-10))
        self.assertEqual(al_consumption, Decimal(-3))

        al_stock_state = StockState.objects.get(case_id=tsactive.supply_point_id, product_id=self.al.product_id)
        ad_stock_state = StockState.objects.get(case_id=tsactive.supply_point_id, product_id=self.ad.product_id)

        self.assertEqual(int(ad_stock_state.stock_on_hand), 20)
        self.assertEqual(int(al_stock_state.stock_on_hand), 14)

        reports = StockReport.objects.filter(domain=TEST_DOMAIN)

        self.assertEqual(reports.count(), 2)
 def rows(self):
     rows = []
     if self.config['location_id']:
         product_id_to_name = {
             product_id: product_name
             for (product_id, product_name) in self.config['unique_products'].values_list('product_id', 'name')
         }
         for supply_point in self.config['stockout_table_supply_points']:
             products_set = self.config['stockouts'].get(supply_point.supply_point_id)
             url = link_format(supply_point.name, make_url(
                 StockLevelsReport,
                 self.config['domain'],
                 '?location_id=%s&startdate=%s&enddate=%s',
                 (supply_point.location_id, self.config['startdate'], self.config['enddate'])
             ))
             if products_set:
                 rows.append(
                     [url if not self.config.get('is_rendered_as_email') else supply_point.name, ', '.join(
                         product_id_to_name[product_id] for product_id in products_set
                     )]
                 )
             else:
                 rows.append(
                     [url if not self.config.get('is_rendered_as_email') else supply_point.name, '-']
                 )
     return rows
    def test_web_user_report_submission(self):
        self.client.login(username=self.username5, password=self.password5)
        view_url = reverse('input_stock', kwargs={'domain': TEST_DOMAIN, 'site_code': 'tsactive'})
        data = {
            'form-TOTAL_FORMS': 2,
            'form-INITIAL_FORMS': 2,
            'form-MAX_NUM_FORMS': 1000
        }
        tsactive = SQLLocation.objects.get(domain=TEST_DOMAIN, site_code='tsactive')

        data['form-0-product_id'] = self.ad.product_id
        data['form-0-product'] = 'ad'
        data['form-0-stock_on_hand'] = 20
        data['form-0-receipts'] = 30

        data['form-1-product_id'] = self.al.product_id
        data['form-1-product'] = 'al'
        data['form-1-stock_on_hand'] = 14
        data['form-1-receipts'] = 17

        response = self.client.post(view_url, data=data)
        url = make_url(
            StockStatus,
            self.domain,
            '?location_id=%s&filter_by_program=all&startdate='
            '&enddate=&report_type=&filter_by_product=all',
            (tsactive.location_id, )
        )
        self.assertRedirects(response, url)
        messages = SMS.objects.filter(domain=TEST_DOMAIN)
        self.assertEqual(messages.count(), 1)
        self.assertEqual(
            messages[0].text,
            'Dear test test2, thank you for reporting the commodities you have. You received ad 30 al 17.'
        )

        stock_states = StockState.objects.filter(case_id=tsactive.supply_point_id)
        stock_transactions = StockTransaction.objects.filter(case_id=tsactive.supply_point_id)

        self.assertEqual(stock_states.count(), 2)
        self.assertEqual(stock_transactions.count(), 6)
        self.assertEqual(stock_transactions.filter(type='consumption').count(), 2)
        self.assertEqual(stock_transactions.filter(type='stockonhand').count(), 2)
        self.assertEqual(stock_transactions.filter(type='receipts').count(), 2)

        ad_consumption = stock_transactions.filter(type='consumption', product_id=self.ad.product_id)[0].quantity
        al_consumption = stock_transactions.filter(type='consumption', product_id=self.al.product_id)[0].quantity

        self.assertEqual(ad_consumption, Decimal(-10))
        self.assertEqual(al_consumption, Decimal(-3))

        al_stock_state = StockState.objects.get(case_id=tsactive.supply_point_id, product_id=self.al.product_id)
        ad_stock_state = StockState.objects.get(case_id=tsactive.supply_point_id, product_id=self.ad.product_id)

        self.assertEqual(int(ad_stock_state.stock_on_hand), 20)
        self.assertEqual(int(al_stock_state.stock_on_hand), 14)

        reports = StockReport.objects.filter(domain=TEST_DOMAIN)

        self.assertEqual(reports.count(), 2)
Exemple #7
0
 def rows(self):
     rows = []
     if self.config['location_id']:
         product_id_to_name = {
             product_id: product_name
             for (product_id, product_name) in self.config['unique_products'].values_list('product_id', 'name')
         }
         for supply_point in self.config['stockout_table_supply_points']:
             products_set = self.config['stockouts'].get(supply_point.supply_point_id)
             url = link_format(supply_point.name, make_url(
                 StockStatus,
                 self.config['domain'],
                 '?location_id=%s&startdate=%s&enddate=%s',
                 (supply_point.location_id, self.config['startdate'], self.config['enddate'])
             ))
             if products_set:
                 rows.append(
                     [url if not self.config.get('is_rendered_as_email') else supply_point.name, ', '.join(
                         product_id_to_name[product_id] for product_id in products_set
                     )]
                 )
             else:
                 rows.append(
                     [url if not self.config.get('is_rendered_as_email') else supply_point.name, '-']
                 )
     return rows
    def rows(self):
        rows = []
        unique_products = self.unique_products(
            get_supply_points(self.config['domain'], self.config['location_id']), all=(not self.config['export'])
        )
        if self.config['location_id']:
            for case_id, products in self.config['months_of_stock'].iteritems():

                sp = SQLLocation.objects.get(supply_point_id=case_id)

                url = make_url(
                    StockStatus,
                    self.config['domain'],
                    '?location_id=%s&filter_by_program=%s&startdate=%s&enddate=%s&report_type=%s',
                    (sp.location_id, self.config['program'] or ALL_OPTION, self.config['startdate'].date(),
                    self.config['enddate'].date(), self.config['report_type'])
                )

                row = [
                    link_format(sp.name, url) if not self.config.get('is_rendered_as_email', False) else sp.name
                ]

                for p in unique_products:
                    product_data = products.get(p.product_id)
                    if product_data:
                        value = '%.1f' % product_data
                    else:
                        value = '-'
                    row.append(value)
                rows.append(row)
        return rows
Exemple #9
0
 def post(self, request, *args, **kwargs):
     InputStockFormSet = formset_factory(InputStockForm)
     formset = InputStockFormSet(request.POST)
     if formset.is_valid():
         try:
             location = SQLLocation.objects.get(site_code=kwargs['site_code'], domain=self.domain)
         except SQLLocation.DoesNotExist:
             raise Http404()
         data = []
         for form in formset:
             product = Product.get(docid=form.cleaned_data['product_id'])
             if form.cleaned_data['receipts']:
                 data.append(
                     StockTransaction(
                         domain=self.domain,
                         location_id=location.location_id,
                         case_id=location.supply_point_id,
                         product=product,
                         action=const.StockActions.RECEIPTS,
                         quantity=form.cleaned_data['receipts']
                     ),
                 )
             if form.cleaned_data['stock_on_hand'] is not None:
                 data.append(
                     StockTransaction(
                         domain=self.domain,
                         location_id=location.location_id,
                         case_id=location.supply_point_id,
                         product=product,
                         action=const.StockActions.STOCKONHAND,
                         quantity=form.cleaned_data['stock_on_hand']
                     ),
                 )
         if data:
             unpacked_data = {
                 'timestamp': datetime.utcnow(),
                 'user': self.request.couch_user,
                 'phone': 'ewsghana-input-stock',
                 'location': location.couch_location,
                 'transactions': data,
             }
             process(self.domain, unpacked_data)
         url = make_url(
             StockLevelsReport,
             self.domain,
             '?location_id=%s&filter_by_program=%s&startdate='
             '&enddate=&report_type=&filter_by_product=%s',
             (location.location_id, ALL_OPTION, ALL_OPTION)
         )
         return HttpResponseRedirect(url)
     context = self.get_context_data(**kwargs)
     context['formset'] = formset
     return self.render_to_response(context)
Exemple #10
0
 def post(self, request, *args, **kwargs):
     InputStockFormSet = formset_factory(InputStockForm)
     formset = InputStockFormSet(request.POST)
     if formset.is_valid():
         try:
             location = SQLLocation.objects.get(
                 site_code=kwargs['site_code'], domain=self.domain)
         except SQLLocation.DoesNotExist:
             raise Http404()
         data = []
         for form in formset:
             product = Product.get(docid=form.cleaned_data['product_id'])
             if form.cleaned_data['receipts']:
                 data.append(
                     StockTransactionHelper(
                         domain=self.domain,
                         location_id=location.location_id,
                         case_id=location.supply_point_id,
                         product_id=product.get_id,
                         action=const.StockActions.RECEIPTS,
                         quantity=form.cleaned_data['receipts']), )
             if form.cleaned_data['stock_on_hand'] is not None:
                 data.append(
                     StockTransactionHelper(
                         domain=self.domain,
                         location_id=location.location_id,
                         case_id=location.supply_point_id,
                         product_id=product.get_id,
                         action=const.StockActions.STOCKONHAND,
                         quantity=form.cleaned_data['stock_on_hand']), )
         if data:
             unpacked_data = {
                 'timestamp': datetime.utcnow(),
                 'user': self.request.couch_user,
                 'phone': 'ewsghana-input-stock',
                 'location': location.couch_location,
                 'transactions': data,
             }
             process(self.domain, unpacked_data)
         url = make_url(
             StockLevelsReport, self.domain,
             '?location_id=%s&filter_by_program=%s&startdate='
             '&enddate=&report_type=&filter_by_product=%s',
             (location.location_id, ALL_OPTION, ALL_OPTION))
         return HttpResponseRedirect(url)
     context = self.get_context_data(**kwargs)
     context['formset'] = formset
     return self.render_to_response(context)
Exemple #11
0
    def test_incomplete_report_submission(self):
        self.client.login(username=self.username5, password=self.password5)
        view_url = reverse('input_stock',
                           kwargs={
                               'domain': TEST_DOMAIN,
                               'site_code': 'tsactive'
                           })
        data = {
            'form-TOTAL_FORMS': 2,
            'form-INITIAL_FORMS': 2,
            'form-MAX_NUM_FORMS': 1000
        }
        tsactive = SQLLocation.objects.get(domain=TEST_DOMAIN,
                                           site_code='tsactive')

        data['form-0-product_id'] = self.ad.product_id
        data['form-0-product'] = 'ad'
        data['form-0-stock_on_hand'] = ''
        data['form-0-receipts'] = ''

        data['form-1-product_id'] = self.al.product_id
        data['form-1-product'] = 'al'
        data['form-1-stock_on_hand'] = 14
        data['form-1-receipts'] = 17

        response = self.client.post(view_url, data=data)
        url = make_url(
            StockStatus, self.domain,
            '?location_id=%s&filter_by_program=all&startdate='
            '&enddate=&report_type=&filter_by_product=all',
            (tsactive.location_id, ))
        self.assertRedirects(response, url)

        stock_states = StockState.objects.filter(
            case_id=tsactive.supply_point_id)
        stock_transactions = StockTransaction.objects.filter(
            case_id=tsactive.supply_point_id)

        self.assertEqual(stock_states.count(), 1)
        self.assertEqual(stock_transactions.count(), 3)

        ad_transactions = stock_transactions.filter(
            product_id=self.ad.product_id)
        self.assertEqual(ad_transactions.count(), 0)

        with self.assertRaises(StockState.DoesNotExist):
            StockState.objects.get(case_id=tsactive.supply_point_id,
                                   product_id=self.ad.product_id)
    def rows(self):
        rows = []
        if self.config["location_id"]:
            for sp in self.get_supply_points:
                location_types = [
                    loc_type.name
                    for loc_type in filter(
                        lambda loc_type: not loc_type.administrative,
                        Domain.get_by_name(self.config["domain"]).location_types,
                    )
                ]
                if sp.location_type in location_types:
                    cls = StockLevelsReport
                else:
                    cls = StockStatus
                url = make_url(
                    cls,
                    self.config["domain"],
                    "?location_id=%s&filter_by_program=%s&startdate=%s"
                    "&enddate=%s&report_type=%s&filter_by_product=%s",
                    (
                        sp.location_id,
                        self.config["program"] or ALL_OPTION,
                        self.config["startdate"],
                        self.config["enddate"],
                        self.config["report_type"],
                        "&filter_by_product=".join(self.config["products"]),
                    ),
                )

                row = [link_format(sp.name, url)]
                for p in self.unique_products(self.get_supply_points):
                    stock = StockState.objects.filter(sql_product=p, case_id=sp.supply_point_id).order_by(
                        "-last_modified_date"
                    )

                    if stock:
                        monthly = stock[0].get_monthly_consumption()
                        if monthly:
                            row.append(int(stock[0].stock_on_hand / monthly))
                        else:
                            row.append(0)
                    else:
                        row.append("-")
                rows.append(row)
        return rows
Exemple #13
0
    def get_redirect_url(self, *args, **kwargs):
        site_code = kwargs['site_code']
        domain = kwargs['domain']

        sql_location = get_object_or_404(SQLLocation, site_code=site_code, domain=domain)
        cls = DashboardReport
        if not sql_location.location_type.administrative:
            cls = StockStatus

        url = make_url(
            cls,
            domain,
            '?location_id=%s&filter_by_program=%s&startdate='
            '&enddate=&report_type=&filter_by_product=%s',
            (sql_location.location_id, ALL_OPTION, ALL_OPTION)
        )
        return url
    def rows(self):
        rows = []
        if self.config['location_id']:
            for sp in self.get_supply_points:
                if sp.location_type.administrative:
                    cls = StockLevelsReport
                else:
                    cls = StockStatus
                url = make_url(
                    cls,
                    self.config['domain'],
                    '?location_id=%s&filter_by_program=%s&startdate=%s&enddate=%s&report_type=%s',
                    (sp.location_id, self.config['program'] or ALL_OPTION, self.config['startdate'].date(),
                    self.config['enddate'].date(), self.config['report_type']))

                row = [link_format(sp.name, url)]
                products = self.unique_products(self.get_supply_points, all=(not self.config['export']))

                transactions = list(StockTransaction.objects.filter(
                    type='stockonhand',
                    product_id__in=products.values_list('product_id', flat=True),
                    case_id=sp.supply_point_id,
                    report__date__lte=self.config['enddate']
                ).order_by('-report__date'))

                states = list(StockState.objects.filter(
                    case_id=sp.supply_point_id,
                    product_id__in=products.values_list('product_id', flat=True),
                    section_id='stock'
                ))

                for p in products:
                    transaction = first_item(transactions, lambda x: x.product_id == p.product_id)
                    state = first_item(states, lambda x: x.product_id == p.product_id)

                    if transaction and state:
                        if state.daily_consumption:
                            row.append("%.1f" % (transaction.stock_on_hand / state.get_monthly_consumption()))
                        else:
                            row.append('-')
                    else:
                        row.append('-')
                rows.append(row)
        return rows
    def test_incomplete_report_submission(self):
        self.client.login(username=self.username5, password=self.password5)
        view_url = reverse('input_stock', kwargs={'domain': TEST_DOMAIN, 'site_code': 'tsactive'})
        data = {
            'form-TOTAL_FORMS': 2,
            'form-INITIAL_FORMS': 2,
            'form-MAX_NUM_FORMS': 1000
        }
        tsactive = SQLLocation.objects.get(domain=TEST_DOMAIN, site_code='tsactive')

        data['form-0-product_id'] = self.ad.product_id
        data['form-0-product'] = 'ad'
        data['form-0-stock_on_hand'] = ''
        data['form-0-receipts'] = ''

        data['form-1-product_id'] = self.al.product_id
        data['form-1-product'] = 'al'
        data['form-1-stock_on_hand'] = 14
        data['form-1-receipts'] = 17

        response = self.client.post(view_url, data=data)
        url = make_url(
            StockStatus,
            self.domain,
            '?location_id=%s&filter_by_program=all&startdate='
            '&enddate=&report_type=&filter_by_product=all',
            (tsactive.location_id, )
        )
        self.assertRedirects(response, url)

        stock_states = StockState.objects.filter(case_id=tsactive.supply_point_id)
        stock_transactions = StockTransaction.objects.filter(case_id=tsactive.supply_point_id)

        self.assertEqual(stock_states.count(), 1)
        self.assertEqual(stock_transactions.count(), 3)

        ad_transactions = stock_transactions.filter(product_id=self.ad.product_id)
        self.assertEqual(ad_transactions.count(), 0)

        with self.assertRaises(StockState.DoesNotExist):
            StockState.objects.get(case_id=tsactive.supply_point_id,
                                   product_id=self.ad.product_id)
    def rows(self):
        rows = []
        if self.config['location_id']:
            for sp in self.get_supply_points:
                location_types = [loc_type.name for loc_type in filter(
                    lambda loc_type: not loc_type.administrative,
                    Domain.get_by_name(self.config['domain']).location_types
                )]
                if sp.location_type in location_types:
                    cls = StockLevelsReport
                else:
                    cls = StockStatus
                url = make_url(
                    cls,
                    self.config['domain'],
                    '?location_id=%s&filter_by_program=%s&startdate=%s'
                    '&enddate=%s&report_type=%s&filter_by_product=%s',
                    (sp.location_id, self.config['program'] or ALL_OPTION, self.config['startdate'],
                    self.config['enddate'], self.config['report_type'],
                    '&filter_by_product='.join(self.config['products'])))

                row = [link_format(sp.name, url)]
                for p in self.unique_products(self.get_supply_points, all=True):
                    transaction = StockTransaction.objects.filter(
                        type='stockonhand', product_id=p.product_id, case_id=sp.supply_point_id,
                        report__date__lte=self.config['enddate'], report__date__gte=self.config['startdate']
                    ).order_by('-report__date')

                    state = StockState.objects.filter(sql_product=p, case_id=sp.supply_point_id)\
                        .order_by('-last_modified_date')

                    if transaction and state:
                        monthly = state[0].get_monthly_consumption()
                        if monthly:
                            row.append(int(transaction[0].stock_on_hand / monthly))
                        else:
                            row.append(0)
                    else:
                        row.append('-')
                rows.append(row)
        return rows
    def rows(self):
        rows = []
        if self.config['location_id']:
            location = SQLLocation.objects.get(
                domain=self.config['domain'],
                location_id=self.config['location_id']
            )
            if location.location_type.name == 'country':
                supply_points = SQLLocation.objects.filter(
                    Q(parent__location_id=self.config['location_id']) |
                    Q(location_type__name='Regional Medical Store', domain=self.config['domain'])
                ).order_by('name').exclude(supply_point_id__isnull=True).exclude(is_archived=True)
            else:
                supply_points = SQLLocation.objects.filter(
                    parent__location_id=self.config['location_id']
                ).order_by('name').exclude(supply_point_id__isnull=True).exclude(is_archived=True)

            products = set(self.unique_products(supply_points))
            for supply_point in supply_points:
                product_map = {p.product_id: p.name for p in products.intersection(set(supply_point.products))}
                stockout = StockTransaction.objects.filter(
                    type='stockonhand',
                    product_id__in=product_map.keys(),
                    case_id=supply_point.supply_point_id,
                    report__date__range=[self.config['startdate'], self.config['enddate']],
                    stock_on_hand=0
                ).order_by('product_id', '-report__date').distinct('product_id').values_list('product_id',
                                                                                             flat=True)
                if stockout:
                    url = make_url(
                        StockLevelsReport,
                        self.config['domain'],
                        '?location_id=%s&startdate=%s&enddate=%s',
                        (supply_point.location_id, self.config['startdate'], self.config['enddate'])
                    )
                    rows.append(
                        [link_format(supply_point.name, url), ', '.join(product_map[id] for id in stockout)]
                    )
        return rows