def _test_subtype(self, initial, final):
        case_id = uuid.uuid4().hex
        CommCareCase(
            _id=case_id,
            domain='fakedomain',
        ).save()

        product_id = uuid.uuid4().hex
        SQLProduct(product_id=product_id, domain='fakedomain').save()
        report = StockReport.objects.create(
            form_id=uuid.uuid4().hex,
            date=ago(1),
            server_date=datetime.utcnow(),
            type=const.REPORT_TYPE_BALANCE
        )

        txn = StockTransaction(
            report=report,
            section_id=const.SECTION_TYPE_STOCK,
            type=const.TRANSACTION_TYPE_STOCKONHAND,
            subtype=initial,
            case_id=case_id,
            product_id=product_id,
            stock_on_hand=Decimal(10),
        )
        txn.save()

        saved = StockTransaction.objects.get(id=txn.id)
        self.assertEqual(final, saved.subtype)
Exemple #2
0
def _get_model_for_stock_transaction(report, transaction_helper, ledger_db):
    assert report.type in const.VALID_REPORT_TYPES
    txn = StockTransaction(
        report=report,
        case_id=transaction_helper.case_id,
        section_id=transaction_helper.section_id,
        product_id=transaction_helper.product_id,
        type=transaction_helper.action,
        subtype=transaction_helper.subaction,
    )

    def lazy_original_balance():
        return ledger_db.get_current_balance(_stock_transaction_to_unique_ledger_reference(txn))

    new_ledger_values = compute_ledger_values(
        lazy_original_balance, report.type,
        transaction_helper.relative_quantity)

    txn.stock_on_hand = new_ledger_values.balance
    txn.quantity = new_ledger_values.delta

    if report.domain:
        # set this as a shortcut for post save signal receivers
        txn.domain = report.domain

    # update the ledger DB in case later transactions reference the same ledger item
    ledger_db.set_current_balance(_stock_transaction_to_unique_ledger_reference(txn), txn.stock_on_hand)
    return txn
    def _test_subtype(self, initial, final):
        case_id = uuid.uuid4().hex
        CommCareCase(
            _id=case_id,
            domain='fakedomain',
        ).save()

        product_id = uuid.uuid4().hex
        SQLProduct(product_id=product_id, domain='fakedomain').save()
        report = StockReport.objects.create(form_id=uuid.uuid4().hex,
                                            date=ago(1),
                                            server_date=datetime.utcnow(),
                                            type=const.REPORT_TYPE_BALANCE)

        txn = StockTransaction(
            report=report,
            section_id=const.SECTION_TYPE_STOCK,
            type=const.TRANSACTION_TYPE_STOCKONHAND,
            subtype=initial,
            case_id=case_id,
            product_id=product_id,
            stock_on_hand=Decimal(10),
        )
        txn.save()

        saved = StockTransaction.objects.get(id=txn.id)
        self.assertEqual(final, saved.subtype)
Exemple #4
0
def _get_model_for_stock_transaction(report, transaction_helper, ledger_db):
    assert report.type in const.VALID_REPORT_TYPES
    txn = StockTransaction(
        report=report,
        case_id=transaction_helper.case_id,
        section_id=transaction_helper.section_id,
        product_id=transaction_helper.product_id,
        type=transaction_helper.action,
        subtype=transaction_helper.subaction,
    )

    def lazy_original_balance():
        return ledger_db.get_current_ledger_value(txn.ledger_reference)

    new_ledger_values = compute_ledger_values(
        lazy_original_balance, report.type,
        transaction_helper.relative_quantity)

    txn.stock_on_hand = new_ledger_values.balance
    txn.quantity = new_ledger_values.delta

    if report.domain:
        # set this as a shortcut for post save signal receivers
        txn.__domain = report.domain

    # update the ledger DB in case later transactions reference the same ledger item
    ledger_db.set_ledger(txn)
    return txn
Exemple #5
0
def _stock_report(case_id, product_id, amount, days_ago):
    report = StockReport.objects.create(form_id=uuid.uuid4().hex, date=ago(days_ago), type=const.REPORT_TYPE_BALANCE)
    txn = StockTransaction(
        report=report,
        section_id=const.SECTION_TYPE_STOCK,
        type=const.TRANSACTION_TYPE_STOCKONHAND,
        case_id=case_id,
        product_id=product_id,
        stock_on_hand=Decimal(amount),
    )
    txn._test_config = ConsumptionConfiguration.test_config()
    txn.quantity = 0
    txn.save()
Exemple #6
0
def _receipt_report(case_id, product_id, amount, days_ago):
    report = StockReport.objects.create(form_id=uuid.uuid4().hex, date=ago(days_ago), type=const.REPORT_TYPE_TRANSFER)
    txn = StockTransaction(
        report=report,
        section_id=const.SECTION_TYPE_STOCK,
        type=const.TRANSACTION_TYPE_RECEIPTS,
        case_id=case_id,
        product_id=product_id,
        quantity=amount,
    )
    previous_transaction = txn.get_previous_transaction()
    txn.stock_on_hand = (previous_transaction.stock_on_hand if previous_transaction else 0) + txn.quantity
    txn.save()
