def handle(self): location = self.user.location domain = self.domain_object location_id = self.location_id if not location_id: return False if location.location_type_name == 'FACILITY': try: data = self.data if not data: return True if not data.get('transactions'): self.on_error(data) return True process(domain.name, data) if not data['errors']: self.on_success() else: self.on_error(data) return True self.respond(self.get_message(data)) except NotAUserClassError: return True except Exception, e: # todo: should we only trap SMSErrors? if settings.UNIT_TESTING or settings.DEBUG: raise send_sms_to_verified_number( self.verified_contact, 'problem with stock report: %s' % str(e))
def handle(self): domain = Domain.get_by_name(self.domain) split_text = self.msg.text.split(' ', 1) if split_text[0].lower() == 'soh': text = split_text[1] elif split_text[0].startswith('soh'): text = split_text[0][3:] else: text = self.msg.text if not domain.commtrack_enabled: return False if not self.sql_location: self.respond(NO_SUPPLY_POINT_MESSAGE) return True try: parser = self.parser formatted_text = EWSFormatter().format(text) data = parser.parse(formatted_text) if not data: return False if EWS_INVALID_REPORT_RESPONSE.enabled(self.domain): filtered_transactions = self.get_valid_reports(data) if not filtered_transactions: return True data['transactions'] = filtered_transactions except NotAUserClassError: return False except (SMSError, NoDefaultLocationException): self.respond(six.text_type(INVALID_MESSAGE)) return True except ProductCodeException as e: self.respond(six.text_type(e)) return True except Exception as e: if settings.UNIT_TESTING or settings.DEBUG: raise self.respond('problem with stock report: %s' % str(e)) return True stockouts = set() if self.sql_location.location_type.name in ['Regional Medical Store', 'Central Medical Store']: stockouts = set(StockState.objects.filter( case_id=self.sql_location.supply_point_id, stock_on_hand=0 ).values_list('sql_product__name', flat=True)) process(domain.name, data) transactions = data['transactions'] if not self.async_response: self.send_messages(parser, stockouts, transactions) else: send_soh_messages_task.delay(self, parser, stockouts, transactions) return True
def handle(self): location = self.user.location domain = self.domain_object location_id = self.location_id if not location_id: return False if location.location_type_name == 'FACILITY': try: data = self.data if not data: return True if not data.get('transactions'): self.on_error(data) return True process(domain.name, data) if not data['errors']: self.on_success() else: self.on_error(data) return True self.respond(self.get_message(data)) except NotAUserClassError: return True except Exception, e: # todo: should we only trap SMSErrors? if settings.UNIT_TESTING or settings.DEBUG: raise send_sms_to_verified_number(self.verified_contact, 'problem with stock report: %s' % str(e))
def handle(self): verified_contact = self.verified_contact domain = Domain.get_by_name(verified_contact.domain) text = self.msg.text try: data = StockReportParser(domain, verified_contact).parse(text) if not data: return False except NotAUserClassError: return False except Exception as e: if settings.UNIT_TESTING or settings.DEBUG: raise send_sms_to_verified_number( verified_contact, 'problem with stock report: %s' % str(e)) return True transactions = data['transactions'] products = [ SQLProduct.objects.get(product_id=transaction.product_id).code for transaction in transactions ] process(domain.name, data) send_sms_to_verified_number( verified_contact, RECEIPT_CONFIRM % {'products': ' '.join(products)}) return True
def handle(self): domain = Domain.get_by_name(self.domain) split_text = self.msg.text.split(' ', 1) if split_text[0].lower() == 'soh': text = split_text[1] elif split_text[0].startswith('soh'): text = split_text[0][3:] else: text = self.msg.text if not domain.commtrack_enabled: return False if not self.sql_location: self.respond(NO_SUPPLY_POINT_MESSAGE) return True try: parser = self.parser formatted_text = EWSFormatter().format(text) data = parser.parse(formatted_text) if not data: return False except NotAUserClassError: return False except (SMSError, NoDefaultLocationException): self.respond(str(INVALID_MESSAGE)) return True except ProductCodeException as e: self.respond(str(e)) return True except Exception as e: if settings.UNIT_TESTING or settings.DEBUG: raise self.respond('problem with stock report: %s' % str(e)) return True stockouts = set() if self.sql_location.location_type.name in [ 'Regional Medical Store', 'Central Medical Store' ]: stockouts = set( StockState.objects.filter( case_id=self.sql_location.supply_point_id, stock_on_hand=0).values_list('sql_product__name', flat=True)) process(domain.name, data) transactions = data['transactions'] if not self.async_response: self.send_messages(parser, stockouts, transactions) else: send_soh_messages_task.delay(self, parser, stockouts, transactions) return True
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)
def fake_sms(user, text): """ Fake a commtrack SMS submission for a user. `text` might be "soh myproduct 100" Don't use this with a real user """ if not user.phone_number: raise ValueError("User does not have a phone number") if not user.get_verified_number(): user.save_verified_number(user.domain, user.phone_number, True, None) domain_obj = Domain.get_by_name(user.domain) parser = StockReportParser(domain_obj, user.get_verified_number()) process(user.domain, parser.parse(text.lower()))
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)
def import_row(row, data_cols, domain, make_tx): """process (import) a single stock report row""" def get_data(): for header, meta in data_cols.iteritems(): val = row[header] if val is not None and val != '': yield make_tx(domain=domain, action_name=meta['action'], product=meta['product'], value=int(val)) report = { 'location': row['loc'], 'timestamp': row['timestamp'], 'user': row['user'], 'phone': row.get('phone'), 'transactions': list(get_data()), } sms.process(domain, report)
def handle(self): verified_contact = self.verified_contact domain = Domain.get_by_name(verified_contact.domain) text = self.msg.text try: data = StockReportParser(domain, verified_contact).parse(text) if not data: return False except NotAUserClassError: return False except Exception as e: if settings.UNIT_TESTING or settings.DEBUG: raise send_sms_to_verified_number(verified_contact, 'problem with stock report: %s' % str(e)) return True transactions = data['transactions'] products = [SQLProduct.objects.get(product_id=transaction.product_id).code for transaction in transactions] process(domain.name, data) send_sms_to_verified_number(verified_contact, RECEIPT_CONFIRM % {'products': ' '.join(products)}) return True
class SOHHandler(KeywordHandler): async_response = False def get_valid_reports(self, data): filtered_transactions = [] excluded_products = [] for product_id, transactions in get_transactions_by_product( data['transactions']).iteritems(): begin_soh = None end_soh = None receipt = 0 for transaction in transactions: if begin_soh is None: sql_location = SQLLocation.objects.get( location_id=transaction.location_id) latest = StockTransaction.latest( sql_location.supply_point_id, SECTION_TYPE_STOCK, transaction.product_id) begin_soh = 0 if latest: begin_soh = float(latest.stock_on_hand) if transaction.action == 'receipts': receipt += float(transaction.quantity) elif not end_soh: end_soh = float(transaction.quantity) if end_soh > begin_soh + receipt: excluded_products.append(transaction.product_id) else: filtered_transactions.append(transaction) if excluded_products: message = ERROR_MESSAGE.format(products_list=', '.join([ SQLProduct.objects.get(product_id=product_id).code for product_id in set(excluded_products) ])) self.respond(message) return filtered_transactions def send_errors(self, transactions, bad_codes): report_helper = ProductsReportHelper(self.user.sql_location, transactions) kwargs = {} if report_helper.reported_products(): kwargs['stocks'] = ", ".join([ product.code for product in report_helper.reported_products().order_by('code') ]) error_message = 'You reported: {stocks}, but there were errors: {err}' else: error_message = '{err}' missing = report_helper.missing_products() if missing: kwargs['missing'] = ", ".join( [product.code for product in missing]) error_message += " Please report {missing}" bad_codes = ', '.join(bad_codes) if bad_codes: kwargs[ 'err'] = 'Unrecognized commodity codes: {bad_codes}.'.format( bad_codes=bad_codes) self.respond('{} {}'.format(error_message.format(**kwargs), unicode(ASSISTANCE_MESSAGE))) def send_ms_alert(self, previous_stockouts, transactions, ms_type): stockouts = { SQLProduct.objects.get(product_id=transaction.product_id).name for transaction in transactions if transaction.quantity == 0 and transaction.action == 'stockonhand' } with_stock = { SQLProduct.objects.get(product_id=transaction.product_id).name for transaction in transactions if transaction.quantity != 0 and transaction.action == 'stockonhand' } resolved_stockouts = previous_stockouts.intersection(with_stock) locations = self.sql_location.parent.get_descendants(include_self=True)\ .filter(location_type__administrative=True) for sql_location in locations: for user in get_all_users_by_location(self.domain, sql_location.location_id): phone_number = get_preferred_phone_number_for_recipient(user) if not phone_number: continue stockouts_and_resolved = [(MS_RESOLVED_STOCKOUTS, resolved_stockouts), (MS_STOCKOUT, stockouts)] for message, data in stockouts_and_resolved: if data: message = message % { 'products_names': ', '.join(data), 'ms_type': ms_type } send_sms(self.domain, user, phone_number, message) def send_message_to_admins(self, message): in_charge_users = map( CommCareUser.wrap, iter_docs(CommCareUser.get_db(), [ in_charge.user_id for in_charge in self.sql_location.facilityincharge_set.all() ])) for in_charge_user in in_charge_users: phone_number = get_preferred_phone_number_for_recipient( in_charge_user) if not phone_number: continue send_sms( self.sql_location.domain, in_charge_user, phone_number, message % { 'name': in_charge_user.full_name, 'location': self.sql_location.name }) @property def parser(self): parser = EWSStockAndReceiptParser(self.domain_object, self.verified_contact) return parser def send_messages(self, parser, stockouts, transactions): if not parser.bad_codes: if self.sql_location.location_type.name == 'Regional Medical Store': self.send_ms_alert(stockouts, transactions, 'RMS') elif self.sql_location.location_type.name == 'Central Medical Store': self.send_ms_alert(stockouts, transactions, 'CMS') message, super_message = SOHAlerts( self.user, self.sql_location).get_alerts(transactions) if super_message: self.send_message_to_admins(super_message) self.respond(message) else: self.send_errors(transactions, parser.bad_codes) def handle(self): domain = Domain.get_by_name(self.domain) split_text = self.msg.text.split(' ', 1) if split_text[0].lower() == 'soh': text = split_text[1] elif split_text[0].startswith('soh'): text = split_text[0][3:] else: text = self.msg.text if not domain.commtrack_enabled: return False if not self.sql_location: self.respond(NO_SUPPLY_POINT_MESSAGE) return True try: parser = self.parser formatted_text = EWSFormatter().format(text) data = parser.parse(formatted_text) if not data: return False if EWS_INVALID_REPORT_RESPONSE.enabled(self.domain): filtered_transactions = self.get_valid_reports(data) if not filtered_transactions: return True data['transactions'] = filtered_transactions except NotAUserClassError: return False except (SMSError, NoDefaultLocationException): self.respond(unicode(INVALID_MESSAGE)) return True except ProductCodeException as e: self.respond(e.message) return True except Exception, e: # todo: should we only trap SMSErrors? if settings.UNIT_TESTING or settings.DEBUG: raise self.respond('problem with stock report: %s' % str(e)) return True stockouts = set() if self.sql_location.location_type.name in [ 'Regional Medical Store', 'Central Medical Store' ]: stockouts = set( StockState.objects.filter( case_id=self.sql_location.supply_point_id, stock_on_hand=0).values_list('sql_product__name', flat=True)) process(domain.name, data) transactions = data['transactions'] if not self.async_response: self.send_messages(parser, stockouts, transactions) else: send_soh_messages_task.delay(self, parser, stockouts, transactions) return True