<commit_before>from decimal import Decimal
import uuid
from xml.etree import ElementTree
from couchdbkit.exceptions import ResourceNotFound
from couchdbkit.ext.django.schema import *
from django.db import transaction
from django.utils.translation import ugettext as _
from casexml.apps.case.mock import CaseBlock
from casexml.apps.case.models import CommCareCase
from casexml.apps.stock import const as stockconst
from casexml.apps.stock.consumption import (ConsumptionConfiguration, compute_default_monthly_consumption,
    compute_consumption)
from casexml.apps.stock.models import StockReport as DbStockReport, StockTransaction as DbStockTransaction, DocDomainMapping
from casexml.apps.case.xml import V2
from corehq.apps.cachehq.mixins import CachedCouchDocumentMixin
from corehq.apps.commtrack import const
from corehq.apps.consumption.shortcuts import get_default_monthly_consumption
from corehq.apps.hqcase.utils import submit_case_blocks
from corehq.apps.users.models import CommCareUser
from casexml.apps.stock.utils import months_of_stock_remaining, state_stock_category
from corehq.apps.domain.models import Domain
from couchforms.signals import xform_archived, xform_unarchived
from dimagi.utils.couch.database import iter_docs
from dimagi.utils.couch.loosechange import map_reduce
from couchforms.models import XFormInstance
from dimagi.utils import parsing as dateparse
from datetime import datetime
from copy import copy
from django.dispatch import receiver
from corehq.apps.locations.signals import location_created, location_edited
from corehq.apps.locations.models import Location
from corehq.apps.commtrack.const import StockActions, RequisitionActions, RequisitionStatus, USER_LOCATION_OWNER_MAP_TYPE, DAYS_IN_MONTH
from corehq.apps.commtrack.xmlutil import XML
from corehq.apps.commtrack.exceptions import LinkedSupplyPointNotFoundError, InvalidProductException
from couchexport.models import register_column_type, ComplexExportColumn
from dimagi.utils.dates import force_to_datetime
from django.db import models
from django.db.models.signals import post_save, post_delete
from dimagi.utils.parsing import json_format_datetime

from dimagi.utils.decorators.memoized import memoized
import json_field

STOCK_ACTION_ORDER = [
    StockActions.RECEIPTS,
    StockActions.CONSUMPTION,
    StockActions.STOCKONHAND,
    StockActions.STOCKOUT,
]

REQUISITION_ACTION_TYPES = [
    # request a product
    RequisitionActions.REQUEST,

    # approve a requisition (it is allowed to be fulfilled)
    # this is optional and depends on app config
    RequisitionActions.APPROVAL,

    # fulfill a requisition (order is ready)
    RequisitionActions.FULFILL,

    # receive the sock (closes the requisition)
    # NOTE: it's not totally clear if this is necessary or
    # should be built into the regular receipt workflow.
    RequisitionActions.RECEIPTS,
]


class Program(Document):
    """
    A program, e.g. "hiv" or "tb"
    """
    domain = StringProperty()
    name = StringProperty()
    code = StringProperty()

    @classmethod
    def by_domain(cls, domain, wrap=True):
        """
        Gets all programs in a domain.
        """
        kwargs = dict(
            view_name='commtrack/programs',
            startkey=[domain],
            endkey=[domain, {}],
            include_docs=True
        )
        if wrap:
            return Program.view(**kwargs)
        else:
            return [row["doc"] for row in Program.view(wrap_doc=False, **kwargs)]

    @classmethod
    def get_by_code(cls, domain, code):
        result = cls.view("commtrack/program_by_code",
                          key=[domain, code],
                          include_docs=True,
                          limit=1).first()
        return result


class Product(Document):
    """
    A product, e.g. "coartem" or "tylenol"
    """
    domain = StringProperty()
    name = StringProperty()
    unit = StringProperty()
    code_ = StringProperty()  # todo: why the hell is this code_ and not code
    description = StringProperty()
    category = StringProperty()
    program_id = StringProperty()
    cost = DecimalProperty()
    product_data = DictProperty()
    is_archived = BooleanProperty(default=False)

    def save(self, *args, **kwargs):
        sql_product, _ = SQLProduct.objects.get_or_create(
            domain=self.domain,
            name=self.name
        )

        result = super(Product, self).save(*args, **kwargs)

        properties_to_sync = ['domain', 'name', 'is_archived']
        for prop in properties_to_sync:
            setattr(sql_product, prop, getattr(self, prop))

        sql_product.save()

        return result

    @property
    def code(self):
        return self.code_

    @code.setter
    def code(self, val):
        self.code_ = val.lower() if val else None

    @classmethod
    def get_by_code(cls, domain, code):
        if not code:
            return None
        result = cls.view("commtrack/product_by_code",
                          key=[domain, code.lower()],
                          include_docs=True).first()<commit_msg>Fix on save product syncing<commit_after>from decimal import Decimal
import uuid
from xml.etree import ElementTree
from couchdbkit.exceptions import ResourceNotFound
from couchdbkit.ext.django.schema import *
from django.db import transaction
from django.utils.translation import ugettext as _
from casexml.apps.case.mock import CaseBlock
from casexml.apps.case.models import CommCareCase
from casexml.apps.stock import const as stockconst
from casexml.apps.stock.consumption import (ConsumptionConfiguration, compute_default_monthly_consumption,
    compute_consumption)