Exemple #7
0
 def _create_stock_state(self, product, consumption):
     xform = XFormInstance.get('test-xform')
     loc = Location.by_site_code(TEST_DOMAIN, 'garms')
     now = datetime.datetime.utcnow()
     report = StockReport(form_id=xform._id,
                          date=(now - datetime.timedelta(days=10)).replace(
                              second=0, microsecond=0),
                          type='balance',
                          domain=TEST_DOMAIN)
     report.save()
     stock_transaction = StockTransaction(
         case_id=loc.linked_supply_point().get_id,
         product_id=product.get_id,
         sql_product=SQLProduct.objects.get(product_id=product.get_id),
         section_id='stock',
         type='stockonhand',
         stock_on_hand=2 * consumption,
         report=report)
     stock_transaction.save()
     report = StockReport(form_id=xform._id,
                          date=now.replace(second=0, microsecond=0),
                          type='balance',
                          domain=TEST_DOMAIN)
     report.save()
     stock_transaction = StockTransaction(
         case_id=loc.linked_supply_point().get_id,
         product_id=product.get_id,
         sql_product=SQLProduct.objects.get(product_id=product.get_id),
         section_id='stock',
         type='stockonhand',
         stock_on_hand=consumption,
         report=report)
     stock_transaction.save()
    def create_transactions(self, domain=None):
        report = StockReport.objects.create(form_id=uuid.uuid4().hex,
                                            date=ago(2),
                                            type=const.REPORT_TYPE_BALANCE,
                                            domain=domain)

        txn = StockTransaction(
            report=report,
            section_id=const.SECTION_TYPE_STOCK,
            type=const.TRANSACTION_TYPE_STOCKONHAND,
            case_id=self.case_id,
            product_id=self.product_id,
            stock_on_hand=Decimal(10),
        )
        txn.save()

        report2 = StockReport.objects.create(form_id=uuid.uuid4().hex,
                                             date=ago(1),
                                             type=const.REPORT_TYPE_BALANCE,
                                             domain=domain)

        txn2 = StockTransaction(
            report=report2,
            section_id=const.SECTION_TYPE_STOCK,
            type=const.TRANSACTION_TYPE_STOCKONHAND,
            case_id=self.case_id,
            product_id=self.product_id,
            stock_on_hand=Decimal(30),
        )
        txn2.save()
Exemple #9
0
    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
Exemple #10
0
def plan_rebuild_stock_state(case_id, section_id, product_id):
    """
    planner for rebuild_stock_state

    yields actions for rebuild_stock_state to take,
    facilitating doing a dry run

    Warning: since some important things are still done through signals
    rather than here explicitly, there may be some effects that aren't
    represented in the plan. For example, inferred transaction creation
    will not be represented, nor will updates to the StockState object.

    """

    # these come out latest first, so reverse them below
    stock_transactions = (
        StockTransaction.get_ordered_transactions_for_stock(
            case_id=case_id, section_id=section_id, product_id=product_id).
        reverse()  # we want earliest transactions first
        .select_related('report'))
    balance = None
    for stock_transaction in stock_transactions:
        if stock_transaction.subtype == stockconst.TRANSACTION_SUBTYPE_INFERRED:
            yield _DeleteStockTransaction(stock_transaction)
        else:
            before = LedgerValues(balance=stock_transaction.stock_on_hand,
                                  delta=stock_transaction.quantity)
            after = _compute_ledger_values(balance, stock_transaction)
            # update balance for the next iteration
            balance = after.balance
            yield _SaveStockTransaction(stock_transaction, before, after)
Exemple #11
0
def get_current_ledger_state(case_ids, ensure_form_id=False):
    """
    Given a list of cases returns a dict of all current ledger data of the following format:
    {
        "case_id": {
            "section_id": {
                 "product_id": StockState,
                 "product_id": StockState,
                 ...
            },
            ...
        },
        ...
    }

    :param ensure_form_id:  Set to True to make sure return StockState
                            have the ``last_modified_form_id`` field populated
    """
    from corehq.apps.commtrack.models import StockState
    if not case_ids:
        return {}

    states = StockState.objects.filter(
        case_id__in=case_ids
    )
    ret = {case_id: {} for case_id in case_ids}
    for state in states:
        sections = ret[state.case_id].setdefault(state.section_id, {})
        sections[state.product_id] = state
        if ensure_form_id and not state.last_modified_form_id:
            transaction = StockTransaction.latest(state.case_id, state.section_id, state.product_id)
            if transaction is not None:
                state.last_modified_form_id = transaction.report.form_id
                state.save()
    return ret
Exemple #12
0
    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
Exemple #13
0
 def get_current_ledger_value(self, unique_ledger_reference):
     latest_txn = StockTransaction.latest(
         case_id=unique_ledger_reference.case_id,
         section_id=unique_ledger_reference.section_id,
         product_id=unique_ledger_reference.entry_id,
     )
     return latest_txn.stock_on_hand if latest_txn else 0
