Example #1
0
class Sale:
    __name__ = 'sale.sale'

    channel = fields.Many2One(
        'sale.channel', 'Channel', required=True, domain=[
            ('id', 'in', Eval('context', {}).get('allowed_read_channels', [])),
        ],
        states={
            'readonly': Or(
                (Eval('id', default=0) > 0),
                Bool(Eval('lines', default=[])),
            )
        }, depends=['id']
    )

    channel_type = fields.Function(
        fields.Char('Channel Type'), 'on_change_with_channel_type'
    )

    has_channel_exception = fields.Function(
        fields.Boolean('Has Channel Exception ?'), 'get_has_channel_exception',
        searcher='search_has_channel_exception'
    )

    exceptions = fields.Function(
        fields.One2Many("channel.exception", None, "Exceptions"),
        'get_channel_exceptions', setter='set_channel_exceptions'
    )

    @classmethod
    def search_has_channel_exception(cls, name, clause):
        """
        Returns domain for sale with exceptions
        """
        ChannelException = Pool().get('channel.exception')
        Sale = Pool().get('sale.sale')

        cursor = Transaction().cursor
        sale_with_exceptions = []

        # TODO: Convert this expression to python sql
        cursor.execute(
            "SELECT DISTINCT(sale.id) "
            "FROM %s as exception "
            "JOIN %s as sale on "
            "(exception.origin = 'sale.sale,'||sale.id) "
            "WHERE is_resolved is %s",
            (AsIs(ChannelException._table), AsIs(Sale._table), False)
        )

        sale_with_exceptions = map(lambda t: t[0], cursor.fetchall())

        return clause[2] and [('id', 'in', sale_with_exceptions)] or \
            [('id', 'not in', sale_with_exceptions)]

    def get_channel_exceptions(self, name=None):
        ChannelException = Pool().get('channel.exception')

        return map(
            int, ChannelException.search([
                ('origin', '=', '%s,%s' % (self.__name__, self.id)),
                ('channel', '=', self.channel.id),
            ], order=[('is_resolved', 'desc')])
        )

    @classmethod
    def set_channel_exceptions(cls, exceptions, name, value):
        pass

    def get_has_channel_exception(self, name):
        """
        Returs True if sale has exception
        """
        ChannelException = Pool().get('channel.exception')

        return bool(
            ChannelException.search([
                ('origin', '=', '%s,%s' % (self.__name__, self.id)),
                ('channel', '=', self.channel.id),
                ('is_resolved', '=', False)
            ])
        )

    @classmethod
    def __setup__(cls):
        super(Sale, cls).__setup__()

        cls._error_messages.update({
            'channel_missing': (
                'Go to user preferences and select a current_channel ("%s")'
            ),
            'channel_change_not_allowed': (
                'Cannot change channel'
            ),
            'not_create_channel': (
                'You cannot create order under this channel because you do not '
                'have required permissions'
            ),
        })

    @classmethod
    def default_channel(cls):
        User = Pool().get('res.user')

        user = User(Transaction().user)
        channel_id = Transaction().context.get('current_channel')

        if channel_id:
            return channel_id
        return user.current_channel and \
            user.current_channel.id  # pragma: nocover

    @staticmethod
    def default_company():
        Sale = Pool().get('sale.sale')
        Channel = Pool().get('sale.channel')

        channel_id = Sale.default_channel()
        if channel_id:
            return Channel(channel_id).company.id

        return Transaction().context.get('company')  # pragma: nocover

    @staticmethod
    def default_invoice_method():
        Sale = Pool().get('sale.sale')
        Channel = Pool().get('sale.channel')
        Config = Pool().get('sale.configuration')

        channel_id = Sale.default_channel()
        if not channel_id:  # pragma: nocover
            config = Config(1)
            return config.sale_invoice_method

        return Channel(channel_id).invoice_method

    @staticmethod
    def default_shipment_method():
        Sale = Pool().get('sale.sale')
        Channel = Pool().get('sale.channel')
        Config = Pool().get('sale.configuration')

        channel_id = Sale.default_channel()
        if not channel_id:  # pragma: nocover
            config = Config(1)
            return config.sale_invoice_method

        return Channel(channel_id).shipment_method

    @staticmethod
    def default_warehouse():
        Sale = Pool().get('sale.sale')
        Channel = Pool().get('sale.channel')
        Location = Pool().get('stock.location')

        channel_id = Sale.default_channel()
        if not channel_id:  # pragma: nocover
            return Location.search([('type', '=', 'warehouse')], limit=1)[0].id
        else:
            return Channel(channel_id).warehouse.id

    @staticmethod
    def default_price_list():
        Sale = Pool().get('sale.sale')
        Channel = Pool().get('sale.channel')

        channel_id = Sale.default_channel()
        if channel_id:
            return Channel(channel_id).price_list.id
        return None  # pragma: nocover

    @staticmethod
    def default_payment_term():
        Sale = Pool().get('sale.sale')
        Channel = Pool().get('sale.channel')

        channel_id = Sale.default_channel()
        if channel_id:
            return Channel(channel_id).payment_term.id
        return None  # pragma: nocover

    @fields.depends('channel', 'party')
    def on_change_channel(self):
        if not self.channel:
            return {}  # pragma: nocover
        res = {}
        for fname in ('company', 'warehouse', 'currency', 'payment_term'):
            fvalue = getattr(self.channel, fname)
            if fvalue:
                res[fname] = fvalue.id
        if (not self.party or not self.party.sale_price_list):
            res['price_list'] = self.channel.price_list.id  # pragma: nocover
        if self.channel.invoice_method:
            res['invoice_method'] = self.channel.invoice_method
        if self.channel.shipment_method:
            res['shipment_method'] = self.channel.shipment_method

        # Update AR record
        for key, value in res.iteritems():
            if '.' not in key:
                setattr(self, key, value)
        return res

    @fields.depends('channel')
    def on_change_party(self):  # pragma: nocover
        res = super(Sale, self).on_change_party()
        channel = self.channel

        if channel:
            if not res.get('price_list') and res.get('invoice_address'):
                res['price_list'] = channel.price_list.id
                res['price_list.rec_name'] = channel.price_list.rec_name
            if not res.get('payment_term') and res.get('invoice_address'):
                res['payment_term'] = channel.payment_term.id
                res['payment_term.rec_name'] = \
                    self.channel.payment_term.rec_name

        # Update AR record
        for key, value in res.iteritems():
            setattr(self, key, value)
        return res

    @fields.depends('channel')
    def on_change_with_channel_type(self, name=None):
        """
        Returns the source of the channel
        """
        if self.channel:
            return self.channel.source

    def check_create_access(self, silent=False):
        """
            Check sale creation in channel
        """
        User = Pool().get('res.user')
        user = User(Transaction().user)

        if user.id == 0:
            return  # pragma: nocover

        if self.channel not in user.allowed_create_channels:
            if silent:
                return False
            self.raise_user_error('not_create_channel')
        return True

    @classmethod
    def write(cls, sales, values, *args):
        """
        Check if channel in sale is is user's create_channel
        """
        if 'channel' in values:
            # Channel cannot be changed at any cost.
            cls.raise_user_error('channel_change_not_allowed')

        super(Sale, cls).write(sales, values, *args)

    @classmethod
    def create(cls, vlist):
        """
        Check if user is allowed to create sale in channel
        """
        User = Pool().get('res.user')
        user = User(Transaction().user)

        for values in vlist:
            if 'channel' not in values and not cls.default_channel():
                cls.raise_user_error(
                    'channel_missing', (user.rec_name,)
                )  # pragma: nocover

        sales = super(Sale, cls).create(vlist)
        for sale in sales:
            sale.check_create_access()
        return sales

    @classmethod
    def copy(cls, sales, default=None):
        """
        Duplicating records
        """
        if default is None:
            default = {}

        for sale in sales:
            if not sale.check_create_access(True):
                default['channel'] = cls.default_channel()

        return super(Sale, cls).copy(sales, default=default)
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from sql import Null
from trytond.model import fields, Check
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval

from .move import StockMixin

__all__ = ['Lot']

STATES_ON_CREATE = {
    'readonly': Eval('id', -1) > 0,
    'invisible': ~Bool(Eval('package'))
}
DEPENDS_ON_CREATE = ['id', 'package']

STATES_REQUIRED = STATES_ON_CREATE.copy()
STATES_REQUIRED['required'] = Eval('package_required', False)
DEPENDS_REQUIRED = DEPENDS_ON_CREATE + ['package_required']