from casexml.apps.stock.models import StockReport as DbStockReport, StockTransaction as DbStockTransaction, DocDomainMapping
from casexml.apps.case.xml import V2
from corehq.apps.cachehq.mixins import CachedCouchDocumentMixin
from corehq.apps.commtrack import const
from corehq.apps.consumption.shortcuts import get_default_monthly_consumption
from corehq.apps.hqcase.utils import submit_case_blocks
from corehq.apps.users.models import CommCareUser
from casexml.apps.stock.utils import months_of_stock_remaining, state_stock_category
from corehq.apps.domain.models import Domain
from couchforms.signals import xform_archived, xform_unarchived
from dimagi.utils.couch.database import iter_docs
from dimagi.utils.couch.loosechange import map_reduce
from couchforms.models import XFormInstance
from dimagi.utils import parsing as dateparse
from datetime import datetime
from copy import copy
from django.dispatch import receiver
from corehq.apps.locations.signals import location_created, location_edited
from corehq.apps.locations.models import Location
from corehq.apps.commtrack.const import StockActions, RequisitionActions, RequisitionStatus, USER_LOCATION_OWNER_MAP_TYPE, DAYS_IN_MONTH
from corehq.apps.commtrack.xmlutil import XML
from corehq.apps.commtrack.exceptions import LinkedSupplyPointNotFoundError, InvalidProductException
from couchexport.models import register_column_type, ComplexExportColumn
from dimagi.utils.dates import force_to_datetime
from django.db import models
from django.db.models.signals import post_save, post_delete
from dimagi.utils.parsing import json_format_datetime

from dimagi.utils.decorators.memoized import memoized
import json_field

STOCK_ACTION_ORDER = [
    StockActions.RECEIPTS,
    StockActions.CONSUMPTION,
    StockActions.STOCKONHAND,
    StockActions.STOCKOUT,
]

REQUISITION_ACTION_TYPES = [
    # request a product
    RequisitionActions.REQUEST,

    # approve a requisition (it is allowed to be fulfilled)
    # this is optional and depends on app config
    RequisitionActions.APPROVAL,

    # fulfill a requisition (order is ready)
    RequisitionActions.FULFILL,

    # receive the sock (closes the requisition)
    # NOTE: it's not totally clear if this is necessary or
    # should be built into the regular receipt workflow.
    RequisitionActions.RECEIPTS,
]


class Program(Document):
    """
    A program, e.g. "hiv" or "tb"
    """
    domain = StringProperty()
    name = StringProperty()
    code = StringProperty()

    @classmethod
    def by_domain(cls, domain, wrap=True):
        """
        Gets all programs in a domain.
        """
        kwargs = dict(
            view_name='commtrack/programs',
            startkey=[domain],
            endkey=[domain, {}],
            include_docs=True
        )
        if wrap:
            return Program.view(**kwargs)
        else:
            return [row["doc"] for row in Program.view(wrap_doc=False, **kwargs)]

    @classmethod
    def get_by_code(cls, domain, code):
        result = cls.view("commtrack/program_by_code",
                          key=[domain, code],
                          include_docs=True,
                          limit=1).first()
        return result


class Product(Document):
    """
    A product, e.g. "coartem" or "tylenol"
    """
    domain = StringProperty()
    name = StringProperty()
    unit = StringProperty()
    code_ = StringProperty()  # todo: why the hell is this code_ and not code
    description = StringProperty()
    category = StringProperty()
    program_id = StringProperty()
    cost = DecimalProperty()
    product_data = DictProperty()
    is_archived = BooleanProperty(default=False)

    def save(self, *args, **kwargs):
        properties_to_sync = [
            ('product_id', '_id'),
            'domain',
            'name',
            'is_archived',
            ('code', 'code_'),
            'description',
            'category',
            'program_id',
            'cost',
            ('units', 'unit'),
            'product_data',
        ]

        result = super(Product, self).save(*args, **kwargs)

        sql_product, _ = SQLProduct.objects.get_or_create(
            product_id=self._id
        )

        for prop in properties_to_sync:
            if isinstance(prop, tuple):
                sql_prop, couch_prop = prop
            else:
                sql_prop = couch_prop = prop

            if hasattr(self, couch_prop):
                setattr(sql_product, sql_prop, getattr(self, couch_prop))

        sql_product.save()

        return result

    @property
    def code(self):
        return self.code_

    @code.setter
    def code(self, val):
        self.code_ = val.lower() if val else None

    @classmethod
    def get_by_code(cls, domain, code):
        if not code:
            return None
        result = cls.view("commtrack/product_by_code",
                          key=[domain, code.lower()],
                          include_docs=True).first()
{'fraction_non_alphanumeric': 0.07769730034803876, 'fraction_numerical': 0.00037625811306556296, 'mean_word_length': 3.1466458658346332, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 20}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7034970', 'n_tokens_mistral': 3052, 'n_tokens_neox': 2974, 'n_words': 804}