Exemple #14
0
    def get_data(self):
        sp_ids = get_relevant_supply_point_ids(
            self.domain,
            self.active_location
        )

        products = Product.by_domain(self.domain)
        if self.program_id:
            products = filter(
                lambda product: product.program_id == self.program_id, products
            )

        for sp_id in sp_ids:
            for product in products:
                loc = SupplyPointCase.get(sp_id).location
                last_transaction = StockTransaction.latest(
                    sp_id,
                    STOCK_SECTION_TYPE,
                    product._id
                )

                yield {
                    'loc_id': loc._id,
                    'loc_path': loc.path,
                    'name': loc.name,
                    'type': loc.location_type,
                    'reporting_status': reporting_status(
                        last_transaction,
                        self.start_date,
                        self.end_date
                    ),
                    'geo': loc._geopoint,
                }
Exemple #15
0
def get_current_ledger_state(case_ids, ensure_form_id=False):
    """
    Given a list of cases returns a dict of all current ledger data of the following format:
    {
        "case_id": {
            "section_id": {
                 "product_id": StockState,
                 "product_id": StockState,
                 ...
            },
            ...
        },
        ...
    }

    :param ensure_form_id:  Set to True to make sure return StockState
                            have the ``last_modified_form_id`` field populated
    """
    from corehq.apps.commtrack.models import StockState
    if not case_ids:
        return {}

    states = StockState.objects.filter(case_id__in=case_ids)
    ret = {case_id: {} for case_id in case_ids}
    for state in states:
        sections = ret[state.case_id].setdefault(state.section_id, {})
        sections[state.product_id] = state
        if ensure_form_id and not state.last_modified_form_id:
            transaction = StockTransaction.latest(state.case_id,
                                                  state.section_id,
                                                  state.product_id)
            if transaction is not None:
                state.last_modified_form_id = transaction.report.form_id
                state.save()
    return ret
Exemple #16
0
def get_stock_state_for_transaction(transaction):
    from corehq.apps.commtrack.models import StockState
    from corehq.apps.locations.models import SQLLocation
    from corehq.apps.products.models import SQLProduct

    # todo: in the worst case, this function makes
    # - three calls to couch (for the case, domain, and commtrack config)
    # - four postgres queries (transacitons, product, location, and state)
    # - one postgres write (to save the state)
    # and that doesn't even include the consumption calc, which can do a whole
    # bunch more work and hit the database.
    sql_product = SQLProduct.objects.get(product_id=transaction.product_id)
    try:
        domain_name = transaction.__domain
    except AttributeError:
        domain_name = sql_product.domain

    try:
        sql_location = SQLLocation.objects.get(supply_point_id=transaction.case_id)
    except SQLLocation.DoesNotExist:
        sql_location = None

    try:
        state = StockState.include_archived.get(
            section_id=transaction.section_id,
            case_id=transaction.case_id,
            product_id=transaction.product_id,
        )
    except StockState.DoesNotExist:
        state = StockState(
            section_id=transaction.section_id,
            case_id=transaction.case_id,
            product_id=transaction.product_id,
            sql_product=sql_product,
            sql_location=sql_location,
        )

    # we may not be saving the latest transaction so make sure we use that
    # todo: this should change to server date
    latest_transaction = StockTransaction.latest(
        case_id=transaction.case_id,
        section_id=transaction.section_id,
        product_id=transaction.product_id
    )
    if latest_transaction != transaction:
        logging.warning(
            'Just fired signal for a stale stock transaction. Domain: {}, instance: {},latest was {}'.format(
                domain_name, transaction, latest_transaction
            )
        )
        transaction = latest_transaction
    state.last_modified_date = transaction.report.server_date
    state.last_modified_form_id = transaction.report.form_id
    state.stock_on_hand = transaction.stock_on_hand

    # so you don't have to look it up again in the signal receivers
    if domain_name:
        state.__domain = domain_name
    return state
Exemple #17
0
def _create_model_for_stock_transaction(report, transaction_helper):
    assert report.type in stockconst.VALID_REPORT_TYPES
    txn = StockTransaction(
        report=report,
        case_id=transaction_helper.case_id,
        section_id=transaction_helper.section_id,
        product_id=transaction_helper.product_id,
        type=transaction_helper.action,
        subtype=transaction_helper.subaction,
    )

    def lazy_original_balance():
        previous_transaction = txn.get_previous_transaction()
        if previous_transaction:
            return previous_transaction.stock_on_hand
        else:
            return None

    new_ledger_values = compute_ledger_values(
        lazy_original_balance, report.type,
        transaction_helper.relative_quantity)

    txn.stock_on_hand = new_ledger_values.balance
    txn.quantity = new_ledger_values.delta

    if report.domain:
        # set this as a shortcut for post save signal receivers
        txn.domain = report.domain
    txn.save()
    return txn
Exemple #18
0
def get_couch_transactions(ref):
    return [
        ledger_transaction_json(tx) for tx in reversed(
            StockTransaction.get_ordered_transactions_for_stock(
                case_id=ref.case_id,
                section_id=ref.section_id,
                product_id=ref.entry_id,
            ).select_related("report"))
    ]
Exemple #19
0
 def _get_ledger(self, unique_ledger_reference):
     try:
         return StockTransaction.latest(
             case_id=unique_ledger_reference.case_id,
             section_id=unique_ledger_reference.section_id,
             product_id=unique_ledger_reference.entry_id,
         )
     except StockTransaction.DoesNotExist:
         return None