class Lot(StockMixin, metaclass=PoolMeta):
    __name__ = 'stock.lot'

    package = fields.Many2One('product.pack',
                              'Packaging',
                              domain=[
                                  ('product.products', 'in', [Eval('product')
Example #3
0
class StatementLine(metaclass=PoolMeta):
    __name__ = 'account.statement.line'
    payment = fields.Many2One(
        'account.payment',
        'Payment',
        domain=[
            If(Bool(Eval('party')), [('party', '=', Eval('party'))], []),
            ('state', 'in', ['processing', 'succeeded', 'failed']),
        ],
        states={
            'invisible': Bool(Eval('payment_group')) | Bool(Eval('invoice')),
            'readonly': Eval('statement_state') != 'draft',
        },
        depends=['party', 'statement_state'])
    payment_group = fields.Many2One('account.payment.group',
                                    "Payment Group",
                                    domain=[
                                        ('company', '=', Eval('company', -1)),
                                    ],
                                    states={
                                        'invisible':
                                        Bool(Eval('payment'))
                                        | Bool(Eval('invoice')),
                                        'readonly':
                                        Eval('statement_state') != 'draft',
                                    },
                                    depends=['company', 'statement_state'])

    @classmethod
    def __setup__(cls):
        super(StatementLine, cls).__setup__()
        invoice_invisible = Bool(Eval('payment')) | Bool(Eval('payment_group'))
        if 'invisible' in cls.invoice.states:
            cls.invoice.states['invisible'] |= invoice_invisible
        else:
            cls.invoice.states['invisible'] = invoice_invisible

    @classmethod
    def copy(cls, lines, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('payment', None)
        default.setdefault('payment_group', None)
        return super(StatementLine, cls).copy(lines, default=default)

    @fields.depends('payment', 'party', 'account', 'statement',
                    '_parent_statement.journal')
    def on_change_payment(self):
        pool = Pool()
        Currency = pool.get('currency.currency')
        if self.payment:
            if not self.party:
                self.party = self.payment.party
            clearing_account = self.payment.journal.clearing_account
            if not self.account and clearing_account:
                self.account = clearing_account
            if self.statement and self.statement.journal:
                with Transaction().set_context(date=self.payment.date):
                    amount = Currency.compute(self.payment.currency,
                                              self.payment.amount,
                                              self.statement.journal.currency)
                self.amount = amount
                if self.payment.kind == 'payable':
                    self.amount *= -1

    @fields.depends('payment_group', 'account')
    def on_change_payment_group(self):
        if self.payment_group:
            self.party = None
            clearing_account = self.payment_group.journal.clearing_account
            if not self.account and clearing_account:
                self.account = clearing_account

    @fields.depends('party', 'payment')
    def on_change_party(self):
        super(StatementLine, self).on_change_party()
        if self.payment:
            if self.payment.party != self.party:
                self.payment = None
        self.payment_group = None

    @fields.depends('account', 'payment', 'payment_group')
    def on_change_account(self):
        super(StatementLine, self).on_change_account()
        if self.payment:
            clearing_account = self.payment.journal.clearing_account
        elif self.payment_group:
            clearing_account = self.payment_group.journal.clearing_account
        else:
            return
        if self.account != clearing_account:
            self.payment = None

    @classmethod
    def post_move(cls, lines):
        pool = Pool()
        Move = pool.get('account.move')
        super(StatementLine, cls).post_move(lines)
        Move.post([
            l.payment.clearing_move for l in lines
            if l.payment and l.payment.clearing_move
            and l.payment.clearing_move.state == 'draft'
        ])
Example #4
0
class InpatientIcu(ModelSQL, ModelView):
    'Patient ICU Information'
    __name__ = 'gnuhealth.inpatient.icu'

    def icu_duration(self, name):

        now = datetime.now()
        admission = datetime.strptime(str(self.icu_admission_date),
                                      '%Y-%m-%d %H:%M:%S')

        if self.discharged_from_icu:
            discharge = datetime.strptime(str(self.icu_discharge_date),
                                          '%Y-%m-%d %H:%M:%S')
            delta = relativedelta(discharge, admission)
        else:
            delta = relativedelta(now, admission)
        years_months_days = str(delta.years) + 'y ' \
                + str(delta.months) + 'm ' \
                + str(delta.days) + 'd'
        return years_months_days

    name = fields.Many2One('gnuhealth.inpatient.registration',
                           'Registration Code',
                           required=True)

    admitted = fields.Boolean('Admitted',
                              help="Will be set when the patient \
        is currently admitted at ICU",
                              on_change_with=['discharged_from_icu'])

    icu_admission_date = fields.DateTime('ICU Admission',
                                         help="ICU Admission Date",
                                         required=True)
    discharged_from_icu = fields.Boolean('Discharged')
    icu_discharge_date = fields.DateTime(
        'Discharge',
        states={
            'invisible': Not(Bool(Eval('discharged_from_icu'))),
            'required': Bool(Eval('discharged_from_icu')),
        },
        depends=['discharged_from_icu'])
    icu_stay = fields.Function(fields.Char('Duration'), 'icu_duration')

    mv_history = fields.One2Many('gnuhealth.icu.ventilation', 'name',
                                 "Mechanical Ventilation History")

    @classmethod
    def __setup__(cls):
        super(InpatientIcu, cls).__setup__()
        cls._constraints += [
            ('check_patient_admitted_at_icu', 'patient_already_at_icu'),
        ]

        cls._error_messages.update({
            'patient_already_at_icu':
            'Our records indicate that the patient'
            ' is already admitted at ICU'
        })

    def check_patient_admitted_at_icu(self):
        # Verify that the patient is not at ICU already
        cursor = Transaction().cursor
        cursor.execute(
            "SELECT count(name) "
            "FROM " + self._table + "  \
            WHERE (name = %s AND admitted)", (str(self.name.id), ))
        if cursor.fetchone()[0] > 1:
            return False
        return True

    @staticmethod
    def default_admitted():
        return True

    def on_change_with_admitted(self):
        # Reset the admission flag when the patient is discharged from ICU
        if self.discharged_from_icu:
            res = False
        else:
            res = True
        return res
"""
from decimal import Decimal

from trytond.model import ModelView, fields, Workflow
from trytond.pool import PoolMeta, Pool
from trytond.transaction import Transaction
from trytond.pyson import Eval, Bool, And, Not, Or
from trytond.wizard import Wizard, StateView, StateTransition, Button

from trytond.modules.payment_gateway.transaction import BaseCreditCardViewMixin

__all__ = ['Sale', 'PaymentTransaction', 'AddSalePaymentView', 'AddSalePayment']
__metaclass__ = PoolMeta

READONLY_IF_PAYMENTS = {
    'readonly': Not(Bool(Eval('payments')))
}


class Sale:
    'Sale'
    __name__ = 'sale.sale'

    # Readonly because the wizard should be the one adding payment gateways as
    # it provides a more cusomizable UX than directly adding a record.
    # For example, taking CC numbers.
    payments = fields.One2Many(
        'sale.payment', 'sale', 'Payments', readonly=True,
    )
    sorted_payments = fields.Function(
        fields.One2Many('sale.payment', None, 'Payments'),
Example #6
0
class InvoiceLine:
    __metaclass__ = PoolMeta
    __name__ = 'account.invoice.line'
    principal = fields.Many2One('commission.agent', 'Commission Principal',
        domain=[
            ('type_', '=', 'principal'),
            ('company', '=', Eval('_parent_invoice', {}).get('company',
                    Eval('company', -1))),
            ],
        states={
            'invisible': If(Bool(Eval('_parent_invoice')),
                Eval('_parent_invoice', {}).get('type') == 'in',
                Eval('invoice_type') == 'in'),
            }, depends=['invoice_type', 'company'])
    commissions = fields.One2Many('commission', 'origin', 'Commissions',
        readonly=True,
        states={
            'invisible': ~Eval('commissions'),
            })
    from_commissions = fields.One2Many('commission', 'invoice_line',
        'From Commissions', readonly=True,
        states={
            'invisible': ~Eval('from_commissions'),
            })

    @property
    def agent_plans_used(self):
        "List of agent, plan tuple"
        used = []
        if self.invoice.agent:
            used.append((self.invoice.agent, self.invoice.agent.plan))
        if self.principal:
            used.append((self.principal, self.principal.plan))
        return used

    def get_commissions(self):
        pool = Pool()
        Commission = pool.get('commission')
        Currency = pool.get('currency.currency')
        Date = pool.get('ir.date')

        if self.type != 'line':
            return []

        today = Date.today()
        commissions = []
        for agent, plan in self.agent_plans_used:
            if not plan:
                continue
            with Transaction().set_context(date=self.invoice.currency_date):
                amount = Currency.compute(self.invoice.currency,
                    self.amount, agent.currency, round=False)
            amount = self._get_commission_amount(amount, plan)
            if amount:
                digits = Commission.amount.digits
                amount = amount.quantize(Decimal(str(10.0 ** -digits[1])))
            if not amount:
                continue

            commission = Commission()
            commission.origin = self
            if plan.commission_method == 'posting':
                commission.date = today
            commission.agent = agent
            commission.product = plan.commission_product
            commission.amount = amount
            commissions.append(commission)
        return commissions

    def _get_commission_amount(self, amount, plan, pattern=None):
        return plan.compute(amount, self.product, pattern=pattern)

    @fields.depends('product', 'principal')
    def on_change_product(self):
        super(InvoiceLine, self).on_change_product()
        if self.product:
            if self.product.principals:
                if self.principal not in self.product.principals:
                    self.principal = self.product.principal
            elif self.principal:
                self.principal = None

    @classmethod
    def view_attributes(cls):
        return super(InvoiceLine, cls).view_attributes() + [
            ('//page[@id="commissions"]', 'states', {
                    'invisible': Eval('type') != 'line',
                    })]

    @classmethod
    def copy(cls, lines, default=None):
        if default is None:
            default = {}
        default.setdefault('commissions', None)
        default.setdefault('from_commissions', None)
        return super(InvoiceLine, cls).copy(lines, default=default)
Example #7
0
class ApacheII(ModelSQL, ModelView):
    'Apache II scoring'
    __name__ = 'gnuhealth.icu.apache2'

    name = fields.Many2One('gnuhealth.inpatient.registration',
                           'Registration Code',
                           required=True)
    score_date = fields.DateTime('Date',
                                 help="Date of the score",
                                 required=True)

    age = fields.Integer('Age', help='Patient age in years')
    temperature = fields.Float('Temperature', help='Rectal temperature')
    mean_ap = fields.Integer('MAP', help='Mean Arterial Pressure')
    heart_rate = fields.Integer('Heart Rate')
    respiratory_rate = fields.Integer('Respiratory Rate')
    fio2 = fields.Float('FiO2')
    pao2 = fields.Integer('PaO2')
    paco2 = fields.Integer('PaCO2')
    aado2 = fields.Integer('A-a DO2', on_change_with=['fio2', 'pao2', 'paco2'])

    ph = fields.Float('pH')
    serum_sodium = fields.Integer('Sodium')
    serum_potassium = fields.Float('Potassium')
    serum_creatinine = fields.Float('Creatinine')
    arf = fields.Boolean('ARF', help='Acute Renal Failure')
    wbc = fields.Float('WBC',
                       help="White blood cells x 1000 - if you"
                       " want to input 4500 wbc / ml, type in 4.5")
    hematocrit = fields.Float('Hematocrit')
    gcs = fields.Integer(
        'GSC',
        help='Last Glasgow Coma Scale'
        ' You can use the GSC calculator from the Patient Evaluation Form.')
    chronic_condition = fields.Boolean(
        'Chronic condition', help='Organ Failure or immunocompromised patient')
    hospital_admission_type = fields.Selection(
        [(None, ''), ('me', 'Medical or emergency postoperative'),
         ('el', 'elective postoperative')],
        'Hospital Admission Type',
        states={
            'invisible': Not(Bool(Eval('chronic_condition'))),
            'required': Bool(Eval('chronic_condition'))
        },
        sort=False)

    apache_score = fields.Integer(
        'Score',
        on_change_with=[
            'age', 'temperature', 'mean_ap', 'heart_rate', 'respiratory_rate',
            'fio2', 'pao2', 'aado2', 'ph', 'serum_sodium', 'serum_potassium',
            'serum_creatinine', 'arf', 'wbc', 'hematocrit', 'gcs',
            'chronic_condition', 'hospital_admission_type'
        ])

    #Default FiO2 PaO2 and PaCO2 so we do the A-a gradient
    #calculation with non-null values

    def on_change_with_aado2(self):
        # Calculates the Alveolar-arterial difference
        # based on FiO2, PaCO2 and PaO2 values
        if (self.fio2 and self.paco2 and self.pao2):
            return (713 * self.fio2) - (self.paco2 / 0.8) - self.pao2

    def on_change_with_apache_score(self):
        # Calculate the APACHE SCORE from the variables in the

        total = 0
        # Age
        if (self.age):
            if (self.age > 44 and self.age < 55):
                total = total + 2
            elif (self.age > 54 and self.age < 65):
                total = total + 3
            elif (self.age > 64 and self.age < 75):
                total = total + 5
            elif (self.age > 74):
                total = total + 6

        # Temperature
        if (self.temperature):
            if ((self.temperature >= 38.5 and self.temperature < 39)
                    or (self.temperature >= 34 and self.temperature < 36)):
                total = total + 1
            elif (self.temperature >= 32 and self.temperature < 34):
                total = total + 2
            elif ((self.temperature >= 30 and self.temperature < 32)
                  or (self.temperature >= 39 and self.temperature < 41)):
                total = total + 3
            elif (self.temperature >= 41 or self.temperature < 30):
                total = total + 4

        # Mean Arterial Pressure (MAP)
        if (self.mean_ap):
            if ((self.mean_ap >= 110 and self.mean_ap < 130)
                    or (self.mean_ap >= 50 and self.mean_ap < 70)):
                total = total + 2
            elif (self.mean_ap >= 130 and self.mean_ap < 160):
                total = total + 3
            elif (self.mean_ap >= 160 or self.mean_ap < 50):
                total = total + 4

        # Heart Rate
        if (self.heart_rate):
            if ((self.heart_rate >= 55 and self.heart_rate < 70)
                    or (self.heart_rate >= 110 and self.heart_rate < 140)):
                total = total + 2
            elif ((self.heart_rate >= 40 and self.heart_rate < 55)
                  or (self.heart_rate >= 140 and self.heart_rate < 180)):
                total = total + 3
            elif (self.heart_rate >= 180 or self.heart_rate < 40):
                total = total + 4

        # Respiratory Rate
        if (self.respiratory_rate):
            if ((self.respiratory_rate >= 10 and self.respiratory_rate < 12) or
                (self.respiratory_rate >= 25 and self.respiratory_rate < 35)):
                total = total + 1
            elif (self.respiratory_rate >= 6 and self.respiratory_rate < 10):
                total = total + 2
            elif (self.respiratory_rate >= 35 and self.respiratory_rate < 50):
                total = total + 3
            elif (self.respiratory_rate >= 50 or self.respiratory_rate < 6):
                total = total + 4

        # FIO2
        if (self.fio2):
            # If Fi02 is greater than 0.5, we measure the AaDO2 gradient
            # Otherwise, we take into account the Pa02 value

            if (self.fio2 >= 0.5):
                if (self.aado2 >= 200 and self.aado2 < 350):
                    total = total + 2

                elif (self.aado2 >= 350 and self.aado2 < 500):
                    total = total + 3

                elif (self.aado2 >= 500):
                    total = total + 4

            else:
                if (self.pao2 >= 61 and self.pao2 < 71):
                    total = total + 1

                elif (self.pao2 >= 55 and self.pao2 < 61):
                    total = total + 3

                elif (self.pao2 < 55):
                    total = total + 4

        # Arterial pH
        if (self.ph):
            if (self.ph >= 7.5 and self.ph < 7.6):
                total = total + 1
            elif (self.ph >= 7.25 and self.ph < 7.33):
                total = total + 2
            elif ((self.ph >= 7.15 and self.ph < 7.25)
                  or (self.ph >= 7.6 and self.ph < 7.7)):
                total = total + 3
            elif (self.ph >= 7.7 or self.ph < 7.15):
                total = total + 4

        # Serum Sodium
        if (self.serum_sodium):
            if (self.serum_sodium >= 150 and self.serum_sodium < 155):
                total = total + 1
            elif ((self.serum_sodium >= 155 and self.serum_sodium < 160)
                  or (self.serum_sodium >= 120 and self.serum_sodium < 130)):
                total = total + 2
            elif ((self.serum_sodium >= 160 and self.serum_sodium < 180)
                  or (self.serum_sodium >= 111 and self.serum_sodium < 120)):
                total = total + 3
            elif (self.serum_sodium >= 180 or self.serum_sodium < 111):
                total = total + 4

        # Serum Potassium
        if (self.serum_potassium):
            if ((self.serum_potassium >= 3 and self.serum_potassium < 3.5) or
                (self.serum_potassium >= 5.5 and self.serum_potassium < 6)):
                total = total + 1
            elif (self.serum_potassium >= 2.5 and self.serum_potassium < 3):
                total = total + 2
            elif (self.serum_potassium >= 6 and self.serum_potassium < 7):
                total = total + 3
            elif (self.serum_potassium >= 7 or self.serum_potassium < 2.5):
                total = total + 4

        # Serum Creatinine
        if (self.serum_creatinine):
            arf_factor = 1
            if (self.arf):
                # We multiply by 2 the score if there is concomitant ARF
                arf_factor = 2
            if ((self.serum_creatinine < 0.6) or
                (self.serum_creatinine >= 1.5 and self.serum_creatinine < 2)):
                total = total + 2 * arf_factor
            elif (self.serum_creatinine >= 2 and self.serum_creatinine < 3.5):
                total = total + 3 * arf_factor
            elif (self.serum_creatinine >= 3.5):
                total = total + 4 * arf_factor

        # Hematocrit
        if (self.hematocrit):
            if (self.hematocrit >= 46 and self.hematocrit < 50):
                total = total + 1
            elif ((self.hematocrit >= 50 and self.hematocrit < 60)
                  or (self.hematocrit >= 20 and self.hematocrit < 30)):
                total = total + 2
            elif (self.hematocrit >= 60 or self.hematocrit < 20):
                total = total + 4

        # WBC ( x 1000 )
        if (self.wbc):
            if (self.wbc >= 15 and self.wbc < 20):
                total = total + 1
            elif ((self.wbc >= 20 and self.wbc < 40)
                  or (self.wbc >= 1 and self.wbc < 3)):
                total = total + 2
            elif (self.wbc >= 40 or self.wbc < 1):
                total = total + 4

        # Immnunocompromised or severe organ failure
        if (self.chronic_condition):
            if (self.hospital_admission_type == 'me'):
                total = total + 5
            else:
                total = total + 2

        return total
class AccountRetencionEfectuada(ModelSQL, ModelView):
    'Account Retencion Efectuada'
    __name__ = 'account.retencion.efectuada'

    name = fields.Char('Number',
                       states={
                           'required': Bool(Eval('name_required')),
                           'readonly': Not(Bool(Eval('name_required'))),
                       },
                       depends=['name_required'])
    name_required = fields.Function(fields.Boolean('Name Required'),
                                    'on_change_with_name_required')
    amount = fields.Numeric('Amount', digits=(16, 2), required=True)
    aliquot = fields.Float('Aliquot')
    date = fields.Date('Date', required=True)
    tax = fields.Many2One('account.retencion',
                          'Tax',
                          domain=[('type', '=', 'efectuada')])
    voucher = fields.Many2One('account.voucher', 'Voucher')
    party = fields.Many2One('party.party', 'Party')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('issued', 'Issued'),
        ('cancelled', 'Cancelled'),
    ],
                             'State',
                             readonly=True)

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        sql_table = cls.__table__()
        super().__register__(module_name)
        cursor.execute(*sql_table.update([sql_table.state], ['cancelled'],
                                         where=sql_table.state == 'canceled'))

    @staticmethod
    def default_amount():
        return Decimal('0.00')

    @staticmethod
    def default_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @fields.depends('tax')
    def on_change_with_name_required(self, name=None):
        if self.tax and self.tax.sequence:
            return False
        return True

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    def delete(cls, retenciones):
        cls.check_delete(retenciones)
        super().delete(retenciones)

    @classmethod
    def check_delete(cls, retenciones):
        for retencion in retenciones:
            if retencion.voucher:
                raise UserError(
                    gettext('account_retencion_ar.msg_not_delete',
                            retention=retencion.name))

    @classmethod
    def copy(cls, retenciones, default=None):
        if default is None:
            default = {}
        current_default = default.copy()
        current_default['state'] = 'draft'
        current_default['name'] = None
        current_default['voucher'] = None
        return super().copy(retenciones, default=current_default)
Example #9
0
class ChagasDUSurvey(ModelSQL, ModelView):
    'Chagas DU Entomological Survey'
    __name__ = 'gnuhealth.chagas_du_survey'

    name = fields.Char('Survey Code', readonly=True)
    du = fields.Many2One('gnuhealth.du', 'DU', help="Domiciliary Unit")
    survey_date = fields.Date('Date', required=True)

    du_status = fields.Selection([
        (None, ''),
        ('initial', 'Initial'),
        ('unchanged', 'Unchanged'),
        ('better', 'Improved'),
        ('worse', 'Worsen'),
    ],
                                 'Status',
                                 help="DU status compared to last visit",
                                 required=True,
                                 sort=False)

    # Findings of Triatomines in the DU
    triatomines = fields.Boolean(
        'Triatomines', help="Check this box if triatomines were found")
    vector = fields.Selection([
        (None, ''),
        ('t_infestans', 'T. infestans'),
        ('t_brasilensis', 'T. brasilensis'),
        ('r_prolixus', 'R. prolixus'),
        ('t_dimidiata', 'T. dimidiata'),
        ('p_megistus', 'P. megistus'),
    ],
                              'Vector',
                              help="Vector",
                              sort=False)

    nymphs = fields.Boolean('Nymphs',
                            "Check this box if triatomine nymphs were found")
    t_in_house = fields.Boolean(
        'Domiciliary',
        help="Check this box if triatomines were found inside the house")
    t_peri = fields.Boolean(
        'Peri-Domiciliary',
        help=
        "Check this box if triatomines were found in the peridomiciliary area")

    # Infrastructure conditions
    dfloor = fields.Boolean('Floor', help="Current floor can host triatomines")
    dwall = fields.Boolean('Walls',
                           help="Wall materials or state can host triatomines")
    droof = fields.Boolean('Roof',
                           help="Roof materials or state can host triatomines")
    dperi = fields.Boolean('Peri-domicilary',
                           help="Peri domiciliary area can host triatomines")

    # Preventive measures

    bugtraps = fields.Boolean('Bug traps',
                              help="The DU has traps to detect triatomines")

    # Chemical controls

    du_fumigation = fields.Boolean('Fumigation',
                                   help="The DU has been fumigated")
    fumigation_date = fields.Date(
        'Fumigation Date',
        help="Last Fumigation Date",
        states={'invisible': Not(Bool(Eval('du_fumigation')))})

    du_paint = fields.Boolean(
        'Insecticide Paint',
        help="The DU has been treated with insecticide-containing paint")
    paint_date = fields.Date('Paint Date',
                             help="Last Paint Date",
                             states={'invisible': Not(Bool(Eval('du_paint')))})

    observations = fields.Text('Observations')
    next_survey_date = fields.Date('Next survey')

    @staticmethod
    def default_survey_date():
        return datetime.now()

    @classmethod
    def create(cls, vlist):
        Sequence = Pool().get('ir.sequence')
        Config = Pool().get('gnuhealth.sequences')

        vlist = [x.copy() for x in vlist]
        for values in vlist:
            if not values.get('name'):
                config = Config(1)
                values['name'] = Sequence.get_id(
                    config.chagas_du_survey_sequence.id)

        return super(ChagasDUSurvey, cls).create(vlist)
Example #10
0
class PurchaseRequest(ModelSQL, ModelView):
    'Purchase Request'
    __name__ = 'purchase.request'

    product = fields.Many2One('product.product', 'Product',
        select=True, readonly=True, domain=[('purchasable', '=', True)])
    description = fields.Text('Description', readonly=True,
        states=STATES, depends=DEPENDS)
    party = fields.Many2One('party.party', 'Party', select=True, states=STATES,
        depends=DEPENDS)
    quantity = fields.Float('Quantity', required=True, states=STATES,
        digits=(16, Eval('uom_digits', 2)), depends=DEPENDS + ['uom_digits'])
    uom = fields.Many2One('product.uom', 'UOM', select=True,
        states={
            'required': Bool(Eval('product')),
            'readonly': STATES['readonly'],
            },
        depends=['product'] + DEPENDS)
    uom_digits = fields.Function(fields.Integer('UOM Digits'),
        'on_change_with_uom_digits')
    computed_quantity = fields.Float('Computed Quantity', readonly=True)
    computed_uom = fields.Many2One('product.uom', 'Computed UOM',
        readonly=True)
    purchase_date = fields.Date('Best Purchase Date', readonly=True)
    supply_date = fields.Date('Expected Supply Date', readonly=True)
    default_uom_digits = fields.Function(fields.Integer('Default UOM Digits'),
        'on_change_with_default_uom_digits')
    stock_level = fields.Float('Stock at Supply Date', readonly=True,
        digits=(16, Eval('default_uom_digits', 2)),
        depends=['default_uom_digits'])
    warehouse = fields.Many2One(
        'stock.location', "Warehouse",
        states={
            'required': Eval('warehouse_required', False),
            },
        domain=[('type', '=', 'warehouse')], depends=['warehouse_required'],
        readonly=True)
    warehouse_required = fields.Function(fields.Boolean('Warehouse Required'),
        'get_warehouse_required')
    purchase_line = fields.Many2One('purchase.line', 'Purchase Line',
        readonly=True)
    purchase = fields.Function(fields.Many2One('purchase.purchase',
        'Purchase'), 'get_purchase', searcher='search_purchase')
    company = fields.Many2One('company.company', 'Company', required=True,
            readonly=True, domain=[
                ('id', If(In('company', Eval('context', {})), '=', '!='),
                    Eval('context', {}).get('company', -1)),
            ])
    origin = fields.Reference('Origin', selection='get_origin', readonly=True)
    exception_ignored = fields.Boolean('Ignored Exception')
    state = fields.Selection([
            ('purchased', "Purchased"),
            ('done', "Done"),
            ('draft', "Draft"),
            ('cancel', "Cancel"),
            ('exception', "Exception"),
            ], "State", required=True, readonly=True, select=True)

    @classmethod
    def __setup__(cls):
        super(PurchaseRequest, cls).__setup__()
        cls._order[0] = ('id', 'DESC')
        cls._error_messages.update({
                'create_request': ('Purchase requests are only created '
                    'by the system.'),
                'delete_purchase_line': ('You can not delete purchased '
                    'request.'),
                })
        cls._buttons.update({
                'handle_purchase_cancellation_exception': {
                    'invisible': Eval('state') != 'exception',
                    'depends': ['state'],
                    },
                })

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        Purchase = pool.get('purchase.purchase')
        PurchaseLine = pool.get('purchase.line')
        model_data = ModelData.__table__()
        purchase = Purchase.__table__()
        purchase_line = PurchaseLine.__table__()
        request = cls.__table__()

        tablehandler = cls.__table_handler__(module_name)
        state_exist = tablehandler.column_exist('state')

        super(PurchaseRequest, cls).__register__(module_name)

        # Migration from 3.6: removing the constraint on the quantity
        tablehandler = cls.__table_handler__(module_name)
        tablehandler.drop_constraint('check_purchase_request_quantity')

        # Migration from 3.8: renaming module of Purchase Request group entry
        cursor = Transaction().connection.cursor()
        cursor.execute(*model_data.update(
                columns=[model_data.module],
                values=['purchase_request'],
                where=((model_data.fs_id == 'group_purchase_request')
                    & (model_data.module == 'stock_supply'))))

        # Migration from 4.0: remove required on product and uom
        tablehandler.not_null_action('product', action='remove')
        tablehandler.not_null_action('uom', action='remove')

        # Migration from 4.2: add state
        if not state_exist:
            cursor = Transaction().connection.cursor()
            update = Transaction().connection.cursor()
            query = request.join(purchase_line, type_='INNER',
                condition=request.purchase_line == purchase_line.id
                ).join(purchase, type_='INNER',
                    condition=purchase_line.purchase == purchase.id
                    ).select(
                        request.id, purchase.state, request.exception_ignored)
            cursor.execute(*query)
            for request_id, purchase_state, exception_ignored in cursor:
                if purchase_state == 'cancel' and not exception_ignored:
                    state = 'exception'
                elif purchase_state == 'cancel':
                    state = 'cancel'
                elif purchase_state == 'done':
                    state = 'done'
                else:
                    state = 'purchased'
                update.execute(*request.update(
                        [request.state],
                        [state],
                        where=request.id == request_id))

        # Migration from 4.4: remove required on origin
        tablehandler.not_null_action('origin', action='remove')

    def get_rec_name(self, name):
        product_name = (self.product.name if self.product else
            self.description.splitlines()[0])
        if self.warehouse:
            return "%s@%s" % (product_name, self.warehouse.name)
        else:
            return product_name

    @classmethod
    def search_rec_name(cls, name, clause):
        res = []
        names = clause[2].split('@', 1)
        res.append(('product.template.name', clause[1], names[0]))
        if len(names) != 1 and names[1]:
            res.append(('warehouse', clause[1], names[1]))
        return ['OR', res,
            ('description',) + tuple(clause[1:]),
            ]

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_exception_ignored():
        return False

    def get_purchase(self, name):
        if self.purchase_line:
            return self.purchase_line.purchase.id

    @classmethod
    def search_purchase(cls, name, clause):
        return [('purchase_line.' + clause[0],) + tuple(clause[1:])]

    @property
    def currency(self):
        return self.company.currency

    @classmethod
    def default_state(cls):
        return 'draft'

    def get_state(self):
        if self.purchase_line:
            if (self.purchase_line.purchase.state == 'cancel'
                    and not self.exception_ignored):
                return 'exception'
            elif self.purchase_line.purchase.state == 'cancel':
                return 'cancel'
            elif self.purchase_line.purchase.state == 'done':
                return 'done'
            else:
                return 'purchased'
        return 'draft'

    @classmethod
    def update_state(cls, requests):
        for request in requests:
            state = request.get_state()
            if state != request.state:
                request.state = state
        cls.save(requests)

    def get_warehouse_required(self, name):
        return self.product and self.product.type in ('goods', 'assets')

    @fields.depends('uom')
    def on_change_with_uom_digits(self, name=None):
        if self.uom:
            return self.uom.digits
        return 2

    @fields.depends('product')
    def on_change_with_default_uom_digits(self, name=None):
        if self.product:
            return self.product.default_uom.digits
        return 2

    @classmethod
    def _get_origin(cls):
        'Return the set of Model names for origin Reference'
        return set()

    @classmethod
    def get_origin(cls):
        pool = Pool()
        IrModel = pool.get('ir.model')
        models = IrModel.search([
                ('model', 'in', list(cls._get_origin())),
                ])
        return [(None, '')] + [(m.model, m.name) for m in models]

    @classmethod
    def create(cls, vlist):
        for vals in vlist:
            for field_name in ('quantity', 'company'):
                if vals.get(field_name) is None:
                    cls.raise_user_error('create_request')
        return super(PurchaseRequest, cls).create(vlist)

    @classmethod
    def delete(cls, requests):
        if any(r.purchase_line for r in requests):
            cls.raise_user_error('delete_purchase_line')
        super(PurchaseRequest, cls).delete(requests)

    @classmethod
    def find_best_supplier(cls, product, date):
        '''
        Return the best supplier and purchase_date for the product.
        '''
        Date = Pool().get('ir.date')

        supplier = None
        today = Date.today()
        for product_supplier in product.product_suppliers:
            supply_date = product_supplier.compute_supply_date(date=today)
            timedelta = date - supply_date
            if not supplier and timedelta >= datetime.timedelta(0):
                supplier = product_supplier.party
                break

        if supplier:
            purchase_date = product_supplier.compute_purchase_date(date)
        else:
            purchase_date = today
        return supplier, purchase_date

    @classmethod
    @ModelView.button_action(
        'purchase_request.wizard_purchase_cancellation_handle_exception')
    def handle_purchase_cancellation_exception(cls, purchases):
        pass
Example #11
0
class Journal(ModelSQL, ModelView):
    'Journal'
    __name__ = 'account.journal'
    name = fields.Char('Name', size=None, required=True, translate=True)
    code = fields.Char('Code', size=None)
    active = fields.Boolean('Active', select=True)
    type = fields.Selection('get_types', 'Type', required=True)
    view = fields.Many2One('account.journal.view', 'View')
    update_posted = fields.Boolean('Allow updating posted moves')
    sequence = fields.Property(
        fields.Many2One('ir.sequence',
                        'Sequence',
                        domain=[('code', '=', 'account.journal')],
                        context={'code': 'account.journal'},
                        states={
                            'required':
                            Bool(Eval('context', {}).get('company', -1)),
                        }))
    credit_account = fields.Property(
        fields.Many2One('account.account',
                        'Default Credit Account',
                        domain=[
                            ('kind', '!=', 'view'),
                            ('company', '=', Eval('context',
                                                  {}).get('company', -1)),
                        ],
                        states={
                            'required':
                            ((Eval('type').in_(['cash', 'write-off']))
                             & (Eval('context', {}).get('company', -1) != -1)),
                            'invisible':
                            ~Eval('context', {}).get('company', -1),
                        },
                        depends=['type']))
    debit_account = fields.Property(
        fields.Many2One('account.account',
                        'Default Debit Account',
                        domain=[
                            ('kind', '!=', 'view'),
                            ('company', '=', Eval('context',
                                                  {}).get('company', -1)),
                        ],
                        states={
                            'required':
                            ((Eval('type').in_(['cash', 'write-off']))
                             & (Eval('context', {}).get('company', -1) != -1)),
                            'invisible':
                            ~Eval('context', {}).get('company', -1),
                        },
                        depends=['type']))
    debit = fields.Function(
        fields.Numeric('Debit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']),
        'get_debit_credit_balance')
    credit = fields.Function(
        fields.Numeric('Credit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']),
        'get_debit_credit_balance')
    balance = fields.Function(
        fields.Numeric('Balance',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']),
        'get_debit_credit_balance')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')

    @classmethod
    def __setup__(cls):
        super(Journal, cls).__setup__()
        cls._order.insert(0, ('name', 'ASC'))

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        super(Journal, cls).__register__(module_name)
        cursor = Transaction().cursor
        table = TableHandler(cursor, cls, module_name)

        # Migration from 1.0 sequence Many2One change into Property
        if table.column_exist('sequence'):
            Property = Pool().get('ir.property')
            sql_table = cls.__table__()
            cursor.execute(*sql_table.select(sql_table.id, sql_table.sequence))
            for journal_id, sequence_id in cursor.fetchall():
                Property.set('sequence', cls._name, journal_id,
                             (sequence_id and 'ir.sequence,' + str(sequence_id)
                              or False))
            table.drop_column('sequence', exception=True)

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_update_posted():
        return False

    @staticmethod
    def default_sequence():
        return None

    @staticmethod
    def get_types():
        Type = Pool().get('account.journal.type')
        types = Type.search([])
        return [(x.code, x.name) for x in types]

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [
            bool_op,
            ('code', ) + tuple(clause[1:]),
            (cls._rec_name, ) + tuple(clause[1:]),
        ]

    @classmethod
    def get_currency_digits(cls, journals, name):
        pool = Pool()
        Company = pool.get('company.company')
        company_id = Transaction().context.get('company')
        if company_id:
            company = Company(company_id)
            digits = company.currency.digits
        else:
            digits = 2
        return dict.fromkeys([j.id for j in journals], digits)

    @classmethod
    def get_debit_credit_balance(cls, journals, names):
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Move = pool.get('account.move')
        context = Transaction().context
        cursor = Transaction().cursor

        result = {}
        ids = [j.id for j in journals]
        for name in ['debit', 'credit', 'balance']:
            result[name] = dict.fromkeys(ids, 0)

        line = MoveLine.__table__()
        move = Move.__table__()
        where = ((move.date >= context['start_date'])
                 & (move.date <= context['end_date']))
        for sub_journals in grouped_slice(journals):
            sub_journals = list(sub_journals)
            red_sql = reduce_ids(move.journal, [j.id for j in sub_journals])
            accounts = None
            for journal in sub_journals:
                credit_account = (journal.credit_account.id
                                  if journal.credit_account else None)
                debit_account = (journal.debit_account.id
                                 if journal.debit_account else None)
                clause = ((move.journal == journal.id)
                          & (((line.credit != Null)
                              & (line.account == credit_account))
                             | ((line.debit != Null)
                                & (line.account == debit_account))))
                if accounts is None:
                    accounts = clause
                else:
                    accounts |= clause

            query = line.join(move, 'LEFT',
                              condition=line.move == move.id).select(
                                  move.journal,
                                  Sum(line.debit),
                                  Sum(line.credit),
                                  where=where & red_sql & accounts,
                                  group_by=move.journal)
            cursor.execute(*query)
            for journal_id, debit, credit in cursor.fetchall():
                # SQLite uses float for SUM
                if not isinstance(debit, Decimal):
                    debit = Decimal(str(debit))
                if not isinstance(credit, Decimal):
                    credit = Decimal(str(credit))
                result['debit'][journal_id] = debit
                result['credit'][journal_id] = credit
                result['balance'][journal_id] = debit - credit
        return result
Example #12
0
class SaleLine:
    __name__ = 'sale.line'

    is_round_off = fields.Boolean('Round Off', readonly=True)

    delivery_mode = fields.Selection(
        [
            (None, ''),
            ('pick_up', 'Pick Up'),
            ('ship', 'Ship'),
        ],
        'Delivery Mode',
        states={
            'invisible':
            Eval('type') != 'line',
            'required':
            And(Eval('type') == 'line', Bool(Eval('product_type_is_goods')))
        },
        depends=['type', 'product_type_is_goods'])

    product_type_is_goods = fields.Function(
        fields.Boolean('Product Type is Goods?'), 'get_product_type_is_goods')

    @classmethod
    def __register__(cls, module_name):
        super(SaleLine, cls).__register__(module_name)

        TableHandler = backend.get('TableHandler')
        cursor = Transaction().cursor

        table = TableHandler(cursor, cls, module_name)

        table.not_null_action('delivery_mode', action='remove')

    @classmethod
    def __setup__(cls):
        super(SaleLine, cls).__setup__()

        # Hide product and unit fields.
        cls.product.states['invisible'] |= Bool(Eval('is_round_off'))
        cls.unit.states['invisible'] |= Bool(Eval('is_round_off'))
        cls.delivery_mode.states['invisible'] |= Bool(Eval('is_round_off'))
        cls.product.depends.insert(0, 'is_round_off')
        cls.unit.depends.insert(0, 'is_round_off')

    @fields.depends('product', 'unit', 'quantity', '_parent_sale.party',
                    '_parent_sale.currency', '_parent_sale.sale_date',
                    'delivery_mode', '_parent_sale.channel',
                    '_parent_sale.shipment_address', 'warehouse',
                    '_parent_sale.warehouse')
    def on_change_delivery_mode(self):
        """
        This method can be overridden by downstream modules to make changes
        according to delivery mode. Like change taxes according to delivery
        mode.
        """
        return {}

    @staticmethod
    def default_is_round_off():
        return False

    def get_invoice_line(self, invoice_type):
        SaleConfiguration = Pool().get('sale.configuration')
        InvoiceLine = Pool().get('account.invoice.line')

        if not self.is_round_off:
            return super(SaleLine, self).get_invoice_line(invoice_type)

        # The line is a round off line and apply the logic here.

        # Check if the invoice line already exists for the sale line
        # If yes, then no line needs to be created
        # XXX: What if the invoice was cancelled ?
        if self.invoice_lines:
            return []

        round_down_account = SaleConfiguration(1).round_down_account
        if not round_down_account:
            self.raise_user_error(
                '''Set round down account from Sale Configuration to
                add round off line''')

        invoice_line = InvoiceLine()
        invoice_line.origin = self
        invoice_line.account = round_down_account
        invoice_line.unit_price = self.unit_price
        invoice_line.description = self.description

        # For positive sales transactions (where the order is effectively
        # a positive total), the round_down is applied on out_invoice
        # and if overall order total is negative, then the round_down is
        # tied to credit note.
        if self.sale.total_amount >= Decimal('0'):
            if invoice_type == 'out_credit_note':
                # positive order looking for credit note
                return []
            invoice_line.quantity = self.quantity
        else:
            if invoice_type == 'out_invoice':
                # negative order looking for invoice
                return []
            invoice_line.quantity = -self.quantity

        return [invoice_line]

    @staticmethod
    def default_delivery_mode():
        Channel = Pool().get('sale.channel')
        User = Pool().get('res.user')

        user = User(Transaction().user)
        sale_channel = user.current_channel
        if Transaction().context.get('current_channel'):
            sale_channel = Channel(
                Transaction().context.get('current_channel'))
        return sale_channel and sale_channel.delivery_mode

    def get_warehouse(self, name):
        """
        Return the warehouse from the channel for orders being picked up and the
        backorder warehouse for orders with ship.
        """
        if self.sale.channel.source == 'pos' and \
                self.delivery_mode == 'ship' and \
                self.sale.channel.backorder_warehouse:
            return self.sale.channel.backorder_warehouse.id
        return super(SaleLine, self).get_warehouse(name)

    def serialize(self, purpose=None):
        """
        Serialize for the purpose of POS
        """
        if purpose == 'pos':
            return {
                'id': self.id,
                'description': self.description,
                'product': self.product and {
                    'id':
                    self.product.id,
                    'code':
                    self.product.code,
                    'rec_name':
                    self.product.rec_name,
                    'default_image':
                    self.product.default_image
                    and self.product.default_image.id,
                },
                'unit': self.unit and {
                    'id': self.unit.id,
                    'rec_name': self.unit.rec_name,
                },
                'unit_price': self.unit_price,
                'quantity': self.quantity,
                'amount': self.amount,
                'delivery_mode': self.delivery_mode
            }
        elif hasattr(super(SaleLine, self), 'serialize'):
            return super(SaleLine, self).serialize(purpose)  # pragma: no cover

    def get_product_type_is_goods(self, name):
        """
        Return True if product is of type goods
        """
        if self.product and self.product.type == 'goods':
            return True
        return False
Example #13
0
class InventoryLine(ModelSQL, ModelView):
    'Stock Inventory Line'
    __name__ = 'stock.inventory.line'
    _states = {
        'readonly': Eval('inventory_state') != 'draft',
        }
    _depends = ['inventory_state']

    product = fields.Many2One('product.product', 'Product', required=True,
        domain=[
            ('type', '=', 'goods'),
            ], states=_states, depends=_depends)
    uom = fields.Function(fields.Many2One('product.uom', 'UOM',
        help="The unit in which the quantity is specified."), 'get_uom')
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
            'get_unit_digits')
    expected_quantity = fields.Float('Expected Quantity', required=True,
        digits=(16, Eval('unit_digits', 2)), readonly=True,
        states={
            'invisible': Eval('id', -1) < 0,
        },
        depends=['unit_digits'],
        help="The quantity the system calculated should be in the location.")
    quantity = fields.Float('Quantity',
        digits=(16, Eval('unit_digits', 2)),
        states=_states, depends=['unit_digits'] + _depends,
        help="The actual quantity found in the location.")
    moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)
    inventory = fields.Many2One('stock.inventory', 'Inventory', required=True,
        ondelete='CASCADE',
        states={
            'readonly': _states['readonly'] & Bool(Eval('inventory')),
            },
        depends=_depends,
        help="The inventory the line belongs to.")
    inventory_state = fields.Function(
        fields.Selection('get_inventory_states', "Inventory State"),
        'on_change_with_inventory_state')

    @classmethod
    def __setup__(cls):
        super(InventoryLine, cls).__setup__()
        t = cls.__table__()
        cls._sql_constraints += [
            ('check_line_qty_pos', Check(t, t.quantity >= 0),
                'stock.msg_inventory_line_quantity_positive'),
            ]
        cls._order.insert(0, ('product', 'ASC'))

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        pool = Pool()
        Move = pool.get('stock.move')
        sql_table = cls.__table__()
        move_table = Move.__table__()

        super(InventoryLine, cls).__register__(module_name)

        table = cls.__table_handler__(module_name)

        # Migration from 3.0: use Move origin
        if table.column_exist('move'):
            cursor.execute(*sql_table.select(sql_table.id, sql_table.move,
                    where=sql_table.move != Null))
            for line_id, move_id in cursor.fetchall():
                cursor.execute(*move_table.update(
                        columns=[move_table.origin],
                        values=['%s,%s' % (cls.__name__, line_id)],
                        where=move_table.id == move_id))
            table.drop_column('move')

        # Migration from 4.6: drop required on quantity
        table.not_null_action('quantity', action='remove')

    @staticmethod
    def default_unit_digits():
        return 2

    @staticmethod
    def default_expected_quantity():
        return 0.

    @fields.depends('product')
    def on_change_product(self):
        self.unit_digits = 2
        if self.product:
            self.uom = self.product.default_uom
            self.unit_digits = self.product.default_uom.digits

    @classmethod
    def get_inventory_states(cls):
        pool = Pool()
        Inventory = pool.get('stock.inventory')
        return Inventory.fields_get(['state'])['state']['selection']

    @fields.depends('inventory', '_parent_inventory.state')
    def on_change_with_inventory_state(self, name=None):
        if self.inventory:
            return self.inventory.state
        return 'draft'

    def get_rec_name(self, name):
        return self.product.rec_name

    @classmethod
    def search_rec_name(cls, name, clause):
        return [('product.rec_name',) + tuple(clause[1:])]

    def get_uom(self, name):
        return self.product.default_uom.id

    def get_unit_digits(self, name):
        return self.product.default_uom.digits

    @property
    def unique_key(self):
        key = []
        for fname in self.inventory.grouping():
            value = getattr(self, fname)
            if isinstance(value, Model):
                value = value.id
            key.append(value)
        return tuple(key)

    @classmethod
    def cancel_move(cls, lines):
        Move = Pool().get('stock.move')
        moves = [m for l in lines for m in l.moves if l.moves]
        Move.cancel(moves)
        Move.delete(moves)

    def get_move(self):
        '''
        Return Move instance for the inventory line
        '''
        pool = Pool()
        Move = pool.get('stock.move')
        Uom = pool.get('product.uom')

        qty = self.quantity
        if qty is None:
            if self.inventory.empty_quantity is None:
                raise InventoryValidationError(
                    gettext('stock.msg_inventory_missing_empty_quantity',
                        inventory=self.inventory.rec_name))
            if self.inventory.empty_quantity == 'keep':
                return
            else:
                qty = 0.0

        delta_qty = Uom.compute_qty(self.uom,
            self.expected_quantity - qty,
            self.uom)
        if delta_qty == 0.0:
            return
        from_location = self.inventory.location
        to_location = self.inventory.lost_found
        if delta_qty < 0:
            (from_location, to_location, delta_qty) = \
                (to_location, from_location, -delta_qty)

        return Move(
            from_location=from_location,
            to_location=to_location,
            quantity=delta_qty,
            product=self.product,
            uom=self.uom,
            company=self.inventory.company,
            effective_date=self.inventory.date,
            origin=self,
            )

    def update_values4complete(self, quantity):
        '''
        Return update values to complete inventory
        '''
        values = {}
        # if nothing changed, no update
        if self.expected_quantity == quantity:
            return values
        values['expected_quantity'] = quantity
        return values

    @classmethod
    def create_values4complete(cls, inventory, quantity):
        '''
        Return create values to complete inventory
        '''
        return {
            'inventory': inventory.id,
            'expected_quantity': quantity,
        }

    @classmethod
    def delete(cls, lines):
        for line in lines:
            if line.inventory_state not in {'cancel', 'draft'}:
                raise AccessError(
                    gettext('stock.msg_inventory_line_delete_cancel',
                        line=line.rec_name,
                        inventory=line.inventory.rec_name))
        super(InventoryLine, cls).delete(lines)
Example #14
0
class Artist(ModelSQL, ModelView):
    'Artist'
    __name__ = 'artist'
    name = fields.Char('Name',
                       required=True,
                       select=True,
                       states=STATES,
                       depends=DEPENDS)
    code = fields.Char('Code',
                       required=True,
                       select=True,
                       states={
                           'readonly': True,
                       },
                       help='The unique code of the artist')
    party = fields.Many2One(
        'party.party',
        'Party',
        states=STATES,
        depends=DEPENDS,
        help='The legal person or organization acting the artist')
    group = fields.Boolean(
        'Group',
        states={
            'readonly':
            Or(
                ~Eval('active'),
                Bool(Eval('group_artists')),
                Bool(Eval('solo_artists')),
            ),
        },
        depends=DEPENDS + ['group_artists', 'solo_artists'],
        help='Check, if artist is a group of other artists, '
        'otherwise the artist is a solo artist')
    solo_artists = fields.Many2Many(
        'artist-artist',
        'group_artist',
        'solo_artist',
        'Solo Artists',
        domain=[
            ('group', '=', False),
        ],
        states={
            'readonly': ~Eval('active'),
            'invisible': ~Eval('group'),
        },
        depends=['active', 'group'],
        help='The membering solo artists of this group')
    group_artists = fields.Many2Many(
        'artist-artist',
        'solo_artist',
        'group_artist',
        'Group Artists',
        domain=[
            ('group', '=', True),
        ],
        states={
            'readonly': ~Eval('active'),
            'invisible': Bool(Eval('group')),
        },
        depends=['active', 'group'],
        help='The groups this solo artist is member of')
    access_parties = fields.Function(
        fields.Many2Many(
            'party.party',
            None,
            None,
            'Access Parties',
            help='Shows the collection of all parties with access '
            'permissions to this artist'), 'get_access_parties')
    invitation_token = fields.Char('Invitation Token',
                                   help='The invitation token of a web user '
                                   'to claim this artist')
    description = fields.Text('Description',
                              states=STATES,
                              depends=DEPENDS,
                              help='A description of the artist')
    picture_data = fields.Binary('Picture Data',
                                 states=STATES,
                                 depends=DEPENDS,
                                 help='Picture data of a photograph or logo')
    picture_data_mime_type = fields.Char('Picture Data Mime Type',
                                         states=STATES,
                                         depends=DEPENDS,
                                         help='The mime type of picture data.')
    payee = fields.Many2One(
        'party.party',
        'Payee',
        domain=[('id', 'in', Eval('access_parties', []))],
        states={
            'readonly': Or(
                ~Eval('active'),
                Bool(Eval('valid_payee')),
            ),
        },
        depends=DEPENDS + ['access_parties', 'group_artists', 'solo_artists'],
        help='The actual payee party of this artist which is '
        'responsible for earnings of this artist')
    payee_proposal = fields.Many2One(
        'party.party',
        'Payee Proposal',
        domain=[('id', 'in', Eval('access_parties', []))],
        states=STATES,
        depends=DEPENDS + ['access_parties'],
        help='The proposed payee party of this artist')
    payee_acceptances = fields.Many2Many(
        'artist.payee.acceptance',
        'artist',
        'party',
        'Payee Acceptances',
        domain=[('id', 'in', Eval('access_parties', []))],
        states=STATES,
        depends=DEPENDS + ['access_parties'],
        help='The parties which accepts the payee proposal')
    valid_payee = fields.Boolean(
        'Valid Payee',
        states={
            'readonly': Or(
                ~Eval('active'),
                ~Bool(Eval('payee')),
            ),
        },
        depends=DEPENDS + ['payee'],
        help='Check, if the payee is manually validated by '
        'administration.')
    bank_account_number = fields.Many2One(
        'bank.account.number',
        'Bank Account Number',
        states={'readonly': ~Bool(Eval('payee'))},
        domain=[('id', 'in', Eval('bank_account_numbers'))],
        depends=['payee', 'bank_account_numbers'],
        help='The bank account number for this artist')
    bank_account_numbers = fields.Function(
        fields.Many2Many(
            'bank.account.number',
            None,
            None,
            'Bank Account Numbers',
            help='Shows the collection of all available bank account '
            'numbers of this artist'), 'on_change_with_bank_account_numbers')
    bank_account_owner = fields.Function(
        fields.Many2One('party.party',
                        'Bank Account Owner',
                        states={
                            'readonly':
                            (Or(~Bool(Eval('payee')),
                                ~Bool(Eval('bank_account_number'))))
                        },
                        help='Shows the bank account owner for this artist',
                        depends=['payee', 'bank_account_number']),
        'on_change_with_bank_account_owner')
    hat_account = fields.Property(
        fields.Many2One('account.account',
                        'Hat Account',
                        domain=[
                            ('kind', '=', 'hat'),
                            ('company', '=', Eval('context',
                                                  {}).get('company', -1)),
                        ],
                        states={
                            'required':
                            Bool(Eval('context', {}).get('company')),
                            'invisible': ~Eval('context', {}).get('company'),
                        }))
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')
    hat_balance = fields.Function(fields.Numeric(
        'Hat Account Balance',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                                  'get_hat_balance',
                                  searcher='search_hat_balance')
    active = fields.Boolean('Active')

    @classmethod
    def __setup__(cls):
        super(Artist, cls).__setup__()
        cls._sql_constraints = [
            ('code_uniq', 'UNIQUE(code)',
             'The code of the Artist must be unique.'),
            ('invitation_token_uniq', 'UNIQUE(invitation_token)',
             'The invitation token of the artist must be unique.'),
        ]
        cls._error_messages.update({
            'wrong_name': ('Invalid Artist name "%%s": You can not use '
                           '"%s" in name field.' % SEPARATOR),
        })
        cls._order.insert(1, ('name', 'ASC'))

    @staticmethod
    def default_invitation_token():
        return str(uuid.uuid4())

    @classmethod
    def validate(cls, artists):
        super(Artist, cls).validate(artists)
        for artist in artists:
            artist.check_name()

    def check_name(self):
        if SEPARATOR in self.name:
            self.raise_user_error('wrong_name', (self.name, ))

    @staticmethod
    def order_code(tables):
        table, _ = tables[None]
        return [CharLength(table.code), table.code]

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_payee_validation_state():
        return 'accepted'

    @classmethod
    def get_access_parties(cls, artists, name):
        parties = {}
        for artist in artists:
            parties[artist.id] = []
            if artist.party:
                parties[artist.id] += [artist.party.id]
            if artist.solo_artists:
                for solo_artist in artist.solo_artists:
                    if solo_artist.party:
                        parties[artist.id] += [solo_artist.party.id]
        return parties

    @fields.depends('party', 'access_parties')
    def on_change_with_bank_account_numbers(self, name=None):
        BankAccountNumber = Pool().get('bank.account.number')

        bank_account_numbers = []
        numbers = BankAccountNumber.search([
            'OR',
            [('account.owner.id', 'in', [p.id for p in self.access_parties])],
            [
                ('account.owner.id', '=',
                 self.party.id if self.party else None),
            ]
        ])
        if numbers:
            bank_account_numbers = [n.id for n in numbers]
        else:
            bank_account_numbers = None
        return bank_account_numbers

    @fields.depends('bank_account_number')
    def on_change_with_bank_account_owner(self, name=None):
        if self.bank_account_number:
            bank_account_owner = (self.bank_account_number.account.owner.id)
        else:
            bank_account_owner = None
        return bank_account_owner

    @fields.depends('payee')
    def on_change_payee(self):
        changes = {}
        if self.payee:
            changes = {
                'payee_acceptances': {
                    'remove': [a.id for a in self.payee_acceptances]
                },
                'valid_payee': False,
                'payee_proposal': None
            }
        return changes

    @fields.depends('payee_proposal')
    def on_change_payee_proposal(self):
        changes = {}
        if self.payee_proposal:
            changes = {
                'payee_acceptances': {
                    'remove': [a.id for a in self.payee_acceptances]
                },
                'valid_payee': False
            }
        return changes

    @classmethod
    def get_hat_balance(cls, artists, names):
        WebUser = Pool().get('web.user')

        result = WebUser.get_balance(items=artists, names=names)
        return result

    def get_currency_digits(self, name):
        Company = Pool().get('company.company')
        if Transaction().context.get('company'):
            company = Company(Transaction().context['company'])
            return company.currency.digits
        return 2

    @classmethod
    def search_hat_balance(cls, name, clause):
        WebUser = Pool().get('web.user')

        result = WebUser.search_balance(name, clause)
        return result

    @classmethod
    def create(cls, vlist):
        Sequence = Pool().get('ir.sequence')
        Configuration = Pool().get('collecting_society.configuration')

        vlist = [x.copy() for x in vlist]
        for values in vlist:
            if not values.get('code'):
                config = Configuration(1)
                values['code'] = Sequence.get_id(config.artist_sequence.id)
        return super(Artist, cls).create(vlist)

    @classmethod
    def copy(cls, artists, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default['code'] = None
        return super(Artist, cls).copy(artists, default=default)

    @classmethod
    def search_rec_name(cls, name, clause):
        return [
            'OR',
            ('code', ) + tuple(clause[1:]),
            ('name', ) + tuple(clause[1:]),
        ]
Example #15
0
from flask.ext.babel import format_currency

from trytond.model import ModelSQL, ModelView, fields
from trytond.pyson import Eval, Not, Bool
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
from trytond import backend
from sql import Null

__all__ = [
    'Product', 'ProductsRelated', 'ProductTemplate', 'ProductMedia',
    'ProductCategory'
]
__metaclass__ = PoolMeta

DEFAULT_STATE = {'invisible': Not(Bool(Eval('displayed_on_eshop')))}
DEFAULT_STATE2 = {
    'invisible': Not(Bool(Eval('displayed_on_eshop'))),
    'required': Bool(Eval('displayed_on_eshop')),
}


class ProductMedia(ModelSQL, ModelView):
    "Product Media"
    __name__ = "product.media"

    sequence = fields.Integer("Sequence", required=True, select=True)
    static_file = fields.Many2One("nereid.static.file",
                                  "Static File",
                                  required=True,
                                  select=True)
Example #16
0
class SaleChannel(ModelSQL, ModelView):
    """
    Sale Channel model
    """
    __name__ = 'sale.channel'

    name = fields.Char('Name',
                       required=True,
                       select=True,
                       states=STATES,
                       depends=DEPENDS)
    code = fields.Char('Code',
                       select=True,
                       states={'readonly': Eval('code', True)},
                       depends=['code'])
    active = fields.Boolean('Active', select=True)
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              select=True,
                              states=STATES,
                              depends=DEPENDS)
    address = fields.Many2One('party.address',
                              'Address',
                              domain=[
                                  ('party', '=', Eval('company_party')),
                              ],
                              depends=['company_party'])
    source = fields.Selection('get_source',
                              'Source',
                              required=True,
                              states=STATES,
                              depends=DEPENDS)

    read_users = fields.Many2Many('sale.channel-read-res.user', 'channel',
                                  'user', 'Read Users')
    create_users = fields.Many2Many('sale.channel-write-res.user', 'channel',
                                    'user', 'Create Users')

    warehouse = fields.Many2One('stock.location',
                                "Warehouse",
                                required=True,
                                domain=[('type', '=', 'warehouse')])
    invoice_method = fields.Selection([('manual', 'Manual'),
                                       ('order', 'On Order Processed'),
                                       ('shipment', 'On Shipment Sent')],
                                      'Invoice Method',
                                      required=True)
    shipment_method = fields.Selection([
        ('manual', 'Manual'),
        ('order', 'On Order Processed'),
        ('invoice', 'On Invoice Paid'),
    ],
                                       'Shipment Method',
                                       required=True)
    currency = fields.Many2One('currency.currency', 'Currency', required=True)
    price_list = fields.Many2One('product.price_list',
                                 'Price List',
                                 required=True)
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   'Payment Term',
                                   required=True)
    company_party = fields.Function(
        fields.Many2One('party.party', 'Company Party'),
        'on_change_with_company_party')
    taxes = fields.One2Many("sale.channel.tax", "channel", "Taxes")

    # These fields would be needed at the time of product imports from
    # external channel
    default_uom = fields.Many2One('product.uom',
                                  'Default Product UOM',
                                  states=PRODUCT_STATES,
                                  depends=['source'])

    exceptions = fields.One2Many('channel.exception', 'channel', 'Exceptions')

    order_states = fields.One2Many("sale.channel.order_state", "channel",
                                   "Order States")

    shipping_carriers = fields.One2Many('sale.channel.carrier', 'channel',
                                        'Shipping Carriers')

    last_order_import_time = fields.DateTime(
        'Last Order Import Time',
        depends=['source', 'last_order_import_time_required'],
        states={
            'invisible': Eval('source') == 'manual',
            'required': Bool(Eval('last_order_import_time_required'))
        })
    last_order_export_time = fields.DateTime("Last Order Export Time",
                                             states=INVISIBLE_IF_MANUAL,
                                             depends=['source'])

    last_shipment_export_time = fields.DateTime('Last shipment export time',
                                                states=INVISIBLE_IF_MANUAL,
                                                depends=['source'])
    last_product_price_export_time = fields.DateTime(
        'Last Product Price Export Time',
        states=INVISIBLE_IF_MANUAL,
        depends=['source'])
    last_product_export_time = fields.DateTime('Last Product Export Time',
                                               states=INVISIBLE_IF_MANUAL,
                                               depends=['source'])

    last_order_import_time_required = fields.Function(
        fields.Boolean('Last Order Import Time Required'),
        'get_last_order_import_time_required')

    last_inventory_export_time = fields.DateTime('Last Inventory Export Time',
                                                 states=INVISIBLE_IF_MANUAL,
                                                 depends=['source'])

    payment_gateways = fields.One2Many('sale.channel.payment_gateway',
                                       'channel', 'Payment Gateways')

    timezone = fields.Selection(TIMEZONES,
                                'Timezone',
                                translate=False,
                                required=True)
    # This field is to set according to sequence
    sequence = fields.Integer('Sequence', select=True)

    invoice_tax_account = fields.Many2One(
        'account.account',
        'Invoice Tax Account',
        domain=[
            ('company', '=', Eval('company')),
            ('kind', 'not in', ['view', 'receivable', 'payable']),
        ],
        depends=['company'],
        help="GL to book for taxes when new taxes are created from channel",
    )
    credit_note_tax_account = fields.Many2One(
        'account.account',
        'Credit Note Tax Account',
        domain=[
            ('company', '=', Eval('company')),
            ('kind', 'not in', ['view', 'receivable', 'payable']),
        ],
        depends=['company'],
        help="GL to book for taxes when new taxes are created from channel",
    )

    @staticmethod
    def default_timezone():
        return 'UTC'

    @staticmethod
    def default_sequence():
        return 10

    def get_last_order_import_time_required(self, name):
        """
        Returns True or False if last_order_import_time field should be required
        or not
        """
        return False

    @classmethod
    def view_attributes(cls):
        return super(SaleChannel, cls).view_attributes() + [
            ('//page[@id="configuration"]', 'states', {
                'invisible': Eval('source') == 'manual',
            }),
            ('//page[@id="last_import_export_time"]', 'states', {
                'invisible': Eval('source') == 'manual',
            }),
            ('//page[@id="product_defaults"]', 'states', {
                'invisible': Eval('source') == 'manual',
            }),
            ('//page[@id="order_states"]', 'states', {
                'invisible': Eval('source') == 'manual',
            }),
            ('//page[@id="import_export_buttons"]', 'states', {
                'invisible': Eval('source') == 'manual',
            })
        ]

    @classmethod
    def __setup__(cls):
        """
        Setup the class before adding to pool
        """
        super(SaleChannel, cls).__setup__()
        cls._buttons.update({
            'import_data_button': {},
            'export_data_button': {},
            'import_order_states_button': {},
            'import_shipping_carriers': {},
        })
        cls._error_messages.update({
            "no_carriers_found":
            "Shipping carrier is not configured for code: %s",
            "no_tax_found":
            "%s (tax) of rate %f was not found.",
            "no_order_states_to_import":
            "No importable order state found\n"
            "HINT: Import order states from Order States tab in Channel"
        })
        cls._order.insert(0, ('sequence', 'ASC'))

    @staticmethod
    def default_default_uom():
        Uom = Pool().get('product.uom')

        unit = Uom.search([('name', '=', 'Unit')])

        return unit and unit[0].id or None

    @classmethod
    def get_source(cls):
        """
        Get the source
        """
        return [('manual', 'Manual')]

    @staticmethod
    def default_last_order_import_time():
        return datetime.utcnow() - relativedelta(months=1)

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_currency():
        pool = Pool()
        Company = pool.get('company.company')
        Channel = pool.get('sale.channel')

        company_id = Channel.default_company()
        return company_id and Company(company_id).currency.id

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @fields.depends('company')
    def on_change_with_company_party(self, name=None):
        Company = Pool().get('company.company')
        company = self.company
        if not company:
            company = Company(SaleChannel.default_company())  # pragma: nocover
        return company and company.party.id or None

    @classmethod
    def get_current_channel(cls):
        """Helper method to get the current current_channel.
        """
        return cls(Transaction().context['current_channel'])

    @classmethod
    @ModelView.button
    def import_shipping_carriers(cls, channels):
        """
        Create shipping carriers by importing data from external channel.

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to reuse this method or call super.

        :param instances: Active record list of magento instances
        """
        raise NotImplementedError("This feature has not been implemented.")

    def get_shipping_carrier(self, code, silent=False):
        """
        Search for an existing carrier by matching code and channel.
        If found, return its active record else raise_user_error.
        """
        SaleCarrierChannel = Pool().get('sale.channel.carrier')

        try:
            carrier, = SaleCarrierChannel.search([
                ('code', '=', code),
                ('channel', '=', self.id),
            ])
        except ValueError:
            if silent:
                return None
            self.raise_user_error('no_carriers_found', error_args=code)
        else:
            return carrier.carrier

    def get_shipping_carrier_service(self, code, silent=False):
        """
        Search for an existing carrier service by matching code and channel.
        If found, return its active record else raise_user_error.
        """
        SaleCarrierChannel = Pool().get('sale.channel.carrier')

        try:
            carrier, = SaleCarrierChannel.search([
                ('code', '=', code),
                ('channel', '=', self.id),
            ])
        except ValueError:
            if silent:
                return None
            self.raise_user_error('no_carriers_found', error_args=code)
        else:
            return carrier.carrier_service

    def get_order_states_to_import(self):
        """
        Return list of `sale.channel.order_state` to import orders
        """
        OrderState = Pool().get('sale.channel.order_state')

        order_states_to_import = ['process_automatically', 'process_manually']
        if Transaction().context.get('include_past_orders', False):
            order_states_to_import.append('import_as_past')

        order_states = OrderState.search([
            ('action', 'in', order_states_to_import),
            ('channel', '=', self.id),
        ])
        if not order_states:
            self.raise_user_error("no_order_states_to_import")
        return order_states

    def export_product_prices(self):
        """
        Export product prices to channel

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to reuse this method or call super.

        :return: List of active records of products for which prices are
        exported
        """
        raise NotImplementedError(
            "This feature has not been implemented for %s channel yet." %
            self.source)

    def export_order_status(self):
        """
        Export order status to external channel

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to reuse this method or call super.

        :return: List of active records of orders exported
        """
        raise NotImplementedError(
            "This feature has not been implemented for %s channel yet." %
            self.source)

    @classmethod
    def import_orders_using_cron(cls):  # pragma: nocover
        """
        Cron method to import orders from channels using cron

        Downstream module need not to implement this method.
        It will automatically call import_orders of the channel
        Silently pass if import_orders is not implemented
        """
        for channel in cls.search([]):
            with Transaction().set_context(company=channel.company.id):
                try:
                    channel.import_orders()
                except NotImplementedError:
                    # Silently pass if method is not implemented
                    pass

    @classmethod
    def export_product_prices_using_cron(cls):  # pragma: nocover
        """
        Cron method to export product prices to external channel using cron

        Downstream module need not to implement this method.
        It will automatically call export_product_prices method of the channel.
        Silently pass if export_product_prices is not implemented
        """
        for channel in cls.search([]):
            with Transaction().set_context(company=channel.company.id):
                try:
                    channel.export_product_prices()
                except NotImplementedError:
                    # Silently pass if method is not implemented
                    pass

    @classmethod
    def export_order_status_using_cron(cls):
        """
        Export sales orders status to external channel using cron
        """
        for channel in cls.search([]):
            try:
                channel.export_order_status()
            except NotImplementedError:
                pass

    def get_listings_to_export_inventory(self):
        """
        This method returns listing, which needs inventory update

        Downstream module can override change its implementation

        :return: List of AR of `product.product.channel_listing`
        """
        ChannelListing = Pool().get('product.product.channel_listing')
        cursor = Transaction().connection.cursor()

        if not self.last_inventory_export_time:
            # Return all active listings
            return ChannelListing.search([('channel', '=', self),
                                          ('state', '=', 'active')])
        else:
            # Query to find listings
            #   in which product inventory is recently updated or
            #   listing it self got updated recently
            cursor.execute(
                """
                SELECT listing.id
                FROM product_product_channel_listing AS listing
                INNER JOIN stock_move ON stock_move.product = listing.product
                WHERE listing.channel = %s AND listing.state = 'active' AND
                (
                    COALESCE(stock_move.write_date, stock_move.create_date) > %s
                    OR
                    COALESCE(listing.write_date, listing.create_date) > %s
                )
                GROUP BY listing.id
            """, (
                    self.id,
                    self.last_inventory_export_time,
                    self.last_inventory_export_time,
                ))
            listing_ids = map(lambda r: r[0], cursor.fetchall())
            return ChannelListing.browse(listing_ids)

    def export_inventory(self):
        """
        Export inventory to external channel
        """
        Listing = Pool().get('product.product.channel_listing')
        Channel = Pool().get('sale.channel')

        last_inventory_export_time = datetime.utcnow()
        channel_id = self.id

        listings = self.get_listings_to_export_inventory()
        # TODO: check if inventory export is allowed for this channel
        Listing.export_bulk_inventory(listings)

        # XXX: Exporting inventory to external channel is an expensive.
        # To avoid lock on sale_channel table save record after
        # exporting all inventory
        with Transaction().new_transaction() as txn:
            channel = Channel(channel_id)
            channel.last_inventory_export_time = last_inventory_export_time
            channel.save()
            txn.commit()

    @classmethod
    def export_inventory_from_cron(cls):  # pragma: nocover
        """
        Cron method to export inventory to external channel
        """
        for channel in cls.search([]):
            with Transaction().set_context(company=channel.company.id):
                try:
                    channel.export_inventory()
                except NotImplementedError:
                    # Silently pass if method is not implemented
                    pass

    def import_orders(self):
        """
        Import orders from external channel.

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement importing or call
        super to delegate.

        :return: List of active records of sale orders that are imported
        """
        raise NotImplementedError(
            "Import orders is not implemented for %s channels" % self.source)

    def import_order(self, order_info):
        """
        Import specific order from external channel based on order_info.

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement importing or call
        super to delegate.

        :param order_info: The type of order_info depends on the channel. It
                           could be an integer ID, a dictionary or anything.

        :return: imported sale order active record
        """
        raise NotImplementedError(
            "Import order is not implemented for %s channels" % self.source)

    def import_products(self):
        """
        Import Products from external channel.

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement importing or call
        super to delegate.

        :return: List of active records of products that are imported
        """
        raise NotImplementedError(
            "Method import_products is not implemented for %s channel yet" %
            self.source)  # pragma: nocover

    def import_product(self, identifier, product_data=None):
        """
        Import specific product from external channel based on product
        identifier.

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement importing or call
        super to delegate.

        :param identifier: product code or sku

        :return: imported product active record
        """
        raise NotImplementedError(
            "Method import_product is not implemented for %s channel yet" %
            self.source)  # pragma: nocover

    def get_product(self, identifier, product_data=None):
        """
        Given a SKU find the product or if it aint there create it and then
        return the active record of the product. This cannot be done async
        under any circumstances, because a product created on another
        transaction will not be visible to the current transaction unless the
        transaction is started over.

        :param identifier: product identifier
        """
        return self.import_product(identifier, product_data)

    @classmethod
    @ModelView.button_action('sale_channel.wizard_import_data')
    def import_data_button(cls, channels):
        pass  # pragma: nocover

    @classmethod
    @ModelView.button_action('sale_channel.wizard_export_data')
    def export_data_button(cls, channels):
        pass  # pragma: nocover

    @classmethod
    @ModelView.button_action('sale_channel.wizard_import_order_states')
    def import_order_states_button(cls, channels):
        """
        Import order states for current channel

        :param channels: List of active records of channels
        """
        pass

    def import_order_states(self):
        """
        Imports order states for current channel

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement importing or call
        super to delegate.
        """
        raise NotImplementedError(
            "This feature has not been implemented for %s channel yet" %
            self.source)

    def get_default_tryton_action(self, code, name=None):
        """
        Return default tryton_actions for this channel
        """
        return {
            'action': 'do_not_import',
            'invoice_method': 'manual',
            'shipment_method': 'manual',
        }

    def get_tryton_action(self, code):
        """
        Get the tryton action corresponding to the channel state
        as per the predefined logic.

        Downstream modules need to inherit method and map states as per
        convenience.

        :param code: Code of channel state
        :returns: A dictionary of tryton action and shipment and invoice methods
        """
        ChannelOrderState = Pool().get('sale.channel.order_state')

        try:
            order_state, = ChannelOrderState.search([
                ('channel', '=', self.id),
                ('code', '=', code),
            ])
        except ValueError:
            return {
                'action': 'do_not_import',
                'invoice_method': 'manual',
                'shipment_method': 'manual',
            }
        else:
            return {
                'action': order_state.action,
                'invoice_method': order_state.invoice_method,
                'shipment_method': order_state.shipment_method,
            }

    def create_order_state(self, code, name):
        """
        This method creates order state for channel with given state code and
        state name. If state already exist, return same.

        :param code: State code used by external channel
        :param name: State name used by external channel
        :return: Active record of order state created or found
        """
        OrderState = Pool().get('sale.channel.order_state')

        order_states = OrderState.search([('code', '=', code),
                                          ('channel', '=', self.id)])

        if order_states:
            return order_states[0]

        values = self.get_default_tryton_action(code, name)
        values.update({
            'name': name,
            'code': code,
            'channel': self.id,
        })

        return OrderState.create([values])[0]

    def get_availability_context(self):
        """
        Return the context in which the stock availability of any product must
        be computed
        """
        return {
            'locations': [self.warehouse.id],
        }

    def get_availability(self, product):
        """
        Return availability of the product within the context of this channel

        Availability consists of three factors:

            type: finite, bucket, infinite
            quantity: (optional) quantity available
            value: in_stock, limited, out_of_stock

        If this looks like the value in the stripe relay API, do not be
        confused, they are the same :)
        """
        Listing = Pool().get('product.product.channel_listing')
        Product = Pool().get('product.product')

        listings = Listing.search([
            ('channel', '=', self.id),
            ('product', '=', product),
        ])
        if listings:
            # If there are listings, return the values from listing since
            # they override channel defaults for a product and channel
            return listings[0].get_availability()

        with Transaction().set_context(**self.get_availability_context()):
            rv = {'type': 'bucket'}
            quantity = Product.get_quantity([product], 'quantity')[product.id]
            if quantity > 0:
                rv['value'] = 'in_stock'
            else:
                rv['value'] = 'out_of_stock'

        return rv

    @classmethod
    def update_order_status_using_cron(cls):  # pragma: nocover
        """
        Cron method to update orders from channels using cron

        Downstream module need not to implement this method.
        It will automatically call update_order_status of the channel
        Silently pass if update_order_status is not implemented
        """
        for channel in cls.search([]):
            with Transaction().set_context(company=channel.company.id):
                try:
                    channel.update_order_status()
                except NotImplementedError:
                    # Silently pass if method is not implemented
                    pass

    def update_order_status(self):
        """This method is responsible for updating order status from external
        channel.
        """
        if self.source == 'manual':
            return
        raise NotImplementedError(
            "This feature has not been implemented for %s channel yet." %
            self.source)

    def get_tax(self, name, rate, silent=False):
        """
        Search for an existing Tax record by matching name and rate.
        If found return its active record else raise user error.
        """
        TaxMapping = Pool().get('sale.channel.tax')
        domain = [('rate', '=', rate), ('channel', '=', self)]
        if name:
            # Search with name when it is provided
            # Name can be explicitly passed as None, when external
            # channels like magento does not provide it.
            domain.append(('name', '=', name))

        try:
            mapped_tax, = TaxMapping.search(domain)
        except ValueError:
            if silent:
                return None
            self.raise_user_error('no_tax_found', error_args=(name, rate))
        else:
            return mapped_tax.tax
Example #17
0
class Product:
    "Product extension for Nereid"
    __name__ = "product.product"

    #: Decides the number of products that would be remebered.
    recent_list_size = 5

    #: The list of fields allowed to be sent back on a JSON response from the
    #: application. This is validated before any product info is built
    #:
    #: The `name`, `sale_price`, `id` and `uri` are sent by default
    #:
    #: .. versionadded:: 0.3
    json_allowed_fields = set(['rec_name', 'sale_price', 'id', 'uri'])

    uri = fields.Char('URI', select=True, states=DEFAULT_STATE2)

    displayed_on_eshop = fields.Boolean('Displayed on E-Shop?', select=True)
    long_description = fields.Text('Long Description')
    media = fields.One2Many(
        "product.media",
        "product",
        "Media",
        states={'invisible': Bool(Eval('use_template_images'))},
        depends=['use_template_images'])
    images = fields.Function(fields.One2Many('nereid.static.file', None,
                                             'Images'),
                             getter='get_product_images')
    up_sells = fields.Many2Many('product.product-product.product',
                                'product',
                                'up_sell',
                                'Up-Sells',
                                states=DEFAULT_STATE)
    cross_sells = fields.Many2Many('product.product-product.product',
                                   'product',
                                   'cross_sell',
                                   'Cross-Sells',
                                   states=DEFAULT_STATE)
    default_image = fields.Function(
        fields.Many2One('nereid.static.file', 'Image'),
        'get_default_image',
    )
    use_template_description = fields.Boolean("Use template's description")
    use_template_images = fields.Boolean("Use template's images")

    @classmethod
    def view_attributes(cls):
        return super(Product, cls).view_attributes() + [
            ('//page[@id="desc"]', 'states', {
                'invisible': Bool(Eval('use_template_description'))
            }),
            ('//page[@id="ecomm_det"]', 'states', {
                'invisible': Not(Bool(Eval('displayed_on_eshop')))
            }),
            ('//page[@id="related_products"]', 'states', {
                'invisible': Not(Bool(Eval('displayed_on_eshop')))
            })
        ]

    @classmethod
    def copy(cls, products, default=None):
        """Duplicate products
        """
        if default is None:
            default = {}
        default = default.copy()

        duplicate_products = []
        for index, product in enumerate(products, start=1):
            if product.displayed_on_eshop:
                default['uri'] = "%s-copy-%d" % (product.uri, index)

            duplicate_products.extend(
                super(Product, cls).copy([product], default))

        return duplicate_products

    @classmethod
    def validate(cls, products):
        super(Product, cls).validate(products)
        cls.check_uri_uniqueness(products)

    def get_default_image(self, name):
        """
        Returns default product image if any.
        """
        return self.images[0].id if self.images else None

    @classmethod
    def __setup__(cls):
        super(Product, cls).__setup__()
        cls.description.states['invisible'] = Bool(
            Eval('use_template_description'))
        cls._error_messages.update({
            'unique_uri': ('URI of Product must be Unique'),
        })
        cls.per_page = 12

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().cursor
        table = TableHandler(cursor, cls, module_name)

        # Drop contraint for Unique URI on database
        # Now using validation method for that purpose
        table.drop_constraint('uri_uniq')

        super(Product, cls).__register__(module_name)

    @staticmethod
    def default_displayed_on_eshop():
        return False

    @fields.depends('template', 'uri')
    def on_change_with_uri(self):
        """
        If the URI is empty, slugify template name into URI
        """
        if not self.uri and self.template:
            return slugify(self.template.name)
        return self.uri

    @staticmethod
    def default_use_template_description():
        return True

    @staticmethod
    def default_use_template_images():
        return True

    @classmethod
    def check_uri_uniqueness(cls, products):
        """
        Ensure uniqueness of products uri.
        """
        query = ['OR']
        for product in products:
            # Do not check for unique uri if product is marked as
            # not displayed on eshop
            if not product.displayed_on_eshop:
                continue

            arg = [
                'AND', [('id', '!=', product.id)],
                [('uri', 'ilike', product.uri)]
            ]
            query.append(arg)
        if query != ['OR'] and cls.search(query):
            cls.raise_user_error('unique_uri')

    @classmethod
    @route('/product/<uri>')
    @route('/product/<path:path>/<uri>')
    def render(cls, uri, path=None):
        """Renders the template for a single product.

        :param uri: URI of the product
        :param path: Ignored parameter. This is used in
                     cases where SEO friendly URL like
                     product/category/sub-cat/sub-sub-cat/product-uri
                     are generated
        """
        products = cls.search([
            ('displayed_on_eshop', '=', True),
            ('uri', '=', uri),
            ('template.active', '=', True),
        ],
                              limit=1)
        if not products:
            return NotFound('Product Not Found')

        cls._add_to_recent_list(int(products[0]))
        return render_template('product.jinja', product=products[0])

    @classmethod
    @route('/products/+recent', methods=['GET', 'POST'])
    def recent_products(cls):
        """
        GET
        ---

        Return a list of recently visited products in JSON

        POST
        ----

        Add the product to the recent list manually. This method is required
        if the product page is cached, or is served by a Caching Middleware
        like Varnish which may clear the session before sending the request to
        Nereid.

        Just as with GET the response is the AJAX of recent products
        """
        if request.method == 'POST':
            cls._add_to_recent_list(request.form.get('product_id', type=int))

        fields = set(request.args.getlist('fields')) or cls.json_allowed_fields
        fields = fields & cls.json_allowed_fields

        if 'sale_price' in fields:
            fields.remove('sale_price')

        response = []
        if hasattr(session, 'sid'):
            products = cls.browse(session.get('recent-products', []))
            for product in products:
                product_val = {}
                for field in fields:
                    product_val[field] = getattr(product, field)
                product_val['sale_price'] = format_currency(
                    product.sale_price(), request.nereid_currency.code)
                response.append(product_val)

        return jsonify(products=response)

    @classmethod
    def _add_to_recent_list(cls, product_id):
        """Adds the given product ID to the list of recently viewed products
        By default the list size is 5. To change this you can inherit
        product.product and set :attr:`recent_list_size` attribute to a
        non negative integer value

        For faster and easier access the products are stored with the ids alone
        this behaviour can be modified by subclassing.

        The deque object cannot be saved directly in the cache as its not
        serialisable. Hence a conversion to list is made on the fly

        .. versionchanged:: 0.3
            If there is no session for the user this function returns an empty
            list. This ensures that the code is consistent with iterators that
            may use the returned value

        :param product_id: the product id to prepend to the list
        """
        if not hasattr(session, 'sid'):
            current_app.logger.warning(
                "No session. Not saving to browsing history")
            return []

        recent_products = deque(session.setdefault('recent-products', []),
                                cls.recent_list_size)
        # XXX: If a product is already in the recently viewed list, but it
        # would be nice to remember the recent_products list in the order of
        # visits.
        if product_id not in recent_products:
            recent_products.appendleft(product_id)
            session['recent-products'] = list(recent_products)
        return recent_products

    @classmethod
    @route('/products')
    @route('/products/<int:page>')
    def render_list(cls, page=1):
        """
        Renders the list of all products which are displayed_on_shop=True

        .. tip::

            The implementation uses offset for pagination and could be
            extremely resource intensive on databases. Hence you might want to
            either have an alternate cache/search server based pagination or
            limit the pagination to a maximum page number.

            The base implementation does NOT limit this and could hence result
            in poor performance

        :param page: The page in pagination to be displayed
        """

        products = Pagination(cls, [
            ('displayed_on_eshop', '=', True),
            ('template.active', '=', True),
        ], page, cls.per_page)
        return render_template('product-list.jinja', products=products)

    def sale_price(self, quantity=0):
        """Return the Sales Price.
        A wrapper designed to work as a context variable in templating

        The price is calculated from the pricelist associated with the current
        user. The user in the case of guest user is logged in user. In the
        event that the logged in user does not have a pricelist set against
        the user, the guest user's pricelist is chosen.

        Finally if neither the guest user, nor the regsitered user has a
        pricelist set against them then the list price is displayed as the
        price of the product

        :param quantity: Quantity
        """
        return self.list_price

    @classmethod
    @route('/sitemaps/product-index.xml')
    def sitemap_index(cls):
        """
        Returns a Sitemap Index Page
        """
        index = SitemapIndex(cls, [
            ('displayed_on_eshop', '=', True),
            ('template.active', '=', True),
        ])
        return index.render()

    @classmethod
    @route('/sitemaps/product-<int:page>.xml')
    def sitemap(cls, page):
        sitemap_section = SitemapSection(cls, [
            ('displayed_on_eshop', '=', True),
            ('template.active', '=', True),
        ], page)
        sitemap_section.changefreq = 'daily'
        return sitemap_section.render()

    def get_absolute_url(self, **kwargs):
        """
        Return the URL of the current product.

        This method works only under a nereid request context
        """
        return url_for('product.product.render', uri=self.uri, **kwargs)

    def _json(self):
        """
        Return a JSON serializable dictionary of the product
        """
        response = {
            'template': {
                'name': self.template.rec_name,
                'id': self.template.id,
                'list_price': self.list_price,
            },
            'code': self.code,
            'description': self.description,
        }
        return response

    def get_long_description(self):
        """
        Get long description of product.

        If the product is set to use the template's long description, then
        the template long description is sent back.

        The returned value is a `~jinja2.Markup` object which makes it
        HTML safe and can be used directly in templates. It is recommended
        to use this method instead of trying to wrap this logic in the
        templates.
        """
        if self.use_template_description:
            description = self.template.long_description
        else:
            description = self.long_description

        return Markup(description or '')

    def get_description(self):
        """
        Get description of product.

        If the product is set to use the template's description, then
        the template description is sent back.

        The returned value is a `~jinja2.Markup` object which makes it
        HTML safe and can be used directly in templates. It is recommended
        to use this method instead of trying to wrap this logic in the
        templates.
        """
        if self.use_template_description:
            description = self.template.description
        else:
            description = self.description
        return Markup(description or '')

    def get_product_images(self, name=None):
        """
        Getter for `images` function field
        """
        product_images = []
        for media in self.media:
            if 'image' in media.static_file.mimetype:
                product_images.append(media.static_file.id)
        return product_images

    def get_images(self):
        """
        Get images of product variant.
        If the product is set to use the template's images, then
        the template images is sent back.
        """
        if self.use_template_images:
            return self.template.images
        return self.images
Example #18
0
class SalePromotion(ModelSQL, ModelView, MatchMixin):
    'Sale Promotion'
    __name__ = 'sale.promotion'

    name = fields.Char('Name', translate=True, required=True)
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        states={
            'readonly': Eval('id', 0) > 0,
        },
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        select=True)
    active = fields.Boolean('Active')
    start_date = fields.Date('Start Date',
                             domain=[
                                 'OR',
                                 ('start_date', '<=',
                                  If(~Eval('end_date', None),
                                     datetime.date.max,
                                     Eval('end_date', datetime.date.max))),
                                 ('start_date', '=', None),
                             ],
                             depends=['end_date'])
    end_date = fields.Date('End Date',
                           domain=[
                               'OR',
                               ('end_date', '>=',
                                If(~Eval('start_date', None),
                                   datetime.date.min,
                                   Eval('start_date', datetime.date.min))),
                               ('end_date', '=', None),
                           ],
                           depends=['start_date'])
    price_list = fields.Many2One('product.price_list',
                                 'Price List',
                                 ondelete='CASCADE',
                                 domain=[
                                     ('company', '=', Eval('company', -1)),
                                 ],
                                 depends=['company'])
    quantity = fields.Float('Quantity',
                            digits=(16, Eval('unit_digits', 2)),
                            depends=['unit_digits'])
    unit = fields.Many2One('product.uom',
                           'Unit',
                           states={
                               'required': Bool(Eval('quantity', 0)),
                           },
                           depends=['quantity'])
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
                                  'on_change_with_unit_digits')
    unit_category = fields.Function(
        fields.Many2One('product.uom.category', 'Unit Category'),
        'on_change_with_unit_category')
    products = fields.Many2Many('sale.promotion-product.product',
                                'promotion',
                                'product',
                                'Products',
                                domain=[
                                    ('default_uom_category', '=',
                                     Eval('unit_category')),
                                ],
                                depends=['unit_category'])
    formula = fields.Char(
        'Formula',
        required=True,
        help=('Python expression that will be evaluated with:\n'
              '- unit_price: the original unit_price'))

    @classmethod
    def __setup__(cls):
        super(SalePromotion, cls).__setup__()
        cls._error_messages.update({
            'invalid_formula': ('Invalid formula "%(formula)s" '
                                'in promotion "%(promotion)s" '
                                'with exception "%(exception)s".'),
        })

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_active():
        return True

    @fields.depends('unit')
    def on_change_with_unit_digits(self, name=None):
        if self.unit:
            return self.unit.digits
        return 2

    @fields.depends('unit')
    def on_change_with_unit_category(self, name=None):
        if self.unit:
            return self.unit.category.id

    @classmethod
    def validate(cls, promotions):
        super(SalePromotion, cls).validate(promotions)
        for promotion in promotions:
            promotion.check_formula()

    def check_formula(self):
        context = self.get_context_formula(None)
        try:
            if not isinstance(self.get_unit_price(**context), Decimal):
                raise ValueError('Not a Decimal')
        except Exception, exception:
            self.raise_user_error(
                'invalid_formula', {
                    'formula': self.formula,
                    'promotion': self.rec_name,
                    'exception': exception,
                })
Example #19
0
class StockReturn(ModelView):
    "Stock Return"
    __name__ = "stock_return"
    _rec_name = 'stock_return'

    order_category = fields.Selection(
        [('purchase', u'采购'), ('return', u'采退')],
        'order_category',
        states={'readonly': Bool(Eval('stock_return_lines'))},
        required=True)
    categories = fields.Many2One(
        'product.category',
        'Categories',
        select=True,
        required=True,
        states={'readonly': Bool(Eval('stock_return_lines'))})
    order_no_with = fields.Function(
        fields.One2Many('order_no', None, 'order_no_with'),
        'on_change_with_order_no_with')
    order_no = fields.Many2One('order_no',
                               'Order',
                               required=True,
                               select=True,
                               domain=[('id', 'in', Eval('order_no_with'))],
                               depends=['order_no_with'])
    stock_return_lines = fields.One2Many(
        'stock_return_lines',
        '',
        'HrpShipmentReturnLines',
        states={'readonly': Equal(Eval('state'), 'done')})
    state = fields.Selection([('assigned', u'未完成'), ('done', u'已完成')],
                             'State',
                             select=True,
                             required=True)
    confirm = fields.Boolean('confirm', states={
        'readonly': False,
    })

    @staticmethod
    def default_state():
        return 'assigned'

    @staticmethod
    def default_order_category():
        return 'purchase'

    @fields.depends('categories', 'state', 'order_category')
    def on_change_with_order_no_with(self, name=None):
        if self.categories and self.state and self.order_category == 'return':
            ShipmentInReturn = Pool().get('stock.shipment.in.return')
            state = self.state
            if self.state == 'assigned':
                state = 'waiting'
            shipmentinreturn = ShipmentInReturn.search([('categories', '=',
                                                         self.categories.id),
                                                        ('state', '=', state)])
            order_no_id = []
            for i in shipmentinreturn:
                order_no_id.append(i.order_no.id)
            return order_no_id
        if self.categories and self.state and self.order_category == 'purchase':
            PurchaseBills = Pool().get('purchase_bills')
            bills = PurchaseBills.search([('state', '=', self.state),
                                          ('categories', '=', self.categories)
                                          ])
            order_no_id = []
            for i in bills:
                order_no_id.append(i.return_shipment.id)
            return order_no_id

    @fields.depends('categories', 'order_no', 'state', 'stock_return_lines',
                    'confirm', 'order_category')
    def on_change_order_no(self, name=None):
        if self.order_category == 'return' and self.state:
            ShipmentInReturn = Pool().get('stock.shipment.in.return')
            state = self.state
            if self.state == 'assigned':
                state = 'waiting'
            shipmentinreturn = ShipmentInReturn.search([
                ('categories', '=', self.categories),
                ('state', '=', state),
                ('order_no', '=', self.order_no),
            ])
            lines = []
            line = 1
            for each in shipmentinreturn:
                dict = {}
                dict['return_id'] = each.id
                dict['line'] = line
                dict['code'] = each.moves[0].product.code
                try:
                    dict['note'] = each.moves[0].product.template.attach
                except:
                    pass
                dict['lot'] = each.moves[0].lot.id
                dict['shelf_life_expiration_date'] = each.moves[
                    0].lot.shelf_life_expiration_date
                dict['product_name'] = each.moves[0].product.template.name
                dict['drug_specifications'] = each.moves[
                    0].product.template.drug_specifications
                dict['quantity'] = each.moves[0].quantity
                lines.append(dict)
                line += 1
            self.stock_return_lines = lines
        if self.order_category == 'purchase' and self.state:
            PurchaseBills = Pool().get('purchase_bills')
            bills = PurchaseBills.search([('state', '=', self.state),
                                          ('categories', '=', self.categories),
                                          ('return_shipment', '=',
                                           self.order_no)])
            lines = []
            line = 1
            for each in bills:
                dict = {}
                dict['purchase_id'] = each.id
                dict['line'] = line
                dict['code'] = each.product.code
                try:
                    dict['note'] = each.product.template.attach
                except:
                    pass
                dict['lot'] = each.lot.id
                dict[
                    'shelf_life_expiration_date'] = each.lot.shelf_life_expiration_date
                dict['product_name'] = each.product.template.name
                dict[
                    'drug_specifications'] = each.product.template.drug_specifications
                dict['quantity'] = each.shipment_quantity
                lines.append(dict)
                line += 1
            self.stock_return_lines = lines
Example #20
0
# -*- coding: utf-8 -*-
"""
    sale.py

"""
from trytond.pool import PoolMeta, Pool
from trytond.model import fields, ModelView, Workflow
from trytond.pyson import Eval, Bool, And

__all__ = ['Sale', 'SaleConfiguration']
__metaclass__ = PoolMeta

STATES = {
    'readonly': Eval('state') == 'done',
    'required': Bool(Eval('is_dhl_de_shipping')),
}
INTERNATIONAL_STATES = {
    'readonly':
    Eval('state') == 'done',
    'required':
    And(Bool(Eval('is_dhl_de_shipping')),
        Bool(Eval('is_international_shipping'))),
}
INTERNATIONAL_DEPENDS = [
    'state', 'is_international_shipping', 'is_dhl_de_shipping'
]

DHL_DE_PRODUCTS = [
    (None, ''),
    ('BPI', 'Weltpaket'),
    ('EPN', 'DHL Paket'),
Example #21
0
class MechanicalVentilation(ModelSQL, ModelView):
    'Mechanical Ventilation History'
    __name__ = 'gnuhealth.icu.ventilation'

    def mv_duration(self, name):
        # Calculate the Mechanical Ventilation time
        now = datetime.now()
        mv_init = now
        mv_finnish = now

        if self.mv_start:
            mv_init = datetime.strptime(str(self.mv_start),
                                        '%Y-%m-%d %H:%M:%S')

        if self.mv_end:
            mv_finnish = datetime.strptime(str(self.mv_end),
                                           '%Y-%m-%d %H:%M:%S')
            delta = relativedelta(mv_finnish, mv_init)
        else:
            delta = relativedelta(now, mv_init)

        years_months_days = str(delta.years) + 'y ' \
                + str(delta.months) + 'm ' \
                + str(delta.days) + 'd'
        return years_months_days

    name = fields.Many2One('gnuhealth.inpatient.icu',
                           'Patient ICU Admission',
                           required=True)

    ventilation = fields.Selection([(None, ''),
                                    ('none', 'None - Maintains Own'),
                                    ('nppv', 'Non-Invasive Positive Pressure'),
                                    ('ett', 'ETT'),
                                    ('tracheostomy', 'Tracheostomy')],
                                   'Type',
                                   help="NPPV = Non-Invasive Positive "
                                   "Pressure Ventilation, BiPAP-CPAP \n"
                                   "ETT - Endotracheal Tube",
                                   sort=False)

    ett_size = fields.Integer(
        'ETT Size',
        states={'invisible': Not(Equal(Eval('ventilation'), 'ett'))})

    tracheostomy_size = fields.Integer(
        'Tracheostomy size',
        states={'invisible': Not(Equal(Eval('ventilation'), 'tracheostomy'))})

    mv_start = fields.DateTime('From',
                               help="Start of Mechanical Ventilation",
                               required=True)
    mv_end = fields.DateTime('To',
                             help="End of Mechanical Ventilation",
                             states={
                                 'invisible': Bool(Eval('current_mv')),
                                 'required': Not(Bool(Eval('current_mv'))),
                             },
                             depends=['current_mv'])
    mv_period = fields.Function(fields.Char('Duration'), 'mv_duration')
    current_mv = fields.Boolean('Current')
    remarks = fields.Char('Remarks')

    @classmethod
    def __setup__(cls):
        super(MechanicalVentilation, cls).__setup__()
        cls._constraints += [
            ('check_patient_current_mv', 'patient_already_on_mv'),
        ]

        cls._error_messages.update({
            'patient_already_on_mv':
            'Our records indicate that the patient'
            ' is already on Mechanical Ventilation !'
        })

    def check_patient_current_mv(self):
        # Check for only one current mechanical ventilation on patient
        cursor = Transaction().cursor
        cursor.execute(
            "SELECT count(name) "
            "FROM " + self._table + "  \
            WHERE (name = %s AND current_mv)", (str(self.name.id), ))
        if cursor.fetchone()[0] > 1:
            return False
        return True

    @staticmethod
    def default_current_mv():
        return True
Example #22
0
class Sale:
    "Sale"
    __name__ = 'sale.sale'

    is_dhl_de_shipping = fields.Function(
        fields.Boolean('Is Shipping', readonly=True), 'get_is_dhl_de_shipping')

    dhl_de_product_code = fields.Selection(
        DHL_DE_PRODUCTS,
        'DHL DE Product Code',
        states=STATES,
        depends=['state', 'is_dhl_de_shipping'])
    dhl_de_export_type = fields.Selection(DHL_DE_EXPORT_TYPES,
                                          'DHL DE Export Type',
                                          states=INTERNATIONAL_STATES,
                                          depends=INTERNATIONAL_DEPENDS)
    dhl_de_export_type_description = fields.Char(
        'Export Type Description',
        states={
            'required':
            And(
                Eval('state').in_(['confirmed', 'processing', 'done']),
                Bool(Eval('is_dhl_de_shipping')),
                Bool(Eval('is_international_shipping'))),
            'readonly':
            Eval('state') == 'done',
        },
        depends=INTERNATIONAL_DEPENDS)
    dhl_de_terms_of_trade = fields.Selection(DHL_DE_INCOTERMS,
                                             'Terms of Trade (incoterms)',
                                             states=INTERNATIONAL_STATES,
                                             depends=INTERNATIONAL_DEPENDS)

    @classmethod
    def view_attributes(cls):
        return super(Sale, cls).view_attributes() + [
            ('//page[@id="dhl_de"]', 'states', {
                'invisible': ~Bool(Eval('is_dhl_de_shipping'))
            }),
            ('//group[@id="dhl_de_international"]', 'states', {
                'invisible': ~Bool(Eval('is_international_shipping'))
            })
        ]

    @staticmethod
    def default_dhl_de_product_code():
        Config = Pool().get('sale.configuration')
        config = Config(1)
        return config.dhl_de_product_code

    @staticmethod
    def default_dhl_de_export_type():
        Config = Pool().get('sale.configuration')
        config = Config(1)
        return config.dhl_de_export_type

    @staticmethod
    def default_dhl_de_terms_of_trade():
        Config = Pool().get('sale.configuration')
        config = Config(1)
        return config.dhl_de_terms_of_trade

    @classmethod
    @ModelView.button
    @Workflow.transition('quotation')
    def quote(cls, sales):
        """
        Downstream implementation of quote method which provides a default
        value to the dhl_de_export_type field.
        """
        for sale in sales:
            if sale.is_dhl_de_shipping and sale.is_international_shipping:
                sale.set_dhl_de_export_type_description()
                sale.save()

        super(Sale, cls).quote(sales)

    def set_dhl_de_export_type_description(self):
        """
        This method sets a default export type description if none is set
        """
        if self.dhl_de_export_type_description:
            return

        if self.description:
            self.dhl_de_export_type_description = self.description
        else:
            self.dhl_de_export_type_description = ', '.join(
                map(lambda line: line.type == 'line' and line.product.name,
                    self.lines))

    def get_is_dhl_de_shipping(self, name):
        """
        Ascertains if the sale is done using DHL (DE)
        """
        return self.carrier and self.carrier.carrier_cost_method == 'dhl_de'

    @fields.depends('is_dhl_de_shipping', 'carrier')
    def on_change_carrier(self):
        """
        Show/Hide dhl de Tab in view on change of carrier
        """
        super(Sale, self).on_change_carrier()

        self.is_dhl_de_shipping = self.carrier and \
            self.carrier.carrier_cost_method == 'dhl_de' or None

    def _get_shipment_sale(self, Shipment, key):
        """
        Downstream implementation which adds dhl-specific fields to the unsaved
        Shipment record.
        """
        ShipmentOut = Pool().get('stock.shipment.out')

        shipment = super(Sale, self)._get_shipment_sale(Shipment, key)

        if Shipment == ShipmentOut and self.is_dhl_de_shipping:
            shipment.dhl_de_product_code = self.dhl_de_product_code
            shipment.dhl_de_export_type = self.dhl_de_export_type
            shipment.dhl_de_export_type_description = \
                self.dhl_de_export_type_description
            shipment.dhl_de_terms_of_trade = self.dhl_de_terms_of_trade

        return shipment
Example #23
0
class PatientRounding(ModelSQL, ModelView):
    # Nursing Rounding for ICU
    # Inherit and append to the existing model the new functionality for ICU

    'Patient Rounding'
    __name__ = 'gnuhealth.patient.rounding'

    icu_patient = fields.Boolean('ICU',
                                 help='Check this box if this is'
                                 'an Intensive Care Unit rounding.')
    # Neurological assesment
    gcs = fields.Many2One(
        'gnuhealth.icu.glasgow',
        'GCS',
        domain=[('name', '=', Eval('name'))],
        depends=['name'],
    )

    pupil_dilation = fields.Selection([('normal', 'Normal'),
                                       ('miosis', 'Miosis'),
                                       ('mydriasis', 'Mydriasis')],
                                      'Pupil Dilation',
                                      sort=False)

    left_pupil = fields.Integer('L', help="size in mm of left pupil")
    right_pupil = fields.Integer('R', help="size in mm of right pupil")

    anisocoria = fields.Boolean(
        'Anisocoria',
        on_change_with=['left_pupil', 'right_pupil'],
    )

    pupillary_reactivity = fields.Selection([(None, ''), ('brisk', 'Brisk'),
                                             ('sluggish', 'Sluggish'),
                                             ('nonreactive', 'Nonreactive')],
                                            'Pupillary Reactivity',
                                            sort=False)

    pupil_consensual_resp = fields.Boolean(
        'Consensual Response', help="Pupillary Consensual Response")

    # Respiratory assesment
    # Mechanical ventilation information is on the patient ICU general info

    respiration_type = fields.Selection([(None, ''), ('regular', 'Regular'),
                                         ('deep', 'Deep'),
                                         ('shallow', 'Shallow'),
                                         ('labored', 'Labored'),
                                         ('intercostal', 'Intercostal')],
                                        'Respiration',
                                        sort=False)

    oxygen_mask = fields.Boolean('Oxygen Mask')
    fio2 = fields.Integer('FiO2')

    peep = fields.Boolean('PEEP')

    peep_pressure = fields.Integer('cm H2O',
                                   help="Pressure",
                                   states={
                                       'invisible': Not(Bool(Eval('peep'))),
                                       'required': Bool(Eval('peep')),
                                   },
                                   depends=['peep'])

    sce = fields.Boolean('SCE', help="Subcutaneous Emphysema")
    lips_lesion = fields.Boolean('Lips lesion')
    oral_mucosa_lesion = fields.Boolean('Oral mucosa lesion')

    # Chest expansion characteristics
    chest_expansion = fields.Selection([(None, ''),
                                        ('symmetric', 'Symmetrical'),
                                        ('asymmetric', 'Asymmetrical')],
                                       'Expansion',
                                       sort=False)
    paradoxical_expansion = fields.Boolean('Paradoxical',
                                           help="Paradoxical Chest Expansion")
    tracheal_tug = fields.Boolean('Tracheal Tug')

    # Trachea position
    trachea_alignment = fields.Selection([(None, ''), ('midline', 'Midline'),
                                          ('right', 'Deviated right'),
                                          ('left', 'Deviated left')],
                                         'Tracheal alignment',
                                         sort=False)

    # Chest Drainages
    chest_drainages = fields.One2Many('gnuhealth.icu.chest_drainage', 'name',
                                      "Drainages")

    # Chest X-Ray
    xray = fields.Binary('Xray')

    # Cardiovascular assessment

    ecg = fields.Many2One(
        'gnuhealth.icu.ecg',
        'ECG',
        domain=[('name', '=', Eval('name'))],
        depends=['name'],
    )

    venous_access = fields.Selection([(None, ''), ('none', 'None'),
                                      ('central', 'Central catheter'),
                                      ('peripheral', 'Peripheral')],
                                     'Venous Access',
                                     sort=False)

    swan_ganz = fields.Boolean('Swan Ganz',
                               help="Pulmonary Artery Catheterization - PAC -")

    arterial_access = fields.Boolean('Arterial Access')

    dialysis = fields.Boolean('Dialysis')

    edema = fields.Selection([(None, ''), ('none', 'None'),
                              ('peripheral', 'Peripheral'),
                              ('anasarca', 'Anasarca')],
                             'Edema',
                             sort=False)

    # Blood & Skin
    bacteremia = fields.Boolean('Bacteremia')
    ssi = fields.Boolean('Surgery Site Infection')
    wound_dehiscence = fields.Boolean('Wound Dehiscence')
    cellulitis = fields.Boolean('Cellulitis')
    necrotizing_fasciitis = fields.Boolean('Necrotizing fasciitis')

    # Abdomen & Digestive

    vomiting = fields.Selection([(None, ''), ('none', 'None'),
                                 ('vomiting', 'Vomiting'),
                                 ('hematemesis', 'Hematemesis')],
                                'Vomiting',
                                sort=False)

    bowel_sounds = fields.Selection([(None, ''), ('normal', 'Normal'),
                                     ('increased', 'Increased'),
                                     ('decreased', 'Decreased'),
                                     ('absent', 'Absent')],
                                    'Bowel Sounds',
                                    sort=False)

    stools = fields.Selection([(None, ''), ('normal', 'Normal'),
                               ('constipation', 'Constipation'),
                               ('diarrhea', 'Diarrhea'), ('melena', 'Melena')],
                              'Stools',
                              sort=False)

    peritonitis = fields.Boolean('Peritonitis signs')

    def on_change_with_anisocoria(self):
        if (self.left_pupil == self.right_pupil):
            return False
        else:
            return True

    @staticmethod
    def default_pupil_dilation():
        return 'normal'
Example #24
0
class SaleSecondaryMixin:
    __slots__ = ()
    sale_secondary_uom = fields.Many2One('product.uom',
                                         "Sale Secondary UOM",
                                         domain=[
                                             ('category', '!=',
                                              Eval('default_uom_category')),
                                         ],
                                         depends=['default_uom_category'])
    sale_secondary_uom_factor = fields.Float(
        "Sale Secondary UOM Factor",
        digits=uom_conversion_digits,
        states={
            'required': Bool(Eval('sale_secondary_uom')),
            'invisible': ~Eval('sale_secondary_uom'),
        },
        depends=['sale_secondary_uom'],
        help="The coefficient for the formula:\n"
        "1 (sale unit) = coefficient (secondary unit)")
    sale_secondary_uom_rate = fields.Float(
        "Sale Secondary UOM Rate",
        digits=uom_conversion_digits,
        states={
            'required': Bool(Eval('sale_secondary_uom')),
            'invisible': ~Eval('sale_secondary_uom'),
        },
        depends=['sale_secondary_uom'],
        help="The coefficient for the formula:\n"
        "coefficient (sale unit) = 1 (secondary unit)")

    @fields.depends('sale_secondary_uom_factor')
    def on_change_sale_secondary_uom_factor(self):
        if not self.sale_secondary_uom_factor:
            self.sale_secondary_uom_rate = None
        else:
            self.sale_secondary_uom_rate = round(
                1. / self.sale_secondary_uom_factor, uom_conversion_digits[1])

    @fields.depends('sale_secondary_uom_rate')
    def on_change_sale_secondary_uom_rate(self):
        if not self.sale_secondary_uom_rate:
            self.sale_secondary_uom_factor = None
        else:
            self.sale_secondary_uom_factor = round(
                1. / self.sale_secondary_uom_rate, uom_conversion_digits[1])

    @property
    def sale_secondary_uom_normal_rate(self):
        uom = self.sale_secondary_uom
        rate = self.sale_secondary_uom_rate
        if self.sale_uom and rate and uom:
            if self.sale_uom.accurate_field == 'factor':
                rate *= self.sale_uom.factor
            else:
                rate /= self.sale_uom.rate
            if uom.accurate_field == 'factor':
                rate /= uom.factor
            else:
                rate *= uom.rate
        return rate

    @property
    def sale_secondary_uom_normal_factor(self):
        uom = self.sale_secondary_uom
        factor = self.sale_secondary_uom_factor
        if uom and factor and self.sale_uom:
            if uom.accurate_field == 'factor':
                factor *= uom.factor
            else:
                factor /= uom.rate
            if self.sale_uom.accurate_field == 'factor':
                factor /= self.sale_uom.factor
            else:
                factor *= self.sale_uom.rate
        return factor

    @classmethod
    def validate(cls, records):
        super().validate(records)
        for record in records:
            record.check_sale_secondary_uom_factor_and_rate()

    def check_sale_secondary_uom_factor_and_rate(self):
        factor = self.sale_secondary_uom_factor
        rate = self.sale_secondary_uom_rate
        if factor and rate:
            if ((rate != round(1. / factor, uom_conversion_digits[1]))
                    and factor != round(1. / rate, uom_conversion_digits[1])):
                raise UOMValidationError(
                    gettext(
                        'sale_secondary_unit'
                        '.msg_sale_secondary_uom_incompatible_factor_rate',
                        record=self))
Example #25
0
class AddSalePaymentView(BaseCreditCardViewMixin, ModelView):
    """
    View for adding Sale Payments
    """
    __name__ = 'sale.payment.add_view'

    sale = fields.Many2One(
        'sale.sale', 'Sale', required=True, readonly=True
    )

    party = fields.Many2One('party.party', 'Party', readonly=True)
    gateway = fields.Many2One(
        'payment_gateway.gateway', 'Gateway', required=True,
        domain=[('users', '=', Eval('user'))],
        depends=['user']
    )
    currency_digits = fields.Function(
        fields.Integer('Currency Digits'),
        'get_currency_digits'
    )
    method = fields.Function(
        fields.Char('Payment Gateway Method'), 'get_method'
    )
    use_existing_card = fields.Boolean(
        'Use existing Card?', states={
            'invisible': Eval('method') != 'credit_card'
        }, depends=['method']
    )
    payment_profile = fields.Many2One(
        'party.payment_profile', 'Payment Profile',
        domain=[
            ('party', '=', Eval('party')),
            ('gateway', '=', Eval('gateway')),
        ],
        states={
            'required': And(
                Eval('method') == 'credit_card', Bool(Eval('use_existing_card'))
            ),
            'invisible': ~Bool(Eval('use_existing_card'))
        }, depends=['method', 'use_existing_card', 'party', 'gateway']
    )
    amount = fields.Numeric(
        'Amount', digits=(16, Eval('currency_digits', 2)),
        required=True, depends=['currency_digits'],
    )
    reference = fields.Char(
        'Reference', states={
            'invisible': Not(Eval('method') == 'manual'),
        }
    )
    user = fields.Many2One(
        "res.user", "Tryton User", readonly=True
    )

    @classmethod
    def __setup__(cls):
        super(AddSalePaymentView, cls).__setup__()

        INV = Or(
            Eval('method') == 'manual',
            And(
                Eval('method') == 'credit_card',
                Bool(Eval('use_existing_card'))
            )
        )
        STATE1 = {
            'required': And(
                ~Bool(Eval('use_existing_card')),
                Eval('method') == 'credit_card'
            ),
            'invisible': INV
        }
        DEPENDS = ['use_existing_card', 'method']

        cls.owner.states.update(STATE1)
        cls.owner.depends.extend(DEPENDS)
        cls.number.states.update(STATE1)
        cls.number.depends.extend(DEPENDS)
        cls.expiry_month.states.update(STATE1)
        cls.expiry_month.depends.extend(DEPENDS)
        cls.expiry_year.states.update(STATE1)
        cls.expiry_year.depends.extend(DEPENDS)
        cls.csc.states.update(STATE1)
        cls.csc.depends.extend(DEPENDS)
        cls.swipe_data.states = {'invisible': INV}
        cls.swipe_data.depends = ['method']

    def get_currency_digits(self, name):
        return self.sale.currency_digits if self.sale else 2

    def get_method(self, name=None):
        """
        Return the method based on the gateway
        """
        return self.gateway.method

    @fields.depends('gateway')
    def on_change_gateway(self):
        if self.gateway:
            return {
                'method': self.gateway.method,
            }
        return {}
Example #26
0
class Attachment(metaclass=PoolMeta):
    __name__ = 'ir.attachment'

    cryptolog_signer = fields.Many2One(
        'party.party',
        'Cryptolog Signer',
        ondelete='RESTRICT',
        states={'readonly': Bool(Eval('cryptolog_status'))},
        depends=['cryptolog_status'])
    cryptolog_id = fields.Char('Cryptolog ID', readonly=True)
    cryptolog_url = fields.Char('Cryptolog URL', readonly=True)
    cryptolog_status = fields.Selection([
        ('', ''),
        ('issued', 'Issued'),
        ('ready', 'Ready'),
        ('expired', 'Expired'),
        ('canceled', 'Canceled'),
        ('failed', 'Failed'),
        ('completed', 'Completed'),
    ],
                                        'Cryptolog Status',
                                        readonly=True)
    cryptolog_data = fields.Function(
        fields.Binary(
            'Signed Document',
            filename='name',
            states={'invisible': Eval('cryptolog_status') != 'completed'},
            depends=['cryptolog_status']), 'cryptolog_get_documents')
    cryptolog_logs = fields.Text('Cryptolog Logs', readonly=True)

    @classmethod
    def __setup__(cls):
        super(Attachment, cls).__setup__()
        cls._buttons.update({
            'cryptolog_request_transaction': {},
            'cryptolog_get_transaction_info': {}
        })

    @classmethod
    def cryptolog_headers(cls):
        return {'Content-Type': 'application/xml'}

    @classmethod
    def cryptolog_basic_auth(cls):
        assert (config.get(CONFIG_SECTION, 'auth_mode') == 'basic')
        username = config.get(CONFIG_SECTION, 'username')
        assert username
        password = config.get(CONFIG_SECTION, 'password')
        assert password
        return requests.auth.HTTPBasicAuth(username, password)

    def append_log(self, method, response):
        self.cryptolog_logs = self.cryptolog_logs or ''
        self.cryptolog_logs += '%s @ %s\n%s\n\n' % (
            method, datetime.datetime.utcnow(), response)

    @classmethod
    @ModelView.button
    def cryptolog_request_transaction(cls, attachments):
        # for now we support only one record
        attachment, = attachments
        url = config.get(CONFIG_SECTION, 'url')
        assert url
        verify = True
        if config.get(CONFIG_SECTION, 'no_verify') == '1':
            verify = False
        method = 'requester.requestTransaction'
        headers = cls.cryptolog_headers()
        auth = cls.cryptolog_basic_auth()
        signer = attachment.cryptolog_signer
        data = {
            'documents': [{
                'documentType': 'pdf',
                'name': attachment.name,
                'content': xmlrpc.client.Binary(attachment.data)
            }],
            'signers': [{
                'firstname': '',
                'lastname': signer.full_name,
                'emailAddress': signer.email,
                'phoneNum': signer.phone
            }],
            'mustContactFirstSigner':
            True,
            'finalDocSent':
            True
        }

        successURL = config.get(CONFIG_SECTION, 'success-url')
        if successURL is not None:
            successURL = successURL.format(att=attachment)
            data['signers'][0]['successURL'] = successURL

        failURL = config.get(CONFIG_SECTION, 'fail-url')
        if failURL is not None:
            failURL = failURL.format(att=attachment)
            data['signers'][0]['failURL'] = failURL

        cancelURL = config.get(CONFIG_SECTION, 'cancel-url')
        if cancelURL is not None:
            cancelURL = cancelURL.format(att=attachment)
            data['signers'][0]['cancelURL'] = cancelURL

        data = xmlrpc.client.dumps((data, ), method)
        req = requests.post(url,
                            headers=headers,
                            auth=auth,
                            data=data,
                            verify=verify)
        if req.status_code > 299:
            raise Exception(req.content)
        response, _ = xmlrpc.client.loads(req.content)
        attachment.cryptolog_status = 'issued'
        attachment.append_log(method, response)
        attachment.cryptolog_id = response[0]['id']
        attachment.cryptolog_url = response[0]['url']
        attachment.save()

    @classmethod
    @ModelView.button
    def cryptolog_get_transaction_info(cls, attachments):
        attachment, = attachments
        url = config.get(CONFIG_SECTION, 'url')
        assert url
        verify = True
        if config.get(CONFIG_SECTION, 'no_verify') == '1':
            verify = False
        method = 'requester.getTransactionInfo'
        headers = cls.cryptolog_headers()
        auth = cls.cryptolog_basic_auth()
        data = xmlrpc.client.dumps((attachment.cryptolog_id, ), method)
        req = requests.post(url,
                            headers=headers,
                            auth=auth,
                            data=data,
                            verify=verify)
        response, _ = xmlrpc.client.loads(req.content)
        attachment.append_log(method, response)
        attachment.cryptolog_status = response[0]['status']
        attachment.save()

    def cryptolog_get_documents(self, name):
        # tryton trick (extra param on context to retrieve file size)
        if self.cryptolog_id and self.cryptolog_status == 'completed':
            if Transaction().context.get('%s.%s' % (self.__name__, name)) == \
                    'size':
                # does not make sense to retrieve the doc juste for the size
                return 1024
            url = config.get(CONFIG_SECTION, 'url')
            verify = True
            if config.get(CONFIG_SECTION, 'no_verify') == '1':
                verify = False
            method = 'requester.getDocuments'
            headers = self.cryptolog_headers()
            auth = self.cryptolog_basic_auth()
            data = xmlrpc.client.dumps((self.cryptolog_id, ), method)
            req = requests.post(url,
                                headers=headers,
                                auth=auth,
                                data=data,
                                verify=verify)
            response, _ = xmlrpc.client.loads(req.content)
            return response[0][0]['content']
Example #27
0
class View(ModelSQL, ModelView):
    "View"
    __name__ = 'ir.ui.view'
    _rec_name = 'model'
    model = fields.Char('Model',
                        select=True,
                        states={
                            'required': Eval('type') != 'board',
                        })
    priority = fields.Integer('Priority', required=True, select=True)
    type = fields.Selection([
        (None, ''),
        ('tree', 'Tree'),
        ('form', 'Form'),
        ('graph', 'Graph'),
        ('calendar', 'Calendar'),
        ('board', 'Board'),
        ('list-form', "List Form"),
    ],
                            'View Type',
                            select=True,
                            domain=[
                                If(Bool(Eval('inherit')), ('type', '=', None),
                                   ('type', '!=', None)),
                            ],
                            depends=['inherit'])
    type_string = type.translated('type')
    data = fields.Text('Data')
    name = fields.Char('Name',
                       states={
                           'invisible': ~(Eval('module') & Eval('name')),
                       },
                       depends=['module'],
                       readonly=True)
    arch = fields.Function(fields.Text('View Architecture',
                                       states={
                                           'readonly': Bool(Eval('name')),
                                       },
                                       depends=['name']),
                           'get_arch',
                           setter='set_arch')
    inherit = fields.Many2One('ir.ui.view',
                              'Inherited View',
                              select=True,
                              ondelete='CASCADE')
    field_childs = fields.Char('Children Field',
                               states={
                                   'invisible': Eval('type') != 'tree',
                               },
                               depends=['type'])
    module = fields.Char('Module',
                         states={
                             'invisible': ~Eval('module'),
                         },
                         readonly=True)
    domain = fields.Char('Domain',
                         states={
                             'invisible': ~Eval('inherit'),
                         },
                         depends=['inherit'])
    # AKE : Force usage of MemoryCache for non serializable data
    _get_rng_cache = MemoryCache('ir_ui_view.get_rng')

    @classmethod
    def __setup__(cls):
        super(View, cls).__setup__()
        cls._order.insert(0, ('priority', 'ASC'))
        cls._buttons.update({
            'show': {
                'readonly': Eval('type') != 'form',
                'depends': ['type'],
            },
        })

    @staticmethod
    def default_priority():
        return 16

    @staticmethod
    def default_module():
        return Transaction().context.get('module') or ''

    def get_rec_name(self, name):
        return '%s (%s)' % (self.model, self.inherit.rec_name
                            if self.inherit else self.type_string)

    @classmethod
    @ModelView.button_action('ir.act_view_show')
    def show(cls, views):
        pass

    @classmethod
    @lru_cache(maxsize=10)
    def get_rng(cls, type_):
        key = (cls.__name__, type_)
        rng = cls._get_rng_cache.get(key)
        if rng is None:
            if type_ == 'list-form':
                type_ = 'form'
            rng_name = os.path.join(os.path.dirname(__file__), type_ + '.rng')
            with open(rng_name, 'rb') as fp:
                rng = etree.fromstring(fp.read())
            cls._get_rng_cache.set(key, rng)
        return rng

    @property
    def rng_type(self):
        if self.inherit:
            return self.inherit.rng_type
        return self.type

    @classmethod
    def validate(cls, views):
        super(View, cls).validate(views)
        cls.check_xml(views)

    @classmethod
    def check_xml(cls, views):
        "Check XML"
        for view in views:
            if not view.arch:
                continue
            xml = view.arch.strip()
            if not xml:
                continue
            try:
                try:
                    encoded = xml.encode('utf-8')
                except UnicodeEncodeError:
                    encoded = xml
                tree = etree.fromstring(encoded)
            except Exception:
                # JCA : print faulty xml
                try:
                    import pprint
                    pprint.pprint(xml)
                except:
                    print(xml)
                raise

            if hasattr(etree, 'RelaxNG'):
                validator = etree.RelaxNG(etree=cls.get_rng(view.rng_type))
                if not validator.validate(tree):
                    error_log = '\n'.join(
                        map(str, validator.error_log.filter_from_errors()))
                    raise XMLError(
                        gettext('ir.msg_view_invalid_xml', name=view.rec_name),
                        error_log)
            root_element = tree.getroottree().getroot()

            # validate pyson attributes
            validates = {
                'states': fields.states_validate,
            }

            def encode(element):
                for attr in ('states', 'domain', 'spell', 'colors'):
                    if not element.get(attr):
                        continue
                    try:
                        value = PYSONDecoder().decode(element.get(attr))
                        validates.get(attr, lambda a: True)(value)
                    except Exception as e:
                        error_log = '%s: <%s %s="%s"/>' % (
                            e, element.get('id')
                            or element.get('name'), attr, element.get(attr))
                        raise XMLError(
                            gettext('ir.msg_view_invalid_xml',
                                    name=view.rec_name), error_log) from e
                for child in element:
                    encode(child)

            encode(root_element)

    def get_arch(self, name):
        value = None
        if self.name and self.module:
            path = os.path.join(self.module, 'view', self.name + '.xml')
            try:
                with file_open(path,
                               subdir='modules',
                               mode='r',
                               encoding='utf-8') as fp:
                    value = fp.read()
            except IOError:
                pass
        if not value:
            value = self.data
        return value

    @classmethod
    def set_arch(cls, views, name, value):
        cls.write(views, {'data': value})

    @classmethod
    def delete(cls, views):
        super(View, cls).delete(views)
        # Restart the cache
        ModelView._fields_view_get_cache.clear()

    @classmethod
    def create(cls, vlist):
        views = super(View, cls).create(vlist)
        # Restart the cache
        ModelView._fields_view_get_cache.clear()
        return views

    @classmethod
    def write(cls, views, values, *args):
        super(View, cls).write(views, values, *args)
        # Restart the cache
        ModelView._fields_view_get_cache.clear()
Example #28
0
_CT_PARTY_STATES = {
    'invisible': Not(In(Eval('ct_kind_analytic', ''), _PARTY + _PRODUCT)),
}

_DT_PRODUCT_STATES = {
    'invisible': Not(In(Eval('dt_kind_analytic', ''), _PRODUCT)),
}

_CT_PRODUCT_STATES = {
    'invisible': Not(In(Eval('ct_kind_analytic', ''), _PRODUCT)),
}

_ACC_PRODUCT_STATES = {
    'required':
    Bool(Eval('product')),
    'invisible':
    And(Not(In(Eval('dt_kind_analytic', ''), _PRODUCT)),
        Not(In(Eval('ct_kind_analytic', ''), _PRODUCT))),
}


class LineWest(ModelSQL, ModelView):
    "Lines of business operations west standart"
    _name = "ekd.account.move.line.west"
    _description = __doc__
    _inherits = {'ekd.account.move.line': 'line'}

    line = fields.Many2One('ekd.account.move.line',
                           'Entries',
                           ondelete="CASCADE",
Example #29
0
class Category:
    __metaclass__ = PoolMeta
    __name__ = 'product.category'
    customs = fields.Boolean('Customs',
                             select=True,
                             states={
                                 'readonly':
                                 Bool(Eval('childs', [0]))
                                 | Bool(Eval('parent')),
                             },
                             depends=['parent'])
    tariff_codes_parent = fields.Boolean(
        "Use Parent's Tariff Codes",
        states={
            'invisible': ~Eval('customs', False),
        },
        depends=['customs'],
        help='Use the tariff codes defined on the parent category')
    tariff_codes = fields.One2Many('product-customs.tariff.code',
                                   'product',
                                   'Tariff Codes',
                                   order=[('sequence', 'ASC'), ('id', 'ASC')],
                                   states={
                                       'invisible':
                                       (Eval('tariff_codes_parent', False)
                                        | ~Eval('customs', False)),
                                   },
                                   depends=['tariff_codes_parent', 'customs'])

    @classmethod
    def __setup__(cls):
        super(Category, cls).__setup__()
        cls.parent.domain = [('customs', '=', Eval('customs', False)),
                             cls.parent.domain or []]
        cls.parent.depends.append('customs')
        cls.parent.states['required'] = Or(
            cls.parent.states.get('required', False),
            Eval('tariff_codes_parent', False))
        cls.parent.depends.append('tariff_codes_parent')

    @classmethod
    def default_customs(cls):
        return False

    @classmethod
    def default_tariff_codes_parent(cls):
        return False

    @fields.depends('parent', 'customs')
    def on_change_with_customs(self):
        if self.parent:
            return self.parent.customs
        return self.customs

    def get_tariff_code(self, pattern):
        if not self.tariff_codes_parent:
            for link in self.tariff_codes:
                if link.tariff_code.match(pattern):
                    return link.tariff_code
        else:
            return self.parent.get_tariff_code(pattern)

    @classmethod
    def view_attributes(cls):
        return super(Category, cls).view_attributes() + [
            ('/form/notebook/page[@id="customs"]', 'states', {
                'invisible': ~Eval('customs', False),
            }),
        ]

    @classmethod
    def delete(cls, categories):
        pool = Pool()
        Product_TariffCode = pool.get('product-customs.tariff.code')
        products = [str(t) for t in categories]

        super(Category, cls).delete(categories)

        for products in grouped_slice(products):
            product_tariffcodes = Product_TariffCode.search([
                'product',
                'in',
                list(products),
            ])
            Product_TariffCode.delete(product_tariffcodes)
class ProductProductAttribute(ModelSQL, ModelView):
    "Product's Product Attribute"
    __name__ = 'product.product.attribute'

    template = fields.Many2One("product.template",
                               "Template",
                               required=True,
                               select=True,
                               ondelete='CASCADE',
                               domain=[
                                   If(Bool(Eval('product')),
                                      ('products', '=', Eval('product')), ()),
                               ],
                               depends=['product'])
    product = fields.Many2One("product.product",
                              "Variant",
                              select=True,
                              domain=[
                                  If(Bool(Eval('template')),
                                     ('template', '=', Eval('template')), ()),
                              ],
                              depends=['template'])

    attribute = fields.Many2One("product.attribute",
                                "Attribute",
                                required=True,
                                select=True,
                                domain=[('sets', '=', Eval('attribute_set'))],
                                depends=['attribute_set'],
                                ondelete='RESTRICT')

    attribute_type = fields.Function(
        fields.Selection(ATTRIBUTE_TYPES, "Attribute Type"),
        'get_attribute_type')

    attribute_set = fields.Function(fields.Many2One("product.attribute.set",
                                                    "Attribute Set"),
                                    'on_change_with_attribute_set',
                                    searcher='search_attribute_set')

    value = fields.Function(fields.Char('Attribute Value'), getter='get_value')

    value_char = fields.Char("Value Char",
                             translate=True,
                             states={
                                 'required': Eval('attribute_type') == 'char',
                                 'invisible':
                                 ~(Eval('attribute_type') == 'char'),
                             },
                             depends=['attribute_type'])
    value_numeric = fields.Numeric("Value Numeric",
                                   states={
                                       'required':
                                       Eval('attribute_type') == 'numeric',
                                       'invisible':
                                       ~(Eval('attribute_type') == 'numeric'),
                                   },
                                   depends=['attribute_type'])
    value_float = fields.Float("Value Float",
                               states={
                                   'required':
                                   Eval('attribute_type') == 'float',
                                   'invisible':
                                   ~(Eval('attribute_type') == 'float'),
                               },
                               depends=['attribute_type'])

    value_selection = fields.Many2One(
        "product.attribute.selection_option",
        "Value Selection",
        domain=[('attribute', '=', Eval('attribute'))],
        states={
            'required': Eval('attribute_type') == 'selection',
            'invisible': ~(Eval('attribute_type') == 'selection'),
        },
        depends=['attribute', 'attribute_type'],
        ondelete='RESTRICT')

    value_boolean = fields.Boolean("Value Boolean",
                                   states={
                                       'required':
                                       Eval('attribute_type') == 'boolean',
                                       'invisible':
                                       ~(Eval('attribute_type') == 'boolean'),
                                   },
                                   depends=['attribute_type'])
    value_integer = fields.Integer("Value Integer",
                                   states={
                                       'required':
                                       Eval('attribute_type') == 'integer',
                                       'invisible':
                                       ~(Eval('attribute_type') == 'integer'),
                                   },
                                   depends=['attribute_type'])
    value_date = fields.Date("Value Date",
                             states={
                                 'required': Eval('attribute_type') == 'date',
                                 'invisible':
                                 ~(Eval('attribute_type') == 'date'),
                             },
                             depends=['attribute_type'])
    value_datetime = fields.DateTime(
        "Value Datetime",
        states={
            'required': Eval('attribute_type') == 'datetime',
            'invisible': ~(Eval('attribute_type') == 'datetime'),
        },
        depends=['attribute_type'])

    @fields.depends('product', '_parent_product.template', 'attribute_set')
    def on_change_product(self):
        if self.product:
            self.template = self.product.template
            self.attribute_set = self.template.attribute_set

    @fields.depends('attribute')
    def on_change_attribute(self):
        self.attribute_type = self.get_attribute_type()

    def get_attribute_type(self, name=None):
        """
        Returns type of attribute
        """
        if self.attribute:
            return self.attribute.type_

    def get_value(self, name=None):
        """
        Consolidated method to return attribute value
        """
        if self.attribute_type == 'selection':
            return self.value_selection.name
        if self.attribute_type == 'datetime':
            # XXX: Localize to the timezone in context
            return self.value_datetime.strftime("%Y-%m-%d %H:%M:%S")
        if self.attribute_type == 'date':
            return datetime.combine(self.value_date, time()). \
                strftime("%Y-%m-%d")
        else:
            return getattr(self, 'value_' + self.attribute_type)

    @fields.depends('template', '_parent_template.id',
                    '_parent_template.attribute_set')
    def on_change_with_attribute_set(self, name=None):
        """
        Returns attribute set for corresponding product's template
        """
        if self.template and self.template.attribute_set:
            return self.template.attribute_set.id

    @classmethod
    def search_attribute_set(cls, name, clause):
        return [
            (('template.attribute_set', ) + tuple(clause[1:])),
        ]