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))
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)
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)
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'], ]
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, }
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
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)
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)
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)
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)
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 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 )
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, )
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)
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)
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'], ]
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", ) )))
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 }
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 }
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", ) })
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 }
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 )
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 }
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 }
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'], ]
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)
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))
def product(self): try: return Product.get(self.product_id) except ResourceNotFound: raise Http404()
def active_product(self): prod_id = self.config.get('product_id') if prod_id: return Product.get(prod_id)
def product_name(product_id): return Product.get(product_id).name
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