Exemple #20
0
 def _get_ledger(self, unique_ledger_reference):
     try:
         return StockTransaction.latest(
             case_id=unique_ledger_reference.case_id,
             section_id=unique_ledger_reference.section_id,
             product_id=unique_ledger_reference.entry_id,
         )
     except StockTransaction.DoesNotExist:
         return None
Exemple #21
0
    def check_stock_models(self, case, product_id, expected_soh, expected_qty, section_id):
        if not isinstance(expected_qty, Decimal):
            expected_qty = Decimal(str(expected_qty))
        if not isinstance(expected_soh, Decimal):
            expected_soh = Decimal(str(expected_soh))

        latest_trans = StockTransaction.latest(case._id, section_id, product_id)
        self.assertIsNotNone(latest_trans)
        self.assertEqual(section_id, latest_trans.section_id)
        self.assertEqual(expected_soh, latest_trans.stock_on_hand)
        self.assertEqual(expected_qty, latest_trans.quantity)
Exemple #22
0
def sync_stock_transactions_for_facility(domain, endpoint, facility, checkpoint,
                                         date, limit=1000, offset=0):
    """
    Syncs stock data from StockTransaction objects in ILSGateway to StockTransaction objects in HQ
    """
    has_next = True
    next_url = ""
    section_id = 'stock'
    supply_point = facility
    case = get_supply_point_case_in_domain_by_id(domain, supply_point)
    if not case:
        return
    location_id = case.location_id
    save_stock_data_checkpoint(checkpoint, 'stock_transaction', limit, offset, date, location_id, True)

    products_saved = set()
    while has_next:
        meta, stocktransactions = endpoint.get_stocktransactions(
            next_url_params=next_url,
            limit=limit,
            offset=offset,
            filters={
                'supply_point': supply_point,
                'date__gte': date,
                'date__lte': checkpoint.start_date
            }
        )
        # set the checkpoint right before the data we are about to process
        meta_limit = meta.get('limit') or limit
        meta_offset = meta.get('offset') or offset
        save_stock_data_checkpoint(
            checkpoint, 'stock_transaction', meta_limit, meta_offset, date, location_id, True
        )
        transactions_to_add = []
        with transaction.atomic():
            for stocktransaction in stocktransactions:
                transactions = sync_stock_transaction(stocktransaction, domain, case, bulk=True)
                transactions_to_add.extend(transactions)
                products_saved.update(map(lambda x: x.product_id, transactions))

        if transactions_to_add:
            # Doesn't send signal
            StockTransaction.objects.bulk_create(transactions_to_add)

        if not meta.get('next', False):
            has_next = False
        else:
            next_url = meta['next'].split('?')[1]
    for product in products_saved:
        # if we saved anything rebuild the stock state object by firing the signal
        # on the last transaction for each product
        last_st = StockTransaction.latest(case.get_id, section_id, product)
        update_stock_state_for_transaction(last_st)
Exemple #23
0
 def _create_stock_state(self, product, consumption):
     xform = XFormInstance.get('test-xform')
     loc = Location.by_site_code(TEST_DOMAIN, 'garms')
     now = datetime.datetime.utcnow()
     report = StockReport(
         form_id=xform._id,
         date=(now - datetime.timedelta(days=10)).replace(second=0, microsecond=0),
         type='balance',
         domain=TEST_DOMAIN
     )
     report.save()
     stock_transaction = StockTransaction(
         case_id=loc.linked_supply_point().get_id,
         product_id=product.get_id,
         sql_product=SQLProduct.objects.get(product_id=product.get_id),
         section_id='stock',
         type='stockonhand',
         stock_on_hand=2 * consumption,
         report=report
     )
     stock_transaction.save()
    def create_transactions(self, domain=None):
        report = StockReport.objects.create(
            form_id=uuid.uuid4().hex,
            date=ago(2),
            type=const.REPORT_TYPE_BALANCE,
            domain=domain
        )

        txn = StockTransaction(
            report=report,
            section_id=const.SECTION_TYPE_STOCK,
            type=const.TRANSACTION_TYPE_STOCKONHAND,
            case_id=self.case_id,
            product_id=self.product_id,
            stock_on_hand=Decimal(10),
        )
        txn.save()

        report2 = StockReport.objects.create(
            form_id=uuid.uuid4().hex,
            date=ago(1),
            type=const.REPORT_TYPE_BALANCE,
            domain=domain
        )

        txn2 = StockTransaction(
            report=report2,
            section_id=const.SECTION_TYPE_STOCK,
            type=const.TRANSACTION_TYPE_STOCKONHAND,
            case_id=self.case_id,
            product_id=self.product_id,
            stock_on_hand=Decimal(30),
        )
        txn2.save()
