def bootstrap_commtrack_settings_if_necessary(domain, requisitions_enabled=False): if not (domain and domain.commtrack_enabled and not CommtrackConfig.for_domain(domain.name)): return c = CommtrackConfig( domain=domain.name, multiaction_enabled=True, multiaction_keyword="report", actions=[ CommtrackActionConfig(action="receipts", keyword="r", caption="Received"), CommtrackActionConfig(action="consumption", keyword="c", caption="Consumed"), CommtrackActionConfig(action="consumption", subaction="loss", keyword="l", caption="Losses"), CommtrackActionConfig(action="stockonhand", keyword="soh", caption="Stock on hand"), CommtrackActionConfig(action="stockout", keyword="so", caption="Stock-out"), ], location_types=[ LocationType(name="state", allowed_parents=[""], administrative=True), LocationType(name="district", allowed_parents=["state"], administrative=True), LocationType(name="block", allowed_parents=["district"], administrative=True), LocationType(name="village", allowed_parents=["block"], administrative=True), LocationType(name="outlet", allowed_parents=["village"]), ], supply_point_types=[], ) if requisitions_enabled: c.requisition_config = get_default_requisition_config() c.save() program = make_program(domain.name, "Default", "def") make_product(domain.name, "Sample Product 1", "pp", program.get_id) make_product(domain.name, "Sample Product 2", "pq", program.get_id) make_product(domain.name, "Sample Product 3", "pr", program.get_id) return c
def make_psi_config(domain): c = CommtrackConfig( domain=domain, multiaction_enabled=True, multiaction_keyword="s", actions=[ CommtrackActionConfig(action_type="stockedoutfor", keyword="d", caption="Stock-out Days"), CommtrackActionConfig(action_type="receipts", keyword="r", caption="Other Receipts"), CommtrackActionConfig(action_type="stockonhand", keyword="b", caption="Balance"), CommtrackActionConfig(action_type="receipts", name="sales", keyword="p", caption="Placements"), ], supply_point_types=[ SupplyPointType(name="CHC", categories=["Public"]), SupplyPointType(name="PHC", categories=["Public"]), SupplyPointType(name="SC", categories=["Public"]), SupplyPointType(name="MBBS", categories=["Private"]), SupplyPointType(name="Pediatrician", categories=["Private"]), SupplyPointType(name="AYUSH", categories=["Private"]), SupplyPointType(name="Medical Store / Chemist", categories=["Traditional"]), SupplyPointType(name="RP", categories=["Traditional"]), SupplyPointType(name="Asha", categories=["Frontline Workers"]), SupplyPointType(name="AWW", categories=["Public", "Frontline Workers"]), SupplyPointType(name="NGO", categories=["Non-traditional"]), SupplyPointType(name="CBO", categories=["Non-traditional"]), SupplyPointType(name="SHG", categories=["Non-traditional"]), SupplyPointType(name="Pan Store", categories=["Traditional"]), SupplyPointType(name="General Store", categories=["Traditional"]), ], ) c.save() return c
def test_get_docs_in_domain_by_class(self): commtrack_config = CommtrackConfig(domain=self.domain) group = Group(domain=self.domain) xform = XFormInstance(domain=self.domain) commtrack_config.save() group.save() xform.save() self.addCleanup(commtrack_config.delete) self.addCleanup(group.delete) self.addCleanup(xform.delete) [commtrack_config_2] = get_docs_in_domain_by_class(self.domain, CommtrackConfig) self.assertEqual(commtrack_config_2.to_json(), commtrack_config.to_json())
def test_get_docs_in_domain_by_class(self): commtrack_config = CommtrackConfig(domain=self.domain) group = Group(domain=self.domain) xform = XFormInstance(domain=self.domain) commtrack_config.save() group.save() xform.save() self.addCleanup(commtrack_config.delete) self.addCleanup(group.delete) self.addCleanup(xform.delete) [commtrack_config_2 ] = get_docs_in_domain_by_class(self.domain, CommtrackConfig) self.assertEqual(commtrack_config_2.to_json(), commtrack_config.to_json())
def _create_unsynced_couch(self): """ Create a CommtrackConfig matching the one created by _create_unsynced_sql """ couch = CommtrackConfig( domain='my_project', use_auto_emergency_levels=False, sync_consumption_fixtures=False, use_auto_consumption=False, individual_consumption_defaults=True, ota_restore_config=StockRestoreConfig( section_to_consumption_types={'s1': 'c1'}, force_consumption_case_types=['type1'], use_dynamic_product_list=True, ), alert_config=AlertConfig( stock_out_facilities=True, stock_out_commodities=True, stock_out_rates=True, non_report=True, ), actions=[ CommtrackActionConfig( action='receipts', subaction='sub-receipts', _keyword='one', caption='first action', ), CommtrackActionConfig( action='receipts', subaction='sub-receipts', _keyword='two', caption='second action', ), ], consumption_config=ConsumptionConfig( min_transactions=1, min_window=2, optimal_window=3, use_supply_point_type_default_consumption=True, exclude_invalid_periods=False, ), stock_levels_config=StockLevelsConfig( emergency_level=0.5, understock_threshold=1.5, overstock_threshold=3, )) couch.save(sync_to_sql=False) return couch
def make_psi_config(domain): c = CommtrackConfig( domain=domain, multiaction_enabled=True, multiaction_keyword='s', actions=[ CommtrackActionConfig( action_type='stockedoutfor', keyword='d', caption='Stock-out Days' ), CommtrackActionConfig( action_type='receipts', keyword='r', caption='Other Receipts' ), CommtrackActionConfig( action_type='stockonhand', keyword='b', caption='Balance' ), CommtrackActionConfig( action_type='receipts', name='sales', keyword='p', caption='Placements' ), ], supply_point_types=[ SupplyPointType(name='CHC', categories=['Public']), SupplyPointType(name='PHC', categories=['Public']), SupplyPointType(name='SC', categories=['Public']), SupplyPointType(name='MBBS', categories=['Private']), SupplyPointType(name='Pediatrician', categories=['Private']), SupplyPointType(name='AYUSH', categories=['Private']), SupplyPointType(name='Medical Store / Chemist', categories=['Traditional']), SupplyPointType(name='RP', categories=['Traditional']), SupplyPointType(name='Asha', categories=['Frontline Workers']), SupplyPointType(name='AWW', categories=['Public', 'Frontline Workers']), SupplyPointType(name='NGO', categories=['Non-traditional']), SupplyPointType(name='CBO', categories=['Non-traditional']), SupplyPointType(name='SHG', categories=['Non-traditional']), SupplyPointType(name='Pan Store', categories=['Traditional']), SupplyPointType(name='General Store', categories=['Traditional']), ] ) c.save() return c
def setUpClass(cls): super(TestStockOut, cls).setUpClass() cls.facility2 = make_loc(code="loc2", name="Test Facility 2", type="FACILITY", domain=TEST_DOMAIN, parent=cls.district) cls.user2 = bootstrap_user( cls.facility2, username='******', domain=TEST_DOMAIN, home_loc='loc2', phone_number='5551235', first_name='test', last_name='Test' ) SLABConfig.objects.create( is_pilot=True, sql_location=cls.facility.sql_location ) slab_config = SLABConfig.objects.create( is_pilot=True, sql_location=cls.facility2.sql_location ) slab_config.closest_supply_points.add(cls.facility.sql_location) slab_config.save() config = CommtrackConfig.for_domain(TEST_DOMAIN) config.use_auto_consumption = False config.individual_consumption_defaults = True config.consumption_config = ConsumptionConfig( use_supply_point_type_default_consumption=True, exclude_invalid_periods=True ) config.save() set_default_consumption_for_supply_point(TEST_DOMAIN, cls.id.get_id, cls.facility_sp_id, 100) set_default_consumption_for_supply_point(TEST_DOMAIN, cls.dp.get_id, cls.facility_sp_id, 100) set_default_consumption_for_supply_point(TEST_DOMAIN, cls.ip.get_id, cls.facility_sp_id, 100)
def prepare_commtrack_config(self): for location_type in LocationType.objects.by_domain(self.domain): location_type.delete() country = self._make_loc_type(name="country", administrative=True) self._make_loc_type(name="Central Medical Store", parent_type=country) region = self._make_loc_type(name="region", administrative=True, parent_type=country) self._make_loc_type(name="Teaching Hospital", parent_type=region) self._make_loc_type(name="Regional Medical Store", parent_type=region) self._make_loc_type(name="Regional Hospital", parent_type=region) district = self._make_loc_type(name="district", administrative=True, parent_type=region) self._make_loc_type(name="Clinic", parent_type=district) self._make_loc_type(name="District Hospital", parent_type=district) self._make_loc_type(name="Health Centre", parent_type=district) self._make_loc_type(name="CHPS Facility", parent_type=district) self._make_loc_type(name="Hospital", parent_type=district) self._make_loc_type(name="Psychiatric Hospital", parent_type=district) self._make_loc_type(name="Polyclinic", parent_type=district) self._make_loc_type(name="facility", parent_type=district) config = CommtrackConfig.for_domain(self.domain) config.consumption_config.exclude_invalid_periods = True config.save()
def commtrack_settings(self): # this import causes some dependency issues so lives in here from corehq.apps.commtrack.models import CommtrackConfig if self.commtrack_enabled: return CommtrackConfig.for_domain(self.name) else: return None
def commtrack_settings_sync(project, locations_types): if MigrationCheckpoint.objects.filter(domain=project).count() != 0: return config = CommtrackConfig.for_domain(project) domain = Domain.get_by_name(project) domain.location_types = [] for i, value in enumerate(locations_types): if not any(lt.name == value for lt in domain.location_types): allowed_parents = [locations_types[i - 1]] if i > 0 else [""] domain.location_types.append( LocationType( name=value, allowed_parents=allowed_parents, administrative=(value.lower() != 'facility') ) ) actions = [action.keyword for action in config.actions] if 'delivered' not in actions: config.actions.append( CommtrackActionConfig( action='receipts', keyword='delivered', caption='Delivered') ) config.save()
def stock_level_config_for_domain(domain, commtrack_enabled): from corehq.apps.commtrack.models import CommtrackConfig ct_config = CommtrackConfig.for_domain(domain) if ct_config is None or not commtrack_enabled: return None else: return ct_config.stock_levels_config
def prepare_commtrack_config(self): """ Bootstraps the domain-level metadata according to the static config. - Sets the proper location types hierarchy on the domain object. - Sets a keyword handler for reporting receipts """ for location_type in LocationType.objects.by_domain(self.domain): location_type.delete() previous = None for loc_type in LOCATION_TYPES: previous, _ = LocationType.objects.get_or_create( domain=self.domain, name=loc_type, parent_type=previous, administrative=(loc_type != 'FACILITY'), ) config = CommtrackConfig.for_domain(self.domain) config.consumption_config.exclude_invalid_periods = True actions = [action.keyword for action in config.actions] if 'delivered' not in actions: config.actions.append( CommtrackActionConfig(action='receipts', keyword='delivered', caption='Delivered')) config.save()
def process(domain, instance): """process an incoming commtrack stock report instance""" config = CommtrackConfig.for_domain(domain) root = etree.fromstring(instance) user_id, transactions = unpack_transactions(root, config) transactions = list(normalize_transactions(transactions)) def get_transactions(all_tx, type_filter): """get all the transactions of the relevant type (filtered by type_filter), grouped by product (returns a dict of 'product subcase id' => list of transactions), with each set of transactions sorted in the correct order for processing """ return map_reduce( lambda tx: [(tx.case_id, )], lambda v: sorted(v, key=lambda tx: tx.priority_order ), # important! data=filter(type_filter, all_tx), include_docs=True) # split transactions by type and product stock_transactions = get_transactions(transactions, lambda tx: tx.category == 'stock') requisition_transactions = get_transactions( transactions, lambda tx: tx.category == 'requisition') case_ids = list( set(itertools.chain(*[tx.get_case_ids() for tx in transactions]))) cases = dict((c._id, c) for c in CommCareCase.view( '_all_docs', keys=case_ids, include_docs=True)) # TODO: code to auto generate / update requisitions from transactions if # project is configured for that. # TODO: when we start receiving commcare-submitted reports, we should be using a server time rather # than relying on timeStart (however timeStart is set to server time for reports received via sms) submit_time = root.find('.//%s' % _('timeStart', META_XMLNS)).text post_processed_transactions = list(transactions) for product_id, product_case in cases.iteritems(): stock_txs = stock_transactions.get(product_id, []) if stock_txs: case_block, reconciliations = process_product_transactions( user_id, submit_time, product_case, stock_txs) root.append(case_block) post_processed_transactions.extend(reconciliations) req_txs = requisition_transactions.get(product_id, []) if req_txs and config.requisitions_enabled: req = RequisitionState.from_transactions(user_id, product_case, req_txs) case_block = etree.fromstring(req.to_xml()) root.append(case_block) replace_transactions(root, post_processed_transactions) submission = etree.tostring(root) logger.debug('submitting: %s' % submission) spoof_submission(get_submit_url(domain), submission, headers={'HTTP_X_SUBMIT_TIME': submit_time}, hqsubmission=False)
def process_stock(xform, case_db=None): """ process the commtrack xml constructs in an incoming submission """ case_db = case_db or CaseDbCache() assert isinstance(case_db, CaseDbCache) if is_device_report(xform): return [] domain = xform.domain config = CommtrackConfig.for_domain(domain) # these are the raw stock report objects from the xml stock_reports = list(unpack_commtrack(xform, config)) # flattened transaction list spanning all stock reports in the form transactions = [t for r in stock_reports for t in r.transactions] # omitted: normalize_transactions (used for bulk requisitions?) if not transactions: return [] # transactions grouped by case/product id grouped_tx = map_reduce(lambda tx: [((tx.case_id, tx.product_id),)], lambda v: sorted(v, key=lambda tx: tx.timestamp), data=transactions, include_docs=True) case_ids = list(set(k[0] for k in grouped_tx)) # list of cases that had stock reports in the form # there is no need to wrap them by case type relevant_cases = [case_db.get(case_id) for case_id in case_ids] user_id = xform.form['meta']['userID'] submit_time = xform['received_on'] # touch every case for proper ota restore logic syncing to be preserved for case in relevant_cases: case_action = CommCareCaseAction.from_parsed_action( submit_time, user_id, xform, AbstractAction(CASE_ACTION_COMMTRACK) ) # hack: clear the sync log id so this modification always counts # since consumption data could change server-side case_action.sync_log_id = '' case.actions.append(case_action) case_db.mark_changed(case) # also purge the sync token cache for the same reason if relevant_cases and xform.get_sync_token(): xform.get_sync_token().invalidate_cached_payloads() # create the django models for report in stock_reports: report.create_models(domain) # TODO make this a signal from corehq.apps.commtrack.signals import send_notifications, raise_events send_notifications(xform, relevant_cases) raise_events(xform, relevant_cases) return relevant_cases
def testOTASettings(self): ct_settings = CommtrackConfig.for_domain(self.domain) ct_settings.consumption_config = ConsumptionConfig( min_transactions=10, min_window=20, optimal_window=60, ) ct_settings.ota_restore_config = StockRestoreConfig( section_to_consumption_types={'stock': 'consumption'}, ) set_default_monthly_consumption_for_domain(self.domain, 5 * DAYS_IN_MONTH) restore_settings = ct_settings.get_ota_restore_settings() self.assertEqual(1, len(restore_settings.section_to_consumption_types)) self.assertEqual('consumption', restore_settings.section_to_consumption_types['stock']) self.assertEqual(10, restore_settings.consumption_config.min_periods) self.assertEqual(20, restore_settings.consumption_config.min_window) self.assertEqual(60, restore_settings.consumption_config.max_window) self.assertEqual(150, restore_settings.consumption_config.default_monthly_consumption_function('foo', 'bar')) self.assertFalse(restore_settings.force_consumption_case_filter(CommCareCase(type='force-type'))) self.assertEqual(0, len(restore_settings.default_product_list)) ct_settings.ota_restore_config.force_consumption_case_types=['force-type'] ct_settings.ota_restore_config.use_dynamic_product_list=True restore_settings = ct_settings.get_ota_restore_settings() self.assertTrue(restore_settings.force_consumption_case_filter(CommCareCase(type='force-type'))) self.assertEqual(3, len(restore_settings.default_product_list))
def prepare_commtrack_config(domain): def _make_loc_type(name, administrative=False, parent_type=None): return LocationType.objects.get_or_create( domain=domain, name=name, administrative=administrative, parent_type=parent_type, )[0] for location_type in LocationType.objects.by_domain(domain): location_type.delete() country = _make_loc_type(name="country", administrative=True) _make_loc_type(name="Central Medical Store", parent_type=country) region = _make_loc_type(name="region", administrative=True, parent_type=country) _make_loc_type(name="Teaching Hospital", parent_type=region) _make_loc_type(name="Regional Medical Store", parent_type=region) _make_loc_type(name="Regional Hospital", parent_type=region) district = _make_loc_type(name="district", administrative=True, parent_type=region) _make_loc_type(name="Clinic", parent_type=district) _make_loc_type(name="District Hospital", parent_type=district) _make_loc_type(name="Health Centre", parent_type=district) _make_loc_type(name="CHPS Facility", parent_type=district) _make_loc_type(name="Hospital", parent_type=district) _make_loc_type(name="Psychiatric Hospital", parent_type=district) _make_loc_type(name="Polyclinic", parent_type=district) _make_loc_type(name="facility", parent_type=district) config = CommtrackConfig.for_domain(domain) config.consumption_config.exclude_invalid_periods = True config.save()
def page_context(self): try: runner = ReportRun.objects.filter( domain=self.domain, complete=False).latest('start_run') except ReportRun.DoesNotExist: runner = None return { 'runner': runner, 'settings': self.settings_context, 'source': self.source, 'sync_url': self.sync_urlname, 'sync_stock_url': self.sync_stock_url, 'clear_stock_url': self.clear_stock_url, 'is_developer': toggles.IS_DEVELOPER.enabled(self.request.couch_user.username), 'is_commtrack_enabled': CommtrackConfig.for_domain(self.domain) }
def process(domain, instance): """process an incoming commtrack stock report instance""" config = CommtrackConfig.for_domain(domain) root = etree.fromstring(instance) transactions = unpack_transactions(root, config) case_ids = [tx["case_id"] for tx in transactions] cases = dict((c._id, c) for c in CommCareCase.view("_all_docs", keys=case_ids, include_docs=True)) # ensure transaction types are processed in the correct order def transaction_order(tx): return [action.action_name for action in config.actions].index(tx["action"]) transactions.sort(key=transaction_order) # apply all transactions to each product case in bulk transactions_by_product = map_reduce(lambda tx: [(tx["case_id"],)], data=transactions, include_docs=True) for product_id, txs in transactions_by_product.iteritems(): product_case = cases[product_id] case_block, reconciliations = process_product_transactions(product_case, txs) for recon in reconciliations: root.append(recon) root.append(case_block) submission = etree.tostring(root) logger.debug("submitting: %s" % submission) submit_time = root.find(".//%s" % _("timeStart", META_XMLNS)).text spoof_submission(get_submit_url(domain), submission, headers={"HTTP_X_SUBMIT_TIME": submit_time})
def setUp(self): # might as well clean house before doing anything delete_all_xforms() delete_all_cases() StockReport.objects.all().delete() StockTransaction.objects.all().delete() self.backend = test.bootstrap(TEST_BACKEND, to_console=True) self.domain = bootstrap_domain() self.ct_settings = CommtrackConfig.for_domain(self.domain.name) if self.requisitions_enabled: self.ct_settings.requisition_config = get_default_requisition_config() self.ct_settings.save() self.loc = make_loc('loc1') self.sp = make_supply_point(self.domain.name, self.loc) self.users = [bootstrap_user(self, **user_def) for user_def in self.user_definitions] if False: # bootstrap additional users for requisitions # needs to get reinserted for requisition stuff later self.approver = bootstrap_user(self, **APPROVER_USER) self.packer = bootstrap_user(self, **PACKER_USER) self.users += [self.approver, self.packer] # everyone should be in a group. self.group = Group(domain=TEST_DOMAIN, name='commtrack-folks', users=[u._id for u in self.users], case_sharing=True) self.group.save() self.sp.owner_id = self.group._id self.sp.save() self.products = sorted(Product.by_domain(self.domain.name), key=lambda p: p._id) self.assertEqual(3, len(self.products))
def testOTASettings(self): domain = bootstrap_domain() ct_settings = CommtrackConfig.for_domain(domain.name) ct_settings.consumption_config = ConsumptionConfig( min_transactions=10, min_window=20, optimal_window=60, ) ct_settings.ota_restore_config = StockRestoreConfig( section_to_consumption_types={'stock': 'consumption'}, ) set_default_monthly_consumption_for_domain(domain.name, 5 * DAYS_IN_MONTH) restore_settings = ct_settings.get_ota_restore_settings() self.assertEqual(1, len(restore_settings.section_to_consumption_types)) self.assertEqual('consumption', restore_settings.section_to_consumption_types['stock']) self.assertEqual(10, restore_settings.consumption_config.min_periods) self.assertEqual(20, restore_settings.consumption_config.min_window) self.assertEqual(60, restore_settings.consumption_config.max_window) self.assertEqual(150, restore_settings.consumption_config.default_monthly_consumption_function('foo', 'bar')) self.assertFalse(restore_settings.force_consumption_case_filter(CommCareCase(type='force-type'))) self.assertEqual(0, len(restore_settings.default_product_list)) ct_settings.ota_restore_config.force_consumption_case_types=['force-type'] ct_settings.ota_restore_config.use_dynamic_product_list=True restore_settings = ct_settings.get_ota_restore_settings() self.assertTrue(restore_settings.force_consumption_case_filter(CommCareCase(type='force-type'))) self.assertEqual(3, len(restore_settings.default_product_list))
def _create_commtrack_config_if_needed(domain): if CommtrackConfig.for_domain(domain): return CommtrackConfig( domain=domain, actions=[ CommtrackActionConfig( action='receipts', keyword='r', caption='Received', ), CommtrackActionConfig( action='consumption', keyword='c', caption='Consumed', ), CommtrackActionConfig( action='consumption', subaction='loss', keyword='l', caption='Losses', ), CommtrackActionConfig( action='stockonhand', keyword='soh', caption='Stock on hand', ), CommtrackActionConfig( action='stockout', keyword='so', caption='Stock-out', ), ], ).save()
def setUp(self): super(XMLTest, self).setUp() self.domain = util.bootstrap_domain(util.TEST_DOMAIN) util.bootstrap_location_types(self.domain.name) util.bootstrap_products(self.domain.name) self.products = sorted(Product.by_domain(self.domain.name), key=lambda p: p._id) self.ct_settings = CommtrackConfig.for_domain(self.domain.name) self.ct_settings.consumptionconfig = ConsumptionConfig( min_transactions=0, min_window=0, optimal_window=60, ) self.ct_settings.save() self.ct_settings.consumptionconfig.commtrack_settings = self.ct_settings self.ct_settings.consumptionconfig.save() self.domain = Domain.get(self.domain._id) self.loc = make_loc('loc1') self.sp = self.loc.linked_supply_point() self.users = [ util.bootstrap_user(self, **user_def) for user_def in self.user_definitions ] self.user = self.users[0] self.case_ids = [self.sp.case_id] self.addCleanup(self.delete_ledger_values, self.sp.case_id)
def prepare_commtrack_config(self): """ Bootstraps the domain-level metadata according to the static config. - Sets the proper location types hierarchy on the domain object. - Sets a keyword handler for reporting receipts """ for location_type in LocationType.objects.by_domain(self.domain): location_type.delete() previous = None for loc_type in LOCATION_TYPES: previous, _ = LocationType.objects.get_or_create( domain=self.domain, name=loc_type, parent_type=previous, administrative=(loc_type != 'FACILITY'), ) config = CommtrackConfig.for_domain(self.domain) config.consumption_config.exclude_invalid_periods = True actions = [action.keyword for action in config.actions] if 'delivered' not in actions: config.actions.append( CommtrackActionConfig( action='receipts', keyword='delivered', caption='Delivered') ) config.save()
def get_data(self): if self.active_product: sql_product = SQLProduct.objects.get(product_id=self.active_product.get_id) filtered_locations = [ location for location in self.locations if sql_product in location.products ] else: filtered_locations = [] for location in filtered_locations: if location.supply_point_id: stock_states = StockState.objects.filter( case_id=location.supply_point_id, section_id=STOCK_SECTION_TYPE, product_id=self.active_product.get_id ).order_by('-last_modified_date') else: stock_states = None stock_levels = CommtrackConfig.for_domain(self.domain).stock_levels_config category = "no-data" if not stock_states: quantity = "No data" months_until_stockout = None else: monthly_consumption = stock_states[0].get_monthly_consumption() quantity = stock_states[0].stock_on_hand if not monthly_consumption: months_until_stockout = None else: months_until_stockout = (float(stock_states[0].stock_on_hand) / float(monthly_consumption)) if quantity == 0: category = 'stockout' months_until_stockout = 0 elif months_until_stockout is None: category = "no-data" elif months_until_stockout < location.location_type.understock_threshold: category = 'understock' elif stock_levels.understock_threshold < months_until_stockout < \ location.location_type.overstock_threshold: category = 'adequate' elif months_until_stockout > location.location_type.overstock_threshold: category = 'overstock' icon, color = self._get_icon_and_color(category) geo_point = None if location.latitude is not None and location.latitude is not None: geo_point = '%s %s' % (location.latitude, location.longitude) yield { 'name': location.name, 'type': location.location_type.name, 'geo': geo_point, 'quantity': quantity, 'category': category, 'icon': icon, 'color': color, 'months_until_stockout': "%.2f" % months_until_stockout if months_until_stockout is not None else "No data", 'last_reported': stock_states[0].last_modified_date if stock_states else None }
def all_sms_codes(domain): config = CommtrackConfig.for_domain(domain) actions = dict((action.keyword, action) for action in config.actions) products = dict((p.code, p) for p in Product.by_domain(domain)) sms_codes = zip(('action', 'product'), (actions, products)) return dict(itertools.chain(*([(k.lower(), (type, v)) for k, v in six.iteritems(codes)] for type, codes in sms_codes)))
def stock_level_config_for_domain(domain, commtrack_enabled): from corehq.apps.commtrack.models import CommtrackConfig ct_config = CommtrackConfig.for_domain(domain) if ((ct_config is None) or (not commtrack_enabled) or LOCATION_TYPE_STOCK_RATES.enabled(domain)): return None else: return ct_config.stock_levels_config
def process_stock(xform): """ process the commtrack xml constructs in an incoming submission """ if is_device_report(xform): return domain = xform.domain config = CommtrackConfig.for_domain(domain) # these are the raw stock report objects from the xml stock_reports = list(unpack_commtrack(xform, config)) # flattened transaction list spanning all stock reports in the form transactions = [t for r in stock_reports for t in r.transactions] # omitted: normalize_transactions (used for bulk requisitions?) if not transactions: return # transactions grouped by case/product id grouped_tx = map_reduce(lambda tx: [((tx.case_id, tx.product_id),)], lambda v: sorted(v, key=lambda tx: tx.timestamp), data=transactions, include_docs=True) # list of cases that had stock reports in the form, properly wrapped by case type try: relevant_cases = [wrap_commtrack_case(result['doc']) for result in CommCareCase.get_db().view('_all_docs', keys=list(set(k[0] for k in grouped_tx)), include_docs=True)] except KeyError: raise Exception("Cannot find case matching supplied entity id") user_id = xform.form['meta']['userID'] submit_time = xform['received_on'] # touch every case for proper ota restore logic syncing to be preserved for case in relevant_cases: case_action = CommCareCaseAction.from_parsed_action( submit_time, user_id, xform, AbstractAction(CASE_ACTION_COMMTRACK) ) # hack: clear the sync log id so this modification always counts # since consumption data could change server-side case_action.sync_log_id = '' case.actions.append(case_action) case.save() # create the django models for report in stock_reports: report.create_models() # TODO make this a signal from corehq.apps.commtrack.signals import send_notifications, raise_events send_notifications(xform, relevant_cases) raise_events(xform, relevant_cases)
def setUp(self): self.endpoint = MockEndpoint('http://test-api.com/', 'dummy', 'dummy') self.api_object = ILSGatewayAPI(TEST_DOMAIN, self.endpoint) self.datapath = os.path.join(os.path.dirname(__file__), 'data') domain = initial_bootstrap(TEST_DOMAIN) CommtrackConfig(domain=domain.name).save() self.api_object.prepare_commtrack_config() for location in Location.by_domain(TEST_DOMAIN): location.delete()
def all_sms_codes(domain): config = CommtrackConfig.for_domain(domain) actions = dict((action.keyword, action) for action in config.actions) products = dict((p.code, p) for p in Product.by_domain(domain)) commands = {config.multiaction_keyword: {"type": "stock_report_generic", "caption": "Stock Report"}} sms_codes = zip(("action", "product", "command"), (actions, products, commands)) return dict(itertools.chain(*([(k.lower(), (type, v)) for k, v in codes.iteritems()] for type, codes in sms_codes)))
def _prepare_ledger_for_es(ledger): from corehq.apps.commtrack.models import CommtrackConfig commtrack_config = CommtrackConfig.for_domain(ledger['domain']) if commtrack_config and commtrack_config.use_auto_consumption: daily_consumption = _get_daily_consumption_for_ledger(ledger) ledger['daily_consumption'] = daily_consumption return ledger
def test_report_with_exclude_disabled(self): commtrack_config = CommtrackConfig(domain=self.domain.name) commtrack_config.consumption_config = ConsumptionConfig() commtrack_config.save() self.create_transactions(self.domain.name) self.assertEqual(StockTransaction.objects.all().count(), 3) self.assertEqual(StockTransaction.objects.filter(type='receipts').count(), 1) commtrack_config.delete()
def should_exclude_invalid_periods(domain): """ Whether the domain's consumption calculation should exclude invalid periods """ from corehq.apps.commtrack.models import CommtrackConfig if domain: config = CommtrackConfig.for_domain(domain) if config: return config.consumption_config.exclude_invalid_periods return False
def get_months_until_stockout_icon(value): stock_levels = CommtrackConfig.for_domain(self.config['domain']).stock_levels_config if float(value) == 0.0: return '%s <span class="icon-remove" style="color:red"/>' % value elif float(value) < stock_levels.understock_threshold: return '%s <span class="icon-warning-sign" style="color:orange"/>' % value elif stock_levels.understock_threshold < float(value) < stock_levels.overstock_threshold: return '%s <span class="icon-ok" style="color:green"/>' % value elif float(value) >= stock_levels.overstock_threshold: return '%s <span class="icon-arrow-up" style="color:purple"/>' % value
def setUp(self): super(ConfirmDeliveryTest, self).setUp() self.api = MockOpenLMISEndpoint("uri://mock/lmis/endpoint", username='******', password='******') openlmis_config = OpenLMISConfig() openlmis_config.enabled = True commtrack_config = CommtrackConfig.get(self.domain.commtrack_settings._id) commtrack_config.openlmis_config = openlmis_config commtrack_config.save()
def setUp(self): super(OpenLMISTestBase, self).setUp() self.api = MockOpenLMISEndpoint("uri://mock/lmis/endpoint", username="******", password="******") openlmis_config = OpenLMISConfig() openlmis_config.enabled = True commtrack_config = CommtrackConfig.for_domain(self.domain.name) commtrack_config.openlmis_config = openlmis_config commtrack_config.save()
def _populate_stock_levels(self): from corehq.apps.commtrack.models import CommtrackConfig ct_config = CommtrackConfig.for_domain(self.domain) if ((ct_config is None) or (not self.commtrack_enabled) or LOCATION_TYPE_STOCK_RATES.enabled(self.domain)): return config = ct_config.stock_levels_config self.emergency_level = config.emergency_level self.understock_threshold = config.understock_threshold self.overstock_threshold = config.overstock_threshold
def should_exclude_invalid_periods(domain): """ Whether the domain's consumption calculation should exclude invalid periods i.e. periods where the stock went up without a receipt being reported """ from corehq.apps.commtrack.models import CommtrackConfig if domain: config = CommtrackConfig.for_domain(domain) if config: return config.consumption_config.exclude_invalid_periods return False
def all_sms_codes(domain): config = CommtrackConfig.for_domain(domain) actions = dict((action.keyword, action) for action in config.actions) products = dict((p.code, p) for p in Product.by_domain(domain)) commands = { config.multiaction_keyword: {'type': 'stock_report_generic', 'caption': 'Stock Report'}, } sms_codes = zip(('action', 'product', 'command'), (actions, products, commands)) return dict(itertools.chain(*([(k.lower(), (type, v)) for k, v in codes.iteritems()] for type, codes in sms_codes)))
def stock_level_config_for_domain(domain, commtrack_enabled): from corehq.apps.commtrack.models import CommtrackConfig ct_config = CommtrackConfig.for_domain(domain) if ( (ct_config is None) or (not commtrack_enabled) or LOCATION_TYPE_STOCK_RATES.enabled(domain) ): return None else: return ct_config.stock_levels_config
def process(domain, instance): """process an incoming commtrack stock report instance""" config = CommtrackConfig.for_domain(domain) root = etree.fromstring(instance) user_id, transactions = unpack_transactions(root, config) transactions = list(normalize_transactions(transactions)) def get_transactions(all_tx, type_filter): """get all the transactions of the relevant type (filtered by type_filter), grouped by product (returns a dict of 'product subcase id' => list of transactions), with each set of transactions sorted in the correct order for processing """ return map_reduce( lambda tx: [(tx.case_id,)], lambda v: sorted(v, key=lambda tx: tx.priority_order), # important! data=filter(type_filter, all_tx), include_docs=True, ) # split transactions by type and product stock_transactions = get_transactions(transactions, lambda tx: tx.category == "stock") requisition_transactions = get_transactions(transactions, lambda tx: tx.category == "requisition") case_ids = list(set(itertools.chain(*[tx.get_case_ids() for tx in transactions]))) cases = dict((c._id, c) for c in CommCareCase.view("_all_docs", keys=case_ids, include_docs=True)) # TODO: code to auto generate / update requisitions from transactions if # project is configured for that. # TODO: when we start receiving commcare-submitted reports, we should be using a server time rather # than relying on timeStart (however timeStart is set to server time for reports received via sms) submit_time = root.find(".//%s" % _("timeStart", META_XMLNS)).text post_processed_transactions = list(transactions) for product_id, product_case in cases.iteritems(): stock_txs = stock_transactions.get(product_id, []) if stock_txs: case_block, reconciliations = process_product_transactions(user_id, submit_time, product_case, stock_txs) root.append(case_block) post_processed_transactions.extend(reconciliations) req_txs = requisition_transactions.get(product_id, []) if req_txs and config.requisitions_enabled: req = RequisitionState.from_transactions(user_id, product_case, req_txs) case_block = etree.fromstring(req.to_xml()) root.append(case_block) replace_transactions(root, post_processed_transactions) submission = etree.tostring(root) logger.debug("submitting: %s" % submission) spoof_submission( get_submit_url(domain), submission, headers={"HTTP_X_SUBMIT_TIME": submit_time}, hqsubmission=False )
def _prepare_ledger_for_es(ledger): from corehq.apps.commtrack.models import CommtrackConfig commtrack_config = CommtrackConfig.for_domain(ledger['domain']) if commtrack_config and commtrack_config.use_auto_consumption: daily_consumption = _get_daily_consumption_for_ledger(ledger) ledger['daily_consumption'] = daily_consumption if not ledger.get('location_id') and ledger.get('case_id'): ledger['location_id'] = _location_id_for_case(ledger['case_id']) return ledger
def setUp(self): super(OpenLMISTestBase, self).setUp() self.api = MockOpenLMISEndpoint("uri://mock/lmis/endpoint", username='******', password='******') openlmis_config = OpenLMISConfig() openlmis_config.enabled = True commtrack_config = CommtrackConfig.for_domain(self.domain.name) commtrack_config.openlmis_config = openlmis_config commtrack_config.save()
def setUpClass(cls): domain = prepare_domain(TEST_DOMAIN) p = Product(domain=domain.name, name='Jadelle', code='jd', unit='each') p.save() p2 = Product(domain=domain.name, name='Male Condom', code='mc', unit='each') p2.save() p3 = Product(domain=domain.name, name='Lofem', code='lf', unit='each') p3.save() p4 = Product(domain=domain.name, name='Ng', code='ng', unit='each') p4.save() p5 = Product(domain=domain.name, name='Micro-G', code='mg', unit='each') p5.save() loc = make_loc(code="garms", name="Test RMS", type="Regional Medical Store", domain=domain.name) test.bootstrap(TEST_BACKEND, to_console=True) cls.user1 = bootstrap_user(username='******', first_name='test1', last_name='test1', domain=domain.name, home_loc=loc) cls.user2 = bootstrap_user(username='******', domain=domain.name, home_loc=loc, first_name='test2', last_name='test2', phone_number='222222', user_data={'role': 'In Charge'}) try: XFormInstance.get(docid='test-xform') except ResourceNotFound: xform = XFormInstance(_id='test-xform') xform.save() sql_location = loc.sql_location sql_location.products = SQLProduct.objects.filter(product_id=p5.get_id) sql_location.save() config = CommtrackConfig.for_domain(domain.name) config.actions.append( CommtrackActionConfig(action='receipts', keyword='rec', caption='receipts')) config.consumption_config = ConsumptionConfig(min_transactions=0, min_window=0, optimal_window=60) config.save()
def handle(self, *args, **options): view = CommtrackConfig.get_db().view( 'commtrack/domain_config', reduce=False ) # preload domains domains_map = { d.name: d for d in Domain.get_all() } """ Default mode is to only copy types over to the domain objects. This is meant to be run after preindexing. Note this will run on both pre and post deploy instances to make sure there aren't any new changes the second time. """ def _process_fn(config): if 'location_types' in config: domain = domains_map[config['domain']].to_json() domain['location_types'] = config['location_types'] return True, domain return False, None migrate_data_command( view=view, fetch_class=CommtrackConfig, process_fn=_process_fn, save_class=Domain ) if 'post_deploy' in args: """ If user explicitly triggers post_deploy flag, then we will actually delete from the commtrack config objects. """ def _post_process_fn(config): if 'location_types' in config: del config['location_types'] return True, config else: return False, None migrate_data_command( view=view, fetch_class=CommtrackConfig, process_fn=_post_process_fn, )
def process_change(self, change): ledger = change.get_document() from corehq.apps.commtrack.models import CommtrackConfig commtrack_config = CommtrackConfig.for_domain(ledger['domain']) if commtrack_config and commtrack_config.use_auto_consumption: daily_consumption = _get_daily_consumption_for_ledger(ledger) ledger['daily_consumption'] = daily_consumption if not ledger.get('location_id') and ledger.get('case_id'): ledger['location_id'] = _location_id_for_case(ledger['case_id']) _update_ledger_section_entry_combinations(ledger)
def _create_commtrack_config_if_needed(domain): if CommtrackConfig.for_domain(domain): return CommtrackConfig( domain=domain, multiaction_enabled=True, multiaction_keyword='report', actions=[ CommtrackActionConfig( action='receipts', keyword='r', caption='Received', ), CommtrackActionConfig( action='consumption', keyword='c', caption='Consumed', ), CommtrackActionConfig( action='consumption', subaction='loss', keyword='l', caption='Losses', ), CommtrackActionConfig( action='stockonhand', keyword='soh', caption='Stock on hand', ), CommtrackActionConfig( action='stockout', keyword='so', caption='Stock-out', ), ], ).save()
def setUp(self): # might as well clean house before doing anything delete_all_xforms() delete_all_cases() StockReport.objects.all().delete() StockTransaction.objects.all().delete() self.backend = test.bootstrap(TEST_BACKEND, to_console=True) self.domain = bootstrap_domain() self.ct_settings = CommtrackConfig.for_domain(self.domain.name) self.ct_settings.consumption_config = ConsumptionConfig( min_transactions=0, min_window=0, optimal_window=60, min_periods=0, ) if self.requisitions_enabled: self.ct_settings.requisition_config = get_default_requisition_config( ) self.ct_settings.save() self.domain = Domain.get(self.domain._id) self.loc = make_loc('loc1') self.sp = make_supply_point(self.domain.name, self.loc) self.users = [ bootstrap_user(self, **user_def) for user_def in self.user_definitions ] if False: # bootstrap additional users for requisitions # needs to get reinserted for requisition stuff later self.approver = bootstrap_user(self, **APPROVER_USER) self.packer = bootstrap_user(self, **PACKER_USER) self.users += [self.approver, self.packer] # everyone should be in a group. self.group = Group(domain=TEST_DOMAIN, name='commtrack-folks', users=[u._id for u in self.users], case_sharing=True) self.group.save() self.sp.owner_id = self.group._id self.sp.save() self.products = sorted(Product.by_domain(self.domain.name), key=lambda p: p._id) self.assertEqual(3, len(self.products))
def bootstrap_commtrack_settings_if_necessary(domain, requisitions_enabled=False): if not(domain and domain.commtrack_enabled and not CommtrackConfig.for_domain(domain.name)): return c = CommtrackConfig( domain=domain.name, multiaction_enabled=True, multiaction_keyword='report', actions=[ CommtrackActionConfig( action='receipts', keyword='r', caption='Received', ), CommtrackActionConfig( action='consumption', keyword='c', caption='Consumed', ), CommtrackActionConfig( action='consumption', subaction='loss', keyword='l', caption='Losses', ), CommtrackActionConfig( action='stockonhand', keyword='soh', caption='Stock on hand', ), CommtrackActionConfig( action='stockout', keyword='so', caption='Stock-out', ), ], location_types=[ LocationType(name='state', allowed_parents=[''], administrative=True), LocationType(name='district', allowed_parents=['state'], administrative=True), LocationType(name='block', allowed_parents=['district'], administrative=True), LocationType(name='village', allowed_parents=['block'], administrative=True), LocationType(name='outlet', allowed_parents=['block', 'village']), ], supply_point_types=[], ) if requisitions_enabled: c.requisition_config = get_default_requisition_config() c.save() program = make_program(domain.name, 'Default', 'def') make_product(domain.name, 'Sample Product 1', 'pp', program.get_id) make_product(domain.name, 'Sample Product 2', 'pq', program.get_id) make_product(domain.name, 'Sample Product 3', 'pr', program.get_id) return c