Exemple #25
0
 def create_models(self):
     # todo: this function should probably move to somewhere in casexml.apps.stock
     if self.tag not in stockconst.VALID_REPORT_TYPES:
         return
     report = DbStockReport.objects.create(form_id=self.form_id, date=self.timestamp, type=self.tag)
     for txn in self.transactions:
         db_txn = DbStockTransaction(
             report=report,
             case_id=txn.case_id,
             section_id=txn.section_id,
             product_id=txn.product_id,
         )
         previous_transaction = db_txn.get_previous_transaction()
         db_txn.type = txn.action
         db_txn.subtype = txn.subaction
         if self.tag == stockconst.REPORT_TYPE_BALANCE:
             db_txn.stock_on_hand = txn.quantity
             db_txn.quantity = 0
         else:
             assert self.tag == stockconst.REPORT_TYPE_TRANSFER
             db_txn.quantity = txn.relative_quantity
             db_txn.stock_on_hand = (previous_transaction.stock_on_hand if previous_transaction else 0) + db_txn.quantity
         db_txn.save()
Exemple #26
0
def create_stock_report(location, products_quantities, date=None):
    date = date or datetime.utcnow()
    sql_location = location.sql_location
    report = StockReport.objects.create(form_id='ews-reminders-test',
                                        domain=sql_location.domain,
                                        type='balance',
                                        date=date,
                                        server_date=date)
    for product_code, quantity in products_quantities.iteritems():
        StockTransaction(stock_on_hand=Decimal(quantity),
                         report=report,
                         type='stockonhand',
                         section_id='stock',
                         case_id=sql_location.supply_point_id,
                         product_id=SQLProduct.objects.get(
                             domain=sql_location.domain,
                             code=product_code).product_id).save()
Exemple #27
0
def recalculate_domain_consumption(domain):
    """
    Given a domain, recalculate all saved consumption settings in that domain.
    """
    # note: might get slow as this gets huge
    found_doc_ids = DocDomainMapping.objects.filter(
        domain_name=domain,
        doc_type='CommCareCase',
    ).values_list('doc_id', flat=True)
    products = Product.by_domain(domain)
    for supply_point_id in found_doc_ids:
        for product in products:
            filtered_transactions = StockTransaction.get_ordered_transactions_for_stock(
                supply_point_id, const.SECTION_TYPE_STOCK, product._id
            )
            if filtered_transactions:
                update_stock_state_for_transaction(filtered_transactions[0])
Exemple #28
0
def _stock_report(case_id, product_id, amount, days_ago):
    report = StockReport.objects.create(form_id=uuid.uuid4().hex, date=ago(days_ago),
                                        type=const.REPORT_TYPE_BALANCE)
    txn = StockTransaction(
        report=report,
        section_id=const.SECTION_TYPE_STOCK,
        type=const.TRANSACTION_TYPE_STOCKONHAND,
        case_id=case_id,
        product_id=product_id,
        stock_on_hand=Decimal(amount),
    )
    txn._test_config = ConsumptionConfiguration.test_config()
    txn.quantity = 0
    txn.save()
Exemple #29
0
def _receipt_report(case_id, product_id, amount, days_ago):
    report = StockReport.objects.create(form_id=uuid.uuid4().hex, date=ago(days_ago),
                                        type=const.REPORT_TYPE_TRANSFER)
    txn = StockTransaction(
        report=report,
        section_id=const.SECTION_TYPE_STOCK,
        type=const.TRANSACTION_TYPE_RECEIPTS,
        case_id=case_id,
        product_id=product_id,
        quantity=amount,
    )
    previous_transaction = txn.get_previous_transaction()
    txn.stock_on_hand = (previous_transaction.stock_on_hand if previous_transaction else 0) + txn.quantity
    txn.save()
Exemple #30
0
def get_current_ledger_transactions(case_id):
    """
    Given a case returns a dict of all current ledger data.
    {
        "section_id": {
             "product_id": StockTransaction,
             "product_id": StockTransaction,
             ...
        },
        ...
    }
    """
    from corehq.apps.commtrack.models import StockState
    results = StockState.objects.filter(case_id=case_id).values_list('case_id', 'section_id', 'product_id')

    ret = {}
    for case_id, section_id, product_id in results:
        sections = ret.setdefault(section_id, {})
        sections[product_id] = StockTransaction.latest(case_id, section_id, product_id)
    return ret
Exemple #31
0
def get_current_ledger_transactions(case_id):
    """
    Given a case returns a dict of all current ledger data.
    {
        "section_id": {
             "product_id": StockTransaction,
             "product_id": StockTransaction,
             ...
        },
        ...
    }
    """
    from corehq.apps.commtrack.models import StockState
    results = StockState.objects.filter(case_id=case_id).values_list(
        'case_id', 'section_id', 'product_id')

    ret = {}
    for case_id, section_id, product_id in results:
        sections = ret.setdefault(section_id, {})
        sections[product_id] = StockTransaction.latest(case_id, section_id,
                                                       product_id)
    return ret
Exemple #32
0
def recalculate_domain_consumption(domain):
    """
    Given a domain, recalculate all saved consumption settings in that domain.
    """
    # note: might get slow as this gets huge
    found_doc_ids = DocDomainMapping.objects.filter(
        domain_name=domain,
        doc_type='CommCareCase',
    ).values_list('doc_id', flat=True)
    products = Product.by_domain(domain)
    for supply_point_id in found_doc_ids:
        for product in products:
            try:
                latest_transaction = StockTransaction.get_ordered_transactions_for_stock(
                    supply_point_id, const.SECTION_TYPE_STOCK, product._id)[0]
            except IndexError:
                pass
            else:
                state = get_stock_state_for_transaction(latest_transaction)
                daily_consumption = get_consumption_for_ledger(state)
                state.daily_consumption = daily_consumption
                with drop_connected_signals(post_save):
                    state.save()
Exemple #33
0
def get_stock_state_json(sql_ledger):
    """Build stock state JSON from latest transaction

    Returns empty dict if stock transactions do not exist.
    """
    # similar to StockTransaction.latest(), but more efficient
    transactions = list(StockTransaction.get_ordered_transactions_for_stock(
        sql_ledger.case_id,
        sql_ledger.section_id,
        sql_ledger.product_id,
    ).select_related("report")[:1])
    if not transactions:
        return {}
    transaction = transactions[0]
    return StockState(
        case_id=sql_ledger.case_id,
        section_id=sql_ledger.section_id,
        product_id=sql_ledger.product_id,
        sql_location=SQLLocation.objects.get_or_None(supply_point_id=sql_ledger.case_id),
        last_modified_date=transaction.report.server_date,
        last_modified_form_id=transaction.report.form_id,
        stock_on_hand=transaction.stock_on_hand,
    ).to_json()
Exemple #34
0
def recalculate_domain_consumption(domain):
    """
    Given a domain, recalculate all saved consumption settings in that domain.
    """
    # note: might get slow as this gets huge
    found_doc_ids = DocDomainMapping.objects.filter(
        domain_name=domain,
        doc_type='CommCareCase',
    ).values_list('doc_id', flat=True)
    products = Product.by_domain(domain)
    for supply_point_id in found_doc_ids:
        for product in products:
            try:
                latest_transaction = StockTransaction.get_ordered_transactions_for_stock(
                    supply_point_id, const.SECTION_TYPE_STOCK, product._id
                )[0]
            except IndexError:
                pass
            else:
                state = get_stock_state_for_transaction(latest_transaction)
                daily_consumption = get_consumption_for_ledger(state)
                state.daily_consumption = daily_consumption
                with drop_connected_signals(post_save):
                    state.save()
Exemple #35
0
    section_id = 'stock'
    params = dict(
        form_id='logistics-xform',
        date=force_to_datetime(stocktransaction.date),
        type='balance',
        domain=domain
    )
    try:
        report, _ = StockReport.objects.get_or_create(**params)
    except StockReport.MultipleObjectsReturned:
        # legacy
        report = StockReport.objects.filter(**params)[0]

    sql_product = SQLProduct.objects.get(code=stocktransaction.product, domain=domain)
    if stocktransaction.report_type.lower() == 'stock received':
        transactions.append(StockTransaction(
            case_id=case.get_id,
            product_id=sql_product.product_id,
            sql_product=sql_product,
            section_id=section_id,
            type='receipts',
            stock_on_hand=Decimal(stocktransaction.ending_balance),
            quantity=Decimal(stocktransaction.quantity),
            report=report
        ))
    elif stocktransaction.report_type.lower() == 'stock on hand':
        if stocktransaction.quantity < 0 and bulk:
            transactions.append(StockTransaction(
                case_id=case.get_id,
                product_id=sql_product.product_id,
                sql_product=sql_product,
                section_id=section_id,
Exemple #36
0
 def _get_stats(self):
     stock_state = StockState.objects.get(**self._stock_state_key)
     latest_txn = StockTransaction.latest(**self._stock_state_key)
     all_txns = StockTransaction.get_ordered_transactions_for_stock(
         **self._stock_state_key)
     return stock_state, latest_txn, all_txns
Exemple #37
0
    def get_stock_payload(self, syncop):
        cases = [e.case for e in syncop.actual_cases_to_sync]
        from lxml.builder import ElementMaker
        E = ElementMaker(namespace=COMMTRACK_REPORT_XMLNS)

        def entry_xml(id, quantity):
            return E.entry(
                id=id,
                quantity=str(int(quantity)),
            )

        def transaction_to_xml(trans):
            return entry_xml(trans.product_id, trans.stock_on_hand)

        def consumption_entry(case_id, product_id, section_id):
            consumption_value = compute_consumption_or_default(
                case_id,
                product_id,
                datetime.utcnow(),
                section_id,
                self.stock_settings.consumption_config
            )
            if consumption_value is not None:
                return entry_xml(product_id, consumption_value)

        def _unique_products(stock_transaction_queryset):
            return sorted(stock_transaction_queryset.values_list('product_id', flat=True).distinct())

        for commtrack_case in cases:
            relevant_sections = sorted(StockTransaction.objects.filter(
                case_id=commtrack_case._id).values_list('section_id', flat=True).distinct())

            section_product_map = defaultdict(lambda: [])
            section_timestamp_map = defaultdict(lambda: json_format_datetime(datetime.utcnow()))
            for section_id in relevant_sections:
                relevant_reports = StockTransaction.objects.filter(case_id=commtrack_case._id, section_id=section_id)
                product_ids = _unique_products(relevant_reports)
                transactions = [StockTransaction.latest(commtrack_case._id, section_id, p) for p in product_ids]
                as_of = json_format_datetime(max(txn.report.date for txn in transactions))
                section_product_map[section_id] = product_ids
                section_timestamp_map[section_id] = as_of
                yield E.balance(*(transaction_to_xml(e) for e in transactions),
                                **{'entity-id': commtrack_case._id, 'date': as_of, 'section-id': section_id})


            for section_id, consumption_section_id in self.stock_settings.section_to_consumption_types.items():

                if (section_id in relevant_sections or
                    self.stock_settings.force_consumption_case_filter(commtrack_case)):

                    consumption_product_ids = self.stock_settings.default_product_list \
                        if self.stock_settings.default_product_list \
                        else section_product_map[section_id]

                    consumption_entries = filter(lambda e: e is not None, [
                        consumption_entry(commtrack_case._id, p, section_id)
                        for p in consumption_product_ids
                    ])

                    if consumption_entries:
                        yield E.balance(
                            *consumption_entries,
                            **{
                                'entity-id': commtrack_case._id,
                                'date': section_timestamp_map[section_id],
                                'section-id': consumption_section_id,
                            }
                        )
Exemple #38
0
def sync_stock_transactions_for_facility(domain, endpoint, facility, xform, checkpoint,
                                         date, limit=1000, offset=0):
    """
    Syncs stock data from StockTransaction objects in ILSGateway to StockTransaction objects in HQ
    """
    has_next = True
    next_url = ""
    section_id = 'stock'
    supply_point = facility
    case = get_supply_point_by_external_id(domain, supply_point)
    if not case:
        return

    save_stock_data_checkpoint(checkpoint, 'stock_transaction', limit, offset, date, facility, True)

    products_saved = set()
    while has_next:
        meta, stocktransactions = endpoint.get_stocktransactions(next_url_params=next_url,
                                                                 limit=limit,
                                                                 offset=offset,
                                                                 filters=(dict(supply_point=supply_point,
                                                                               date__gte=date,
                                                                               order_by='date')))

        # set the checkpoint right before the data we are about to process
        meta_limit = meta.get('limit') or limit
        meta_offset = meta.get('offset') or offset
        save_stock_data_checkpoint(checkpoint, 'stock_transaction', meta_limit, meta_offset, date, facility, True)
        transactions_to_add = []
        with transaction.commit_on_success():
            for stocktransaction in stocktransactions:
                params = dict(
                    form_id=xform._id,
                    date=force_to_datetime(stocktransaction.date),
                    type='balance',
                    domain=domain,
                )
                try:
                    report, _ = StockReport.objects.get_or_create(**params)
                except StockReport.MultipleObjectsReturned:
                    # legacy
                    report = StockReport.objects.filter(**params)[0]

                sql_product = SQLProduct.objects.get(code=stocktransaction.product, domain=domain)
                if stocktransaction.quantity != 0:
                    transactions_to_add.append(StockTransaction(
                        case_id=case._id,
                        product_id=sql_product.product_id,
                        sql_product=sql_product,
                        section_id=section_id,
                        type='receipts' if stocktransaction.quantity > 0 else 'consumption',
                        stock_on_hand=Decimal(stocktransaction.ending_balance),
                        quantity=Decimal(stocktransaction.quantity),
                        report=report
                    ))
                transactions_to_add.append(StockTransaction(
                    case_id=case._id,
                    product_id=sql_product.product_id,
                    sql_product=sql_product,
                    section_id=section_id,
                    type='stockonhand',
                    stock_on_hand=Decimal(stocktransaction.ending_balance),
                    report=report
                ))
                products_saved.add(sql_product.product_id)

        if transactions_to_add:
            # Doesn't send signal
            StockTransaction.objects.bulk_create(transactions_to_add)

        if not meta.get('next', False):
            has_next = False
        else:
            next_url = meta['next'].split('?')[1]

    for product in products_saved:
        # if we saved anything rebuild the stock state object by firing the signal
        # on the last transaction for each product
        last_st = StockTransaction.latest(case._id, section_id, product)
        update_stock_state_for_transaction(last_st)
Exemple #39
0
def create_models_for_stock_report(domain, stock_report_helper):
    """
    Save stock report and stock transaction models to the database.
    """
    assert stock_report_helper._form.domain == domain
    domain = domain
    if stock_report_helper.tag not in stockconst.VALID_REPORT_TYPES:
        return
    report = StockReport.objects.create(
        form_id=stock_report_helper.form_id,
        date=stock_report_helper.timestamp,
        type=stock_report_helper.tag,
        domain=domain,
    )
    for txn in stock_report_helper.transactions:
        db_txn = StockTransaction(
            report=report,
            case_id=txn.case_id,
            section_id=txn.section_id,
            product_id=txn.product_id,
        )
        if domain:
            # set this as a shortcut for post save signal receivers
            db_txn.domain = domain
        db_txn.type = txn.action
        db_txn.subtype = txn.subaction
        if stock_report_helper.tag == stockconst.REPORT_TYPE_BALANCE:
            db_txn.stock_on_hand = txn.quantity
            db_txn.quantity = 0
        else:
            assert stock_report_helper.tag == stockconst.REPORT_TYPE_TRANSFER
            previous_transaction = db_txn.get_previous_transaction()
            db_txn.quantity = txn.relative_quantity
            db_txn.stock_on_hand = db_txn.quantity + (
                previous_transaction.stock_on_hand
                if previous_transaction else 0
            )
        db_txn.save()
Exemple #40
0
 def create_models(self, domain=None):
     """
     Save stock report and stock transaction models to the database.
     """
     # todo: this function should probably move to somewhere in casexml.apps.stock
     if self.tag not in stockconst.VALID_REPORT_TYPES:
         return
     report = StockReport.objects.create(
         form_id=self.form_id,
         date=self.timestamp,
         type=self.tag,
         domain=self._form.domain,
     )
     for txn in self.transactions:
         db_txn = StockTransaction(
             report=report,
             case_id=txn.case_id,
             section_id=txn.section_id,
             product_id=txn.product_id,
         )
         if domain:
             # set this as a shortcut for post save signal receivers
             db_txn.domain = domain
         db_txn.type = txn.action
         db_txn.subtype = txn.subaction
         if self.tag == stockconst.REPORT_TYPE_BALANCE:
             db_txn.stock_on_hand = txn.quantity
             db_txn.quantity = 0
         else:
             assert self.tag == stockconst.REPORT_TYPE_TRANSFER
             previous_transaction = db_txn.get_previous_transaction()
             db_txn.quantity = txn.relative_quantity
             db_txn.stock_on_hand = (previous_transaction.stock_on_hand if previous_transaction else 0) + db_txn.quantity
         db_txn.save()
Exemple #41
0
 def get_latest_transaction(case_id, section_id, entry_id):
     return StockTransaction.latest(case_id, section_id, entry_id)
Exemple #42
0
 def get_latest_transaction(case_id, section_id, entry_id):
     return StockTransaction.latest(case_id, section_id, entry_id)
Exemple #43
0
 def _get_stats(self):
     stock_state = StockState.objects.get(**self._stock_state_key)
     latest_txn = StockTransaction.latest(**self._stock_state_key)
     all_txns = StockTransaction.get_ordered_transactions_for_stock(
         **self._stock_state_key)
     return stock_state, latest_txn, all_txns
Exemple #44
0
 def check_stock_models(self, case, product_id, expected_soh, expected_qty, section_id):
     latest_trans = StockTransaction.latest(case._id, section_id, product_id)
     self.assertEqual(section_id, latest_trans.section_id)
     self.assertEqual(expected_soh, latest_trans.stock_on_hand)
     self.assertEqual(expected_qty, latest_trans.quantity)
Exemple #45
0
def get_couch_transactions(case_id, section_id, product_id):
    return list(reversed(
        StockTransaction.get_ordered_transactions_for_stock(
            case_id=case_id, section_id=section_id, product_id=product_id)
        .select_related("report")
    ))
Exemple #46
0
    def get_stock_payload(self, syncop):
        cases = [e.case for e in syncop.actual_cases_to_sync]
        from lxml.builder import ElementMaker
        E = ElementMaker(namespace=COMMTRACK_REPORT_XMLNS)

        def entry_xml(id, quantity):
            return E.entry(
                id=id,
                quantity=str(int(quantity)),
            )

        def transaction_to_xml(trans):
            return entry_xml(trans.product_id, trans.stock_on_hand)

        def consumption_entry(case_id, product_id, section_id):
            consumption_value = compute_consumption_or_default(
                case_id,
                product_id,
                datetime.utcnow(),
                section_id,
                self.stock_settings.consumption_config
            )
            if consumption_value is not None:
                return entry_xml(product_id, consumption_value)

        def _unique_products(stock_transaction_queryset):
            return sorted(stock_transaction_queryset.values_list('product_id', flat=True).distinct())

        for commtrack_case in cases:
            relevant_sections = sorted(StockTransaction.objects.filter(
                case_id=commtrack_case._id).values_list('section_id', flat=True).distinct())

            section_product_map = defaultdict(lambda: [])
            section_timestamp_map = defaultdict(lambda: json_format_datetime(datetime.utcnow()))
            for section_id in relevant_sections:
                relevant_reports = StockTransaction.objects.filter(case_id=commtrack_case._id, section_id=section_id)
                product_ids = _unique_products(relevant_reports)
                transactions = [StockTransaction.latest(commtrack_case._id, section_id, p) for p in product_ids]
                as_of = json_format_datetime(max(txn.report.date for txn in transactions))
                section_product_map[section_id] = product_ids
                section_timestamp_map[section_id] = as_of
                yield E.balance(*(transaction_to_xml(e) for e in transactions),
                                **{'entity-id': commtrack_case._id, 'date': as_of, 'section-id': section_id})


            for section_id, consumption_section_id in self.stock_settings.section_to_consumption_types.items():

                if (section_id in relevant_sections or
                    self.stock_settings.force_consumption_case_filter(commtrack_case)):

                    consumption_product_ids = self.stock_settings.default_product_list \
                        if self.stock_settings.default_product_list \
                        else section_product_map[section_id]

                    yield E.balance(
                        *filter(lambda e: e is not None,
                                [consumption_entry(commtrack_case._id, p, section_id)
                                 for p in consumption_product_ids]),
                        **{'entity-id': commtrack_case._id, 'date': section_timestamp_map[section_id],
                           'section-id': consumption_section_id}
                    )